Record string response from keyboard and use Enter key to continue loop

OS: Windows 10

PsychoPy version: 1.90

Hi All!

In my experiment, using the keyboard subjects need to enter string items (e.g. architect, scientist) as responses after viewing a picture and then they press “Enter” on keyboard to view the next picture (I have programmed the task into a sequential block). And following are some if conditions that I need to implement in the routine:

  1. If the subject presses the “Enter” key without entering any letter, then they cannot proceed to the next trial.

  2. If the subject presses the “Enter” key after entering at least one letter and this happens within the first 0.5 seconds of the trial then they will receive a text feedback asking them to wait for the stimulus. This feedback text stays on screen for 2 seconds and then subject proceeds to the next trial.

  3. If the subject presses the “Enter” key after entering at least one letter and this happens after 6 seconds of the trial duration then they will receive a text feedback asking them to respond quicker in next trial. This feedback text stays on screen for 2 seconds and then subject proceeds to the next trial.

I primarily used Builder and added some codes in the Coder but I don’t get the desired output. Instead, the feedback to make quick response is shown at the same time as the picture and even after pressing the enter key, I cannot proceed to the next trial.

Following is the code (please bear in mind I am using a sequential loop for the presentation of images):

# set up handler to look after randomisation of conditions etc
prac_tr = data.TrialHandler(nReps=1, method='sequential', 
extraInfo=expInfo, originPath=-1,
trialList=data.importConditions(u'prac_trials.csv'),
seed=None, name='prac_tr')
thisExp.addLoop(prac_tr)  # add the loop to the experiment
thisPrac_tr = prac_tr.trialList[0]  # so we can initialise stimuli with 
some values
# abbreviate parameter names if possible (e.g. rgb = 
thisPrac_tr.rgb)
if thisPrac_tr != None:
for paramName in thisPrac_tr:
    exec('{} = thisPrac_tr[paramName]'.format(paramName))

for thisPrac_tr in prac_tr:
currentLoop = prac_tr
# abbreviate parameter names if possible (e.g. rgb = 
thisPrac_tr.rgb)
if thisPrac_tr != None:
    for paramName in thisPrac_tr:
        exec('{} = thisPrac_tr[paramName]'.format(paramName))

# ------Prepare to start Routine "prac"-------
t = 0
pracClock.reset()  # clock
frameN = -1
continueRoutine = True
# update component parameters for each repeat
prac_faces.setOpacity(1)
prac_faces.setPos((0, 0))
prac_faces.setOri(0)
prac_faces.setImage(fix_face)
prac_faces.setSize((1, 1))
key_resp_2 = event.BuilderKeyResponse()
key_resp_3 = event.BuilderKeyResponse()
prac_fast.setColor(u'red', colorSpace='rgb')
prac_fast.setPos((0, 0))
prac_fast.setText(u'Please try to respond faster!')
prac_fast.setFont(u'Arial')
prac_fast.setHeight(0.1)
prac_slow.setColor(u'red', colorSpace='rgb')
prac_slow.setPos((0, 0))
prac_slow.setText(u'Please wait for the stimulus!')
prac_slow.setFont(u'Arial')
prac_slow.setHeight(0.1)
prac_blank.setColor(u'white', colorSpace='rgb')
prac_blank.setPos((0, 0))
prac_blank.setText(u'')
prac_blank.setFont(u'Arial')
prac_blank.setHeight(0.1)
# keep track of which components have finished
pracComponents = [prac_faces, prac_text, key_resp_2, key_resp_3, prac_fast, prac_slow, prac_blank]
for thisComponent in pracComponents:
    if hasattr(thisComponent, 'status'):
        thisComponent.status = NOT_STARTED

