Read epoched BIDS data as MNE Epochs#

Some BIDS datasets contain recordings that were already segmented into trials, marked with "RecordingType": "epoched" in their JSON sidecar. mne_bids.read_raw_bids() refuses to load such recordings, because representing them as continuous mne.io.Raw data would be wrong. Instead, mne_bids.read_epochs_bids() reads them as mne.Epochs, applying the same sidecar metadata (channels.tsv, *_eeg.json, participants.tsv, …).

Since mne_bids.write_raw_bids() only writes continuous data, we will first build a small epoched EEGLAB dataset ourselves, and then read it back.

# Authors: The MNE-BIDS developers
# SPDX-License-Identifier: BSD-3-Clause

We are importing everything we need for this example:

import json
import shutil
from pathlib import Path

import mne
import numpy as np
import pandas as pd
from mne.datasets import eegbci

from mne_bids import (
    BIDSPath,
    make_dataset_description,
    print_dir_tree,
    read_epochs_bids,
    read_raw_bids,
)

Create an epoched BIDS dataset#

We use the EEG Motor Movement/Imagery Dataset and epoch one motor imagery run around the “imagine moving the left fist” and “imagine moving the right fist” cues.

edf_path = eegbci.load_data(subjects=1, runs=4, update_path=True)[0]
raw = mne.io.read_raw_edf(edf_path, preload=False)
eegbci.standardize(raw)  # set channel names
raw.info["line_freq"] = 60

events, _ = mne.events_from_annotations(raw)
raw.set_annotations(None)  # the EEGLAB exporter writes events, not annotations
event_id = {"left_fist": 2, "right_fist": 3}
# keep only the events we epoch on, so each exported epoch has exactly one event
events = events[np.isin(events[:, 2], list(event_id.values()))]
epochs = mne.Epochs(raw, events, event_id, tmin=-1, tmax=3, baseline=None, preload=True)
Using default location ~/mne_data for EEGBCI...
Downloading EEGBCI data
Attempting to create new mne-python configuration file:
/home/circleci/.mne/mne-python.json
Could not read the /home/circleci/.mne/mne-python.json json file during the writing. Assuming it is empty. Got: Expecting value: line 1 column 1 (char 0)
Download complete in 08s (2.5 MB)
Extracting EDF parameters from /home/circleci/mne_data/MNE-eegbci-data/files/eegmmidb/1.0.0/S001/S001R04.edf...
Setting channel info structure...
Creating raw.info structure...
Used Annotations descriptions: [np.str_('T0'), np.str_('T1'), np.str_('T2')]
Not setting metadata
15 matching events found
No baseline correction applied
0 projection items activated
Loading data for 15 events and 641 original time points ...
0 bad epochs dropped

Now we store these epochs in a BIDS folder as an EEGLAB .set file, together with the sidecar files. The important piece is "RecordingType": "epoched" in the *_eeg.json sidecar, which is how BIDS marks a recording as epoched.

mne_data_dir = Path(mne.get_config("MNE_DATASETS_EEGBCI_PATH"))
bids_root = mne_data_dir / "eegmmidb_bids_epochs_example"
if bids_root.exists():
    shutil.rmtree(bids_root)

bids_path = BIDSPath(
    subject="01",
    task="imagery",
    suffix="eeg",
    extension=".set",
    datatype="eeg",
    root=bids_root,
).mkdir()
mne.export.export_epochs(bids_path.fpath, epochs, overwrite=True)

make_dataset_description(path=bids_root, name="EEGBCI epoched example")
sidecar = {
    "TaskName": "imagery",
    "SamplingFrequency": epochs.info["sfreq"],
    "PowerLineFrequency": 60,
    "EEGReference": "n/a",
    "SoftwareFilters": "n/a",
    "RecordingType": "epoched",
    "EpochLength": len(epochs.times) / epochs.info["sfreq"],
}
bids_path.copy().update(extension=".json").fpath.write_text(
    json.dumps(sidecar, indent=4), encoding="utf-8"
)
channels = pd.DataFrame({"name": epochs.ch_names, "type": "EEG", "units": "µV"})
channels.to_csv(
    bids_path.copy().update(suffix="channels", extension=".tsv").fpath,
    sep="\t",
    index=False,
)
(bids_root / "participants.tsv").write_text(
    "participant_id\nsub-01\n", encoding="utf-8"
)
print_dir_tree(bids_root)
Writing '/home/circleci/mne_data/eegmmidb_bids_epochs_example/dataset_description.json'...
|eegmmidb_bids_epochs_example/
|--- dataset_description.json
|--- participants.tsv
|--- sub-01/
|------ eeg/
|--------- sub-01_task-imagery_channels.tsv
|--------- sub-01_task-imagery_eeg.json
|--------- sub-01_task-imagery_eeg.set

Read the epoched data back#

mne_bids.read_raw_bids() detects the "epoched" recording type and refuses to load the file:

try:
    read_raw_bids(bids_path)
except RuntimeError as error:
    print(error)
RecordingType is "epoched"; use mne_bids.read_epochs_bids() instead of read_raw_bids().

mne_bids.read_epochs_bids() is the right tool for this recording:

epochs_in = read_epochs_bids(bids_path)
epochs_in
Extracting parameters from /home/circleci/mne_data/eegmmidb_bids_epochs_example/sub-01/eeg/sub-01_task-imagery_eeg.set...
Not setting metadata
15 matching events found
No baseline correction applied
0 projection items activated
Ready.
Reading channel info from /home/circleci/mne_data/eegmmidb_bids_epochs_example/sub-01/eeg/sub-01_task-imagery_channels.tsv.
General
Filename(s) sub-01_task-imagery_eeg.set
MNE object type EpochsEEGLAB
Measurement date Unknown
Participant sub-01
Experimenter Unknown
Acquisition
Total number of events 15
Events counts left_fist: 8
right_fist: 7
Time range -1.000 – 3.000 s
Baseline off
Sampling frequency 160.00 Hz
Time points 641
Metadata No metadata set
Channels
EEG
Head & sensor digitization 64 points
Filters
Highpass 0.00 Hz
Lowpass 80.00 Hz


The event labels survived the round trip, so we can select conditions and average as usual:

epochs_in["left_fist"].average().plot()
EEG (64 channels)
<Figure size 640x300 with 1 Axes>

Note

Epoched recordings stored in continuous formats (.edf / .bdf / .vhdr) are also supported: mne_bids.read_epochs_bids() slices them into trials of length EpochLength (from the sidecar), using the onsets in events.tsv when present.

Total running time of the script: (0 minutes 8.665 seconds)

Gallery generated by Sphinx-Gallery