From c1cac5c06180d6c4eb7e538463d99e8a635c683f Mon Sep 17 00:00:00 2001 From: tahatt13 Date: Fri, 7 Jun 2024 16:52:28 +0200 Subject: [PATCH 01/35] including Liu2024 Dataset --- docs/source/dataset_summary.rst | 1 + docs/source/datasets.rst | 2 +- docs/source/whats_new.rst | 1 + moabb/datasets/__init__.py | 2 +- moabb/datasets/liu2024.py | 138 ++++++++++++++++++++++++++++++++ 5 files changed, 142 insertions(+), 2 deletions(-) create mode 100644 moabb/datasets/liu2024.py diff --git a/docs/source/dataset_summary.rst b/docs/source/dataset_summary.rst index 24a3ca229..f919d4415 100644 --- a/docs/source/dataset_summary.rst +++ b/docs/source/dataset_summary.rst @@ -42,6 +42,7 @@ Motor Imagery :class:`Weibo2014`,10,60,7,80,4s,200Hz,1,1,5600 :class:`Zhou2016`,4,14,3,160,5s,250Hz,3,2,11496 :class:`Stieger2021`,62,64,4,450,3s,1000Hz,7 or 11,1,250000 + :class:`Liu2024`,62,64,4,450,3s,1000Hz,7 or 11,1,250000 P300/ERP ====================== diff --git a/docs/source/datasets.rst b/docs/source/datasets.rst index b13fc7dfc..2d3596ba6 100644 --- a/docs/source/datasets.rst +++ b/docs/source/datasets.rst @@ -31,7 +31,7 @@ Motor Imagery Datasets Weibo2014 Zhou2016 Stieger2021 - + Liu2024 ------------ ERP Datasets diff --git a/docs/source/whats_new.rst b/docs/source/whats_new.rst index 3d06d2f6b..76651d43c 100644 --- a/docs/source/whats_new.rst +++ b/docs/source/whats_new.rst @@ -468,3 +468,4 @@ API changes .. _Brian Irvine: https://github.com/brianjohannes .. _Bruna Lopes: https://github.com/brunaafl .. _Yash Chauhan: https://github.com/jiggychauhi +.. _Taha Habib: https://github.com/tahatt13 \ No newline at end of file diff --git a/moabb/datasets/__init__.py b/moabb/datasets/__init__.py index b8cf8a116..5dd9a0307 100644 --- a/moabb/datasets/__init__.py +++ b/moabb/datasets/__init__.py @@ -81,7 +81,7 @@ from .utils import _init_dataset_list from .Weibo2014 import Weibo2014 from .Zhou2016 import Zhou2016 - +from .liu2024 import Liu2024 # Call this last in order to make sure the dataset list is populated with # the datasets imported in this file. diff --git a/moabb/datasets/liu2024.py b/moabb/datasets/liu2024.py new file mode 100644 index 000000000..6baa2dad7 --- /dev/null +++ b/moabb/datasets/liu2024.py @@ -0,0 +1,138 @@ +import mne +import numpy as np +from scipy.io import loadmat + +from moabb.datasets import download as dl +from moabb.datasets.base import BaseDataset +from moabb.datasets.utils import add_stim_channel_epoch, add_stim_channel_trial + + +_LIU2024_URL = "XXXX" + +class Liu2024(BaseDataset): + """ + + Dataset [1]_ from the study + + .. admonition:: Dataset summary + + + ============ ======= ======= ========== ================= ============ =============== =========== + Name #Subj #Chan #Classes #Trials / class Trials len Sampling rate #Sessions + ============ ======= ======= ========== ================= ============ =============== =========== + BNCI2014_001 9 22 4 144 4s 250Hz 2 + ============ ======= ======= ========== ================= ============ =============== =========== + + + **Dataset description** + + References + ---------- + + .. [1] + + Notes + ----- + + .. versionadded:: 1.0.0 + + """ + + def __init__(self): + super().__init__( + subjects=list(range(1, 12 + 1)), + sessions_per_subject=1, + events={"1.0": 101, "0.0": 100}, + code="Thielen2015", + interval=(0, 0.3), + paradigm="imagery", + doi="10.34973/1ecz-1232", + ) + + def _get_single_subject_data(self, subject): + """Return the data of a single subject.""" + file_path_list = self.data_path(subject) + + # Channels + montage = mne.channels.read_custom_montage(file_path_list[-1]) + + # There is only one session, each of 3 runs + sessions = {"0": {}} + for i_b in range(NR_RUNS): + # EEG + raw = mne.io.read_raw_gdf( + file_path_list[2 * i_b], + stim_channel="status", + preload=True, + verbose=False, + ) + + # Drop redundant ANA and EXG channels + ana = [f"ANA{1 + i}" for i in range(32)] + exg = [f"EXG{1 + i}" for i in range(8)] + raw.drop_channels(ana + exg) + + # Set electrode positions + raw.set_montage(montage) + + # Read info file + tmp = loadmat(file_path_list[2 * i_b + 1]) + + # Labels at trial level (i.e., symbols) + trial_labels = tmp["labels"].astype("uint8").flatten() - 1 + + # Codes (select optimized subset and layout, and repeat to trial length) + subset = ( + tmp["subset"].astype("uint8").flatten() - 1 + ) # the optimized subset of 36 codes from a set of 65 + layout = ( + tmp["layout"].astype("uint8").flatten() - 1 + ) # the optimized position of the 36 codes in the grid + codes = tmp["codes"][:, subset[layout]] + codes = np.tile(codes, (NR_CYCLES_PER_TRIAL, 1)) + + # Find onsets of trials + events = mne.find_events(raw, verbose=False) + trial_onsets = events[:, 0] + + # Create stim channel with trial information (i.e., symbols) + # Specifically: 200 = symbol-0, 201 = symbol-1, 202 = symbol-2, etc. + raw = add_stim_channel_trial(raw, trial_onsets, trial_labels, offset=200) + + # Create stim channel with epoch information (i.e., 1 / 0, or on / off) + # Specifically: 100 = "0", 101 = "1" + raw = add_stim_channel_epoch( + raw, trial_onsets, trial_labels, codes, PRESENTATION_RATE, offset=100 + ) + + # Add data as a new run + run_name = str(i_b) + sessions["0"][run_name] = raw + + return sessions + + def data_path( + self, subject, path=None, force_update=False, update_path=None, verbose=None + ): + """Return the data paths of a single subject.""" + if subject not in self.subject_list: + raise (ValueError("Invalid subject number")) + + sub = f"sub-{subject:02d}" + subject_paths = [] + for i_b in range(NR_RUNS): + blk = f"test_sync_{1 + i_b:d}" + + # EEG + url = f"{Thielen2015_URL:s}/sourcedata/{sub}/{blk}/{sub}_{blk}.gdf" + subject_paths.append(dl.data_dl(url, self.code, path, force_update, verbose)) + + # Labels at trial level (i.e., symbols) + url = f"{Thielen2015_URL:s}/sourcedata/{sub}/{blk}/{sub}_{blk}.mat" + subject_paths.append(dl.data_dl(url, self.code, path, force_update, verbose)) + + # Channel locations + url = f"{Thielen2015_URL:s}/resources/biosemi64.loc" + subject_paths.append(dl.data_dl(url, self.code, path, force_update, verbose)) + + return subject_paths From f20d296a4db3a750e5835daabf51f01cd430865b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 7 Jun 2024 14:53:45 +0000 Subject: [PATCH 02/35] [pre-commit.ci] auto fixes from pre-commit.com hooks --- docs/source/whats_new.rst | 2 +- moabb/datasets/__init__.py | 3 ++- moabb/datasets/liu2024.py | 5 +++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/source/whats_new.rst b/docs/source/whats_new.rst index 76651d43c..c112796ff 100644 --- a/docs/source/whats_new.rst +++ b/docs/source/whats_new.rst @@ -468,4 +468,4 @@ API changes .. _Brian Irvine: https://github.com/brianjohannes .. _Bruna Lopes: https://github.com/brunaafl .. _Yash Chauhan: https://github.com/jiggychauhi -.. _Taha Habib: https://github.com/tahatt13 \ No newline at end of file +.. _Taha Habib: https://github.com/tahatt13 diff --git a/moabb/datasets/__init__.py b/moabb/datasets/__init__.py index 5dd9a0307..5b3a41fae 100644 --- a/moabb/datasets/__init__.py +++ b/moabb/datasets/__init__.py @@ -61,6 +61,7 @@ from .hinss2021 import Hinss2021 from .huebner_llp import Huebner2017, Huebner2018 from .Lee2019 import Lee2019_ERP, Lee2019_MI, Lee2019_SSVEP +from .liu2024 import Liu2024 from .mpi_mi import MunichMI # noqa: F401 from .mpi_mi import GrosseWentrup2009 from .neiry import DemonsP300 @@ -81,7 +82,7 @@ from .utils import _init_dataset_list from .Weibo2014 import Weibo2014 from .Zhou2016 import Zhou2016 -from .liu2024 import Liu2024 + # Call this last in order to make sure the dataset list is populated with # the datasets imported in this file. diff --git a/moabb/datasets/liu2024.py b/moabb/datasets/liu2024.py index 6baa2dad7..aadeb9403 100644 --- a/moabb/datasets/liu2024.py +++ b/moabb/datasets/liu2024.py @@ -9,10 +9,11 @@ _LIU2024_URL = "XXXX" + class Liu2024(BaseDataset): """ - Dataset [1]_ from the study + Dataset [1]_ from the study .. admonition:: Dataset summary @@ -29,7 +30,7 @@ class Liu2024(BaseDataset): References ---------- - .. [1] + .. [1] Notes ----- From 3dfa4f194dab26af8592d60c16202d19471e94af Mon Sep 17 00:00:00 2001 From: tahatt13 Date: Mon, 10 Jun 2024 15:47:43 +0200 Subject: [PATCH 03/35] Function data_path --- moabb/datasets/liu2024.py | 75 ++++++++++++++++++++++----------------- 1 file changed, 43 insertions(+), 32 deletions(-) diff --git a/moabb/datasets/liu2024.py b/moabb/datasets/liu2024.py index 6baa2dad7..e0d4e1ffb 100644 --- a/moabb/datasets/liu2024.py +++ b/moabb/datasets/liu2024.py @@ -1,5 +1,8 @@ import mne import numpy as np +import os +import zipfile as z +from pathlib import Path from scipy.io import loadmat from moabb.datasets import download as dl @@ -7,12 +10,12 @@ from moabb.datasets.utils import add_stim_channel_epoch, add_stim_channel_trial -_LIU2024_URL = "XXXX" +_LIU2024_URL = "https://figshare.com/articles/dataset/EEG_datasets_of_stroke_patients/21679035/5" class Liu2024(BaseDataset): """ - Dataset [1]_ from the study + Dataset [1]_ from the study on burst-VEP [2]_. .. admonition:: Dataset summary @@ -29,8 +32,13 @@ class Liu2024(BaseDataset): References ---------- - .. [1] + .. [1] Liu, Haijie; Lv, Xiaodong (2022). EEG datasets of stroke patients. figshare. Dataset. + DOI: https://doi.org/10.6084/m9.figshare.21679035.v5 + .. [2] Liu, H., Wei, P., Wang, H. et al. An EEG motor imagery dataset for brain computer interface in acute stroke + patients. Sci Data 11, 131 (2024). + DOI: https://doi.org/10.1038/s41597-023-02787-8 + Notes ----- @@ -40,15 +48,41 @@ class Liu2024(BaseDataset): def __init__(self): super().__init__( - subjects=list(range(1, 12 + 1)), + subjects=list(range(1, 9 + 1)), sessions_per_subject=1, - events={"1.0": 101, "0.0": 100}, - code="Thielen2015", - interval=(0, 0.3), + events={"right_hand": 1, "left_hand": 2}, + code="Liu2024", + interval=(0, 4), paradigm="imagery", - doi="10.34973/1ecz-1232", + doi="10.6084/m9.figshare.21679035.v5", ) + def data_path( + self, subject, path=None, force_update=False, update_path=None, verbose=None + ): + """Return the data paths of a single subject.""" + if subject not in self.subject_list: + raise (ValueError("Invalid subject number")) + + subject_paths = [] + + url = "https://figshare.com/ndownloader/files/38516654" + path_zip = dl.data_dl(url, self.code) + #sub = f"sub-{subject:02d}" + path_zip = Path(path_zip) + path_folder = path_zip.parent + + if not (path_folder / "edffile").is_dir(): + zip_ref = z.ZipFile(path_zip, "r") + zip_ref.extractall(path_folder) + sub = f"sub-{subject:02d}" + + subject_path = path_folder / "edffile" / sub / "eeg" / f"{sub}_task-motor-imagery_eeg.edf" + subject_paths.append(str(subject_path)) + + return subject_paths + + def _get_single_subject_data(self, subject): """Return the data of a single subject.""" file_path_list = self.data_path(subject) @@ -111,28 +145,5 @@ def _get_single_subject_data(self, subject): return sessions - def data_path( - self, subject, path=None, force_update=False, update_path=None, verbose=None - ): - """Return the data paths of a single subject.""" - if subject not in self.subject_list: - raise (ValueError("Invalid subject number")) - sub = f"sub-{subject:02d}" - subject_paths = [] - for i_b in range(NR_RUNS): - blk = f"test_sync_{1 + i_b:d}" - - # EEG - url = f"{Thielen2015_URL:s}/sourcedata/{sub}/{blk}/{sub}_{blk}.gdf" - subject_paths.append(dl.data_dl(url, self.code, path, force_update, verbose)) - - # Labels at trial level (i.e., symbols) - url = f"{Thielen2015_URL:s}/sourcedata/{sub}/{blk}/{sub}_{blk}.mat" - subject_paths.append(dl.data_dl(url, self.code, path, force_update, verbose)) - - # Channel locations - url = f"{Thielen2015_URL:s}/resources/biosemi64.loc" - subject_paths.append(dl.data_dl(url, self.code, path, force_update, verbose)) - - return subject_paths + \ No newline at end of file From 3532270357851e76bb6d68b2a8d1439db754f271 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 10 Jun 2024 13:52:02 +0000 Subject: [PATCH 04/35] [pre-commit.ci] auto fixes from pre-commit.com hooks --- moabb/datasets/liu2024.py | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/moabb/datasets/liu2024.py b/moabb/datasets/liu2024.py index e0d4e1ffb..485564b06 100644 --- a/moabb/datasets/liu2024.py +++ b/moabb/datasets/liu2024.py @@ -1,8 +1,8 @@ -import mne -import numpy as np -import os import zipfile as z from pathlib import Path + +import mne +import numpy as np from scipy.io import loadmat from moabb.datasets import download as dl @@ -10,12 +10,15 @@ from moabb.datasets.utils import add_stim_channel_epoch, add_stim_channel_trial -_LIU2024_URL = "https://figshare.com/articles/dataset/EEG_datasets_of_stroke_patients/21679035/5" +_LIU2024_URL = ( + "https://figshare.com/articles/dataset/EEG_datasets_of_stroke_patients/21679035/5" +) + class Liu2024(BaseDataset): """ - Dataset [1]_ from the study on burst-VEP [2]_. + Dataset [1]_ from the study on burst-VEP [2]_. .. admonition:: Dataset summary @@ -38,7 +41,7 @@ class Liu2024(BaseDataset): .. [2] Liu, H., Wei, P., Wang, H. et al. An EEG motor imagery dataset for brain computer interface in acute stroke patients. Sci Data 11, 131 (2024). DOI: https://doi.org/10.1038/s41597-023-02787-8 - + Notes ----- @@ -67,8 +70,8 @@ def data_path( subject_paths = [] url = "https://figshare.com/ndownloader/files/38516654" - path_zip = dl.data_dl(url, self.code) - #sub = f"sub-{subject:02d}" + path_zip = dl.data_dl(url, self.code) + # sub = f"sub-{subject:02d}" path_zip = Path(path_zip) path_folder = path_zip.parent @@ -77,12 +80,13 @@ def data_path( zip_ref.extractall(path_folder) sub = f"sub-{subject:02d}" - subject_path = path_folder / "edffile" / sub / "eeg" / f"{sub}_task-motor-imagery_eeg.edf" + subject_path = ( + path_folder / "edffile" / sub / "eeg" / f"{sub}_task-motor-imagery_eeg.edf" + ) subject_paths.append(str(subject_path)) return subject_paths - def _get_single_subject_data(self, subject): """Return the data of a single subject.""" file_path_list = self.data_path(subject) @@ -144,6 +148,3 @@ def _get_single_subject_data(self, subject): sessions["0"][run_name] = raw return sessions - - - \ No newline at end of file From b474ee8d7ef0383f41d3838b099ee41428b44dac Mon Sep 17 00:00:00 2001 From: tahatt13 Date: Tue, 11 Jun 2024 15:10:34 +0200 Subject: [PATCH 05/35] data_infos and get_single_subject_data functions --- moabb/datasets/liu2024.py | 104 ++++++++++++++++---------------------- 1 file changed, 43 insertions(+), 61 deletions(-) diff --git a/moabb/datasets/liu2024.py b/moabb/datasets/liu2024.py index 485564b06..dfda00a19 100644 --- a/moabb/datasets/liu2024.py +++ b/moabb/datasets/liu2024.py @@ -3,11 +3,13 @@ import mne import numpy as np +import pandas as pd from scipy.io import loadmat from moabb.datasets import download as dl from moabb.datasets.base import BaseDataset from moabb.datasets.utils import add_stim_channel_epoch, add_stim_channel_trial +from mne.channels import read_custom_montage _LIU2024_URL = ( @@ -18,7 +20,7 @@ class Liu2024(BaseDataset): """ - Dataset [1]_ from the study on burst-VEP [2]_. + Dataset [1]_ from the study on motor imagery [2]_. .. admonition:: Dataset summary @@ -26,12 +28,13 @@ class Liu2024(BaseDataset): ============ ======= ======= ========== ================= ============ =============== =========== Name #Subj #Chan #Classes #Trials / class Trials len Sampling rate #Sessions ============ ======= ======= ========== ================= ============ =============== =========== - BNCI2014_001 9 22 4 144 4s 250Hz 2 + BNCI2014_001 50 33 2 40 4s 500Hz 1 ============ ======= ======= ========== ================= ============ =============== =========== **Dataset description** + References ---------- @@ -45,20 +48,34 @@ class Liu2024(BaseDataset): Notes ----- - .. versionadded:: 1.0.0 + .. versionadded:: 1.1.0 """ def __init__(self): super().__init__( - subjects=list(range(1, 9 + 1)), + subjects=list(range(1, 50 + 1)), sessions_per_subject=1, - events={"right_hand": 1, "left_hand": 2}, + events={ "left_hand": 0, "right_hand": 1}, code="Liu2024", interval=(0, 4), paradigm="imagery", doi="10.6084/m9.figshare.21679035.v5", ) + + def data_infos(self): + """Returns the data paths of the channels, electrodes and events informations""" + + url_electrodes = "https://figshare.com/ndownloader/files/38516078" + url_channels = "https://figshare.com/ndownloader/files/38516069" + url_events = "https://figshare.com/ndownloader/files/38516084" + + path_channels = dl.data_dl(url_channels, self.code + "channels" ) + path_electrodes = dl.data_dl(url_electrodes, self.code + "electrodes" ) + path_events = dl.data_dl(url_events, self.code + "events" ) + + return path_channels, path_electrodes, path_events + def data_path( self, subject, path=None, force_update=False, update_path=None, verbose=None @@ -71,7 +88,6 @@ def data_path( url = "https://figshare.com/ndownloader/files/38516654" path_zip = dl.data_dl(url, self.code) - # sub = f"sub-{subject:02d}" path_zip = Path(path_zip) path_folder = path_zip.parent @@ -90,61 +106,27 @@ def data_path( def _get_single_subject_data(self, subject): """Return the data of a single subject.""" file_path_list = self.data_path(subject) + path_channels, path_electrodes, path_events = self.data_infos() + + # Read the subject's data + raw = mne.io.read_raw_edf(file_path_list[0], preload=False) + + # Selecting the EEG channels and the STIM channel + selected_channels = raw.info['ch_names'][:-3] + [''] + selected_channels.remove("CPz") - # Channels - montage = mne.channels.read_custom_montage(file_path_list[-1]) + raw = raw.pick(selected_channels) - # There is only one session, each of 3 runs + # Updating the types of the channels + channel_types = channel_types[:-2] + ['stim'] + channel_dict = dict(zip(selected_channels, channel_types)) + raw.info.set_channel_types(channel_dict) + + montage = read_custom_montage(path_electrodes) + raw.set_montage(montage, on_missing='ignore') + + # There is only one session sessions = {"0": {}} - for i_b in range(NR_RUNS): - # EEG - raw = mne.io.read_raw_gdf( - file_path_list[2 * i_b], - stim_channel="status", - preload=True, - verbose=False, - ) - - # Drop redundant ANA and EXG channels - ana = [f"ANA{1 + i}" for i in range(32)] - exg = [f"EXG{1 + i}" for i in range(8)] - raw.drop_channels(ana + exg) - - # Set electrode positions - raw.set_montage(montage) - - # Read info file - tmp = loadmat(file_path_list[2 * i_b + 1]) - - # Labels at trial level (i.e., symbols) - trial_labels = tmp["labels"].astype("uint8").flatten() - 1 - - # Codes (select optimized subset and layout, and repeat to trial length) - subset = ( - tmp["subset"].astype("uint8").flatten() - 1 - ) # the optimized subset of 36 codes from a set of 65 - layout = ( - tmp["layout"].astype("uint8").flatten() - 1 - ) # the optimized position of the 36 codes in the grid - codes = tmp["codes"][:, subset[layout]] - codes = np.tile(codes, (NR_CYCLES_PER_TRIAL, 1)) - - # Find onsets of trials - events = mne.find_events(raw, verbose=False) - trial_onsets = events[:, 0] - - # Create stim channel with trial information (i.e., symbols) - # Specifically: 200 = symbol-0, 201 = symbol-1, 202 = symbol-2, etc. - raw = add_stim_channel_trial(raw, trial_onsets, trial_labels, offset=200) - - # Create stim channel with epoch information (i.e., 1 / 0, or on / off) - # Specifically: 100 = "0", 101 = "1" - raw = add_stim_channel_epoch( - raw, trial_onsets, trial_labels, codes, PRESENTATION_RATE, offset=100 - ) - - # Add data as a new run - run_name = str(i_b) - sessions["0"][run_name] = raw - - return sessions + sessions["0"] = raw + + return sessions \ No newline at end of file From ef26d1c31c1a6461ed51b493e50b0402fc1b35ef Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 11 Jun 2024 13:10:59 +0000 Subject: [PATCH 06/35] [pre-commit.ci] auto fixes from pre-commit.com hooks --- moabb/datasets/liu2024.py | 33 ++++++++++++++------------------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/moabb/datasets/liu2024.py b/moabb/datasets/liu2024.py index dfda00a19..95a1da5de 100644 --- a/moabb/datasets/liu2024.py +++ b/moabb/datasets/liu2024.py @@ -2,14 +2,10 @@ from pathlib import Path import mne -import numpy as np -import pandas as pd -from scipy.io import loadmat +from mne.channels import read_custom_montage from moabb.datasets import download as dl from moabb.datasets.base import BaseDataset -from moabb.datasets.utils import add_stim_channel_epoch, add_stim_channel_trial -from mne.channels import read_custom_montage _LIU2024_URL = ( @@ -56,26 +52,25 @@ def __init__(self): super().__init__( subjects=list(range(1, 50 + 1)), sessions_per_subject=1, - events={ "left_hand": 0, "right_hand": 1}, + events={"left_hand": 0, "right_hand": 1}, code="Liu2024", interval=(0, 4), paradigm="imagery", doi="10.6084/m9.figshare.21679035.v5", ) - + def data_infos(self): """Returns the data paths of the channels, electrodes and events informations""" - + url_electrodes = "https://figshare.com/ndownloader/files/38516078" url_channels = "https://figshare.com/ndownloader/files/38516069" url_events = "https://figshare.com/ndownloader/files/38516084" - - path_channels = dl.data_dl(url_channels, self.code + "channels" ) - path_electrodes = dl.data_dl(url_electrodes, self.code + "electrodes" ) - path_events = dl.data_dl(url_events, self.code + "events" ) - return path_channels, path_electrodes, path_events + path_channels = dl.data_dl(url_channels, self.code + "channels") + path_electrodes = dl.data_dl(url_electrodes, self.code + "electrodes") + path_events = dl.data_dl(url_events, self.code + "events") + return path_channels, path_electrodes, path_events def data_path( self, subject, path=None, force_update=False, update_path=None, verbose=None @@ -110,23 +105,23 @@ def _get_single_subject_data(self, subject): # Read the subject's data raw = mne.io.read_raw_edf(file_path_list[0], preload=False) - + # Selecting the EEG channels and the STIM channel - selected_channels = raw.info['ch_names'][:-3] + [''] + selected_channels = raw.info["ch_names"][:-3] + [""] selected_channels.remove("CPz") raw = raw.pick(selected_channels) - # Updating the types of the channels - channel_types = channel_types[:-2] + ['stim'] + # Updating the types of the channels + channel_types = channel_types[:-2] + ["stim"] channel_dict = dict(zip(selected_channels, channel_types)) raw.info.set_channel_types(channel_dict) montage = read_custom_montage(path_electrodes) - raw.set_montage(montage, on_missing='ignore') + raw.set_montage(montage, on_missing="ignore") # There is only one session sessions = {"0": {}} sessions["0"] = raw - return sessions \ No newline at end of file + return sessions From be9c82cdb1c45ae18dd8cf0c1af73041f5a47996 Mon Sep 17 00:00:00 2001 From: tahatt13 Date: Tue, 11 Jun 2024 15:11:43 +0200 Subject: [PATCH 07/35] Data Description --- moabb/datasets/liu2024.py | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/moabb/datasets/liu2024.py b/moabb/datasets/liu2024.py index dfda00a19..5d0ecfbe9 100644 --- a/moabb/datasets/liu2024.py +++ b/moabb/datasets/liu2024.py @@ -33,7 +33,30 @@ class Liu2024(BaseDataset): **Dataset description** - + This dataset includes data from 50 acute stroke patients (the time after stroke ranges from 1 day to 30 days) + admitted to the stroke unit of Xuanwu Hospital of Capital Medical University. The patients included 39 males (78%) + and 11 females (22%), aged between 31 and 77 years, with an average age of 56.70 years (SD = 10.57) + Before the start of the experiment, the subject sat in a chair in a position as comfortable as possible with an + EEG cap placed on their head; subjects were positioned approximately 80 cm away from a computer screen in front of them. + The computer played audio instructions to the patient about the procedure. Each experiment lasted approximately 20 minutes, + including preparation time and approximately 10 minutes of signal recording. Before the start of the MI experiment, + the patients opened their eyes and closed their eyes for 1 minute each. The MI experiment was divided into 40 trials, and + each trial took 8 seconds, which consisted of three stages (instruction, MI and break). In the instruction stage, patients + were prompted to imagine grasping a spherical object with the left- or right-hand. In the MI stage, participants imagined + performing this action, a video of gripping motion is played on the computer, which leads the patient imagine grabbing the + ball. This video stays playing for 4 s. Patients only imagine one hand movement.In the break stage, participants were allowed + to relax and rest. The MI experiments alternated between the left- and right-hand, and the patients moved onto the next stage + of the experiment according to the instructions. + + The EEG data were collected through a wireless multichannel EEG acquisition system (ZhenTec NT1, Xi’an ZhenTec Intelligence + Technology Co., Ltd., China). The system includes an EEG cap, an EEG acquisition amplifier, a data receiver and host computer + software. The EEG cap had electrodes placed according to the international 10-10 system, including 29 EEG recording electrodes + and 2 electrooculography (EOG) electrodes. The reference electrode located at CPz position and the grounding electrode located + at FPz position. All the EEG electrodes and grounding electrode are Ag/AgCl semi-dry EEG electrodes based on highly absorbable + porous sponges that are dampened with 3% NaCl solution. The EOG electrodes are composed by Ag/AgCl electrodes and conductive + adhesive hydrogel. The common-mode rejection ratio was 120 dB, the input impedance was 1 GΩ, the input noise was less than + 0.4 μVrms, and the resolution was 24 bits. The acquisition impedance was less than or equal to 20 kΩ. The sampling frequency + was 500 Hz. References ---------- From fcda22015dfec337b89466cf94314b25f10824d3 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 11 Jun 2024 13:12:23 +0000 Subject: [PATCH 08/35] [pre-commit.ci] auto fixes from pre-commit.com hooks --- moabb/datasets/liu2024.py | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/moabb/datasets/liu2024.py b/moabb/datasets/liu2024.py index 19cdda27d..4d8e6420b 100644 --- a/moabb/datasets/liu2024.py +++ b/moabb/datasets/liu2024.py @@ -29,29 +29,29 @@ class Liu2024(BaseDataset): **Dataset description** - This dataset includes data from 50 acute stroke patients (the time after stroke ranges from 1 day to 30 days) + This dataset includes data from 50 acute stroke patients (the time after stroke ranges from 1 day to 30 days) admitted to the stroke unit of Xuanwu Hospital of Capital Medical University. The patients included 39 males (78%) - and 11 females (22%), aged between 31 and 77 years, with an average age of 56.70 years (SD = 10.57) + and 11 females (22%), aged between 31 and 77 years, with an average age of 56.70 years (SD = 10.57) Before the start of the experiment, the subject sat in a chair in a position as comfortable as possible with an - EEG cap placed on their head; subjects were positioned approximately 80 cm away from a computer screen in front of them. + EEG cap placed on their head; subjects were positioned approximately 80 cm away from a computer screen in front of them. The computer played audio instructions to the patient about the procedure. Each experiment lasted approximately 20 minutes, - including preparation time and approximately 10 minutes of signal recording. Before the start of the MI experiment, + including preparation time and approximately 10 minutes of signal recording. Before the start of the MI experiment, the patients opened their eyes and closed their eyes for 1 minute each. The MI experiment was divided into 40 trials, and - each trial took 8 seconds, which consisted of three stages (instruction, MI and break). In the instruction stage, patients - were prompted to imagine grasping a spherical object with the left- or right-hand. In the MI stage, participants imagined - performing this action, a video of gripping motion is played on the computer, which leads the patient imagine grabbing the + each trial took 8 seconds, which consisted of three stages (instruction, MI and break). In the instruction stage, patients + were prompted to imagine grasping a spherical object with the left- or right-hand. In the MI stage, participants imagined + performing this action, a video of gripping motion is played on the computer, which leads the patient imagine grabbing the ball. This video stays playing for 4 s. Patients only imagine one hand movement.In the break stage, participants were allowed - to relax and rest. The MI experiments alternated between the left- and right-hand, and the patients moved onto the next stage + to relax and rest. The MI experiments alternated between the left- and right-hand, and the patients moved onto the next stage of the experiment according to the instructions. - The EEG data were collected through a wireless multichannel EEG acquisition system (ZhenTec NT1, Xi’an ZhenTec Intelligence + The EEG data were collected through a wireless multichannel EEG acquisition system (ZhenTec NT1, Xi’an ZhenTec Intelligence Technology Co., Ltd., China). The system includes an EEG cap, an EEG acquisition amplifier, a data receiver and host computer - software. The EEG cap had electrodes placed according to the international 10-10 system, including 29 EEG recording electrodes - and 2 electrooculography (EOG) electrodes. The reference electrode located at CPz position and the grounding electrode located - at FPz position. All the EEG electrodes and grounding electrode are Ag/AgCl semi-dry EEG electrodes based on highly absorbable + software. The EEG cap had electrodes placed according to the international 10-10 system, including 29 EEG recording electrodes + and 2 electrooculography (EOG) electrodes. The reference electrode located at CPz position and the grounding electrode located + at FPz position. All the EEG electrodes and grounding electrode are Ag/AgCl semi-dry EEG electrodes based on highly absorbable porous sponges that are dampened with 3% NaCl solution. The EOG electrodes are composed by Ag/AgCl electrodes and conductive - adhesive hydrogel. The common-mode rejection ratio was 120 dB, the input impedance was 1 GΩ, the input noise was less than - 0.4 μVrms, and the resolution was 24 bits. The acquisition impedance was less than or equal to 20 kΩ. The sampling frequency + adhesive hydrogel. The common-mode rejection ratio was 120 dB, the input impedance was 1 GΩ, the input noise was less than + 0.4 μVrms, and the resolution was 24 bits. The acquisition impedance was less than or equal to 20 kΩ. The sampling frequency was 500 Hz. References From 88d43f32dcaa28a3f271c1cf95d384bfd4639d75 Mon Sep 17 00:00:00 2001 From: tahatt13 Date: Wed, 12 Jun 2024 17:04:20 +0200 Subject: [PATCH 09/35] updating get_single_subject fct and data_path & adding encoding fct --- moabb/datasets/liu2024.py | 97 +++++++++++++++++++++++++++++---------- 1 file changed, 72 insertions(+), 25 deletions(-) diff --git a/moabb/datasets/liu2024.py b/moabb/datasets/liu2024.py index 19cdda27d..2d5a25d45 100644 --- a/moabb/datasets/liu2024.py +++ b/moabb/datasets/liu2024.py @@ -1,16 +1,23 @@ +import os import zipfile as z from pathlib import Path import mne +import pandas as pd +import numpy as np from mne.channels import read_custom_montage from moabb.datasets import download as dl from moabb.datasets.base import BaseDataset -_LIU2024_URL = ( - "https://figshare.com/articles/dataset/EEG_datasets_of_stroke_patients/21679035/5" -) +# Link to the raw data +LIU2024_URL = "https://figshare.com/ndownloader/files/38516654" + +# Links to the channels, electrodes and events informations files +url_channels = "https://figshare.com/ndownloader/files/38516069" +url_electrodes = "https://figshare.com/ndownloader/files/38516078" +url_events = "https://figshare.com/ndownloader/files/38516084" class Liu2024(BaseDataset): @@ -24,7 +31,7 @@ class Liu2024(BaseDataset): ============ ======= ======= ========== ================= ============ =============== =========== Name #Subj #Chan #Classes #Trials / class Trials len Sampling rate #Sessions ============ ======= ======= ========== ================= ============ =============== =========== - BNCI2014_001 50 33 2 40 4s 500Hz 1 + Liu2024 50 29 2 40 4s 500Hz 1 ============ ======= ======= ========== ================= ============ =============== =========== @@ -60,14 +67,14 @@ class Liu2024(BaseDataset): .. [1] Liu, Haijie; Lv, Xiaodong (2022). EEG datasets of stroke patients. figshare. Dataset. DOI: https://doi.org/10.6084/m9.figshare.21679035.v5 - .. [2] Liu, H., Wei, P., Wang, H. et al. An EEG motor imagery dataset for brain computer interface in acute stroke + .. [2] Liu, Haijie, Wei, P., Wang, H. et al. An EEG motor imagery dataset for brain computer interface in acute stroke patients. Sci Data 11, 131 (2024). DOI: https://doi.org/10.1038/s41597-023-02787-8 Notes ----- - .. versionadded:: 1.1.0 + .. versionadded:: 1.1.1 """ @@ -79,19 +86,15 @@ def __init__(self): code="Liu2024", interval=(0, 4), paradigm="imagery", - doi="10.6084/m9.figshare.21679035.v5", + doi="10.1038/s41597-023-02787-8", ) def data_infos(self): - """Returns the data paths of the channels, electrodes and events informations""" - - url_electrodes = "https://figshare.com/ndownloader/files/38516078" - url_channels = "https://figshare.com/ndownloader/files/38516069" - url_events = "https://figshare.com/ndownloader/files/38516084" + """Returns the data paths of the electrodes and events informations""" - path_channels = dl.data_dl(url_channels, self.code + "channels") - path_electrodes = dl.data_dl(url_electrodes, self.code + "electrodes") - path_events = dl.data_dl(url_events, self.code + "events") + path_channels = dl.data_dl(url_channels, self.code ) + path_electrodes = dl.data_dl(url_electrodes, self.code ) + path_events = dl.data_dl(url_events, self.code ) return path_channels, path_electrodes, path_events @@ -100,20 +103,22 @@ def data_path( ): """Return the data paths of a single subject.""" if subject not in self.subject_list: - raise (ValueError("Invalid subject number")) + raise ValueError("Invalid subject number") - subject_paths = [] - - url = "https://figshare.com/ndownloader/files/38516654" - path_zip = dl.data_dl(url, self.code) + # Download the zip file containing the data + path_zip = dl.data_dl(LIU2024_URL, self.code) path_zip = Path(path_zip) path_folder = path_zip.parent + # Extract the zip file if it hasn't been extracted yet if not (path_folder / "edffile").is_dir(): zip_ref = z.ZipFile(path_zip, "r") zip_ref.extractall(path_folder) + + subject_paths = [] sub = f"sub-{subject:02d}" + # Construct the path to the subject's data file subject_path = ( path_folder / "edffile" / sub / "eeg" / f"{sub}_task-motor-imagery_eeg.edf" ) @@ -121,28 +126,70 @@ def data_path( return subject_paths + def encoding(self, events_df : pd.DataFrame): + """Encoding the columns 'value' and 'trial_type' in the events file into a single event type + 'trial_type' can be 1 ( left hand ) or 2 ( right hand), but for convenience we use 0 and 1 + 'value' can be 1 ( instruction ), 2 ( MI ) or 3 ( break ) + For example if trial_type = 1 and value = 2, the event type will be 12 + if trial_type = 0 and value = 2, the event type will be 2 + """ + # docstring for parameters + event_type = events_df['value'].values + (events_df['trial_type'].values -1) * 10 + + return event_type + def _get_single_subject_data(self, subject): """Return the data of a single subject.""" + #docstrings + file_path_list = self.data_path(subject) path_channels, path_electrodes, path_events = self.data_infos() - # Read the subject's data + # Read the subject's raw data raw = mne.io.read_raw_edf(file_path_list[0], preload=False) - # Selecting the EEG channels and the STIM channel + # Selecting the EEG channels and the STIM channel excluding the CPz + # reference channel and the EOG channels selected_channels = raw.info["ch_names"][:-3] + [""] selected_channels.remove("CPz") - raw = raw.pick(selected_channels) - # Updating the types of the channels - channel_types = channel_types[:-2] + ["stim"] + # Updating the types of the channels after extracting them from the channels file + channels_info = pd.read_csv(path_channels, sep='\t') + channel_types = [type.lower() for type in channels_info['type'].tolist()[:-2]] + ["stim"] channel_dict = dict(zip(selected_channels, channel_types)) raw.info.set_channel_types(channel_dict) + # Renaming the .tsv file to make sure it's recognized as .tsv + #cdt for the rename + os.rename(path_electrodes, path_electrodes +".tsv") + path_electrodes = path_electrodes + ".tsv" + + # Read and set the montage montage = read_custom_montage(path_electrodes) raw.set_montage(montage, on_missing="ignore") + events_df = pd.read_csv(path_events, sep='\t') + + # Convert onset from milliseconds to seconds + onset_seconds = events_df['onset'].values / 1000 + + # Encode the events + event_type = self.encoding(events_df) + + # Convert onset from seconds to samples for the events array + sfreq = raw.info['sfreq'] + onset_samples = (onset_seconds * sfreq).astype(int) + + # Create the events array + events = np.column_stack((onset_samples, np.zeros_like(onset_samples), event_type)) + + # Creating and setting annoations from the events + annotations = mne.annotations_from_events(events, + sfreq = raw.info['sfreq'], + event_desc = event_type) + raw.set_annotations(annotations) + # There is only one session sessions = {"0": {}} sessions["0"] = raw From abc6b2942cce4eed5acf277d729ad0de16d2ade5 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 12 Jun 2024 15:05:10 +0000 Subject: [PATCH 10/35] [pre-commit.ci] auto fixes from pre-commit.com hooks --- moabb/datasets/liu2024.py | 48 +++++++++++++++++++++------------------ 1 file changed, 26 insertions(+), 22 deletions(-) diff --git a/moabb/datasets/liu2024.py b/moabb/datasets/liu2024.py index 8acdcf487..ed2c99f85 100644 --- a/moabb/datasets/liu2024.py +++ b/moabb/datasets/liu2024.py @@ -3,8 +3,8 @@ from pathlib import Path import mne -import pandas as pd import numpy as np +import pandas as pd from mne.channels import read_custom_montage from moabb.datasets import download as dl @@ -92,9 +92,9 @@ def __init__(self): def data_infos(self): """Returns the data paths of the electrodes and events informations""" - path_channels = dl.data_dl(url_channels, self.code ) - path_electrodes = dl.data_dl(url_electrodes, self.code ) - path_events = dl.data_dl(url_events, self.code ) + path_channels = dl.data_dl(url_channels, self.code) + path_electrodes = dl.data_dl(url_electrodes, self.code) + path_events = dl.data_dl(url_events, self.code) return path_channels, path_electrodes, path_events @@ -126,21 +126,21 @@ def data_path( return subject_paths - def encoding(self, events_df : pd.DataFrame): - """Encoding the columns 'value' and 'trial_type' in the events file into a single event type + def encoding(self, events_df: pd.DataFrame): + """Encoding the columns 'value' and 'trial_type' in the events file into a single event type 'trial_type' can be 1 ( left hand ) or 2 ( right hand), but for convenience we use 0 and 1 'value' can be 1 ( instruction ), 2 ( MI ) or 3 ( break ) - For example if trial_type = 1 and value = 2, the event type will be 12 + For example if trial_type = 1 and value = 2, the event type will be 12 if trial_type = 0 and value = 2, the event type will be 2 """ - # docstring for parameters - event_type = events_df['value'].values + (events_df['trial_type'].values -1) * 10 + # docstring for parameters + event_type = events_df["value"].values + (events_df["trial_type"].values - 1) * 10 return event_type def _get_single_subject_data(self, subject): """Return the data of a single subject.""" - #docstrings + # docstrings file_path_list = self.data_path(subject) path_channels, path_electrodes, path_events = self.data_infos() @@ -148,46 +148,50 @@ def _get_single_subject_data(self, subject): # Read the subject's raw data raw = mne.io.read_raw_edf(file_path_list[0], preload=False) - # Selecting the EEG channels and the STIM channel excluding the CPz + # Selecting the EEG channels and the STIM channel excluding the CPz # reference channel and the EOG channels selected_channels = raw.info["ch_names"][:-3] + [""] selected_channels.remove("CPz") raw = raw.pick(selected_channels) # Updating the types of the channels after extracting them from the channels file - channels_info = pd.read_csv(path_channels, sep='\t') - channel_types = [type.lower() for type in channels_info['type'].tolist()[:-2]] + ["stim"] + channels_info = pd.read_csv(path_channels, sep="\t") + channel_types = [type.lower() for type in channels_info["type"].tolist()[:-2]] + [ + "stim" + ] channel_dict = dict(zip(selected_channels, channel_types)) raw.info.set_channel_types(channel_dict) # Renaming the .tsv file to make sure it's recognized as .tsv - #cdt for the rename - os.rename(path_electrodes, path_electrodes +".tsv") + # cdt for the rename + os.rename(path_electrodes, path_electrodes + ".tsv") path_electrodes = path_electrodes + ".tsv" # Read and set the montage montage = read_custom_montage(path_electrodes) raw.set_montage(montage, on_missing="ignore") - events_df = pd.read_csv(path_events, sep='\t') + events_df = pd.read_csv(path_events, sep="\t") # Convert onset from milliseconds to seconds - onset_seconds = events_df['onset'].values / 1000 + onset_seconds = events_df["onset"].values / 1000 # Encode the events event_type = self.encoding(events_df) # Convert onset from seconds to samples for the events array - sfreq = raw.info['sfreq'] + sfreq = raw.info["sfreq"] onset_samples = (onset_seconds * sfreq).astype(int) # Create the events array - events = np.column_stack((onset_samples, np.zeros_like(onset_samples), event_type)) + events = np.column_stack( + (onset_samples, np.zeros_like(onset_samples), event_type) + ) # Creating and setting annoations from the events - annotations = mne.annotations_from_events(events, - sfreq = raw.info['sfreq'], - event_desc = event_type) + annotations = mne.annotations_from_events( + events, sfreq=raw.info["sfreq"], event_desc=event_type + ) raw.set_annotations(annotations) # There is only one session From 198cb42a401513a33b6717e1a8740dbcf7b14cc8 Mon Sep 17 00:00:00 2001 From: tahatt13 Date: Thu, 13 Jun 2024 10:37:22 +0200 Subject: [PATCH 11/35] Finishing the code --- moabb/datasets/liu2024.py | 85 ++++++++++++++++++++++++++++++++------- 1 file changed, 71 insertions(+), 14 deletions(-) diff --git a/moabb/datasets/liu2024.py b/moabb/datasets/liu2024.py index ed2c99f85..42b87d7b2 100644 --- a/moabb/datasets/liu2024.py +++ b/moabb/datasets/liu2024.py @@ -31,7 +31,7 @@ class Liu2024(BaseDataset): ============ ======= ======= ========== ================= ============ =============== =========== Name #Subj #Chan #Classes #Trials / class Trials len Sampling rate #Sessions ============ ======= ======= ========== ================= ============ =============== =========== - Liu2024 50 29 2 40 4s 500Hz 1 + Liu2024 50 29 2 40 4s 500Hz 1 ============ ======= ======= ========== ================= ============ =============== =========== @@ -90,7 +90,16 @@ def __init__(self): ) def data_infos(self): - """Returns the data paths of the electrodes and events informations""" + """Returns the data paths of the electrodes and events informations + + This function downloads the necessary data files for channels, electrodes, + and events from their respective URLs and returns their local file paths. + + Returns + ------- + tuple + A tuple containing the local file paths to the channels, electrodes, and events information files. + """ path_channels = dl.data_dl(url_channels, self.code) path_electrodes = dl.data_dl(url_electrodes, self.code) @@ -101,7 +110,30 @@ def data_infos(self): def data_path( self, subject, path=None, force_update=False, update_path=None, verbose=None ): - """Return the data paths of a single subject.""" + """Return the data paths of a single subject, in our case only one path is returned. +. + Parameters + ---------- + subject : int + The subject number to fetch data for. + path : None | str + Location of where to look for the data storing location. If None, the environment + variable or config parameter MNE_DATASETS_(dataset)_PATH is used. If it doesn’t exist, + the “~/mne_data” directory is used. If the dataset is not found under the given path, the data + will be automatically downloaded to the specified folder. + force_update : bool + Force update of the dataset even if a local copy exists. + update_path : bool | None Deprecated + If True, set the MNE_DATASETS_(dataset)_PATH in mne-python config to the given path. + If None, the user is prompted. + verbose : bool, str, int, or None + If not None, override default verbose level (see mne.verbose()). + + Returns + ------- + list + A list containing the path to the subject's data file. The list contains only one path. + """ if subject not in self.subject_list: raise ValueError("Invalid subject number") @@ -127,20 +159,43 @@ def data_path( return subject_paths def encoding(self, events_df: pd.DataFrame): - """Encoding the columns 'value' and 'trial_type' in the events file into a single event type - 'trial_type' can be 1 ( left hand ) or 2 ( right hand), but for convenience we use 0 and 1 - 'value' can be 1 ( instruction ), 2 ( MI ) or 3 ( break ) - For example if trial_type = 1 and value = 2, the event type will be 12 - if trial_type = 0 and value = 2, the event type will be 2 + """Encode the columns 'value' and 'trial_type' in the events file into a single event type. + + Parameters + ---------- + events_df : pd.DataFrame + DataFrame containing the events information. + + Returns + ------- + np.ndarray + Array of encoded event types. + + Notes + ----- + 'trial_type' can take values { 1 : Left hand, 2 : Right hand }, but for convenience we use 0 and 1. + 'value' can take values { 1 : instructions, 2 : MI, 3 : break}. + For example, if trial_type = 1 and value = 2, the event type will be 12. + If trial_type = 0 and value = 2, the event type will be 2. """ - # docstring for parameters + event_type = events_df["value"].values + (events_df["trial_type"].values - 1) * 10 return event_type def _get_single_subject_data(self, subject): - """Return the data of a single subject.""" - # docstrings + """Return the data of a single subject. + + Parameters + ---------- + subject : int + The subject number to fetch data for. + + Returns + ------- + dict + A dictionary containing the raw data for the subject. + """ file_path_list = self.data_path(subject) path_channels, path_electrodes, path_events = self.data_infos() @@ -163,9 +218,11 @@ def _get_single_subject_data(self, subject): raw.info.set_channel_types(channel_dict) # Renaming the .tsv file to make sure it's recognized as .tsv - # cdt for the rename - os.rename(path_electrodes, path_electrodes + ".tsv") - path_electrodes = path_electrodes + ".tsv" + # Check if the file already has the ".tsv" extension + if not path_electrodes.endswith(".tsv"): + # Rename the file + os.rename(path_electrodes, path_electrodes + ".tsv") + path_electrodes = path_electrodes + ".tsv" # Read and set the montage montage = read_custom_montage(path_electrodes) From fc10533b1727b7ad2d92356da103bd40f4f528ab Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 13 Jun 2024 08:40:36 +0000 Subject: [PATCH 12/35] [pre-commit.ci] auto fixes from pre-commit.com hooks --- moabb/datasets/liu2024.py | 56 +++++++++++++++++++-------------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/moabb/datasets/liu2024.py b/moabb/datasets/liu2024.py index 42b87d7b2..2602b251e 100644 --- a/moabb/datasets/liu2024.py +++ b/moabb/datasets/liu2024.py @@ -91,7 +91,7 @@ def __init__(self): def data_infos(self): """Returns the data paths of the electrodes and events informations - + This function downloads the necessary data files for channels, electrodes, and events from their respective URLs and returns their local file paths. @@ -111,28 +111,28 @@ def data_path( self, subject, path=None, force_update=False, update_path=None, verbose=None ): """Return the data paths of a single subject, in our case only one path is returned. -. - Parameters - ---------- - subject : int - The subject number to fetch data for. - path : None | str - Location of where to look for the data storing location. If None, the environment - variable or config parameter MNE_DATASETS_(dataset)_PATH is used. If it doesn’t exist, - the “~/mne_data” directory is used. If the dataset is not found under the given path, the data - will be automatically downloaded to the specified folder. - force_update : bool - Force update of the dataset even if a local copy exists. - update_path : bool | None Deprecated - If True, set the MNE_DATASETS_(dataset)_PATH in mne-python config to the given path. - If None, the user is prompted. - verbose : bool, str, int, or None - If not None, override default verbose level (see mne.verbose()). - - Returns - ------- - list - A list containing the path to the subject's data file. The list contains only one path. + . + Parameters + ---------- + subject : int + The subject number to fetch data for. + path : None | str + Location of where to look for the data storing location. If None, the environment + variable or config parameter MNE_DATASETS_(dataset)_PATH is used. If it doesn’t exist, + the “~/mne_data” directory is used. If the dataset is not found under the given path, the data + will be automatically downloaded to the specified folder. + force_update : bool + Force update of the dataset even if a local copy exists. + update_path : bool | None Deprecated + If True, set the MNE_DATASETS_(dataset)_PATH in mne-python config to the given path. + If None, the user is prompted. + verbose : bool, str, int, or None + If not None, override default verbose level (see mne.verbose()). + + Returns + ------- + list + A list containing the path to the subject's data file. The list contains only one path. """ if subject not in self.subject_list: raise ValueError("Invalid subject number") @@ -160,17 +160,17 @@ def data_path( def encoding(self, events_df: pd.DataFrame): """Encode the columns 'value' and 'trial_type' in the events file into a single event type. - + Parameters ---------- events_df : pd.DataFrame DataFrame containing the events information. - + Returns ------- np.ndarray Array of encoded event types. - + Notes ----- 'trial_type' can take values { 1 : Left hand, 2 : Right hand }, but for convenience we use 0 and 1. @@ -185,12 +185,12 @@ def encoding(self, events_df: pd.DataFrame): def _get_single_subject_data(self, subject): """Return the data of a single subject. - + Parameters ---------- subject : int The subject number to fetch data for. - + Returns ------- dict From a3a31b5ad674842223b02403581e56dad940fed2 Mon Sep 17 00:00:00 2001 From: tahatt13 Date: Thu, 13 Jun 2024 11:12:51 +0200 Subject: [PATCH 13/35] updating docstrings for data_path --- moabb/datasets/liu2024.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/moabb/datasets/liu2024.py b/moabb/datasets/liu2024.py index 2602b251e..05a24258c 100644 --- a/moabb/datasets/liu2024.py +++ b/moabb/datasets/liu2024.py @@ -123,7 +123,7 @@ def data_path( will be automatically downloaded to the specified folder. force_update : bool Force update of the dataset even if a local copy exists. - update_path : bool | None Deprecated + update_path : bool | None If True, set the MNE_DATASETS_(dataset)_PATH in mne-python config to the given path. If None, the user is prompted. verbose : bool, str, int, or None From d7722d613657add3bd2cd75109c9d8722c0c7cfa Mon Sep 17 00:00:00 2001 From: tahatt13 Date: Thu, 13 Jun 2024 11:56:36 +0200 Subject: [PATCH 14/35] Updating dataset_summary and updating the get_single_subject fct to handle the case of existing file in path_electrodes --- docs/source/dataset_summary.rst | 2 +- moabb/datasets/liu2024.py | 13 ++++++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/docs/source/dataset_summary.rst b/docs/source/dataset_summary.rst index f919d4415..7e3521b92 100644 --- a/docs/source/dataset_summary.rst +++ b/docs/source/dataset_summary.rst @@ -42,7 +42,7 @@ Motor Imagery :class:`Weibo2014`,10,60,7,80,4s,200Hz,1,1,5600 :class:`Zhou2016`,4,14,3,160,5s,250Hz,3,2,11496 :class:`Stieger2021`,62,64,4,450,3s,1000Hz,7 or 11,1,250000 - :class:`Liu2024`,62,64,4,450,3s,1000Hz,7 or 11,1,250000 + :class:`Liu2024`,50,29,2,40,4s,500Hz,1,1,160000 P300/ERP ====================== diff --git a/moabb/datasets/liu2024.py b/moabb/datasets/liu2024.py index 05a24258c..fa94676d4 100644 --- a/moabb/datasets/liu2024.py +++ b/moabb/datasets/liu2024.py @@ -220,9 +220,16 @@ def _get_single_subject_data(self, subject): # Renaming the .tsv file to make sure it's recognized as .tsv # Check if the file already has the ".tsv" extension if not path_electrodes.endswith(".tsv"): - # Rename the file - os.rename(path_electrodes, path_electrodes + ".tsv") - path_electrodes = path_electrodes + ".tsv" + # Create the new path + new_path_electrodes = path_electrodes + ".tsv" + # Check if the target filename already exists + if not os.path.exists(new_path_electrodes): + # Perform the rename operation only if the target file doesn't exist + os.rename(path_electrodes, new_path_electrodes) + path_electrodes = new_path_electrodes + else: + # If the file already exists, simply keep the original path + path_electrodes = new_path_electrodes # Read and set the montage montage = read_custom_montage(path_electrodes) From 5beb6bc8061c0f0dfb862ec5d3a48674afbb8db0 Mon Sep 17 00:00:00 2001 From: tahatt13 Date: Thu, 13 Jun 2024 12:10:58 +0200 Subject: [PATCH 15/35] adapting the return of get_single_subject_data fct --- moabb/datasets/liu2024.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/moabb/datasets/liu2024.py b/moabb/datasets/liu2024.py index fa94676d4..e4da668a7 100644 --- a/moabb/datasets/liu2024.py +++ b/moabb/datasets/liu2024.py @@ -259,7 +259,6 @@ def _get_single_subject_data(self, subject): raw.set_annotations(annotations) # There is only one session - sessions = {"0": {}} - sessions["0"] = raw - + sessions = {"0": {"run_1": raw}} + return sessions From 7217051b7626cd03aea285c9e499d3d270b53a57 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 13 Jun 2024 10:13:26 +0000 Subject: [PATCH 16/35] [pre-commit.ci] auto fixes from pre-commit.com hooks --- moabb/datasets/liu2024.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/moabb/datasets/liu2024.py b/moabb/datasets/liu2024.py index e4da668a7..b868849ad 100644 --- a/moabb/datasets/liu2024.py +++ b/moabb/datasets/liu2024.py @@ -260,5 +260,5 @@ def _get_single_subject_data(self, subject): # There is only one session sessions = {"0": {"run_1": raw}} - + return sessions From 672e96255392e8da7b81d03741887381670f7da7 Mon Sep 17 00:00:00 2001 From: tahatt13 Date: Thu, 13 Jun 2024 13:43:50 +0200 Subject: [PATCH 17/35] Adding dataset description and preload = True when reading the data in the get_single_subject fct --- moabb/datasets/liu2024.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/moabb/datasets/liu2024.py b/moabb/datasets/liu2024.py index e4da668a7..c48baae74 100644 --- a/moabb/datasets/liu2024.py +++ b/moabb/datasets/liu2024.py @@ -61,6 +61,8 @@ class Liu2024(BaseDataset): 0.4 μVrms, and the resolution was 24 bits. The acquisition impedance was less than or equal to 20 kΩ. The sampling frequency was 500 Hz. + In the dataset, we've removed the 2 EOG channels and we kept the 29 EEG channels and the STIM channel. + References ---------- @@ -201,7 +203,7 @@ def _get_single_subject_data(self, subject): path_channels, path_electrodes, path_events = self.data_infos() # Read the subject's raw data - raw = mne.io.read_raw_edf(file_path_list[0], preload=False) + raw = mne.io.read_raw_edf(file_path_list[0], preload=True) # Selecting the EEG channels and the STIM channel excluding the CPz # reference channel and the EOG channels @@ -259,6 +261,6 @@ def _get_single_subject_data(self, subject): raw.set_annotations(annotations) # There is only one session - sessions = {"0": {"run_1": raw}} - + sessions = {"0": {"0": raw}} + return sessions From def1e7086bcff537d58f73b8e8cff01acfcf10d6 Mon Sep 17 00:00:00 2001 From: bruAristimunha Date: Thu, 13 Jun 2024 13:48:09 +0200 Subject: [PATCH 18/35] fix: codespell --- moabb/datasets/liu2024.py | 50 +++++++++++++++++++-------------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/moabb/datasets/liu2024.py b/moabb/datasets/liu2024.py index b868849ad..716f1048d 100644 --- a/moabb/datasets/liu2024.py +++ b/moabb/datasets/liu2024.py @@ -14,7 +14,7 @@ # Link to the raw data LIU2024_URL = "https://figshare.com/ndownloader/files/38516654" -# Links to the channels, electrodes and events informations files +# Links to the channels, electrodes and events information files url_channels = "https://figshare.com/ndownloader/files/38516069" url_electrodes = "https://figshare.com/ndownloader/files/38516078" url_events = "https://figshare.com/ndownloader/files/38516084" @@ -90,7 +90,7 @@ def __init__(self): ) def data_infos(self): - """Returns the data paths of the electrodes and events informations + """Returns the data paths of the electrodes and events information This function downloads the necessary data files for channels, electrodes, and events from their respective URLs and returns their local file paths. @@ -111,28 +111,28 @@ def data_path( self, subject, path=None, force_update=False, update_path=None, verbose=None ): """Return the data paths of a single subject, in our case only one path is returned. - . - Parameters - ---------- - subject : int - The subject number to fetch data for. - path : None | str - Location of where to look for the data storing location. If None, the environment - variable or config parameter MNE_DATASETS_(dataset)_PATH is used. If it doesn’t exist, - the “~/mne_data” directory is used. If the dataset is not found under the given path, the data - will be automatically downloaded to the specified folder. - force_update : bool - Force update of the dataset even if a local copy exists. - update_path : bool | None - If True, set the MNE_DATASETS_(dataset)_PATH in mne-python config to the given path. - If None, the user is prompted. - verbose : bool, str, int, or None - If not None, override default verbose level (see mne.verbose()). - - Returns - ------- - list - A list containing the path to the subject's data file. The list contains only one path. + + Parameters + ---------- + subject : int + The subject number to fetch data for. + path : None | str + Location of where to look for the data storing location. If None, the environment + variable or config parameter MNE_DATASETS_(dataset)_PATH is used. If it doesn’t exist, + the “~/mne_data” directory is used. If the dataset is not found under the given path, the data + will be automatically downloaded to the specified folder. + force_update : bool + Force update of the dataset even if a local copy exists. + update_path : bool | None + If True, set the MNE_DATASETS_(dataset)_PATH in mne-python config to the given path. + If None, the user is prompted. + verbose : bool, str, int, or None + If not None, override default verbose level (see mne.verbose()). + + Returns + ------- + list + A list containing the path to the subject's data file. The list contains only one path. """ if subject not in self.subject_list: raise ValueError("Invalid subject number") @@ -252,7 +252,7 @@ def _get_single_subject_data(self, subject): (onset_samples, np.zeros_like(onset_samples), event_type) ) - # Creating and setting annoations from the events + # Creating and setting annotations from the events annotations = mne.annotations_from_events( events, sfreq=raw.info["sfreq"], event_desc=event_type ) From 8c6fed39f98902d0133e95f11dc103340c6a5946 Mon Sep 17 00:00:00 2001 From: bruAristimunha Date: Thu, 13 Jun 2024 13:48:52 +0200 Subject: [PATCH 19/35] fix: changing to static method the encoding --- moabb/datasets/liu2024.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/moabb/datasets/liu2024.py b/moabb/datasets/liu2024.py index 716f1048d..7c3d360d7 100644 --- a/moabb/datasets/liu2024.py +++ b/moabb/datasets/liu2024.py @@ -158,7 +158,8 @@ def data_path( return subject_paths - def encoding(self, events_df: pd.DataFrame): + @staticmethod + def encoding(events_df: pd.DataFrame): """Encode the columns 'value' and 'trial_type' in the events file into a single event type. Parameters From 1844119d3346d6f9d5ae3549d98231acfcf5abe9 Mon Sep 17 00:00:00 2001 From: tahatt13 Date: Thu, 13 Jun 2024 14:14:29 +0200 Subject: [PATCH 20/35] repushing and resolving pre-commit conflicts --- moabb/datasets/liu2024.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/moabb/datasets/liu2024.py b/moabb/datasets/liu2024.py index 3d0d88c80..ae101e8cd 100644 --- a/moabb/datasets/liu2024.py +++ b/moabb/datasets/liu2024.py @@ -261,6 +261,6 @@ def _get_single_subject_data(self, subject): raw.set_annotations(annotations) # There is only one session - sessions = {"0": {"run_1": raw}} - + sessions = {"0": {"0": raw}} + return sessions From 0633556bdbf5bcb9c75c6d4118a3e4e0e6f817fb Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 13 Jun 2024 12:15:10 +0000 Subject: [PATCH 21/35] [pre-commit.ci] auto fixes from pre-commit.com hooks --- moabb/datasets/liu2024.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/moabb/datasets/liu2024.py b/moabb/datasets/liu2024.py index ae101e8cd..c48baae74 100644 --- a/moabb/datasets/liu2024.py +++ b/moabb/datasets/liu2024.py @@ -262,5 +262,5 @@ def _get_single_subject_data(self, subject): # There is only one session sessions = {"0": {"0": raw}} - + return sessions From 1ba5c36b3504fe6c0469a73126b6bd06e8dcdc05 Mon Sep 17 00:00:00 2001 From: bruAristimunha Date: Thu, 13 Jun 2024 19:31:17 +0200 Subject: [PATCH 22/35] fix: changing the mapping --- moabb/datasets/liu2024.py | 157 ++++++++++++++++++++++---------------- 1 file changed, 93 insertions(+), 64 deletions(-) diff --git a/moabb/datasets/liu2024.py b/moabb/datasets/liu2024.py index de270d3ab..1f8c0487c 100644 --- a/moabb/datasets/liu2024.py +++ b/moabb/datasets/liu2024.py @@ -1,4 +1,5 @@ import os +import shutil import zipfile as z from pathlib import Path @@ -84,31 +85,13 @@ def __init__(self): super().__init__( subjects=list(range(1, 50 + 1)), sessions_per_subject=1, - events={"left_hand": 0, "right_hand": 1}, + events={"left_hand": 2, "right_hand": 4, "break": 3, "instr": 1}, code="Liu2024", - interval=(0, 4), + interval=(2, 6), paradigm="imagery", doi="10.1038/s41597-023-02787-8", ) - def data_infos(self): - """Returns the data paths of the electrodes and events information - - This function downloads the necessary data files for channels, electrodes, - and events from their respective URLs and returns their local file paths. - - Returns - ------- - tuple - A tuple containing the local file paths to the channels, electrodes, and events information files. - """ - - path_channels = dl.data_dl(url_channels, self.code) - path_electrodes = dl.data_dl(url_electrodes, self.code) - path_events = dl.data_dl(url_events, self.code) - - return path_channels, path_electrodes, path_events - def data_path( self, subject, path=None, force_update=False, update_path=None, verbose=None ): @@ -176,15 +159,39 @@ def encoding(events_df: pd.DataFrame): Notes ----- - 'trial_type' can take values { 1 : Left hand, 2 : Right hand }, but for convenience we use 0 and 1. - 'value' can take values { 1 : instructions, 2 : MI, 3 : break}. - For example, if trial_type = 1 and value = 2, the event type will be 12. - If trial_type = 0 and value = 2, the event type will be 2. - """ + # The 'trial_type' variable can take the following values: + # - 1 : Left hand + # - 2 : Right hand + # However, for convenience, we map these values to 0 and 1 respectively. + + # The 'value' variable can take the following values: + # - 1 : instructions + # - 2 : MI + # - 3 : break - event_type = events_df["value"].values + (events_df["trial_type"].values - 1) * 10 + """ + # Define the mapping dictionary + encoding_mapping = { + (1, 1): 1, # Right hand, instructions + (1, 2): 2, # Right hand, MI + (1, 3): 3, # Right hand, break + (2, 1): 1, # Left hand, instructions + (2, 2): 4, # Left hand, MI + (2, 3): 3, # Left hand, break + } + + mapping = { + 1: "instr", + 3: "break", + 2: "left_hand", + 4: "right_hand", + } + # Apply the mapping to the DataFrame + event_category = events_df.apply( + lambda row: encoding_mapping[(row["trial_type"], row["value"])], axis=1 + ) - return event_type + return event_category, mapping def _get_single_subject_data(self, subject): """Return the data of a single subject. @@ -200,11 +207,17 @@ def _get_single_subject_data(self, subject): A dictionary containing the raw data for the subject. """ - file_path_list = self.data_path(subject) + file_path_list = self.data_path(subject)[0] path_channels, path_electrodes, path_events = self.data_infos() # Read the subject's raw data - raw = mne.io.read_raw_edf(file_path_list[0], preload=True) + raw = mne.io.read_raw_edf(file_path_list, preload=False) + # Read channels information + channels_info = pd.read_csv(path_channels, sep="\t") + # Normalize and Read the montage + path_electrodes = self._normalize_extension(path_electrodes) + # Read and set the montage + montage = read_custom_montage(path_electrodes) # Selecting the EEG channels and the STIM channel excluding the CPz # reference channel and the EOG channels @@ -212,56 +225,72 @@ def _get_single_subject_data(self, subject): selected_channels.remove("CPz") raw = raw.pick(selected_channels) - # Updating the types of the channels after extracting them from the channels file - channels_info = pd.read_csv(path_channels, sep="\t") - channel_types = [type.lower() for type in channels_info["type"].tolist()[:-2]] + [ - "stim" - ] + # Updating the types of the channels after extracting them from the + # channels file + channel_types = channels_info["type"].str.lower().tolist()[:-2] + ["stim"] channel_dict = dict(zip(selected_channels, channel_types)) raw.info.set_channel_types(channel_dict) - # Renaming the .tsv file to make sure it's recognized as .tsv - # Check if the file already has the ".tsv" extension - if not path_electrodes.endswith(".tsv"): - # Create the new path - new_path_electrodes = path_electrodes + ".tsv" - # Check if the target filename already exists - if not os.path.exists(new_path_electrodes): - # Perform the rename operation only if the target file doesn't exist - os.rename(path_electrodes, new_path_electrodes) - path_electrodes = new_path_electrodes - else: - # If the file already exists, simply keep the original path - path_electrodes = new_path_electrodes - - # Read and set the montage - montage = read_custom_montage(path_electrodes) - raw.set_montage(montage, on_missing="ignore") - events_df = pd.read_csv(path_events, sep="\t") - # Convert onset from milliseconds to seconds - onset_seconds = events_df["onset"].values / 1000 - # Encode the events - event_type = self.encoding(events_df) + event_category, mapping = self.encoding(events_df) - # Convert onset from seconds to samples for the events array - sfreq = raw.info["sfreq"] - onset_samples = (onset_seconds * sfreq).astype(int) - - # Create the events array + _, idx_trigger = np.nonzero(raw.copy().pick("").get_data()) + # Create the events array based on the stimulus channel events = np.column_stack( - (onset_samples, np.zeros_like(onset_samples), event_type) + (idx_trigger, np.repeat(1000, len(idx_trigger)), event_category) ) # Creating and setting annotations from the events annotations = mne.annotations_from_events( - events, sfreq=raw.info["sfreq"], event_desc=event_type + events, sfreq=raw.info["sfreq"], event_desc=mapping ) - raw.set_annotations(annotations) + raw = raw.set_annotations(annotations) + # Removing the stimulus channels + raw = raw.pick_types(eeg=True, eog=True) + # Setting the montage + raw = raw.set_montage(montage, on_missing="ignore") + # Loading dataset + raw = raw.load_data() # There is only one session sessions = {"0": {"0": raw}} return sessions + + def data_infos(self): + """Returns the data paths of the electrodes and events information + + This function downloads the necessary data files for channels, electrodes, + and events from their respective URLs and returns their local file paths. + + Returns + ------- + tuple + A tuple containing the local file paths to the channels, electrodes, + and events information files. + """ + + path_channels = dl.data_dl(url_channels, self.code) + + path_electrodes = dl.data_dl(url_electrodes, self.code) + + path_events = dl.data_dl(url_events, self.code) + + return path_channels, path_electrodes, path_events + + @staticmethod + def _normalize_extension(file_name): + # Renaming the .tsv file to make sure it's recognized as .tsv + # Check if the file already has the ".tsv" extension + + file_electrodes_tsv = file_name + ".tsv" + + if not os.path.exists(file_electrodes_tsv): + # Perform the rename operation only if the target file + # doesn't exist + shutil.copy(file_name, file_electrodes_tsv) + file_name = file_electrodes_tsv + + return file_name From af82ca35c5b95e1942ba06d102209a512d720d03 Mon Sep 17 00:00:00 2001 From: bruAristimunha Date: Fri, 14 Jun 2024 11:53:54 +0200 Subject: [PATCH 23/35] fix: changing the unmatching between the trigger and the events from the csv --- moabb/datasets/liu2024.py | 55 +++++++++++++++++---------------------- 1 file changed, 24 insertions(+), 31 deletions(-) diff --git a/moabb/datasets/liu2024.py b/moabb/datasets/liu2024.py index 1f8c0487c..a1d7cf88e 100644 --- a/moabb/datasets/liu2024.py +++ b/moabb/datasets/liu2024.py @@ -1,5 +1,6 @@ import os import shutil +import warnings import zipfile as z from pathlib import Path @@ -172,19 +173,19 @@ def encoding(events_df: pd.DataFrame): """ # Define the mapping dictionary encoding_mapping = { - (1, 1): 1, # Right hand, instructions - (1, 2): 2, # Right hand, MI + (2, 2): 0, # Left hand, MI + (1, 2): 1, # Right hand, MI + (1, 1): 2, # Right hand, instructions (1, 3): 3, # Right hand, break - (2, 1): 1, # Left hand, instructions - (2, 2): 4, # Left hand, MI + (2, 1): 2, # Left hand, instructions (2, 3): 3, # Left hand, break } mapping = { - 1: "instr", + 0: "left_hand", + 1: "right_hand", + 2: "instr", 3: "break", - 2: "left_hand", - 4: "right_hand", } # Apply the mapping to the DataFrame event_category = events_df.apply( @@ -210,36 +211,27 @@ def _get_single_subject_data(self, subject): file_path_list = self.data_path(subject)[0] path_channels, path_electrodes, path_events = self.data_infos() - # Read the subject's raw data - raw = mne.io.read_raw_edf(file_path_list, preload=False) - # Read channels information - channels_info = pd.read_csv(path_channels, sep="\t") + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + # Read the subject's raw data + raw = mne.io.read_raw_edf( + file_path_list, verbose=False, infer_types=True, stim_channel="" + ) # Normalize and Read the montage path_electrodes = self._normalize_extension(path_electrodes) # Read and set the montage montage = read_custom_montage(path_electrodes) - # Selecting the EEG channels and the STIM channel excluding the CPz - # reference channel and the EOG channels - selected_channels = raw.info["ch_names"][:-3] + [""] - selected_channels.remove("CPz") - raw = raw.pick(selected_channels) - - # Updating the types of the channels after extracting them from the - # channels file - channel_types = channels_info["type"].str.lower().tolist()[:-2] + ["stim"] - channel_dict = dict(zip(selected_channels, channel_types)) - raw.info.set_channel_types(channel_dict) - events_df = pd.read_csv(path_events, sep="\t") # Encode the events event_category, mapping = self.encoding(events_df) _, idx_trigger = np.nonzero(raw.copy().pick("").get_data()) + n_label_stim = len(event_category) # Create the events array based on the stimulus channel events = np.column_stack( - (idx_trigger, np.repeat(1000, len(idx_trigger)), event_category) + (idx_trigger[:n_label_stim], np.zeros_like(event_category), event_category) ) # Creating and setting annotations from the events @@ -248,12 +240,14 @@ def _get_single_subject_data(self, subject): ) raw = raw.set_annotations(annotations) - # Removing the stimulus channels - raw = raw.pick_types(eeg=True, eog=True) - # Setting the montage - raw = raw.set_montage(montage, on_missing="ignore") + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + # Removing the stimulus channels + raw = raw.pick(["eeg", "eog"]) + # Setting the montage + raw = raw.set_montage(montage, on_missing="ignore", verbose=False) # Loading dataset - raw = raw.load_data() + raw = raw.load_data(verbose=False) # There is only one session sessions = {"0": {"0": raw}} @@ -291,6 +285,5 @@ def _normalize_extension(file_name): # Perform the rename operation only if the target file # doesn't exist shutil.copy(file_name, file_electrodes_tsv) - file_name = file_electrodes_tsv - return file_name + return file_electrodes_tsv From b2e88afac7f17d34384c49a6434de44f94bb4cb2 Mon Sep 17 00:00:00 2001 From: bruAristimunha Date: Fri, 14 Jun 2024 12:10:21 +0200 Subject: [PATCH 24/35] ehn: using pylint to improve the code (remove not used variables, and change the module); --- moabb/datasets/liu2024.py | 82 +++++++++++++++++++++++++-------------- 1 file changed, 52 insertions(+), 30 deletions(-) diff --git a/moabb/datasets/liu2024.py b/moabb/datasets/liu2024.py index a1d7cf88e..701176e59 100644 --- a/moabb/datasets/liu2024.py +++ b/moabb/datasets/liu2024.py @@ -1,8 +1,11 @@ +"""Liu2024 Motor imagery dataset.""" + import os import shutil import warnings import zipfile as z from pathlib import Path +from typing import Any, Dict, Tuple import mne import numpy as np @@ -17,9 +20,8 @@ LIU2024_URL = "https://figshare.com/ndownloader/files/38516654" # Links to the channels, electrodes and events information files -url_channels = "https://figshare.com/ndownloader/files/38516069" -url_electrodes = "https://figshare.com/ndownloader/files/38516078" -url_events = "https://figshare.com/ndownloader/files/38516084" +LIU2024_ELECTRODES = "https://figshare.com/ndownloader/files/38516078" +LIU2024_EVENTS = "https://figshare.com/ndownloader/files/38516084" class Liu2024(BaseDataset): @@ -68,12 +70,12 @@ class Liu2024(BaseDataset): References ---------- - .. [1] Liu, Haijie; Lv, Xiaodong (2022). EEG datasets of stroke patients. figshare. Dataset. - DOI: https://doi.org/10.6084/m9.figshare.21679035.v5 + .. [1] Liu, Haijie; Lv, Xiaodong (2022). EEG datasets of stroke patients. + figshare. Dataset. DOI: https://doi.org/10.6084/m9.figshare.21679035.v5 - .. [2] Liu, Haijie, Wei, P., Wang, H. et al. An EEG motor imagery dataset for brain computer interface in acute stroke - patients. Sci Data 11, 131 (2024). - DOI: https://doi.org/10.1038/s41597-023-02787-8 + .. [2] Liu, Haijie, Wei, P., Wang, H. et al. An EEG motor imagery dataset + for brain computer interface in acute stroke patients. Sci Data 11, 131 + (2024). DOI: https://doi.org/10.1038/s41597-023-02787-8 Notes ----- @@ -96,21 +98,23 @@ def __init__(self): def data_path( self, subject, path=None, force_update=False, update_path=None, verbose=None ): - """Return the data paths of a single subject, in our case only one path is returned. + """Return the data paths of a single subject. Parameters ---------- subject : int The subject number to fetch data for. path : None | str - Location of where to look for the data storing location. If None, the environment - variable or config parameter MNE_DATASETS_(dataset)_PATH is used. If it doesn’t exist, - the “~/mne_data” directory is used. If the dataset is not found under the given path, the data + Location of where to look for the data storing location. If None, + the environment variable or config parameter MNE_(dataset) is used. + If it doesn’t exist, the “~/mne_data” directory is used. If the + dataset is not found under the given path, the data will be automatically downloaded to the specified folder. force_update : bool Force update of the dataset even if a local copy exists. update_path : bool | None - If True, set the MNE_DATASETS_(dataset)_PATH in mne-python config to the given path. + If True, set the MNE_DATASETS_(dataset)_PATH in mne-python config + to the given path. If None, the user is prompted. verbose : bool, str, int, or None If not None, override default verbose level (see mne.verbose()). @@ -118,7 +122,7 @@ def data_path( Returns ------- list - A list containing the path to the subject's data file. The list contains only one path. + A list containing the path to the subject's data file. """ if subject not in self.subject_list: raise ValueError("Invalid subject number") @@ -145,8 +149,8 @@ def data_path( return subject_paths @staticmethod - def encoding(events_df: pd.DataFrame): - """Encode the columns 'value' and 'trial_type' in the events file into a single event type. + def encoding(events_df: pd.DataFrame) -> Tuple[np.array, Dict[int, str]]: + """Encode the columns 'value' and 'trial_type' into a single event type. Parameters ---------- @@ -209,7 +213,7 @@ def _get_single_subject_data(self, subject): """ file_path_list = self.data_path(subject)[0] - path_channels, path_electrodes, path_events = self.data_infos() + path_electrodes, path_events = self.data_infos() with warnings.catch_warnings(): warnings.simplefilter("ignore") @@ -225,14 +229,9 @@ def _get_single_subject_data(self, subject): events_df = pd.read_csv(path_events, sep="\t") # Encode the events - event_category, mapping = self.encoding(events_df) + event_category, mapping = self.encoding(events_df=events_df) - _, idx_trigger = np.nonzero(raw.copy().pick("").get_data()) - n_label_stim = len(event_category) - # Create the events array based on the stimulus channel - events = np.column_stack( - (idx_trigger[:n_label_stim], np.zeros_like(event_category), event_category) - ) + events = self.create_event_array(raw=raw, event_category=event_category) # Creating and setting annotations from the events annotations = mne.annotations_from_events( @@ -266,16 +265,14 @@ def data_infos(self): and events information files. """ - path_channels = dl.data_dl(url_channels, self.code) - - path_electrodes = dl.data_dl(url_electrodes, self.code) + path_electrodes = dl.data_dl(LIU2024_ELECTRODES, self.code) - path_events = dl.data_dl(url_events, self.code) + path_events = dl.data_dl(LIU2024_EVENTS, self.code) - return path_channels, path_electrodes, path_events + return path_electrodes, path_events @staticmethod - def _normalize_extension(file_name): + def _normalize_extension(file_name: str) -> str: # Renaming the .tsv file to make sure it's recognized as .tsv # Check if the file already has the ".tsv" extension @@ -287,3 +284,28 @@ def _normalize_extension(file_name): shutil.copy(file_name, file_electrodes_tsv) return file_electrodes_tsv + + @staticmethod + def create_event_array(raw: Any, event_category: np.ndarray) -> np.ndarray: + """ + This method creates an event array based on the stimulus channel. + + Parameters + ---------- + raw : mne.io.Raw + The raw data. + event_category : np.ndarray + The event categories. + + Returns + ------- + events : np.ndarray + The created events array. + """ + _, idx_trigger = np.nonzero(raw.copy().pick("").get_data()) + n_label_stim = len(event_category) + # Create the events array based on the stimulus channel + events = np.column_stack( + (idx_trigger[:n_label_stim], np.zeros_like(event_category), event_category) + ) + return events From 893bd9460b64dcda190a44ac5f9dad9a98e3f29c Mon Sep 17 00:00:00 2001 From: tahatt13 Date: Tue, 25 Jun 2024 16:18:43 +0200 Subject: [PATCH 25/35] modifying the python version in the pre-commit file --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4fe4dbf4e..490f0e8a7 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -38,7 +38,7 @@ repos: rev: 24.3.0 hooks: - id: black - language_version: python3.8 + language_version: python3 args: [ --line-length=90, --target-version=py38 ] - repo: https://github.com/asottile/blacken-docs From 8e89702ee713060dc81d04c2214158c8a83b44f2 Mon Sep 17 00:00:00 2001 From: tahatt13 Date: Tue, 25 Jun 2024 16:19:13 +0200 Subject: [PATCH 26/35] adding description to enhancements --- docs/source/whats_new.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/whats_new.rst b/docs/source/whats_new.rst index 66e6512f5..39559a7bf 100644 --- a/docs/source/whats_new.rst +++ b/docs/source/whats_new.rst @@ -17,7 +17,7 @@ Develop branch Enhancements ~~~~~~~~~~~~ -- None +- Add new dataset :class:`moabb.datasets.Liu2024` dataset (:gh:`619` by `Taha Habib`_) Bugs ~~~~ From 634cee2327c26e6bc38ce3dbcc6712821585b0f2 Mon Sep 17 00:00:00 2001 From: tahatt13 Date: Tue, 25 Jun 2024 16:24:48 +0200 Subject: [PATCH 27/35] adjusting the encoding --- moabb/datasets/liu2024.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/moabb/datasets/liu2024.py b/moabb/datasets/liu2024.py index 701176e59..003a2687f 100644 --- a/moabb/datasets/liu2024.py +++ b/moabb/datasets/liu2024.py @@ -88,7 +88,7 @@ def __init__(self): super().__init__( subjects=list(range(1, 50 + 1)), sessions_per_subject=1, - events={"left_hand": 2, "right_hand": 4, "break": 3, "instr": 1}, + events={"left_hand": 1, "right_hand": 2, "instr": 3, "break": 4}, code="Liu2024", interval=(2, 6), paradigm="imagery", From 44fe1f4412f0f3f3647eb0c9f9820af9a64aec44 Mon Sep 17 00:00:00 2001 From: tahatt13 Date: Wed, 26 Jun 2024 14:54:52 +0200 Subject: [PATCH 28/35] adjusting comments and dataset description --- moabb/datasets/liu2024.py | 41 +++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/moabb/datasets/liu2024.py b/moabb/datasets/liu2024.py index 003a2687f..da76fd265 100644 --- a/moabb/datasets/liu2024.py +++ b/moabb/datasets/liu2024.py @@ -19,7 +19,7 @@ # Link to the raw data LIU2024_URL = "https://figshare.com/ndownloader/files/38516654" -# Links to the channels, electrodes and events information files +# Links to the electrodes and events information files LIU2024_ELECTRODES = "https://figshare.com/ndownloader/files/38516078" LIU2024_EVENTS = "https://figshare.com/ndownloader/files/38516084" @@ -32,11 +32,11 @@ class Liu2024(BaseDataset): .. admonition:: Dataset summary - ============ ======= ======= ========== ================= ============ =============== =========== - Name #Subj #Chan #Classes #Trials / class Trials len Sampling rate #Sessions - ============ ======= ======= ========== ================= ============ =============== =========== - Liu2024 50 29 2 40 4s 500Hz 1 - ============ ======= ======= ========== ================= ============ =============== =========== + ========= ======== ======= ========== ================= ============ =============== =========== + Name #Subj #Chan #Classes #Trials / class Trials len Sampling rate #Sessions + ========= ======== ======= ========== ================= ============ =============== =========== + Liu2024 50 29 2 40 4s 500Hz 1 + ========= ======== ======= ========== ================= ============ =============== =========== **Dataset description** @@ -164,25 +164,24 @@ def encoding(events_df: pd.DataFrame) -> Tuple[np.array, Dict[int, str]]: Notes ----- - # The 'trial_type' variable can take the following values: - # - 1 : Left hand - # - 2 : Right hand - # However, for convenience, we map these values to 0 and 1 respectively. + The 'trial_type' variable can take the following values: + - 1 : Left hand + - 2 : Right hand - # The 'value' variable can take the following values: - # - 1 : instructions - # - 2 : MI - # - 3 : break + The 'value' variable can take the following values: + - 1 : instructions + - 2 : MI + - 3 : break """ # Define the mapping dictionary encoding_mapping = { - (2, 2): 0, # Left hand, MI - (1, 2): 1, # Right hand, MI - (1, 1): 2, # Right hand, instructions - (1, 3): 3, # Right hand, break - (2, 1): 2, # Left hand, instructions - (2, 3): 3, # Left hand, break + (2, 2): 1, # Left hand, MI + (1, 2): 2, # Right hand, MI + (1, 1): 3, # Right hand, instructions + (1, 3): 4, # Right hand, break + (2, 1): 3, # Left hand, instructions + (2, 3): 4, # Left hand, break } mapping = { @@ -255,7 +254,7 @@ def _get_single_subject_data(self, subject): def data_infos(self): """Returns the data paths of the electrodes and events information - This function downloads the necessary data files for channels, electrodes, + This function downloads the necessary data files for electrodes and events from their respective URLs and returns their local file paths. Returns From 304f57a9a53b07e7e73e4f26ceadefb1cb1bcc33 Mon Sep 17 00:00:00 2001 From: tahatt13 Date: Fri, 5 Jul 2024 11:38:02 +0200 Subject: [PATCH 29/35] solving channels types and names issues & correcting encoding --- moabb/datasets/liu2024.py | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/moabb/datasets/liu2024.py b/moabb/datasets/liu2024.py index da76fd265..ce9d02453 100644 --- a/moabb/datasets/liu2024.py +++ b/moabb/datasets/liu2024.py @@ -65,8 +65,6 @@ class Liu2024(BaseDataset): 0.4 μVrms, and the resolution was 24 bits. The acquisition impedance was less than or equal to 20 kΩ. The sampling frequency was 500 Hz. - In the dataset, we've removed the 2 EOG channels and we kept the 29 EEG channels and the STIM channel. - References ---------- @@ -185,10 +183,10 @@ def encoding(events_df: pd.DataFrame) -> Tuple[np.array, Dict[int, str]]: } mapping = { - 0: "left_hand", - 1: "right_hand", - 2: "instr", - 3: "break", + 1: "left_hand", + 2: "right_hand", + 3: "instr", + 4: "break", } # Apply the mapping to the DataFrame event_category = events_df.apply( @@ -220,6 +218,19 @@ def _get_single_subject_data(self, subject): raw = mne.io.read_raw_edf( file_path_list, verbose=False, infer_types=True, stim_channel="" ) + + # Dropping reference channels with constant values + raw = raw.drop_channels(["CPz"]) + + # Renaming channels accurately + raw.rename_channels({"HEOR": "VEOR", "": "STI"}) + + # Create a dictionary with the channel names and their new types + mapping = {"STI": "stim", "VEOR": "eog", "HEOL": "eog"} + + # Set the new channel types + raw.set_channel_types(mapping) + # Normalize and Read the montage path_electrodes = self._normalize_extension(path_electrodes) # Read and set the montage @@ -243,7 +254,7 @@ def _get_single_subject_data(self, subject): # Removing the stimulus channels raw = raw.pick(["eeg", "eog"]) # Setting the montage - raw = raw.set_montage(montage, on_missing="ignore", verbose=False) + raw = raw.set_montage(montage, verbose=False) # Loading dataset raw = raw.load_data(verbose=False) # There is only one session @@ -301,7 +312,7 @@ def create_event_array(raw: Any, event_category: np.ndarray) -> np.ndarray: events : np.ndarray The created events array. """ - _, idx_trigger = np.nonzero(raw.copy().pick("").get_data()) + _, idx_trigger = np.nonzero(raw.copy().pick("STI").get_data()) n_label_stim = len(event_category) # Create the events array based on the stimulus channel events = np.column_stack( From 0a3e6e972d22751303e83cdd0ff55269a6b743f1 Mon Sep 17 00:00:00 2001 From: tahatt13 Date: Fri, 5 Jul 2024 12:06:18 +0200 Subject: [PATCH 30/35] Correcting the number of trials per class and the total trials --- docs/source/dataset_summary.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/dataset_summary.rst b/docs/source/dataset_summary.rst index 7e3521b92..4cd6351db 100644 --- a/docs/source/dataset_summary.rst +++ b/docs/source/dataset_summary.rst @@ -42,7 +42,7 @@ Motor Imagery :class:`Weibo2014`,10,60,7,80,4s,200Hz,1,1,5600 :class:`Zhou2016`,4,14,3,160,5s,250Hz,3,2,11496 :class:`Stieger2021`,62,64,4,450,3s,1000Hz,7 or 11,1,250000 - :class:`Liu2024`,50,29,2,40,4s,500Hz,1,1,160000 + :class:`Liu2024`,50,29,2,20,4s,500Hz,1,1,2000 P300/ERP ====================== From d9c1b7757af1734c471e069743629c65f641c816 Mon Sep 17 00:00:00 2001 From: tahatt13 Date: Fri, 5 Jul 2024 12:08:27 +0200 Subject: [PATCH 31/35] Correcting data description --- moabb/datasets/liu2024.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/moabb/datasets/liu2024.py b/moabb/datasets/liu2024.py index ce9d02453..06239b32c 100644 --- a/moabb/datasets/liu2024.py +++ b/moabb/datasets/liu2024.py @@ -35,7 +35,7 @@ class Liu2024(BaseDataset): ========= ======== ======= ========== ================= ============ =============== =========== Name #Subj #Chan #Classes #Trials / class Trials len Sampling rate #Sessions ========= ======== ======= ========== ================= ============ =============== =========== - Liu2024 50 29 2 40 4s 500Hz 1 + Liu2024 50 29 2 20 4s 500Hz 1 ========= ======== ======= ========== ================= ============ =============== =========== From 3f79b9ce122f8842e999abba6951c6dfe1df33ae Mon Sep 17 00:00:00 2001 From: tahatt13 Date: Fri, 5 Jul 2024 13:34:04 +0200 Subject: [PATCH 32/35] Adding the possibility to exclude/include break and instructions events --- moabb/datasets/liu2024.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/moabb/datasets/liu2024.py b/moabb/datasets/liu2024.py index 06239b32c..8d4940488 100644 --- a/moabb/datasets/liu2024.py +++ b/moabb/datasets/liu2024.py @@ -77,16 +77,23 @@ class Liu2024(BaseDataset): Notes ----- + To add the break and instruction events, set the `break_events` and + `instr_events` parameters to True while instantiating the class. .. versionadded:: 1.1.1 """ - def __init__(self): + def __init__(self, break_events=False, instr_events=False): + events = {"left_hand": 1, "right_hand": 2} + if break_events: + events["instr"] = 3 + if instr_events: + events["break"] = 4 super().__init__( subjects=list(range(1, 50 + 1)), sessions_per_subject=1, - events={"left_hand": 1, "right_hand": 2, "instr": 3, "break": 4}, + events=events, code="Liu2024", interval=(2, 6), paradigm="imagery", From d98602782e4489e82ec4456882639047927369db Mon Sep 17 00:00:00 2001 From: tahatt13 Date: Mon, 8 Jul 2024 10:42:34 +0200 Subject: [PATCH 33/35] Correcting code to include/exclude instr and break events --- moabb/datasets/liu2024.py | 38 ++++++++++++++++++++++++++++++-------- 1 file changed, 30 insertions(+), 8 deletions(-) diff --git a/moabb/datasets/liu2024.py b/moabb/datasets/liu2024.py index 8d4940488..ffd67224e 100644 --- a/moabb/datasets/liu2024.py +++ b/moabb/datasets/liu2024.py @@ -85,6 +85,8 @@ 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} if break_events: events["instr"] = 3 @@ -153,8 +155,7 @@ def data_path( return subject_paths - @staticmethod - def encoding(events_df: pd.DataFrame) -> Tuple[np.array, Dict[int, str]]: + def encoding(self, events_df: pd.DataFrame) -> Tuple[np.array, Dict[int, str]]: """Encode the columns 'value' and 'trial_type' into a single event type. Parameters @@ -183,18 +184,39 @@ def encoding(events_df: pd.DataFrame) -> Tuple[np.array, Dict[int, str]]: encoding_mapping = { (2, 2): 1, # Left hand, MI (1, 2): 2, # Right hand, MI - (1, 1): 3, # Right hand, instructions - (1, 3): 4, # Right hand, break - (2, 1): 3, # Left hand, instructions - (2, 3): 4, # Left hand, break } mapping = { 1: "left_hand", 2: "right_hand", - 3: "instr", - 4: "break", } + + if self.instr_events: + encoding_mapping.update( + { + (1, 1): 3, # Right hand, instructions + (2, 1): 3, # Left hand, instructions + } + ) + mapping[3] = "instr" + + if self.break_events: + encoding_mapping.update( + { + (1, 3): 4, # Right hand, break + (2, 3): 4, # Left hand, break + } + ) + mapping[4] = "break" + + # Filter out rows that won't be encoded + valid_tuples = encoding_mapping.keys() + events_df = events_df[ + events_df.apply( + lambda row: (row["trial_type"], row["value"]) in valid_tuples, axis=1 + ) + ] + # Apply the mapping to the DataFrame event_category = events_df.apply( lambda row: encoding_mapping[(row["trial_type"], row["value"])], axis=1 From 02d5e4ecf874fe3b88fa3480d878163adf1fe86e Mon Sep 17 00:00:00 2001 From: PierreGtch <25532709+PierreGtch@users.noreply.github.com> Date: Mon, 8 Jul 2024 22:11:05 +0200 Subject: [PATCH 34/35] Remove table from docstring Signed-off-by: PierreGtch <25532709+PierreGtch@users.noreply.github.com> --- moabb/datasets/liu2024.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/moabb/datasets/liu2024.py b/moabb/datasets/liu2024.py index ffd67224e..82dbf4af6 100644 --- a/moabb/datasets/liu2024.py +++ b/moabb/datasets/liu2024.py @@ -29,16 +29,6 @@ class Liu2024(BaseDataset): Dataset [1]_ from the study on motor imagery [2]_. - .. admonition:: Dataset summary - - - ========= ======== ======= ========== ================= ============ =============== =========== - Name #Subj #Chan #Classes #Trials / class Trials len Sampling rate #Sessions - ========= ======== ======= ========== ================= ============ =============== =========== - Liu2024 50 29 2 20 4s 500Hz 1 - ========= ======== ======= ========== ================= ============ =============== =========== - - **Dataset description** This dataset includes data from 50 acute stroke patients (the time after stroke ranges from 1 day to 30 days) admitted to the stroke unit of Xuanwu Hospital of Capital Medical University. The patients included 39 males (78%) From a81dabf27f404b5b92f32eaa3f266fe7fe69a0b5 Mon Sep 17 00:00:00 2001 From: Pierre Guetschel Date: Mon, 8 Jul 2024 22:12:46 +0200 Subject: [PATCH 35/35] Add CSV row --- moabb/datasets/summary_imagery.csv | 1 + 1 file changed, 1 insertion(+) diff --git a/moabb/datasets/summary_imagery.csv b/moabb/datasets/summary_imagery.csv index 90c6ea79f..389615cc4 100644 --- a/moabb/datasets/summary_imagery.csv +++ b/moabb/datasets/summary_imagery.csv @@ -16,3 +16,4 @@ Shin2017B,29,30,2,30,10s,200Hz,3,1,5220, Weibo2014,10,60,7,80,4s,200Hz,1,1,5600,https://paperswithcode.com/dataset/weibo2014-moabb Zhou2016,4,14,3,160,5s,250Hz,3,2,11496,https://paperswithcode.com/dataset/zhou2016-moabb Stieger2021,62,64,4,450,3s,1000Hz,7 or 11,1,250000, +Liu2024,50,29,2,20,4s,500Hz,1,1,2000,