labstreaminglayer / pylsl

Python bindings (pylsl) for liblsl
MIT License
143 stars 59 forks source link

Time Synchronization Issue #8

Closed ElRoggo closed 1 year ago

ElRoggo commented 5 years ago

Hello guys!

I am currently using the Pylsl interface for sending event markers to an EEG signal, being acquired by the Enobio 8 headset, using their proprietary NIC software, as is recommended (instead of sending markers by TCP), as we need to analyze ERPs.

The experiment itself was designed using PsychoPy, with the Python code being generated using the standalone version, and then the Pylsl library was added to the generated code.

In a (possibly) unrelated note, I needed to delete the importing of the GUI module, as it seemed to be incompatible with the Pylsl interface (no events would be sent to the signal if it was used).

However, when I tried to test the latency of the event marking in the signal, I verified that the events always appeared earlier in the signal, than what the timestamp in python was at the time - basically, I got the python timestamp using:

int(round(datetime.datetime.now( ).timestamp( ) * 1000))

which was always ahead of the signals' timestamp. The difference in time was also not consistent, sometimes being 6 ms, and at others upwards of 30 ms (and always rising from the start of the experiment), which would render me unable to check for ERPs properly.

I'm using Python 3.6.4 from the Anaconda distribution, on macOS Sierra 10.12.6, and the Python timestamp is being calculated both immediately before and immediately after the push_sample, being the same in most situations.

Thanks in advance,

Igor Rodrigues

tstenner commented 5 years ago

