Stop trial from advancing based on user input in PsychoPy

196 views Asked by At

I'm coding an experiment for an EyeTracker machine using PsychoPy.

As suggested in PsychoPy's google group, I've used the Builder View to create the basic structure of the experiment, compiled the script and then added modifications on the Coder View, so the way to handle user's input is the standard generated by PsychoPy. Since I need the user to write his/her input and then advancing to the next question by pressing the Enter key, I have 2 key response objects, so the first one collects user's input, while the second triggers when pressing the Enter key. For example:

        # *B1_P2_key_01* updates
        if t >= 0.0 and B1_P2_key_01.status == NOT_STARTED:
            # keep track of start time/frame for later
            B1_P2_key_01.tStart = t  # underestimates by a little under one frame
            B1_P2_key_01.frameNStart = frameN  # exact frame index
            B1_P2_key_01.status = STARTED
            # keyboard checking is just starting
            win.callOnFlip(B1_P2_key_01.clock.reset)  # t=0 on next screen flip
        if B1_P2_key_01.status == STARTED:
            #theseKeys = event.getKeys(keyList=['1', '2', '3', '4', '5', '6', '7', '8', '9', '0'])
            theseKeys = event.getKeys(keyList=['num_1', 'num_2', 'num_3', 'num_4', 'num_5', 'num_6', 'num_7', 'num_8', 'num_9', 'num_0'])
            # check for quit:
            if "escape" in theseKeys:
                endExpNow = True
            if len(theseKeys) > 0 and b1p2_counter < 3:  # at least one key was pressed
                B1_P2_key_01.keys.extend(theseKeys)  # storing all keys
                B1_P2_key_01.rt.append(B1_P2_key_01.clock.getTime())
                print "accepted key"
                b1p2_counter += 1

        # *B1_P2_key_02* updates
        if t >= 0.0 and B1_P2_key_02.status == NOT_STARTED:
            # keep track of start time/frame for later
            B1_P2_key_02.tStart = t  # underestimates by a little under one frame
            B1_P2_key_02.frameNStart = frameN  # exact frame index
            B1_P2_key_02.status = STARTED
            # keyboard checking is just starting
            win.callOnFlip(B1_P2_key_02.clock.reset)  # t=0 on next screen flip
            event.clearEvents(eventType='keyboard')
        if B1_P2_key_02.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
                B1_P2_key_02.keys = theseKeys[-1]  # just the last key pressed
                B1_P2_key_02.rt = B1_P2_key_02.clock.getTime()
                # a response ends the routine
                msg2='finish_b1p2_'+paths_list[imagecounter]
                print msg2
                #tracker.sendMessage(msg2)
                continueRoutine = False

Please note that I need the user's input to be displayed on the screen as he/she types it, so I've implemented a solution based on the use of another TextStim object, as suggested on the group too:

        # *B1_P2_resp_01* updates
        if t >= 0.0 and B1_P2_resp_01.status == NOT_STARTED:
            # keep track of start time/frame for later
            B1_P2_resp_01.tStart = t  # underestimates by a little under one frame
            B1_P2_resp_01.frameNStart = frameN  # exact frame index
            B1_P2_resp_01.setAutoDraw(True)

        displaystring=" ".join(B1_P2_key_01.keys) #convert list of pressed keys to string
        displaystring=displaystring.replace(' ','') #remove intermediate spaces
        # Do some text cleanup...replacing key names with puntuation and effectively disabling keys like 'back','shift', etc.
        displaystring=displaystring.replace('space',' ') 
        displaystring=displaystring.replace('comma',',')
        displaystring=displaystring.replace('lshift','')
        displaystring=displaystring.replace('rshift','')
        displaystring=displaystring.replace('period','.')
        displaystring=displaystring.replace('back','')
        displaystring=displaystring.replace('num_1','1')
        displaystring=displaystring.replace('num_2','2')
        displaystring=displaystring.replace('num_3','3')
        displaystring=displaystring.replace('num_4','4')
        displaystring=displaystring.replace('num_5','5')
        displaystring=displaystring.replace('num_6','6')
        displaystring=displaystring.replace('num_7','7')
        displaystring=displaystring.replace('num_8','8')
        displaystring=displaystring.replace('num_9','9')
        displaystring=displaystring.replace('num_0','0')
        #displaystring=displaystring.replace('backspace','')
        #set text of AnswerDisplay to modified string
        B1_P2_resp_01.setText(displaystring)
        #print displaystring

Now my issue here is how to prevent the user from advancing if he/she hasn't typed anything, or if the values provided are outside of a given range (example: values can only go from 0 to 100). I'm aware that there are some alternatives out there, but using a structure different from PsychoPy's default one. And I need this input control to be implemented on the currently provided one.

I've tried to handle the Enter event, but the text object still records the enter key being pressed, so it changes its text. I've also tried to control it from the first key_response object, but then the other one in charge of handling the Enter key stops working.

Long story short, I need to set up a condition for the second key_response object (the one handling the Enter key) to decide if the user can advance to the next trial, while sticking to this format, plus not affecting the on-screen display of input.

Help on this subject would be much appreciated.

1

There are 1 answers

2
Jonas Lindeløv On

You haven't included the critical part, which is the loop around your first code snippet. This loop is probably something like

while continueRoutine:
      # Your code here

... which runs the code until continueRoutine is set to False which happens when the user presses return. You want to add one more condition. So something like this:

displaystring = ''  # to prevent carry-over from the last trial in the first iteration of the loop.
continueRoutine = True  # this is probably already set higher up in the script and could be ignored
while continueRoutine and displaystring:
    # your code here

You can be very flexible with these conditions, e.g. if you want a minimum of 5 seconds to pass, you would do

while continueRoutine and displaystring and t < 5: