Skip to content

Commit 02b0444

Browse files
1 parent f9ddaf3 commit 02b0444

215 files changed

Lines changed: 1518 additions & 1720 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

docs/.buildinfo

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
# Sphinx build info version 1
22
# This file records the configuration used when building these files. When it is not found, a full rebuild will be done.
3-
config: b3da49418314ae0f1d898c4e4e8b292d
3+
config: b5ea3e3ca47360e7923b9c986ea36f85
44
tags: 645f666f9bcd5a90fca523b33c5a78b7
Binary file not shown.
Binary file not shown.

docs/_downloads/d04c0dc7171471ea51f4d366ca95bb84/tutorial_5_build_a_custom_dataset.py renamed to docs/_downloads/0b1ec83b0a2aca7fc81aefb4043b4efe/tutorial_5_combining_datasets.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"""
2-
====================================
3-
Tutorial 5: Creating a dataset class
4-
====================================
2+
=============================================================
3+
Tutorial 5: Combining Multiple Datasets into a Single Dataset
4+
=============================================================
55
"""
66

77
# Author: Gregoire Cattan
Binary file not shown.

docs/_downloads/1c7d0b138bb905c4ef3742123909d332/plot_sliding_estimator.ipynb

Lines changed: 46 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,14 @@
1515
},
1616
"outputs": [],
1717
"source": [
18-
"# Authors: MOABB contributors\n#\n# License: BSD (3-clause)\n# sphinx_gallery_thumbnail_number = 1\n\nimport warnings\n\nimport matplotlib.pyplot as plt\nimport numpy as np\nfrom mne.decoding import SlidingEstimator, cross_val_multiscore\nfrom sklearn.linear_model import LogisticRegression\nfrom sklearn.pipeline import make_pipeline\nfrom sklearn.preprocessing import StandardScaler\n\nimport moabb\nfrom moabb.datasets import BNCI2014_001\nfrom moabb.paradigms import LeftRightImagery\n\n\nmoabb.set_log_level(\"info\")\nwarnings.filterwarnings(\"ignore\")"
18+
"# Authors: MOABB contributors\n#\n# License: BSD (3-clause)\n# sphinx_gallery_thumbnail_number = 2\n\nimport warnings\n\nimport matplotlib.pyplot as plt\nimport numpy as np\nfrom mne.decoding import SlidingEstimator, cross_val_multiscore\nfrom scipy.stats import ttest_1samp\nfrom sklearn.linear_model import LogisticRegression\nfrom sklearn.pipeline import make_pipeline\nfrom sklearn.preprocessing import StandardScaler\n\nimport moabb\nfrom moabb.datasets import BNCI2014_001\nfrom moabb.paradigms import LeftRightImagery\n\n\nmoabb.set_log_level(\"info\")\nwarnings.filterwarnings(\"ignore\")"
1919
]
2020
},
2121
{
2222
"cell_type": "markdown",
2323
"metadata": {},
2424
"source": [
25-
"## Loading the Dataset\n\nWe instantiate the BNCI2014-001 dataset and restrict the analysis to the\nfirst 9 subjects to keep the example reasonably fast.\n\n"
25+
"## Loading the Dataset\n\nWe instantiate the BNCI2014-001 dataset and use all 9 subjects.\n\n"
2626
]
2727
},
2828
{
@@ -33,14 +33,14 @@
3333
},
3434
"outputs": [],
3535
"source": [
36-
"dataset = BNCI2014_001()\ndataset.subject_list = dataset.subject_list[:9]"
36+
"dataset = BNCI2014_001()"
3737
]
3838
},
3939
{
4040
"cell_type": "markdown",
4141
"metadata": {},
4242
"source": [
43-
"## Choosing a Paradigm\n\nThe :class:`~moabb.paradigms.LeftRightImagery` paradigm extracts\nleft-hand and right-hand motor-imagery epochs, applies a band-pass filter\n(8\u201332 Hz by default), and returns the data as a 3-D NumPy array of shape\n``(n_trials, n_channels, n_times)``.\n\n"
43+
"## Choosing a Paradigm\n\nThe :class:`~moabb.paradigms.LeftRightImagery` paradigm extracts\nleft-hand and right-hand motor-imagery epochs, applies a band-pass filter\n(8--32 Hz by default), and returns the data as a 3-D NumPy array of shape\n``(n_trials, n_channels, n_times)``.\n\n"
4444
]
4545
},
4646
{
@@ -76,7 +76,7 @@
7676
"cell_type": "markdown",
7777
"metadata": {},
7878
"source": [
79-
"## Evaluating Each Subject\n\nFor each subject we:\n\n1. Retrieve the preprocessed epochs via the paradigm.\n2. Run stratified 5-fold cross-validation with\n :func:`~mne.decoding.cross_val_multiscore`, which returns an array of\n shape ``(n_folds, n_times)``.\n3. Average over folds to obtain a single time course per subject.\n\nAll per-subject time courses are collected for later aggregation.\n\n"
79+
"## Evaluating Each Subject\n\nFor each subject we:\n\n1. Retrieve the preprocessed epochs via the paradigm using\n ``return_epochs=True`` so we can extract the correct time vector and\n sampling frequency from the :class:`mne.Epochs` metadata.\n2. Run stratified 5-fold cross-validation with\n :func:`~mne.decoding.cross_val_multiscore`, which returns an array of\n shape ``(n_folds, n_times)``.\n3. Average over folds to obtain a single time course per subject.\n\nAll per-subject time courses are collected for later aggregation.\n\n"
8080
]
8181
},
8282
{
@@ -87,14 +87,14 @@
8787
},
8888
"outputs": [],
8989
"source": [
90-
"all_scores = []\n\nfor subject in dataset.subject_list:\n X, y, meta = paradigm.get_data(dataset=dataset, subjects=[subject])\n\n # cross_val_multiscore returns (n_folds, n_times)\n scores = cross_val_multiscore(sliding, X, y, cv=5, n_jobs=1)\n all_scores.append(scores.mean(axis=0)) # average over folds\n\n# Stack into (n_subjects, n_times)\nall_scores = np.array(all_scores)"
90+
"all_scores = []\n\nfor subject in dataset.subject_list:\n epochs, y, meta = paradigm.get_data(\n dataset=dataset, subjects=[subject], return_epochs=True\n )\n X = epochs.get_data()\n\n # cross_val_multiscore returns (n_folds, n_times)\n scores = cross_val_multiscore(sliding, X, y, cv=5, n_jobs=1)\n all_scores.append(scores.mean(axis=0)) # average over folds\n\n# Stack into (n_subjects, n_times)\nall_scores = np.array(all_scores)"
9191
]
9292
},
9393
{
9494
"cell_type": "markdown",
9595
"metadata": {},
9696
"source": [
97-
"## Building the Time Vector\n\nThe time axis of the decoded epochs starts at ``tmin`` (0 s relative to the\nmotor-imagery cue) and ends at the trial duration defined by the dataset\n(4 s for BNCI2014-001 at 250 Hz).\n\n"
97+
"## Extracting the Time Vector\n\nBecause we used ``return_epochs=True``, we can read the time axis and\nsampling frequency directly from the last Epochs object rather than\nhard-coding dataset-specific values.\n\n"
9898
]
9999
},
100100
{
@@ -105,14 +105,14 @@
105105
},
106106
"outputs": [],
107107
"source": [
108-
"sfreq = 250 # BNCI2014-001 sampling frequency\ntmin = paradigm.tmin # 0.0 s\ntmax = dataset.interval[1] - dataset.interval[0] # 4.0 s\ntimes = np.linspace(tmin, tmax, all_scores.shape[1])"
108+
"times = epochs.times\nsfreq = epochs.info[\"sfreq\"]\nprint(f\"Sampling frequency: {sfreq} Hz, {len(times)} time points\")"
109109
]
110110
},
111111
{
112112
"cell_type": "markdown",
113113
"metadata": {},
114114
"source": [
115-
"## Plotting Time-Resolved Decoding Accuracy\n\nWe plot the group-average AUC score together with the standard error of the\nmean (SEM) across subjects. A horizontal dashed line at 0.5 indicates\nchance level.\n\n"
115+
"## Statistical Significance\n\nWe run a one-sample *t*-test against chance level (AUC = 0.5) at each time\npoint. Time points with *p* < 0.05 (uncorrected) are flagged as\nsignificant.\n\n"
116116
]
117117
},
118118
{
@@ -123,7 +123,43 @@
123123
},
124124
"outputs": [],
125125
"source": [
126-
"mean_scores = all_scores.mean(axis=0)\nsem_scores = all_scores.std(axis=0) / np.sqrt(len(dataset.subject_list))\n\nfig, ax = plt.subplots(figsize=(8, 4))\nax.plot(times, mean_scores, label=\"Mean AUC across subjects\", color=\"steelblue\")\nax.fill_between(\n times,\n mean_scores - sem_scores,\n mean_scores + sem_scores,\n alpha=0.3,\n color=\"steelblue\",\n label=\"\u00b1SEM\",\n)\nax.axhline(0.5, linestyle=\"--\", color=\"k\", label=\"Chance level (AUC = 0.5)\")\nax.axvline(0, linestyle=\":\", color=\"gray\", label=\"Cue onset\")\nax.set_xlabel(\"Time (s)\")\nax.set_ylabel(\"AUC\")\nax.set_title(\"Time-Resolved Decoding \u2013 Left vs. Right Motor Imagery\\n(BNCI2014-001)\")\nax.legend(loc=\"upper left\")\nax.set_xlim(times[0], times[-1])\nax.set_ylim(0.4, 1.0)\nplt.tight_layout()\nplt.show()"
126+
"_, p_values = ttest_1samp(all_scores, 0.5, axis=0)\nsig_mask = p_values < 0.05"
127+
]
128+
},
129+
{
130+
"cell_type": "markdown",
131+
"metadata": {},
132+
"source": [
133+
"## Plot 1 -- Mean AUC Time Course with Significance\n\nWe plot the group-average AUC score together with the standard error of the\nmean (SEM) across subjects. A horizontal dashed line at 0.5 indicates\nchance level. Time points that are significantly above chance are\nhighlighted with an orange bar along the *x*-axis.\n\n"
134+
]
135+
},
136+
{
137+
"cell_type": "code",
138+
"execution_count": null,
139+
"metadata": {
140+
"collapsed": false
141+
},
142+
"outputs": [],
143+
"source": [
144+
"mean_scores = all_scores.mean(axis=0)\nsem_scores = all_scores.std(axis=0) / np.sqrt(len(dataset.subject_list))\n\nfig, ax = plt.subplots(figsize=(8, 4))\nax.plot(times, mean_scores, label=\"Mean AUC across subjects\", color=\"steelblue\")\nax.fill_between(\n times,\n mean_scores - sem_scores,\n mean_scores + sem_scores,\n alpha=0.3,\n color=\"steelblue\",\n label=\"\\u00b1SEM\",\n)\nax.axhline(0.5, linestyle=\"--\", color=\"k\", label=\"Chance level (AUC = 0.5)\")\nax.axvline(times[0], linestyle=\":\", color=\"gray\", label=\"MI onset\")\n\n# Mark significant time points with a bar at the bottom of the axes\nax.fill_between(\n times,\n 0.0,\n 0.03,\n where=sig_mask,\n color=\"tab:orange\",\n alpha=0.7,\n label=\"p < 0.05 (uncorrected)\",\n transform=ax.get_xaxis_transform(),\n)\n\nax.set_xlabel(\"Time (s)\")\nax.set_ylabel(\"AUC\")\nax.set_title(\"Time-Resolved Decoding \\u2013 Left vs. Right Motor Imagery\\n(BNCI2014-001)\")\nax.legend(loc=\"upper left\", fontsize=\"small\")\nax.set_xlim(times[0], times[-1])\nax.set_ylim(0.4, 1.0)\nplt.tight_layout()\nplt.show()"
145+
]
146+
},
147+
{
148+
"cell_type": "markdown",
149+
"metadata": {},
150+
"source": [
151+
"## Plot 2 -- Per-Subject Heatmap\n\nA heatmap of AUC scores (subjects x time) gives a richer picture than the\nmean curve alone, revealing inter-subject variability and the temporal\nstructure of discriminability for each participant.\n\n"
152+
]
153+
},
154+
{
155+
"cell_type": "code",
156+
"execution_count": null,
157+
"metadata": {
158+
"collapsed": false
159+
},
160+
"outputs": [],
161+
"source": [
162+
"fig, ax = plt.subplots(figsize=(8, 4))\nim = ax.imshow(\n all_scores,\n aspect=\"auto\",\n origin=\"lower\",\n extent=[times[0], times[-1], 0.5, len(dataset.subject_list) + 0.5],\n cmap=\"RdBu_r\",\n vmin=0.3,\n vmax=0.7,\n)\nax.set_xlabel(\"Time (s)\")\nax.set_ylabel(\"Subject\")\nax.set_yticks(range(1, len(dataset.subject_list) + 1))\nax.set_title(\"Per-Subject Time-Resolved AUC\\n(BNCI2014-001)\")\nax.axvline(times[0], linestyle=\":\", color=\"k\", linewidth=0.8)\nax.set_xlim(times[0], times[-1])\nfig.colorbar(im, ax=ax, label=\"AUC\")\nplt.tight_layout()\nplt.show()"
127163
]
128164
}
129165
],

