psychopy.org | Reference | Downloads | Github

Two feedback routines for response option

OS (e.g. Win10): MacBook v10.10.5
PsychoPy version (e.g. 1.84.x): v3.2.4
What are you trying to achieve?:

Hello,

I am creating an experiment, and so far everything has worked but I am stuck on this one part. Basically, I will present images and participants respond by pressing one of the following keys: 1, 2, 3, 4, 5, 6. I want it to be so that if a participant presses keys 1, 2, or 3, they will be shown a text question on the screen for an unlimited amount of time, to which they will answer with the keys of F or R (unlimited response time). After they answer (doesn’t matter which key they select), they will be shown another question and then they have to answer with the keys of Y, N, or I (again the question will be presented for an unlimited amount of time; it is dependent on them responding). Once the participant responds, there will be a 500 ms ‘+’ sign on the screen and the next image in the loop will be presented. If a participant does not select the keys of 1,2, or 3, then the 500 ms ‘+’ sign will be presented, and the next image would be presented right after without these feedback routines. So, I just want these feedback routines to show only when the keys of 1,2, or 3 are pressed.

What I’ve tried to do so far, albeit unsuccessfully, is try to make the keys of 1, 2, and 3 the ‘correct’ answer, and made it so if the participant picks this ‘correct’ answer, they will be redirected to the feedback routine. However, every time I press one of these keys, Psychopy crashes for me. Additionally, when I don’t press these keys, I just get stuck in the ‘+’ ISI until I quit the program. However, the one time, I did get the feedback to show up when I pressed one of the keys, it only showed me the 2nd feedback routine (it completely skipped the first). I think I’m going about this the wrong way, so any help would be appreciated.

Thank you kindly.

Please always give the actual error messages, so we know what the problem is.

Unless you write your own custom code, only a single key press can be the “correct” one. You could do it in the following way, by inserting a code component (from the “custom” component panel) into your feedback routine. In the “begin routine” tab of that component, put something like this:

if '4' in response.keys or '5' in response.keys or '6' in resp.keys:
    continueRoutine = False 

Replace resp with the actual name of your keyboard response component.

In this case, don’t attempt to set which key is correct in the keyboard component: let that be handled by the code above. Just have it so that the valid keys are restricted to ‘1’ through ‘6’ and have it set to “Force end of routine”.

Hey Michael,

Thank you for replying.
I tried your method and this is the error I got:

I also attached how my program’s layout looks.

You still need the .keys bit, i.e. key_resp_2.keys

Thanks, so now it shows the feedback component. But it doesn’t show the SECOND feedback right after the first feedback.

I can’t actually see a second feedback routine in your flow panel, although there does seem to be a tab for a routine called feedback2.

But I’d suggest that you just use exactly the same technique with the second feedback routine.

Hey Michael,

Thank you so much for your help. I very much appreciate it.
Your suggestion was right. The only problem now is that this feedback shows up regardless of what key I press, and I only want these feedback routines to show up ONLY if keys 1, 2, or 3 are pressed. So if I press 4, I just want the next image to show up, and not any feedback routines.

I’m a bit lost here. If that second feedback routine has its own code component inserted into it, containing exactly the same code in its “Begin routine” tab, then it should behave in exactly the same way as the first feedback routine.

i.e. the code in the “begin routine” tab is specific to that particular routine - it won’t affect any other.

So what I want to happen is that if I press the 1, 2 or 3 keys in the test phase, then the feedback routines should show up (which happens). However, if I want to press the 4, 5 or 6 keys, then I just want the next image to show up, and no feedback routines. But what happens is that the feedback routines show up even when I press 4, 5 or 6.

OK, so it basically sounds like that code isn’t doing anything at all, in either of the two routines?

So we need to do some debugging. Please (temporarily) amend the code above, to this:

print('response.keys: ', response.keys)

if '4' in key_resp_2.keys or '5' in key_resp_2.keys or '6' in key_resp_2.keys:
    print('Should NOT show feedback.')
    continueRoutine = False
else
    print('SHOULD show feedback')

and show us a few examples of what the output is.

EDIT Actually, before doing that, it might be useful to show a screenshot of the code component, just to make sure that everything there is what it should be.

Sure. Here are some screenshots:

The second routine works, since it is following the first routine. The problem is that the first routine keeps showing up no matter what key I press.

There is a typo in code_3. Python is case-sensitive, so make sure continueRoutine has a capital R in the middle. That would explain why that code currently has no effect: it is creating a new variable continueroutine that will not be used anywhere else.

