Skip to content

Commit

Permalink
Merge branch 'develop' into within_session
Browse files Browse the repository at this point in the history
  • Loading branch information
bruAristimunha authored Nov 26, 2024
2 parents a785ef7 + e52b71d commit 686572e
Show file tree
Hide file tree
Showing 11 changed files with 129 additions and 19 deletions.
40 changes: 40 additions & 0 deletions docs/source/dataset_summary.rst
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,29 @@ there is a tutorial explaining how to do so, and we welcome warmly any new contr

See also `Datasets-Support <https://github.com/NeuroTechX/moabb/wiki/Datasets-Support>`__ for supplementary
detail on datasets (class name, size, licence, etc.)
Dataset, #Subj, #Chan, #Classes, #Trials, Trial length, Freq, #Session, #Runs, Total_trials, PapersWithCode leaderboard

Columns definitions:
* **Dataset** is the name of the dataset.
* **#Subj** is the number of subjects.
* **#Chan** is the number of EEG channels.
* **#Trials / class** is the number of repetitions performed by one subject for each class. This number is computed using only the first subject of each dataset. *The definitions of a **class** and of a **trial** depend on the paradigm used (see sections below)*.
* **Trial length** is the duration of trial in seconds.
* **Total_trials** is the total number of trials in the dataset (all subjects and classes together).
* **Freq** is the sampling frequency of the raw data.
* **#Session** is the number of sessions per subject. Different sessions are often recorded on different days.
* **#Runs** is the number of runs per session. A run is a continuous recording of the EEG data. Often, the different runs of a given session are recorded without removing the EEG cap in between.
* **PapersWithCode leaderboard** is the link to the dataset on the PapersWithCode leaderboard.

Motor Imagery
======================

Motor Imagery is a BCI paradigm where the subject imagines performing movements. Each movement is associated with a different command to build an application.

Motor Imagery-specific definitions:
* **#Classes** is the number of different imagery tasks.
* **Trial** is one repetition of the imagery task.

.. csv-table::
:file: ../build/summary_imagery.csv
:header-rows: 1
Expand All @@ -30,6 +49,12 @@ Motor Imagery
P300/ERP
======================

ERP (Event-Related Potential) is a BCI paradigm where the subject is presented with a stimulus and the EEG response is recorded. The P300 is a positive peak in the EEG signal that occurs around 300 ms after the stimulus.

P300-specific definitions:
* **A trial** is one flash.
* **The classes** are binary: a trial is **target** if the key on which the subject focuses is flashed and **non-target** otherwise.

.. csv-table::
:file: ../build/summary_p300.csv
:header-rows: 1
Expand All @@ -39,6 +64,13 @@ P300/ERP
SSVEP
======================

SSVEP (Steady-State Visually Evoked Potential) is a BCI paradigm where the subject is presented with flickering stimuli. The EEG signal is modulated at the same frequency as the stimulus. Each stimulus is flickering at a different frequency.

SSVEP-specific definitions:
* **#Classes** is the number of different stimulation frequencies.
* **A trial** is one symbol selection. This includes multiple flashes.


.. csv-table::
:file: ../build/summary_ssvep.csv
:header-rows: 1
Expand All @@ -58,6 +90,14 @@ Hornero, R. (2021). Brain–computer interfaces based on code-modulated visual e
potentials (c-VEP): A literature review. Journal of Neural Engineering, 18(6), 061002.
DOI: https://doi.org/10.1088/1741-2552/ac38cf

c-VEP-specific definitions:
* **A trial** is one symbol selection. This includes multiple flashes.
* **#Trial classes** is the number of different symbols.
* **#Epoch classes** is the number of possible intensities for the flashes (for a visual cVEP paradigm). Typically, there are only two intensities: on and off.
* **#Epochs / class** the number of flashes per intensity in each session.
* **Codes** is the type of code used in the experiment.
* **Presentation rate** is the rate at which the codes are presented.

