I think the following code with a few adaptations could fixe your issue.
Here is some code I wrote in which I saved both EEG data and some lsl string messages in a XDF file using labrecorder.
Load the file using mnelab
import pyxdf
import pylsl
from mnelab.io import read_raw
xdf_filename = "path_to_your_datafile.xdf"
raw = read_raw(xdf_filename, stream_id=None)
Locate your streams
Basically you have a continuous data stream and a non continuous that starts on the first stimulation. If I’m correct, both have a timestamp expressed in seconds set by the LSL protocol, so you can’t just add stimulations with their absolute timestamp. The idea here is to substract the very first EEG timestamp to your stimulation stream such that you get the position of stimuli from sample 0. It is afterwards convenient to translate those stimuli as MNE annotations, with onsets expressed in seconds.
First locate them.
You must here change the match values of “type” or match using the “name” of the steam.
eeg_stream = None
msg_stream = None
streams, header = pyxdf.load_xdf(xdf_filename)
# detect the EEG stream
states_list_eeg = pyxdf.match_streaminfos(pyxdf.resolve_streams(xdf_filename),
[{"type": "EEG"}])
for states_stream_id in states_list_eeg:
for stream in streams:
if stream["info"]["stream_id"] == states_stream_id:
eeg_stream = stream
print('Found eeg stream {}'.format(states_stream_id))
break
assert eeg_stream is not None, 'EEG stream not found'
# detect the message stream
states_list_ids = pyxdf.match_streaminfos(pyxdf.resolve_streams(xdf_filename),
[{"type": "msg_states"}])
for states_stream_id in states_list_ids:
for stream in streams:
if stream["info"]["stream_id"] == states_stream_id:
msg_stream = stream
print('Found message stream {}'.format(states_stream_id))
break
assert eeg_stream is not None, 'lsl message stream not found in xdf file'
Locate the very first timestamp in the continuous EEG channel
first_samp = eeg_stream["time_stamps"][0]
use it as a correction for relative time stamp of the message channel
print('first time stamp correction {}'.format(first_samp))
onsets = msg_stream["time_stamps"] - first_samp
Manually generate and add timestamped annotations to your RawArray
descriptions = [item for sub in msg_stream["time_series"] for item in sub]
raw.annotations.append(onsets, [0] * len(onsets), descriptions)
Plot your data
raw.plot()
As you can see in my case it worked at synchronizing annotations that I knew started at timestamps 1sec and 2sec.
Please tell me if you’re successful.
ps: please note that events with duration of 0 have been ignored by mne so I’d recomment to test using [1] * len(onsets) in the durations parameter when appending annotations.
