open-ephys-plugins / network-events

Adds TTL events via a network connection
4 stars 4 forks source link

"Sampling rate" of network TTL events #8

Open joschaschmiedt opened 3 months ago

joschaschmiedt commented 3 months ago

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

jsiegle commented 3 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:

joschaschmiedt commented 3 months ago

@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.

https://github.com/open-ephys-plugins/network-events/blob/f064e0462dc1561bd0ba8fc2eb16e82396a69112/Source/NetworkEvents.cpp#L328

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?

jsiegle commented 3 months ago

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.

joschaschmiedt commented 3 months ago

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?

jsiegle commented 3 months ago

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.

joschaschmiedt commented 2 months ago

Can I help with protecting the LFP viewer? Is there an issue in the main repo already?

jsiegle commented 2 months ago

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.

joschaschmiedt commented 2 months ago

Thanks for the update. I'll wait until then.