psychopy.org | Reference | Downloads | Github

Number of TRs is different between experiments

I’m currently running an experiment in psychopy using a Magnetom 3T Prisma scanner. For whatever reason, the total number of TRs I get for running the same experiment twice varies quite a bit. Since the experiment is supposed to be exactly the same I need the timecourses to line up perfectly. Also, in my log files I have psychopy print out the TR number and the time in seconds right next to it. I noticed that the time (s) varies for each experiment. In other words, for example, the first TR will count as 1 second for the first experiment and then count as .999 seconds for the second experiment. This, I believe, is what is causing my number of TRs to vary between experiments by the end (in total I’m collecting around 2000 TRs for each experiment).

Any help with this would be great!

Hi,

Have you already had a look at this page?: http://www.psychopy.org/general/timing/nonSlipTiming.html

Hope this helps.

Jan

1 Like

Thanks! I’ll give this a shot. It looks like it might do the trick. Also, next time i’ll search the forum before posting my question;)

Thanks again!

so i’ve looked into the nonslip timing documentation but it is still unclear to me how to implement this in my code. In my script the loop below prints (log_msg function) out each volume and the time in seconds right next to it (onset). How do I make sure that onset is equal to vol for each repetition time using the add() method or CountdownTimer() as specified in the documentation?

allKeys = event.getKeys()
for key in allKeys:
    if key == MR_settings['sync']:
        onset = globalClock.getTime()
        # do your experiment code at this point if you want it sync'd to the TR
        # for demo just display a counter & time, updated at the start of each TR
        counter.setText("+")
        counter.size = 10
        log_msg(u"%3d  %7.3f sync\n" % (vol, onset))
        counter.draw()
        win.flip()
        vol += 1
    else:

You only need to collect the first sync signal from the MRI. After that, you can assume that the MRI will keep absolutely perfect time at 1 s per TR. There is little point in measuring the sync signal on every successive TR after that, as there will be some slop in the measurement itself (i.e. the MRI machine timing will be more accurate than the precision with which you can measure the pulse coming it).

Once the first signal is detected, the goal of your code then becomes to also maintain an overall pace equal to the machine’s TRs. You can verify this at the end of the experiment by simply subtracting the time at the end of study from the time at the first pulse, and it should, within one screen refresh, match the intended duration of the task . For example, I’ve been doing some fMRI this week, and an intended task duration of 6 min 24 s never varied by more than 6 ms from that goal.

But what you will find is that there is a little slippage here and there between the same points in the code: as you note, it might be 999 ms on one cycle, and 1000 on the next. Using a countdown timer allows you to immediately counteract that, rather than letting these errors accumulate. e.g. lets say the first trial took 1001 ms, so the countdown timer will have gone negative (to -0.001 s). By adding 1.0 s, to the timer for the next trial, that trial starts with a value of 0.999 s rather than 1.000 s on the clock. That is, we’ve counteracted the excessive time taken on the first trial by slightly decreasing the time available for the next. That way, the errors can’t accumulate.

Something like this:

started = False
TR_timer = core.CountdownTimer()

while not started:
    # wait until getting a pulse from the scanner:
    if event.getKeys(MR_settings['sync']):
        start_time =  globalClock.getTime()
        TR_timer.reset()
        started = True
    else:
        time.sleep(0.001) # let the system breathe a bit

# for 2000 s:
for TR in range(2000):
    TR_timer.add(1.000)
    counter.setText(str(TR)) # update once per second
    
    while TR_timer.getTime() > 0.0:
        counter.draw() # draw it at 60 times per second
        win.flip()

end_time =  globalClock.getTime() - start_time
print('That took: ' + str(end_time))
    

This should count from 0 to 1999, displaying that updated count once per TR, and the total duration should be within one screen refresh of 2000 s. You’ll notice that the screen should clear the moment the scanner noise stops.

1 Like