- 07 Mar 2025
- PDF
Hyperscanning
- Updated on 07 Mar 2025
- PDF
Requirements
If you would like to measure two participants at once, you will need:
- Two Flow Headsets
- Two Acquisition Computers
The easiest way for the data to be aligned is to put the computers onto the same clock. This can be done using NTP and synchronizing the two computers to the same time server. If the time server is on the local network, each computer should have <1ms error for a total error of <2ms. If the time server is on the internet, you should measure the error to decide if it is acceptable or not.
Synchronization via Task Events
When the NTP error is too high, we also support synchronizing via custom task events.
Diagram 1: Two task connections emit task events to two acquisition drivers
To synchronize two datasets recorded for hyperscanning, you can configure the task client to emit events to both acquisition drivers at the same time. Each task records the time the event was emitted, and each acquisition driver will record the time the event was received. When the task and acquisition driver are running on the same PC (PC1), the latency between when the event is sent (ts_tx) and when it is received (ts_rx) is negligible (ε), and there is no clock difference since the sent time and the receive time are recorded with the same clock. However, when the event is sent to another PC (PC2) the receive time will be affected by the clock offset between PC1 and PC2 (θ) and any network latency that occurred (ℓ).
That is:
PC1 ts_rx - PC1 ts_tx = ε
PC2 ts_rx - PC1 ts_tx = θ + ℓ
In order to synchronize the sessions we need to find the clock offset θ and correct the NIRS times by it.
To calculate θ, we’ll need to find the network latency ℓ and send it as a special task event named ping_latency_ms. Ping is a common tool to determine the latency, and we’ll use a pure Python implementation in our example. You can use your own implementation, but be aware that the quality of the ℓ will affect the quality of synchronization.
from kernel.sdk.task import TaskClient, ClientProtocol
from tcppinglib import tcpping
tcp_port = 6767
conn1 = TaskClient(“localhost”,tcp_port, protocol=ClientProtocol.TCP)
remote_ip = “xxx.xxx.xxx.xxx”
conn2 = TaskClient(remote_ip,tcp_port, protocol=ClientProtocol.TCP) # fill ip in with the ip of the remote pc
while True:
conn2_rtt = tcpping(remote_ip,tcp_port,count=4,interval=0.25).avg_rtt # get the average round-trip time for 1 second
conn1.send_event(ping_latency_ms=0.0) # latency on localhost is negligible
conn2.send_event(ping_latency_ms=conn2_rtt/2) # assume network symmetry, so latency to pc2 is rtt/2
The offset will appear as ClockOffset in the SNIRF file later, and we can use this to correct the NIRS data. There are multiple ways to do the correction; here we’ll just use a simple average:
from snirf import Snirf
import numpy as np
snirf_file = Snirf('some\path\session1.snirf')
# get the offset info
offset_idx = np.where(snirf_file.nirs[0].stim[0].dataLabels == "ClockOffset")[0].item()
offset_column = snirf_file.nirs[0].stim[0].data[:,offset_idx]
offsets = offset_column[~np.isnan(offset_column)]
average_offset = np.average(offsets)
# now correct the nirs timestamps by the average
snirf_file.nirs[0].data[0].time = snirf_file.nirs[0].data[0].time - average_offset
snirf_file.save() # write corrected timestamps back to disk
Note that the corrected timestamps may be negative (in case the receiver’s clock is behind the sender’s clock).