# -------Start Routine "prac"-------
while continueRoutine:
    # get current time
    t = pracClock.getTime()
    frameN = frameN + 1  # number of completed frames (so 0 is the first frame)
    # update/draw components on each frame
    
    # *prac_faces* updates
    if t >= 0.0 and prac_faces.status == NOT_STARTED:
        # keep track of start time/frame for later
        prac_faces.tStart = t
        prac_faces.frameNStart = frameN  # exact frame index
        prac_faces.setAutoDraw(True)
    if prac_faces.status == STARTED and bool(key_resp_3.status == STOPPED):
        prac_faces.setAutoDraw(False)
    
    # *prac_text* updates
    if t >= 2 and prac_text.status == NOT_STARTED:
        # keep track of start time/frame for later
        prac_text.tStart = t
        prac_text.frameNStart = frameN  # exact frame index
        prac_text.setAutoDraw(True)
    if prac_text.status == STARTED and bool(key_resp_3.status == STOPPED):
        prac_text.setAutoDraw(False)
    
    # *key_resp_2* updates
    if t >= 2 and key_resp_2.status == NOT_STARTED:
        # keep track of start time/frame for later
        key_resp_2.tStart = t
        key_resp_2.frameNStart = frameN  # exact frame index
        key_resp_2.status = STARTED
        # keyboard checking is just starting
        win.callOnFlip(key_resp_2.clock.reset)  # t=0 on next screen flip
        event.clearEvents(eventType='keyboard')
    if key_resp_2.status == STARTED and bool(key_resp_3.status == STOPPED):
        key_resp_2.status = STOPPED
    if key_resp_2.status == STARTED:
        theseKeys = event.getKeys(keyList=['q', 'w', 'r', 't', 'y', 'u', 'i', 'o', 'p', 'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', 'z', 'x', 'c', 'v', 'b', 'n', 'm'])
        
        # check for quit:
        if "escape" in theseKeys:
            endExpNow = True
        if len(theseKeys) > 0:  # at least one key was pressed
            key_resp_2.keys.extend(theseKeys)  # storing all keys
            key_resp_2.rt.append(key_resp_2.clock.getTime())
    
    # *key_resp_3* updates
    if t >= 0.0 and key_resp_3.status == NOT_STARTED:
        # keep track of start time/frame for later
        key_resp_3.tStart = t
        key_resp_3.frameNStart = frameN  # exact frame index
        key_resp_3.status = STARTED
        # keyboard checking is just starting
        win.callOnFlip(key_resp_3.clock.reset)  # t=0 on next screen flip
        event.clearEvents(eventType='keyboard')
    if key_resp_3.status == STARTED:
        theseKeys = event.getKeys(keyList=['return'])
        
        # check for quit:
        if "escape" in theseKeys:
            endExpNow = True
        if len(theseKeys) > 0:  # at least one key was pressed
            key_resp_3.keys = theseKeys[-1]  # just the last key pressed
            key_resp_3.rt = key_resp_3.clock.getTime()
            key_resp_3.status == STOPPED
    
    if key_resp_2.keys in ['', [], None] and key_resp_3.keys != None:
        continueRoutine = True
    
    # *prac_fast* updates
    if (key_resp_3 == STOPPED and key_resp_3.rt > 6.0) and prac_fast.status == NOT_STARTED:
        prac_fast.status == STARTED
        # keep track of start time/frame for later
        prac_fast.tStart = t
        prac_fast.frameNStart = frameN  # exact frame index
        prac_fast.setAutoDraw(True)
    if prac_fast.status == STARTED and t >= (prac_fast.tStart + 2):
        prac_fast.setAutoDraw(False)
    
    # *prac_slow* updates
    if (key_resp_3.status == STOPPED and key_resp_3.rt < 0.5) and prac_slow.status == NOT_STARTED:
        prac_slow.status == STARTED
        # keep track of start time/frame for later
        prac_slow.tStart = t
        prac_slow.frameNStart = frameN  # exact frame index
        prac_slow.setAutoDraw(True)
    if prac_slow.status == STARTED and t >= (prac_slow.tStart + 2):
        prac_slow.setAutoDraw(False)
    
    # *prac_blank* updates
    if t >= key_resp_3.status == STOPPED and prac_blank.status == NOT_STARTED:
        # keep track of start time/frame for later
        prac_blank.tStart = t
        prac_blank.frameNStart = frameN  # exact frame index
        prac_blank.setAutoDraw(True)
    frameRemains = key_resp_3.status == STOPPED + 0.5- win.monitorFramePeriod * 0.75  # most of one frame period left
    if prac_blank.status == STARTED and t >= frameRemains:
        prac_blank.setAutoDraw(False)
        continueRoutine = False
    
    # 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 pracComponents:
        if hasattr(thisComponent, "status") and thisComponent.status != FINISHED:
            continueRoutine = True
            break  # at least one component has not yet finished
    
    # check for quit (the Esc key)
    if endExpNow or event.getKeys(keyList=["escape"]):
        core.quit()
    
    # refresh the screen
    if continueRoutine:  # don't flip if this routine is over or we'll get a blank screen
        win.flip()

# -------Ending Routine "prac"-------
for thisComponent in pracComponents:
    if hasattr(thisComponent, "setAutoDraw"):
        thisComponent.setAutoDraw(False)
# check responses
if key_resp_2.keys in ['', [], None]:  # No response was made
    key_resp_2.keys=None
prac_tr.addData('key_resp_2.keys',key_resp_2.keys)
if key_resp_2.keys != None:  # we had a response
    prac_tr.addData('key_resp_2.rt', key_resp_2.rt)