I've contacted Neuroelectrics once regarding specifics in their LSL support and unfortunately I just got a stock response that they'd forward me instructions on how to use LabRecorder (which they didn't).

If you're serious about ERPs, you should verify the timing with an indepent synchronization source. I'm a big fan of the LabStreamer (full disclosure: @mgrivich makes and sells them and loaned me one), but if you're on a budget you can also send TTL triggers (via the parallel port or a cheap microcontroller, e.g. the Teensy LC/3.2 or the Arduino Leonardo / Pro Micro) to the neuroelectrics TTL adapter.

In a (possibly) unrelated note, I needed to delete the importing of the GUI module, as it seemed to be incompatible with the Pylsl interface (no events would be sent to the signal if it was used).

That might be related to the Qt thread issue, but I can't say for sure. Maybe @cboulay knows more?

int(round(datetime.datetime.now( ).timestamp( ) * 1000))

Always use lsl_clock(). There are several clock sources in modern OSs with different properties (accuracy, monotonicity, jerkiness) and you need to use the one LSL uses.

ElRoggo commented 5 years ago

I've contacted Neuroelectrics once regarding specifics in their LSL support and unfortunately I just got a stock response that they'd forward me instructions on how to use LabRecorder (which they didn't).

If you're serious about ERPs, you should verify the timing with an indepent synchronization source. I'm a big fan of the LabStreamer (full disclosure: @mgrivich makes and sells them and loaned me one), but if you're on a budget you can also send TTL triggers (via the parallel port or a cheap microcontroller, e.g. the Teensy LC/3.2 or the Arduino Leonardo / Pro Micro) to the neuroelectrics TTL adapter.

Unfortunately, it is impossible for us to use hardware synchronization, the software synchronization between events sent using Pylsl and the EEG data stream will have to do...

int(round(datetime.datetime.now( ).timestamp( ) * 1000))

Always use lsl_clock(). There are several clock sources in modern OSs with different properties (accuracy, monotonicity, jerkiness) and you need to use the one LSL uses.

lsl_clock() returns the time since the computer last turned on, correct? So, in order to achieve the ms timestamp that is used in the Enobio NECBOX controller (or better said, to test the latency between the push_sample and the events actually registering), one should calculate the difference between the UTC timestamp from datetime.datetime.now().timestamp() and an initial call to local_clock(), and then add this value to subsequent calls to local_clock(), called at the times that we want time measured right?

Because if this is the case, I just ran an experiment that registered a 4 ms advance at the beginning, but at the end (45 mins after), had a 4 ms latency...

dmedine commented 5 years ago

lsl_clock() returns the time since the computer was turned on on Windows machines. On Linux and OSX I believe it gives the time since the epoch (00:00 1970 GMT).

On 20.02.2019 16:52, Igor Rodrigues wrote:

I've contacted Neuroelectrics once regarding specifics in their
LSL support and unfortunately I just got a stock response that
they'd forward me instructions on how to use LabRecorder (which
they didn't).

If you're serious about ERPs, you should verify the timing with an
indepent synchronization source.
I'm a big fan of the LabStreamer
<https://www.neurobs.com/menu_presentation/menu_hardware/labstreamer>
(full disclosure: @mgrivich <https://github.com/mgrivich> makes
and sells them and loaned me one), but if you're on a budget you
can also send TTL triggers (via the parallel port or a cheap
microcontroller, e.g. the Teensy LC/3.2 or the Arduino Leonardo /
Pro Micro) to the neuroelectrics TTL adapter.

Unfortunately, it is impossible for us to use hardware synchronization, the software synchronization between events sent using Pylsl and the EEG data stream will have to do...

    int(round(datetime.datetime.now( ).timestamp( ) * 1000))

Always use |lsl_clock()|. There are several clock sources in
modern OSs with different properties (accuracy, monotonicity,
jerkiness) and you need to use the one LSL uses.

|lsl_clock()| returns the time since the computer last turned on, correct? So, in order to achieve the ms timestamp that is used in the Enobio NECBOX controller (or better said, to test the latency between the |push_sample| and the events actually registering), one should calculate the difference between the UTC timestamp from |datetime.datetime.now().timestamp()| and an initial call to |local_clock()|, and then add this value to subsequent calls to |local_clock()|, called at the times that we want time measured right?

Because if this is the case, I just ran an experiment that registered a 4 ms advance at the beginning, but at the end (45 mins after), had a 4 ms latency...

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/labstreaminglayer/liblsl-Python/issues/8#issuecomment-465635384, or mute the thread https://github.com/notifications/unsubscribe-auth/ADch7g4_VOjgvJSzxvSLcA_Cpkz-0Lq7ks5vPW8igaJpZM4bDRMr.

ElRoggo commented 5 years ago

lsl_clock() returns the time since the computer was turned on on Windows machines. On Linux and OSX I believe it gives the time since the epoch (00:00 1970 GMT).

Well, I am running OSX (as stated in the OP) and I called local_clock() from the shell after importing pylsl and it returned 113983.822118478 referent to a 1970 timestamp. If simply multiplied by 1000 to obtain ms, it's a date from 1973. Is this expected behaviour?

dmedine commented 5 years ago

I'm not much of an OSX expert, but I thought that it should count up be from 1970. At least, that is when the epoch is. Maybe I'm wrong about the point in time it counts from.

On 20.02.2019 17:40, Igor Rodrigues wrote:

lsl_clock() returns the time since the computer was turned on on
Windows machines. On Linux and OSX I believe it gives the time
since the epoch (00:00 1970 GMT).

Well, I am running OSX (as stated in the OP) and I called |local_clock()| from the shell after importing pylsl and it returned |113983.822118478| referent to a 1970 timestamp. If simply multiplied by 1000 to obtain ms, it's a date from 1973. Is this expected behaviour?

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/labstreaminglayer/liblsl-Python/issues/8#issuecomment-465657333, or mute the thread https://github.com/notifications/unsubscribe-auth/ADch7vhMOsjBZkIoOlrJmomiHFt0YYF9ks5vPXqDgaJpZM4bDRMr.

tstenner commented 5 years ago

lsl_clock() returns the time since the computer was turned on on Windows machines. On Linux and OSX I believe it gives the time since the epoch (00:00 1970 GMT).

Not necessarily, for Boost this might be true it's not guaranteed.

There is no fixed relationship between values returned by steady_clock::now() and wall-clock time. source

Is this expected behaviour?

The starting point is not guaranteed and shouldn't be relied upon unless you really know that two different clocks really use the same system clock.

cboulay commented 1 year ago

Closing as stale, won't fix, and we now are a little more consistent in telling people they cannot relate LSL's clock to the wall clock.

This doesn't solve the user's issue with the Enobio system but that's not a pylsl issue.