Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add c-VEP paradigm and first dataset #463

Merged
merged 38 commits into from Aug 23, 2023
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
5d66b05
add thielen2021 cvep dataset
Aug 17, 2023
5ff57bf
add cvep paradigm
Aug 17, 2023
8a3fc82
add thielen2021 cvep dataset tests
Aug 17, 2023
5d7ed20
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Aug 18, 2023
2fbbd93
Merge branch 'develop' into cvep
PierreGtch Aug 18, 2023
ce2dd52
update documentation related to addition c-VEP
Aug 18, 2023
3b28bbe
Update cVEP paradigm according to the changes of #408
PierreGtch Aug 18, 2023
61b88aa
change c-VEP paradigm to (potentially) multi-class
Aug 18, 2023
5aa96b3
add c-VEP paradigm tests
Aug 18, 2023
b46711e
add c-VEP banchmark tests
Aug 18, 2023
1fb1f0f
merge
Aug 18, 2023
ed26730
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Aug 18, 2023
f342731
update c-VEP documentation
Aug 18, 2023
f1a93fc
Merge branch 'cvep' of https://github.com/thijor/moabb into cvep
Aug 18, 2023
0acad37
remove redundant interval parameter from Thielen2021 dataset
Aug 18, 2023
52a1d19
remove redundant tmin/tmax tests from BaseCVEP
Aug 18, 2023
203d5c9
Add thielen2021 to datasets __init__.py
PierreGtch Aug 18, 2023
805ca94
Add default interval
PierreGtch Aug 18, 2023
017b903
Complete scoring cases and add test
PierreGtch Aug 18, 2023
7281703
Reformat events in FakeDataset code
PierreGtch Aug 18, 2023
3189e58
Fix coquille
PierreGtch Aug 18, 2023
b0f095c
Update fake cVEP datasets parameters
PierreGtch Aug 18, 2023
9b2b957
update documentation cvep
Aug 18, 2023
9a1d07a
Merge branch 'develop' into cvep
PierreGtch Aug 19, 2023
21a5cf9
Add link in dataset_summary.rst
PierreGtch Aug 19, 2023
5f2264c
Refactoring the tests
bruAristimunha Aug 19, 2023
ee5faf4
Updating pytest
bruAristimunha Aug 19, 2023
48f51de
Fixing conflict with mne-bids and pytest
bruAristimunha Aug 19, 2023
8be6263
Increase the codacov
bruAristimunha Aug 20, 2023
525b76f
update documentation cvep
Aug 21, 2023
e66c40f
split functionality for reuse and update documentation
Aug 21, 2023
2b75568
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Aug 21, 2023
86edc5f
add channel locations thielen2021 from loc file
Aug 21, 2023
b2d1f0f
Merge branch 'cvep' of https://github.com/thijor/moabb into cvep
Aug 21, 2023
6de909b
Clarify paradigm doc (epoch level decoding)
PierreGtch Aug 21, 2023
5438d14
Merge branch 'develop' into cvep
bruAristimunha Aug 21, 2023
1c06549
Fixing conflict
bruAristimunha Aug 22, 2023
98d6e45
Merge branch 'develop' into cvep
bruAristimunha Aug 22, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
11 changes: 11 additions & 0 deletions docs/source/dataset_summary.rst
Expand Up @@ -78,6 +78,17 @@ SSVEP
Wang2016,34,62,40,6,5s,250Hz,1


c-VEP
======================


bruAristimunha marked this conversation as resolved.
Show resolved Hide resolved
.. csv-table::
:header: Dataset, #Subj, #Chan, #Classes, #Trials / class, Trials length, #Epochs / class, Sampling rate, #Sessions
:class: sortable

Thielen2021,30,8,20,5,31.5s,18900 NT / 18900 T,512Hz,1


Resting States
======================

Expand Down
11 changes: 11 additions & 0 deletions docs/source/datasets.rst
Expand Up @@ -75,6 +75,17 @@ SSVEP Datasets
Lee2019_SSVEP