I fixed it, but it still shows the feedback when I press 4, 5, 6. :frowning:

OK, the other typo there is that to have any effect continueRoutine has to be set to False rather than True: this variable is always automatically reset to True at the start of the routine, so setting it to True results in no change.

I’m just going to guess at the intended logic here: do you want the feedback to only run if the person pushes 'r' or 'f'? If so, then you need some double negatives:

if not 'r' in key_resp_3.keys and not 'f' in key_resp_3.keys:
    continueRoutine = False

This would be equivalent to:

if '4' in key_resp_3.keys or '5' in key_resp_3.keys or '6' in key_resp_3.keys:
    continueRoutine = False

assuming that r, f, 4, 5, and 6 are the only allowed responses. If not, just use the first one that checks for r and f.

It can take a while to get your head around Boolean logic…

Hey Michael,

So the test is that images will be shown sequentially, and the person rates their confidence in having seen the face before, from a scale of 1 to 6. (1,2,3 means they have seen the face before, and 4,5,6 means the face is new)

If they select 1, 2, or 3 then the feedback will run asking them to select the keys of ‘r’ or ‘f’ (was their decision based on Recollection or Familiarity) and after this selection, the second feedback will run asking for the keys of ‘y’, ‘n’ or ‘i’ (if they saw the face in the same background, to which they answer Yes, No, or I don’t know). Once an answer is made in the first routine, the second routine shows up, and once the second routine’s answer is made, then the next image in the loop shows up.

If however they select 4, 5, or 6, then the image would be new to them, meaning the feedback should not show up (they would not recollect/familiarize with the image, nor would they have seen the face with the same background, since it would be completely new). If they select 4, 5, or 6, then the next image should show up, and the next image show up and so on, until they see an image where they press 1, 2, or 3 (and then the two feedback routines show up…)

The problem I’m running into is that when I press the keys of 4, 5, 6, the feedback1 and then feedback2 still shows up. However, I want the next image to show up in this case, not the feedback1, and in turn, not feedback2. Did this make things clearer? Sorry if it’s a bit annoying.

I tried doing the double negatives, but the routines still show up after 4,5,6 response. I have pasted the script code below for the feedback routine:

