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
5 Likes
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
- 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()
2 Likes
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)
2 Likes
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
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