docs/_downloads/275776a57e5f58879d57ccb28b4daf54/tutorial_5_build_a_custom_dataset.ipynb renamed to docs/_downloads/1d572f55de1f1c75feff109ca6a55fab/tutorial_5_combining_datasets.ipynb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"cell_type": "markdown",
55
"metadata": {},
66
"source": [
7-
"\n# Tutorial 5: Creating a dataset class\n"
7+
"\n# Tutorial 5: Combining Multiple Datasets into a Single Dataset\n"
88
]
99
},
1010
{
Binary file not shown.

docs/_downloads/337d0bb0f3327b553a011f5603258154/tutorial_4_adding_a_dataset.py

Lines changed: 95 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,23 @@
11
"""
22
====================================
3-
Tutorial 4: Creating a dataset class
3+
Tutorial 4: Creating custom datasets
44
====================================
5+
6+
MOABB provides several ways to integrate a custom dataset, depending on the
7+
format of your data:
8+
9+
1. :class:`~moabb.datasets.base.BaseDataset` — for datasets with arbitrary
10+
file formats. Requires implementing data downloading and file reading.
11+
2. :class:`~moabb.datasets.base.BaseBIDSDataset` /
12+
:class:`~moabb.datasets.base.LocalBIDSDataset` — for datasets already
13+
provided in `BIDS format <https://bids.neuroimaging.io/>`_.
14+
``BaseBIDSDataset`` is used for online datasets (only the download step
15+
needs to be implemented); ``LocalBIDSDataset`` is used for local or private
16+
datasets with no subclassing required at all.
17+
18+
**BIDS is the preferred format for new datasets in MOABB.**
19+
20+
This tutorial illustrates both approaches.
521
"""
622

723
# Authors: Pedro L. C. Rodrigues, Sylvain Chevallier
@@ -16,12 +32,15 @@
1632
from sklearn.pipeline import make_pipeline
1733

1834
from moabb.datasets import download as dl
19-
from moabb.datasets.base import BaseDataset
35+
from moabb.datasets.base import BaseBIDSDataset, BaseDataset
2036
from moabb.evaluations import WithinSessionEvaluation
2137
from moabb.paradigms import LeftRightImagery
2238

2339

2440
##############################################################################
41+
# 1. Creating a dataset class from scratch (BaseDataset)
42+
# ======================================================
43+
#
2544
# Creating some Data
2645
# ------------------
2746
#
@@ -30,7 +49,7 @@
3049
# 8 channels lasting for 150 seconds (sampling frequency 256 Hz). We have
3150
# included the script that creates this dataset and have uploaded it online.
3251
# The fake dataset is available on the
33-
# `Zenodo website <https://sandbox.zenodo.org/record/369543>`_
52+
# `Zenodo website <https://zenodo.org/records/14973598>`_
3453

3554

3655
def create_example_dataset():
@@ -172,3 +191,76 @@ def data_path(
172191
# your data on public server (like Zenodo or Figshare) and signal that you
173192
# want to add your dataset to MOABB in the `dedicated issue <https://github.com/NeuroTechX/moabb/issues/1>`_. # noqa: E501
174193
# You could then follow the instructions on `how to contribute <https://github.com/NeuroTechX/moabb/blob/master/CONTRIBUTING.md>`_ # noqa: E501
194+
195+
##############################################################################
196+
# 2. Creating a BIDS dataset class (BaseBIDSDataset)
197+
# ==================================================
198+
#
199+
# If your dataset is already provided in the
200+
# `BIDS format <https://bids.neuroimaging.io/>`_, you can subclass
201+
# :class:`~moabb.datasets.base.BaseBIDSDataset` instead of
202+
# :class:`~moabb.datasets.base.BaseDataset`.
203+
# The ``BaseBIDSDataset`` base class handles reading the BIDS files
204+
# automatically via ``mne-bids``; you only need to implement the
205+
# ``_download_subject`` method that downloads the data for a single subject
206+
# and returns the local path to the **root** of the BIDS dataset.
207+
#
208+
# Several MOABB datasets already use this approach — for example
209+
# :class:`moabb.datasets.Zhou2016`.
210+
#
211+
# The skeleton below shows the minimal implementation required.
212+
# **Note:** ``ExampleBIDSDataset`` is intentionally non-functional — its
213+
# ``_download_subject`` raises ``NotImplementedError``. Replace it with your
214+
# own download logic before instantiating the class.
215+
216+
217+
class ExampleBIDSDataset(BaseBIDSDataset):
218+
"""Skeleton showing how to wrap an online BIDS dataset.
219+
220+
Replace ``_download_subject`` with the actual download logic for your
221+
dataset (e.g. fetching a zip archive from Zenodo and extracting it).
222+
"""
223+
224+
def __init__(self):
225+
super().__init__(
226+
subjects=[1, 2, 3],
227+
sessions_per_subject=1,
228+
events={"left_hand": 1, "right_hand": 2},
229+
code="ExampleBIDSDataset",
230+
interval=[0, 0.75],
231+
paradigm="imagery",
232+
doi="",
233+
)
234+
235+
def _download_subject(self, subject, path, force_update, update_path, verbose):
236+
"""Download the BIDS dataset for *subject* and return the BIDS root."""
237+
# Example (not executed here — replace with your actual download URL):
238+
#
239+
# url = f"https://zenodo.org/records/XXXXX/files/bids_dataset.zip"
240+
# bids_root = dl.data_dl(url, "ExampleBIDSDataset")
241+
# return bids_root
242+
raise NotImplementedError("Replace this with your actual download logic.")
243+
244+
245+
##############################################################################
246+
# Using LocalBIDSDataset for local/private BIDS datasets
247+
# -------------------------------------------------------
248+
#
249+
# If you already have a BIDS dataset on your local machine and you do **not**
250+
# want to write a dedicated class, you can use
251+
# :class:`~moabb.datasets.base.LocalBIDSDataset` directly.
252+
# It auto-discovers subjects and sessions from the BIDS directory structure:
253+
#
254+
# .. code-block:: python
255+
#
256+
# from moabb.datasets.base import LocalBIDSDataset
257+
#
258+
# dataset = LocalBIDSDataset(
259+
# bids_root="/path/to/bids/dataset",
260+
# events={"left_hand": 1, "right_hand": 2},
261+
# interval=[0, 0.75],
262+
# paradigm="imagery",
263+
# )
264+
#
265+
# paradigm = LeftRightImagery()
266+
# X, labels, meta = paradigm.get_data(dataset=dataset, subjects=[1])
Binary file not shown.

0 commit comments

Comments
 (0)