# -------Run Routine "test"-------
while continueRoutine and routineTimer.getTime() > 0:
    # get current time
    t = testClock.getTime()
    tThisFlip = win.getFutureFlipTime(clock=testClock)
    tThisFlipGlobal = win.getFutureFlipTime(clock=None)
    frameN = frameN + 1  # number of completed frames (so 0 is the first frame)
    # update/draw components on each frame
    
    # *testimage* updates
    if testimage.status == NOT_STARTED and tThisFlip >= 0.0-frameTolerance:
        # keep track of start time/frame for later
        testimage.frameNStart = frameN  # exact frame index
        testimage.tStart = t  # local t and not account for scr refresh
        testimage.tStartRefresh = tThisFlipGlobal  # on global time
        win.timeOnFlip(testimage, 'tStartRefresh')  # time at next scr refresh
        testimage.setAutoDraw(True)
    if testimage.status == STARTED:
        # is it time to stop? (based on global clock, using actual start)
        if tThisFlipGlobal > testimage.tStartRefresh + 3-frameTolerance:
            # keep track of stop time/frame for later
            testimage.tStop = t  # not accounting for scr refresh
            testimage.frameNStop = frameN  # exact frame index
            win.timeOnFlip(testimage, 'tStopRefresh')  # time at next scr refresh
            testimage.setAutoDraw(False)
    
    # *text_2* updates
    if text_2.status == NOT_STARTED and tThisFlip >= 3-frameTolerance:
        # keep track of start time/frame for later
        text_2.frameNStart = frameN  # exact frame index
        text_2.tStart = t  # local t and not account for scr refresh
        text_2.tStartRefresh = tThisFlipGlobal  # on global time
        win.timeOnFlip(text_2, 'tStartRefresh')  # time at next scr refresh
        text_2.setAutoDraw(True)
    if text_2.status == STARTED:
        # is it time to stop? (based on global clock, using actual start)
        if tThisFlipGlobal > text_2.tStartRefresh + 5-frameTolerance:
            # keep track of stop time/frame for later
            text_2.tStop = t  # not accounting for scr refresh
            text_2.frameNStop = frameN  # exact frame index
            win.timeOnFlip(text_2, 'tStopRefresh')  # time at next scr refresh
            text_2.setAutoDraw(False)
    
    # *key_resp_2* updates
    waitOnFlip = False
    if key_resp_2.status == NOT_STARTED and tThisFlip >= 0-frameTolerance:
        # keep track of start time/frame for later
        key_resp_2.frameNStart = frameN  # exact frame index
        key_resp_2.tStart = t  # local t and not account for scr refresh
        key_resp_2.tStartRefresh = tThisFlipGlobal  # on global time
        win.timeOnFlip(key_resp_2, 'tStartRefresh')  # time at next scr refresh
        key_resp_2.status = STARTED
        # keyboard checking is just starting
        waitOnFlip = True
        win.callOnFlip(key_resp_2.clock.reset)  # t=0 on next screen flip
        win.callOnFlip(key_resp_2.clearEvents, eventType='keyboard')  # clear events on next screen flip
    if key_resp_2.status == STARTED:
        # is it time to stop? (based on global clock, using actual start)
        if tThisFlipGlobal > key_resp_2.tStartRefresh + 8-frameTolerance:
            # keep track of stop time/frame for later
            key_resp_2.tStop = t  # not accounting for scr refresh
            key_resp_2.frameNStop = frameN  # exact frame index
            win.timeOnFlip(key_resp_2, 'tStopRefresh')  # time at next scr refresh
            key_resp_2.status = FINISHED
    if key_resp_2.status == STARTED and not waitOnFlip:
        theseKeys = key_resp_2.getKeys(keyList=['1', '2', '3', '4', '5', '6'], waitRelease=False)
        if len(theseKeys):
            theseKeys = theseKeys[0]  # at least one key was pressed
            
            # check for quit:
            if "escape" == theseKeys:
                endExpNow = True
            key_resp_2.keys = theseKeys.name  # just the last key pressed
            key_resp_2.rt = theseKeys.rt
            # a response ends the routine
            continueRoutine = False
    
    # check for quit (typically the Esc key)
    if endExpNow or defaultKeyboard.getKeys(keyList=["escape"]):
        core.quit()
    
    # check if all components have finished
    if not continueRoutine:  # a component has requested a forced-end of Routine
        break
    continueRoutine = False  # will revert to True if at least one component still running
    for thisComponent in testComponents:
        if hasattr(thisComponent, "status") and thisComponent.status != FINISHED:
            continueRoutine = True
            break  # at least one component has not yet finished
    
    # refresh the screen
    if continueRoutine:  # don't flip if this routine is over or we'll get a blank screen
        win.flip()

# -------Ending Routine "test"-------
for thisComponent in testComponents:
    if hasattr(thisComponent, "setAutoDraw"):
        thisComponent.setAutoDraw(False)
trials2.addData('testimage.started', testimage.tStartRefresh)
trials2.addData('testimage.stopped', testimage.tStopRefresh)
trials2.addData('text_2.started', text_2.tStartRefresh)
trials2.addData('text_2.stopped', text_2.tStopRefresh)
# check responses
if key_resp_2.keys in ['', [], None]:  # No response was made
    key_resp_2.keys = None
trials2.addData('key_resp_2.keys',key_resp_2.keys)
if key_resp_2.keys != None:  # we had a response
    trials2.addData('key_resp_2.rt', key_resp_2.rt)
trials2.addData('key_resp_2.started', key_resp_2.tStartRefresh)
trials2.addData('key_resp_2.stopped', key_resp_2.tStopRefresh)

# ------Prepare to start Routine "feedback"-------
# update component parameters for each repeat
if not '1' in key_resp_2.keys and not '2' in key_resp_2.keys and not '3' in key_resp_2.keys:
    continueRoutine = False 
key_resp_3.keys = []
key_resp_3.rt = []
# keep track of which components have finished
feedbackComponents = [feedback3, key_resp_3]
for thisComponent in feedbackComponents:
    thisComponent.tStart = None
    thisComponent.tStop = None
    thisComponent.tStartRefresh = None
    thisComponent.tStopRefresh = None
    if hasattr(thisComponent, 'status'):
        thisComponent.status = NOT_STARTED
# reset timers
t = 0
_timeToFirstFrame = win.getFutureFlipTime(clock="now")
feedbackClock.reset(-_timeToFirstFrame)  # t0 is time of first possible flip
frameN = -1
continueRoutine = True

