Hi all,
I’m a heavy user of the pynetstation (called egi in psychopy) module and have made some edits/updates to better utilize a pretty fantastic module. One thing I see often that causes timing issues is the use of egi.ms_localtime()
, so I’ll make some clarification here as to how it works.
In each call to egi.ms_localtime()
the function will do a high-precision timestamp of the moment the function is called and return that timestamp value.
The egi.ms_localtime()
function is used throughout the egi module, working behind the scenes. For instance, when you call egi.sync()
, you’re actually sending a message to NetStation that says, “Recalibrate event timing to this most current timestamp I’m sending you.” You must send egi.sync
at the start of every trial so that any clock drift is accounted for throughout your experiment. In your sample testing code @gstothart , this is done perfectly.
On to the meat: callOnFlip()
This function in PsychoPy takes whatever argument you pass it and waits to execute the function the moment the back-buffer flips to the screen (correct my terminology if I’m mistaken Jon). While intuitively this seems like the best choice for sending timestamps, it depends upon your calling of a timestamp to occur at the time of the flip. In your sample code @gstothart, you made the same mistake I made for years trying to get this to work: you called egi.ms_localtime()
in your callOnFlip()
statement. What happens here is egi.ms_localtime()
has parenthesis, and thus is actually calling the function at that moment (as opposed to when the argument is run after the flip), returning a timestamp value. What you want is to have egi.ms_localtime()
to be called when the flip occurs, not when you load callOnFlip()
.
To circumvent that issue, I’ve made an edited version of egi.simple
which has another function called send_timestamped_event()
and SendSimpleTimestampedEvent()
which is functionally identical to send_event()
and SendSimpleEvent()
with the added bonus of calling the timestamp at function run-time as opposed to the current procedure of passing a timestamp as an argument. I’m uploading a copy to this post, but am in the works of making this update a post in my github account for future uses of the module in PsychoPy and other software.
simple.py (34.0 KB)
In order to take advantage of this, replace your python file simple.py
in your egi
folder in your PsychoPy distro with the one I’ve uploaded, and replace your line:
mywin.callOnFlip(ns.SendSimpleEvent,'stm-' 'stm-', timestamp=egi.ms_localtime())
with
mywin.callOnFlip(ns.SendSimpleTimestampedEvent,'stm-' 'stm-')
(Also, why do you have 2 'stm-' 'stm-'
in your event code? Not sure how that works with the function)
This should cut out a majority of your “jitter” or variance in offsets.
Unfortunately, your graphics card is integrated on the motherboard, and those tend to have little precision in timing (as can be seen by the timeByFrames results). I recently upgraded one of our systems with a simple Nvidia $35 card and it works much more reliably. Not to say changing your code with my updated egi.simple
won’t help, but potentially you’ll still have some unmanageable variance as a result.
I know this was a lengthy response, but it hopefully will help many others in the future as they search the internet for offset resolutions.
Best,
Josh
P.S. : What confuses many about the function egi.ms_localtime
is how it is called in example scripts. The original programmer of the module wrote an example script where ms_localtime = egi.ms_localtime
is called, and later on in a call to send_event()
they pass ms_localtime()
to the timestamp
variable. What they have set up is storing the function egi.ms_localtime
as a variable called ms_localtime
(the reuse of the name ms_localtime is a bit confusing) so that whenever they want to run the function egi.ms_localtime()
, they would call the new variable with parenthesis at the end: ms_localtime()
. if you were to set timestamp=ms_localtime
without parenthesis the experiment would crash because you aren’t passing a value to timestamp
, you’re passing a function which is not expected in the code.