Detect keys being pressed from one routine to another

Hi there,

I’m designing an fMRI task with PsychoPy3 on Windows 10. Right now I’m testing it with a keyboard but I want to adapt it later to a Cedrus Lumina response pad.

In the task, the participant needs to simultaneously press and hold 4 keys to start the experiment and then release and press one of the keys again accordingly to a target position on the screen. This is working well. However, I want to measure if the participant releases any key before the target onset - premature responses.
I defined the 4 keys pressing in one routine and the premature responses in another in the builder.
Can you help me doing this? I search previous messages but couldn’t figure it out with the getKeys function. Please find below what the code looks like. Thank you very much, Sónia

# *key1_baseline1* updates
waitOnFlip = False
if key1_baseline1.status == NOT_STARTED and tThisFlip >= 0-frameTolerance:
    # keep track of start time/frame for later
    key1_baseline1.frameNStart = frameN  # exact frame index
    key1_baseline1.tStart = t  # local t and not account for scr refresh
    key1_baseline1.tStartRefresh = tThisFlipGlobal  # on global time
    win.timeOnFlip(key1_baseline1, 'tStartRefresh')  # time at next scr refresh
    key1_baseline1.status = STARTED
    # keyboard checking is just starting
    waitOnFlip = True
    win.callOnFlip(key1_baseline1.clock.reset)  # t=0 on next screen flip
    win.callOnFlip(key1_baseline1.clearEvents, eventType='keyboard')  # clear events on next screen flip
if key1_baseline1.status == STARTED and not waitOnFlip:
    theseKeys = key1_baseline1.getKeys(keyList=['1', '2', '3', '4'], waitRelease=False)
    _key1_baseline1_allKeys.extend(theseKeys)
    if len(_key1_baseline1_allKeys):
        key1_baseline1.keys = [key.name for key in _key1_baseline1_allKeys]  # storing all keys
        key1_baseline1.rt = [key.rt for key in _key1_baseline1_allKeys]
if len(key1_baseline1.keys) > 3: #change for 4 buttons on cedrus lumina
    continueRoutine = False

#second routine
# *prematureResponses_baseline1* updates
waitOnFlip = False
if prematureResponses_baseline1.status == NOT_STARTED and tThisFlip >= 0.0-frameTolerance:
    # keep track of start time/frame for later
    prematureResponses_baseline1.frameNStart = frameN  # exact frame index
    prematureResponses_baseline1.tStart = t  # local t and not account for scr refresh
    prematureResponses_baseline1.tStartRefresh = tThisFlipGlobal  # on global time
    win.timeOnFlip(prematureResponses_baseline1, 'tStartRefresh')  # time at next scr refresh
    prematureResponses_baseline1.status = STARTED
    # keyboard checking is just starting
    waitOnFlip = True
    win.callOnFlip(prematureResponses_baseline1.clock.reset)  # t=0 on next screen flip
    win.callOnFlip(prematureResponses_baseline1.clearEvents, eventType='keyboard')  # clear events on next screen flip
if prematureResponses_baseline1.status == STARTED:
    # is it time to stop? (based on global clock, using actual start)
    if tThisFlipGlobal > prematureResponses_baseline1.tStartRefresh + cueTime_bas-frameTolerance:
        # keep track of stop time/frame for later
        prematureResponses_baseline1.tStop = t  # not accounting for scr refresh
        prematureResponses_baseline1.frameNStop = frameN  # exact frame index
        win.timeOnFlip(prematureResponses_baseline1, 'tStopRefresh')  # time at next scr refresh
        prematureResponses_baseline1.status = FINISHED
