Timing delay issue during behavioural assay

Hello!

Recently, I’ve designed a behavioural assay and finished implementing it with Psychopy.

I made some objects to appear and let subjects respond to it.

After a pilot study, I checked the onset times of the objects appeared during the study and found that

the timing was getting delayed and eventually reached to ~0.6s delay as shown below.

I anticipated a little bit of delay but this, seems too much. I don’t think it’s because of computational limit (my desktop is not that poor)

Onset Time
0.038
14.040
28.061
40.081

412.621
430.642

Can you please guess what might be the reason for this phenomenon and let me know how to fix it?

Thanks.

No, there’s no chance we could work out the problem in your code from that little information. Sorry.

Share with us the minimal amount of code that has a problem; no code about loading stimuli or collecting responses, just the code that shows the bad timing problem.

while t < totalDuration:
	if t>=buttonpresstime and buttonpresschk ==1:
		self.fix1.draw()
		self.fix2.draw()
		experiment.win.update()

	if event.getKeys(["escape"]):
		core.quit()
	
	t = time.time() - self.trialStart
	if presentOnsetFix:
		experiment.win.clearBuffer()
		self.fix1.draw()
		self.fix2.draw()
		self.onsetFixOnset = self.presentStimulus(experiment,experiment.experimentPlan[self.trialIndex][0])
		presentOnsetFix = 0
	elif t >= experiment.onsetfix_dur and presentStim:
		self.fix1.draw()
		self.fix2.draw()
		self.stimOnset = self.presentStimulusITI(experiment)
		presentStim = 0
	


if buttonpresschk == 1 :
	responseinfo = str(experiment.experimentPlan[self.trialIndex][2]) + "\t" + str(experiment.experimentPlan[self.trialIndex][3]) +"\t" + _**str(self.stimOnset - self.startTime)**_ + "\t" + str(buttonpresstime - respStart) + "\t" + button  + "\t" + str(correctness) + "\n"
else :
	responseinfo = str(experiment.experimentPlan[self.trialIndex][2]) + "\t" + str(experiment.experimentPlan[self.trialIndex][3]) + "\t" + **_str(self.stimOnset - self.startTime)_** + "\t" + str(0.01) + "\t-\t" + str(correctness) + "\n"

self.startTime is measured at the beginning, and self.stimOnset - self.startTime is the timing value.

This is some part of my code showing the bad timing. Of course I can show you more if you want.

Thanks again!

FYI, the while loop shown above does not end in that way. I just cut some part of the loop and showed with the printing part (self.stimOnset - self.startTime)

OK, yes. From your code the reason is clear enough.

On each frame you wait until the time has exceeded your duration rather than testing whether you’re within one frame of your intended duration. If you arrive at that if-statement 1ms short of the experiment.onsetfix_dur then you present another frame, and you overshoot by 15ms (on a 60Hz monitor). If you do the same the next trial you gradually acrue a large overshoot.

Solutions are described here: http://www.psychopy.org/general/timing/nonSlipTiming.html

  • use frames for timing if you’re confident you never drop frames
  • use an if-statement that stops if you have more than 1/2 a frame duration remaining (fine if your intended duration is a correct multiple of the frame rate)
  • use countdown timer and add the intended duration to accommodate gradual slippage in either direction

Thank you so much for your kind and careful advice but I need one more thing to ask… sorry for my bad skills

For last couple of hours, I changed my timing conditions with countdown timer and still got the same problem.

I think it’s because of that I didn’t manage the frame directly.

If I want to use frames for timing, how can I make use of them? (I couldn’t find the way to do it from the link you gave)

Would you suggest something like core.StaticPeriod? with my monitor Hz given?

Once again, we can’t help you unless you show us what you did. Please post the minimal code

Sorry, I forgot to post it.

At first, I used CountdownTimer() and now I’m using MonotonicClock()

	timer = core.MonotonicClock()

	# while t < totalDuration:
	while timer.getTime()<totalDuration:
			
		if event.getKeys(["escape"]):
			core.quit()

		# t = time.time() - self.trialStart
				
		if presentOnsetFix:

			experiment.win.clearBuffer()
			experiment.experimentPlan[self.trialIndex][0].draw()
			self.fix1.draw()
			self.fix2.draw()
			experiment.win.update()
			self.stimOnset = time.time()
			presentOnsetFix = 0

		elif timer.getTime() >= experiment.stim_dur and presentISI:
			self.fix1.draw()
			self.fix2.draw()

		-- so on --

Right yes, that’s clear now. You aren’t using the countdown timer correctly.

The point of the countdown timer is that you use it to countdown to zero and you “add” time to it which allows for the fact that the last event might have gone ‘negative’ and any overshoot gets combined with the next event.

Here’s a minimal script to demonstrate:


from psychopy import visual, core

#set parameters
stimDuration = 1.0
oneFrame = 1/60.0

#create stimuli
win = visual.Window([800,600])
stim = visual.TextStim(win)

clock = core.Clock()
timer = core.CountdownTimer()

timer.reset() # (not needed here but good idea!)
for thisTrialN in range(5):
    stim.text = "trial {}".format(thisTrialN)
    # ecah trial you add duration to the residual from last
    timer.add(stimDuration)
    #then wait until zero
    while timer.getTime() > oneFrame/2:
        stim.draw()
        win.flip()
    print clock.getTime()

which outputs:

0.999669075012
2.00048398972
2.99986696243
4.00053715706
5.00119113922

(i.e on my standard office machine it’s accurate to around 0.15ms)

3 Likes

Wooooooow!! It’s awesome!!!

Thank you so much!! It works!!

Again, I really appreciate your brilliant advice. Thank you!

“Liking” posts and clicking “solved” is ideal