--------------
c-VEP Datasets
--------------

.. autosummary::
:toctree: generated/
:template: class.rst

Thielen2021


----------------------
Resting State Datasets
----------------------
Expand Down
13 changes: 13 additions & 0 deletions docs/source/paradigms.rst
Expand Up @@ -42,6 +42,19 @@ SSVEP Paradigms
SSVEP
FilterBankSSVEP


---------------
c-VEP Paradigms
---------------

.. autosummary::
:toctree: generated/
:template: class.rst

SinglePass
CVEP


--------------
Fixed Interval Windows Processings
--------------
Expand Down
2 changes: 2 additions & 0 deletions docs/source/whats_new.rst
Expand Up @@ -44,6 +44,7 @@ Enhancements
- Rename many dataset class names to standardize and deprecate old names (:gh:`455` by `Pierre Guetschel`_)
- Change many dataset codes to match the class names (:gh:`455` by `Pierre Guetschel`_)
- Add :obj:`moabb.datasets.compound_dataset.utils.compound_dataset_list` (:gh:`455` by `Pierre Guetschel`_)
- Add c-VEP paradigm and Thielen2021 c-VEP dataset (:gh:`463` by `Jordy Thielen`_)

Bugs
~~~~
Expand Down Expand Up @@ -376,3 +377,4 @@ API changes
.. _Pierre Guetschel: https://github.com/PierreGtch
.. _Ludovic Darmet: https://github.com/ludovicdmt
.. _Thomas Moreau: https://github.com/tommoral
.. _Jordy Thielen: https://github.com/thijor
220 changes: 220 additions & 0 deletions moabb/datasets/thielen2021.py
thijor marked this conversation as resolved.
Show resolved Hide resolved
@@ -0,0 +1,220 @@
import h5py
import mne
import numpy as np
from mne import create_info
from mne.io import RawArray
from scipy.io import loadmat

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


Thielen2021_URL = "https://public.data.donders.ru.nl/dcc/DSC_2018.00122_448_v3"
ELECTRODE_MAPPING = {
PierreGtch marked this conversation as resolved.
Show resolved Hide resolved
"AF3": "Fz",
"F3": "T7",
"FC5": "O1",
"P7": "POz",
"P8": "Oz",
"FC6": "Iz",
"F4": "O2",
"AF4": "T8",
}
SESSIONS = (
"20181128",
"20181206",
"20181217",
"20181217",
"20181217",
"20181218",
"20181218",
"20181219",
"20181219",
"20181220",
"20181220",
"20181220",
"20190107",
"20190107",
"20190110",
"20190110",
"20190110",
"20190117",
"20190117",
"20190118",
"20190118",
"20190118",
"20190220",
"20190222",
"20190225",
"20190301",
"20190307",
"20190308",
"20190311",
"20190311",
)
NR_BLOCKS = 5 # Each session consisted of 5 blocks (i.e., runs)
NR_CYCLES_PER_TRIAL = 15 # Each trial contained 15 cycles of a 2.1 second code
PRESENTATION_RATE = 60 # Codes were presented at a 60 Hz monitor refresh rate
thijor marked this conversation as resolved.
Show resolved Hide resolved


