psychopy.org | Reference | Downloads | Github

End routine after specified time

Hello everybody,

I am working on a mental rotation task with psychopy builder and everything looks good so far, except one thing: How can you end a looping routine after a specified time?

Background: The task is meant to cause interference for 6 minutes and must not be finished earlier, therefore I added many stimuli and want to end the loop/routine after 360 seconds.

I already tried to insert custom code in the End Routine area (with ‘trials’ being the actual name of the loop):

if t=360
	trials.finished=True

When trying to run the experiment I always get ‘invalid syntax’, is it the t variable or what am I missing? Thank you in advance!

Greetings
Pave

Two things in the first line:

if t >= 360:

if statements end with a colon, and you can’t test for exact values of floating point numbers, as they can’t be exactly represented by computers.

Also, add this:

continueRoutine = False

As ending the loop only takes effect after the routine has ended, so you need to end that early too.

Thank you for your quick reply!

I implemented your suggestions and now I don’t get a syntax error but the routine does not end after 6 minutes…

The code now looks like this:

if t>=360:
    trials.finished=True
    continueRoutine=False

Could it be affected by the nReps value for the loop? Because I set it to 5 to make sure, that no participant will finish the routine before the timer has ended. There are no error prompts or something like that.

Greetings,
Pave

Ahh, OK. The value of t restarts at zero on each each routine, so you need to calculate a time that crosses the boundary of routines. Builder maintains a clock called globalClock that you can use, which keeps counting throughout the experiment. You need to get the value of that clock at the start of the first trial and then check against that later:

Begin routine tab:

if trials.thisN == 0: # only on the first trial
    startTime = globalClock.getTime()

Each frame tab:

if globalClock.getTime() - startTime >= 360:
    trials.finished = True
    continueRoutine = False
1 Like

That works, thanks a lot Michael!

One more minor question: When the time is up the routine ends only if a key is then pressed. Is it possible to force the routine to end without waiting for a keystroke / the next picture?

The continueRoutine = False line should achieve that. I wonder if the keyboard component is interfering with that, by changing the value of continueRoutine back to True. Try shifting the code component relative to the keyboard component (e.g. if placed below the keyboard component, it’s code will execute later, and overturn whatever the result of the keyboard component was.) You can right-click on component icons to alter their order.

Oh wait, I confused the different files and the code got in the wrong section (End routine instead of each frame), it works fine now.

Sorry for the confusion and thank you very much for your help!

Hi everyone,

I’m trying to solve the same problem. I’m trying to build a verbal fluency task (write down as many exemplars of given semantic category as possible in restricted time 120 sec)

Basic overview of procedure

  1. Instructions -> 2. Semantic category cue(3 sec) -> 3. Loop begins (subject writes down exemplars, the routine resets each time subject confirms his written response by pressing enter on keyboard). I want the loop to end after the time limit (120 sec).

I used code snippets provided by Michael
At “Begine routine”

if trials.thisTrial == 0:
startTime = globalClock.getTime()

At “Each frame”

if globalClock.getTime() - startTime >= 120:
trials.finished = True
continueRoutine = False

However, after the cue (2.) has been presented I receive syntax error reporting that name “startTime” is not defined.

I’m a bit clueless about what might be the problem. Thanks for any advice.

Hi @Drahomir , the error means that you are using startTime before it has been defined. So, there may be an issue with the following conditional statement not being satisfied:

Hi dvbridges,

Thanks for response

Where should I then define startTime if not at the beginning of the routine?

You can start it at the beginning of the routine where the time is collected on the first trial only, but for some reason this is not working. I think is it because of the incorrect use of the trialhander attribute. Use:

if trials.thisTrialN == 0:
    startTime = globalClock.getTime()

Well, I don’t get any error now. However, the loop terminates only after all nReps have been completed. So the loop is still completed based on the number of completed routines, not based on expired time limit.

This is the part of the i suspect to be the culprit.

set up handler to look after randomisation of conditions etc

trials = data.TrialHandler(nReps=5, method=‘sequential’,
extraInfo=expInfo, originPath=-1,
trialList=[None],
seed=None, name=‘trials’)
thisExp.addLoop(trials) # add the loop to the experiment
thisTrial = trials.trialList[0] # so we can initialise stimuli with some values

abbreviate parameter names if possible (e.g. rgb = thisTrial.rgb)

if thisTrial != None:
for paramName in thisTrial.keys():
exec(paramName + ‘= thisTrial.’ + paramName)

for thisTrial in trials:
currentLoop = trials
# abbreviate parameter names if possible (e.g. rgb = thisTrial.rgb)
if thisTrial != None:
for paramName in thisTrial.keys():
exec(paramName + ‘= thisTrial.’ + paramName)

# ------Prepare to start Routine "Task"-------
t = 0
TaskClock.reset()  # clock
frameN = -1
continueRoutine = True
routineTimer.add(120.000000)
# update component parameters for each repeat
theseKeys=""
shift_flag = False
text_3.alignHoriz ='center'

