From 2158adcf137e85acde0cd38fa95336779f3606c2 Mon Sep 17 00:00:00 2001 From: Sylvain Chevallier Date: Tue, 29 Mar 2022 10:11:41 +0200 Subject: [PATCH 1/4] add brain invaders datasets --- docs/source/datasets.rst | 5 + moabb/datasets/__init__.py | 2 +- moabb/datasets/braininvaders.py | 699 +++++++++++++++++++++++++++----- 3 files changed, 603 insertions(+), 103 deletions(-) diff --git a/docs/source/datasets.rst b/docs/source/datasets.rst index 366889d64..2545d58c1 100644 --- a/docs/source/datasets.rst +++ b/docs/source/datasets.rst @@ -40,7 +40,12 @@ ERP Datasets :toctree: generated/ :template: class.rst + bi2012a bi2013a + bi2014a + bi2014b + bi2015a + bi2015b BNCI2014008 BNCI2014009 BNCI2015003 diff --git a/moabb/datasets/__init__.py b/moabb/datasets/__init__.py index 010254c58..e808d2617 100644 --- a/moabb/datasets/__init__.py +++ b/moabb/datasets/__init__.py @@ -17,7 +17,7 @@ BNCI2015003, BNCI2015004, ) -from .braininvaders import bi2013a +from .braininvaders import bi2012, bi2013a, bi2014a, bi2014b, bi2015a, bi2015b from .epfl import EPFLP300 from .gigadb import Cho2017 from .huebner_llp import Huebner2017, Huebner2018 diff --git a/moabb/datasets/braininvaders.py b/moabb/datasets/braininvaders.py index 84b9a5d0a..2a7e532fa 100644 --- a/moabb/datasets/braininvaders.py +++ b/moabb/datasets/braininvaders.py @@ -1,39 +1,405 @@ import glob import os -import zipfile +import os.path as osp +import shutil +import zipfile as z +from distutils.dir_util import copy_tree import mne +import numpy as np import yaml from mne.channels import make_standard_montage +from scipy.io import loadmat from moabb.datasets import download as dl from moabb.datasets.base import BaseDataset -BI2013a_URL = "https://zenodo.org/record/1494240/files/" +BI2012a_URL = "https://zenodo.org/record/2649069/files/" +BI2013a_URL = "https://zenodo.org/record/2669187/files/" +BI2014a_URL = "https://zenodo.org/record/3266223/files/" +BI2014b_URL = "https://zenodo.org/record/3267302/files/" +BI2015a_URL = "https://zenodo.org/record/3266930/files/" +BI2015b_URL = "https://zenodo.org/record/3268762/files/" + + +def _bi_get_subject_data(ds, subject): # noqa: C901 + file_path_list = ds.data_path(subject) + + sessions = {} + + for file_path in file_path_list: + if ds.code in [ + "Brain Invaders 2012", + "Brain Invaders 2014a", + "Brain Invaders 2014b", + "Brain Invaders 2015b", + ]: + session_name = "session_1" + elif ds.code == "Brain Invaders 2013a": + session_number = file_path.split(os.sep)[-2].replace("Session", "") + session_name = "session_" + session_number + elif ds.code == "Brain Invaders 2015a": + session_name = f'session_{file_path.split("_")[-1][1:2]}' + if session_name not in sessions.keys(): + sessions[session_name] = {} + + if ds.code == "Brain Invaders 2012": + condition = file_path.split("/")[-1].split(".")[0].split(os.sep)[-1] + run_name = "run_" + condition + # fmt: off + chnames = [ + 'F7', 'F3', 'Fz', 'F4', 'F8', 'T7', 'C3', 'Cz', 'C4', + 'T8', 'P7', 'P3', 'Pz', 'P4', 'P8', 'O1', 'O2', 'STI 014' + ] + # fmt: on + chtypes = ["eeg"] * 17 + ["stim"] + X = loadmat(file_path)[condition].T + S = X[1:18, :] + stim = (X[18, :] + X[19, :])[None, :] + X = np.concatenate([S, stim]) + sfreq = 128 + elif ds.code == "Brain Invaders 2013a": + run_number = file_path.split(os.sep)[-1] + run_number = run_number.split("_")[-1] + run_number = run_number.split(".mat")[0] + run_name = "run_" + run_number + # fmt: off + chnames = [ + "Fp1", "Fp2", "F5", "AFz", "F6", "T7", "Cz", "T8", "P7", + "P3", "Pz", "P4", "P8", "O1", "Oz", "O2", "STI 014", + ] + # fmt: on + chtypes = ["eeg"] * 16 + ["stim"] + X = loadmat(file_path)["data"].T + sfreq = 512 + elif ds.code == "Brain Invaders 2014a": + run_name = "run_1" + # fmt: off + chnames = [ + 'Fp1', 'Fp2', 'F3', 'AFz', 'F4', 'T7', 'Cz', 'T8', 'P7', + 'P3', 'Pz', 'P4', 'P8', 'O1', 'Oz', 'O2', 'STI 014' + ] + # fmt: on + chtypes = ["eeg"] * 16 + ["stim"] + file_path = file_path_list[0] + D = loadmat(file_path)["samples"].T + S = D[1:17, :] + stim = D[-1, :] + X = np.concatenate([S, stim[None, :]]) + sfreq = 512 + elif ds.code == "Brain Invaders 2014b": + # fmt: off + chnames = [ + 'Fp1', 'Fp2', 'AFz', 'F7', 'F3', 'F4', 'F8', 'FC5', 'FC1', 'FC2', + 'FC6', 'T7', 'C3', 'Cz', 'C4', 'T8', 'CP5', 'CP1', 'CP2', 'CP6', + 'P7', 'P3', 'Pz', 'P4', 'P8', 'PO7', 'O1', 'Oz', 'O2', 'PO8', 'PO9', + 'PO10', 'STI 014'] + # fmt: on + chtypes = ["eeg"] * 32 + ["stim"] + run_name = "run_1" + + D = loadmat(file_path)["samples"].T + if subject % 2 == 1: + S = D[1:33, :] + else: + S = D[33:65, :] + stim = D[-1, :] + X = np.concatenate([S, stim[None, :]]) + sfreq = 512 + elif ds.code == "Brain Invaders 2015a": + run_name = "run_1" + # fmt: off + chnames = [ + 'Fp1', 'Fp2', 'AFz', 'F7', 'F3', 'F4', 'F8', 'FC5', 'FC1', 'FC2', 'FC6', + 'T7', 'C3', 'Cz', 'C4', 'T8', 'CP5', 'CP1', 'CP2', 'CP6', 'P7', 'P3', + 'Pz', 'P4', 'P8', 'PO7', 'O1', 'Oz', 'O2', 'PO8', 'PO9', 'PO10', 'STI 014' + ] + # fmt: on + chtypes = ["eeg"] * 32 + ["stim"] + D = loadmat(file_path)["DATA"].T + S = D[1:33, :] + stim = D[-2, :] + D[-1, :] + X = np.concatenate([S, stim[None, :]]) + sfreq = 512 + elif ds.code == "Brain Invaders 2015b": + run_name = "run_" + file_path.split("_")[-1].split(".")[0][1] + # fmt: off + chnames = [ + 'Fp1', 'Fp2', 'AFz', 'F7', 'F3', 'F4', 'F8', 'FC5', 'FC1', 'FC2', + 'FC6', 'T7', 'C3', 'Cz', 'C4', 'T8', 'CP5', 'CP1', 'CP2', 'CP6', + 'P7', 'P3', 'Pz', 'P4', 'P8', 'PO7', 'O1', 'Oz', 'O2', 'PO8', 'PO9', + 'PO10', 'STI 014'] + # fmt: on + chtypes = ["eeg"] * 32 + ["stim"] + + D = loadmat(file_path)["mat_data"].T + if subject % 2 == 1: + S = D[1:33, :] + else: + S = D[33:65, :] + stim = D[-1, :] + idx_target = (stim >= 60) & (stim <= 85) + idx_nontarget = (stim >= 20) & (stim <= 45) + stim[idx_target] = 2 + stim[idx_nontarget] = 1 + X = np.concatenate([S, stim[None, :]]) + sfreq = 512 + + info = mne.create_info( + ch_names=chnames, + sfreq=sfreq, + ch_types=chtypes, + verbose=False, + ) + raw = mne.io.RawArray(data=X, info=info, verbose=False) + raw.set_montage(make_standard_montage("standard_1020")) + + if ds.code == "Brain Invaders 2012": + # get rid of the Fz channel (it is the ground) + raw.info["bads"] = ["Fz"] + raw.pick_types(eeg=True, stim=True) + + sessions[session_name][run_name] = raw + return sessions + + +def _bi_data_path( # noqa: C901 + ds, subject, path=None, force_update=False, update_path=None, verbose=None +): + if subject not in ds.subject_list: + raise (ValueError("Invalid subject number")) + + subject_paths = [] + if ds.code == "Brain Invaders 2012": + # check if has the .zip + url = f"{BI2012a_URL}subject_{subject:02}.zip" + path_zip = dl.data_dl(url, "BRAININVADERS2012") + path_folder = path_zip.strip(f"subject_{subject:02}.zip") + + # check if has to unzip + if not (osp.isdir(path_folder + f"subject_{subject}")) and not ( + osp.isdir(path_folder + f"subject_0{subject}") + ): + zip_ref = z.ZipFile(path_zip, "r") + zip_ref.extractall(path_folder) + + # filter the data regarding the experimental conditions + if ds.training: + subject_paths.append( + osp.join(f"{path_folder}subject_{subject:02}", "training.mat") + ) + if ds.online: + subject_paths.append( + osp.join(f"{path_folder}subject_{subject:02}", "online.mat") + ) + + elif ds.code == "Brain Invaders 2013a": + if subject in [1, 2, 3, 4, 5, 6, 7]: + zipname_list = [ + f"subject{subject:02}_session{i:02}.zip" for i in range(1, 8 + 1) + ] + else: + zipname_list = [f"subject{subject:02}.zip"] + + for i, zipname in enumerate(zipname_list): + url = BI2013a_URL + zipname + path_zip = dl.data_dl(url, "BRAININVADERS2013") + path_folder = path_zip.strip(zipname) + + # check if has the directory for the subject + directory = f"{path_folder}subject_{subject:02}" + if not (osp.isdir(directory)): + os.makedirs(directory) + + if not (osp.isdir(osp.join(directory, f"Session{i + 1}"))): + zip_ref = z.ZipFile(path_zip, "r") + zip_ref.extractall(path_folder) + os.makedirs(osp.join(directory, f"Session{i + 1}")) + copy_tree(path_zip.strip(".zip"), directory) + shutil.rmtree(path_zip.strip(".zip")) + + # filter the data regarding the experimental conditions + meta_file = directory + os.sep + "meta.yml" + with open(meta_file, "r") as stream: + meta = yaml.load(stream, Loader=yaml.FullLoader) + conditions = [] + if ds.adaptive: + conditions = conditions + ["adaptive"] + if ds.nonadaptive: + conditions = conditions + ["nonadaptive"] + types = [] + if ds.training: + types = types + ["training"] + if ds.online: + types = types + ["online"] + filenames = [] + for run in meta["runs"]: + run_condition = run["experimental_condition"] + run_type = run["type"] + if (run_condition in conditions) and (run_type in types): + filenames = filenames + [run["filename"]] + + # list the filepaths for this subject + for filename in filenames: + subject_paths = subject_paths + glob.glob( + osp.join(directory, "Session*", filename.replace(".gdf", ".mat")) + ) + + elif ds.code == "Brain Invaders 2014a": + url = f"{BI2014a_URL}subject_{subject:02}.zip" + path_zip = dl.data_dl(url, "BRAININVADERS2014A") + path_folder = path_zip.strip(f"subject_{subject:02}.zip") + + # check if has to unzip + path_folder_subject = f"{path_folder}subject_{subject:02}" + if not (osp.isdir(path_folder_subject)): + os.mkdir(path_folder_subject) + zip_ref = z.ZipFile(path_zip, "r") + zip_ref.extractall(path_folder_subject) + + # filter the data regarding the experimental conditions + subject_paths.append(osp.join(path_folder_subject, f"subject_{subject:02}.mat")) + + elif ds.code == "Brain Invaders 2014b": + group = (subject + 1) // 2 + url = f"{BI2014b_URL}group_{group:02}_mat.zip" + path_zip = dl.data_dl(url, "BRAININVADERS2014B") + path_folder = path_zip.strip(f"group_{group:02}_mat.zip") + + # check if has to unzip + path_folder_subject = f"{path_folder}group_{group:02}" + if not (osp.isdir(path_folder_subject)): + os.mkdir(path_folder_subject) + zip_ref = z.ZipFile(path_zip, "r") + zip_ref.extractall(path_folder_subject) + + subject_paths = [] + # filter the data regarding the experimental conditions + if subject % 2 == 1: + subject_paths.append( + osp.join(path_folder_subject, f"group_{group:02}_sujet_01.mat") + ) + else: + subject_paths.append( + osp.join(path_folder_subject, f"group_{group:02}_sujet_02.mat") + ) + # Collaborative session are not loaded + # subject_paths.append(osp.join(path_folder_subject, f'group_{(subject+1)//2:02}.mat') + + elif ds.code == "Brain Invaders 2015a": + # TODO: possible fusion with 2014a? + url = f"{BI2015a_URL}subject_{subject:02}_mat.zip" + path_zip = dl.data_dl(url, "BRAININVADERS2015A") + path_folder = path_zip.strip(f"subject_{subject:02}.zip") + + # check if has to unzip + path_folder_subject = f"{path_folder}subject_{subject:02}" + if not (osp.isdir(path_folder_subject)): + os.mkdir(path_folder_subject) + zip_ref = z.ZipFile(path_zip, "r") + zip_ref.extractall(path_folder_subject) + + # filter the data regarding the experimental conditions + subject_paths = [] + for session in [1, 2, 3]: + subject_paths.append( + osp.join( + path_folder_subject, f"subject_{subject:02}_session_{session:02}.mat" + ) + ) + elif ds.code == "Brain Invaders 2015b": + # TODO: possible fusion with 2014b? + url = f"{BI2015b_URL}group_{(subject+1)//2:02}_mat.zip" + path_zip = dl.data_dl(url, "BRAININVADERS2015B") + path_folder = path_zip.strip(f"group_{(subject+1)//2:02}_mat.zip") + # check if has to unzip + path_folder_subject = f"{path_folder}group_{(subject+1)//2:02}" + if not (osp.isdir(path_folder_subject)): + os.mkdir(path_folder_subject) + zip_ref = z.ZipFile(path_zip, "r") + zip_ref.extractall(path_folder_subject) + + subject_paths = [] + subject_paths = [ + osp.join( + path_folder, + f"group_{(subject+1)//2:02}", + f"group_{(subject+1)//2:02}_s{i}", + ) + for i in range(1, 5) + ] + + return subject_paths + + +class bi2012(BaseDataset): + """P300 dataset bi2012 from a "Brain Invaders" experiment + + Dataset following the setup from [1]_ carried-out at University of + Grenoble Alpes. + + **Dataset Description** + + This dataset contains electroencephalographic (EEG) recordings of 25 subjects testing + the Brain Invaders, a visual P300 Brain-Computer Interface inspired by the famous vintage + video game Space Invaders (Taito, Tokyo, Japan). The visual P300 is an event-related + potential elicited by a visual stimulation, peaking 240-600 ms after stimulus onset. EEG + data were recorded by 16 electrodes in an experiment that took place in the GIPSA-lab, + Grenoble, France, in 2012). A full description of the experiment is available in [1]_. + + **Authors** + + Principal Investigator: B.Sc. Gijsbrecht Franciscus Petrus Van Veen + Technical Supervisors: Ph.D. Alexandre Barachant, Eng. Anton Andreev, Eng. Grégoire Cattan, + Eng. Pedro. L. C. Rodrigues + Scientific Supervisor: Ph.D. Marco Congedo + + **ID of the dataset** + BI.EEG.2012-GIPSA + + References + ---------- + + .. [1] Van Veen, G., Barachant, A., Andreev, A., Cattan, G., Rodrigues, P. C., & + Congedo, M. (2019). Building Brain Invaders: EEG data of an experimental validation. + arXiv preprint arXiv:1905.05182. + """ + + def __init__(self, Training=True, Online=False): + super().__init__( + subjects=list(range(1, 26)), + sessions_per_subject=1, + events=dict(Target=2, NonTarget=1), + code="Brain Invaders 2012", + interval=[0, 1], + paradigm="p300", + doi="https://doi.org/10.5281/zenodo.2649006", + ) + + self.training = Training + self.online = Online + + def _get_single_subject_data(self, subject): + """return data for a single subject""" + return _bi_get_subject_data(self, subject) + + def data_path( + self, subject, path=None, force_update=False, update_path=None, verbose=None + ): + return _bi_data_path(self, subject, path, force_update, update_path, verbose) class bi2013a(BaseDataset): """P300 dataset bi2013a from a "Brain Invaders" experiment Dataset following the setup from [1]_ carried-out at University of - Grenoble Alpes [1]_. + Grenoble Alpes. **Dataset Description** This dataset concerns an experiment carried out at GIPSA-lab (University of Grenoble Alpes, CNRS, Grenoble-INP) in 2013. - Principal Investigators: Erwan Vaineau, Dr. Alexandre Barachant - Scientific Supervisor : Dr. Marco Congedo - Technical Supervisor : Anton Andreev - - The experiment uses the Brain Invaders P300-based Brain-Computer Interface - [7]_, which uses the Open-ViBE platform for on-line EEG data acquisition and - processing [1]_ [9]_. For classification purposes the Brain Invaders - implements on-line Riemannian MDM classifiers [2]_ [3]_ [4]_ [6]_. This experiment - features both a training-test (classical) mode of operation and a - calibration-less mode of operation [4]_ [5]_ [6]_ [8]_. - The recordings concerned 24 subjects in total. Subjects 1 to 7 participated to eight sessions, run in different days, subject 8 to 24 participated to one session. Each session consisted in two runs, one in a Non-Adaptive @@ -42,22 +408,22 @@ class bi2013a(BaseDataset): was a Training (calibration) phase and an Online phase, always passed in this order. In the non-Adaptive run the data from the Training phase was used for classifying the trials on the Online phase using the training-test - version of the MDM algorithm [3]_ [4]_. In the Adaptive run, the data from the + version of the MDM algorithm [2]_. In the Adaptive run, the data from the training phase was not used at all, instead the classifier was initialized with generic class geometric means and continuously adapted to the incoming - data using the Riemannian method explained in [4]_. Subjects were completely + data using the Riemannian method explained in [2]_. Subjects were completely blind to the mode of operation and the two runs appeared to them identical. In the Brain Invaders P300 paradigm, a repetition is composed of 12 flashes, of which 2 include the Target symbol (Target flashes) and 10 do - not (non-Target flash). Please see [7]_ for a description of the paradigm. + not (non-Target flash). Please see [3]_ for a description of the paradigm. For this experiment, in the Training phases the number of flashes is fixed (80 Target flashes and 400 non-Target flashes). In the Online phases the number of Target and non-Target still are in a ratio 1/5, however their number is variable because the Brain Invaders works with a fixed number of game levels, however the number of repetitions needed to destroy the target (hence to proceed to the next level) depends on the user’s performance - [4]_ [5]_. In any case, since the classes are unbalanced, an appropriate score + [2]_. In any case, since the classes are unbalanced, an appropriate score must be used for quantifying the performance of classification methods (e.g., balanced accuracy, AUC methods, etc). @@ -71,50 +437,37 @@ class bi2013a(BaseDataset): * Reference: left ear-lobe. * Ground: N/A. + **Authors** + + Principal Investigators: Erwan Vaineau, Dr. Alexandre Barachant + Scientific Supervisor : Dr. Marco Congedo + Technical Supervisor : Anton Andreev + References ---------- - .. [1] Arrouët C, Congedo M, Marvie J-E, Lamarche F, Lècuyer A, Arnaldi B - (2005) Open-ViBE: a 3D Platform for Real-Time Neuroscience. - Journal of Neurotherapy, 9(1), 3-25. - .. [2] Barachant A, Bonnet S, Congedo M, Jutten C (2013) Classification of - covariance matrices using a Riemannian-based kernel for BCI - applications. Neurocomputing 112, 172-178. - .. [3] Barachant A, Bonnet S, Congedo M, Jutten C (2012) Multi-Class Brain - Computer Interface, Classification by Riemannian Geometry. - IEEE Transactions on Biomedical Engineering 59(4), 920-928 - .. [4] Barachant A, Congedo M (2014) A Plug & Play P300 BCI using + .. [1] Vaineau, E., Barachant, A., Andreev, A., Rodrigues, P. C., + Cattan, G. & Congedo, M. (2019). Brain invaders adaptive + versus non-adaptive P300 brain-computer interface dataset. + arXiv preprint arXiv:1904.09111. + .. [2] Barachant A, Congedo M (2014) A Plug & Play P300 BCI using Information Geometry. arXiv:1409.0107. - .. [5] Congedo M, Barachant A, Andreev A (2013) A New Generation of - Brain-Computer Interface Based on Riemannian Geometry. - arXiv:1310.8115. - .. [6] Congedo M, Barachant A, Bhatia R (2017) Riemannian Geometry for - EEG-based Brain-Computer Interfaces; a Primer and a Review. - Brain-Computer Interfaces, 4(3), 155-174. .. [7] Congedo M, Goyat M, Tarrin N, Ionescu G, Rivet B,Varnet L, Rivet B, Phlypo R, Jrad N, Acquadro M, Jutten C (2011) “Brain Invaders”: a prototype of an open-source P300-based video game working with the OpenViBE platform. Proc. IBCI Conf., Graz, Austria, 280-283. - .. [8] Congedo M, Korczowski L, Delorme A, Lopes da Silva F. (2016) - Spatio-temporal common pattern: A companion method for ERP analysis - in the time domain. Journal of Neuroscience Methods, 267, 74-88. - .. [9] Renard Y, Lotte F, Gibert G, Congedo M, Maby E, Delannoy V, Bertrand - O, Lécuyer A (2010) OpenViBE: An Open-Source Software Platform to - Design, Test and Use Brain-Computer Interfaces in Real and Virtual - Environments. PRESENCE : Teleoperators and Virtual Environments - 19(1), 35-53. """ def __init__(self, NonAdaptive=True, Adaptive=False, Training=True, Online=False): super().__init__( - subjects=list(range(1, 24 + 1)), + subjects=list(range(1, 25)), sessions_per_subject=1, - events=dict(Target=1, NonTarget=2), + events=dict(Target=33285, NonTarget=33286), code="Brain Invaders 2013a", interval=[0, 1], paradigm="p300", - doi="", + doi="https://doi.org/10.5281/zenodo.2669187", ) self.adaptive = Adaptive @@ -124,75 +477,217 @@ def __init__(self, NonAdaptive=True, Adaptive=False, Training=True, Online=False def _get_single_subject_data(self, subject): """return data for a single subject""" + return _bi_get_subject_data(self, subject) + + def data_path( + self, subject, path=None, force_update=False, update_path=None, verbose=None + ): + return _bi_data_path(self, subject, path, force_update, update_path, verbose) + + +class bi2014a(BaseDataset): + """P300 dataset bi2014a from a "Brain Invaders" experiment + + **Dataset Description** + + This dataset contains electroencephalographic (EEG) recordings of 71 subjects + playing to a visual P300 Brain-Computer Interface (BCI) videogame named Brain Invaders. + The interface uses the oddball paradigm on a grid of 36 symbols (1 Target, 35 Non-Target) + that are flashed pseudo-randomly to elicit the P300 response. EEG data were recorded + using 16 active dry electrodes with up to three game sessions. The experiment took place + at GIPSA-lab, Grenoble, France, in 2014. A full description of the experiment is available + at [1]_. The ID of this dataset is bi2014a. + + **Authors** + + Investigators: Eng. Louis Korczowski, B. Sc. Ekaterina Ostaschenko + Technical Support: Eng. Anton Andreev, Eng. Grégoire Cattan, Eng. Pedro. L. C. Rodrigues, + M. Sc. Violette Gautheret + Scientific Supervisor: Ph.D. Marco Congedo + + References + ---------- + + .. [1] Korczowski, L., Ostaschenko, E., Andreev, A., Cattan, G., Rodrigues, P. L. C., + Gautheret, V., & Congedo, M. (2019). Brain Invaders calibration-less P300-based + BCI using dry EEG electrodes Dataset (bi2014a). + https://hal.archives-ouvertes.fr/hal-02171575 + """ + + def __init__(self): + super().__init__( + subjects=list(range(1, 65)), + sessions_per_subject=1, + events=dict(Target=2, NonTarget=1), + code="Brain Invaders 2014a", + interval=[0, 1], + paradigm="p300", + doi="https://doi.org/10.5281/zenodo.3266222", + ) + + def _get_single_subject_data(self, subject): + """return data for a single subject""" + return _bi_get_subject_data(self, subject) - file_path_list = self.data_path(subject) - sessions = {} - for file_path in file_path_list: + def data_path( + self, subject, path=None, force_update=False, update_path=None, verbose=None + ): + return _bi_data_path(self, subject, path, force_update, update_path, verbose) - session_number = file_path.split(os.sep)[-2].replace("Session", "") - session_name = "session_" + session_number - if session_name not in sessions.keys(): - sessions[session_name] = {} - run_number = file_path.split(os.sep)[-1] - run_number = run_number.split("_")[-1] - run_number = run_number.split(".gdf")[0] - run_name = "run_" + run_number +class bi2014b(BaseDataset): + """P300 dataset bi2014b from a "Brain Invaders" experiment + + **Dataset Description** + + This dataset contains electroencephalographic (EEG) recordings of 38 subjects playing in + pair (19 pairs) to the multi-user version of a visual P300-based Brain-Computer Interface (BCI) + named Brain Invaders. The interface uses the oddball paradigm on a grid of 36 symbols (1 Target, + 35 Non-Target) that are flashed pseudo-randomly to elicit a P300 response, an evoked-potential + appearing about 300ms after stimulation onset. EEG data were recorded using 32 active wet + electrodes per subjects (total: 64 electrodes) during three randomized conditions + (Solo1, Solo2, Collaboration). The experiment took place at GIPSA-lab, Grenoble, France, in 2014. + A full description of the experiment is available at [1]_. The ID of this dataset is bi2014b. + + **Authors** + + Investigators: Eng. Louis Korczowski, B. Sc. Ekaterina Ostaschenko + Technical Support: Eng. Anton Andreev, Eng. Grégoire Cattan, Eng. Pedro. L. C. Rodrigues, + M. Sc. Violette Gautheret + Scientific Supervisor: Ph.D. Marco Congedo - raw_original = mne.io.read_raw_gdf(file_path, preload=True) - raw_original.rename_channels({"FP1": "Fp1", "FP2": "Fp2"}) - raw_original.set_montage(make_standard_montage("standard_1020")) + References + ---------- + + .. [1] Korczowski, L., Ostaschenko, E., Andreev, A., Cattan, G., Rodrigues, P. L. C., + Gautheret, V., & Congedo, M. (2019). Brain Invaders Solo versus Collaboration: + Multi-User P300-Based Brain-Computer Interface Dataset (bi2014b). + https://hal.archives-ouvertes.fr/hal-02173958 + """ - sessions[session_name][run_name] = raw_original + def __init__(self): + super().__init__( + subjects=list(range(1, 38)), + sessions_per_subject=1, + events=dict(Target=2, NonTarget=1), + code="Brain Invaders 2014b", + interval=[0, 1], + paradigm="p300", + doi="https://doi.org/10.5281/zenodo.3267301", + ) - return sessions + def _get_single_subject_data(self, subject): + """return data for a single subject""" + return _bi_get_subject_data(self, subject) def data_path( self, subject, path=None, force_update=False, update_path=None, verbose=None ): + return _bi_data_path(self, subject, path, force_update, update_path, verbose) - if subject not in self.subject_list: - raise (ValueError("Invalid subject number")) - # check if has the .zip - url = "{:s}subject{:d}.zip".format(BI2013a_URL, subject) - path_zip = dl.data_dl(url, "BRAININVADERS") - path_folder = path_zip.strip("subject{:d}.zip".format(subject)) +class bi2015a(BaseDataset): + """P300 dataset bi2015a from a "Brain Invaders" experiment - # check if has to unzip - if not (os.path.isdir(path_folder + "subject{:d}".format(subject))): - print("unzip", path_zip) - zip_ref = zipfile.ZipFile(path_zip, "r") - zip_ref.extractall(path_folder) + **Dataset Description** - # filter the data regarding the experimental conditions - meta_file = os.path.join("subject{:d}".format(subject), "meta.yml") - meta_path = path_folder + meta_file - with open(meta_path, "r") as stream: - meta = yaml.load(stream, Loader=yaml.FullLoader) - conditions = [] - if self.adaptive: - conditions = conditions + ["adaptive"] - if self.nonadaptive: - conditions = conditions + ["nonadaptive"] - types = [] - if self.training: - types = types + ["training"] - if self.online: - types = types + ["online"] - filenames = [] - for run in meta["runs"]: - run_condition = run["experimental_condition"] - run_type = run["type"] - if (run_condition in conditions) and (run_type in types): - filenames = filenames + [run["filename"]] + This dataset contains electroencephalographic (EEG) recordings + of 43 subjects playing to a visual P300 Brain-Computer Interface (BCI) + videogame named Brain Invaders. The interface uses the oddball paradigm + on a grid of 36 symbols (1 Target, 35 Non-Target) that are flashed + pseudo-randomly to elicit the P300 response. EEG data were recorded using + 32 active wet electrodes with three conditions: flash duration 50ms, 80ms + or 110ms. The experiment took place at GIPSA-lab, Grenoble, France, in 2015. + A full description of the experiment is available at [1]_. The ID of this + dataset is bi2015a. - # list the filepaths for this subject - subject_paths = [] - for filename in filenames: - subject_paths = subject_paths + glob.glob( - os.path.join( - path_folder, "subject{:d}".format(subject), "Session*", filename - ) - ) # noqa - return subject_paths + **Authors** + + Investigators: Eng. Louis Korczowski, B. Sc. Martine Cederhout + Technical Support: Eng. Anton Andreev, Eng. Grégoire Cattan, Eng. Pedro. L. C. Rodrigues, + M. Sc. Violette Gautheret + Scientific Supervisor: Ph.D. Marco Congedo + + References + ---------- + + .. [1] Korczowski, L., Cederhout, M., Andreev, A., Cattan, G., Rodrigues, P. L. C., + Gautheret, V., & Congedo, M. (2019). Brain Invaders calibration-less P300-based + BCI with modulation of flash duration Dataset (bi2015a) + https://hal.archives-ouvertes.fr/hal-02172347 + """ + + def __init__(self): + super().__init__( + subjects=list(range(1, 44)), + sessions_per_subject=3, + events=dict(Target=2, NonTarget=1), + code="Brain Invaders 2015a", + interval=[0, 1], + paradigm="p300", + doi="https://doi.org/10.5281/zenodo.3266929", + ) + + def _get_single_subject_data(self, subject): + """return data for a single subject""" + return _bi_get_subject_data(self, subject) + + def data_path( + self, subject, path=None, force_update=False, update_path=None, verbose=None + ): + return _bi_data_path(self, subject, path, force_update, update_path, verbose) + + +class bi2015b(BaseDataset): + """P300 dataset bi2015b from a "Brain Invaders" experiment + + **Dataset Description** + + This dataset contains electroencephalographic (EEG) recordings + of 44 subjects playing in pair to the multi-user version of a visual + P300 Brain-Computer Interface (BCI) named Brain Invaders. The interface + uses the oddball paradigm on a grid of 36 symbols (1 or 2 Target, + 35 or 34 Non-Target) that are flashed pseudo-randomly to elicit the + P300 response. EEG data were recorded using 32 active wet electrodes + per subjects (total: 64 electrodes) during four randomised conditions + (Cooperation 1-Target, Cooperation 2-Targets, Competition 1-Target, + Competition 2-Targets). The experiment took place at GIPSA-lab, Grenoble, + France, in 2015. A full description of the experiment is available at + A full description of the experiment is available at [1]_. The ID of this + dataset is bi2015a. + + **Authors** + + Investigators: Eng. Louis Korczowski, B. Sc. Martine Cederhout + Technical Support: Eng. Anton Andreev, Eng. Grégoire Cattan, Eng. Pedro. L. C. Rodrigues, + M. Sc. Violette Gautheret + Scientific Supervisor: Ph.D. Marco Congedo + + References + ---------- + + .. [1] Korczowski, L., Cederhout, M., Andreev, A., Cattan, G., Rodrigues, P. L. C., + Gautheret, V., & Congedo, M. (2019). Brain Invaders Cooperative versus Competitive: + Multi-User P300-based Brain-Computer Interface Dataset (bi2015b) + https://hal.archives-ouvertes.fr/hal-02172347 + """ + + def __init__(self): + super().__init__( + subjects=list(range(1, 45)), + sessions_per_subject=1, + events=dict(Target=2, NonTarget=1), + code="Brain Invaders 2015b", + interval=[0, 1], + paradigm="p300", + doi="https://doi.org/10.5281/zenodo.3267307", + ) + + def _get_single_subject_data(self, subject): + """return data for a single subject""" + return _bi_get_subject_data(self, subject) + + def data_path( + self, subject, path=None, force_update=False, update_path=None, verbose=None + ): + return _bi_data_path(self, subject, path, force_update, update_path, verbose) From 65a579c6dbcd47cafd6380e3bc83bde1ba216382 Mon Sep 17 00:00:00 2001 From: Sylvain Chevallier Date: Tue, 29 Mar 2022 11:32:35 +0200 Subject: [PATCH 2/4] add version added for datasets --- moabb/datasets/braininvaders.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/moabb/datasets/braininvaders.py b/moabb/datasets/braininvaders.py index 2a7e532fa..9fc090c06 100644 --- a/moabb/datasets/braininvaders.py +++ b/moabb/datasets/braininvaders.py @@ -358,6 +358,10 @@ class bi2012(BaseDataset): **ID of the dataset** BI.EEG.2012-GIPSA + Notes + ----- + .. versionadded:: 0.4.6 + References ---------- @@ -505,6 +509,10 @@ class bi2014a(BaseDataset): M. Sc. Violette Gautheret Scientific Supervisor: Ph.D. Marco Congedo + Notes + ----- + .. versionadded:: 0.4.6 + References ---------- @@ -556,6 +564,10 @@ class bi2014b(BaseDataset): M. Sc. Violette Gautheret Scientific Supervisor: Ph.D. Marco Congedo + Notes + ----- + .. versionadded:: 0.4.6 + References ---------- @@ -608,6 +620,10 @@ class bi2015a(BaseDataset): M. Sc. Violette Gautheret Scientific Supervisor: Ph.D. Marco Congedo + Notes + ----- + .. versionadded:: 0.4.6 + References ---------- @@ -663,6 +679,10 @@ class bi2015b(BaseDataset): M. Sc. Violette Gautheret Scientific Supervisor: Ph.D. Marco Congedo + Notes + ----- + .. versionadded:: 0.4.6 + References ---------- From 477a214420e116de8e8889ed9e1067aa154b1ad3 Mon Sep 17 00:00:00 2001 From: Sylvain Chevallier Date: Wed, 6 Apr 2022 15:10:27 +0200 Subject: [PATCH 3/4] correct scaling factor for dry electrodes --- moabb/datasets/braininvaders.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/moabb/datasets/braininvaders.py b/moabb/datasets/braininvaders.py index 9fc090c06..664cc7c84 100644 --- a/moabb/datasets/braininvaders.py +++ b/moabb/datasets/braininvaders.py @@ -84,7 +84,7 @@ def _bi_get_subject_data(ds, subject): # noqa: C901 chtypes = ["eeg"] * 16 + ["stim"] file_path = file_path_list[0] D = loadmat(file_path)["samples"].T - S = D[1:17, :] + S = D[1:17, :] * 1e-6 stim = D[-1, :] X = np.concatenate([S, stim[None, :]]) sfreq = 512 From e25cb3790a53e845eae7ef7ddce9b231607a091f Mon Sep 17 00:00:00 2001 From: Sylvain Chevallier Date: Wed, 6 Apr 2022 15:24:35 +0200 Subject: [PATCH 4/4] apply scaling factor for uncorrected datasets --- moabb/datasets/braininvaders.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/moabb/datasets/braininvaders.py b/moabb/datasets/braininvaders.py index 664cc7c84..8ea72f9b0 100644 --- a/moabb/datasets/braininvaders.py +++ b/moabb/datasets/braininvaders.py @@ -55,7 +55,7 @@ def _bi_get_subject_data(ds, subject): # noqa: C901 # fmt: on chtypes = ["eeg"] * 17 + ["stim"] X = loadmat(file_path)[condition].T - S = X[1:18, :] + S = X[1:18, :] * 1e-6 stim = (X[18, :] + X[19, :])[None, :] X = np.concatenate([S, stim]) sfreq = 128 @@ -101,9 +101,9 @@ def _bi_get_subject_data(ds, subject): # noqa: C901 D = loadmat(file_path)["samples"].T if subject % 2 == 1: - S = D[1:33, :] + S = D[1:33, :] * 1e-6 else: - S = D[33:65, :] + S = D[33:65, :] * 1e-6 stim = D[-1, :] X = np.concatenate([S, stim[None, :]]) sfreq = 512 @@ -118,7 +118,7 @@ def _bi_get_subject_data(ds, subject): # noqa: C901 # fmt: on chtypes = ["eeg"] * 32 + ["stim"] D = loadmat(file_path)["DATA"].T - S = D[1:33, :] + S = D[1:33, :] * 1e-6 stim = D[-2, :] + D[-1, :] X = np.concatenate([S, stim[None, :]]) sfreq = 512 @@ -135,9 +135,9 @@ def _bi_get_subject_data(ds, subject): # noqa: C901 D = loadmat(file_path)["mat_data"].T if subject % 2 == 1: - S = D[1:33, :] + S = D[1:33, :] * 1e-6 else: - S = D[33:65, :] + S = D[33:65, :] * 1e-6 stim = D[-1, :] idx_target = (stim >= 60) & (stim <= 85) idx_nontarget = (stim >= 20) & (stim <= 45)