if prematureResponses_baseline1.status == STARTED and not waitOnFlip:
    theseKeys = prematureResponses_baseline1.getKeys(keyList=None, waitRelease=False)
    _prematureResponses_baseline1_allKeys.extend(theseKeys)
    if len(_prematureResponses_baseline1_allKeys):
        prematureResponses_baseline1.keys = _prematureResponses_baseline1_allKeys[-1].name  # just the last key pressed
        prematureResponses_baseline1.rt = _prematureResponses_baseline1_allKeys[-1].rt```

Hi there,

Please can you share the line where you define:

A link that might be helpful is this one Measuring key lift time relative to stimulus onset (I was trying to achieve a similar thing!)

Essentially what you want to do is retrieve the ‘duration’ attribute of your keypresses - which should tell you if anything was released prematurely!

Hope this helps,

Becca

Hi Becca,

Thank you for answering.

I defined key1_baseline1 as key1_baseline1 = keyboard.Keyboard().

I already read your previous post on the forum but here I have a slight different problem because the participant presses the 4 buttons in the first routine (the buttons press ends the routine) and he keeps the buttons pressed until the second routine where I want to measure premature responses. So, I need to read key releases here but without the subject pressing the keys again. Since the first routine ends after the 4 buttons are pressed, I cannot gather more information about keys release here. So I need a way to read what keys are being pressed when I start the second routine.

Thank you.
Kind regards,
Sónia

Hi Sónia,

I see what you mean (I had not picked up that this your issue is likely referring to code components inside builder - so I missed the vital part about carrying key presses across routines).

This demo should do what you need with a small tweak (I made the demo watching 2 keys not 4, so you would just need to list the 4 keys you want to watch in place of my 2 keys).

The critical thing is when you make your initial getKeys call you must have clear set to ‘False’ and waitRelease set to False (meaning we are not going to wait for a release, but we will later be able to call these key events in later routines - because the buffer hadn’t been cleared).

I hope that you still find this helpful.

multi_press_and_lift.psyexp (11.5 KB)

Becca

Hi Becca,

Thank you so much for the suggestion, I will try as soon as possible.

Merry Christmas :slight_smile:

Sónia

Merry christmas to you to! :slight_smile:

Hi Becca,

Thank you so much for the example, now I can detect premature responses.

However, I found another issue in the first part where the participant needs to hold the 4 buttons simultaneously. Maybe you can also help me with that. If the participant presses each button at a time, the script assumes that the four buttons were pressed and moves on (e.g., simultaneously pressing 1 and 2, then 3 and 4 but releasing 1; at the end, only buttons 2, 3 and 4 are being pressed). But I really need to make sure that the participant is holding the 4 buttons at the same time.

I hope I explained the problem in a clear way.

Thank you and a happy new year!
Sónia

Hi Sónia,

As a start point the Keyboard class returns a variable ‘duration’ if a key has been lifted.

i.e. keys[-1].name corresponds to the name of the last key pressed keys[-1].rt corresponds to the time the key was pressed down and keys[-1].duration corresponds to the time of a key lift.

So, it sounds like you want to check if any of the keys have the attribute duration (so have been lfted), and if so, remove 1 from the count variable. Something like:

if len(keys):
    keysPressed.append(keys[-1]) # add all the key attributes (not just name)
for keyWatched in keysWatched:
    if keyWatched in keysPressed:
        count+=1
    if keyWatched.duration:
        count -=1

Hopefully this will give you a start point to try some things out!

Becca

Hi Becca,

Thank you for your help.
I managed to find a solution based on your tips and it is working perfectly now.
I leave the code here in case someone has the same problem:

#Begin routine
kb= keyboard.Keyboard()
list_keys_pressed = []
keysWatched=['1', '2','3','4']

#Each frame
keys_pressed = kb.getKeys(keysWatched, waitRelease=False, clear=False)
keys_released = kb.getKeys(keysWatched, waitRelease=True)

if keys_pressed:
    if keys_pressed[-1].name not in list_keys_pressed:
        list_keys_pressed.append(keys_pressed[-1].name)
        
if keys_released:
    if keys_released[-1].duration:
        index_release = list_keys_pressed.index(keys_released[-1].name)
        del list_keys_pressed[index_release]

if len(list_keys_pressed) == 4:
    continueRoutine = False

Thank you for all the help!

Kind regards,
Sónia

1 Like

Fabulous, thank-you for sharing your final solution for future users!