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:
start_time = globalClock.getTime()
started = True
time.sleep(0.001) # let the system breathe a bit
# for 2000 s:
for TR in range(2000):
counter.setText(str(TR)) # update once per second
while TR_timer.getTime() > 0.0:
counter.draw() # draw it at 60 times per second
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.