11"""BNCI 2014-001 Motor imagery dataset."""
22
3+ import io
4+ import zipfile
5+ from pathlib import Path
6+
37import numpy as np
4- from mne import create_info
8+ from mne import Annotations , create_info
59from mne .channels import make_standard_montage
610from mne .io import RawArray
711from mne .utils import verbose
@@ -73,6 +77,7 @@ def load_data(
7377 dictionary containing events and their code.
7478 """
7579 dataset_list = {
80+ "BNCI2003-004" : _load_data_iva_2003 ,
7681 "BNCI2014-001" : _load_data_001_2014 ,
7782 "BNCI2014-002" : _load_data_002_2014 ,
7883 "BNCI2014-004" : _load_data_004_2014 ,
@@ -88,6 +93,7 @@ def load_data(
8893 }
8994
9095 baseurl_list = {
96+ "BNCI2003-004" : "https://www.bbci.de/competition/" ,
9197 "BNCI2014-001" : BNCI_URL ,
9298 "BNCI2014-002" : BNCI_URL ,
9399 "BNCI2015-001" : BNCI_URL ,
@@ -119,6 +125,57 @@ def load_data(
119125 )
120126
121127
128+ @verbose
129+ def _load_data_iva_2003 (
130+ subject ,
131+ path = None ,
132+ force_update = False ,
133+ update_path = None ,
134+ base_url = None ,
135+ only_filenames = False ,
136+ verbose = None ,
137+ ):
138+ """Loads data for the BNCI2003-IVa dataset."""
139+ # Raises ValueError is subject is not between 1 and 5
140+ if (subject < 1 ) or (subject > 5 ):
141+ raise ValueError (f"Subject must be between 1 and 5. Got { subject } " )
142+
143+ subject_names = ["aa" , "al" , "av" , "aw" , "ay" ]
144+
145+ # fmt: off
146+ ch_names = ['Fp1' , 'AFp1' , 'Fpz' , 'AFp2' , 'Fp2' , 'AF7' , 'AF3' ,
147+ 'AF4' , 'AF8' , 'FAF5' , 'FAF1' , 'FAF2' , 'FAF6' , 'F7' ,
148+ 'F5' , 'F3' , 'F1' , 'Fz' , 'F2' , 'F4' , 'F6' , 'F8' , 'FFC7' ,
149+ 'FFC5' , 'FFC3' , 'FFC1' , 'FFC2' , 'FFC4' , 'FFC6' , 'FFC8' ,
150+ 'FT9' , 'FT7' , 'FC5' , 'FC3' , 'FC1' , 'FCz' , 'FC2' , 'FC4' ,
151+ 'FC6' , 'FT8' , 'FT10' , 'CFC7' , 'CFC5' , 'CFC3' , 'CFC1' ,
152+ 'CFC2' , 'CFC4' , 'CFC6' , 'CFC8' , 'T7' , 'C5' , 'C3' , 'C1' ,
153+ 'Cz' ,'C2' , 'C4' , 'C6' , 'T8' , 'CCP7' , 'CCP5' , 'CCP3' , 'CCP1' ,
154+ 'CCP2' , 'CCP4' , 'CCP6' , 'CCP8' , 'TP9' , 'TP7' , 'CP5' , 'CP3' ,
155+ 'CP1' , 'CPz' , 'CP2' , 'CP4' , 'CP6' , 'TP8' , 'TP10' , 'PCP7' ,
156+ 'PCP5' , 'PCP3' , 'PCP1' , 'PCP2' , 'PCP4' , 'PCP6' , 'PCP8' , 'P9' ,
157+ 'P7' , 'P5' , 'P3' , 'P1' , 'Pz' , 'P2' , 'P4' , 'P6' , 'P8' , 'P10' ,
158+ 'PPO7' , 'PPO5' , 'PPO1' , 'PPO2' , 'PPO6' , 'PPO8' , 'PO7' , 'PO3' ,
159+ 'PO1' , 'POz' , 'PO2' , 'PO4' , 'PO8' , 'OPO1' , 'OPO2' , 'O1' ,
160+ 'Oz' , 'O2' , 'OI1' , 'OI2' , 'I1' , 'I2' ]
161+ # fmt: on
162+ ch_type = ["eeg" ] * 118
163+
164+ url = "{u}download/competition_iii/berlin/100Hz/data_set_IVa_{r}_mat.zip" .format (
165+ u = base_url , r = subject_names [subject - 1 ]
166+ )
167+
168+ filename = data_path (url , path , force_update , update_path )
169+
170+ if only_filenames :
171+ return filename
172+
173+ runs , ev = _convert_bbci2003 (filename [0 ], ch_names , ch_type )
174+
175+ session = {"0train" : {"0" : runs }}
176+ return session
177+
178+
122179@verbose
123180def _load_data_001_2014 (
124181 subject ,
@@ -698,6 +755,102 @@ def _convert_run_bbci(run, ch_types, verbose=None):
698755 return raw , event_id
699756
700757
758+ def _convert_bbci2003 (filename , ch_names , ch_type ):
759+ """
760+ Process motor imagery data from MAT files.
761+
762+ Parameters
763+ ----------
764+ filename (str):
765+ Path to the MAT file.
766+ ch_names (list of str):
767+ List of channel names.
768+ ch_type (list of str):
769+ List of channel types.
770+
771+ Returns
772+ -------
773+ raw (instance of RawArray):
774+ returns MNE Raw object.
775+ """
776+ zip_path = Path (filename )
777+
778+ with zipfile .ZipFile (zip_path , "r" ) as z :
779+ mat_files = [f for f in z .namelist () if f .endswith (".mat" )]
780+
781+ if not mat_files :
782+ raise FileNotFoundError ("No .mat file found in zip archive." )
783+
784+ with z .open (mat_files [0 ]) as f :
785+ data = loadmat (io .BytesIO (f .read ()))
786+
787+ run = data
788+ raw , ev = _convert_run_bbci2003 (run , ch_names , ch_type )
789+ return raw , ev
790+
791+
792+ @verbose
793+ def _convert_run_bbci2003 (run , ch_names , ch_types , verbose = None ):
794+ """
795+ Converts one run to a raw MNE object.
796+
797+ Parameters
798+ ----------
799+ run (ndarray):
800+ The continuous EEG signal.
801+ ch_names (list of str):
802+ List of channel names.
803+ ch_types (list of str):
804+ List of channel types.
805+ verbose (bool, str, int, or None):
806+ If not None, override default verbose level (see :func:`mne.verbose`
807+ and :ref:`Logging documentation <tut_logging>` for more).
808+
809+ Returns:
810+ raw (instance of RawArray):
811+ MNE Raw object.
812+ event_id (dict):
813+ Dictionary containing class names.
814+ """
815+ class_map = {
816+ "right" : "right_hand" ,
817+ "foot" : "feet" ,
818+ }
819+
820+ raw_labels = run ["mrk" ]["y" ][0 , 0 ][0 ]
821+ labels_mask = ~ np .isnan (raw_labels )
822+ valid_labels = raw_labels [labels_mask ]
823+ labels = valid_labels .astype (int ) - 1
824+
825+ raw_positions = run ["mrk" ][0 ][0 ]["pos" ][0 ]
826+ positions = raw_positions [labels_mask ]
827+
828+ sfreq = float (run ["nfo" ][0 , 0 ]["fs" ][0 , 0 ])
829+ eeg_data = run ["cnt" ]
830+ raw_classes = run ["mrk" ]["className" ]
831+
832+ while isinstance (raw_classes , (list , np .ndarray )) and len (raw_classes ) == 1 :
833+ raw_classes = raw_classes [0 ]
834+ class_names = [cls [0 ] for cls in raw_classes ]
835+
836+ for i , word in enumerate (class_names ):
837+ if word in class_map :
838+ class_names [i ] = class_map [word ]
839+
840+ info = create_info (ch_names = ch_names , ch_types = ch_types , sfreq = sfreq )
841+
842+ onset = positions / sfreq
843+ duration = 0
844+ description = [class_names [i ] for i in labels ]
845+ annotations = Annotations (onset = onset , duration = duration , description = description )
846+
847+ event_id = {name : i for i , name in enumerate (class_names )}
848+ raw = RawArray (data = eeg_data .T , info = info , verbose = verbose )
849+ raw .set_annotations (annotations )
850+
851+ return raw , event_id
852+
853+
701854@verbose
702855def _convert_run_epfl (run , verbose = None ):
703856 """Convert one run to raw."""
@@ -752,6 +905,51 @@ def data_path(
752905 )
753906
754907
908+ class BNCI2003_004 (MNEBNCI ):
909+ """
910+ BNCI2003_IVa Motor Imagery dataset.
911+
912+ Dataset IVa from BCI Competition III [1]_.
913+
914+ **Dataset Description**
915+
916+ This data set was recorded from five healthy subjects. Subjects sat in
917+ a comfortable chair with arms resting on armrests. This data set
918+ contains only data from the 4 initial sessions without feedback.
919+ Visual cues indicated for 3.5 s which of the following 3 motor
920+ imageries the subject should perform: (L) left hand, (R) right hand,
921+ (F) right foot. The presentation of target cues were intermitted by
922+ periods of random length, 1.75 to 2.25 s, in which the subject could
923+ relax.
924+
925+ There were two types of visual stimulation: (1) where targets were
926+ indicated by letters appearing behind a fixation cross (which might
927+ nevertheless induce little target-correlated eye movements), and (2)
928+ where a randomly moving object indicated targets (inducing target-
929+ uncorrelated eye movements). From subjects al and aw 2 sessions of
930+ both types were recorded, while from the other subjects 3 sessions
931+ of type (2) and 1 session of type (1) were recorded.
932+
933+ References
934+ ----------
935+ .. [1] Guido Dornhege, Benjamin Blankertz, Gabriel Curio, and Klaus-Robert
936+ Müller. Boosting bit rates in non-invasive EEG single-trial
937+ classifications by feature combination and multi-class paradigms.
938+ IEEE Trans. Biomed. Eng., 51(6):993-1002, June 2004.
939+ """
940+
941+ def __init__ (self ):
942+ super ().__init__ (
943+ subjects = list (range (1 , 6 )),
944+ sessions_per_subject = 1 ,
945+ events = {"right_hand" : 0 , "feet" : 1 },
946+ code = "BNCI2003-004" ,
947+ interval = [0 , 3.5 ],
948+ paradigm = "imagery" ,
949+ doi = "10.1109/TBME.2004.827088" ,
950+ )
951+
952+
755953@depreciated_alias ("BNCI2014001" , "1.1" )
756954class BNCI2014_001 (MNEBNCI ):
757955 """BNCI 2014-001 Motor Imagery dataset.
0 commit comments