class Thielen2021(BaseDataset):
"""c-VEP dataset from Thielen et al. (2021)

Dataset [1]_ from the study on zero-training c-VEP [2]_.

.. admonition:: Dataset summary

============= ======= ======= ================== =============== =============== ===========
Name #Subj #Chan #Trials / class Trials length Sampling rate #Sessions
============= ======= ======= ================== =============== =============== ===========
Thielen2021 30 8 18900 NT / 18900 T 0.3s 512Hz 1
============= ======= ======= ================== =============== =============== ===========

**Dataset description**

EEG recordings were acquired at a sampling rate of 512 Hz, employing 8 Ag/AgCl electrodes. The Biosemi ActiveTwo EEG
amplifier was utilized during the experiment. The electrode array consisted of Fz, T7, O1, POz, Oz, Iz, O2, and T8,
connected as EXG channels.

During the experimental sessions, participants engaged in passive operation (i.e., without feedback) of a 4 x 5
visual speller Brain-Computer Interface (BCI), comprising 20 distinct classes. Each cell of the symbol grid
thijor marked this conversation as resolved.
Show resolved Hide resolved
underwent luminance modulation at full contrast, accomplished through pseudo-random noise-codes derived from a
collection of modulated Gold codes. These codes are binary, have a balanced distribution of ones and zeros, and
adhering to a limited run-length pattern (maximum run-length of 2 bits). The codes were presented at a presentation
thijor marked this conversation as resolved.
Show resolved Hide resolved
rate of 60 Hz. As one cycle of these modulated Gold codes contains 126 bits, the duration of one cycle is 2.1
seconds.

For each of the five blocks, a trial started with a cueing phase, during which the target symbol was highlighted in
a green hue for a duration of 1 second. Following this, participants maintained their gaze fixated on the target
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
a green hue for a duration of 1 second. Following this, participants maintained their gaze fixated on the target
a green hue for 1 second. Following this, participants maintained their gaze fixated on the target

symbol while all symbols flashed in accordance with their respective pseudo-random noise-codes for a duration of
31.5 seconds (i.e., 15 code cycles). Each block encompassed 20 trials, presented in a randomized sequence, thereby
Comment on lines +102 to +103
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
symbol while all symbols flashed in accordance with their respective pseudo-random noise-codes for a duration of
31.5 seconds (i.e., 15 code cycles). Each block encompassed 20 trials, presented in a randomized sequence, thereby
symbol while all symbols flashed in following their respective pseudo-random noise-codes for
31.5 seconds (i.e., 15 code cycles). Each block encompassed 20 trials, presented in a randomized sequence,

ensuring that each symbol was attended to once within the span of a block.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
ensuring that each symbol was attended to once within the span of a block.
ensuring that each symbol was attended to once within a block.


Note, here, we only load the offline data of this study, and ignore the online phase.
thijor marked this conversation as resolved.
Show resolved Hide resolved

References
----------

.. [1] Thielen, J. (Jordy), Pieter Marsman, Jason Farquhar, Desain, P.W.M. (Peter) (2023): From full calibration to
zero training for a code-modulated visual evoked potentials brain computer interface. Version 3. Radboud
University. (dataset). DOI: https://doi.org/10.34973/9txv-z787

.. [2] Thielen, J., Marsman, P., Farquhar, J., & Desain, P. (2021). From full calibration to zero training for a
code-modulated visual evoked potentials for brain–computer interface. Journal of Neural Engineering, 18(5),
056007. DOI: https://doi.org/10.1088/1741-2552/abecef

Notes
-----

.. versionadded:: 0.6.0

"""

def __init__(self, interval=None):
thijor marked this conversation as resolved.
Show resolved Hide resolved
super().__init__(
subjects=list(range(1, 30 + 1)),
sessions_per_subject=1,
events={"1.0": 101, "0.0": 100},
code="Thielen2021",
interval=[0, 0.3] if interval is None else interval,
paradigm="cvep",
doi="10.34973/9txv-z787",
)

def _get_single_subject_data(self, subject):
file_path_list = self.data_path(subject)

# Codes
codes = np.tile(loadmat(file_path_list[-1])["codes"], (NR_CYCLES_PER_TRIAL, 1))

# There is only one session, each of 5 blocks (i.e., runs)
sessions = {"session_1": {}}
for i_b in range(NR_BLOCKS):
# EEG
raw = mne.io.read_raw_gdf(
file_path_list[2 * i_b],
stim_channel="status",
preload=True,
verbose=False,
)
mne.rename_channels(raw.info, ELECTRODE_MAPPING)
thijor marked this conversation as resolved.
Show resolved Hide resolved

