What’s new ===
“Enhancements” for new features
“Bugs” for bug fixes
“API changes” for backward-incompatible changes
Version 1.8.0 (2026-10-06)#
Enhancements#
moabb.datasets.PerezBlanco2026,moabb.datasets.Wang2026andmoabb.datasets.Han2026read from NEMAR: PerezBlanco2026 and Wang2026 take the subject’s original files from the published depositsnm000304andnm000335(nemar_seed_cache; Wang2026 declares the deposit’ssub-<id>subject labels), and Han2026 reads NEMAR’s raw import of OpenNeuro ds007327 (on007327) before OpenNeuro. The private deposits of Brodu2012 (nm000366), Lenaig2026 (nm000333), MartinezPeon2025 (nm000332), MIBMPI2024 (nm000331) and MIND2026 (nm000337) get theirnemar_idonce published (#1318 by Bruno Aristimunha).moabb.datasets.Tavakolan2017,moabb.datasets.Liu2025,moabb.datasets.Zhou2020andmoabb.datasets.Wu2020take the extracted per-subject tree from the NEMAR store (fill_tree_from_store) before downloading the per-subject ZIP, so a subject already mirrored in a deposit loads with no upstream request (#1313 by Bruno Aristimunha).moabb.datasets.Chang2025passesfname="MNE-chang2025-data/files/<id>"todata_dlso the NEMAR store lookup matches the deposit’s extraMNE-chang2025-data/prefix instead of downloading the 3.6 GB figshare ZIP (#1314 by Bruno Aristimunha).moabb.datasets.BI2013aplaces the extracted runs the NEMAR deposit keeps (subject_NN/Session*/+meta.yml) viafill_tree_from_storebefore asking for the per-session ZIPs, so a mirrored subject loads with no upstream request (#1315 by Bruno Aristimunha).moabb.datasets.Mainsah2025subclasses build their EDF file list from the active NEMAR store’ssourcedata_provenance.json(verifying each file against its recorded SHA-256, the same hashSHA256SUMS.txtlists) when the store holds them, so noSHA256SUMS.txtrequest to PhysioNet is made on a store-backed load (#1316 by Bruno Aristimunha).The four Castillos2023 classes take the shared
4Class-CVEP/tree from the NEMAR store (fill_tree_from_store) before downloading the shared 0.6 GB ZIP, which is now fetched at most once into the shared cache folder instead of once per class (#1317 by Bruno Aristimunha).Rename
moabb.datasets.Lenaig2026tomoabb.datasets.Gueho2026(the class used the first author’s given name; Crossref 10.1109/BCI69045.2026.11435072 lists Guého, Lénaïg as first author).Lenaig2026stays as a deprecated alias until 1.9,codeis unchanged so caches/results/NEMAR paths stay valid, and the renamed pair no longer triggers theis_abbrevwarning. Also fix dataset metadata/docstrings found by the W42 class-name audit: PerezBlanco2026 reference [1] title/authors, BNCI2025_002 first author (Kobler), Daly2020 year (2020), Hinss2021 data URL (Zenodo 5055046, the record the loader downloads), Han2026 record title, ErpCore2021_* METADATA (doi/year 2021), and the Ofner2017 ‘ataset’ typo (#1318 by Bruno Aristimunha).moabb.datasets.Chen2017SingleFlicker,moabb.datasets.EPFLP300,moabb.datasets.Nguyen2017_V,moabb.datasets.Vagaja2023,moabb.datasets.Jeong2020,moabb.datasets.Stieger2021,moabb.datasets.Yi2025,moabb.datasets.Yang2025,moabb.datasets.Schrag2026Pediatricandmoabb.datasets.Brandl2020seed their loader cache from their NEMAR deposits (nemar_seed_cache), so a subject mirrored in a deposit loads with no upstream request; EPFLP300 and Schrag2026Pediatric check the seeded tree before downloading their per-subject ZIP, Stieger2021 returns locally seeded session files without listing Figshare, and GuttmannFlury2025 also accepts the double-nestedS05/S05/...tree its deposit holds for subjects 5 and 9 (by Bruno Aristimunha).Add
n_jobs_datato all evaluations (default1, unchanged behavior): it is forwarded asn_jobstoget_data(), so subjects are loaded in parallel.moabb.datasets.base.BaseDataset.get_data()already supportedn_jobsbut evaluations never passed it, so loading was always sequential; this matters when reads are latency-bound, e.g. raw EDF files on a network filesystem (#1250 by Bruno Aristimunha).Install a
moabbconsole script ([project.scripts] moabb = "moabb.run:main"), so benchmarks run withmoabb --pipelines=./pipelines/ --evaluations WithinSessioninstead ofpython -m moabb.run ...; the command-line logic ofmoabb.runmoved into a reusablemain(argv=None)(#175 by Bruno Aristimunha).Test MOABB on free-threaded CPython 3.14 (
3.14t) with an allowed-to-fail CI job that also checks importing MOABB keeps the GIL disabled, and requireh5py>=3.16.0on Python 3.14+, the first h5py release with free-threaded (cp314t) wheels (#829 by Bruno Aristimunha).nemar_subject_templateonmoabb.datasets.base.BaseDatasetnow also accepts an explicit{moabb_subject: bids_label}mapping, so NEMAR deposits whose BIDS subject labels no format template can produce (sub-zdvm,sub-A01, non-contiguous ids) are declared as data instead of by overriding_nemar_subject; string templates keep working unchanged.moabb.datasets.Lioi2020_XP2drops its_nemar_subjectoverride for the mapping, with the same labels (#1098 by Bruno Aristimunha).Evaluations now log datasets they drop from the paradigm’s default list (
datasets=None) at INFO instead of WARNING, so a default benchmark run no longer floods the console with expected incompatibilities; datasets passed explicitly still warn when removed (#862 by Bruno Aristimunha).Evaluations now save results as soon as they are computed instead of once per dataset: in
process(), each(pipeline, subject)is written to the HDF5 file as soon as all of its folds have finished, so a crash later in a dataset no longer loses the subjects already evaluated. The file layout, the saved values and theoverwrite/ resume behaviour are unchanged: a rerun skips the saved subjects and computes only the missing ones. Writes still happen only in the main process, also withn_jobs > 1(#419 by Bruno Aristimunha).Fetch PhysioNet
/files/downloads (moabb.datasets.PhysionetMI,Mainsah2025) from the byte-identical AWS Open Data mirrorphysionet-open.s3.amazonaws.comfirst, falling back tophysionet.orgon any mirror error;physionet.orgoften serveseegmmidbat 10-40 kB/s and times out mid-file. SetMOABB_S3_MIRROR=0to disable (#1245 by Bruno Aristimunha).Add
moabb.datasets.Wang2026Handwriting, a Chinese handwriting-imagery dataset from Wang et al. (2026, Scientific Data, CC-BY 4.0; figshare, BIDS): 21 healthy adults in two handwriting-imagery tasks – CCS-HI (five Chinese character strokes) and SV-HI (six Pinyin vowels) – each recorded in two sessions, 40 trials of 4 s per class, 32-channel 10-10 EEG at 1 kHz. The two BIDS tasks share a session and carry no run entity, so the loader keys them by task; the release’strial_typelabels are normalized to the class names and the two sessions with fewer valid trials (sub-06 ses-02 SV-HI, sub-13 ses-02 CCS-HI) are kept as released (#1244 by LiQing).Add standalone
moabb.paradigms.CognitiveTaskfor non-motor endogenous tasks (handwriting/stroke imagery, mental arithmetic, mental rotation, word generation, music imagery). It subclassesBaseParadigmdirectly — not motor imagery — with MotorImagery-matching 8-32 Hz defaults, N-class event logic, and validity on bothparadigm="cognitive"and the existingparadigm="imagery"tag (follows the Cued Cognitive Tasks branch of #1056).moabb.datasets.Shin2017B(mental arithmetic vs rest),moabb.datasets.BNCI2024_001(10-letter handwritten character task) andmoabb.datasets.Wang2026Handwriting(Chinese stroke/vowel handwriting imagery) flip toparadigm="cognitive"with a newsummary_cognitive.csvtable, updated docstrings/METADATA, and runtime/docs/test allowlists (neural-signature handler, bubble-plot colormap, metadata catalog checks).moabb.datasets.Dreyer2023(and itsA/B/Csubclasses) now load their EEG from NEMAR’snm000250sourcedata/store when the download provider allows it, falling back to the OSF host exactly like other NEMAR-mirrored datasets; this stops MOABB’s docs/example builds from failing on OSF’sfiles.de-1.osf.io429 rate limiting.get_subject_info()reads the demographicperformance.csvfrom the store once it is published there, else falls back to OSF (#1237 by Bruno Aristimunha).Add
moabb.datasets.Peters2026, the BCI-FIT longitudinal P300 calibration dataset (Peters et al., OpenNeurods007720, CC0; NEMAR mirroron007720): five participants with ALS, 40 visits (6-10 per participant), 88 Matrix and RSVP calibration runs recorded with BciPy 2.0 and dry Wearable Sensing caps at 300 Hz, 66,150 flashes (4,109Target/ 62,041NonTarget). Sessions are visits; runs are named<k>matrix/<k>rsvpso analyses can split by layout. The closed-loop CopyPhrase runs are not loaded. Two participants switched between the DSI-Flex (7 channels) and the DSI-24 (19 channels) across visits and the caps share only Cz, P3, Pz and P4, so the default (codePeters2026) returns all visits with those four channels, whilecap="DSI-24"(Peters-dsi24-2026; 32 visits, 19 channels) andcap="DSI-Flex"(Peters-dsiflex-2026; 8 visits, 7 channels) return one cap’s visits with its full montage. The DSI-Flex channel the README calls FCz is kept as Cz, as in the files and BciPy’s device definition (FCz is that cap’s ground). The RSVP P300 peaks later than the Matrix one (median 92 ms) while the flash-locked visual response does not, so it is a genuine latency difference and event times are used as released. Dataset proposed by LiQing in #1243 (#1274 by Bruno Aristimunha).Add
moabb.datasets.Yagan2023, a P300 row-column speller dataset (Mendeley Data, CC-BY 4.0; 18 healthy subjects, 160-character copy-spelling in 10 blocks, 32-channel BrainVision EEG at 1 kHz, ~560k intensification events with an exact 2/13 target ratio per block). S14 target markers are stamped 2-3 ms after their flash in the released marker files, so each S1-S13 flash is relabelledTarget/NonTargetby pairing it with any S14 within 10 ms (#1220 by LiQing).Document a download-free, paradigm-only metadata preflight using the existing
MotorImagery.is_validAPI, including explicitn_classessemantics and unknown evaluation compatibility; add a no-I/O regression test for the recipe (by Bruno Aristimunha).Ship MOABB’s reference benchmark pipeline configs as package data and add
moabb.pipelines.get_benchmark_pipelines(), so published baseline pipelines are available from normal wheel/sdist installs instead of only from a source checkout. Pipeline parsing now also accepts a single.pyconfig and rejects valid-but-empty config directories instead of silently returning no pipelines (#1149 by lindicaphxag-tech).Accept a metadata-aware splitter class (one that declares
metadata_columnsand implementssplit(self, y, metadata)) ascv_classinCrossSubjectEvaluation,CrossSubjectSplitterandCrossSessionSplitter. It is built withcv_kwargsand called directly instead of being wrapped as the inner groups-CV, so it can use any metadata column and leave rows out of both train and test (e.g. test one target subject on one session and drop its other sessions). Ordinary scikit-learn CVs keep the inner-CV path (#1104 by Bruno Aristimunha).Allow
CrossSubjectEvaluationto accept an optional top-levelsplitterinstance, enabling transfer-learning protocols to reuse MOABB’s existing caching, parallel execution, and result handling while preserving the default protocol (#1088 by lindicaphxag-tech).Add Leelakittisin2025 sit-stand transition imagery, PerezBlanco2026 wrist motor-execution, and Vagaja2023 VR motor-imagery datasets (:pr:`1199`) (by Bruno Aristimunha).
Add MartinezPeon2025, MILimbEEG dataset loaders with synthetic regression coverage ({gh}`1198` by Bruno Aristimunha).
Add Pan2023, Pan2025, PoloHortiguela2025 dataset loaders with synthetic regression coverage ({gh}`1197` by Bruno Aristimunha).
Add MIMED2024 and WRCC2023 MI-A/MI-B/MI-C motor-imagery datasets with isolated recording windows (#1196, by Bruno Aristimunha).
Add
moabb.datasets.MIND2026,moabb.datasets.MOVING2024,moabb.datasets.NeBULA2025, andmoabb.datasets.Thapa2025large-recording motor-imagery and motor-execution datasets. Preserve the MIND acquisition-restart guard and document execution-only tasks (#1195 by Bruno Aristimunha).Add Batista2022, Farabbi2020, Han2026, Kodera2023 and Kueper2024 dataset loaders (#1194).
Add Brodu2012, IMUMIA2026, Leeuwis2021, MartinezPeon2024 and PardoGarcia2026 motor-imagery dataset loaders by Bruno Aristimunha in #1193.
Add the continuous-recording
datasets.Shin2022anddatasets.NeuroTUMBCI2025motor-imagery datasets (#1192, by Bruno Aristimunha).Add the offline calibration subsets of Perdikis2018 and Alawieh2025 (#1191) (by Bruno Aristimunha).
Add Jia2019, MIBMPI2024, Ortiz2023 and Wang2025 motor-imagery loaders (#1190 by Bruno Aristimunha).
Add three OpenNeuro motor-imagery datasets:
moabb.datasets.Peterson2020,moabb.datasets.Daly2020, andmoabb.datasets.Damm2026(:pr:`1189`, by Bruno Aristimunha).Add
moabb.datasets.NETBCI2026(NETBCI, Recherche Data Gouv doi:10.57745/RBJRC7): 19 subjects, 74-channel EEG, right-hand motor imagery vs rest over 4 longitudinal sessions x 6 online feedback runs; per-subject EEG is read out of the 49 GB archive by HTTP range requests (#1188 by Bruno Aristimunha).Add four OpenNeuro motor-imagery datasets:
moabb.datasets.Lee2022(ds004022, 7 orthopedic-impairment patients, 4 upper-limb MI tasks),moabb.datasets.Lioi2020_XP1(ds002336) andmoabb.datasets.Lioi2020_XP2(ds002338), the two EEG-fMRI right-hand MI / neurofeedback experiments of Lioi et al., andmoabb.datasets.Iwama2023(ds004444, 30 subjects, 129-channel HD-EEG, up to 16 sessions) (#1186 by Bruno Aristimunha).Audit every dataset’s
METADATAand docstring against its primary paper and data-repository record:countryis now an ISO 3166-1 alpha-2 code everywhere, loader declarations (sessions_per_subject,subjects,runs_per_session) and thesummary_*.csvtables match what each loader actually returns, and per-dataset fields (reference/ground, hardware, filters, licence, demographics, trial counts, DOIs) are corrected with quoted evidence;Cattan2019_VRdeclares its two sessions,Nakanishi2015exposes subject 10,PhysionetMI/MAMEM3/BI2015bmetadata now come from their own records (#1203 by Bruno Aristimunha).Add
moabb.analysis.plotting.plot_critical_difference()for comparing pipelines across complete multi-dataset benchmarks with Friedman ranks and Nemenyi critical-difference groups (#1127 by lindicaphxag-tech).Spell MNE’s renamed template montages everywhere: MNE 1.13 renamed
standard_1005/standard_1020(and the otherstandard_*templates) tocolin27_*(identical electrode files), warns on the old names and MNE 1.14 removes them, so everymake_standard_montage/set_montagecall in MOABB now spellscolin27_*.METADATAmontage labels are descriptive and unchanged (#1200 by Bruno Aristimunha).Add
moabb.datasets.Ma2022, the SHU cross-session motor-imagery dataset: 25 subjects, five sessions, 32 EEG channels at 250 Hz and 11,988 retained 4 s trials. Read the authors’ EDF release and BIDS events from NEMARnm000288(publication pending), preserving bad-channel flags. These are authors-preprocessed, concatenated imagery windows, not continuous amplifier recordings. TheMa-edf2022code prevents reuse of legacy MATLAB caches; there is no MATLAB fallback (#1178 by LiQing and Bruno Aristimunha)Document how to add a BIDS dataset: the
BaseBIDSDatasetdocstring now namesmoabb.datasets.Zhou2016as the template subclass and lists the steps, and the dataset-submission section of the data summary links to it (#726 by Bruno Aristimunha).Finish moving the BIDS conversion example to the public dataset API: it locates the original files with
data_path()instead of readingMNE_DATAand hard-coding theMNE-alexeeg-datafolder, documents theformatandoverwriteoptions ofconvert_to_bids(), and points toLocalBIDSDatasetfor loading the BIDS copy back (#1082 by Bruno Aristimunha).Make GitHub’s “Cite this repository” export the MOABB software citation as BibTeX
@miscinstead of@software, which many bibliography styles drop together with the DOI:CITATION.cffgains apreferred-citationof typegenericthat reuses the same authors, title, version and DOI, matching the@miscentries already in the README and Citing MOABB and related publications; the BibTeX block in Citing MOABB and related publications is re-indented with spaces (#1071 by Bruno Aristimunha).Explain the default 8-32 Hz band-pass of
moabb.paradigms.MotorImageryandmoabb.paradigms.LeftRightImageryin a Notes section: it keeps the mu and beta rhythms, limits muscle and ocular artifacts, and matches the MOABB benchmark, and the section shows how to change it (fmin/fmax), disable it (fmin=None, fmax=None) or use a filter bank. The defaults are unchanged (#533 by Bruno Aristimunha).
API changes#
moabb.evaluations.CrossSubjectEvaluationnow rejects custom splitter outputs with more than three items instead of silently treating all middle items as calibration data; custom splitters must yield(train, test)or(train, cal, test)(#1207 by lindicaphxag-tech).moabb.evaluations.CrossSessionEvaluationcustom cross-validation folds that hold out multiple sessions now emit one result row per held-out session instead of one aggregate row per fold. The default leave-one-session-out behavior is unchanged (#1210 by lindicaphxag-tech).Saved evaluation model paths now include the paradigm and optional suffix, so separate benchmark runs no longer overwrite artifacts that otherwise share the same evaluation/dataset/subject/session/pipeline keys (#1182 by lindicaphxag-tech). Existing legacy
Models_*/GridSearch_*trees remain untouched at their historical paths and are not auto-migrated because those paths do not encode the missing paradigm/suffix provenance; legacy artifacts therefore remain manually readable in place.n_trialsofmoabb.analysis.plotting.dataset_bubble_plot()(and of the dataset dictionaries ofmoabb.datasets.utils.plot_datasets_grid()andmoabb.datasets.utils.plot_datasets_cluster()) is now the number of trials of one subject over all its sessions, as in the summary tables; the bubble size no longer multiplies it byn_sessions, which only sets the transparency (#1310 by Bruno Aristimunha).
Requirements#
Require
mne>=1.13for thecolin27_*montage names andmne.io.read_raw_brainvision()’soverrides(#1200 by Bruno Aristimunha).Require
mne-bids>=0.20(no change to themnefloor) (by Bruno Aristimunha).
Bugs#
moabb.datasets.BNCI2015_004names its five mental tasks again (word association, mental subtraction, spatial navigation, right hand and feet imagery, coded 1-5 as in the .mat files) and epochs the imagery period [3, 10] s; since v1.5 the events were those of another dataset, the window covered the fixation cross, andmoabb.paradigms.MotorImageryrejected the dataset (#1323 by Bruno Aristimunha)moabb.datasets.Wang2016lists all 35 subjects (S35.mat was never loaded) (#1323 by Bruno Aristimunha)moabb.datasets.BNCI2014_002cites Steyrl et al. 2016 (DOI 10.1515/bmt-2014-0117) again, not a 2012 review (#1323 by Bruno Aristimunha)METADATA.experiment.eventsofmoabb.datasets.BNCI2014_002,moabb.datasets.BNCI2014_004,moabb.datasets.BNCI2014_008,moabb.datasets.BNCI2014_009andmoabb.datasets.BI2015bequal the datasets’event_id(#1323 by Bruno Aristimunha)Fix the trial and session counts of the dataset summary tables and of
moabb.analysis.plotting.dataset_bubble_plot().#Trials / classcounts every session and run of one subject, but the bubble size multiplied it again by#Sessions(BNCI2014_004 was drawn with 3,600 trials per subject instead of 720, EPFLP300 with 13,216 instead of 3,304), andscripts/generating_metainfo.pycomputedTotal_trialsas#Sessionsx#Runsx the first subject’s trials of all sessions x#Subj(62,208 instead of 5,184 for BNCI2014_001). The rows of BNCI2014_001, BNCI2014_002, BNCI2014_004, BNCI2015_001, Cho2017, Lee2019_MI, PhysionetMI, Schirrmeister2017, Shin2017A, Weibo2014, Zhou2016, MAMEM1, MAMEM2, MAMEM3, BI2013a, BI2014a, BI2015a, Cattan2019_VR, Huebner2017, Huebner2018 and Sosulski2019 now give the counts the loaders return, checked on every subject (Huebner2017 and Huebner2018 gave the trials of one run, 364 NT / 112 T, instead of 9,826 NT / 3,024 T and 10,920 NT / 3,360 T over three sessions). The rows of BCIComp2020UpperLimb, Brandl2020, Dreyer2023 (and A, B, C), Gao2026, GuttmannFlury2025_MI, GuttmannFlury2025_ME, GuttmannFlury2025_SSVEP, HefmiIch2025, Jeong2020, Ofner2017, Tavakolan2017, Wang2026Handwriting, Yang2025, Zhou2020 and Zuo2025 (a per-session or per-run#Trials / class; Yang2025 and Wang2026Handwriting also list the 2 and 11 classes their loaders return by default) and of BNCI2015_004, Kaneshiro2015, Liu2025 and Shin2017B (aTotal_trialsmultiplied by the sessions or runs, or missing the subjects) now follow the counts their documentation states. When subjects have different numbers of sessions,#Sessionsnow starts with that of a typical subject, the number the plots read: BI2013a1 (8 for subjects 1-7)instead of8 for subjects 1-7 else 1, Sosulski2019 60 instead of 80, and BNCI2015_001, Gao2026, GuttmannFlury2025_*, Kaya2018, Liu2025, Ortiz2023, Peters2026, RomaniBF2025ERP and Thapa2025. The column definitions of Data Summary state these conventions (#1310 by Bruno Aristimunha).moabb.datasets.Lee2021Mobile_ERPandLee2021Mobile_SSVEPnow always download the original recordings from OSF: the project (r7s9b) stores every BrainVision file twice under the same file name, as the BIDS copy/sub-XX/ses-YY/eeg/(73 channels at 100 Hz, EEG merged with the IMU channels) and as/sourcedata/sub-XX/ses-YY/eeg/(50 channels at 500 Hz). The listing kept only file names and the loader took whichever copy OSF listed first, so which recording a subject got depended on OSF’s listing order, and the.vhdr,.eegand.vmrkof one recording could come from different copies.moabb.datasets.download.create_metainfo_osf()now returns each file’s OSFpath, the loader takes the BrainVision files from/sourcedata/only and raisesFileNotFoundErrorwhen one of them is not listed there, andforce_update=Truenow replaces files already in the cache. Cached files keep their location and names.METADATAnow says 500 Hz and 48 EEG + 2 EOG channels, which is what the loader returns, instead of the 100 Hz / 73 channels of the BIDS copy (#1297 by Bruno Aristimunha).Fix
moabb.datasets.Kodera2023returning two sampling rates: the 14 subjects of the 2020-2021 recordings are at 500 Hz and the 15 of the 2021-2023 recordings at 1000 Hz (checked on every header of the archive), whileMETADATAsaid 500 Hz. Without a paradigmresample, a call mixing both setups built epochs of 2001 and 4001 samples and stopped innp.concatenate. By default all 29 subjects now return the nine shared channels at 500 Hz (the 1000 Hz recordings are resampled when loaded). The newcohortoption returns one setup with its full montage:cohort="16ch"(codeKodera-16ch2023) the 14 2020-2021 subjects with all 16 scalp channels at 500 Hz,cohort="9ch"(codeKodera-9ch2023) the 15 2021-2023 subjects at the native 1000 Hz; a subject of the other setup raises aValueErrorand downloads are shared. CachedKodera2023epochs and results of subjects 3-10, 16, 17, 22, 23 and 27-29 computed at 1000 Hz must be deleted and recomputed (#1276 by Bruno Aristimunha).Fix
moabb.datasets.PerezBlanco2026returning one run at another sampling rate:sub-09run-01is stored at about 1200 Hz (EDF header 1199.4 Hz) while the other 447 runs of the 45 subjects are at 512 Hz, so any paradigm call including subject 9 stopped innp.concatenate(“the array at index 0 has size 1281 and the array at index 2 has size 3000”). The loader now resamples any run that is not at 512 Hz to 512 Hz after locating its events. CachedPerezBlanco2026epochs and results of subject 9 must be deleted and recomputed (#XXXX by Bruno Aristimunha).Fix
moabb.datasets.PerezBlanco2026returning one run at another sampling rate:sub-09run-01is stored at about 1200 Hz (EDF header 1199.4 Hz) while the other 447 runs of the 45 subjects are at 512 Hz, so any paradigm call including subject 9 stopped innp.concatenate(“the array at index 0 has size 1281 and the array at index 2 has size 3000”). The loader now resamples any run that is not at 512 Hz to 512 Hz after locating its events. CachedPerezBlanco2026epochs and results of subject 9 must be deleted and recomputed (#1277 by Bruno Aristimunha).moabb.datasets.Zhang2017no longer guesses channel names for files it was not checked against. Every released file has 17 channels and an empty BCI2000ChannelNamesparameter (all 180.datfiles of the 12 subjects, checked) and keeps the EGI sensor orderE1-E16,E28. Before, a file with another channel count gotEEG001.. names and a file with header names got those labels, which would have given its subject a channel set of its own; such files now raise aValueError. The returned data are unchanged (#XXXX by Bruno Aristimunha).moabb.datasets.Zhang2017no longer guesses channel names for files it was not checked against. Every released file has 17 channels and an empty BCI2000ChannelNamesparameter (all 180.datfiles of the 12 subjects, checked) and keeps the EGI sensor orderE1-E16,E28. Before, a file with another channel count gotEEG001.. names and a file with header names got those labels, which would have given its subject a channel set of its own; such files now raise aValueError. The returned data are unchanged (#1278 by Bruno Aristimunha).Fix the Castillos2023 c-VEP loaders (
moabb.datasets.CastillosBurstVEP100,CastillosBurstVEP40,CastillosCVEP100,CastillosCVEP40) failing for every subject withValueError: The event_id dictionary has values that do not match any event codes ... Missing event codes: [0]: the trial labels were a view ofepochs.events, so making them 0-based also rewrote the epochs’ event codes, which MNE now checks when the epochs are read. The labels are now a copy, and all 12 subjects of the four datasets load again (#XXXX by Bruno Aristimunha).The Castillos2023 c-VEP datasets (
moabb.datasets.CastillosBurstVEP100,CastillosBurstVEP40,CastillosCVEP100,CastillosCVEP40) now return the same 28 EEG channels, in one order, for every subject. The released files use two 32-channel layouts: subjects 1, 5, 6, 7, 11 and 12 carry the LiveAmp-32 labels (F9, P9, P10, F10) and subjects 2, 3, 4, 8, 9 and 10 the actiCAP-32 labels (FT9, TP9, TP10, FT10), in a different channel order, so arrays mixing the two groups stacked different electrodes at the same index (e.g. index 9 was CP5 for subject 1 and TP9 for subject 2) andreturn_epochs=Truefailed with “epochs[1].info[‘ch_names’] must match”. The four layout-specific electrodes are dropped;METADATAand the summary table list 28 channels. Cached Castillos2023 epochs and saved results must be deleted and recomputed (#XXXX by Bruno Aristimunha).The Castillos2023 c-VEP datasets (
moabb.datasets.CastillosBurstVEP100,CastillosBurstVEP40,CastillosCVEP100,CastillosCVEP40) now return the same 28 EEG channels, in one order, for every subject. The released files use two 32-channel layouts: subjects 1, 5, 6, 7, 11 and 12 carry the LiveAmp-32 labels (F9, P9, P10, F10) and subjects 2, 3, 4, 8, 9 and 10 the actiCAP-32 labels (FT9, TP9, TP10, FT10), in a different channel order, so arrays mixing the two groups stacked different electrodes at the same index (e.g. index 9 was CP5 for subject 1 and TP9 for subject 2) andreturn_epochs=Truefailed with “epochs[1].info[‘ch_names’] must match”. The four layout-specific electrodes are dropped;METADATAand the summary table list 28 channels. Cached Castillos2023 epochs and saved results must be deleted and recomputed (#1282 by Bruno Aristimunha).Fix the Castillos2023 c-VEP loaders (
moabb.datasets.CastillosBurstVEP100,CastillosBurstVEP40,CastillosCVEP100,CastillosCVEP40) failing for every subject withValueError: The event_id dictionary has values that do not match any event codes ... Missing event codes: [0]: the trial labels were a view ofepochs.events, so making them 0-based also rewrote the epochs’ event codes, which MNE now checks when the epochs are read. The labels are now a copy, and all 12 subjects of the four datasets load again (#1279 by Bruno Aristimunha).Fix
moabb.datasets.PhysionetMIfailing in every paradigm call that mixes subjects 88, 92 or 100 with other subjects: these three subjects were recorded at 128 Hz (every imagery and execution run) and the other 106 at 160 Hz, soLeftRightImagery().get_data(PhysionetMI(), subjects=[1, 88])stopped innp.concatenate(“the array at index 0 has size 481 and the array at index 6 has size 385”) andreturn_epochs=Trueinconcatenate_epochs(“epochs[6].info[‘sfreq’] must match”); the docstring only mentioned subject 88. The loader now resamples any run that is not at 160 Hz to 160 Hz, so all 109 subjects load together. Cached epochs and saved results that include subjects 88, 92 or 100 must be deleted and recomputed (#XXXX by Bruno Aristimunha).Fix
moabb.datasets.PhysionetMIfailing in every paradigm call that mixes subjects 88, 92 or 100 with other subjects: these three subjects were recorded at 128 Hz (every imagery and execution run) and the other 106 at 160 Hz, soLeftRightImagery().get_data(PhysionetMI(), subjects=[1, 88])stopped innp.concatenate(“the array at index 0 has size 481 and the array at index 6 has size 385”) andreturn_epochs=Trueinconcatenate_epochs(“epochs[6].info[‘sfreq’] must match”); the docstring only mentioned subject 88. The loader now resamples any run that is not at 160 Hz to 160 Hz, so all 109 subjects load together. Cached epochs and saved results that include subjects 88, 92 or 100 must be deleted and recomputed (#1280 by Bruno Aristimunha).Fix the BigP3BCI loaders (
moabb.datasets.Mainsah2025_Atomoabb.datasets.Mainsah2025_S2) returning slightly different sampling rates from run to run (e.g. 256.0, 256.00008 and 256.00009 Hz within one subject): every EDF stores the run as one data record whose 8-character duration field is truncated, so the rate MNE derives from the header is off by less than 1 ppm and differs per file. Arrays were unaffected, butreturn_epochs=True(and anything concatenating epochs, e.g. deep-learning pipelines) stopped with “epochs[3].info[‘sfreq’] must match” even for a single subject. Runs whose header rate is explained by this truncation are now relabelled at exactly 256 Hz (no resampling; the samples are unchanged), and any other rate raises. CachedMainsah2025-*epochs computed before this fix carry the per-file rates and should be deleted and recomputed (#XXXX by Bruno Aristimunha).Fix the BigP3BCI loaders (
moabb.datasets.Mainsah2025_Atomoabb.datasets.Mainsah2025_S2) returning slightly different sampling rates from run to run (e.g. 256.0, 256.00008 and 256.00009 Hz within one subject): every EDF stores the run as one data record whose 8-character duration field is truncated, so the rate MNE derives from the header is off by less than 1 ppm and differs per file. Arrays were unaffected, butreturn_epochs=True(and anything concatenating epochs, e.g. deep-learning pipelines) stopped with “epochs[3].info[‘sfreq’] must match” even for a single subject. Runs whose header rate is explained by this truncation are now relabelled at exactly 256 Hz (no resampling; the samples are unchanged), and any other rate raises. CachedMainsah2025-*epochs computed before this fix carry the per-file rates and should be deleted and recomputed (#1281 by Bruno Aristimunha).moabb.datasets.Ofner2017returns the same channel names in the same order for every run, whichreturn_all_modalitiesneeds: subject 1’s execution runs store the EOG channels as eog-r, eog-m, eog-l (all other runs: eog-l, eog-m, eog-r), so arrays with EOG swapped eog-l and eog-r, and its imagination runs hold 13 placeholder channelsarmeodummy-0..``armeodummy-12`` (random values) instead of the Armeo arm-kinematics channels;MotorImagery().get_data(Ofner2017(return_all_modalities=True), subjects=[1], return_epochs=True)failed with “epochs[10].info[‘ch_names’] must match”. The EOG channels are now reordered by name and the placeholders renamed to the Armeo names and set to 0, like the (empty) Armeo channels of the other runs;METADATAlists the Armeo names. The default EEG-only output is unchanged. Cached Ofner2017 epochs and results computed withreturn_all_modalitiesmust be deleted and recomputed (#XXXX by Bruno Aristimunha).moabb.datasets.Ofner2017returns the same channel names in the same order for every run, whichreturn_all_modalitiesneeds: subject 1’s execution runs store the EOG channels as eog-r, eog-m, eog-l (all other runs: eog-l, eog-m, eog-r), so arrays with EOG swapped eog-l and eog-r, and its imagination runs hold 13 placeholder channelsarmeodummy-0..``armeodummy-12`` (random values) instead of the Armeo arm-kinematics channels;MotorImagery().get_data(Ofner2017(return_all_modalities=True), subjects=[1], return_epochs=True)failed with “epochs[10].info[‘ch_names’] must match”. The EOG channels are now reordered by name and the placeholders renamed to the Armeo names and set to 0, the value returned for runs without Armeo recording (NaN in 270 of the 300 released runs);METADATAlists the Armeo names. The default EEG-only output is unchanged. Cached Ofner2017 epochs and results computed withreturn_all_modalitiesmust be deleted and recomputed (#XXXX by Bruno Aristimunha).moabb.datasets.Ofner2017returns the same channel names in the same order for every run, whichreturn_all_modalitiesneeds: subject 1’s execution runs store the EOG channels as eog-r, eog-m, eog-l (all other runs: eog-l, eog-m, eog-r), so arrays with EOG swapped eog-l and eog-r, and its imagination runs hold 13 placeholder channelsarmeodummy-0..``armeodummy-12`` (random values) instead of the Armeo arm-kinematics channels;MotorImagery().get_data(Ofner2017(return_all_modalities=True), subjects=[1], return_epochs=True)failed with “epochs[10].info[‘ch_names’] must match”. The EOG channels are now reordered by name and the placeholders renamed to the Armeo names and set to 0, the value returned for runs without Armeo recording (NaN in 270 of the 300 released runs);METADATAlists the Armeo names. The default EEG-only output is unchanged. Cached Ofner2017 epochs and results computed withreturn_all_modalitiesmust be deleted and recomputed (#1283 by Bruno Aristimunha).Fix
moabb.datasets.Chang2025silently skipping subjects 10, 15, 19, 20, 24 and 25 on a fresh download (“Subject directory for S211 not found … Skipping”): the figshare ZIPs are split by original subject ID (S201-S230, without S207 and S218), but the loader chose the ZIP by MOABB subject number, so these six subjects fetched the neighbouring ZIP and returned no data unless the next subject had been loaded first. The ZIP is now chosen by original ID. Results computed with these subjects missing should be recomputed (#XXXX by Bruno Aristimunha).Fix
moabb.datasets.Chang2025silently skipping subjects 10, 15, 19, 20, 24 and 25 on a fresh download (“Subject directory for S211 not found … Skipping”): the figshare ZIPs are split by original subject ID (S201-S230, without S207 and S218), but the loader chose the ZIP by MOABB subject number, so these six subjects fetched the neighbouring ZIP and returned no data unless the next subject had been loaded first. The ZIP is now chosen by original ID. Results computed with these subjects missing should be recomputed (#1284 by Bruno Aristimunha).The NEMAR sourcedata prefetch of
get_data()now also fetches the files a deposit’s provenance attributes to no subject (montages, band and interval definitions, shared tables), which every subject’s load reads: before, a per-subject prefetch skipped them and the loader fetched them from the upstream host, e.g.Brandl2020’smnt.matfrom DepositOnce with the default"auto"provider, or failed under"nemar". A store that lacks them is no longer reported as complete (by Bruno Aristimunha).moabb.datasets.download.download_if_missing()now takes a missing file from the active NEMAR store, asdata_dl()already did, so a file a deposit keeps in itssourcedata/under the URL’s trailing path segments no longer comes from the upstream host. This servesMainsah2025’s PhysioNet indexSHA256SUMS.txtfromsourcedata/SHA256SUMS.txtof each study’s deposit once that file is added there, the last request those loaders still sent to PhysioNet with the default provider (#1294 by Bruno Aristimunha).Archive-based loaders no longer download the archive when NEMAR already served its contents: NEMAR deposits mirror the extracted members, not the
.zip, so the URL lookup inmoabb.datasets.download.data_dl()could not match andBI2012,BI2014a,BI2014b,BI2015a,BI2015b,BNCI2019_001,BNCI2025_001andHinss2021fetched the whole archive from the upstream host even with the default"auto"provider. They now ask the newmoabb.datasets.download.fill_from_store()for the members they read (located with the newdata_dl_destination()) and download only when the store lacks one of them; members are linked all-or-nothing, so a partial deposit never leaves a half-extracted tree (by Bruno Aristimunha).moabb.datasets.Huebner2017,moabb.datasets.Huebner2018,moabb.datasets.Liu2024andmoabb.datasets.Sosulski2019no longer download the ZenodosubjectXX.zip/ figshareedffile.zip/ FreiDok archive when NEMAR (nm000199,nm000195,nm000158,nm000266) or an earlier extraction already holds the files they read:data_pathnow looks for the extracted subject directory (Huebner, Sosulski2019) or EDF (Liu2024) first, throughmoabb.datasets.download.fill_from_store()and the newmoabb.datasets.download.fill_tree_from_store(), which places a whole extracted directory from the store and refuses one whose BrainVision/EEGLAB headers name files the store lacks. Liu2024 now extracts only the archive members not on disk yet, so a later extraction does not write through the links into the NEMAR store, and Sosulski2019 extracts the archive again when a subject’s inner ZIP is missing instead of wheneverextracted/is absent (#1293 by Bruno Aristimunha).Nieto2022andTrianaGuzman2024declared their NEMAR raw mirrors (on003626,on005342) but always downloaded from OpenNeuro’s S3 bucket; like the other OpenNeuro loaders they now read the mirror first under the"auto"and"nemar"providers and keep OpenNeuro as the fallback and for"upstream"(by Bruno Aristimunha).Chailloux2020,Nieto2022andTrianaGuzman2024declared their NEMAR raw mirrors (on003190,on003626,on005342) but always downloaded from OpenNeuro’s S3 bucket; like the other OpenNeuro loaders they now read the mirror first under the"auto"and"nemar"providers and keep OpenNeuro as the fallback and for"upstream"(by Bruno Aristimunha).moabb.datasets.HefmiIch2025andmoabb.datasets.AguileraRodriguez2025read what their NEMAR deposits (nm000347,nm000174) hold instead of asking figshare/Mendeley again. HefmiIch2025 fetched its figshare file list with a barerequests.getonapi.figshare.com; it now caches it asfigshare_filelist_28955456.json(like the MAMEM loaders), takes it from the deposit’ssourcedata/when present, else frommoabb.datasets.download.fs_get_file_list(), and downloads each epoch file under its local name so the store serves it. The URL lookup ofmoabb.datasets.download.data_dl()also accepts a stored file that only adds an extension to an extension-less URL name (exactly one candidate, two or more path segments): the AguileraRodriguez2025 deposit keeps Mendeley’s<uuid>/file_downloadedasfile_downloaded.edf/.xdf(#1295 by Bruno Aristimunha).moabb.datasets.Peterson2020loads again with the default"auto"download provider: the NEMAR mirror (on003810) ships the BIDSevents.tsvsidecars, soread_raw_bidsreplaced the EDF’sOVTK_GDF_Right/OVTK_GDF_Tongueannotations with the sidecar values7/9and every load stopped withValueError: Could not find any of the events you specified. Both codings now map tomotor_imagery/rest(by Bruno Aristimunha).Fix every
moabb.datasets.Thapa2025load failing with “The header file must be given to read the data, not a file with extension ‘.eeg’”:data_pathlisted the runs withmne_bids.find_matching_paths()without an extension filter, so it returned each run’s_eeg.eegand_eeg.vmrkbeside its_eeg.vhdrandread_raw_brainvisionwas handed the binary file. It now returns the.vhdrheaders only; this affected both the Figshare archive and the NEMARnm000324copy (#1292 by Bruno Aristimunha).With the default
"auto"download provider, 28 datasets whose loader caches files under names their download URLs do not carry no longer re-download from the upstream host what NEMAR already served: NEMAR deposits are snapshots of the loader’s own cache (extracted archives, files named after figshare/Dataverse/Mendeley ids), which the URL-tail lookup ofmoabb.datasets.download.data_dl()cannot match. With the new class attributenemar_seed_cache = True,get_data()links the subject’s prefetched store files into the loader cache at the location the deposit’s provenance records (upstream_cache_root), using the newmoabb.datasets.download.seed_loader_cache(); recordings whose headers name files the deposit lacks (BrainVision, EEGLAB, Curry) are not placed. Seeding is opt-in because a loader that skips its download once anything is cached would load less data from an incomplete deposit (seen for Lee2021Mobile and BI2013a); it is enabled for the loaders an audit of everynemar_idverified to load the same runs from NEMAR alone: Batista2022, Beetl2021_A/B, Farabbi2020, GuttmannFlury2025_ME/MI/P300, Han2024Fatigue, Jia2019, Kaya2018, Liu2020BETA, Liu2022EldBETA, Ma2020, MILimbEEG, Mainsah2025 (all studies; the EDF list still comes from PhysioNet’sSHA256SUMS.txt), Nguyen2017_S/SL, Ortiz2023, PoloHortiguela2025, Pressel2016, RomaniBF2025ERP, Rozado2015, Simoes2020, Speier2017, Wairagkar2018, Wang2021Combined, Zhang2017 and Zuo2025 (by Bruno Aristimunha).Enable
nemar_seed_cacheformoabb.datasets.NETBCI2026,moabb.datasets.NeBULA2025,moabb.datasets.Kodera2023,moabb.datasets.IMUMIA2026,moabb.datasets.MAMEM1,moabb.datasets.MAMEM2,moabb.datasets.MAMEM3,moabb.datasets.Kojima2024A,moabb.datasets.Kojima2024Bandmoabb.datasets.Thapa2025, so with the default"auto"provider they load from NEMAR alone instead of fetching the archive, figshare file list or Dataverse export again. Their deposits were completed for every subject (BrainVision.eeg/.vmrk, Curry.cdt.dpa/.ceo, the figshare file lists and the Dataverse export added tosourcedata/), checked against the loaders’ subject lists and, for archives, against the upstream archive listing.moabb.datasets.IMUMIA2026now picks a subject’s.cdtby the subject-position field of its name instead of by its rank in the task folder, which picked another participant’s file (or failed) when the cache holds only some subjects (#1296 by Bruno Aristimunha).moabb.datasets.Thielen2021andmoabb.datasets.Kaneshiro2015no longer contact their upstream host for files the NEMAR store holds: the URL lookup ofmoabb.datasets.download.data_dl()probes four trailing URL segments instead of three (the Thielen2021 depositnm000214keepsoffline/sub-NN/block_k/<file>), and Kaneshiro2015 skips its truncation check (aRangerequest to stacks.stanford.edu on every load) for a file the size of the store copy (nm000263) (#1302 by Bruno Aristimunha).Enable
nemar_seed_cacheformoabb.datasets.GuttmannFlury2025_SSVEP,moabb.datasets.Nguyen2017_L,moabb.datasets.Zhang2025andmoabb.datasets.Zheng2020, so with the default"auto"provider they load from NEMAR alone instead of downloading the Zenodo per-subject ZIP or the figshare files again. Their loaders check their own cache before downloading, and their deposits (nm000310,nm000252,nm000211,nm000205) hold every file they read for every subject (checked against the loaders’ subject and session lists). Zheng2020’s manifest records no subject, so its first load seeds every group (#1299 by Bruno Aristimunha).moabb.datasets.Leelakittisin2025,moabb.datasets.MOVING2024,moabb.datasets.Perdikis2018andmoabb.datasets.Shin2022no longer download their archive (Zenodov1_raw_SNN.zip,edf.rar, the pilot.tar.gz; the figshare ZIP) when NEMAR (nm000330,nm000293,nm000307,nm000315) or an earlier extraction already holds the subject:data_pathlooks for the extracted subject first and places it from the store withmoabb.datasets.download.fill_tree_from_store(). The newmoabb.datasets.download.store_glob()finds Shin2022’s date-named subject directory in the store (#1300 by Bruno Aristimunha).moabb.datasets.MIMED2024,moabb.datasets.Weibo2014,moabb.datasets.Shin2017Aandmoabb.datasets.Shin2017Btake the subject’s files from their NEMAR deposits (nm000296,nm000146,nm000267,nm000268) withmoabb.datasets.download.fill_from_store()before downloading the Mendeley/Dataverse/TU Berlin archive (Shin2017 only withaccept=True, as for the download). MIMED2024 now downloads whenever the subject’s files are absent instead of only when theMotor Imagerydirectory is, andmoabb.datasets.utils.download_and_extract_subject_zip()gainedskip_existingso that extraction never writes through a link into the store (#1301 by Bruno Aristimunha).moabb.datasets.Liu2024no longer loads subjects 5, 15 and 49 by default: their released files are derived from the recordings of subjects 4, 11 and 47 (EDF of sub-05 bit-identical to sub-04; raw MAT files equal to the partner’s recording minus uniform noise), whileparticipants.tsvlists them as different patients, so keeping them leaked one subject’s EEG across cross-subject folds. The dataset now exposes 47 subjects;include_derived_subjects=Truerestores all 50 released IDs with a warning. Scores computed on all 50 subjects are not comparable with the new default (#1248 by Bruno Aristimunha).Follow-ups from the review of #1257-#1270:
moabb.benchmark()lets the evaluation auto-select datasets when noinclude_datasets/exclude_datasetsis given, so its default run no longer warns about filtered datasets (#862), and “No datasets left” now lists the reasons; the weekly scheduled link check no longer accepts 5xx answers, so a host that stays down is reported; the PhysioNet S3 mirror is skipped for the rest of the process after its first failure instead of warning and timing out once per file (#1245); a metadata-awarecv_classnow rejects agroupsargument it would silently ignore, and the CrossSession limitation is documented (#1104); a Zhou2020 file that does not match the channel layout raises instead of being skipped (#1120); the Rozado2015 unit check accepts common spellings of microvolts (#1070);Lioi2020_XP2.nemar_subject_templateis read-only and a missing mapping key names the expected key type (#1098); theprocess()Notes state that saving finished subjects is best effort withn_jobs > 1(#419) (by Bruno Aristimunha).With
optuna=True, ROC-AUC scoring failed withAttributeError: ... has no attribute 'decision_function'for classifiers that only providepredict_proba(skorch/braindecode nets,GaussianNB, …): optuna-integration’sOptunaSearchCVdefinesdecision_functionunconditionally, so scikit-learn’s scorer picked it.OptunaSearchCVClassifiernow exposes it only when the underlying estimator has it, as scikit-learn’sBaseSearchCVdoes (#1251 by Bruno Aristimunha).Hyper-parameter search failed with multi-metric paradigm scorers: a list of metric names (
scorer=["accuracy", "balanced_accuracy"]) reachedGridSearchCVwithrefit=True(scikit-learn requires a metric name), and withoptuna=Trueevery trial failed withKeyError: 'test_score'becauseOptunaSearchCVtakes a single scorer. Grid search now refits on the primary (first) metric and Optuna optimises it; all metrics are still reported on the outer folds (#1252 by Bruno Aristimunha).LearningCurveSplitterwithpolicy='ratio'took an unstratified prefix of the shuffled training pool, so small subsets often missed classes: splits were skipped with a warning, or fitting crashed withy contains previously unseen labelswhen a test class was absent from training (multi-class MI, SSVEP). Ratio subsets are now prefixes of a class-interleaved ordering of the same pool: sizes, nesting across sizes and per-permutation randomness are unchanged, and every class is present as soon as the subset holds at least one sample per class (#1253 by Bruno Aristimunha).Saving a fold model (
save_model=True) for a hyper-parameter search (GridSearchCV/ Optuna) over a torch (skorch/braindecode) pipeline pickled the whole search object with the live network instead of using the torch-aware path used for unwrapped pipelines.moabb.evaluations.utils._save_model_cv()now saves the search’sbest_estimator_like an unwrapped pipeline (save_paramsfor the net) and writesbest_params_<fold>.json; searches over non-torch pipelines are saved as before (#1256 by Bruno Aristimunha).moabb.benchmark()matchedinclude_datasetsgiven as dataset objects by code but then evaluated the paradigm’s freshly constructed default instances, silently dropping the caller’s constructor arguments (e.g.Shin2017A(accept=True)still failed with the licence error, subject restrictions were ignored). The caller’s instances are now used (#1254 by Bruno Aristimunha).moabb.benchmark()chose the epochs branch (return_epochs=True, mne_labels=True) by pipeline name, so SSVEP template classifiers (SSVEP_TRCA,SSVEP_CCA, …) in a config named e.g.TRCAinstead ofTRCA-SSVEPstill received epochs but with label-encoded integers, which they read as stimulus frequencies: no error, chance-level scores (Wang2016 TRCA 0.04 instead of 0.81 on subject 1). Routing now also uses the estimator type (_pipeline_requires_epochs()) (#1255 by Bruno Aristimunha).moabb.pipelines.utils.parse_pipelines_from_directory()(andmoabb.benchmark()) ran every.pypipeline config as a module namedcustomthat was not registered insys.modules, so a function or class defined in the config could not be pickled:save_model=Truefailed withPicklingError: Can't pickle <function _zscore ...>: import of module 'custom' failedfor e.g. aFunctionTransformer(_zscore), as didpickle.dumpsof the parsed pipeline. Each config now runs as its own module, registered under a name derived from its path (moabb_pipeline_config_<file name>_<hash>), so configs sharing a file name in different directories do not collide, and parsing the same file first lets another Python session unpickle the saved models. Worker processes (n_jobs > 1) cannot import such a module: they still receive its objects by value, andsave_modelnow falls back to cloudpickle when pickle cannot store an object by reference, so models saved in workers (where the samePicklingErrorwas raised) are written too and load withpickle.load()(#1309 by Bruno Aristimunha).Models saved with
save_model=Truecould not be loaded: after each fit, the evaluations mark every step that has no__sklearn_is_fitted__(e.g.LinearDiscriminantAnalysis,SVC,StandardScaler, pyRiemann’sCSP,TangentSpaceandMDM;Pipeline,FunctionTransformerand pyRiemann’sCovariancesdefine it) with a method bound to a local function, which pickle stores asgetattr(step, '_sklearn_is_fitted_true'), sopickle.loadoffitted_model_<fold>.pklraisedAttributeError: ... has no attribute '_sklearn_is_fitted_true'. The marker is now a module-level function, and the saved files load (#1309 by Bruno Aristimunha).Fix
moabb.datasets.Hinss2021building its recording at a hard-coded 500 Hz from EEGLAB files that hold 2 s epochs at 250 Hz: every time span was halved and every frequency doubled, so each “2 s” trial covered two released epochs (4 s), and each event sat one sample after its epoch’s start, which dropped the last trial of every session in MOABB and braindecode. The sampling rate is now read from the files, events mark the first sample of each epoch, andintervalis[0, 1.996]so a trial is exactly one released epoch. Hinss2021 scores computed before this fix used time-compressed data: delete cached epochs and results and re-run (#663 by Bruno Aristimunha).Fix
moabb.datasets.Zhou2020S-subjects (1-12) loading asEEG1..``EEG41`` without a montage. The loader took the first 41 of the 45 data columns of the NPZsignalarray as EEG, so hEOG/vEOG and the M1/M2 mastoids were served as EEG and POz, PO4, Oz and F6 were dropped. Channel names now come from the authors’readme_MIdataset.docxandMI_dataset.pyon the original IEEE DataPort deposit (doi:10.21227/f1c7-7x89): columns 0-25 are the 26 shared fronto-central channels, 26-27 hEOG/vEOG, 28-29 M1/M2 and 30-44 F5, AF3, AF4, P5-P6, PO3, POz, PO4, Oz, F6, a placement confirmed against the recordings’ correlation structure. Both cohorts get thecolin27_1005montage; EOG (eog) and mastoids (misc) are kept only withreturn_all_modalities. Results and cached epochs computed before this fix used the wrong channels: delete them and re-run (#1120 by Bruno Aristimunha).Fix
moabb.datasets.Zhou2020crashing every paradigm and evaluation that loads S- and A-subjects together: since #1120 the S-subjects (1-12) returned 41 EEG channels and the A-subjects (13-20) 26, soMotorImagery().get_data(Zhou2020()),WithinSessionEvaluationandCrossSubjectEvaluationstopped innp.concatenate(“along dimension 1, the array at index 0 has size 41 and the array at index 2 has size 26”). By default all 20 subjects now return the 26 channels both cohorts share (plus hEOG/vEOG withreturn_all_modalities). The newcohortoption selects one group with its full montage:cohort="S"returns subjects 1-12 with all 41 EEG channels (and the M1/M2 mastoids withreturn_all_modalities),cohort="A"subjects 13-20; subject numbers stay 1-20 and asking a cohort for a subject of the other one raises aValueError. Each option has its own code (Zhou2020,Zhou-s2020,Zhou-a2020) andMETADATA, so caches and results never mix channel sets; downloads are shared. CachedZhou2020epochs of S-subjects computed with the 41-channel layout must be deleted and recomputed (#1275 by Bruno Aristimunha).Fix
moabb.datasets.Beetl2021_Aandmoabb.datasets.Beetl2021_Bannotating the final phase’s test run with the training runs’ four-class map.final_MI_label.txtholds the competition’s three-class labels, whose class 2 is “other” (right hand or feet for A, feet or rest for B; 100 of each subject’s 200 test trials), so those trials were served asright_hand(A) orfeet(B). The test run now has its own map and the merged class is a separate event,right_hand_or_feet/feet_or_rest, listed last inevent_id; passeventsto select the four training classes or the competition’s three. The leaderboard phase, whose test labels were never released, now serves its training run only instead of slices of the final-phase label file.intervalstops one sample before 4 s, so MNE’s inclusivetmaxno longer drops the last trial of every run (300 instead of 298 trials per dataset-A subject). Synthetic regression tests cover the maps, the paradigm selection, the leaderboard phase and the trial count (#1241 by Bhargav Kowshik).Warn that
moabb.datasets.Han2024Fatiguesession'1'(fatigue) trial labels are unreliable: the fatigue MAT blocks do not store trials in the documented target order — single-trial SSVEP scoring against the loader-assigned labels sits at the 1/16 chance level while training-session labels verify at 0.70-0.87 accuracy, and per-block one-to-one assignments show no fixed permutation, so the intended order must come from the authors’ acquisition logs. The loader now emits aUserWarningwhen the fatigue session is loaded, documents the caveat, and keeps both sessions loadable; the training-session event order is regression-tested (#1238 by LiQing).Fix
moabb.datasets.MIND2026download 404ing immediately on every fresh fetch: the loader hardcoded ScienceDB’sgetZipFile?...&version=V3endpoint, which ScienceDB has since removed (HEAD-> 404).version=V4is the current replacement and was confirmed live with an exact byte-count match to this loader’s own ~74 GB docstring estimate (HEAD-> 200,content-length: 74409645600). No other behavior changes; the synthetic regression test is unaffected since it never hits the network (by Bruno Aristimunha).Fix
moabb.datasets.Lenaig2026’sdata_path()raisingFileNotFoundError: Some data files are missing.on every subject: it hard-coded anEEG_24Chan_AudioStim/wrapper directory for the extracted RAR, but the current Zenodo v2 archive (record21156618) extractsEXP1//EXP2/directly at the root – confirmed by direct inspection on Voyager (find ... -iname '*EEG_24Chan*'only finds the.raritself).data_path()now locates each run’s file with a recursive glob that matches either layout, without changing which events/labels are read (#1225 by Bruno Aristimunha).Fix
moabb.datasets.Schrag2026Pediatriccrashingconvert_to_bids()withValueError: Raw object must have annotations to be saved in BIDS formaton subject 1’s personalized-stimulus game run: the loader’s own documented policy of dropping all trial labels when a run’sTrial Startedmarker count drifts more than 10%% from its movements-CSV row count (true for that run, 15%% drift) produced a zero-annotationRaw, whichbids_interface’s writer then rejected._get_single_subject_datanow skips a run that ends up with zero events instead of returning it, logging a warning that names the subject and run; the docstring documents this (#1225 by Bruno Aristimunha).Fix
moabb.datasets.Lenaig2026crashingconvert_to_bids()withAttributeError: 'int' object has no attribute 'items':METADATA.experiment.trials_per_classwas a bare int (10) instead of theDict[str, int]the schema declares, andbids_interface._build_readmeunconditionally calls_format_dict()on it. Now a per-class dict ({"Stimulus": 10, "Silence": 10}), matching the documented 10 repetitions per condition (#1225 by Bruno Aristimunha).Fix
moabb.datasets.Dataset.convert_to_bids()aborting the whole multi-subject convert when exactly one subject raisesFileNotFoundError(a loader’s own “nothing to write for this subject” signal, e.g.moabb.datasets.Schrag2026Pediatricsubject 16’s single game run being dropped entirely by the >10%% drift policy): every subject after the one that raised was previously silently skipped as well. The per-subject loop now catchesFileNotFoundError, logs a warning naming the subject, and continues converting the rest (#1225 by Bruno Aristimunha).Keep
moabb.evaluations.CrossSubjectEvaluationresult provenance subject-specific when a grouped cross-validation fold holds out multiple subjects at once. The estimator is still fitted once per fold, while scores, cache identities, and saved-model paths are emitted per held-out subject and session instead of assigning the whole fold to its first subject (by lindicaphxag-tech).Fix
moabb.datasets.Dataset.convert_to_bids()crashing on datasets whose MOABB run-label suffix is literally"calibration"or"crosstalk"(Wang2025, Leeuwis2021, Brandl2020, Romani_BF2025_ERP). After theacq-switch (#1217), those descriptions collided withmne-bids’ reservedacq-calibration/acq-crosstalkMEG fine-calibration/crosstalk magic strings (which requiretask=None) and raisedValueError: task must be None if the acquisition is "calibration" or "crosstalk".run_moabb_to_bidsnow rewrites the two reserved tokens to short aliases ("calib"/"xtalk") andrun_bids_to_moabbinverts the mapping, keeping the round trip exact for every run label currently emitted on develop and preserving the legacyrecording-read fallback (#1218 by Bruno Aristimunha).Fix
moabb.datasets.MILimbEEGconvert aborting on subjects S4 and S7 withValueError: Expected a complete four-second stored trial. A dataset-wide scan of all 3719 motor-imagery CSVs confirms every file has exactly 500 data rows; 124 files (62 per subject across S4 and S7) start with a Mendeley v2 header variant whose first field is literallyNaN(NaN,0,1,...,15) instead of empty.float("NaN")succeeds, so the previous first-line sniff mis-flagged them as header-less and pandas kept the extra row._read_trialnow also treats a first line with a NaN-or-empty leading cell and sequential column-index remainder as a header; a synthetic regression covers both the NaN variant and the empty variant and short trials remain a hard error (no silent zero-pad) (#1218 by Bruno Aristimunha).Fix
moabb.datasets.Dataset.convert_to_bids()producing files the official bids-validator (@bids/validatorv2) rejects._build_sidecar_enrichmentnow wraps string or flat-dictacq.filtersso every top-level value ofHardwareFiltersis an object (schema requires objects, or the exact string"n/a"), fixingJSON_SCHEMA_VALIDATION_ERROR.run_moabb_to_bidsnow stores the MOABB run-label description (e.g. thefeedback1in"1feedback1") in theacquisition(acq-) entity instead ofrecording(rec-), which is not valid for_eeg/_events/_channelsfiles and raisedALL_FILENAME_RULES_HAVE_ISSUES.run_bids_to_moabbstill accepts the legacyrec-entity so existing MOABB caches keep reading back to the same run label (#1217 by Bruno Aristimunha).Keep
moabb.evaluations.CrossSessionEvaluationresult provenance session-specific when a custom cross-validator holds out more than one recording session in the same fold. The estimator is still fitted once per fold, but each held-out session is scored and stored separately, matching the evaluation’s session-level result contract in both the flattened and legacy execution paths (by lindicaphxag-tech).Fix metadata-aware splitters collapsing distinct compound groups when column values contain the
-separator. Multi-column group identities now use a canonical collision-free encoding, preserving the intended cross-validation and leakage boundary (by lindicaphxag-tech).Make
moabb.analysis.chance_level.chance_by_chance()independent of result-row order when test-fold sizes vary: dataset-level adjusted thresholds now take the strictest exact-binomial cutoff across the fold sizes actually present (the cutoff is discrete and not strictly monotone in fold size), while inconsistent class counts are rejected (by lindicaphxag-tech).Fix
SSVEP_TRCAtrial centering: the inter-trial covariance step subtracted the mean across channels at each sample instead of each channel’s mean over time, and did it in place on the filterbank data it received, which also fedQand the class templates. Centering is now per channel over time, on a copy (#1183 by Arthur031221).Use
gmeanin TRCA and TRCSP for compatibility with pyRiemann 0.12 and 0.13, and pass the TRCSP mean metric by keyword (by Bruno Aristimunha).Fix the
-e/--evaluationsflag ofpython -m moabb.run, which usedtype=listand so split its value into single characters:-e WithinSessionreachedmoabb.benchmark()as['W', 'i', 't', ...]and raisedKeyError: 'W'. It now takes one or more evaluation names, space separated (by Iain)Fix the two install pages asking for optional extras MOABB does not have: the pip install page gave
pip install moabb[deepleaning,carbonemission,docs], which is missing therofdeeplearning, and pip only warns about an unrecognised extra, so following that page leftbraindecodeuninstalled. The from-sources page asked forexternal, removed in 1.2.0 (by Iain).Fix evaluations passing NumPy arrays instead of
mne.Epochstomoabb.pipelines.classification.SSVEP_itCCAandmoabb.pipelines.classification.SSVEP_eCCA, whosefitthen raisedValueError: X should be an MNE Epochs object.The check that switches an evaluation to epochs only listedSSVEP_CCA,SSVEP_TRCAandSSVEP_MsetCCA(by Arthur031221)Fix
moabb.analysis.meta_analysis.compute_pvals_wilcoxon()reporting the wrong tail when the sign of the mean paired difference disagrees with the signed-rank statistic: the one-tailed p-value is now taken directly fromscipy.stats.wilcoxon(..., alternative="greater")instead of halving the two-sided value and choosing the side from the mean (#1177 by Azra Bano)Fix equation formatting in
moabb.analysis.meta_analysis.compute_pvals_corrected_ttest()and the surrounding docstrings so Sphinx renders the Nadeau & Bengio variance correction and \(n_2/n_1\) ratio instead of raw\fracsource (#1204 by Quentin Barthelemy).Resample the 600 Hz recordings of subject
zdvm(bases 2, 3, 5 and 7) inmoabb.datasets.MartinezCagigal2023Paryto the declared 256 Hz on load, fixing theValueErrorraised when theCVEPparadigm concatenated the 601-sample epochs of those sessions with the 257-sample epochs of every other recording (#1201 by LiQing)Read and write the BIDS JSON sidecars and the electrodes TSV in
bids_interfaceas UTF-8, the encodingmne_bidswrites them in, completing #1059. On Windows the locale codepage was used, so addingPublicationYearto adataset_description.jsonwith non-ASCII author names wrote them back garbled, or raisedUnicodeDecodeError(#1231 by Rızgar Ozan)Document why
moabb.datasets.BNCI2015_003subjects 1 and 2 have a 1:35Target/NonTargetratio (150 / 5250 epochs) instead of the 1:5 of subjects 3-10 (300 / 1500):s1.matands2.matwere recorded with the single-character speller (36 flash codes, one target), the others with the row/column speller (12 flash codes, two targets). The loader returns every flash in the released files; the counts come from the data, not from event decoding (#274 by Bruno Aristimunha)Fix
moabb.datasets.Rozado2015returning a trigger channel asFp1and EEG 32 times too small. Channel 0 of the 57-channel BioSemi XDF stream isTrig1, but the loader took the first 32 channels by position, soFp1held the trigger code, every other label was shifted by one electrode andCzwas dropped; it also scaled the stream’s microvolt values as raw ADC counts (31.25 nV) instead of 1e-6. The scalp channels are now picked by their stream label and scaled from the declared microvolts; the median absolute 8-30 Hz epoch amplitude goes from 0.11 uV to 3.5 uV (median over the 30 subjects); the docstring also flags the two noisy subjects (10, 15) and the two 75-trial subjects (16, 18) (#1070 by Bruno Aristimunha)Fix
moabb.datasets.Rozado2015returning a trigger channel asFp1and EEG 32 times too small. Channel 0 of the 57-channel BioSemi XDF stream isTrig1, but the loader took the first 32 channels by position, soFp1held the trigger code, every other label was shifted by one electrode andCzwas dropped; it also scaled the stream’s microvolt values as raw ADC counts (31.25 nV) instead of 1e-6. The scalp channels are now picked by their stream label and scaled from the declared microvolts; the median absolute 8-30 Hz epoch amplitude goes from 0.11 uV to 3.5 uV (median over the 30 subjects); the docstring also flags the two noisy subjects (10, 15) and the two 75-trial subjects (16, 18). Delete cached epochs and results computed before this fix and re-run (#1070 by Bruno Aristimunha)Fix the log record
moabb.datasets.EPFLP300writes when it unzips a subject archive:logger.info("unzip", path_zip)passed the path without a%splaceholder, so formatting the record raisedTypeError: not all arguments converted during string formatting. Stock handlers only print that error, but any handler that formats records strictly, such as one that re-raises formatting errors, madeget_datafail (#1298 by Bruno Aristimunha).Document in
moabb.datasets.EPFLP300that subject 3’s sessions"0"and"1"store T7 and T8 as copies of the mastoid channels MA1 and MA2, so that after the mastoid reference T7 is the opposite of T8 and the 32 EEG channels have rank 31: covariance estimators without shrinkage (e.g.xdawn_estimator="scm"ofXdawnCovariances, as inpipelines/XdawnCov_MDM.yml) fail there with aLinAlgError(#1311 by Bruno Aristimunha).moabb.datasets.Zhou2016now reads its BIDS release from NEMAR (nm000115) under the default"auto"download provider instead of always downloading the Zenodosub-N.ziparchives: the deposit has nosourcedata/, so the base class asked NEMAR for it, failed, and fell back to Zenodo on every load. The deposit holds the same EDF,events.tsv,channels.tsvandelectrodes.tsvfiles as the archives (without theirdesc-<hash>entity); loading subjects 1 and 2 from each source gives identical raw data, annotations, channels, epochs, labels and metadata. A subject already fetched loads without a request;"upstream"keeps the Zenodo path, and under"auto"a NEMAR failure falls back to Zenodo with a warning (#1303 by Bruno Aristimunha).Fix epochs of one dataset coming out with different numbers of samples when its subjects were recorded at different sampling rates and the paradigm resamples. MOABB resamples the epochs, and MNE gives
round(n_times * new_sfreq / native_sfreq)samples, so a 0-3 s epoch has 601 samples at 200 Hz when it was recorded at 160 Hz (481 samples) but 602 when it was recorded at 128 Hz (385 samples), the last one at 3.005 s, aftertmax; any call that stacked such subjects (get_dataover several subjects,CrossSubjectEvaluation) stopped innp.concatenate. Resampled epochs now keep at most the number of samplesmne.Epochsgives for the window at the new rate (601 here), and a call whose recordings still differ by a sample (downsampling by more than a factor of two rounds the length down, e.g. 769 samples at 256 Hz give 384 at 128 Hz against 385 from 250 Hz) cuts every epoch to the shortest length; nothing is padded. Data at a single native rate are unchanged unless resampling placed samples aftertmax(e.g. 128 Hz resampled to 200 Hz), which are now dropped. The resample step is part of the cache key, so cached epochs and arrays of resampled data are recomputed once (#1308 by Bruno Aristimunha).moabb.datasets.ErpCore2021(all seven components) now reads its recordings from NEMAR (nm000132) under the default"auto"download provider instead of always downloading them from OSF: the deposit has nosourcedata/, so the base class asked NEMAR for it, failed, and fell back to OSF on every load.nm000132is the ERP CORE BIDS dataset itself, one dataset whose components are thetask-entity; each class now downloads its subject’s files of its task (ERN and LRP sharetask-flankers) plus the subject’selectrodes.tsvandcoordsystem.jsoninto one store shared by the seven classes,MNE-erpcore2021-data/nm000132. A recording already on disk, from NEMAR or from an earlier OSF download, loads without a request;"upstream"keeps the OSF path, and under"auto"a NEMAR failure falls back to OSF with a warning (#1304 by Bruno Aristimunha).
Code health#
Share loader code across the new dataset PRs instead of private copies:
download_and_extract_subject_zip()gainsfnameandredownload_corrupted(one warned re-download of a corrupted archive),rename_stimulus_codes()maps BrainVisionStimulus/S <n>markers to class labels,SetRawAnnotationskeepsEDGE boundarymarkers, and the OpenNeuro raw-mirror mixin (moabb.datasets._openneuro_mirror) lives here once (#1200 by Bruno Aristimunha).Modernise the BIDS layer on
mne-bids>=0.20and drop MOABB workarounds it made obsolete: usemne_bids.find_matching_paths()withignore_json=Trueand the publicmne_bids.BIDSPath.find_matching_sidecar()instead of a private helper, letmne_bids.write_raw_bids()serialise annotation extras intoevents.tsvand skip its README (readme=False) instead of patching both after writing, rely on mne-bids for the standardparticipants.jsoncolumn descriptions, remove an obsolete EDF-conversion warning filter, and locate Thapa2025 files withfind_matching_pathsinstead of probing every session/run pair (by Bruno Aristimunha).
Version 1.7.1 (Stable - PyPi)#
Enhancements#
None.
API changes#
None.
Requirements#
None.
Bugs#
Let the
"auto"download provider fall back whennemar-pyleaks anhttpx.TransportError, such as a proxy failure, so locally cached upstream data remains usable when NEMAR is unreachable (by Bruno Aristimunha).
Code health#
None.
Version 1.7 (Stable - PyPi)#
Enhancements#
Make
moabb.analysis.plotting.dataset_bubble_plot()layouts reproducible by default and add arandom_stateparameter for controlled alternative layouts (#1168 by Anna Sokolova).
API changes#
None.
Requirements#
None.
Bugs#
Let
moabb.datasets.base.BaseDataset.get_data()use a complete processing cache before prefetching NEMAR, so cached data remains available when NEMAR or the network is unavailable (by Bruno Aristimunha).
Code health#
None.
Version 1.6.1 (Stable - PyPi)#
Enhancements#
None.
API changes#
moabb.datasets.metadata.AcquisitionMetadata.n_channelsis derived fromchannel_typesinstead of stored beside it. The two could disagree, and for 34 of the 147 catalogued datasets they did – the field was being used with two meanings, some counting EEG electrodes only and others every recorded channel. It now consistently means the total, which is what each dataset’s ownsensorslist andtest_n_channels_matches_raw_data(which counts every non-stimchannel) already assumed:moabb.datasets.Dreyer2023A, for instance, reported 27 against its own 32 sensors and now reports 32. Passingn_channels=to the constructor is no longer accepted (by Bruno Aristimunha).
Requirements#
None.
Bugs#
Point the monthly download job at
moabb/tests/test_download.py; it randownload.py, a file that does not exist, so it collected nothing and has been failing every month while no@pytest.mark.downloadtest executed anywhere in CI (by Bruno Aristimunha).Stop importing moabb from restyling the caller’s matplotlib.
moabb.analysis.plottingapplies a seaborn theme to the globalrcParamswhen it is imported, andmoabb/analysis/__init__.pyimported it eagerly – soimport moabb.datasetsalone changedfont.family,axes.gridandaxes.spines.*for every figure the caller drew afterwards, values matplotlib reads at axes creation and a caller therefore cannot undo. The path was indirect:moabb.datasets.bids_interfaceimportsmoabb.analysis.resultsforget_digest, which runs that__init__. The plotting imports are now deferred to the functions that use them and to a module-level__getattr__for the three re-exported plotting helpers, so every public name still resolves and an explicitimport moabb.analysis.plottingstill applies the theme, leaving MOABB’s own figures unchanged (by Bruno Aristimunha).Type the seven non-EEG channels of
moabb.datasets.BNCI2025_001asmisc.read_raw_eeglabtypes anything it does not recognise aseeg, sox/y/vx/vy/validity/targetPosX/targetPoxY– the hand kinematics and the target position of the reaching task – were picked as EEG by every paradigm and fed to classifiers as features, leaking the labels they encode. The declaredn_channels(67) also disagreed with the dataset’s own 60-electrode montage; it is now 71 withchannel_types={"eeg": 60, "eog": 4, "misc": 7}. Accuracies on this dataset will fall, which is the point (by Bruno Aristimunha).Give
moabb.datasets.Rodrigues2017the montage #700 announced but never shipped, the same omission asmoabb.datasets.Cattan2019_PHMDbelow: both share the 16-electrode setup, both spelledFc5/Fc6– the only two namesstandard_1020cannot resolve – and neither loader calledset_montage. ItsMETADATAalso declaredstandard_1010, which is not a montage MNE can build (by Bruno Aristimunha).Give
moabb.datasets.Cattan2019_PHMDthe montage #700 announced but never shipped: that PR fixed only the unit scaling, leaving the loader with noset_montagecall at all. Its channel list also spelled the two frontal-central electrodesFc5/Fc6, the only two of its sixteen names thatstandard_1020cannot resolve; they are nowFC5/FC6(by Bruno Aristimunha).Correct
moabb.datasets.Cattan2019_PHMDintervalfrom[0, 1]to[0, 60]. Each marker starts a one-minute relaxation block – as the dataset’s ownblock_duration_s=60.0records – butSetRawAnnotationsderives annotation durations frominterval, so every block was annotated as lasting one second.interval[0]is unchanged, so onsets do not move (by Bruno Aristimunha).Prefetch the NEMAR sourcedata store per subject rather than per dataset. The guard returned as soon as the store held anything, and the deposit’s provenance manifest lands inside it, so a store holding only a manifest counted as complete. Presence is now settled per subject from the cached manifest, at no network cost:
nemar.download()walks index, version, metadata and manifest before it consultstrust_existing, so a call with nothing to do is still four round-trips. Note that deposits whose manifest predates thesubjectfield – which is every deposit today – are fetched as a whole tree, so for those the older whole-store rule was already right and is kept; the per-subject check matters once manifests record subjects, and today for a store left partial by an interrupted fetch (by Bruno Aristimunha).
Code health#
None.
Version 1.6.0 (Stable - PyPi)#
Enhancements#
Declare
nemar_idfor 15 datasets whose NEMAR deposits are now public, somoabb.set_download_provider()can serve them from NEMAR’ssourcedata/instead of falling back to the upstream host (by Bruno Aristimunha).Add
moabb.analysis.meta_analysis.compute_pvals_corrected_ttest(), the Nadeau & Bengio corrected resampled t-test, for comparing pipelines evaluated with overlapping cross-validation resamples (e.g. within-session k-fold): the variance of the per-fold score differences is inflated by the test/train ratio \(n_2/n_1\), so the test does not inherit the optimistic variance of the naive resampled t-test (#1075 by Azra Bano)Add cross-subject transfer learning to
moabb.evaluations.CrossSubjectEvaluationthrough an optional target-calibration slice.moabb.evaluations.splitters.CrossSubjectSplittergainscalibration_size(the fraction of each held-out subject/session pair set aside for adaptation, in[0, 1]) andcalibration_labeled; whencalibration_size > 0each fold becomes(train, calibration, test). The calibration trials never enter the training fold - they are handed to the pipeline steps that opt in viaset_fit_request(X_target_unlabeled, orX_target_labeledandy_target_labeledwhencalibration_labeled=True, plus the per-trialsubjectsarray), so train, calibration and test stay trial-disjoint. Named presets are available through the newmoabb.evaluations.CrossSubjectModeenum and thecs_modeargument, covering train-only, unlabeled target adaptation at 20 / 50 / 100 percent, and labeled target calibration at 20 / 50 percent;TRAIN_TRIALWISEadditionally scores one target trial at a time, so a method cannot exploit statistics of the whole test block. Two examples are added underexamples/how_to_benchmark/(#1093 by Bruno Aristimunha and Anton Andreev)Add
moabb.datasets.Wang2026, one motor-imagery dataset from the Wang et al. 2026 sensory-guided joint-learning study. The release contains 39 globally unique participants: 31 in the primary randomized experiment (joint learning, n=15; BCI2000 control, n=8; tactile control, n=8) plus an independently recruited EEGNet-control cohort (n=8). Agroupfilter preserves stable global IDs across the four cohort archives. The longitudinal protocol used online 1D and 2D cursor control with 62 EEG channels at 1000 Hz and four classes over at least four regular sessions plus a baseline. Bounded 8-MiB HTTP range read-ahead extracts one subject without downloading a complete 8.7–25.3 GB archive. Raw construction is explicitly disabled until the authors supply the physical units or gain/offset needed to convert released values to volts (#1126 by Paul-Adrien Graignic and Bruno Aristimunha)Add
moabb.datasets.Lenaig2026- SSAEP-BCI data of 48 participants in response to a set of four auditory stimuli: a pure tone (used as a reference), cicada song and cat’s purr, and brownian noise. EEG acquisition is performed using a 24-channel (international 10-20 system, passive electrodes, impedance maintained below 10 kΩ) at a sampling rate of 500 Hz. The stimuli are amplitude-modulated by a 40 Hz sinusoid and have a duration of 10 seconds. The experiment is conducted at two loudness levels (60 and 66 phons, diotic presentation), with 24 participants each. The measurement consists in one session of two 10-minute runs (separated by a 5-minute break), each including 50 trials (10 repetitions per condition). (#1121 by Henrique Lefundes)Add
moabb.datasets.Schrag2026Pediatric— open-access pediatric SSVEP-BCI dataset (47 children aged 5-18, g.tec g.GAMMAsys + g.USBamp at 256 Hz, 16 channels) covering both an online 4-target SSVEP game (6.25 / 10 / 11.11 / 14.28 Hz) and an opt-in 12-stimulus personalization recording (4 contrasts x 3 sizes at 10 Hz). XDF + Unity markers; trial labels are derived from the matchingMovements/CSV (live fbCCA classifier output). Single 1.2 GB zip on Zenodo (10.5281/zenodo.19440997) extracted per-subject on first use; the SSVEP game is exposed as two runs (standard and personal stimulus) of a single session (by Bruno Aristimunha and Emily Schrag)Add 7 new imagined speech dataset adapters:
moabb.datasets.AguileraRodriguez2025(15 subjects, 4 Spanish words, traditional vs gamified paradigm),moabb.datasets.Nguyen2017_V,moabb.datasets.Nguyen2017_S,moabb.datasets.Nguyen2017_L, andmoabb.datasets.Nguyen2017_SL(Nguyen et al. 2017 vowels / short words / long words / short-vs-long conditions),moabb.datasets.Nieto2022(10 subjects, 4 directional tasks across inner / pronounced / visualized speech modalities, 128-ch BioSemi), andmoabb.datasets.Pressel2016(15 subjects, 11-class Spanish vowels and directional commands) (by Bruno Aristimunha)Welcome imagined speech as a distinct category of imagery datasets with a dedicated documentation section (see Data Summary), a new
moabb/datasets/summary_imagined_speech.csvsummary table, and a groupedImagined Speech Datasetslisting in the API reference. The new datasets continue to use the existingparadigm="imagery"tag so all motor-imagery paradigm classes work unchanged (by Bruno Aristimunha)Add 2 new BCI Competition 2020 dataset adapters:
moabb.datasets.BCIComp2020UpperLimb(Track 4, 15 subjects, 3 grasping tasks on a single right arm, 3 recording days 7 days apart for session-to-session transfer evaluation) andmoabb.datasets.BCIComp2020WalkingERP(Track 5, 15 subjects, visual P300 oddball during walking at 1.6 m/s on a treadmill, simultaneous scalp-EEG + ear-EEG + EOG + IMU recording) (by Bruno Aristimunha)Remove
moabb.datasets.BCIComp2020IS(BCI Competition 2020 Track 3 Imagined Speech): the released.matfiles publish trials in a scrambled order without the original time information, so per-trial temporal context is lost and the dataset cannot be evaluated reliably — any reported accuracy on random splits is likely inflated by residual intra-block leakage. The authors could not be reached to recover the trial timing, so the loader is removed entirely (by Bruno Aristimunha)Re-host
moabb.datasets.Pressel2016(from Google Drive) andmoabb.datasets.Nguyen2017_V/_S/_L/_SL(from Dropbox) to Zenodo records10.5281/zenodo.19502780and10.5281/zenodo.19502794so automated download works withoutgdown, Dropbox rate limits, or the 4 GB ZIP64 prefix bug in the upstream Nguyen archive. Metadata now points at the Zenodo DOI + repository (by Bruno Aristimunha)Mark artifact trials in
moabb.datasets.Pressel2016withBAD_artifactannotations instead of silently dropping them at load time, so downstreamreject_by_annotationpipelines decide how to handle them. ~52 percent artifact rate on subject 1, matching the 10-52 percent range reported in the paper (by Bruno Aristimunha)Preserve
moabb.datasets.BNCI2014_001source artifact flags with optionalartifact_handlingannotation modes.SetRawAnnotationsnow keepsBAD_artifact/bnci_artifact(and anybad-prefixed) annotations when it re-derives event annotations, so per-trial artifact flags survive the fullget_datapipeline (this also makes themoabb.datasets.Pressel2016BAD_artifactmarkers effective).reject_by_annotationis now exposed on all epoching paradigms (motor imagery, P300, SSVEP, c-VEP, resting state), letting users choose whetherBAD_artifacttrials are dropped during epoch generation (by copilot-swe-agent and Bruno Aristimunha).Add unified interactive macro table for dataset summary page with 58 metadata columns, SearchPanes filtering, paradigm distribution bar, and CSV export (#1043 by Bruno Aristimunha).
Add rich HTML repr for preprocessing pipeline transformers: pipelines now render as interactive sklearn diagrams in Jupyter notebooks and sphinx-gallery docs, showing the three-stage flow (Raw, Epochs, Array) with readable step names, event lists, and key parameters instead of raw dict dumps (by Zach Munro and Bruno Aristimunha).
Expose
motor_imageryandmental_arithmetickeyword-only parameters onmoabb.datasets.Shin2017A(default: MI=True, MA=False) andmoabb.datasets.Shin2017B(default: MI=False, MA=True), allowing users to load both conditions simultaneously while preserving backward compatibility (by Bruno Aristimunha)Add resting state annotations and EMG channel support to
moabb.datasets.Lee2019resting state runs for BIDS export compatibility (by Bruno Aristimunha)Skip zip extraction in
moabb.datasets.GuttmannFlury2025when files are already extracted, with/scratchfallback for NFS filesystems on compute nodes (by Bruno Aristimunha)Re-enable auto-execution of the Riemannian Artifact Rejection tutorial (
examples/advanced_examples/plot_riemannian_artifact_rejection.py) now that pyRiemann 0.11 is on PyPI with per-potato metrics andmethod_combinationsupport onPotatoField(by Bruno Aristimunha)Use NEMAR as the default download source for datasets with an assigned
nemar_id, while preserving existing dataset-specific downloaders as a fallback (by Bruno Aristimunha).moabb.datasets.base.BaseDataset.download()now fetches NEMAR’ssourcedata/— the original pre-BIDS distribution, byte-identical to what the upstream host serves and stored under the same upstream filenames — instead of the deposit’s BIDS copy. The BIDS copy is a re-encoding whose events and session/run labels differ from what each dataset’s own loader produces, so substituting it would silently change results rather than only change where the bytes come from;sourcedata/changes the source without touching the science. Loading is unaffected:get_datastill runs each dataset’s own parser. Falls back to the dataset’s upstream downloader when a deposit publishes nosourcedata/(by Bruno Aristimunha).Serve the original, pre-BIDS distribution from NEMAR through the new
moabb.datasets.base.BaseDataset.sourcedata_path(). NEMAR deposits republish the files exactly as the authors distributed them undersourcedata/, keeping the upstream filenames, so NEMAR can now stand in for upstream hosts that are slow, rate-limited, behind a bot gate, or retired. Passingsubject=fetches a single subject, resolved through the deposit’ssourcedata_provenance.jsonmanifest (deposits enriched before that manifest recorded subjects fall back to the whole tree with a warning); without it the fullsourcedata/is downloaded, because that tree keeps the upstream layout rather than a BIDS one. Backed bymoabb.datasets.download.nemar_sourcedata_dl()(by Bruno Aristimunha).Add
moabb.set_download_provider()/moabb.get_download_provider()to pin where MOABB fetches data from:"auto"(default — NEMAR first, upstream fallback),"nemar"(NEMAR only; a failure is raised rather than silently falling back to a host the caller opted out of), or"upstream"(never use NEMAR). Also settable per run via theMOABB_DOWNLOAD_PROVIDERenvironment variable, which takes precedence over the stored config (by Bruno Aristimunha).Add
moabb.datasets.preprocessing.EuclideanAlignment, a trial-level Euclidean Alignment transformer (He & Wu 2020; Junqueira et al. 2024) that whitens each trial by the inverse square root of the Euclidean mean covariance to remove per-domain covariance shift before a (deep) model sees the data. Inductive and leakage-free by default (fitlearns the reference from training trials,transformre-applies it to unseen trials);fit_transformgives the transductive, per-recording form. Accepts anmne.BaseEpochsor an(n_trials, n_channels, n_times)ndarray, uses a shrinkage covariance estimator ("lwf") for robustness, and adds no new dependency (pyriemann >= 0.11is already required). Distinct frompyriemann.transfer.TLCenter, which recenters covariance matrices (#1108 by Bruno Aristimunha).Add an
n_jobsparameter tomoabb.paradigms.base.BaseParadigm.get_data()andmoabb.datasets.base.BaseDataset.get_data()to load and preprocess subjects in parallel withjoblib.Parallel. Per-subject processing (reading, filtering, resampling, epoching) is independent, so this gives a near-linear speedup on datasets with many subjects, with identical numerical results. moabb’s own patches to the shared BIDS cache files (participants.tsv/.json,dataset_description.json) now take the mne-bids cross-process file lock, so parallel caching stays consistent (#1124 by Bruno Aristimunha).Add
examples/advanced_examples/plot_geometry_aware_recentering.py, comparing a fitted training reference with a transductive, test-batch-updated tangent-space reference under within- and cross-session evaluation (by Meysam Rahimipour).Drive cross-validation folds with any stock scikit-learn cross-validator passed as
cv_class, controlled by agroupsargument — a metadata column name, a list of column names (compound key, e.g.["subject", "session"]), or a callablemetadata -> array— together with callablecv_kwargsresolved against the metadata (e.g.cv_class=PredefinedSplitwith atest_foldcallable to target a single fold).groupsis exposed onmoabb.evaluations.WithinSessionEvaluation,moabb.evaluations.WithinSubjectEvaluation,moabb.evaluations.CrossSessionEvaluationandmoabb.evaluations.CrossSubjectEvaluationand threaded to their splitters; each splitter keeps its default grouping ("subject"/"session"/ labels) whengroupsisNone.moabb.evaluations.splitters.CrossDatasetSplittergainsgroups(itsgroup_columnargument is now a deprecated alias) (#1104 by Bruno Aristimunha).Add
moabb.analysis.meta_analysis.compute_lowest_subject_scores(), which selects one shared lowest-performing cohort per dataset from a mandatory, explicit reference pipeline and scores every pipeline on those exact subjects. The helper requires identical subject coverage across pipelines and macro-averages the supplied session scores per subject before ranking; it does not recompute subject-level F1 scores from predictions (#1133 by Aditya Singh)
API changes#
None.
Requirements#
Use
pyriemann.clustering.PotatoFieldper-potato metrics and themethod_combinationparameter (pyRiemann PR #423) used by the Riemannian Artifact Rejection tutorial (by Bruno Aristimunha)Bump minimum supported Python to
3.11(required bymne-bids >= 0.18) (#1124 by Bruno Aristimunha)Bump minimum
mne-bidsto0.19for its cross-process file lock (mne_bids._fileio._open_lock), used to keep parallel BIDS caching (get_data(n_jobs>1)) consistent. This also lets the BIDS cache drop the module-level monkey-patch ofmne_bids.dig._write_dig_bids(mne-bids >= 0.19 writes the*_electrodes.jsonSpatialReferencesidecar itself) and derive theKeywordsfield viamake_dataset_description(keywords=...)instead of a post-write patch (#1124 by Bruno Aristimunha)
Bugs#
Restore the final
AttributeErrorof themoabb.pipelinesmodule-level__getattr__, dropped in #692. Unknown names evaluated toNoneinstead of raising, sofrom moabb.pipelines import *failed withTypeError: 'NoneType' object is not iterableand a mistyped class name in a pipeline YAML surfaced asTypeError: 'NoneType' object is not callable(#1159 by Iain)Fix
moabb.analysis.meta_analysisrejecting non-finite score inputs instead of lettingNaNor infinite values leak into low-level statistics.moabb.analysis.meta_analysis.compute_pvals_wilcoxon(),moabb.analysis.meta_analysis.compute_pvals_perm(), andmoabb.analysis.meta_analysis.compute_effect()now require finite score tables; the all-zero Wilcoxon case still reports a one-tailedp=0.5in both directions and keeps off-diagonal p-values strictly inside(0, 1)for Stouffer’s method;moabb.analysis.meta_analysis.compute_effect()reports0for identical zero-spread pairs and signed infinity for constant nonzero offsets; andmoabb.analysis.meta_analysis.compute_pvals_corrected_ttest()now rejects non-finite score differences and non-finiten_train/n_testinstead of collapsing a non-finite statistic top=0.5(#678 by Aditya Singh)Point
moabb.datasets.Schrag2026Pediatricat Zenodo version 3.0 and correct its licence. Versions 1.0 and 2 registeredCC-BY-ND-4.0while the preprint stated CC-BY-4.0; the authors resolved that in version 3.0, whose Zenodo record registersCC-BY-4.0– so theNoDerivativesterm MOABB reported no longer applies. Version 3.0 also publishes one archive per subject, so loading a subject now downloads ~20-40 MB instead of the single ~1.2 GB archive that held all 47. (by Bruno Aristimunha)Fix the
LogVariance+LDArow of the benchmark results page (The largest EEG-based Benchmark for Open Science) linking topipelines/LogVar_grid.yml, which is theLog Variance SVM gridpipeline and classifies withSVC. It now links topipelines/LogVar.yml, theLog Variance LDApipeline the row reports. TheLogVariance+SVMrow keepsLogVar_grid.ymland loses its#L7fragment, which highlighted the sharedLogVariancestep rather than theSVCone (by Bhargav Kowshik).Fix
moabb.datasets.BNCI2022_001epoching its instantaneous waypoint markers as 90-second trials: onlytrajectory_startis a trial event now (32 bounded, non-overlapping 90 s epochs per subject), and the loader annotates each trigger pulse once (rising edge) instead of once per held sample, which had inflated subject 1 to 33,114 annotations and a 364 GiB allocation. Waypoint and trajectory-end markers stay available as annotations on the loaded raws (#1143 by Bruno Aristimunha)Retag
moabb.datasets.Kaneshiro2015fromp300toimageryso its declared default paradigm accepts it: its six visual object categories carry no Target/NonTarget pair, so the P300 paradigm rejected every subject. The catalog row and documentation grouping move with it (#1143 by Bruno Aristimunha)Allow
moabb.paradigms.RestingStateToP300Adapterto be constructed with defaults:events=Nonenow means all of the dataset’s events, matching how every in-repo usage builds it (#1143 by Bruno Aristimunha)Fix
moabb.datasets.Lee2024silently dropping 345 upstream files.data_pathno longer guesses filenames and skips failures: it downloads a real inventory – the NEMAR deposit’s provenance manifest when the sourcedata store is in use (so the NEMAR path never contacts the upstream host), or the upstream git tree otherwise – through the sharedmoabb.datasets.download.data_dl(), which also serves the files from the NEMAR store. Subject 8’s unpadded upstream names (sub8_*) are normalized to the padded names the loader reads, mixtures of combined and per-block training files andparam.matare covered by the inventory, and any download failure now raises instead of leaving a silently incomplete directory (#1142 by Bruno Aristimunha)Read XDF recordings with a built-in minimal reader (
moabb.datasets._xdf) instead of the optionalpyxdfdependency, for all four XDF datasets:moabb.datasets.AguileraRodriguez2025,moabb.datasets.Chen2017SingleFlicker,moabb.datasets.Rozado2015andmoabb.datasets.Schrag2026Pediatric. The immediate defect: AguileraRodriguez2025’s recorder wrote a non-conforming XDF footer (floatsample_count), which makespyxdf1.17.5 raiseValueErrorfor every gamified session; the built-in reader ignores footers entirely. It returnspyxdf-shaped streams and applies the same per-segment linear dejittering to regular-rate streams, and appliespyxdf’s clock-offset synchronization (Huber-ADMM robust fit, ported, BSD-2-Clause) before dejittering, inpyxdf’s order. Validated againstpyxdfon real recordings of each dataset: bit-identical loaded data for all four. Themoabb[xdf]extra is kept as an empty no-op so existing install commands still work (by Bruno Aristimunha)Prefetch the NEMAR sourcedata store inside
moabb.datasets.base.BaseDataset.get_data(): the store introduced in #1146 was only filled by an explicitdownload()call, so a plainget_data()on a fresh machine still fetched from the upstream host even with the provider pinned to"nemar". The requested subjects’sourcedata/is now fetched before loading, with the provider policydownloadalready implements:"upstream"skips NEMAR,"nemar"treats a failure as fatal,"auto"warns per subject and leaves that subject to the dataset’s own downloader. Also fix the store fill in the deprecatedmoabb.datasets.download.data_path():pooch.retrievetreats its destination as a directory holding<md5(url)>-<basename>, and loaders such asmoabb.datasets.Rodrigues2017’sos.listdir()it, so a store hit written as a plain file at the destination raisedNotADirectoryError; the hit now lands inside the wrapper directory under pooch’s unique name (by Bruno Aristimunha)Wire the NEMAR sourcedata store into loading (#1147):
data_dland the deprecateddata_pathnow serve a requested file from the dataset’sNEMAR/<nemar_id>/sourcedata/store before consulting the URL-derived layout, probing the store by the trailing segments of the URL path since it keeps the upstream filenames.dataset.download()followed byget_data()therefore no longer re-contacts the upstream host – verified live againstSchirrmeister2017, whose host is currently unreachable. The URL-derived trees remain as lookups so nothing already downloaded is fetched again;force_updatestill refetches upstream and pinning the provider to"upstream"opts loading out of the store (#1146 by Bruno Aristimunha)Store
moabb.datasets.ErpCore2021as the single combined BIDS dataset it is, with components separated by thetask-entity in one sharedMNE-erpcore2021-dataroot, instead of seven standalone per-component BIDS datasets; likewise store Dreyer2023 in one sharedMNE-dreyer2023-dataroot, since the A/B/C classes only select subject ranges of one globally numbered dataset. Pre-existing downloads in the legacy separated layouts are still read without re-fetching (#1146 by Bruno Aristimunha)Honor the download-flag contract across every dataset:
CacheConfig.overwrite_*now erases the cache even whenuse=False(previously a silent no-op with the default config),moabb.datasets.CompoundDatasetforwardspath/force_update/update_path/verboseto the wrapped dataset,data_pathimplementations that ignoredpathorforce_update(14 datasets, including Kojima2024’s narrowed signature that madedownload()raiseTypeError) now honor them,force_updatealso re-extracts stale archives, and a regression test enforces the contract for every future dataset. On the NEMAR side,moabb.datasets.base.BaseDataset.download()falls back to the upstream host per failing subject instead of discarding the whole NEMAR batch,sourcedata_path()matches provenance subjects by both the raw MOABB id and the dataset’snemar_subject_templatelabel, transport failures are no longer misreported as “deposit publishes no sourcedata”, and manifest filenames containing glob metacharacters are escaped before selection (#1146 by Bruno Aristimunha)Fix
moabb.datasets.Brandl2020downloading HTML error pages instead of data. DepositOnce migrated to DSpace 7, whose web host now answers every path - including ones that were files - with HTTP 200 and a 1306-byte application shell, so all 16 subjects and the montage were cached as byte-identical HTML under their.matnames, with no error raised. Downloads now go to the DSpace REST host that serves the bytes, addressed by the per-file bitstream UUIDs that were already present in the module but unused, and each download is rejected and removed unless it really begins with the MATLAB file banner, so an error page can no longer be cached as data.moabb.datasets.download.data_dl()gains an optionalfnamefor APIs whose download URLs do not end in the filename (#1141 by Bruno Aristimunha)Fix dataset download paths that silently omitted files, returned nonexistent paths, or repeatedly extracted archives (#1135 by Bruno Aristimunha).
Use NEMAR’s OpenNeuro rehosts for
moabb.datasets.TrianaGuzman2024andmoabb.datasets.Chailloux2020(#1136 by Bruno Aristimunha).Fix
moabb.datasets.Schirrmeister2017re-downloading every recording.data_pathmoved each freshly fetched EDF out of the directory thatmoabb.datasets.download.data_dl()owns and intoMNE-schirrmeister2017-data/<train|test>/, so the next call found the download cache empty and fetched the whole file again. The refetched copy was then left behind because the destination already existed, leaving two copies of a multi-gigabyte recording on disk.data_pathnow returns the pathdata_dl()reports and only reads from the old location when a file is already there, so an existing local copy is still reused and never re-downloaded (#851 by Aditya Singh)Fix how the benchmark results page (The largest EEG-based Benchmark for Open Science) describes what its tables report. It stated that results are “mean accuracy and standard deviation across all folds for all sessions and subjects”, and both halves are inaccurate:
moabb.paradigms.MotorImageryselects the metric from the number of classes, so two-class scenarios are scored with ROC-AUC rather than accuracy, andmoabb.evaluations.WithinSessionEvaluationaverages the cross-validation folds within each session before returning a score, so the reported standard deviation is across (subject, session) pairs and not across individual folds (#1128 by Bhargav Kowshik)Fix datasets ignoring a change of download directory: datasets now inherit
MNE_DATAwithout persisting a redundant per-dataset mirror, andmoabb.utils.set_download_dir()removes legacyMNE_DATASETS_<SIGN>_PATHentries that still mirror the previous shared location while preserving explicit overrides.moabb.datasets.RomaniBF2025ERPnow uses the same path mechanism and honourspathandforce_update. Adds isolated regression coverage across every dataset (#1115 by Bruno Aristimunha).Add a
__repr__tomoabb.datasets.base.BaseDatasetso datasets display by their code (e.g.BNCI2014-001) when printed, instead of the verbose default<...object at 0x...>. This declutters the output ofprint(paradigm.datasets)in the tutorials and of the paradigm and evaluation compatibility warnings (by Danae)Add
age_medianfield tomoabb.datasets.metadata.schema.ParticipantMetadataand populateage_std/age_median/n_blocksmetadata formoabb.datasets.Rodrigues2017(Alphawaves), fixing aTypeErrorat import time (by Grace Xu)Fix
moabb.datasets.BNCI2014_001descriptiveMETADATA, which had many fields copied from BCI Competition IV Data set 1. Correctn_subjects(4 → 9),n_classes(2 → 4),class_labels/events/imagery_tasks(nowleft_hand/right_hand/feet/tongue),synchronicity(asynchronous → synchronous),sessions_per_subject(1 → 2), the per-session trial structure (288 trials, 6 runs of 48), the acquisitionreference/ground/filtersand thepreprocessingband (0.5–100 Hz with a 50 Hz notch, no 100 Hz downsampling), and the datasetdescriptionso they all match the dataset’s own constructor and docstring (#1094 by YG-paaleee)Fix
moabb.datasets.BNCI2014_001stimulus protocol timing in the generated documentation figure to show 2 s fixation, 1.25 s cue, and motor imagery through t=6 s (by Bruno Aristimunha)Fix stim-marker placement in
moabb.datasets.BCIComp2020WalkingERP(Track 5):build_raw_from_epochswas called withonset_sample=0, which placed the event at sample 0 of each trial — the start of the pre-stim baseline (t=-190 ms), not the actual stimulus onset. Withinterval=[-0.19, 0.8], the paradigm was therefore reading the leading zero buffer as “pre-stim data” and missing the last 180 ms of real post-stim data. Now the loader passesonset_sample=19so the marker lands on t=0 and the interval picks the real 100-sample epoch as published (by Bruno Aristimunha)Fix session key off-by-one in
moabb.datasets.Lee2019that caused silent data loss when filtering sessions, and improve session filtering inmoabb.datasets.base.BaseDatasetto match compound session keys (e.g.,"0train") by integer prefix (#1046 by Benedetto Leto and Bruno Aristimunha).Fix BIDS conversion failures across multiple datasets: crop BDF/EDF signals to exact data records in
bids_interface, add standard montage fiducials when missing, fixmoabb.datasets.BNCI2016_002KeyErrorin event mapping, handle lowercasetriggerattribute inmoabb.datasets.BNCI2022_001.matfiles, detect and re-download truncated files inmoabb.datasets.Kaneshiro2015, add stim-channel annotations inmoabb.datasets.Lee2024for BIDS compatibility, convert µV to V inmoabb.datasets.MartinezCagigal2023Checkerandmoabb.datasets.MartinezCagigal2023Paryto fix BDF physical range overflow, and handle alternatedatakey inmoabb.datasets.Zuo2025.matfiles (by Bruno Aristimunha)Fix
moabb.datasets.Chang2025BIDS conversion crash by gracefully skipping subjects with missing directories or.setfiles (by Bruno Aristimunha)Fix
moabb.datasets.GuttmannFlury2025BIDS exportOSErrorby correcting channel types (Trig→ stim,HEO/VEO→ eog,M1/M2→ misc) so trigger channel values no longer exceed EEG physical range limits (by Bruno Aristimunha)Fix
numpy.void.get()error inmoabb.datasets.Lee2019resting state EMG channel handling (by Bruno Aristimunha)Fix
moabb.datasets.castillos2023.BaseCastillos2023extraction check using wrong directory name (4Class-VEPinstead of4Class-CVEP), causing re-extraction on every call, and replace fragilerstrippath derivation with properos.pathmanipulation (by Bruno Aristimunha)Fix data path lookup in
moabb.datasets.Forenzo2023that makes MOABB unable to find the downloaded data (#1048 by Ethan Davis).Fix wrong paper reference in
moabb.datasets.Thielen2021(associated_paper_doipointed to the Ahmadi electrode-montage reference instead of the dataset’s primary publication), restore Radboud data-repository DOI as__init__.doi, and add regression testtest_primary_paper_matches_dataset_codethat validates every<Surname><Year>dataset against its cited primary paper (by Bruno Aristimunha)Fix
UnicodeEncodeErrorwhen the GBK codec fails on'\xef'in BIDS metadata export by explicitly settingencoding="utf-8"on file writes inbids_interface(#1059 by sli930)Modified example usage and fixed epoch extraction with an adjustable buffer that prevents last epochs being dropped in
moabb.datasets.RomaniBF2025ERP(#1065 by Michele Romani).Fix zip extraction in
moabb.datasets.Wairagkar2018dataset loader (#1066 by Barış Talar).Fix EEG layout corruption in
moabb.datasets.BNCI2020_002: the F-contiguousbciexp.datawas reshaped in default C-order, producing a trial-fastest interleaved layout that disagreed with the per-trial stim markers and made every epoch sample the wrong trial. The reshape now transposes to trial-major before flattening (by Bruno Aristimunha).Fix
stim_trialcontent inmoabb.datasets.MartinezCagigal2023Checkerandmoabb.datasets.MartinezCagigal2023Pary: the channel was carrying the per-recording trial index instead of the attended command id, breaking multiclass classification across recordings. The marker is now the command id (resolved via the newmoabb.datasets.utils.resolve_cvep_command_ids()helper), and the_trial_metaannotation extras gain acommand_idkey alongsidetrial_id(by Bruno Aristimunha).Cache Figshare’s file listing in
moabb.datasets.download.fs_get_file_list()(process-levellru_cache) and persist it on disk next to the data for MAMEM (moabb.datasets.MAMEM1/MAMEM2/MAMEM3). Once a dataset has been downloaded, subsequent calls never contact Figshare; passforce_update=Trueto bypass both layers (by Bruno Aristimunha).Fix Windows download path sanitization that changed absolute paths like
C:\datainto relativeC-\datapaths (#1079 by Anton Andreev).Fix missing electrode positions (NaN xyz) in six motor-imagery datasets so topographic maps, interpolation, and spatial methods work:
moabb.datasets.Forenzo2023andmoabb.datasets.GuttmannFlury2025_MI/_MEnormalize Neuroscan ALL_CAPS labels and applystandard_1005(CB1/CB2 kept asmisc);moabb.datasets.Dreyer2023falls back tostandard_1005when the BIDS archive ships noelectrodes.tsv;moabb.datasets.BNCI2003_004maps its 26 legacy Berlin channel labels to their modern 10-5 equivalents for exact positions;moabb.datasets.BNCI2014_002applies an approximate 3x5 grid for its unlabeled small-Laplacian channels; andmoabb.datasets.Zhang2017applies theGSN-HydroCel-32montage in EGI sensor order. Adds the sharedmoabb.datasets.utils.set_neuroscan_montage()helper (#1089 by Bruno Aristimunha).Fix
BaseEvaluation._aggregate_fold_resultsaborting the whole evaluation withTypeError: agg function failed [how->mean,dtype->object]when a single fold contributes a non-numericscore(e.g. an error fold). The numeric aggregation columns are now coerced withpandas.to_numeric(errors="coerce")beforegroupby.agg, so a bad fold becomesNaNand is skipped instead of taking down every subject/pipeline (#1095 by Bruno Aristimunha).Fix
moabb.evaluations.splitters.WithinSessionSplitterandmoabb.evaluations.splitters.WithinSubjectSplitteroverwriting an explicitn_splitspassed throughcv_kwargswith then_foldsdefault; the caller-providedn_splitsnow takes precedence, so a single holdout split can be requested directly viacv_class=StratifiedShuffleSplit, n_splits=1.moabb.evaluations.WithinSessionEvaluationandmoabb.evaluations.WithinSubjectEvaluationnow honour then_splitsargument instead of always running 5 folds, andmoabb.evaluations.splitters.WithinSubjectSplitternow yields reproducible per-subject folds for a fixedrandom_state(#1106 by Bruno Aristimunha).Evaluations now apply
cv_kwargsto the default cross-validation class. Caller settings override splitter defaults, and splitter construction forwards each setting without duplicate keyword arguments (by Stanley C.).Fix numeric sorting in the dataset summary tables (Data Summary): columns containing the
variessentinel (e.g.Total_trials) were auto-detected as strings by DataTables and sorted lexicographically (11000 < 1114 < 11496). A customnum-variescolumn type now treats such columns as numeric, sorting sentinel rows last while keeping their displayed text unchanged (#1118 by Bhargav Kowshik).Fix
make htmlcrash inscripts/generate_macro_table.pywhen a dataset has a missing (NaN) value in an optional metadata column (country, DOI, data URL, …): the floatNaNis truthy, so it slipped past theif not valueguards and crashed the string formatters (TypeError: object of type 'float' has no len())._format_cellnow normalizesNaNtoNonebefore dispatching, and_dataset_link/_paradigm_tag– the only two format branches without an empty-value guard, which raisedAttributeErroronhtml.escape(None)– guard it too, so all eleven branches render a missing cell as empty (#1117 by Bhargav Kowshik).Fix
extra_runsparameter behaviour inmoabb.datasets.RomaniBF2025ERP, and defaulted to False for fair cross-subject comparability.(by Michele Romani).
Code health#
Point
MANIFEST.inat the files the repository actually ships. Three of its six directives namedREADME.rst,LICENSE.txtandNOTICE.txt; the repository hasREADME.mdandLICENSE, and no notice file at all, so everypython -m build --sdistprinted threewarning: no files found matching ...lines. The sdist contents are unchanged – setuptools already picked up the readme and the license throughproject.readmeand its defaultlicense-files– only the warnings go away (by Bhargav Kowshik).Bump the
ruff-pre-commithook fromv0.15.9tov0.15.20(quarterlypre-commit.ciautoupdate). The bump is lint-neutral on the current tree:ruff checkandruff format --checkreturn identical results at both pins across all 229 tracked*.py/*.pyifiles, andpre-commit run --all-filesrewrites no file (#1119 by pre-commit-ci).Fix deprecated
pyriemann.utils.{mean,covariance,base}import paths: bump the minimumpyriemannto0.12and update all import sites inmoabb/pipelines/csp.py,moabb/pipelines/classification.py,moabb/datasets/preprocessing.py, and the Riemannian artifact rejection example to usepyriemann.geometry.*(introduced in pyriemann 0.12, removal of the old paths scheduled for 0.14), and importPotato/PotatoFieldfrompyriemann.artifact_detection(moved frompyriemann.clusteringin 0.12) (by copilot-swe-agent).Install CPU-only PyTorch wheels in CI by setting
UV_TORCH_BACKEND=cpuin the test, braindecode, and docs workflows, so runners no longer download multi-GB CUDA builds oftorch(pulled transitively via thedeeplearningextra / braindecode) (#1083 by Bhargav Kowshik).Fix
moabb.evaluations.splitters.LearningCurveSplitternot randomising its training subsample whengroupsare passed, which covers every learning curve run throughmoabb.evaluations.CrossSessionEvaluationormoabb.evaluations.CrossSubjectEvaluation._get_data_size_subsetstakes a prefix of the training indices, which is only a random draw becauseStratifiedShuffleSplitshuffles them;GroupShuffleSplitreturns them ascending, so subsets could repeat across permutations and were biased toward the earliest samples in recording order. The grouped branch now lazily shuffles each consumed training fold before the prefix is taken; the ungrouped branch and held-out folds are unchanged for a given seed (#1158 by Bhargav Kowshik).
Version 1.5.0 (Stable - PyPi)#
Enhancements#
Add 9 new dataset adapters (8 SSVEP and 1 ERP) covering 357+ subjects:
moabb.datasets.Liu2020BETA(70 subjects, 40-class JFPM),moabb.datasets.Liu2022EldBETA(100 elderly subjects, 9-class, BIDS/GDF format),moabb.datasets.Kim2025BetaRange(40 subjects, 40-class beta-range),moabb.datasets.Dong2023(59 subjects, 40-class),moabb.datasets.Lee2021Mobile_SSVEPandmoabb.datasets.Lee2021Mobile_ERP(24 subjects, mobile BCI),moabb.datasets.Chen2017SingleFlicker(12 subjects, spatial SSVEP),moabb.datasets.Wang2021Combined(8 subjects, combined SSVEP), andmoabb.datasets.Han2024Fatigue(24 subjects, fatigue study with low/high frequency paradigms). Add shared utilitiesbuild_raw_from_epochs,TSINGHUA_64CH_NAMES, andFIGSHARE_DL_URLtomoabb.datasets.utils(by Bruno Aristimunha)Add 17 new motor imagery dataset adapters covering 345+ subjects:
moabb.datasets.Tavakolan2017,moabb.datasets.Zhang2017,moabb.datasets.Forenzo2023,moabb.datasets.Zhou2020,moabb.datasets.Jeong2020,moabb.datasets.Kaya2018,moabb.datasets.Kumar2024,moabb.datasets.Rozado2015,moabb.datasets.Brandl2020,moabb.datasets.Ma2020,moabb.datasets.Wairagkar2018,moabb.datasets.Wu2020,moabb.datasets.Yang2025,moabb.datasets.Chang2025,moabb.datasets.HefmiIch2025,moabb.datasets.TrianaGuzman2024,moabb.datasets.Yi2025, andmoabb.datasets.Zuo2025(by Bruno Aristimunha)Add
moabb.datasets.GuttmannFlury2025_MI,moabb.datasets.GuttmannFlury2025_ME,moabb.datasets.GuttmannFlury2025_SSVEP, andmoabb.datasets.GuttmannFlury2025_P300multi-paradigm dataset adapters with Zenodo re-hosted data (by Bruno Aristimunha)Add
moabb.datasets.Liu2025visual imagery dataset adapter (62 subjects) with Zenodo re-hosted data, and fixmoabb.datasets.Zhou2020download path (by Bruno Aristimunha)Add
moabb.datasets.Gao2026visual imagery dataset adapter (25 subjects) (by Bruno Aristimunha)Add 20
moabb.datasets.mainsah2025.Mainsah2025(BigP3BCI) P300 dataset adapters (studies A-S2, ~305 subjects from PhysioNet) (by Bruno Aristimunha)Add 3 new P300/ERP dataset adapters:
moabb.datasets.Simoes2020(15 ASD subjects, Zenodo re-hosted from Kaggle),moabb.datasets.Speier2017(13 subjects, RSVP),moabb.datasets.Chailloux2020(15 subjects) (by Bruno Aristimunha)Add
get_trial_info()andsuggest_interval()methods to help users choose an optimal epoch interval for variable-length trials, and preserve per-trialtriallengthmetadata through BIDS conversion via annotation extras (#816)Redesign dataset API pages with a structured snapshot card, visual summary blocks, HED-tag visualization, benchmark highlights, citation/public API cards, and responsive mobile improvements (#1000 by Bruno Aristimunha)
Add GA4 pageview metrics and popularity ranking to dataset documentation cards, with inline sparkline charts showing 90-day traffic trends (by Bruno Aristimunha)
Refine dataset documentation cards by removing PapersWithCode links from summary tables, folding page-view metrics into the Citation & Impact card, and adding an expandable Overview teaser with a direct link to the Overview tab (#1025 by Bruno Aristimunha)
Polish API reference page with color-coded concept highlights, section breaks, table styling, and scoped CSS to avoid affecting other pages (by Bruno Aristimunha)
Implementation of Pseudo-Online framework (#641 by Igor Carrara and Bruno Aristimunha)
Introduce a new logo for the MOABB library (#858 by Pierre Guetschel and community)
Better verbosity control for initialization of the library (#850 by Bruno Aristimunha)
Enhanced BNCI datasets with comprehensive participant demographics documentation (by Bruno Aristimunha)
Added
_participant_demographicsclass attribute to BNCI2014-002, BNCI2014-009, BNCI2015-001, and BNCI2015-004 for programmatic access to demographics (by Bruno Aristimunha)Improved BIDS conversion with guaranteed montage preservation for all BNCI datasets (by Bruno Aristimunha)
Dataset-specific metadata extraction for BNCI2014-001, BNCI2014-004, and BNCI2014-008 with age, gender, and clinical information (by Bruno Aristimunha)
Improved error messages for dataset compatibility checks in evaluations - now provides specific reasons when datasets are incompatible (e.g., “dataset has only 1 session(s), but CrossSessionEvaluation requires at least 2 sessions”) by Bruno Aristimunha
Ability to join rows from the tables of MOABB predictive performance scores and detailed CodeCarbon compute profiling metrics by the column codecarbon_task_name in MOABB results and the column task_name in CodeCarbon results (#866 by Ethan Davis).
Adding two c-VEP datasets:
moabb.datasets.MartinezCagigal2023Checkerandmoabb.datasets.MartinezCagigal2023Paryby Victor Martinez-CagigalAllow custom paradigms to have multiple scores for evaluations (#948 by Ethan Davis)
Ability to parameterize the scoring rule of paradigms (#948 by Ethan Davis)
Extend scoring configuration to accept lists of metric callables, scorer objects, and tuple kwargs (e.g., needs_proba/needs_threshold) for multi-metric evaluations (#948 by Ethan Davis and Bruno Aristimunha)
Implement
moabb.evaluations.WithinSubjectSplitterfor k-fold cross-validation within each subject across all sessions (by Bruno Aristimunha)Add
cv_classandcv_kwargsparameters to all evaluation classes (WithinSessionEvaluation, CrossSessionEvaluation, CrossSubjectEvaluation) for custom cross-validation strategies (#963 by Bruno Aristimunha)Implement
LearningCurveSplitteras a dedicated sklearn-compatible cross-validator for learning curves, enabling learning curve analysis with any evaluation type (#963 by Bruno Aristimunha)Flattened parallel evaluation: CV folds are now evaluated in parallel within each dataset via
_process_parallel(), replacing per-fold sequential evaluation while preserving per-dataset scheduling (by Bruno Aristimunha)Auto-generate dataset documentation admonitions (Participants, Equipment, Preprocessing, Data Access, Experimental Protocol) from class-level
METADATAwhen missing, while preserving manually written sections (#960 by Bruno Aristimunha)Add a “Report an Issue on GitHub” feedback section to all dataset docstrings so users can easily report dataset problems (#982 by Bruno Aristimunha)
Add
additional_metadataparameter toparadigm.get_data()to fetch additional metadata columns from BIDSevents.tsvfiles. Supports"all"to load all columns or a list of specific column names (#744 by Matthias Dold)Add
get_additional_metadata()method tomoabb.datasets.base.BaseDatasetallowing datasets to provide additional metadata for epochs. Implemented for BIDS datasets inmoabb.datasets.base.BaseBIDSDataset(#744 by Matthias Dold)Add automatic HED 8.4.0 (Hierarchical Event Descriptors) annotations to BIDS export with 83 validated paradigm-specific tags covering all MOABB datasets,
HEDVersionindataset_description.json, events.json sidecar patching, per-dataset override viaExperimentMetadata.hed_tags, andLabel/fallback for unmapped events (#974 by Bruno Aristimunha)Add tutorial on time-resolved decoding with
mne.decoding.SlidingEstimator, showing how to evaluate per-time-point AUC across subjects as an alternative to pseudo-online evaluation (#718 by Bruno Aristimunha)Add advanced tutorial on Riemannian Artifact Rejection (Riemannian Potato and Potato Field) as a pre-processing step using pipeline surgery (by Davoud Hajhassani and Bruno Aristimunha)
Simplify the Riemannian Artifact Rejection tutorial by using pyRiemann’s enhanced
PotatoFieldAPI (per-potato metrics and configurable p-value combination), and refresh the 2D potato visualization to better match the pyRiemann reference (#1011 by Bruno Aristimunha)Add pipeline surgery methods (
find_steps,insert_step,remove_step) toFixedPipelinefor easier pipeline manipulation (by Davoud Hajhassani and Bruno Aristimunha)Add license metadata to all datasets with known licenses, covering BNCI, BrainInvaders, ErpCore2021, Castillos, MartinezCagigal2023, Beetl2021, Kojima2024, Dreyer2023, and many others (#989 by Bruno Aristimunha)
Add parametrized test
test_all_datasets_have_licenseto ensure all datasets declare a license in their documentation metadata (#989 by Bruno Aristimunha)Add and correct
licenseandrepositoryfields inDocumentationMetadataacross all datasets against upstream sources; standardize all license strings to SPDX identifiers; correct DOIs for BNCI2014_002 and MAMEM1/2/3 datasets (by Katelyn Begany)Validate and correct metadata across 45 datasets against original publications, fixing ~920 fields including country codes, preprocessing conflation, reference electrodes, and fabricated auxiliary channels (#1001 by Bruno Aristimunha)
Add
convert_to_bids()method for exporting raw EEG datasets to clean BIDS-compliant directory structures without processing-pipeline hash in filenames (by Bruno Aristimunha)Expose
subjectsandsessionsparameters on all dataset constructors to allow filtering at instantiation time (e.g.,PhysionetMI(subjects=[1, 2, 3])). Addall_subjectsproperty andsessionsparameter toget_data()(by Bruno Aristimunha)Enhance Tutorial 4 (“Creating a dataset class”) with a new section demonstrating
BaseBIDSDatasetandLocalBIDSDatasetas the recommended approach for adding BIDS-format datasets to MOABB (#1007 by Bruno Aristimunha)Add Copy and CSV export buttons to benchmark results tables on the results webpage, enabling users to copy or download table data directly (#1002 by Bruno Aristimunha)
Add license chip with official Creative Commons SVG icons to dataset documentation pages, showing license type with inline icons and links to license deeds (#1015 by Bruno Aristimunha)
Add adjusted chance levels, distribution plot, and restyle analysis plots with colorblind-friendly palette (#1019 by Bruno Aristimunha)
Enrich documentation metadata for Hinss2021, ErpCore2021 (all 7 variants), Schirrmeister2017, MartinezCagigal2023 (Checker + Pary), and Rodrigues2017 with investigators, institution, country, ethics approval, funding, contact info, acknowledgements, and citation instructions extracted from published papers. All 83 datasets now have
investigatorspopulated (#1017 by Bruno Aristimunha)Add
return_all_modalitieskeyword-only parameter tomoabb.datasets.base.BaseDatasetand 30+ multi-modal dataset subclasses, allowing users to retain non-EEG channels (EOG, EMG, ECG, misc) when loading data. AcceptsTrue(all non-stim channels), or adictofmne.pick_types()keyword arguments for fine-grained control (e.g.dict(eeg=True, eog=True)). The setting is respected through the preprocessing pipeline (RawToEpochs) and BIDS conversion so non-EEG channels survive epoching and export. Add sharedpick_channels_for_modalities()helper tomoabb.datasets.utils(#966, #1030 by Bruno Aristimunha)Add new
moabb.analysis.neural_signaturesmodule with interactive Plotly-based neural signature visualizations for all five BCI paradigms: Motor Imagery ERD/ERS topomaps, P300/ERP waveforms, SSVEP power spectra and SNR, c-VEP evoked responses with PSD, and Resting State band power distributions. Public API:generate_neural_signature,neural_signature_html,get_plotly_template,get_plotly_colorscale. Produces standalone HTML reports with MOABB branding, interactive SVG head diagrams, electrode selection, and per-subject views (#1039 by Bruno Aristimunha)Add
generate_figuresparameter toconvert_to_bids()for optional neural signature generation into{bids_root}/derivatives/neural_signatures/during BIDS export (#1039 by Bruno Aristimunha)Conditionally export
generate_neural_signatureandneural_signature_htmlfrommoabb.analysiswhenplotlyis installed (#1039 by Bruno Aristimunha)
API changes#
Removed
SinglePassandFilterBankintermediate classes from motor imagery and P300 paradigms.FilterBankLeftRightImagerynow inherits fromLeftRightImagery,FilterBankMotorImageryinherits fromMotorImagery, andP300inherits directly fromBaseP300.RestingStateToP300Adapternow inherits fromBaseP300. Docstring inheritance is handled viaNumpyDocstringInheritanceInitMeta(#467 by Bruno Aristimunha).Allow CodeCarbon script level configurations when instantiating a
moabb.evaluations.base.BaseEvaluationchild class (#866 by Ethan Davis).When CodeCarbon is installed, MOABB HDF5 results have an additional column codecarbon_task_name. If CodeCarbon is configured to save to file, its own tabular results have a column task_name. These columns are unique UUID4s. Related rows can be joined to see detailed costs and benefits of predictive performance and computing profiling metrics (#866 by Ethan Davis).
Isolated model fitting, duration tracking, and CodeCarbon compute profiling tracking. New and consistent ordering of duration and CodeCarbon tracking across all evaluations: (Higher priority, closest to model fitting) required duration tracking, (lower priority, second closest to model fitting) optional CodeCarbon tracking (#866 by Ethan Davis).
Replaced unreliable wall clock duration tracking (Python’s time.time()) in favor of performance counter duration tracking (Python’s time.perf_counter()) (#866 by Ethan Davis).
Enable choice of online or offline CodeCarbon through the parameterization of codecarbon_config when instantiating a
moabb.evaluations.base.BaseEvaluationchild class (#956 by Ethan Davis)Renamed stimulus channel from
stimtoSTIin BNCI motor imagery and error-related potential datasets for clarity and BIDS compliance (by Bruno Aristimunha).Added four new BNCI P300/ERP dataset classes:
moabb.datasets.BNCI2015_009(AMUSE),moabb.datasets.BNCI2015_010(RSVP),moabb.datasets.BNCI2015_012(PASS2D), andmoabb.datasets.BNCI2015_013(ErrP) (by Bruno Aristimunha).Removed
data_sizeandn_permsparameters frommoabb.evaluations.WithinSessionEvaluation. Usecv_class=LearningCurveSplitterwithcv_kwargs=dict(data_size=..., n_perms=...)instead (#963 by Bruno Aristimunha)Learning curve results now automatically include “data_size” and “permutation” columns when using
LearningCurveSplitter(#963 by Bruno Aristimunha)Replace wildcard imports with explicit class imports in
moabb.paradigms(#1004 by Bruno Aristimunha)
Requirements#
Allows CodeCarbon environment variables or a configuration file to be defined in the home directory or the current working directory (#866 by Ethan Davis).
Added
filelockas a core dependency to fix missing import errors in utils (#959 by Mateusz Naklicki).Switch
pyriemanndependency from GitHub source back to PyPI (>=0.7). The Riemannian Artifact Rejection tutorial still requirespyriemannfrom source forPotatoFieldfeatures and is no longer auto-executed during doc builds (#1011 by Bruno Aristimunha)Add type hints to
moabb.evaluations.base.BaseEvaluationand all concrete evaluation classes (#732 by Sarthak Tayal)Add
plotly>=5.18.0as optionalinteractivedependency (pip install moabb[interactive]), included inmoabb[all](#1039 by Bruno Aristimunha)
Bugs#
Fix trial acceptance condition in
moabb.datasets.Stieger2021that allowed epochs to extend beyond actual motor imagery duration into adjacent trials’ resting periods (#816)Fix DOI escaping in “See DOI” fallback link on dataset citation cards (#1000 by Bruno Aristimunha)
Prefer paper DOI over data DOI in dataset citation card when both are available (#1000 by Bruno Aristimunha)
Fix timeline SVG card artifact caused by link styling on dataset pages (by Bruno Aristimunha)
Fix dataset documentation teaser rendering by skipping raw reStructuredText directives in previews, hiding empty page-view rows when analytics are unavailable, and clearing dataset summary reST warnings (#1025 by Bruno Aristimunha)
Fix class-balance visualization counts by normalizing metadata/event class labels (e.g.,
NonTargetvsnon-target) and use the first valid dataset subject in generated quickstart snippets instead of hardcodedsubjects=[1](#1000 by Bruno Aristimunha)Fix missing
P300from the list of valid paradigms in themoabb.benchmark()docstring (by Bruno Aristimunha)Fix critical trigger alignment bug in
moabb.datasets.Liu2024wherecreate_event_array()selected the first 40 of 120 STI triggers (mixing instruction, MI, and break onsets) instead of filtering for only the MI onset triggers (value=2). Also fix swapped left/right hand label mapping inencoding()and correct epoch interval from(2, 6)to(0, 4)to match MI onset triggers (by Bruno Aristimunha)Fixed incorrect DOIs in Dreyer2023, RomaniBF2025ERP, BNCI2015_003, BNCI2015_004, and BNCI2015_012 datasets (#977 by Bruno Aristimunha)
Added missing metadata DOIs for AlexMI, PhysionetMI, GrosseWentrup2009, Shin2017A, Shin2017B, BNCI2014_004, and BNCI2003_004 datasets (#977 by Bruno Aristimunha)
Fixed montage not being set before BIDS cache conversion in BNCI datasets (by Bruno Aristimunha)
Fixed measurement date setting for BNCI datasets to use specific collection years from papers (by Bruno Aristimunha)
Ensured proper subject ID assignment for BIDS compliance across all BNCI datasets (by Bruno Aristimunha)
Correct
moabb.pipelines.classification.SSVEP_CCA,moabb.pipelines.classification.SSVEP_TRCAandmoabb.pipelines.classification.SSVEP_MsetCCAbehavior (#625 by Sylvain Chevallier)Fix scikit-learn LogisticRegression elasticnet penalty parameter deprecation by re-adding penalty=’elasticnet’ for ElasticNet configurations with 0 < l1_ratio < 1 (#869 by Bruno Aristimunha)
Fixing option to pickle model (#870 by Ethan Davis)
Normalize Zenodo download paths and add a custom user-agent to improve download robustness (#946 by Bruno Aristimunha)
Use the BNCI mirror host to avoid download timeouts (#946 by Bruno Aristimunha)
Repair incomplete or corrupted
moabb.datasets.Zhou2016subject downloads by validating extracted EEG/events files and re-downloading under a subject-level lock, preventing empty-session failures during parallel docs/CI runs (by Bruno Aristimunha)Prevent Python mutable default argument when defining CodeCarbon configurations (#956 by Ethan Davis)
Fix copytree FileExistsError in BrainInvaders2013a download by adding dirs_exist_ok=True (by Bruno Aristimunha)
Ensure optional additional scoring columns in evaluation results (#957 by Ethan Davis)
Fix pandas
ArrowStringArrayshuffle warning by converting.unique()results to numpy arrays in splitters, avoiding issues with newer pandas versions (#963 by Bruno Aristimunha)Fix crash in
moabb.datasets.Huebner2017when regex match on vhdr filenames returnsNone(#1036 by Sarthak Tayal)Replace production
assertstatements with properValueError/TypeErrorexceptions across analysis, pipelines, paradigms, and datasets modules (#1036 by Sarthak Tayal)Fix silent pipeline name collision in
moabb.pipelines.utils.create_pipeline_from_configby raisingValueErroron duplicate names (#1036 by Sarthak Tayal)Replace bare
print()calls with proper logging inmoabb.datasets.MartinezCagigal2023Checker(#1036 by Sarthak Tayal)LearningCurveSplitternow skips training splits that collapse to a single class (e.g., with very smalldata_size) and emits aRuntimeWarninginstead of producing NaN results (#963 by Bruno Aristimunha)Fix double µV-to-V conversion in BNCI2003-004 and BNCI2015-006: data loaded in microvolts was labeled as volts without unit conversion, causing a second scaling during EDF export via
mne_bids(by Bruno Aristimunha)Fix
Beetl2021_AandBeetl2021_B403 Forbidden errors by skipping Figshare API calls when data already exists locally, and fix double-nested zip extraction directory structure (#969 by Bruno Aristimunha)Fix wrong channel names in Riemannian Artifact Rejection tutorial that caused
pick()to fail on BNCI2014-009 (by Bruno Aristimunha)Fix
moabb.datasets.RomaniBF2025ERPto follow MOABB nomenclature pattern by using dynamic folder nameMNE-{code}-datainstead of hardcoded folder name. Automatically migrates legacy folderBrainForm-BIDS-eeg-datasetto new nomenclature for backward compatibility (by Bruno Aristimunha)Move BIDS cache lock file from the BIDS subject folder to the
code/folder for BIDS validator compliance. Lock files are now written per-session ascode/sub-{subject}_ses-{session}_desc-{hash}_lockfile.json. Backward compatibility is preserved for caches created with the old location (#986 by Pierre Guetschel and Bruno Aristimunha)Fixed
erase()inBIDSInterfaceBaseto handle multi-session datasets correctly by using per-sessionrm()calls instead of a single subject-level call, which previously caused aRuntimeErrorwhen looking upscans.tsvacross multiple sessions (#986 by Pierre Guetschel and Bruno Aristimunha)Fix
MOABB_RESULTSdefault path to respectMNE_DATAconfiguration instead of hardcoding~/mne_data, and fix docs CI cache to use workspace-relativeMNE_DATApath and cache~/.mneconfig directory (by Bruno Aristimunha)Fix
moabb.datasets.RomaniBF2025ERPget_data()failing with description merge error when adding stim channel, causing sessions to be silently dropped (#991 by Bruno Aristimunha)Fix docs CI cache: set
MNE_DATAenv var and persist~/.mneconfig directory so dataset paths survive cache restore (by Bruno Aristimunha)Fix CI dataset cache reuse across commits/PR updates by using stable cache keys and default-branch cache saves for docs/tests workflows, avoiding repeated dataset downloads (by Bruno Aristimunha)
Fix
moabb.datasets.Liu2024download failure by switching Figshare URLs fromfigshare.com/ndownloadertondownloader.figshare.comand addingBadZipFilerecovery for corrupted cached downloads (#992 by Bruno Aristimunha)Remove redundant
autoattribute METADATAfrom MartinezCagigal2023 Checker and Pary docstrings (#1022 by Bruno Aristimunha)Fix TRCA Riemannian mean convergence failure by regularizing ill-conditioned cross-covariance matrices in
moabb.pipelines.classification.SSVEP_TRCA. Eigenvalue clamping bounds the condition number, eliminatingConvergence not reachedandinvalid value encountered in logwarnings (by Bruno Aristimunha)Fix SSVEP CCA-family estimator consistency and eCCA formulation:
moabb.pipelines.classification.SSVEP_CCA,moabb.pipelines.classification.SSVEP_MsetCCA,moabb.pipelines.classification.SSVEP_itCCA, andmoabb.pipelines.classification.SSVEP_eCCAnow return predictions in the same label space asclasses_and alignpredict_probacolumns withclasses_order.moabb.pipelines.classification.SSVEP_CCAandmoabb.pipelines.classification.SSVEP_eCCAnow infer frequencies robustly from epochs metadata (withfreq_mapoverride), andmoabb.pipelines.classification.SSVEP_eCCAnow uses the corrected 4-feature filter assignments with updated reference/citation alignment (by Bruno Aristimunha)Add documentation note to
moabb.datasets.PhysionetMIthat subject 88 was recorded at 128 Hz instead of 160 Hz, which causes errors when loaded alongside other subjects (#538 by Bruno Aristimunha)Fix BIDS validator compliance: monkey-patch
mne_bidsto generateelectrodes.jsonsidecar withSpatialReferencekey required by BIDS validator v2.4.0 whenspace-CapTrakentity is present (by Bruno Aristimunha)Fix
HardwareFiltersBIDS sidecar format: wrap flat filter dicts in the required nested structure{"FilterName": {"key": "value"}}instead of writing a flat dict, and wrap string filters similarly (by Bruno Aristimunha)Fix
doifield indataset_description.jsonto use BIDS-requireddoi:<value>format by adding thedoi:prefix when missing (by Bruno Aristimunha)Fix
write_raw_bidsoverwrite error for multi-session datasets by detecting when a subject already exists inparticipants.tsvand settingoverwrite=Truefor subsequent sessions (by Bruno Aristimunha)Fix
moabb.datasets.Ofner2017generic channel names (eeg-0..eeg-60) in subject 1 execution GDF files by mapping them to correct 10-20 montage labels (by Bruno Aristimunha)Fix
moabb.datasets.Wang2021Combinedsegfault during BIDS conversion by switching frommne.io.read_raw_anttomne.io.read_raw_cnt, avoiding a crash in the ANT reader’s C library (libEep) on macOS (by Bruno Aristimunha)Fix brittle channel picking in
BIDSInterfaceRawEDFthat enumerated every MNE channel type keyword; replaced with stim-exclusion approach (#1030 by Bruno Aristimunha)Fix
set_montagecrash inmoabb.datasets.Thielen2015whenreturn_all_modalities=Trueby retyping ANA/EXG channels tomisc(#1030 by Bruno Aristimunha)Fix duplicate stim channels and dead CPz reference channel in
moabb.datasets.Liu2024whenreturn_all_modalities=True(#1030 by Bruno Aristimunha)Fix
RawToEpochssilently stripping all non-EEG channels regardless ofreturn_all_modalitiessetting (#1030 by Bruno Aristimunha)Fix HED annotation semantics for Motor Imagery events per expert review: decompose each MI event into separate
(Sensory-event, Experimental-stimulus, Visual-presentation)and(Agent-action, ...)top-level groups per HED Rules 2b/2e/2f, remove conflatedCue+Experimental-stimulusroles, fix SSVEPresttag toExperiment-structure, and extract shared_MI_SENSORYconstant to reduce tag duplication. Revert arrow-specific cues from paradigm-level HED defaults to generic sensory prefix, and add per-datasethed_tagsoverrides for the 6 datasets that actually use arrow cues (BNCI2014-001, BNCI2014-004, Lee2019_MI, Zhou2016, Shin2017A, GrosseWentrup2009) (#1035 by Bruno Aristimunha)
Code health#
Generate dataset timeline SVGs at Sphinx build time instead of tracking pre-rendered files in git (by Bruno Aristimunha)
Fix Sphinx documentation warnings and move sliding-estimator tutorial to advanced examples (by Bruno Aristimunha)
Fix autosummary descriptions in API page by skipping
dataset_timeline_extin summary context (by Bruno Aristimunha)Hide right sidebar and admonition icons on dataset pages for a cleaner layout using per-page meta directives (by Bruno Aristimunha)
Add GA4 pageview export script and CI workflow integration for automated dataset traffic metrics (by Bruno Aristimunha)
Resolve all 216 pytest warnings across the test suite by addressing root causes: clear Epochs annotations before concatenation, replace lambda with named function in test pipelines, re-apply montage after
add_reference_channels, conditionally passgroupsparameter in splitters usingGroupsConsumerMixin, useos.environinFakeDatasetto avoid non-standard config warnings, and suppress intentionalOptunaSearchCVexperimental warnings at init (by Bruno Aristimunha)Added systematic DOI validation test suite that checks format, docstring tracking, resolution, and author overlap across all datasets (#977 by Bruno Aristimunha)
Further reorganized BNCI datasets into year-specific modules (
bnci_2003,bnci_2014,bnci_2015,bnci_2019) with shared helpers inlegacy_basefor clearer maintenance. The temporarylegacy.pyfile has been removed (by Bruno Aristimunha).Added new datasets
moabb.datasets.BNCI2020_001,moabb.datasets.BNCI2020_002,moabb.datasets.BNCI2022_001,moabb.datasets.BNCI2025_001, andmoabb.datasets.BNCI2025_002(by Bruno Aristimunha).Persist docs/test CI MNE dataset cache across runs to reduce cold-cache downloads (#946 by Bruno Aristimunha)
Refactor evaluation scoring into shared utility functions for future improvements (#948 by Bruno Aristimunha)
Centralize CV resolution in BaseEvaluation with new
_resolve_cv()method for consistent cross-validation handling across all evaluation types. Add_build_result()and_build_scored_result()helpers to centralize result dict construction across WithinSession, CrossSession, and CrossSubject evaluations, replacing manual dict assembly in each (#963 by Bruno Aristimunha)Remove redundant learning curve methods (
get_data_size_subsets(),score_explicit(),_evaluate_learning_curve()) from WithinSessionEvaluation in favor of unified splitter-based approach (#963 by Bruno Aristimunha)Generic metadata column registration:
LearningCurveSplitterdeclares ametadata_columnsclass attribute, andBaseEvaluationauto-detects it viahasattr(cv_class, "metadata_columns")instead of hardcoding class checks, making it extensible to future custom splitters (#963 by Bruno Aristimunha)Fix
get_n_splits()delegation inWithinSessionSplitterandWithinSubjectSplitterto properly forward to the innercv_class.get_n_splits()instead of hardcodingn_folds, giving correct split counts when using custom CV classes likeLearningCurveSplitter(#963 by Bruno Aristimunha)Remove dead
_fit_and_score()function and unusedparadigm/mne_labelsparameters from_evaluate_fold()inevaluations/base.py(by Bruno Aristimunha)Memory optimization in
_process_parallel(): passX,y,metadataas top-level positional args tojoblib.delayed()so the loky backend can auto-mmap large numpy arrays, avoiding N full copies for N parallel tasks (by Bruno Aristimunha)Remove duplicate
get_inner_splitter_metadata()fromWithinSessionSplitter,WithinSubjectSplitter, andCrossSubjectSplitter. All splitters now store a_current_splitterreference, andBaseEvaluation._build_scored_result()reads metadata generically from it (#963 by Bruno Aristimunha)Extract
_fit_cv(),_maybe_save_model_cv(), and_attach_emissions()intoBaseEvaluation, removing duplicated model-fitting, model-saving, and carbon-tracking boilerplate fromWithinSessionEvaluation,CrossSessionEvaluation, andCrossSubjectEvaluation(#963 by Bruno Aristimunha)Extract
_load_data()helper intoBaseEvaluationto centralize data loading logic (epoch requirement checking andparadigm.get_data()call) that was duplicated across all three evaluation classes (#963 by Bruno Aristimunha)Extract
_get_nchan()helper intoBaseEvaluationto replace repeated channel count extraction (X.info["nchan"] if isinstance(X, BaseEpochs) else X.shape[1]) in all evaluation classes (#963 by Bruno Aristimunha)Move
_pipeline_requires_epochs()fromevaluations.pytoutils.pyfor shared access byBaseEvaluation._load_data()(#963 by Bruno Aristimunha)Move
WithinSessionSplittercreation outside the per-session loop inWithinSessionEvaluation, since splitter parameters do not change per session (#963 by Bruno Aristimunha)Add a compile smoke test (
moabb/tests/test_compilation.py) that validates syntax for all Python files undermoabb/usingpy_compile(#960 by Bruno Aristimunha)Add persistent DOI resolution cache (
moabb/tests/doi_cache.json) fortest_doi_validation.pyto avoid network requests on every test run, reducing DOI test time from ~9 minutes to <1 second. Refresh with--update-doi-cache(#996 by Bruno Aristimunha)Fix
UtilEvaluationtest class not discovered by pytest: renamed toTestUtilEvaluationand replacedself.skipTest(unittest-only) withpytest.skip(#1005 by Bruno Aristimunha)Add 282 parametrized tests in
moabb/tests/test_neural_signatures.pycovering all five paradigms, HTML generation, template/colorscale utilities, and edge cases for themoabb.analysis.neural_signaturesmodule (#1039 by Bruno Aristimunha)
Version 1.4.3 (Stable - PyPi)#
Enhancements#
Add “Open in Colab” buttons for gallery examples (#853 by Bruno Aristimunha)
Refresh docs homepage design and citation visibility (#853 by Bruno Aristimunha)
Add
FixedPipelineandmoabb.datasets.preprocessing.make_fixed_pipelineto avoid scikit-learn unfitted pipeline warnings (#850 by Bruno Aristimunha)
API changes#
None.
Requirements#
Improve compatibility with Python 3.14 (#848 by Bruno Aristimunha)
Bugs#
Fixing warnings from the latest scikit-learn version within the Preprocessing logic (#850 by Bruno Aristimunha)
Fixing compatibility with Scikit-learn 1.8 (#852 by Bruno Aristimunha)
Code health#
Generate notebooks in docs CI for Colab integration (#853 by Bruno Aristimunha)
Version 1.4.2#
Enhancements#
Adding dataset
moabb.datasets.RomaniBF2025ERPconverted to BIDS (#825 by Romani Michele)Improve compute_pvals_perm function (#818 by Quentin Barthelemy)
Bugs#
Fixes the management of include/exclude datasets in
moabb.benchmark(), adds additional verifications (#834 by Anton Andreev)Fixing pagination issue with figshare (#839 by Bruno Aristimunha)
Fixes
SetRawAnnotationsin case no STIM channel is present (#838 by Pierre Guetschel and Simon Kojima)
API changes#
None.
Version - 1.4#
Enhancements#
Update to pyRiemann 0.9 and numpy 2.0 for improved compatibility (#789 by Gregoire Cattan and Bruno Aristimunha)
Adding
moabb.datasets.Kojima2024A(#807 by Simon Kojima)Adding
moabb.datasets.Kojima2024B(#806 by Simon Kojima)Add new dataset
moabb.datasets.BNCI2003_004dataset (#811 by Griffin Keeler)Added the ability to feed pipelines using a list of dictionaries in
moabb.benchmark()(#826 by Anton Andreev)
Bugs#
Fixing label swapped issue with
moabb.datasets.Kalunga2016dataset (#814 by Kosei Nakada)Fix the
moabb.datasets.Dreyer2023(#828 by Simon Kojima)
API changes#
None.
Version - 1.3#
Enhancements#
Adding a tutorial for
moabb.evaluations.splitters.WithinSessionSplitter(#776 by Thomas Kooiman, Paul Verhoeven, Jorge Sanmartin Martinez, and Radovan Vodila )Adding new motor imagery dataset, Dreyer2023 (#404 by Sara Sedlar, Sylvain Chevallier and Bruno Aristimunha)
Reordering the examples in the documentation (#706 by Bruno Aristimunha)
Creating the meta information for the BIDS converted datasets (#688 by Bruno Aristimunha)
Adding
moabb.datasets.Beetl2021_Aandmoabb.datasets.Beetl2021_B(#675 by Samuel Boehm)Adding
moabb.evaluations.splitters.CrossSubjectSplitter(#722 by Bruna Lopes and Bruno Aristimunha)Adding
moabb.evaluations.splitters.CrossSessionSplitter(#720 by Bruna Lopes and Bruno Aristimunha)Adding
moabb.datasets.base.BaseBIDSDatasetandmoabb.datasets.base.LocalBIDSDataset(#724 by Pierre Guetschel)Adding
moabb.analysis.plotting.dataset_bubble_plot()plus the corresponding tutorial (#753 by Pierre Guetschel)Adding
moabb.datasets.utils.plot_all_datasetsand update the tutorial (#758 by Pierre Guetschel)Improve the dataset model cards in each API page (#765 by Pierre Guetschel)
Refactor
moabb.evaluations.CrossSessionEvaluation,moabb.evaluations.CrossSubjectEvaluationandmoabb.evaluations.WithinSessionEvaluationto use the new splitter classes (#769 by Bruno Aristimunha)Adding tutorial on using mne-features (#762 by Alexander de Ranitz, Luuk Neervens, Charlynn van Osch and Bruno Aristimunha)
Creating tutorial to expose the pre-processing steps (#771 by Bruno Aristimunha)
Add function to auto-generate tables for the paper results documentation page (#785 by Lucas Heck)
Improving the Filterbank tutorial and implementing the mutual information selection to reproduce the FilterbankCSP (#787 by Bruno Aristimunha)
A tutorial on how to create and use a MOABB dataset from X y (non continuous, epoched) data (#800 by Anton Andreev)
Improving the parallel writing of results (#803 by Bruno Aristimunha)
Bugs#
Fix regression in evaluations ignoring
process_pipelineflag (#774 by Bruno Aristimunha)Fix caching issue with incomplete results (#715 by Sylvain Chevallier)
Fix learning curve example (#717 by Pierre Guetschel)
Pick all data channels in filter preprocessing step (#729 by Pierre Guetschel)
Fix CI for permutation testing (#757 by Quentin Barthelemy)
Fix download issue with Schirrmeister2017 dataset (#751 by Zheyu Yao)
Fix code carbon example code (#777 by Amar Enkhbat)
Including the fix_bad_channels for the
moabb.datasets.Stieger2021(#783 by Bruno Aristimunha)Fix the
moabb.datasets.Wang2016(#781 by Ulysse Durand)Fix warnings raised when building the documentation (#784 by Lucas Heck)
Remove an unnecessary line in the README.md (#791 by Lionel Kusch)
Update the dead link about the tutorial of GitHub in CONTRIBUTING.md (#792 by Lionel Kusch)
Fix: number of trial per class for PHMD_ML dataset (#797 by Gregoire Cattan)
Converting the
moabb.datasets.Zhou2016to BIDS (#802 by Bruno Aristimunha)
API changes#
Removing the deep learning module from inside moabb in favour of braindecode integration (#692 by Bruno Aristimunha )
Version - 1.2.0#
Enhancements#
Adding
moabb.evaluations.splitters.WithinSessionSplitter(#664 by Bruna Lopes_)Update version of pyRiemann to 0.7 (#671 by Gregoire Cattan)
Add columns definitions in the datasets doc (#672 by Pierre Guetschel)
Add ERP CORE datasets
moabb.datasets.erpcore2021.ErpCore2021dataset (#627 by Taha Habib)Update paths of BIDS cache to better follow the standards. Cache created in previous MOABB versions should still be compatible (#707 by Pierre Guetschel)
Bugs#
Fix Stieger2021 dataset bugs (#651 by Martin Wimpff)
Unpinning major version Scikit-learn and numpy (#652 by Bruno Aristimunha)
Replacing the func:numpy.string_ to func:numpy.bytes_ (#665 by Bruno Aristimunha)
Fixing the set_download_dir that was not working when we tried to set the dir more than 10 times at the same time (#668 by Bruno Aristimunha)
Creating stimulus channels in
moabb.datasets.Zhou2016andmoabb.datasets.PhysionetMIto allow braindecode compatibility (#669 by Bruno Aristimunha)Improving the CI (#686 by Bruno Aristimunha)
Making the download test work again (#693 by Bruno Aristimunha)
Fix the EpochSelectChannel that caused incorrect channel selection in example (#685 by AFF)
Fixing the logger on the Stieger2021 and Wang2016 dataset (#693 by Bruno Aristimunha)
Change the way of creating the path to the folder (#697 by Sebastien Velut)
Fixing bug with braindecode and moabb datasets EPFLP300 (#696 by Bruno Aristimunha)
Fixing the dataset details for bids conversion (#698 by Bruno Aristimunha)
Fixing unit issue and lack of montage with
moabb.datasets.Rodrigues2017,moabb.datasets.Rodrigues2017,moabb.datasets.castillos2023.BaseCastillos2023,moabb.datasets.castillos2023.BaseCastillos2023,moabb.datasets.Huebner2018,moabb.datasets.Cattan2019_PHMD,moabb.datasets.Ofner2017(#700 Bruno Aristimunha)Fix t-test permutation tests (#684 and #709 by Gregoire Cattan, Anton Andreev, Marco Congedo and Bruno Aristimunha)
API changes#
Removing the braindecode module from inside moabb (#666 by Bruno Aristimunha )
Version - 1.1.1#
Enhancements#
Add possibility to use OptunaGridSearch (#630 by Igor Carrara)
Add scripts to upload results on PapersWithCode (#561 by Pierre Guetschel)
Centralize dataset summary tables in CSV files (#635 by Pierre Guetschel)
Add new dataset
moabb.datasets.Liu2024dataset (#619 by Taha Habib)Add choice to choose the size of time window (by Sebastien Velut)
Bugs#
Fix caching in the workflows (#632 by Pierre Guetschel)
API changes#
Include optuna as soft-dependency in the benchmark function and in the base of evaluation (#630 by Igor Carrara)
Version - 1.1.0#
Enhancements#
Add cache option to the evaluation (#518 by Bruno Aristimunha)
Option to interpolate channel in paradigms’ match_all method (#480 by Gregoire Cattan)
Add leave k-Subjects out evaluations (#470 by Bruno Aristimunha)
Update Braindecode dependency to 0.8 (#542 by Pierre Guetschel)
Improve transform function of AugmentedDataset (#541 by Quentin Barthelemy)
Add new paper results website (#556 by Bruno Aristimunha)
Move cVEP common functions to
moabb.datasets.utils(#564 #557 by Pierre Guetschel)Normalize c-VEP description tables (#562 #566 by Pierre Guetschel and Bruno Aristimunha)
Update citation in README (#573 by Igor Carrara)
Update pyRiemann dependency (#577 by Gregoire Cattan)
Add resting stage Hinss2021 dataset (#580 by Gregoire Cattan and Yash Chauhan)
Expose the learning rate parameter in the keras deep learning methods and optimize parameters (#589 and #592 by Bruno Aristimunha)
Updating the braindecode pipelines for the new braindecode version 0.8.1 (#589 by Bruno Aristimunha)
Add SSVEP and ERP paradigms to DL pipelines (#590 by Pierre Guetschel)
Allow to pass a single pipeline file to
benchmark(#591 by Pierre Guetschel)Add new dataset
moabb.datasets.Stieger2021(#604 by Reinmar Kobler and Bruno Aristimunha)Exposing the drop_rate for all the deep learning parameters (#592 by Bruno Aristimunha)
Add new dataset
moabb.datasets.Rodrigues2017dataset (#602 by Gregoire Cattan and Pedro L. C. Rodrigues)Change unittest to pytest (#618 by Bruno Aristimunha)
Remove tensorflow import warning (#622 by Bruno Aristimunha)
Bugs#
Fix TRCA implementation for different stimulation freqs and for signal filtering (:gh:522 by Sylvain Chevallier)
Fix saving to BIDS runs with a description string in their name (#530 by Pierre Guetschel)
Fix import of keras BatchNormalization for TF 2.13 and higher (#544 by Brian Irvine)
Fix the doc summary tables of
moabb.datasets.Lee2019_SSVEP(#548 #547 #546 by Pierre Guetschel)Fix the doc summary for Castillos2023 dataset (#561 by Bruno Aristimunha)
Fix format string receiving incorrect number of args in bids interface (#563 by Pierre Guetschel)
Fix number of sessions in doc of
moabb.datasets.Sosulski2019(#565 by Pierre Guetschel)Fix code column of
moabb.datasets.CastillosCVEP100andmoabb.datasets.CastillosCVEP100(#567 by Pierre Guetschel)MAINT updating the packages pre-release (#578 by Bruno Aristimunha)
Fix mne_bids version incompatibility with mne (#586 by Bruna Lopes)
Updating the parameters of the SSVEP_TRCA method (#589 by Bruno Aristimunha)
Fix and updating the parameters for the benchmark function (#588 by Bruno Aristimunha)
Fix result table display (#599 by Sylvain Chevallier)
Fix
SetRawAnnotationssetting incorrect annotations when the dataset’s interval does not start at 0 (#607 by Pierre Guetschel)Fix download link for GigaDB Cho2017 and Lee2019 datasets (#621 by Anton Andreev)
API changes#
None
Version - 1.0.0#
Enhancements#
Adding extra thank you section in the documentation (#390 by Bruno Aristimunha)
Adding new script to get the meta information of the datasets (#389 by Bruno Aristimunha)
Fixing the dataset description based on the meta information (#389 and 398 by Bruno Aristimunha and Sara Sedlar)
Adding second deployment of the documentation (#374 by Bruno Aristimunha)
Adding Parallel evaluation for
moabb.evaluations.WithinSessionEvaluation(),moabb.evaluations.CrossSessionEvaluation()(#364 by Bruno Aristimunha)Add example with VirtualReality BrainInvaders dataset (#393 by Gregoire Cattan and Pedro L. C. Rodrigues)
Adding saving option for the models (#401 by Bruno Aristimunha and Igor Carrara)
Adding example to load different type of models (#401 by Bruno Aristimunha and Igor Carrara)
Add resting state paradigm with dataset and example (#400 by Gregoire Cattan and Pedro L. C. Rodrigues)
Speeding the augmentation method by 400% with NumPy vectorization (#419 by Bruno Aristimunha)
Add possibility to convert datasets to BIDS, plus example (PR #408, PR #391 by Pierre Guetschel and Bruno Aristimunha)
Allow caching intermediate processing steps on disk, plus example (PR #408, issue #385 by Pierre Guetschel)
Restructure the paradigms and datasets to move all preprocessing steps to
moabb.datasets.preprocessingand as sklearn pipelines (PR #408 by Pierre Guetschel)Add
moabb.paradigms.FixedIntervalWindowsProcessing()andmoabb.paradigms.FilterBankFixedIntervalWindowsProcessing(), plus example (PR #408, issue #424 by Pierre Guetschel)Define
moabb.paradigms.base.BaseProcessing(), common parent tomoabb.paradigms.base.BaseParadigm()andmoabb.paradigms.BaseFixedIntervalWindowsProcessing()(PR #408 by Pierre Guetschel)Allow passing a fixed processing pipeline to
moabb.paradigms.base.BaseProcessing.get_data()and cache its result on disk (PR #408, issue #367 by Pierre Guetschel)Update
moabb.datasets.fake.FakeDataset()’s code to be unique for each parameter combination (PR #408 by Pierre Guetschel)Systematically set the annotations when loading data, eventually using the stim channel (PR #408 by Pierre Guetschel)
Allow
moabb.datasets.utils.dataset_search()to search across paradigmsparadigm=None(PR #408 by Pierre Guetschel)Improving the review processing with more pre-commit bots (#435 by Bruno Aristimunha)
Add methods
make_processing_pipelinesandmake_labels_pipelinetomoabb.paradigms.base.BaseProcessing(#447 by Pierre Guetschel)Pipelines’ digests are now computed from the whole processing+classification pipeline (#447 by Pierre Guetschel)
Update all dataset codes to remove white spaces and underscores (#448 by Pierre Guetschel)
Add
moabb.utils.depreciated_aliasdecorator (#455 by Pierre Guetschel)Rename many dataset class names to standardize and deprecate old names (#455 by Pierre Guetschel)
Change many dataset codes to match the class names (#455 by Pierre Guetschel)
Add
moabb.datasets.compound_dataset.utils.compound_dataset_list(#455 by Pierre Guetschel)Add c-VEP paradigm and Thielen2021 c-VEP dataset (#463 by Jordy Thielen)
Add option to plot scores vertically. (#417 by Sara Sedlar)
Change naming scheme for runs and sessions to align to BIDS standard (#471 by Pierre Guetschel)
Increase the python version to 3.11 (#470 by Bruno Aristimunha)
Add match_all method in paradigm to support CompoundDataset evaluation with MNE epochs (#473 by Gregoire Cattan)
Automate setting of event_id in compound dataset and add data_origin information to the data (#475 by Gregoire Cattan)
Add possibility of not saving the model (#489 by Igor Carrara)
Add CVEP and BurstVEP dataset from Castillos from Toulouse lab (#531 by Sebastien Velut)
Add c-VEP dataset from Thielen et al. 2015 (#557 by Jordy Thielen)
Bugs#
Restore 3 subject from Cho2017 (#392 by Igor Carrara and Sylvain Chevallier)
Correct downloading with VirtualReality BrainInvaders dataset (#393 by Gregoire Cattan)
Rename event subtraction in
moabb.datasets.Shin2017B()(#397 by Pierre Guetschel)Save parameters of
moabb.datasets.PhysionetMI()(#403 by Pierre Guetschel)Fixing issue with parallel evaluation (#401 by Bruno Aristimunha and Igor Carrara)
Fixing SSLError from BCI competition IV (#404 by Bruno Aristimunha)
Fixing
moabb.datasets.bnci.MNEBNCI.data_paththat returned the data itself instead of paths (#412 by Pierre Guetschel)Adding
moabb.datasets.fakein the init file to use in braindecode object (#414 by Bruno Aristimunha)Fixing the parallel download issue when the dataset have the same directory (#421 by Sara Sedlar)
Fixing fixes the problem with the annotation loading for the P300 datasets Sosulski2019, Huebner2017 and Huebner2018 (#396 by Sara Sedlar)
Removing the print in the dataset list (#423 by Bruno Aristimunha)
Fixing bug in
moabb.pipelines.utils_pytorch.BraindecodeDatasetLoaderwhere incorrect y was used in transform calls (#426 by Gabriel Schwartz)Fixing one test in
moabb.pipelines.utils_pytorch.BraindecodeDatasetLoader(#426 by Bruno Aristimunha)Fix
moabb.benchmark()overwritinginclude_datasetslist (#408 by Pierre Guetschel)Fix
moabb.paradigms.base.BaseParadigm()using attributes before defining them (PR #408, issue #425 by Pierre Guetschel)Fix
FakeImageryParadigm,FakeP300ParadigmandFakeSSVEPParadigmis_validmethods to only accept the correct datasets (PR #408 by Pierre Guetschel)Fix
dataset_listconstruction, which could be empty due to bad import order (PR #449 by Thomas Moreau).Fixing dataset downloader from servers with non-http (PR #433 by Sara Sedlar)
Fix
dataset_listto include deprecated datasets (PR #464 by Bruno Aristimunha)Fixed bug in
moabb.analysis.results.get_string_repto handle addresses such as 0x__0A as well (PR #468 by Anton Andreev)Moving the
moabb.evaluations.grid_searchto inside the base evaluation (#487 by Bruno Aristimunha)Removing joblib Parallel (#488 by Igor Carrara)
Fix case when events specified via
raw.annotationsbut no events (#491 by Pierre Guetschel)Fix bug in downloading Shin2017A dataset (#493 by Igor Carrara)
Fix the cropped option in the dataset preprocessing (#502 by Bruno Aristimunha)
Fix bug in
moabb.datasets.utils.dataset_search()with missing cvep paradigm (#557 by Jordy Thielen)Fix mistakes in
moabb.datasets.thielen2021considering wrong docs and hardcoded trial stim channel (#557 by Jordy Thielen)
API changes#
None
Version - 0.5.0#
Enhancements#
Speeding the augmentation model (#365 by Bruno Aristimunha)
Add VirtualReality BrainInvaders dataset (#358 by Gregoire Cattan)
Switch to python-3.8, update dependencies, fix code link in doc, add code coverage (#315 by Sylvain Chevallier)
Adding a comprehensive benchmarking function (#264 by Divyesh Narayanan and Sylvain Chevallier)
Add meta-information for datasets in documentation (#317 by Bruno Aristimunha)
Add GridSearchCV for different evaluation procedure (#319 by Igor Carrara)
Add new tutorial to benchmark with GridSearchCV (#323 by Igor Carrara)
Add six deep learning models (Tensorflow), and build a tutorial to show to use the deep learning model (#326 by Igor Carrara, Bruno Aristimunha and Sylvain Chevallier)
Add a augmentation model to the pipeline (#326 by Igor Carrara)
Add BrainDecode example (#340 by Igor Carrara and Bruno Aristimunha)
Add Google Analytics to the documentation (#335 by Bruno Aristimunha)
Add support to Braindecode classifier (#328 by Bruno Aristimunha)
Add CodeCarbon to track emission CO₂ (#350 by Igor Carrara, Bruno Aristimunha and Sylvain Chevallier)
Add CodeCarbon example (#356 by Igor Carrara and Bruno Aristimunha)
Add MsetCCA method for SSVEP classification, parametrise CCA n_components in CCA based methods (#359 by Emmanuel Kalunga and Sylvain Chevallier)
Set epochs’ metadata field in get_data (#371 by Pierre Guetschel)
Add possibility to use transformers to apply fixed pre-processings before evaluations (#372 by Pierre Guetschel)
Add seed parameter to FakeDataset (#372 by Pierre Guetschel)
Bugs#
Fix circular import with braindecode (#363 by Bruno Aristimunha)
Fix bug for MotorImagery when we handle all events (#327 by Igor Carrara)
Fixing CI to handle with new deep learning dependencies (#332 and #326 by Igor Carrara, Bruno Aristimunha and Sylvain Chevallier)
Correct CI error due to isort (#330 by Bruno Aristimunha)
Restricting Python <= 3.11 version and adding tensorflow, keras, scikeras, braindecode, skorch and torch, as optional dependence (#329 by Bruno Aristimunha)
Fix numpy variable to handle with the new version of python (#324 by Bruno Aristimunha)
Correct CI error due to black (#292 by Sylvain Chevallier)
Preload Schirrmeister2017 raw files (#290 by Pierre Guetschel)
Incorrect event assignation for Lee2019 in MNE >= 1.0.0 (#298 by Sylvain Chevallier)
Correct usage of name simplification function in analyze (#306 by Divyesh Narayanan)
Fix downloading path issue for Weibo2014 and Zhou2016, numy error in DemonsP300 (#315 by Sylvain Chevallier)
Fix unzip error for Huebner2017 and Huebner2018 (#318 by Sylvain Chevallier)
Fix n_classes when events set to None (#337 by Igor Carrara and Sylvain Chevallier)
Change n_jobs=-1 to self.n_jobs in GridSearch (#344 by Igor Carrara)
Fix dropped epochs issue (#371 by Pierre Guetschel)
Fix redundancy website issue (#372 by Bruno Aristimunha)
API changes#
None
Version - 0.4.6#
Enhancements#
Add P300 BrainInvaders datasets (#283 by Sylvain Chevallier)
Add explicit warning when lambda function are used to parametrize pipelines (#278 by Jan Sosulski)
Bugs#
Correct default path for ERP visualization (#279 by Jan Sosulski)
Correct documentation (#282 and #284 by Jan Sosulski)
Version - 0.4.5#
Enhancements#
Progress bars, pooch, tqdm (#258 by Divyesh Narayanan and Sylvain Chevallier)
Adding test and example for set_download_dir (#249 by Divyesh Narayanan)
Update to newer version of Schirrmeister2017 dataset (#265 by Robin Schirrmeister)
Adding Huebner2017 and Huebner2018 P300 datasets (#260 by Jan Sosulski)
Adding Sosulski2019 auditory P300 datasets (#266 by Jan Sosulski)
New script to visualize ERP on all datasets, as a sanity check (#261 by Jan Sosulski)
Bugs#
Removing dependency on mne method for PhysionetMI data downloading, renaming runs (#257 by Divyesh Narayanan)
Correcting events management in Schirrmeister2017, renaming session and run (#255 by Pierre Guetschel and Sylvain Chevallier)
Switch session and runs in MAMEM1, 2 and 3 to avoid error in WithinSessionEvaluation (#256 by Sylvain Chevallier)
Correct doctstrings for the documentation, including Lee2017 (#256 by Sylvain Chevallier)
Version - 0.4.4#
Enhancements#
Add TRCA algorithm for SSVEP (#238 by Ludovic Darmet)
Bugs#
Remove unused argument from dataset_search (#243 by Divyesh Narayanan)
Remove MNE call to _fetch_dataset and use MOABB _fetch_file (#235 by Jan Sosulski)
Correct doc formatting (#232 by Sylvain Chevallier)
API changes#
Minimum supported Python version is now 3.7
MOABB now depends on scikit-learn >= 1.0
Version - 0.4.3#
Enhancements#
Rewrite Lee2019 to add P300 and SSVEP datasets (#217 by Pierre Guetschel)
Bugs#
Avoid information leakage for MNE Epochs pipelines in evaluation (#222 by Sylvain Chevallier)
Correct error in set_download_dir (#225 by Sylvain Chevallier)
Ensure that channel order is consistent across dataset when channel argument is specified in paradigm (#229 by Sylvain Chevallier)
API changes#
ch_names argument added to init of moabb.datasets.fake.FakeDataset (#229 by Sylvain Chevallier)
Version - 0.4.2#
Enhancements#
None
Bugs#
Correct error when downloading Weibo dataset (#212 by Sylvain Chevallier)
API changes#
None
Version - 0.4.1#
Enhancements#
None
Bugs#
Correct path error for first time launch (#204 by Sylvain Chevallier)
Fix optional dependencies issues for PyPi (#205 by Sylvain Chevallier)
API changes#
Remove update_path on all datasets, update_path parameter in dataset.data_path() is deprecated (#207 by Sylvain Chevallier)
Version - 0.4.0#
Enhancements#
Implementation for learning curves (#155 by Jan Sosulski)
Adding Neiry Demons P300 dataset (#156 by Vladislav Goncharenko)
Coloredlogs removal (#163 by Vladislav Goncharenko)
Update for README (#164 by Vladislav Goncharenko and Sylvain Chevallier)
Test all relevant python versions in Github Actions CI (#167 by Vladislav Goncharenko)
Adding motor imagery part of the Lee2019 dataset (#170 by Ali Abdul Hussain)
CI: deploy docs from CI pipeline (#124 by Erik Bjäreholt, Divyesh Narayanan and Sylvain Chevallier)
Remove dependencies: WFDB and pyunpack (#180 and #188 by Sylvain Chevallier)
Add support for FigShare API (#188 by Sylvain Chevallier)
New download backend function relying on Pooch, handling FTP, HTTP and HTTPS (#188 by Sylvain Chevallier)
Complete rework of examples and tutorial (#188 by Sylvain Chevallier)
Change default storage location for results: instead of moabb source code directory it is now stored in mne_data (#188 by Sylvain Chevallier)
Major update of test (#188 by Sylvain Chevallier)
Adding troubleshooting and badges in README (#189 by Jan Sosulski and Sylvain Chevallier)
Use MNE epoch in evaluation (#192 by Sylvain Chevallier)
Allow changing of storage location (#192 by Divyesh Narayanan and Sylvain Chevallier)
Deploy docs on moabb.github.io (#196 by Sylvain Chevallier)
Broadening subject_list type for
moabb.datasets.base.BaseDataset(#198 by Sylvain Chevallier)Adding this what’s new (#200 by Sylvain Chevallier)
Improving cache usage and save computation time in CI (#200 by Sylvain Chevallier)
Rewrite Lee2019 to add P300 and SSVEP datasets (#217 by Pierre Guetschel)
Bugs#
Restore basic logging (#177 by Jan Sosulski)
Correct wrong type of results dataframe columns (#188 by Sylvain Chevallier)
Add
acceptarg to acknowledge licence formoabb.datasets.Shin2017A()andmoabb.datasets.Shin2017B()(#201 by Sylvain Chevallier)
API changes#
Drop update_path from moabb.download.data_path and moabb.download.data_dl
Version 0.3.0#
Enhancements#
Expose sklearn error_score parameter (#70 by Jan Sosulski)
Adds a
unit_factorattribute to base_paradigms (#72 by Jan Sosulski)Allow event lists in P300 paradigm (#83 by Jan Sosulski)
Return epochs instead of np.ndarray in process_raw (#86 by Jan Sosulski)
Set path for hdf5 files (#92 by Jan Sosulski)
Update to MNE 0.21 (#101 by Ramiro Gatti and Sylvain Chevallier)
Adding a baseline correction (#115 by Ramiro Gatti)
Adding SSVEP datasets: MAMEM1, MAMEM2, MAMEM3, Nakanishi2015, Wang2016, (#118 by Sylvain Chevallier, Quentin Barthelemy, and Divyesh Narayanan)
Switch to GitHub Actions (#124 by Erik Bjäreholt)
Allow recording of additional scores/parameters/metrics in evaluation (#127 and #128 by Jan Sosulski)
Fix Ofner2017 and PhysionetMI annotations (#135 by Ali Abdul Hussain)
Adding Graz workshop tutorials (#130 and #137 by Sylvain Chevallier and Lucas Custódio)
Adding pre-commit configuration using isort, black and flake8 (#140 by Vladislav Goncharenko)
style: format Python code with black (#147 by Erik Bjäreholt)
Switching to Poetry dependency management (#150 by Vladislav Goncharenko)
Using Prettier to format md and yml files (#151 by Vladislav Goncharenko)
Bugs#
Use stim_channels or check annotation when loading files in Paradigm (#72 by Jan Sosulski)
Correct MNE issues (#76 by Sylvain Chevallier)
Fix capitalization in channel names of cho dataset (#90 by Jan Sosulski)
Correct failing CI tests (#100 by Sylvain Chevallier)
Fix EPFL dataset flat signal sections and wrong scaling (#104 and #96 by Jan Sosulski)
Fix schirrmeister dataset for Python3.8 (#105 by Robin Schirrmeister)
Correct event detection problem and duplicate event error (#106 by Sylvain Chevallier)
Fix channel selection in paradigm (#108 by Sylvain Chevallier)
Fix upperlimb Ofner2017 error and gdf import problem (#111 and #110 by Sylvain Chevallier)
Fix event_id in events_from_annotations missed (#112 by Ramiro Gatti)
Fix h5py>=3.0 compatibility issue (#138 by Mohammad Mostafa Farzan)
Python 2 support removal (#148 by Vladislav Goncharenko)
Travis-ci config removal (#149 by Vladislav Goncharenko)
API changes#
None
Version 0.2.1#
Enhancements#
Add Tikhonov regularized CSP in
moabb.pipelines.cspfrom the paper (#60 by Vinay Jayaram)update to MNE version 0.19 (#73 by Jan Sosulski)
Improve doc building in CI (#60 by Sylvain Chevallier)
Bugs#
Update GigaDB Cho2017 URL (Pedro L. C. Rodrigues and Vinay Jayaram)
Fix braininvaders ERP data (Pedro L. C. Rodrigues)
Replace MNE
read_montagewithmake_standard_montage(Jan Sosulski)Correct Flake and PEP8 error (Sylvain Chevallier)
API changes#
None
Version 0.2.0#
Enhancements#
MOABB corresponding to the paper version by Vinay Jayaram and Alexandre Barachant
Creating P300 paradigm and BNCI datasets (#53 by Pedro L. C. Rodrigues)
Adding EPFL P300 dataset (#56 by Pedro L. C. Rodrigues)
Adding BrainInvaders P300 dataset (#57 by Pedro L. C. Rodrigues)
Creating SSVEP paradigm and SSVEPExo dataset (#59 by Sylvain Chevallier)
Bugs#
None
API changes#
None