Open joschaschmiedt opened 4 months ago
Any events that are received within the same process
callback will be associated with the same sample number: https://github.com/open-ephys-plugins/network-events/blob/f064e0462dc1561bd0ba8fc2eb16e82396a69112/Source/NetworkEvents.cpp#L362
Since the default buffer size for the GUI is 21 ms, this matches what you've observed. To increase the resolution, there are two options:
@jsiegle, thanks that's very helpful.
For the second option, I suppose the sample number should be stored in the object pushed to the TTLQueue on message arrival in the NetworkEvents::run
thread.
What's the best way to retrieve the current or latest sample number from there (in a thread-safe way)? Also with getFirstSampleNumberForBlock
?
Edit: getFirstSampleNumberForBlock
simply returns GenericProcessor::startTimestampsForBlock
, which is a std::map
updated by the main JUCE WASAPI thread. That means we can't simply use that method to retreive the sample number in the Network Events thread.
Edit 2: Re-reading your comment, did you mean recording the system time with something like this on message arrival and then computing an offset during process
?
The second approach is what I had in mind. Since the messages are being received in a separate thread, you can store the current software time (from Time::getHighResolutionTicks()
in the StringTTL
struct. When creating the actual TTL event, you can compute the offset with the current software time and translate that into the associated sample number by multiplying by the sample rate.
This is a useful feature in general, so if you end up implementing this we would gladly merge a PR.
I implemented storing the tick timestamp when the TTL messages are received here: https://github.com/open-ephys-plugins/network-events/pull/7/commits/12cfbcc2ab7569ebba369bd80a304148cb2bc7d5 (see also #7)
This seems to work well and the resulting event timestamps are on the order of the original (see example data: TTL_data_with_sampleoffset.zip).
However, the LFP viewer does not like that events are now sometimes in the past and will crash here when the eventTime
is negative. copyFrom
doesn't like negative indices very much.
What would you suggest to solve this issue? Are there any other places where time-shifted events could become a problem?
The ideal solution to this would be to add some protections to the LFP Viewer to prevent this crash from occurring. This is on our TODO list for the version 1.0 release. I'm not sure what other plugins might be affected by this, but this is something we can test.
Another option would be to shift all of the event sample numbers so they fall within the range of the current buffer. This will result in a slight delay in the actual event times, but should be handled properly by all downstream plugins.
Can I help with protecting the LFP viewer? Is there an issue in the main repo already?
We have an internal issue to track this, but it's not in the main repo. Once we have a fix for version 1.0, we will merge the changes into the development
branch.
Thanks for the update. I'll wait until then.
I'm wondering what the minimum distance between network TTL events is such that they get associated different timestamps. I.e., what's the "sampling rate" of the network TTL events, what factors determine it, and if it can be improved.
Sending TTL commands with a delay of 1 ms in between resulted in many events associated with the same sample number and timestamp, roughly on the order dt=21 ms (see Python script below and attached data). No events were missing. This was tested with version 0.2.1 and Open Ephys 0.6.7 using a NI-DAQmx source node.
Python script
```python import zmq import time def run_client(): # Connect network handler ip = "127.0.0.1" port = 52005 timeout = 1.0 url = f"tcp://{ip}:{port}" with zmq.Context() as context: with context.socket(zmq.REQ) as socket: socket.RCVTIMEO = int(timeout * 1000) # timeout in milliseconds socket.connect(url) # Start data acquisition socket.send_string("StartAcquisition") print(socket.recv_string()) time.sleep(1) socket.send_string("StartRecord") print(socket.recv_string()) for line in range(1, 64): socket.send_string(f"TTL Line={line} State=1") print(socket.recv_string()) socket.send_string(f"TTL Line={line} State=0") print(socket.recv_string()) time.sleep(0.001) for word in range(0, 2**16, 16): socket.send_string(f"TTL Word={word}") print(socket.recv_string()) time.sleep(0.001) time.sleep(1) socket.send_string("StopRecord") print(socket.recv_string()) socket.send_string("StopAcquisition") print(socket.recv_string()) if __name__ == "__main__": run_client() ```TTL.zip