Confusion about how to implement non-slip timing for trials with known end points

This is an old thread, but this and an even older thread are the most relevant ones that came up when I searched for information about solving issues with non-slip timing and varying (but known in advance) routine durations.

I think my work-around for this was to insert some dummy fixed number like 9999 as the trial duration. Then push the “compile script” button. In that script, do a search for all instances of 9999 and replace with your relevant variable name. Make sure you save this edited script with a different name than the default, otherwise Builder will over-write it any time a change is made. Note that you now need to run this amended script rather than the original Builder experiment. In PsychoPy, this can be done from the Coder view rather than the Builder view. If any changes need to made to your experiment, you’ll need to go back to your original Builder experiment, make the changes graphically there, and then re-do this find and replace strategy in the new generated Python script.

I found an alternative solution that avoids having to edit generated code, so I’ll try to describe how it works. It’s mostly for my future self, but I thought I’d put it here in case it helps others.

When debugging/developing it became impractical to always have to let Builder generate a script, do a search-and-replace for the faux duration, then load the edited script and run the experiment. (as described in Michael’s post)

I found a hack that can be implemented directly in Builder. Here are the steps for tricking PsychoPy into treating a routine as “non-slip-eligible” while allowing for variable durations:

  1. Set a faux, very long duration for all of the routine’s component durations (say 9999s)

(try letting the Builder generate the Python script if you want, and make sure that in the preparatory section for your routine, there’s a line with routineTimer.add(9999.000000))

  1. Add a code snippet component to your routine

  2. In the code snippet’s ‘Begin Routine’ section, add code for fetching the trial duration you want. This will differ based on where/how you are storing the trial duration data. In my case I used:

trial_duration = stimuli_data[trial_counter]['trial_duration']
  1. Still in the code snippet’s ‘Begin Routine’ section, below the above line, you want to “add” time to the routineTimer by first decreasing it by the faux trial duration you added in step 1, and then increasing it by the trial duration you actually want.
routineTimer.add(-9999.000000)
routineTimer.add(trial_duration)

(or, for efficiency now that you understand what you are doing)

routineTimer.add(-9999.000000 + trial_duration)

So in the end you will have something like

# code snippet component -> 'Begin Routine'
trial_duration = stimuli_data[trial_counter]['trial_duration']
routineTimer.add(-9999.000000 + trial_duration)

The downside with this solution is that your trial’s components never get the chance to end before the routineTimer says it’s time to stop. So if you save the onset/offset times for your components, in your exported data the registered offset time will always be ‘None’. All I did was add trials_loop.addData('t_end_trial', tThisFlipGlobal) (since I’m using a loop) in the code snippet’s ‘End Routine’ section, to keep track of when the trial actually ended. As far as I can tell this seems to be about as accurate as the components’ “offset time registration” normally is.

Apart from the small nuisance with the offset times, this seems to work great so far, when I’ve checked the sum trial durations over time they seem to be as accurate as Michael describes. Please let me know if there is an issue with this solution that I’ve overlooked, since it could spoil precious fMRI data otherwise.

(also, to anyone who stumbles upon this and is confused about what non-slip timing even is: I recommend getting the official book. so many things that aren’t well documented elsewhere IMO became much clearer after reading it, including non-slip timing)

1 Like