.. csv-table::
:file: ../build/summary_cvep.csv
:header-rows: 1
Expand Down
7 changes: 6 additions & 1 deletion docs/source/whats_new.rst
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,18 @@ Enhancements
~~~~~~~~~~~~
- Adding :class:`moabb.evaluations.splitters.WithinSessionSplitter` (:gh:`664` by `Bruna Lopes_`)

- Update version of pyRiemann to 0.7 (:gh:`671` by `Gregoire Cattan`_)
- Add columns definitions in the datasets doc (:gh:`672` by `Pierre Guetschel`_)

Bugs
~~~~

- Fix Stieger2021 dataset bugs (:gh:`651` by `Martin Wimpff`_)
- Unpinning major version Scikit-learn and numpy (:gh:`652` by `Bruno Aristimunha`_)
- Replacing the func:`numpy.string_` to func:`numpy.bytes_` (:gh:`665` by `Bruno Aristimunha`_)
- Fixing the set_download_dir that was not working when we tried to set the dir more than 10 times at the same time (:gh:`668` by `Bruno Aristimunha`_)
- Fixing the set_download_dir that was not working when we tried to set the dir more than 10 times at the same time (:gh:`668` by `Bruno Aristimunha`_)
- Creating stimulus channels in :class:`moabb.datasets.Zhou2016` and :class:`moabb.datasets.PhysionetMI` to allow braindecode compatibility (:gh:`669` by `Bruno Aristimunha`_)


API changes
~~~~~~~~~~~
Expand Down
6 changes: 5 additions & 1 deletion moabb/datasets/Zhou2016.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

from .base import BaseDataset
from .download import get_dataset_path
from .utils import stim_channels_with_selected_ids


DATA_PATH = "https://ndownloader.figshare.com/files/3662952"
Expand Down Expand Up @@ -88,6 +89,7 @@ def __init__(self):
paradigm="imagery",
doi="10.1371/journal.pone.0162657",
)
self.events = dict(left_hand=1, right_hand=2, feet=3)

def _get_single_subject_data(self, subject):
"""Return data for a single subject."""
Expand All @@ -105,7 +107,9 @@ def _get_single_subject_data(self, subject):
stim[stim == "2"] = "right_hand"
stim[stim == "3"] = "feet"
raw.annotations.description = stim
out[sess_key][run_key] = raw
out[sess_key][run_key] = stim_channels_with_selected_ids(
raw, desired_event_id=self.events
)
out[sess_key][run_key].set_montage(make_standard_montage("standard_1005"))
return out

Expand Down
11 changes: 6 additions & 5 deletions moabb/datasets/liu2024.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

from moabb.datasets import download as dl
from moabb.datasets.base import BaseDataset
from moabb.datasets.utils import stim_channels_with_selected_ids