# check responses
if key_resp_3.keys in ['', [], None]:  # No response was made
    key_resp_3.keys=None
prac_tr.addData('key_resp_3.keys',key_resp_3.keys)
if key_resp_3.keys != None:  # we had a response
    prac_tr.addData('key_resp_3.rt', key_resp_3.rt)
# the Routine "prac" was not non-slip safe, so reset the non-slip timer
routineTimer.reset()
thisExp.nextEntry()

Any kind of help is very much appreciated!

Thanks in advance,

Mo

@movivi, try the following, bearing in mind to change the name of your components to suit (or change code to suit). In this example, I have a text component for feedback (feedbackText), a keyboard component (resp; allows any response, does not end the routine on keypress, and stores all keys) and an image component for presenting images (doesnt matter about the name). In the code component add the following to relevant tabs:

###### Begin routine ######

drawFeedback = []
feedbackTextStart = 0
tooSoon = False
tooLate = False

 ###### Each Frame ######
if resp.keys:
        # Scenerio 1
    if resp.keys[0] == 'return':
        resp.keys, resp.rt = [], []  # reset key presses and rts
    if len(resp.keys) > 1:
        # Scenerio 2 
        if resp.keys[1] == 'return' and t <=.5 and not drawFeedback:
            feedbackText.setText("You must wait until stim appears")
            drawFeedback.append(True)
            feedbackTextStart = t + 2  # Set time for feedback
            tooSoon = True
            resp.keys, resp.rt = [], [] # reset key presses and rts
        # Scenerio 3
        elif resp.keys[-1] == 'return' and t >= 6 and not drawFeedback:
            feedbackText.setText("Please respond faster")
            drawFeedback.append(True)
            feedbackTextStart = t + 2
            tooLate = True
        # Scenario - everything fine and end routine
        elif resp.keys[-1] == 'return' and not np.any([tooSoon, tooLate]):
            continueRoutine = False
        
# After feedback drawn for 2 seconds
if feedbackTextStart != 0 and True in drawFeedback:
    if t >= feedbackTextStart:
        feedbackText.setText("")
        # If too soon, carry on and allow more responses
        tooSoon = False
        # End routine if received response but took too long
        if tooLate == True:
            continueRoutine = False



1 Like

Thank you for your help @dvbridges. I tried using your code and now the issue is that the routine is stuck on the first image presented. Regardless of whether I enter other keys before pressing the Enter key, the first image remains on the screen and no feedback is presented.

Following is how I replaced some of my codes with yours. I totally get your logic and I am surprised why I don’t get the desired output:

# set up handler to look after randomisation of conditions etc
prac_tr = data.TrialHandler(nReps=1, method='sequential', 
extraInfo=expInfo, originPath=-1,
trialList=data.importConditions('prac_trials.csv'),
seed=None, name='prac_tr')
thisExp.addLoop(prac_tr)  # add the loop to the experiment
thisPrac_tr = prac_tr.trialList[0]  # so we can initialise stimuli with some values
# abbreviate parameter names if possible (e.g. rgb = thisPrac_tr.rgb)
if thisPrac_tr != None:
for paramName in thisPrac_tr:
    exec('{} = thisPrac_tr[paramName]'.format(paramName))

for thisPrac_tr in prac_tr:
currentLoop = prac_tr
# abbreviate parameter names if possible (e.g. rgb = thisPrac_tr.rgb)
if thisPrac_tr != None:
    for paramName in thisPrac_tr:
        exec('{} = thisPrac_tr[paramName]'.format(paramName))

# ------Prepare to start Routine "prac"-------
t = 0
pracClock.reset()  # clock
frameN = -1
continueRoutine = True
# update component parameters for each repeat
prac_faces.setOpacity(1)
prac_faces.setPos((0, 0))
prac_faces.setSize((1, 1))
prac_faces.setOri(0)
prac_faces.setImage(fix_face)
resp = event.BuilderKeyResponse()
drawFeedback = []
feedbackTextStart = 0
tooSoon = False
tooLate = False
# keep track of which components have finished
pracComponents = [prac_faces, prac_text, resp]
for thisComponent in pracComponents:
    if hasattr(thisComponent, 'status'):
        thisComponent.status = NOT_STARTED