if trials.thisTrialN == 0:
    startTime = globalClock.getTime()
key_press_last = event.BuilderKeyResponse()
# keep track of which components have finished
TaskComponents = [text_3, key_press_last]
for thisComponent in TaskComponents:
    if hasattr(thisComponent, 'status'):
        thisComponent.status = NOT_STARTED

# -------Start Routine "Task"-------
while continueRoutine and routineTimer.getTime() > 0:
    # get current time
    t = TaskClock.getTime()
    frameN = frameN + 1  # number of completed frames (so 0 is the first frame)
    # update/draw components on each frame
    if globalClock.getTime() - startTime >= 120:
        trials.finished = True
        continueRoutine = False

What seems to be the problem is that my code contains another “countinueRoutine = False” command

This code allows the written text to be a response:

n = len(theseKeys)
i = 0
while i < n:

if theseKeys[i] == 'return':
    # pressing RETURN means time to stop
    continueRoutine = False
    break

elif theseKeys[i] == 'backspace':
    inputText = inputText[:-1]  # lose the final character
    i = i + 1

elif theseKeys[i] == 'space':
    inputText += ' '
    i = i + 1

elif theseKeys[i] in ['lshift', 'rshift']:
    shift_flag = True
    i = i + 1

So everytime an enter/return is pressed the routine gets repeated meaning that the countdown begins again. This is however confusing as the countdown should be referenced only to the first trial of the loop as written in the “Begin Routine*” tab.

I tried it again this time setting the countdown to 1 s. The experiment quit before I was even able to write a sentence. However, if I was quick enough to press enter, then another 1s had to pass for experiment to quit. Hence, this suggests that the countdown is being reseted for each new Routine(trial).

Yes, so your keyboard is ending the routine, and this is why you have 5 repeats, because trials.finished is never called, and so the loop is never broken. Instead of ending the routine on return, you could store the typed word in the datafile, and clear the text component ready for the next word.

# Each Frame
if theseKeys[i] == 'return':
    # Store word
    thisExp.addData("word", text.text)
    thisExp.addData("wordTime", t)  # Store time
    text.text = ''       # Clear text component
    thisExp.nextEntry()  # Move to next row
#...etc

You could record other information as well, e.g., time return pressed (see above)

1 Like

Thank you greatly. The experiment now quits after the time limit.

Is it possible to store time of individual words always beginning from 0 with this code? Because if I understand the code right, “wordTime” stores the time each word was confirmed (pressed return) from the beginning of the routine. So to get the time it took the word to be written I have to substract the time of current word from the previous one. Also, is it possible to store RT for the first key(letter) and last key(“return”) pressed separatelly into two columns?

D

For timing you could create as new clock:

# Begin routine
wordClock = core.Clock()  # Create new clock
wordClock .reset()

# Each Frame
if theseKeys[i] == 'return':
    # Store word
    thisExp.addData("word", text.text)
    thisExp.addData("wordTime", wordClock.getTime())  # Store time
    text.text = ''       # Clear text component
    thisExp.nextEntry()  # Move to next row
    wordClock.reset()  # Reset clock to zero

To get the time on the first letter (return pressed time is covered in the code above):

# After checking for return etc and you add a character...
else:
    if len(text.text) == 0:  # if first letter about to be entered, save time
        thisExp.addData("firstLetterTime", wordClock.getTime())
1 Like

Thanks :slight_smile:

Seems to work well for “return”. Where should I incorporate this part?

After checking for return etc and you add a character…

else:
if len(text.text) == 0: # if first letter about to be entered, save time
thisExp.addData(“firstLetterTime”, wordClock.getTime())

I’m getting an error: “IndetationError: expected an intended block” wherever I put this line in “Each frame” tab.

You need to insert this code wherever you are adding a characters other than space etc to the text component or your inputText variable.

I put it here at the bottom and it seems to track the time of the first key being pressed. I then assume that it works correctly as it should.

    # *text_3* updates
    if t >= 0.0 and text_3.status == NOT_STARTED:
        # keep track of start time/frame for later
        text_3.tStart = t
        text_3.frameNStart = frameN  # exact frame index
        text_3.setAutoDraw(True)
    frameRemains = 0.0 + 120- win.monitorFramePeriod * 0.75  # most of one frame period left
    if text_3.status == STARTED and t >= frameRemains:
        text_3.setAutoDraw(False)
    if text_3.status == STARTED:  # only update if drawing
        text_3.setText((inputText), log=False)
    if len(inputText)==0:
        thisExp.addData("wtb", wordClock.getTime())

It really needs to go with the code component code, where you are collecting key responses. There will be a point at which you decide that a key press needs to be added to the text component, and it is that point that you save the first key press. Can I see the code that enters key responses?

I have put a text input example of Pavlovia for both Python and JS, which would also work:
https://run.pavlovia.org/demos/textinput/html