What’s new#

  • “Enhancements” for new features

  • “Bugs” for bug fixes

  • “API changes” for backward-incompatible changes

Version 1.7 (Source - GitHub)#

Enhancements#

  • None yet.

API changes#

  • None yet.

Requirements#

  • None yet.

Bugs#

  • None yet.

Code health#

  • None yet.

Version 1.6.1 (Stable - PyPi)#

Enhancements#

  • None.

API changes#

  • moabb.datasets.metadata.AcquisitionMetadata.n_channels is derived from channel_types instead 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 own sensors list and test_n_channels_matches_raw_data (which counts every non-stim channel) already assumed: moabb.datasets.Dreyer2023A, for instance, reported 27 against its own 32 sensors and now reports 32. Passing n_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 ran download.py, a file that does not exist, so it collected nothing and has been failing every month while no @pytest.mark.download test executed anywhere in CI (by Bruno Aristimunha).

  • Stop importing moabb from restyling the caller’s matplotlib. moabb.analysis.plotting applies a seaborn theme to the global rcParams when it is imported, and moabb/analysis/__init__.py imported it eagerly – so import moabb.datasets alone changed font.family, axes.grid and axes.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_interface imports moabb.analysis.results for get_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 explicit import moabb.analysis.plotting still applies the theme, leaving MOABB’s own figures unchanged (by Bruno Aristimunha).

  • Type the seven non-EEG channels of moabb.datasets.BNCI2025_001 as misc. read_raw_eeglab types anything it does not recognise as eeg, so x/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 declared n_channels (67) also disagreed with the dataset’s own 60-electrode montage; it is now 71 with channel_types={"eeg": 60, "eog": 4, "misc": 7}. Accuracies on this dataset will fall, which is the point (by Bruno Aristimunha).

  • Give moabb.datasets.Rodrigues2017 the montage #700 announced but never shipped, the same omission as moabb.datasets.Cattan2019_PHMD below: both share the 16-electrode setup, both spelled Fc5/Fc6 – the only two names standard_1020 cannot resolve – and neither loader called set_montage. Its METADATA also declared standard_1010, which is not a montage MNE can build (by Bruno Aristimunha).

  • Give moabb.datasets.Cattan2019_PHMD the montage #700 announced but never shipped: that PR fixed only the unit scaling, leaving the loader with no set_montage call at all. Its channel list also spelled the two frontal-central electrodes Fc5/Fc6, the only two of its sixteen names that standard_1020 cannot resolve; they are now FC5/FC6 (by Bruno Aristimunha).

  • Correct moabb.datasets.Cattan2019_PHMD interval from [0, 1] to [0, 60]. Each marker starts a one-minute relaxation block – as the dataset’s own block_duration_s=60.0 records – but SetRawAnnotations derives annotation durations from interval, 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 consults trust_existing, so a call with nothing to do is still four round-trips. Note that deposits whose manifest predates the subject field – 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_id for 15 datasets whose NEMAR deposits are now public, so moabb.set_download_provider() can serve them from NEMAR’s sourcedata/ 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.CrossSubjectEvaluation through an optional target-calibration slice. moabb.evaluations.splitters.CrossSubjectSplitter gains calibration_size (the fraction of each held-out subject/session pair set aside for adaptation, in [0, 1]) and calibration_labeled; when calibration_size > 0 each fold becomes (train, calibration, test). The calibration trials never enter the training fold - they are handed to the pipeline steps that opt in via set_fit_request (X_target_unlabeled, or X_target_labeled and y_target_labeled when calibration_labeled=True, plus the per-trial subjects array), so train, calibration and test stay trial-disjoint. Named presets are available through the new moabb.evaluations.CrossSubjectMode enum and the cs_mode argument, covering train-only, unlabeled target adaptation at 20 / 50 / 100 percent, and labeled target calibration at 20 / 50 percent; TRAIN_TRIALWISE additionally scores one target trial at a time, so a method cannot exploit statistics of the whole test block. Two examples are added under examples/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). A group filter 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 matching Movements/ 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, and moabb.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), and moabb.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.csv summary table, and a grouped Imagined Speech Datasets listing in the API reference. The new datasets continue to use the existing paradigm="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) and moabb.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 .mat files 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) and moabb.datasets.Nguyen2017_V/_S/_L/_SL (from Dropbox) to Zenodo records 10.5281/zenodo.19502780 and 10.5281/zenodo.19502794 so automated download works without gdown, 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.Pressel2016 with BAD_artifact annotations instead of silently dropping them at load time, so downstream reject_by_annotation pipelines 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_001 source artifact flags with optional artifact_handling annotation modes. SetRawAnnotations now keeps BAD_artifact/bnci_artifact (and any bad-prefixed) annotations when it re-derives event annotations, so per-trial artifact flags survive the full get_data pipeline (this also makes the moabb.datasets.Pressel2016 BAD_artifact markers effective). reject_by_annotation is now exposed on all epoching paradigms (motor imagery, P300, SSVEP, c-VEP, resting state), letting users choose whether BAD_artifact trials 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_imagery and mental_arithmetic keyword-only parameters on moabb.datasets.Shin2017A (default: MI=True, MA=False) and moabb.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.Lee2019 resting state runs for BIDS export compatibility (by Bruno Aristimunha)

  • Skip zip extraction in moabb.datasets.GuttmannFlury2025 when files are already extracted, with /scratch fallback 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 and method_combination support on PotatoField (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’s sourcedata/ — 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_data still runs each dataset’s own parser. Falls back to the dataset’s upstream downloader when a deposit publishes no sourcedata/ (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 under sourcedata/, keeping the upstream filenames, so NEMAR can now stand in for upstream hosts that are slow, rate-limited, behind a bot gate, or retired. Passing subject= fetches a single subject, resolved through the deposit’s sourcedata_provenance.json manifest (deposits enriched before that manifest recorded subjects fall back to the whole tree with a warning); without it the full sourcedata/ is downloaded, because that tree keeps the upstream layout rather than a BIDS one. Backed by moabb.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 the MOABB_DOWNLOAD_PROVIDER environment 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 (fit learns the reference from training trials, transform re-applies it to unseen trials); fit_transform gives the transductive, per-recording form. Accepts an mne.BaseEpochs or an (n_trials, n_channels, n_times) ndarray, uses a shrinkage covariance estimator ("lwf") for robustness, and adds no new dependency (pyriemann >= 0.11 is already required). Distinct from pyriemann.transfer.TLCenter, which recenters covariance matrices (#1108 by Bruno Aristimunha).

  • Add an n_jobs parameter to moabb.paradigms.base.BaseParadigm.get_data() and moabb.datasets.base.BaseDataset.get_data() to load and preprocess subjects in parallel with joblib.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 a groups argument — a metadata column name, a list of column names (compound key, e.g. ["subject", "session"]), or a callable metadata -> array — together with callable cv_kwargs resolved against the metadata (e.g. cv_class=PredefinedSplit with a test_fold callable to target a single fold). groups is exposed on moabb.evaluations.WithinSessionEvaluation, moabb.evaluations.WithinSubjectEvaluation, moabb.evaluations.CrossSessionEvaluation and moabb.evaluations.CrossSubjectEvaluation and threaded to their splitters; each splitter keeps its default grouping ("subject" / "session" / labels) when groups is None. moabb.evaluations.splitters.CrossDatasetSplitter gains groups (its group_column argument 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.PotatoField per-potato metrics and the method_combination parameter (pyRiemann PR #423) used by the Riemannian Artifact Rejection tutorial (by Bruno Aristimunha)

  • Bump minimum supported Python to 3.11 (required by mne-bids >= 0.18) (#1124 by Bruno Aristimunha)

  • Bump minimum mne-bids to 0.19 for 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 of mne_bids.dig._write_dig_bids (mne-bids >= 0.19 writes the *_electrodes.json SpatialReference sidecar itself) and derive the Keywords field via make_dataset_description(keywords=...) instead of a post-write patch (#1124 by Bruno Aristimunha)

Bugs#

  • Restore the final AttributeError of the moabb.pipelines module-level __getattr__, dropped in #692. Unknown names evaluated to None instead of raising, so from moabb.pipelines import * failed with TypeError: 'NoneType' object is not iterable and a mistyped class name in a pipeline YAML surfaced as TypeError: 'NoneType' object is not callable (#1159 by Iain)

  • Fix moabb.analysis.meta_analysis rejecting non-finite score inputs instead of letting NaN or infinite values leak into low-level statistics. moabb.analysis.meta_analysis.compute_pvals_wilcoxon(), moabb.analysis.meta_analysis.compute_pvals_perm(), and moabb.analysis.meta_analysis.compute_effect() now require finite score tables; the all-zero Wilcoxon case still reports a one-tailed p=0.5 in both directions and keeps off-diagonal p-values strictly inside (0, 1) for Stouffer’s method; moabb.analysis.meta_analysis.compute_effect() reports 0 for identical zero-spread pairs and signed infinity for constant nonzero offsets; and moabb.analysis.meta_analysis.compute_pvals_corrected_ttest() now rejects non-finite score differences and non-finite n_train/n_test instead of collapsing a non-finite statistic to p=0.5 (#678 by Aditya Singh)

  • Point moabb.datasets.Schrag2026Pediatric at Zenodo version 3.0 and correct its licence. Versions 1.0 and 2 registered CC-BY-ND-4.0 while the preprint stated CC-BY-4.0; the authors resolved that in version 3.0, whose Zenodo record registers CC-BY-4.0 – so the NoDerivatives term 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+LDA row of the benchmark results page (The largest EEG-based Benchmark for Open Science) linking to pipelines/LogVar_grid.yml, which is the Log Variance SVM grid pipeline and classifies with SVC. It now links to pipelines/LogVar.yml, the Log Variance LDA pipeline the row reports. The LogVariance+SVM row keeps LogVar_grid.yml and loses its #L7 fragment, which highlighted the shared LogVariance step rather than the SVC one (by Bhargav Kowshik).

  • Fix moabb.datasets.BNCI2022_001 epoching its instantaneous waypoint markers as 90-second trials: only trajectory_start is 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.Kaneshiro2015 from p300 to imagery so 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.RestingStateToP300Adapter to be constructed with defaults: events=None now means all of the dataset’s events, matching how every in-repo usage builds it (#1143 by Bruno Aristimunha)

  • Fix moabb.datasets.Lee2024 silently dropping 345 upstream files. data_path no 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 shared moabb.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 and param.mat are 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 optional pyxdf dependency, for all four XDF datasets: moabb.datasets.AguileraRodriguez2025, moabb.datasets.Chen2017SingleFlicker, moabb.datasets.Rozado2015 and moabb.datasets.Schrag2026Pediatric. The immediate defect: AguileraRodriguez2025’s recorder wrote a non-conforming XDF footer (float sample_count), which makes pyxdf 1.17.5 raise ValueError for every gamified session; the built-in reader ignores footers entirely. It returns pyxdf-shaped streams and applies the same per-segment linear dejittering to regular-rate streams, and applies pyxdf’s clock-offset synchronization (Huber-ADMM robust fit, ported, BSD-2-Clause) before dejittering, in pyxdf’s order. Validated against pyxdf on real recordings of each dataset: bit-identical loaded data for all four. The moabb[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 explicit download() call, so a plain get_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 policy download already 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 deprecated moabb.datasets.download.data_path(): pooch.retrieve treats its destination as a directory holding <md5(url)>-<basename>, and loaders such as moabb.datasets.Rodrigues2017’s os.listdir() it, so a store hit written as a plain file at the destination raised NotADirectoryError; 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_dl and the deprecated data_path now serve a requested file from the dataset’s NEMAR/<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 by get_data() therefore no longer re-contacts the upstream host – verified live against Schirrmeister2017, whose host is currently unreachable. The URL-derived trees remain as lookups so nothing already downloaded is fetched again; force_update still refetches upstream and pinning the provider to "upstream" opts loading out of the store (#1146 by Bruno Aristimunha)

  • Store moabb.datasets.ErpCore2021 as the single combined BIDS dataset it is, with components separated by the task- entity in one shared MNE-erpcore2021-data root, instead of seven standalone per-component BIDS datasets; likewise store Dreyer2023 in one shared MNE-dreyer2023-data root, 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 when use=False (previously a silent no-op with the default config), moabb.datasets.CompoundDataset forwards path/force_update/update_path/verbose to the wrapped dataset, data_path implementations that ignored path or force_update (14 datasets, including Kojima2024’s narrowed signature that made download() raise TypeError) now honor them, force_update also 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’s nemar_subject_template label, 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.Brandl2020 downloading 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 .mat names, 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 optional fname for 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.TrianaGuzman2024 and moabb.datasets.Chailloux2020 (#1136 by Bruno Aristimunha).

  • Fix moabb.datasets.Schirrmeister2017 re-downloading every recording. data_path moved each freshly fetched EDF out of the directory that moabb.datasets.download.data_dl() owns and into MNE-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_path now returns the path data_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.MotorImagery selects the metric from the number of classes, so two-class scenarios are scored with ROC-AUC rather than accuracy, and moabb.evaluations.WithinSessionEvaluation averages 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_DATA without persisting a redundant per-dataset mirror, and moabb.utils.set_download_dir() removes legacy MNE_DATASETS_<SIGN>_PATH entries that still mirror the previous shared location while preserving explicit overrides. moabb.datasets.RomaniBF2025ERP now uses the same path mechanism and honours path and force_update. Adds isolated regression coverage across every dataset (#1115 by Bruno Aristimunha).

  • Add a __repr__ to moabb.datasets.base.BaseDataset so datasets display by their code (e.g. BNCI2014-001) when printed, instead of the verbose default <...object at 0x...>. This declutters the output of print(paradigm.datasets) in the tutorials and of the paradigm and evaluation compatibility warnings (by Danae)

  • Add age_median field to moabb.datasets.metadata.schema.ParticipantMetadata and populate age_std / age_median / n_blocks metadata for moabb.datasets.Rodrigues2017 (Alphawaves), fixing a TypeError at import time (by Grace Xu)

  • Fix moabb.datasets.BNCI2014_001 descriptive METADATA, which had many fields copied from BCI Competition IV Data set 1. Correct n_subjects (4 → 9), n_classes (2 → 4), class_labels / events / imagery_tasks (now left_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 acquisition reference / ground / filters and the preprocessing band (0.5–100 Hz with a 50 Hz notch, no 100 Hz downsampling), and the dataset description so they all match the dataset’s own constructor and docstring (#1094 by YG-paaleee)

  • Fix moabb.datasets.BNCI2014_001 stimulus 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_epochs was called with onset_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. With interval=[-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 passes onset_sample=19 so 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.Lee2019 that caused silent data loss when filtering sessions, and improve session filtering in moabb.datasets.base.BaseDataset to 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, fix moabb.datasets.BNCI2016_002 KeyError in event mapping, handle lowercase trigger attribute in moabb.datasets.BNCI2022_001 .mat files, detect and re-download truncated files in moabb.datasets.Kaneshiro2015, add stim-channel annotations in moabb.datasets.Lee2024 for BIDS compatibility, convert µV to V in moabb.datasets.MartinezCagigal2023Checker and moabb.datasets.MartinezCagigal2023Pary to fix BDF physical range overflow, and handle alternate data key in moabb.datasets.Zuo2025 .mat files (by Bruno Aristimunha)

  • Fix moabb.datasets.Chang2025 BIDS conversion crash by gracefully skipping subjects with missing directories or .set files (by Bruno Aristimunha)

  • Fix moabb.datasets.GuttmannFlury2025 BIDS export OSError by 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 in moabb.datasets.Lee2019 resting state EMG channel handling (by Bruno Aristimunha)

  • Fix moabb.datasets.castillos2023.BaseCastillos2023 extraction check using wrong directory name (4Class-VEP instead of 4Class-CVEP), causing re-extraction on every call, and replace fragile rstrip path derivation with proper os.path manipulation (by Bruno Aristimunha)

  • Fix data path lookup in moabb.datasets.Forenzo2023 that makes MOABB unable to find the downloaded data (#1048 by Ethan Davis).

  • Fix wrong paper reference in moabb.datasets.Thielen2021 (associated_paper_doi pointed to the Ahmadi electrode-montage reference instead of the dataset’s primary publication), restore Radboud data-repository DOI as __init__.doi, and add regression test test_primary_paper_matches_dataset_code that validates every <Surname><Year> dataset against its cited primary paper (by Bruno Aristimunha)

  • Fix UnicodeEncodeError when the GBK codec fails on '\xef' in BIDS metadata export by explicitly setting encoding="utf-8" on file writes in bids_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.Wairagkar2018 dataset loader (#1066 by Barış Talar).

  • Fix EEG layout corruption in moabb.datasets.BNCI2020_002: the F-contiguous bciexp.data was 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_trial content in moabb.datasets.MartinezCagigal2023Checker and moabb.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 new moabb.datasets.utils.resolve_cvep_command_ids() helper), and the _trial_meta annotation extras gain a command_id key alongside trial_id (by Bruno Aristimunha).

  • Cache Figshare’s file listing in moabb.datasets.download.fs_get_file_list() (process-level lru_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; pass force_update=True to bypass both layers (by Bruno Aristimunha).

  • Fix Windows download path sanitization that changed absolute paths like C:\data into relative C-\data paths (#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.Forenzo2023 and moabb.datasets.GuttmannFlury2025_MI/_ME normalize Neuroscan ALL_CAPS labels and apply standard_1005 (CB1/CB2 kept as misc); moabb.datasets.Dreyer2023 falls back to standard_1005 when the BIDS archive ships no electrodes.tsv; moabb.datasets.BNCI2003_004 maps its 26 legacy Berlin channel labels to their modern 10-5 equivalents for exact positions; moabb.datasets.BNCI2014_002 applies an approximate 3x5 grid for its unlabeled small-Laplacian channels; and moabb.datasets.Zhang2017 applies the GSN-HydroCel-32 montage in EGI sensor order. Adds the shared moabb.datasets.utils.set_neuroscan_montage() helper (#1089 by Bruno Aristimunha).

  • Fix BaseEvaluation._aggregate_fold_results aborting the whole evaluation with TypeError: agg function failed [how->mean,dtype->object] when a single fold contributes a non-numeric score (e.g. an error fold). The numeric aggregation columns are now coerced with pandas.to_numeric(errors="coerce") before groupby.agg, so a bad fold becomes NaN and is skipped instead of taking down every subject/pipeline (#1095 by Bruno Aristimunha).

  • Fix moabb.evaluations.splitters.WithinSessionSplitter and moabb.evaluations.splitters.WithinSubjectSplitter overwriting an explicit n_splits passed through cv_kwargs with the n_folds default; the caller-provided n_splits now takes precedence, so a single holdout split can be requested directly via cv_class=StratifiedShuffleSplit, n_splits=1. moabb.evaluations.WithinSessionEvaluation and moabb.evaluations.WithinSubjectEvaluation now honour the n_splits argument instead of always running 5 folds, and moabb.evaluations.splitters.WithinSubjectSplitter now yields reproducible per-subject folds for a fixed random_state (#1106 by Bruno Aristimunha).

  • Evaluations now apply cv_kwargs to 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 varies sentinel (e.g. Total_trials) were auto-detected as strings by DataTables and sorted lexicographically (11000 < 1114 < 11496). A custom num-varies column type now treats such columns as numeric, sorting sentinel rows last while keeping their displayed text unchanged (#1118 by Bhargav Kowshik).

  • Fix make html crash in scripts/generate_macro_table.py when a dataset has a missing (NaN) value in an optional metadata column (country, DOI, data URL, …): the float NaN is truthy, so it slipped past the if not value guards and crashed the string formatters (TypeError: object of type 'float' has no len()). _format_cell now normalizes NaN to None before dispatching, and _dataset_link/_paradigm_tag – the only two format branches without an empty-value guard, which raised AttributeError on html.escape(None) – guard it too, so all eleven branches render a missing cell as empty (#1117 by Bhargav Kowshik).

Code health#

  • Point MANIFEST.in at the files the repository actually ships. Three of its six directives named README.rst, LICENSE.txt and NOTICE.txt; the repository has README.md and LICENSE, and no notice file at all, so every python -m build --sdist printed three warning: no files found matching ... lines. The sdist contents are unchanged – setuptools already picked up the readme and the license through project.readme and its default license-files – only the warnings go away (by Bhargav Kowshik).

  • Bump the ruff-pre-commit hook from v0.15.9 to v0.15.20 (quarterly pre-commit.ci autoupdate). The bump is lint-neutral on the current tree: ruff check and ruff format --check return identical results at both pins across all 229 tracked *.py / *.pyi files, and pre-commit run --all-files rewrites no file (#1119 by pre-commit-ci).

  • Fix deprecated pyriemann.utils.{mean,covariance,base} import paths: bump the minimum pyriemann to 0.12 and update all import sites in moabb/pipelines/csp.py, moabb/pipelines/classification.py, moabb/datasets/preprocessing.py, and the Riemannian artifact rejection example to use pyriemann.geometry.* (introduced in pyriemann 0.12, removal of the old paths scheduled for 0.14), and import Potato/PotatoField from pyriemann.artifact_detection (moved from pyriemann.clustering in 0.12) (by copilot-swe-agent).

  • Install CPU-only PyTorch wheels in CI by setting UV_TORCH_BACKEND=cpu in the test, braindecode, and docs workflows, so runners no longer download multi-GB CUDA builds of torch (pulled transitively via the deeplearning extra / braindecode) (#1083 by Bhargav Kowshik).

  • Fix moabb.evaluations.splitters.LearningCurveSplitter not randomising its training subsample when groups are passed, which covers every learning curve run through moabb.evaluations.CrossSessionEvaluation or moabb.evaluations.CrossSubjectEvaluation. _get_data_size_subsets takes a prefix of the training indices, which is only a random draw because StratifiedShuffleSplit shuffles them; GroupShuffleSplit returns 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#

API changes#

  • Removed SinglePass and FilterBank intermediate classes from motor imagery and P300 paradigms. FilterBankLeftRightImagery now inherits from LeftRightImagery, FilterBankMotorImagery inherits from MotorImagery, and P300 inherits directly from BaseP300. RestingStateToP300Adapter now inherits from BaseP300. Docstring inheritance is handled via NumpyDocstringInheritanceInitMeta (#467 by Bruno Aristimunha).

  • Allow CodeCarbon script level configurations when instantiating a moabb.evaluations.base.BaseEvaluation child 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.BaseEvaluation child class (#956 by Ethan Davis)

  • Renamed stimulus channel from stim to STI in 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), and moabb.datasets.BNCI2015_013 (ErrP) (by Bruno Aristimunha).

  • Removed data_size and n_perms parameters from moabb.evaluations.WithinSessionEvaluation. Use cv_class=LearningCurveSplitter with cv_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 filelock as a core dependency to fix missing import errors in utils (#959 by Mateusz Naklicki).

  • Switch pyriemann dependency from GitHub source back to PyPI (>=0.7). The Riemannian Artifact Rejection tutorial still requires pyriemann from source for PotatoField features and is no longer auto-executed during doc builds (#1011 by Bruno Aristimunha)

  • Add type hints to moabb.evaluations.base.BaseEvaluation and all concrete evaluation classes (#732 by Sarthak Tayal)

  • Add plotly>=5.18.0 as optional interactive dependency (pip install moabb[interactive]), included in moabb[all] (#1039 by Bruno Aristimunha)

Bugs#

  • Fix trial acceptance condition in moabb.datasets.Stieger2021 that 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., NonTarget vs non-target) and use the first valid dataset subject in generated quickstart snippets instead of hardcoded subjects=[1] (#1000 by Bruno Aristimunha)

  • Fix missing P300 from the list of valid paradigms in the moabb.benchmark() docstring (by Bruno Aristimunha)

  • Fix critical trigger alignment bug in moabb.datasets.Liu2024 where create_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 in encoding() 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_TRCA and moabb.pipelines.classification.SSVEP_MsetCCA behavior (#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.Zhou2016 subject 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 ArrowStringArray shuffle 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.Huebner2017 when regex match on vhdr filenames returns None (#1036 by Sarthak Tayal)

  • Replace production assert statements with proper ValueError / TypeError exceptions across analysis, pipelines, paradigms, and datasets modules (#1036 by Sarthak Tayal)

  • Fix silent pipeline name collision in moabb.pipelines.utils.create_pipeline_from_config by raising ValueError on duplicate names (#1036 by Sarthak Tayal)

  • Replace bare print() calls with proper logging in moabb.datasets.MartinezCagigal2023Checker (#1036 by Sarthak Tayal)

  • LearningCurveSplitter now skips training splits that collapse to a single class (e.g., with very small data_size) and emits a RuntimeWarning instead 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_A and Beetl2021_B 403 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.RomaniBF2025ERP to follow MOABB nomenclature pattern by using dynamic folder name MNE-{code}-data instead of hardcoded folder name. Automatically migrates legacy folder BrainForm-BIDS-eeg-dataset to 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 as code/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() in BIDSInterfaceBase to handle multi-session datasets correctly by using per-session rm() calls instead of a single subject-level call, which previously caused a RuntimeError when looking up scans.tsv across multiple sessions (#986 by Pierre Guetschel and Bruno Aristimunha)

  • Fix MOABB_RESULTS default path to respect MNE_DATA configuration instead of hardcoding ~/mne_data, and fix docs CI cache to use workspace-relative MNE_DATA path and cache ~/.mne config directory (by Bruno Aristimunha)

  • Fix moabb.datasets.RomaniBF2025ERP get_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_DATA env var and persist ~/.mne config 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.Liu2024 download failure by switching Figshare URLs from figshare.com/ndownloader to ndownloader.figshare.com and adding BadZipFile recovery for corrupted cached downloads (#992 by Bruno Aristimunha)

  • Remove redundant autoattribute METADATA from 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, eliminating Convergence not reached and invalid value encountered in log warnings (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, and moabb.pipelines.classification.SSVEP_eCCA now return predictions in the same label space as classes_ and align predict_proba columns with classes_ order. moabb.pipelines.classification.SSVEP_CCA and moabb.pipelines.classification.SSVEP_eCCA now infer frequencies robustly from epochs metadata (with freq_map override), and moabb.pipelines.classification.SSVEP_eCCA now uses the corrected 4-feature filter assignments with updated reference/citation alignment (by Bruno Aristimunha)

  • Add documentation note to moabb.datasets.PhysionetMI that 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_bids to generate electrodes.json sidecar with SpatialReference key required by BIDS validator v2.4.0 when space-CapTrak entity is present (by Bruno Aristimunha)

  • Fix HardwareFilters BIDS 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 doi field in dataset_description.json to use BIDS-required doi:<value> format by adding the doi: prefix when missing (by Bruno Aristimunha)

  • Fix write_raw_bids overwrite error for multi-session datasets by detecting when a subject already exists in participants.tsv and setting overwrite=True for subsequent sessions (by Bruno Aristimunha)

  • Fix moabb.datasets.Ofner2017 generic 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.Wang2021Combined segfault during BIDS conversion by switching from mne.io.read_raw_ant to mne.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 BIDSInterfaceRawEDF that enumerated every MNE channel type keyword; replaced with stim-exclusion approach (#1030 by Bruno Aristimunha)

  • Fix set_montage crash in moabb.datasets.Thielen2015 when return_all_modalities=True by retyping ANA/EXG channels to misc (#1030 by Bruno Aristimunha)

  • Fix duplicate stim channels and dead CPz reference channel in moabb.datasets.Liu2024 when return_all_modalities=True (#1030 by Bruno Aristimunha)

  • Fix RawToEpochs silently stripping all non-EEG channels regardless of return_all_modalities setting (#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 conflated Cue + Experimental-stimulus roles, fix SSVEP rest tag to Experiment-structure, and extract shared _MI_SENSORY constant to reduce tag duplication. Revert arrow-specific cues from paradigm-level HED defaults to generic sensory prefix, and add per-dataset hed_tags overrides 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_ext in 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 pass groups parameter in splitters using GroupsConsumerMixin, use os.environ in FakeDataset to avoid non-standard config warnings, and suppress intentional OptunaSearchCV experimental 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 in legacy_base for clearer maintenance. The temporary legacy.py file 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, and moabb.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: LearningCurveSplitter declares a metadata_columns class attribute, and BaseEvaluation auto-detects it via hasattr(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 in WithinSessionSplitter and WithinSubjectSplitter to properly forward to the inner cv_class.get_n_splits() instead of hardcoding n_folds, giving correct split counts when using custom CV classes like LearningCurveSplitter (#963 by Bruno Aristimunha)

  • Remove dead _fit_and_score() function and unused paradigm/mne_labels parameters from _evaluate_fold() in evaluations/base.py (by Bruno Aristimunha)

  • Memory optimization in _process_parallel(): pass X, y, metadata as top-level positional args to joblib.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() from WithinSessionSplitter, WithinSubjectSplitter, and CrossSubjectSplitter. All splitters now store a _current_splitter reference, and BaseEvaluation._build_scored_result() reads metadata generically from it (#963 by Bruno Aristimunha)

  • Extract _fit_cv(), _maybe_save_model_cv(), and _attach_emissions() into BaseEvaluation, removing duplicated model-fitting, model-saving, and carbon-tracking boilerplate from WithinSessionEvaluation, CrossSessionEvaluation, and CrossSubjectEvaluation (#963 by Bruno Aristimunha)

  • Extract _load_data() helper into BaseEvaluation to centralize data loading logic (epoch requirement checking and paradigm.get_data() call) that was duplicated across all three evaluation classes (#963 by Bruno Aristimunha)

  • Extract _get_nchan() helper into BaseEvaluation to 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() from evaluations.py to utils.py for shared access by BaseEvaluation._load_data() (#963 by Bruno Aristimunha)

  • Move WithinSessionSplitter creation outside the per-session loop in WithinSessionEvaluation, 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 under moabb/ using py_compile (#960 by Bruno Aristimunha)

  • Add persistent DOI resolution cache (moabb/tests/doi_cache.json) for test_doi_validation.py to 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 UtilEvaluation test class not discovered by pytest: renamed to TestUtilEvaluation and replaced self.skipTest (unittest-only) with pytest.skip (#1005 by Bruno Aristimunha)

  • Add 282 parametrized tests in moabb/tests/test_neural_signatures.py covering all five paradigms, HTML generation, template/colorscale utilities, and edge cases for the moabb.analysis.neural_signatures module (#1039 by Bruno Aristimunha)

Version 1.4.3 (Stable - PyPi)#

Enhancements#

API changes#

  • None.

Requirements#

Bugs#

Code health#

Version 1.4.2#

Enhancements#

Bugs#

API changes#

  • None.

Version - 1.4#

Enhancements#

Bugs#

API changes#

  • None.

Version - 1.3#

Enhancements#

Bugs#

API changes#

  • Removing the deep learning module from inside moabb in favour of braindecode integration (#692 by Bruno Aristimunha )

Version - 1.2.0#

Enhancements#

Bugs#

API changes#

Version - 1.1.1#

Enhancements#

Bugs#

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#

Bugs#

API changes#

  • None

Version - 1.0.0#

Enhancements#

Bugs#

API changes#

  • None

Version - 0.5.0#

Enhancements#

Bugs#

API changes#

  • None

Version - 0.4.6#

Enhancements#

Bugs#

Version - 0.4.5#

Enhancements#

Bugs#

Version - 0.4.4#

Enhancements#

Bugs#

API changes#

  • Minimum supported Python version is now 3.7

  • MOABB now depends on scikit-learn >= 1.0

Version - 0.4.3#

Enhancements#

Bugs#

API changes#

Version - 0.4.2#

Enhancements#

  • None

Bugs#

API changes#

  • None

Version - 0.4.1#

Enhancements#

  • None

Bugs#

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#

Bugs#

API changes#

  • Drop update_path from moabb.download.data_path and moabb.download.data_dl

Version 0.3.0#

Enhancements#

Bugs#

API changes#

  • None

Version 0.2.1#

Enhancements#

Bugs#

API changes#

  • None

Version 0.2.0#

Enhancements#

Bugs#

  • None

API changes#

  • None