# -------Start Routine "prac"-------
while continueRoutine:
    # get current time
    t = pracClock.getTime()
    frameN = frameN + 1  # number of completed frames (so 0 is the first frame)
    # update/draw components on each frame
    
    # *prac_faces* updates
    if t >= 0.0 and prac_faces.status == NOT_STARTED:
        # keep track of start time/frame for later
        prac_faces.tStart = t
        prac_faces.frameNStart = frameN  # exact frame index
        prac_faces.setAutoDraw(True)
    if prac_faces.status == STARTED and bool(resp.status == STOPPED):
        prac_faces.setAutoDraw(False)
    
    # *prac_text* updates
    if t >= 2 and prac_text.status == NOT_STARTED:
        # keep track of start time/frame for later
        prac_text.tStart = t
        prac_text.frameNStart = frameN  # exact frame index
        prac_text.setAutoDraw(True)
    if prac_text.status == STARTED and bool(resp.status == STOPPED):
        prac_text.setAutoDraw(False)
    
    # *resp* updates
    if t >= 0 and resp.status == NOT_STARTED:
        # keep track of start time/frame for later
        resp.tStart = t
        resp.frameNStart = frameN  # exact frame index
        resp.status = STARTED
        # keyboard checking is just starting
        win.callOnFlip(resp.clock.reset)  # t=0 on next screen flip
    if resp.status == STARTED:
        theseKeys = event.getKeys(keyList=['q', 'w', 'r', 't', 'y', 'u', 'i', 'o', 'p', 'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', 'z', 'x', 'c', 'v', 'b', 'n', 'm', 'return'])
        
        # check for quit:
        if "escape" in theseKeys:
            endExpNow = True
    if resp.keys:
            # Scenerio 1
        if resp.keys[0] == 'return':
            resp.keys, resp.rt = [], []  # reset key presses and rts
        if len(resp.keys) > 1:
            # Scenerio 2 
            if resp.keys[1] == 'return' and t <=.5 and not drawFeedback:
                feedbackText.setText("You must wait until stim appears")
                feedbackText.setColor(u'red', colorSpace='rgb')
                drawFeedback.append(True)
                feedbackTextStart = t + 2  # Set time for feedback
                tooSoon = True
                resp.keys, resp.rt = [], [] # reset key presses and rts
            # Scenerio 3
            elif resp.keys[-1] == 'return' and t >= 6 and not drawFeedback:
                feedbackText.setText("Please respond faster")
                feedbackText.setColor(u'red', colorSpace='rgb')
                drawFeedback.append(True)
                feedbackTextStart = t + 2
                tooLate = True
            # Scenario - everything fine and end routine
            elif resp.keys[-1] == 'return' and not np.any([tooSoon, tooLate]):
                continueRoutine = False
    # After feedback drawn for 2 seconds
    if feedbackTextStart != 0 and True in drawFeedback:
        if t >= feedbackTextStart:
            feedbackText.setText("")
            # If too soon, carry on and allow more responses
            tooSoon = False
            # End routine if received response but took too long
            if tooLate == True:
                continueRoutine = False
    
    # 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 pracComponents:
        if hasattr(thisComponent, "status") and thisComponent.status != FINISHED:
            continueRoutine = True
            break  # at least one component has not yet finished
    
    # check for quit (the Esc key)
    if endExpNow or event.getKeys(keyList=["escape"]):
        core.quit()
    
    # refresh the screen
    if continueRoutine:  # don't flip if this routine is over or we'll get a blank screen
        win.flip()

# -------Ending Routine "prac"-------
for thisComponent in pracComponents:
    if hasattr(thisComponent, "setAutoDraw"):
        thisComponent.setAutoDraw(False)
# check responses
if resp.keys in ['', [], None]:  # No response was made
    resp.keys=None
prac_tr.addData('resp.keys',resp.keys)
if resp.keys != None:  # we had a response
    prac_tr.addData('resp.rt', resp.rt)
# the Routine "prac" was not non-slip safe, so reset the non-slip timer
routineTimer.reset()
thisExp.nextEntry()

I hope I made the changes appropriately. Also not mentioned in my initial post, there is a 500 ms interval between the end of one trial and the beginning of another. If continueRoutine = False based on the keyboard response then I am not sure how to incorporate the 500 ms interval.

Hope this issue is not too complicated to solve!

Mo

@movivi, have you added a text component called “feedbackText”? I cannot see one in the code. For the interval, you could just add an extra routine at the beginning of your loop, with a blank component having a duration of 500ms. Alternatively, look in to using static component here.

Yes, I did the text component during the initialization of the practice block:

