Difficulty reading event ids from .xdf file

Dear users,

I am reading EEG data from an .xdf file, which was recorded using LSLrecorder. The EEG channels are in one stream, the event codes in another. I am using a combination of mne, mnelab, and pyxdf.

Here is the code I’m using to read in the data:

### Identify stream containing EEG channels
streams = pyxdf.resolve_streams(raw_fname) # raw_fname is an .xdf file
stream_id = pyxdf.match_streaminfos(streams, [{"type": "signal"}])

### Read in data
raw = read_raw_xdf(raw_fname, stream_ids = stream_id)

### Get events
events, event_dict = mne.events_from_annotations(raw)

The problem is that events is not what I expect. The event codes for this dataset should be 1,2,3,10,20,30,40. However, events instead contains values 1-7 that appear to be labelled / annotated (?) with the correct values. This can be seen in events_dict:

> print(events_dict)
>> {'1': 1, '10': 2, '2': 3, '20': 4, '3': 5, '30': 6, '40': 7}

…and by visualizing the raw data (the coloured values in the first output are the expected codes, the small black values are stored in events):

%matplotlib notebook
raw.plot(events=events,  event_id=None, n_channels=16,  
              scalings=dict(eeg=.00009), # Convert V to mV
              duration=30,  title='Raw EEG');

Output:

…and also by vizualising the event codes themselves:

%matplotlib inline
mne.viz.plot_events(events, sfreq=1024.0, event_id = event_dict);

Output

event_codes2

So, I’m really not sure what is happening. Is there a way to isolate the correct codes as events? I know I could probably replace the values in events with their corresponding labels from events_dict. However, I am reluctant to do this because I don’t know where the first set of values come from or whether code-to-label mappings would be consistent across different datasets.

Any advice would be greatly appreciated :slight_smile:

  • Python version: 3.9.13
  • MNE version: 1.6.1
  • operating system: Windows 10

Edit: corrected some typos

Can you show the streams contained in your XDF file (e.g. a screenshot of the XDF import dialog)? In particular, I’d like to know the stream type of your event stream. Marker streams should be converted to annotations, but I’ve only tested this extensively with string streams. I only have one example of an int32 marker stream, which does work correctly though.

On second thought, I think this is normal (expected) behavior of mne.events_from_annotations(). You need to pass an event_id dict which contains the mappings you want, otherwise the function will generate events starting with 0 (or 1, not sure).

Thanks @cbrnr for the quick reply. I tried passing a dict (with sensible condition labels and expected codes) to mne.events_from_annotations():

event_ids = {‘study_Aloud’:1, ‘study_silent’:2, ‘study_control’:3,
‘test_aloud’:10, ‘test_silent’:20, ‘test_control’:30, ‘test_foil’:40}

raw = read_raw_xdf(raw_fname, stream_ids = stream_id, marker_ids = event_ids)

This results in events being an empty array.

In answer to your first post, here is a screenshot of the import dialogue:

and here is the output of pyxdf.resolve_streams(raw_fname):

[{‘stream_id’: 3,
‘name’: ‘openvibeMarkers’,
‘type’: ‘Markers’,
‘source_id’: ‘openvibeMarkers’,
‘created_at’: ‘86188.64367220001’,
‘uid’: ‘feaf55b6-d682-46c3-96b6-4d197ca24ba2’,
‘session_id’: ‘default’,
‘hostname’: ‘DESKTOP-60A31L7’,
‘channel_count’: 1,
‘channel_format’: ‘int32’,
‘nominal_srate’: 0.0},
{‘stream_id’: 2,
‘name’: ‘openvibeMarkers’,
‘type’: ‘Markers’,
‘source_id’: ‘(0x1667ff32, 0x4fcea460)’,
‘created_at’: ‘86197.424702200005’,
‘uid’: ‘4ff4be9c-e916-40ca-b535-e5269b70f20f’,
‘session_id’: ‘default’,
‘hostname’: ‘DESKTOP-60A31L7’,
‘channel_count’: 1,
‘channel_format’: ‘int32’,
‘nominal_srate’: 0.0},
{‘stream_id’: 1,
‘name’: ‘openvibeSignal’,
‘type’: ‘signal’,
‘source_id’: ‘(0x9bb6dc2a, 0xf503f176)’,
‘created_at’: ‘86197.421560999996’,
‘uid’: ‘0f3f0cf3-c668-4689-9d10-ff363e7ae504’,
‘session_id’: ‘default’,
‘hostname’: ‘DESKTOP-60A31L7’,
‘channel_count’: 17,
‘channel_format’: ‘float32’,
‘nominal_srate’: 512.0}]

For context, triggers were sent from the stimulus presentation program (PsychoPy) via OpenVibe, hence the ‘openvibeMarker’ label. I’m not sure why there are two streams from openvibe - codes were only sent via one stream, so I think the other is redundant

That won’t work, because the original labels 1, 2, 3, 10, 20, 30, and 40 get mapped to 1, 2, 3, 4, 5, 6, and 7. You want to convert the labels to their integer code, so you can pass event_id=int as mentioned in the Notes section.

Oh, I see! Thank you very much, using mne.events_from_annotations(... event_id=int) resolved the problem.

As an aside, using this method results in keys and values of event_dict being identical:

print(event_dict)

{‘1’: 1, ‘10’: 10, ‘2’: 2, ‘20’: 20, ‘3’: 3, ‘30’: 30, ‘40’: 40}

I was able to replace the keys with sensible condition labels using:

event_dict[‘condition 1’] = event_dict.pop(‘1’)
event_dict[‘condition 2’] = event_dict.pop(‘2’)

…etc

1 Like