# -------Run Routine "feedback"-------
while continueRoutine:
    # get current time
    t = feedbackClock.getTime()
    tThisFlip = win.getFutureFlipTime(clock=feedbackClock)
    tThisFlipGlobal = win.getFutureFlipTime(clock=None)
    frameN = frameN + 1  # number of completed frames (so 0 is the first frame)
    # update/draw components on each frame
    
    # *feedback3* updates
    if feedback3.status == NOT_STARTED and tThisFlip >= 0.0-frameTolerance:
        # keep track of start time/frame for later
        feedback3.frameNStart = frameN  # exact frame index
        feedback3.tStart = t  # local t and not account for scr refresh
        feedback3.tStartRefresh = tThisFlipGlobal  # on global time
        win.timeOnFlip(feedback3, 'tStartRefresh')  # time at next scr refresh
        feedback3.setAutoDraw(True)
    
    # *key_resp_3* updates
    waitOnFlip = False
    if key_resp_3.status == NOT_STARTED and tThisFlip >= 0.0-frameTolerance:
        # keep track of start time/frame for later
        key_resp_3.frameNStart = frameN  # exact frame index
        key_resp_3.tStart = t  # local t and not account for scr refresh
        key_resp_3.tStartRefresh = tThisFlipGlobal  # on global time
        win.timeOnFlip(key_resp_3, 'tStartRefresh')  # time at next scr refresh
        key_resp_3.status = STARTED
        # keyboard checking is just starting
        waitOnFlip = True
        win.callOnFlip(key_resp_3.clock.reset)  # t=0 on next screen flip
        win.callOnFlip(key_resp_3.clearEvents, eventType='keyboard')  # clear events on next screen flip
    if key_resp_3.status == STARTED and not waitOnFlip:
        theseKeys = key_resp_3.getKeys(keyList=['f', 'r'], waitRelease=False)
        if len(theseKeys):
            theseKeys = theseKeys[0]  # at least one key was pressed
            
            # check for quit:
            if "escape" == theseKeys:
                endExpNow = True
            key_resp_3.keys = theseKeys.name  # just the last key pressed
            key_resp_3.rt = theseKeys.rt
            # a response ends the routine
            continueRoutine = False
    
    # check for quit (typically the Esc key)
    if endExpNow or defaultKeyboard.getKeys(keyList=["escape"]):
        core.quit()
    
    # check if all components have finished
    if not continueRoutine:  # a component has requested a forced-end of Routine
        break
    continueRoutine = False  # will revert to True if at least one component still running
    for thisComponent in feedbackComponents:
        if hasattr(thisComponent, "status") and thisComponent.status != FINISHED:
            continueRoutine = True
            break  # at least one component has not yet finished
    
    # refresh the screen
    if continueRoutine:  # don't flip if this routine is over or we'll get a blank screen
        win.flip()

# -------Ending Routine "feedback"-------
for thisComponent in feedbackComponents:
    if hasattr(thisComponent, "setAutoDraw"):
        thisComponent.setAutoDraw(False)
trials2.addData('feedback3.started', feedback3.tStartRefresh)
trials2.addData('feedback3.stopped', feedback3.tStopRefresh)
# check responses
if key_resp_3.keys in ['', [], None]:  # No response was made
    key_resp_3.keys = None
trials2.addData('key_resp_3.keys',key_resp_3.keys)
if key_resp_3.keys != None:  # we had a response
    trials2.addData('key_resp_3.rt', key_resp_3.rt)
trials2.addData('key_resp_3.started', key_resp_3.tStartRefresh)
trials2.addData('key_resp_3.stopped', key_resp_3.tStopRefresh)
# the Routine "feedback" was not non-slip safe, so reset the non-slip timer
routineTimer.reset()

# ------Prepare to start Routine "feedback2_3"-------
# update component parameters for each repeat
if not 'r' in key_resp_3.keys and not 'f' in key_resp_3.keys:
    continueRoutine = False 
key_resp_8.keys = []
key_resp_8.rt = []
# keep track of which components have finished
feedback2_3Components = [text_11, key_resp_8]
for thisComponent in feedback2_3Components:
    thisComponent.tStart = None
    thisComponent.tStop = None
    thisComponent.tStartRefresh = None
    thisComponent.tStopRefresh = None
    if hasattr(thisComponent, 'status'):
        thisComponent.status = NOT_STARTED
# reset timers
t = 0
_timeToFirstFrame = win.getFutureFlipTime(clock="now")
feedback2_3Clock.reset(-_timeToFirstFrame)  # t0 is time of first possible flip
frameN = -1
continueRoutine = True

