Calling win.flip() at every frame when using static stimuli: pros and cons

First I’d like to thank you guys for such a wonderful software. Amazing.
I just noticed that the code generated from the builder calls win.flip() on every frame. Response times (RTs) are thus sampled at the refresh rate right? (so at 60Hz, there’s one check of the keyboard every 16.6ms). I am totally fine with it, but I wonder if we should not get rid of this constraint when using static stimuli (i.e., stimuli that remain constant until the response). It would actually improve RT resolution. Here is a coding example, feel free to criticize/comment:

> timer=core.Clock()
> Routine = True
> stimulus.draw()
> event.clearEvents(eventType='keyboard')
> win.flip()
> timer.reset()
> while Routine:
>    key_press = event.getKeys(keyList=["f", "j"])
>    if len(key_press) > 0:
>        RT = timer.getTime()
>        win.flip()
>        Routine = False

Btw, is it better to call event.getKeys() or event.waitKeys() in this case? Using event.waitKeys() removes the need of the while loop:

> timer=core.Clock()
> stimulus.draw()
> win.flip()
> timer.reset()
> key_press = event.waitKeys(keyList=["f", "j"])
> RT = timer.getTime()
> win.flip()

Best,
Mat

Hi Mat,

If crafting your own experiments in code, then yes, that can be an excellent technique to increase the precision of RT measurement. But Builder is general purpose software and can’t be that flexible. For example, what if the stimuli you create appear to be static in terms of what gets entered in Builder stimulus component dialogs, but you animate them in a code component?

This is actually a common technique, but there is no way that Builder could know about it. Breaking the refresh loop would also lock out code components from running code on every frame to do non display-related activities (e.g. periodic interfacing with external hardware).

Builder might evolve in the future to take advantage of new screen update techniques (e.g. Sub-ms precision and flexible frame durations with G-Sync), but for the time being, people wanting to break out of the screen refresh loop need to get their hands dirty with writing their own code, which it certainly seems you’re capable of doing.

Cheers

PS Please surround python code with backticks

Hi Michael,

Excellent response, as usual. Sorry I was editing my code when you replied. Another possibly is to use the multiprocessing library (one thread for stim presentation, another for timing stuff). I do that when using dynamic stimuli, but I guess it’s way too hard to incorporate within the Builder.
I added another edit in my previous post. In terms of efficiency, I am wondering if I should use a getKeys() within a while loop, or simply remove the while loop and call waitKeys() (see my second code example). I guess there is absolutely no difference in terms of timing. Also, I have seen some threads arguing that the keyboard buffer should be cleared (by calling clearEvents) before calling getKeys(), but I see absolutely no difference.

Cheers

You need to do this before the first getKeys() only, as there might be a response that is sitting in the queue from well before getKeys() is called.

Look in the code for waitKeys(): I guess it is just wrapping a getKeys() loop. But if you do use a loop, remember you need to build in some pause time so that other processes get some breathing time on the CPU. A very tight loop hogs all the CPU time to itself, choking other processes, which might just eventually break in and seize control for quite a while to get through a backlog, completely mucking up your timing. You get this for free when using win.flip(), as that usually provides at least a couple of ms per refresh loop of idle time. waitKeys also is a good citizen like that too.

ie on each iteration, call something like time.sleep(0.001) to yield time to other processes. This means that your loop won’t iterate at light speed, but something like slightly less than 1 kHz.

You are right, waitKeys() is just a wrapping of getKeys() within a while loop. I am not convinced about your argument against a very tight loop. I don’t exactly see how it would muck up timing with modern computers. If it does, then the community should be aware of this issue (I guess I am not the only one using this ‘trick’)!
Cheers

The problem is worse with modern computers. In the old days that some of us still remember, your program was the only thing running and so very modest PCs could do pretty great things in real time. Even the OS generally kept out of your way.

A modern OS isn’t optimised for real-time control and has has potentially hundreds of processes running simultaneously, all vying for simultaneous use of resources. Having multiple cores doesn’t mitigate that entirely.

https://discourse.psychopy.org/search?q=%20time.sleep(0.001)

Would be great if someone would volunteer to update our documentation to make this more obvious…

Excellent Michael, very helpful. A few last comments:

  1. There may be a serious issue when using the time.sleep() function. See doc: The time.sleep() function uses the underlying operating system’s sleep() function. Ultimately there are limitations of this function. For example on a standard Windows installation, the smallest interval you may sleep is 10 - 13 milliseconds.
  2. Shouldn’t we incorporate a time.sleep() into the while loop of the waitKeys() function?
  3. Do we know the amount of possible timing distortion introduced by CPU overload? That is, is there a way to estimate potential distortions introduced by my initial keyboard polling code?
  4. Bottom line: calling win.flip at each frame may be the best method given these limitations…

Cheers

It seems psychopy.ioHub module now solves the issue.
However, is there any method which simply tells your loop how many VSync cycles have passed without actually flipping the buffer?