# Link to the raw data
Expand Down Expand Up @@ -77,15 +78,15 @@ class Liu2024(BaseDataset):
def __init__(self, break_events=False, instr_events=False):
self.break_events = break_events
self.instr_events = instr_events
events = {"left_hand": 1, "right_hand": 2}
self.events = {"left_hand": 1, "right_hand": 2}
if break_events:
events["instr"] = 3
self.events["instr"] = 3
if instr_events:
events["break"] = 4
self.events["break"] = 4
super().__init__(
subjects=list(range(1, 50 + 1)),
sessions_per_subject=1,
events=events,
events=self.events,
code="Liu2024",
interval=(2, 6),
paradigm="imagery",
Expand Down Expand Up @@ -277,7 +278,7 @@ def _get_single_subject_data(self, subject):
# Loading dataset
raw = raw.load_data(verbose=False)
# There is only one session
sessions = {"0": {"0": raw}}
sessions = {"0": {"0": stim_channels_with_selected_ids(raw, self.event_id)}}

return sessions

Expand Down
6 changes: 4 additions & 2 deletions moabb/datasets/mpi_mi.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

from moabb.datasets import download as dl
from moabb.datasets.base import BaseDataset
from moabb.datasets.utils import stim_channels_with_selected_ids
from moabb.utils import depreciated_alias


Expand Down Expand Up @@ -56,10 +57,11 @@ class GrosseWentrup2009(BaseDataset):
"""

def __init__(self):
self.events_id = dict(right_hand=2, left_hand=1)
super().__init__(
subjects=list(range(1, 11)),
sessions_per_subject=1,
events=dict(right_hand=2, left_hand=1),
events=self.events_id,
code="GrosseWentrup2009",
interval=[0, 7],
paradigm="imagery",
Expand All @@ -76,7 +78,7 @@ def _get_single_subject_data(self, subject):
stim[stim == "20"] = "right_hand"
stim[stim == "10"] = "left_hand"
raw.annotations.description = stim
return {"0": {"0": raw}}
return {"0": {"0": stim_channels_with_selected_ids(raw, self.event_id)}}

def data_path(
self, subject, path=None, force_update=False, update_path=None, verbose=None
Expand Down
11 changes: 8 additions & 3 deletions moabb/datasets/physionet_mi.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

from moabb.datasets.base import BaseDataset
from moabb.datasets.download import data_dl, get_dataset_path
from moabb.datasets.utils import stim_channels_with_selected_ids


BASE_URL = "https://physionet.org/files/eegmmidb/1.0.0/"
Expand Down Expand Up @@ -79,7 +80,7 @@ def __init__(self, imagined=True, executed=False):
paradigm="imagery",
doi="10.1109/TBME.2004.827072",
)

self.events = dict(left_hand=2, right_hand=3, feet=5, hands=4, rest=1)
self.imagined = imagined
self.executed = executed
self.feet_runs = []
Expand Down Expand Up @@ -123,7 +124,9 @@ def _get_single_subject_data(self, subject):
stim[stim == "T1"] = "left_hand"
stim[stim == "T2"] = "right_hand"
raw.annotations.description = stim
data[str(idx)] = raw
data[str(idx)] = stim_channels_with_selected_ids(
raw, desired_event_id=self.events
)
idx += 1

# feet runs
Expand All @@ -136,7 +139,9 @@ def _get_single_subject_data(self, subject):
stim[stim == "T1"] = "hands"
stim[stim == "T2"] = "feet"
raw.annotations.description = stim
data[str(idx)] = raw
data[str(idx)] = stim_channels_with_selected_ids(
raw, desired_event_id=self.events
)
idx += 1

return {"0": data}
Expand Down
6 changes: 4 additions & 2 deletions moabb/datasets/sosulski2019.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

from moabb.datasets import download as dl
from moabb.datasets.base import BaseDataset
from moabb.datasets.utils import stim_channels_with_selected_ids


SPOT_PILOT_P300_URL = (
Expand Down Expand Up @@ -95,12 +96,13 @@ def __init__(
self.n_channels = 31
self.use_soas_as_sessions = use_soas_as_sessions
self.description_map = {"Stimulus/S 21": "Target", "Stimulus/S 1": "NonTarget"}
self.events = dict(Target=21, NonTarget=1)
code = "Sosulski2019"
interval = [-0.2, 1] if interval is None else interval
super().__init__(
subjects=list(range(1, 13 + 1)),
sessions_per_subject=1,
events=dict(Target=21, NonTarget=1),
events=self.events,
code=code,
interval=interval,
paradigm="p300",
Expand Down Expand Up @@ -133,7 +135,7 @@ def _get_single_run_data(self, file_path):
if self.reject_non_iid:
raw.set_annotations(raw.annotations[7:85]) # non-iid rejection
raw.annotations.rename(self.description_map)
return raw
return stim_channels_with_selected_ids(raw, self.events)

def _get_single_subject_data(self, subject):
"""Return data for a single subject."""
Expand Down
2 changes: 1 addition & 1 deletion moabb/datasets/summary_imagery.csv
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Dataset, #Subj, #Chan, #Classes, #Trials, Trial length, Freq, #Session, #Runs, Total_trials, PapersWithCode leaderboard
Dataset, #Subj, #Chan, #Classes, #Trials / class, Trial length, Freq, #Session, #Runs, Total_trials, PapersWithCode leaderboard
AlexMI,8,16,3,20,3s,512Hz,1,1,480,https://paperswithcode.com/dataset/alexandremotorimagery-moabb
BNCI2014_001,9,22,4,144,4s,250Hz,2,6,62208,https://paperswithcode.com/dataset/bnci2014-001-moabb-1
BNCI2014_002,14,15,2,80,5s,512Hz,1,8,17920,https://paperswithcode.com/dataset/bnci2014-002-moabb-1
Expand Down
7 changes: 4 additions & 3 deletions moabb/datasets/upper_limb.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from mne.io import read_raw_gdf

from moabb.datasets.base import BaseDataset
from moabb.datasets.utils import stim_channels_with_selected_ids

from . import download as dl

Expand Down Expand Up @@ -58,7 +59,7 @@ class Ofner2017(BaseDataset):
def __init__(self, imagined=True, executed=False):
self.imagined = imagined
self.executed = executed
event_id = {
self.event_id = {
"right_elbow_flexion": 1536,
"right_elbow_extension": 1537,
"right_supination": 1538,
Expand All @@ -72,7 +73,7 @@ def __init__(self, imagined=True, executed=False):
super().__init__(
subjects=list(range(1, 16)),
sessions_per_subject=n_sessions,
events=event_id,
events=self.event_id,
code="Ofner2017",
interval=[0, 3], # according to paper 2-5
paradigm="imagery",
Expand Down Expand Up @@ -114,7 +115,7 @@ def _get_single_subject_data(self, subject):
stim[stim == "1541"] = "right_hand_open"
stim[stim == "1542"] = "rest"
raw.annotations.description = stim
data[str(ii)] = raw
data[str(ii)] = stim_channels_with_selected_ids(raw, self.event_id)

out[session_name] = data
return out
Expand Down
50 changes: 50 additions & 0 deletions moabb/datasets/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import inspect

import mne
import numpy as np
from mne import create_info
from mne.io import RawArray
Expand Down Expand Up @@ -273,3 +274,52 @@ def add_stim_channel_epoch(
)
raw = raw.add_channels([RawArray(data=stim_chan, info=info, verbose=False)])
return raw


def stim_channels_with_selected_ids(
raw: mne.io.BaseRaw, desired_event_id: dict, stim_channel_name="STIM"
):
"""
Add a stimulus channel with filtering and renaming based on events_ids.
Parameters
----------
raw: mne.Raw
The raw object to add the stimulus channel to.
desired_event_id: dict
Dictionary with events
"""

# Get events using the consistent event_id mapping
events, _ = mne.events_from_annotations(raw, event_id=desired_event_id)

# Filter the events array to include only desired events
desired_event_ids = list(desired_event_id.values())
filtered_events = events[np.isin(events[:, 2], desired_event_ids)]

# Create annotations from filtered events using the inverted mapping
event_desc = {v: k for k, v in desired_event_id.items()}
annot_from_events = mne.annotations_from_events(
events=filtered_events,
event_desc=event_desc,
sfreq=raw.info["sfreq"],
orig_time=raw.info["meas_date"],
)
raw.set_annotations(annot_from_events)

# Create the stim channel data array
stim_channs = np.zeros((1, raw.n_times))
for event in filtered_events:
sample_index = event[0]
event_code = event[2] # Consistent event IDs
stim_channs[0, sample_index] = event_code

# Create the stim channel and add it to raw

stim_info = mne.create_info(
[stim_channel_name], sfreq=raw.info["sfreq"], ch_types=["stim"]
)
stim_raw = mne.io.RawArray(stim_channs, stim_info, verbose=False)
raw_with_stim = raw.copy().add_channels([stim_raw], force_update_info=True)

return raw_with_stim
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ h5py = "^3.10.0"
scikit-learn = ">=1.4.2"
matplotlib = "^3.6.2"
seaborn = "^0.12.1"
pyriemann = "^0.6"
pyriemann = "^0.7"
PyYAML = "^6.0"
pooch = "^1.6.0"
requests = "^2.28.1"
Expand Down

0 comments on commit 686572e

Please sign in to comment.