I'm new to Python and I'm programming a simple psychology experiment. In a nutshell, I'm presenting participants with a series of randomized images and having them to press one key if they detect a face in a given image.
One of my problems is that the program crashes when participant presses the key too fast - that is, I've noticed that the program logs responses even if the participant is pressing a key when there is no image present. Each image will only be present on the screen for 10 seconds. Participant usually takes ~0.5 second on average to make a response.
Is there a key for me to program the experiment so that that Psychopy will only log key presses ONCE, AFTER image is presented on screen? I've pasted my code below.
Thanks so much.
StimList=['Face1.png','Face2.png',]
StimList.extend(['Noise1.png','Noise2.png'])
# randomize lists:
numpy.random.shuffle(StimList)
outstr=""
for TrialNo in range(len(StimList)):
# load our image:
img=visual.ImageStim(
win=win,
image=StimList[TrialNo],
)
# draw the fixation cross and wait for trial start:
win.flip()
time.sleep(1) # wait 1 second on fixation cross
# start a trial: loop until a key has been pressed (or trial times out)
FaceDetected=0 # same as false
Responded=0 #revise
timer=core.Clock()
timer.reset()
while (not Responded) and (timer.getTime()<TimeOut): #remove not responded
img.draw()# outside loop
win.flip() #outside loop
keys=event.getKeys(keyList=['y','Y', 'n','N'], modifiers=False, timeStamped=timer)
if keys:
if (keys[0][0]=='y') | (keys[0][0]=='Y'):
FaceDetected=True
Responded=True
RT=keys[0][1]
elif (keys[0][0]=='n') | (keys[0][0]=='N'):
FaceDetected=False
Responded=True
RT=keys[0][1]
outstr=outstr+str(TrialNo)+", "+ StimList[TrialNo] +", "+str(FaceDetected)+", "+str(RT)+"\n"
print(outstr)
# first open the file:
outfile=open('tmpdata.csv', 'w')
outfile.write(outstr)
outfile.close()
win.close()
There are a bunch of Python issues with the code above, which I suspect are due to negative transfer from another programming language. For example, in Python you should use or in logical comparisons, not |, which in Python is the operator for bitwise 'OR', a different beast. Also, you might want to try out the more Pythonic for TrialNo, stimulus in enumerate(StimList): in place of for TrialNo in range(len(StimList)):, and avoid standard Python functions like time.sleep() when you could have more precise control using PsychoPy's timing classes or screen refresh counting.
But in PsychoPy API-specific terms relevant to your main question, you need to call event.clearEvents() prior to first drawing your stimulus (e.g. when you reset the trial timer). That will clear the keyboard buffer of any previously-pressed keys.
In further PsychoPy-specific hints, avoid creating objects repeatedly. e.g. the timer only needs to be created once, at the start of the script. Then you just reset it once per trial. At the moment, the reset is actually redundant, as the timer is zeroed when it is created. Timers are simple and multiple creation doesn't really impact performance, but stimuli are more complicated and you should definitely avoid creating them over and over again. e.g. here just create your image stimulus once. Then on each trial, just update its image property. That itself takes time to do, as the file needs to be read from disk. So ideally you would be doing that during the fixation stimulus period, or between trials as it is currently.
Your code really shows a few issues rather than just the one you raised in the question. hence, you might find the forum at https://discourse.psychopy.org more useful than the single question and answer format here at SO.
Related
In a basic graphical application I've been working on, there's a problem where holding down a key sends keyboard input extremely quickly, to the point where trying to slow it down with some kind of cooldown doesn't work.
It does work if the cooldown is substantial (>2 seconds cooldown), which is unacceptable for a lot of games.
For any program that automatically links a keypress to a root.move statement will allow extreme speeds across the screen.
Is there a way to limit keypress inputs? If not, is there a way to remove/cancel out the excessive inputs?
This won't be consistent across platforms or other computers due to different input speeds, but using a simple counter to ensure it only fires once every 3 times is more than enough to slow it down to reasonable speeds.
lcounter=0
def left(event):
global leftv,lcounter
leftv=True
if lcounter==2:
playercoords[0]-=1
render(playercoords[0],playercoords[1]+30)
lcounter=0
else:
lcounter+=1
`
My use case
I need to know when a (specific) key is pressed and held down. The use case after detection is fairly easy. When the key is released, send a signal to stop the callback (which I know already).
Desired behavior
Here is a rough scheme of how the algo looks like:
def the_callback():
if key_held == the_hotkey:
someObj.start() # this class Obj works totally well so no issues here on
elif key_released == the_hotkey:
someObj.stop()
else:
# we don't care. continue looking for Keyboard events
# here any kinda listener or just a loop which passes events to the callback
I should mention that any kinda listener which blocks the execution is okay as it will run in its own thread (already running pynput.keyboard.Listener in a thread so not a problem)
What I've tried
I used pynput and its pynput.keyboard.Listener to detect key-presses and invoke callbacks accordingly but I couldn't make that work to detect when a key is HELD down.
the current solution looks roughly like:
# not real code. just rough scheme
def on_pressed(key):
if key == my_hotkey:
if running_already: # this part works well already
obj.stop()
else:
obj.start()
else:
# we don't care
with pynput.keyboard.Listener(on_press=on_pressed) as listener:
listener.join() # blocking call until SystemExit, `return False` from callback or `listener.stop()`
I have a very strong feeling that I can make this work by adding on_release=another_callback_that_handles_releases (available within pynput.keyboard.listener).
Perhaps by storing the last known pressed keystroke, and checking if the key released was the same as the hotkey which was pressed earlier but I'm not sure how would I go about it and can that even work?
Then I decided to give keyboard (different lib) a go.
I wrote the below code for the same which can detect keys being held down. This below code achieves almost nearly what I want:
import keyboard as kb, time
while 1:
while kb.is_pressed('q'):
print('Key is held')
time.sleep(0.5) # sleep added just to stop it from spamming the stdout
else:
print('No it\'s Not')
time.sleep(0.5)
The issue with this solution is, it's not very well suited for OSX and Ubuntu. And it has some issues working with special keys. Moreover, I have the hotkey stored as pynput.keyboard.Key.f7 (for eg) or pynput.keyboard.KeyCode(char='s') # for character keys and these enums have different values than what keyboard uses to scan key IDs (using keyboard.hook()).
The final question
How should I go about detecting a key being HELD down. I'd prefer to achieve this using pynput as the rest of the code base uses it but 'keyboard is fine too.
Again I have a feeling that using on_press=a_callback and on_release=another_callback this might be achieved but I'm not entirely sure about it. Lastly, the solution is preferred to be cross platform (I'm fine with using three different functions depending on value of platform.system()).
How would you go about achieving it?
EDIT-1
HERE is what I wrote as an attempt (and MCVE) after suggestion by Isak. This works almost perfectly with just 1 flaw. And that is that it doesn't listen to keypresses right from the program start.
It takes some time for some unknown reason before it starts to actually detect any keypress. Good thing is that once it detects the keypress for the first time, it works flawlessly.
What am I missing there?
Try to check for the key_pressed event on the specific key until the event becomes key_released. So when you detect a click on the key you execute your code and when it detects the release of that key the code stops
I figured out why My Approach was taking a lot of time to initialize before starting the Listener. It was because of the while loop which didn't have any time.sleep() calls and it was probably messing with the system (although I wouldn't expect that to happen as it runs in its own thread but probably the while loop doesn't release the GIL as it's just in the loop doing literally nothing without any sort of delay).
I just added time.sleep(0.2) inside the while loop (the outer one). Any delay would do as that would release the GIL for some time and the Listener thread would be processed and made active.
Edit: Accepting the answer by Isak as accepted as that is the correct approach.
I'm setting up an experiment on psychopy in which a stimulus is displayed on the screen for a set period of time (say 0.5s), after which a blank screen is presented with a fixation. I'm using event.waitKeys() in order to get Keyboard input.
I have written the following code.
for i in range (1, 21):
answer = cf.Stimulus() #This is a function for generating the stimulus
img = visual.ImageStim(
win=win,
image="temp.jpg",
units="pix"
)
img.draw() #This is the first screen
fixation.draw()
win.flip()
core.wait(0.5)
fixation.draw() #This is the second screen
win.flip()
keysarray = event.waitKeys()
os.remove('temp.jpg')
The problem which I'm running into here is that, if the keyboard input has been received before the wait time of 0.5 ends in the first screen, events.waitKeys() doesn't register this key entry and still waits on the second screen for the keyboard input. The program only moves forward if a key entry is received for the second screen.
Instead, I want the program to go to the next stimulus whenever a keyboard input is presented between the start of screen 1 to the end of screen 2. That is, if the keyboard input is received in screen 1 itself (before the end of 0.5s), I want the input to be registered and the program to move on to the next stimulus (either by moving to screen 2 for a very short duration of time, or by skipping screen 2 all together). I can't seem to figure out how this can be achieved.
The short answer here is that event.waitKeys() defaults to clearing the event queue, so that only new keypresses are detected. You can get the behaviour you want by overriding that:
keysarray = event.waitKeys(clearEvents=False)
But I think that keys pressed prior to calling the function would not have useful reaction times recorded (although keyboard handling has changed a lot in version 3.1).
Having said that, there are a bunch of other issues with this code that could be improved to fit a more optimal PsychoPy style. I'd suggest posting it at the user forum at https://discourse.psychopy.org. That forum is better suited to back-and-forth discussions than the single question/answer format here at SO.
I want this animation to play with a slight delay inbetween each dripx() command, but I can't delay anything else because the players character is also effected by any sleep/wait commands. (Kinda new to python/pygame so I don't know everything)
def wateranimation():
drip1()
drip2()
drip3()
drip4()
drip5()
drip6()
There are 2 ways to do this:
1) Store the current state, using a class. Every frame you call a specific function with the number of milliseconds since the last frame, and have it figure out if it needs to do anything this frame.
2) Threaded programming. You create a start a thread that just has the job of running that animation, and spends most of its time asleep.
I have a wxPython application with a multi-stage GUI. First a simple form pops for selecting from one of many (> 100) options (it's a part number list with a search box). Once the user has made their selection it builds the appropriate form and shows it, hiding the initial selection dialog. Due to the nature of this project, each secondary form has several matplotlib figures in a Notebook, around 7 or 8 figures each with 2-5 axes each. Because of this, the form takes several seconds between initialization and when it can be shown on the screen.
Does wxPython have a way to build a frame in the background? I don't mind forcing the user to wait a short while before it can be shown, but as it is right now building the form hogs the event loop and everything becomes unresponsive. If I use a thread to build the form, it completes successfully but when I call .Show() nothing happens and there's no error message.
As you can imagine, such a GUI has fairly complex code so it would be difficult to show a SSCCE (and it's not open source). If needed I can try to hack together something that would approximate my problem.
I have used BusyInfo before to tell the user that something is happening. You would put that in your frame's init() BEFORE you actually start creating the matplotlib figures. You can read about it here:
http://wiki.wxpython.org/BusyInfo
Another idea would be to create a second frame with a progressbar in it and a message. The progressbar would be set to just bounce back and forth and when you got done creating the matplot stuff, you would close the second frame.
Mike's idea of using wxBusyInfo is useful when something takes a long time, but it's possible you could make it take less time instead (or at least as well).
First, when inserting many (although I wouldn't say that 100 is that many, 1000 however definitely is) items into a wxChoice, freeze it before adding them -- and thaw it afterwards. This should cut down the time needed for the insertion drastically.
Second, creating all controls of a multi-page wxNotebook (or another wxBookCtrl) can be long, even in C++. So the idea is to not do it immediately but only create the controls of the page you are going to initially show to the user. And then create the other pages controls only when the user is about to select them, i.e. in your wxEVT_BOOKCTRL_PAGE_CHANGING event handler.
If you put this in place, you might not need wxBusyInfo any longer...