# Labels at trial level (i.e., symbols)
labels = (
np.array(h5py.File(file_path_list[2 * i_b + 1], "r")["v"])
.astype("uint8")
.flatten()
- 1
)

# Find onsets of trials
# N.B. Every 2.1 seconds an event was generated (15 times per trial, plus one 16th "leaking epoch")
thijor marked this conversation as resolved.
Show resolved Hide resolved
# N.B. This "leaking epoch" is not always present, so taking epoch[::16, :] won't work
events = mne.find_events(raw, verbose=False)
cond = np.logical_or(
np.diff(events[:, 0]) < 1.8 * raw.info["sfreq"],
np.diff(events[:, 0]) > 2.4 * raw.info["sfreq"],
)
idx = np.concatenate(([0], 1 + np.where(cond)[0]))
onsets = events[idx, 0]

# Create stim channel with trial information (i.e., symbols)
# N.B. 200 = symbol-0, 201 = symbol-1, 202 = symbol-2, etc.
stim_chan = np.zeros((1, len(raw)))
for onset, label in zip(onsets, labels):
stim_chan[0, onset] = 200 + label
info = create_info(
ch_names=["stim_trial"],
ch_types=["stim"],
sfreq=raw.info["sfreq"],
verbose=False,
)
raw = raw.add_channels([RawArray(data=stim_chan, info=info, verbose=False)])

# Create stim channel with epoch information (i.e., 1 / 0, or on / off)
# N.B. 100 = "0", 101 = "1"
stim_chan = np.zeros((1, len(raw)))
for onset, label in zip(onsets, labels):
idx = (
onset
+ np.arange(codes.shape[0]) / PRESENTATION_RATE * raw.info["sfreq"]
).astype("int")
stim_chan[0, idx] = 100 + codes[:, label]
info = create_info(
ch_names=["stim_epoch"],
ch_types=["stim"],
sfreq=raw.info["sfreq"],
verbose=False,
)
raw = raw.add_channels([RawArray(data=stim_chan, info=info, verbose=False)])

# Add data as a new run
sessions["session_1"][f"run_{1 + i_b:02d}"] = raw

return sessions

def data_path(
self, subject, path=None, force_update=False, update_path=None, verbose=None
):
if subject not in self.subject_list:
raise (ValueError("Invalid subject number"))

sub = f"sub-{subject:02d}"
ses = SESSIONS[subject - 1]
subject_paths = []
for i_b in range(NR_BLOCKS):
blk = f"block_{1 + i_b:d}"

# EEG
url = f"{Thielen2021_URL:s}/sourcedata/offline/{sub}/{blk}/{sub}_{ses}_{blk}_main_eeg.gdf"
subject_paths.append(dl.data_dl(url, self.code, path, force_update, verbose))

# Labels at trial level (i.e., symbols)
url = f"{Thielen2021_URL:s}/sourcedata/offline/{sub}/{blk}/trainlabels.mat"
subject_paths.append(dl.data_dl(url, self.code, path, force_update, verbose))

# Codes
url = f"{Thielen2021_URL:s}/resources/mgold_61_6521_flip_balanced_20.mat"
subject_paths.append(dl.data_dl(url, self.code, path, force_update, verbose))

return subject_paths
4 changes: 2 additions & 2 deletions moabb/paradigms/__init__.py
Expand Up @@ -5,10 +5,10 @@
have two-class, multi-class, or continuous paradigms; similarly,
different preprocessing is necessary for ERP vs ERD paradigms.
"""
# flake8: noqa
from moabb.paradigms.cvep import *
from moabb.paradigms.fixed_interval_windows import *
from moabb.paradigms.motor_imagery import *

# flake8: noqa
from moabb.paradigms.p300 import *
from moabb.paradigms.resting_state import *
from moabb.paradigms.ssvep import *