# Initialize components for Routine "prac"
pracClock = core.Clock()
prac_faces = visual.ImageStim(
win=win, name='prac_faces',
image='sin', mask=None,
ori=1.0, pos=[0,0], size=1.0,
color=[1,1,1], colorSpace='rgb', opacity=1.0,
flipHoriz=False, flipVert=False,
texRes=128, interpolate=True, depth=0.0)
prac_text = visual.TextStim(win=win, name='prac_text',
text='What is your answer?',
font='Arial',
pos=(0, 0.7), height=0.1, wrapWidth=None, ori=0, 
color='black', colorSpace='rgb', opacity=1, 
languageStyle='LTR',
depth=-1.0);
feedbackText = visual.TextStim(win=win, name='feedbackText',
font='Arial',
pos=(0, 0.0), height=0.1, wrapWidth=None, ori=0, 
color='red', colorSpace='rgb', opacity=1, 
languageStyle='LTR',
depth=-1.0);

Also I tried initializing the text component right before the practice block loop and still it doesn’t show the feedback and stays on the same first image.

Would you mind uploading a minimal (not working) psyexp file?

Also, when I added the text component called feedbackText, it was a Builder component, rather than code. If you have not used a Builder component, the feedback text will not draw unless you tell it to draw.

@dvbridges enclosed are the relevant materials to try running the experiment in the builder mode. I did set the text component to be drawn if the conditions were satisfied still no progress.

psychopy_discourse_prototype.psyexp (8.9 KB)
prac_trials.csv (26 Bytes)

Thanks @movivi, there is not actually a code component in the example you posted. Instead of using your example, take a look at the one I have made, and copy that one - ignore the weird name of the experiment :). Remember to update the conditions file with images you have.

checkingLetters.psyexp (10.0 KB)checkingLetters.xlsx (7.8 KB)

1 Like

Thank you so much @dvbridges! Now it’s perfect!

Mo

Actually while the subject enters their response, I would also like them to see their keyboard input on the screen. I tried adding the following code snippet to my current script but psychopy gives the following error: TypeError: must be str, not list

How can I resolve this issue? Your help is very much appreciated.

    # *resp* updates
    if t >= 0.0 and resp.status == NOT_STARTED:
        # keep track of start time/frame for later
        resp.tStart = t
        resp.frameNStart = frameN  # exact frame index
        resp.status = STARTED
        # keyboard checking is just starting
        win.callOnFlip(resp.clock.reset)  # t=0 on next screen flip
        event.clearEvents(eventType='keyboard')
    if resp.status == STARTED:
        theseKeys = event.getKeys()        

   if resp.keys:
            # Scenerio 1
        if resp.keys[0] == 'return':
            resp.keys, resp.rt = [], []  # reset key presses and rts
        if len(resp.keys) > 1:
            inputText += theseKeys
            msgText.setText = inputText
            # Scenerio 2 
            if resp.keys[1] == 'return' and t <= 1 and not drawFeedback:
                win.flip(clearBuffer=True)
                image.setAutoDraw(False)
                prac_text.setAutoDraw(False)
                feedbackText.setText("Please wait for the stimulus!")
                drawFeedback.append(True)
                feedbackTextStart = t + 2  # Set time for feedback
                tooSoon = True
                resp.keys, resp.rt = [], [] # reset key presses and rts
            # Scenerio 3
            elif resp.keys[-1] == 'return' and t >= 6.5 and not drawFeedback:
                win.flip(clearBuffer=True)
                image.setAutoDraw(False)
                prac_text.setAutoDraw(False)
                feedbackText.setText("Please try to respond faster!")
                drawFeedback.append(True)
                feedbackTextStart = t + 2
                tooLate = True
            # Scenario - everything fine and end routine
            elif resp.keys[-1] == 'return' and not np.any([tooSoon, tooLate]):
                continueRoutine = False
            
    # After feedback drawn for 2 seconds
    if feedbackTextStart != 0 and True in drawFeedback:
        if t >= feedbackTextStart:
            feedbackText.setText("")
    # End routine if received response but took too long
            if tooSoon == True:
                continueRoutine = False
    # End routine if received response but took too long
            if tooLate == True:
                continueRoutine = False
    
    # *msgText* updates
    if t >= 0.0 and tooSoon != True and msgText.status == NOT_STARTED:
        # keep track of start time/frame for later
        msgText.tStart = t
        msgText.frameNStart = frameN  # exact frame index
        msgText.setAutoDraw(True)

Please always provide the full text of an error message. It helps to narrow things down more quickly (e.g. by pointing directly to the line where the error occurred).

So I’m just going to guess that it occurred in this line:

inputText += theseKeys

I guess inputText is a string. theseKeys is a list of strings, even if there was only one response. You can’t add a list to another non-list object. You need to either extract an individual string from the list, or concatenate all strings within a list into a single string. e.g.

inputText += ''.join(theseKeys)
2 Likes

Thanks @Michael for your reply, it works now!