# -------Run Routine "feedback2_3"-------
while continueRoutine:
    # get current time
    t = feedback2_3Clock.getTime()
    tThisFlip = win.getFutureFlipTime(clock=feedback2_3Clock)
    tThisFlipGlobal = win.getFutureFlipTime(clock=None)
    frameN = frameN + 1  # number of completed frames (so 0 is the first frame)
    # update/draw components on each frame
    
    # *text_11* updates
    if text_11.status == NOT_STARTED and tThisFlip >= 0.0-frameTolerance:
        # keep track of start time/frame for later
        text_11.frameNStart = frameN  # exact frame index
        text_11.tStart = t  # local t and not account for scr refresh
        text_11.tStartRefresh = tThisFlipGlobal  # on global time
        win.timeOnFlip(text_11, 'tStartRefresh')  # time at next scr refresh
        text_11.setAutoDraw(True)
    
    # *key_resp_8* updates
    waitOnFlip = False
    if key_resp_8.status == NOT_STARTED and tThisFlip >= 0.0-frameTolerance:
        # keep track of start time/frame for later
        key_resp_8.frameNStart = frameN  # exact frame index
        key_resp_8.tStart = t  # local t and not account for scr refresh
        key_resp_8.tStartRefresh = tThisFlipGlobal  # on global time
        win.timeOnFlip(key_resp_8, 'tStartRefresh')  # time at next scr refresh
        key_resp_8.status = STARTED
        # keyboard checking is just starting
        waitOnFlip = True
        win.callOnFlip(key_resp_8.clock.reset)  # t=0 on next screen flip
        win.callOnFlip(key_resp_8.clearEvents, eventType='keyboard')  # clear events on next screen flip
    if key_resp_8.status == STARTED and not waitOnFlip:
        theseKeys = key_resp_8.getKeys(keyList=['y', 'n', 'i'], waitRelease=False)
        if len(theseKeys):
            theseKeys = theseKeys[0]  # at least one key was pressed
            
            # check for quit:
            if "escape" == theseKeys:
                endExpNow = True
            key_resp_8.keys = theseKeys.name  # just the last key pressed
            key_resp_8.rt = theseKeys.rt
            # a response ends the routine
            continueRoutine = False
    
    # check for quit (typically the Esc key)
    if endExpNow or defaultKeyboard.getKeys(keyList=["escape"]):
        core.quit()
    
    # check if all components have finished
    if not continueRoutine:  # a component has requested a forced-end of Routine
        break
    continueRoutine = False  # will revert to True if at least one component still running
    for thisComponent in feedback2_3Components:
        if hasattr(thisComponent, "status") and thisComponent.status != FINISHED:
            continueRoutine = True
            break  # at least one component has not yet finished
    
    # refresh the screen
    if continueRoutine:  # don't flip if this routine is over or we'll get a blank screen
        win.flip()

# -------Ending Routine "feedback2_3"-------
for thisComponent in feedback2_3Components:
    if hasattr(thisComponent, "setAutoDraw"):
        thisComponent.setAutoDraw(False)
trials2.addData('text_11.started', text_11.tStartRefresh)
trials2.addData('text_11.stopped', text_11.tStopRefresh)
# check responses
if key_resp_8.keys in ['', [], None]:  # No response was made
    key_resp_8.keys = None
trials2.addData('key_resp_8.keys',key_resp_8.keys)
if key_resp_8.keys != None:  # we had a response
    trials2.addData('key_resp_8.rt', key_resp_8.rt)
trials2.addData('key_resp_8.started', key_resp_8.tStartRefresh)
trials2.addData('key_resp_8.stopped', key_resp_8.tStopRefresh)
# the Routine "feedback2_3" was not non-slip safe, so reset the non-slip timer
routineTimer.reset()
thisExp.nextEntry()

Hey Michael,

I just fixed it. Turns out the 3.2 Psychopy version was the problem. Once I went to 3.1, the continueRoutine script started working! Thank you for your help and patience.

Hi Evi, yes thanks for posting that script, that showed that the issue was a bug released in 3.2.0, where continueRoutine was re-set to True just before a routine, over-riding any custom code. You’ve already identified that this can be addressed by reverting to 3.1.5 or thereabouts, but just posting this link here for other people’s reference:

I should have realised this was your problem, as I’m the one who fixed it in the code repository, a couple of months back! That fix hasn’t yet been officially released, but will be available in the next version of PsychoPy, and it would be worth you upgrading to that for your experiment, as there are many other advantages (in particular, much better timing) compared to version 3.1.

If you want to, you could jump the queue and go straight to the release candidate for the next version. We’ve changed the version numbering from numbers to date-based, so instead of 3.3, it will be known as version 2020.1:

Thank you!