Note
Go to the end to download the full example code.
End-to-End EEG Classification Tutorial#
This comprehensive tutorial demonstrates how to build an end-to-end EEG classification pipeline for motor imagery using SPD Learn. We cover everything from data loading to model selection, training, and evaluation.
Introduction to EEG and Motor Imagery Classification#
Electroencephalography (EEG) measures electrical activity in the brain through electrodes placed on the scalp. Motor imagery (MI) is a mental process where a person imagines performing a motor action without actually executing it.
Why Motor Imagery Classification?
Brain-Computer Interfaces (BCIs): Allows paralyzed patients to control devices through thought alone
Rehabilitation: Helps stroke patients recover motor function
Gaming and Entertainment: Enables hands-free control
The SPD Approach
Traditional approaches use spatial filters like Common Spatial Patterns (CSP) to extract discriminative features. SPD Learn takes this further by operating directly on the manifold of Symmetric Positive Definite (SPD) matrices (covariance matrices), preserving their geometric structure.
This tutorial uses the BNCI2014_001 dataset [Tangermann et al., 2012] (BCI Competition IV 2a), which contains 4-class motor imagery data from 9 subjects.
Setup and Imports#
First, we import the necessary libraries:
MOABB: For loading standardized EEG datasets with proper preprocessing
Braindecode: For the EEGClassifier wrapper (scikit-learn compatible)
SPD Learn: For geometric deep learning models
scikit-learn: For evaluation metrics and cross-validation
import warnings
import matplotlib.pyplot as plt
import numpy as np
import torch
from braindecode import EEGClassifier
from moabb.datasets import BNCI2014_001
from moabb.paradigms import MotorImagery
from sklearn.metrics import (
ConfusionMatrixDisplay,
accuracy_score,
balanced_accuracy_score,
confusion_matrix,
)
from sklearn.preprocessing import LabelEncoder
from skorch.callbacks import EarlyStopping, EpochScoring, GradientNormClipping
from skorch.dataset import ValidSplit
from spd_learn.models import EEGSPDNet, SPDNet, TSMNet
# Suppress warnings for cleaner output
warnings.filterwarnings("ignore")
# Set random seed for reproducibility
torch.manual_seed(42)
np.random.seed(42)
/opt/hostedtoolcache/Python/3.11.16/x64/lib/python3.11/site-packages/braindecode/models/eegpt.py:497: FutureWarning: Montage name 'standard_1020' is deprecated and will be removed in MNE 1.14. Use 'colin27_1020' instead.
montage = mne.channels.make_standard_montage("standard_1020")
/opt/hostedtoolcache/Python/3.11.16/x64/lib/python3.11/site-packages/braindecode/models/eegpt.py:1452: FutureWarning: Montage name 'standard_1020' is deprecated and will be removed in MNE 1.14. Use 'colin27_1020' instead.
montage = make_standard_montage("standard_1020")
Loading Data from MOABB#
MOABB (Mother of All BCI Benchmarks) provides standardized access to many EEG datasets. We use the BNCI2014_001 dataset:
9 subjects: Each recorded on 2 days (sessions)
4 classes: Left hand, right hand, feet, tongue
22 EEG channels: Standard 10-20 montage
250 Hz sampling rate: After resampling
The dataset is split into training (session 1) and testing (session 2), simulating real-world cross-session transfer.
# Load the dataset
dataset = BNCI2014_001()
# Create paradigm with 4 motor imagery classes
# MOABB handles filtering (8-35 Hz bandpass is applied by default for MI)
paradigm = MotorImagery(n_classes=4)
print("=" * 60)
print("Dataset Information")
print("=" * 60)
print(f"Dataset: {dataset.code}")
print(f"Number of subjects: {len(dataset.subject_list)}")
print("Sessions per subject: 2 (train + test)")
print("Classes: left_hand, right_hand, feet, tongue")
Choosing from all possible events
============================================================
Dataset Information
============================================================
Dataset: BNCI2014-001
Number of subjects: 9
Sessions per subject: 2 (train + test)
Classes: left_hand, right_hand, feet, tongue
Preprocessing Recommendations#
Proper preprocessing is crucial for good BCI performance. MOABB handles most preprocessing automatically, but here are key considerations:
Filtering
Motor imagery is characterized by Event-Related Desynchronization (ERD) and Synchronization (ERS) in the mu (8-12 Hz) and beta (13-30 Hz) bands
Default: 8-35 Hz bandpass filter (captures both mu and beta)
For multi-frequency analysis, use FilterBankMotorImagery
Epoching
Motor imagery effects typically occur 0.5-4 seconds after cue onset
Default: 0 to 4 seconds post-cue
Baseline correction is applied automatically
Artifact Handling
Eye blinks and muscle artifacts can contaminate signals
MOABB applies basic artifact rejection
For production: Consider ICA or other artifact removal methods
Tip
For SPD methods, signal quality is crucial because noise affects the covariance matrix estimation. Ensure clean data before training.
# Cache configuration for faster repeated runs
cache_config = dict(
save_raw=True,
save_epochs=True,
save_array=True,
use=True,
overwrite_raw=False,
overwrite_epochs=False,
overwrite_array=False,
)
# Load data for a single subject (we'll do proper evaluation later)
subject_id = 1
X, labels, meta = paradigm.get_data(
dataset=dataset, subjects=[subject_id], cache_config=cache_config
)
# Encode labels to integers
le = LabelEncoder()
y = le.fit_transform(labels)
print(f"\nData loaded for Subject {subject_id}:")
print(f" Shape: {X.shape} (trials, channels, timepoints)")
print(" Sampling rate: 250 Hz")
print(f" Epoch length: {X.shape[2] / 250:.1f} seconds")
print(f" Classes: {le.classes_}")
# Split by session (simulates real-world scenario)
train_idx = meta.query("session == '0train'").index.to_numpy()
test_idx = meta.query("session == '1test'").index.to_numpy()
print("\nData split:")
print(f" Training (Session 1): {len(train_idx)} trials")
print(f" Testing (Session 2): {len(test_idx)} trials")
This is nemar-py 0.3.1.
Preparing to download nm000139 from https://data.nemar.org/
Target directory is not empty and has no dataset_description.json. Continuing so interrupted downloads can resume.
Retrieving 5 of 769 manifest files (16 concurrent downloads).
Overall: 0%| | 0.00/8.97k [00:00<?, ?B/s]
Overall: 4%|▍ | 366/8.97k [00:00<00:07, 1.14kB/s]
Overall: 44%|████▍ | 3.96k/8.97k [00:00<00:00, 10.9kB/s]
Finished downloading nm000139 v1.0.2.
This is nemar-py 0.3.1.
Preparing to download nm000139 from https://data.nemar.org/
Retrieving 6 of 769 manifest files (16 concurrent downloads).
S3: 0%| | 0.00/82.6M [00:00<?, ?B/s]
S3: 1%| | 1.00M/82.6M [00:00<00:19, 4.32MB/s]
S3: 24%|██▍ | 20.0M/82.6M [00:00<00:00, 76.1MB/s]
S3: 63%|██████▎ | 52.0M/82.6M [00:00<00:00, 165MB/s]
S3: 100%|██████████| 82.6M/82.6M [00:00<00:00, 165MB/s]
Finished downloading nm000139 v1.0.2.
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/participants.tsv'...
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/participants.json'...
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-0train/eeg/sub-1_ses-0train_space-CapTrak_electrodes.tsv'...
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-0train/eeg/sub-1_ses-0train_space-CapTrak_coordsystem.json'...
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-0train/eeg/sub-1_ses-0train_space-CapTrak_electrodes.json'...
The provided raw data contains annotations, but you did not pass an "event_id" mapping from annotation descriptions to event codes. We will generate arbitrary event codes. To specify custom event codes, please pass "event_id".
Used Annotations descriptions: ['feet', 'left_hand', 'right_hand', 'tongue']
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-0train/eeg/sub-1_ses-0train_task-imagery_run-0_desc-a14c6715ac710d5ab0fcf652037d4b8e_events.tsv'...
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-0train/eeg/sub-1_ses-0train_task-imagery_run-0_desc-a14c6715ac710d5ab0fcf652037d4b8e_events.json'...
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/dataset_description.json'...
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-0train/eeg/sub-1_ses-0train_task-imagery_run-0_desc-a14c6715ac710d5ab0fcf652037d4b8e_eeg.json'...
Copying data files to sub-1_ses-0train_task-imagery_run-0_desc-a14c6715ac710d5ab0fcf652037d4b8e_eeg.edf
Found no extension for raw file, assuming "BTi" format and appending extension .pdf
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-0train/eeg/sub-1_ses-0train_task-imagery_run-0_desc-a14c6715ac710d5ab0fcf652037d4b8e_channels.tsv'...
Converting data files to EDF format
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-0train/sub-1_ses-0train_scans.tsv'...
Wrote /home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-0train/sub-1_ses-0train_scans.tsv entry with eeg/sub-1_ses-0train_task-imagery_run-0_desc-a14c6715ac710d5ab0fcf652037d4b8e_eeg.edf.
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-0train/eeg/sub-1_ses-0train_task-imagery_run-0_desc-a14c6715ac710d5ab0fcf652037d4b8e_eeg.json'...
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/participants.tsv'...
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/participants.json'...
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-0train/eeg/sub-1_ses-0train_space-CapTrak_electrodes.tsv'...
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-0train/eeg/sub-1_ses-0train_space-CapTrak_coordsystem.json'...
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-0train/eeg/sub-1_ses-0train_space-CapTrak_electrodes.json'...
The provided raw data contains annotations, but you did not pass an "event_id" mapping from annotation descriptions to event codes. We will generate arbitrary event codes. To specify custom event codes, please pass "event_id".
Used Annotations descriptions: ['feet', 'left_hand', 'right_hand', 'tongue']
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-0train/eeg/sub-1_ses-0train_task-imagery_run-1_desc-a14c6715ac710d5ab0fcf652037d4b8e_events.tsv'...
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-0train/eeg/sub-1_ses-0train_task-imagery_run-1_desc-a14c6715ac710d5ab0fcf652037d4b8e_events.json'...
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/dataset_description.json'...
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-0train/eeg/sub-1_ses-0train_task-imagery_run-1_desc-a14c6715ac710d5ab0fcf652037d4b8e_eeg.json'...
Copying data files to sub-1_ses-0train_task-imagery_run-1_desc-a14c6715ac710d5ab0fcf652037d4b8e_eeg.edf
Found no extension for raw file, assuming "BTi" format and appending extension .pdf
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-0train/eeg/sub-1_ses-0train_task-imagery_run-1_desc-a14c6715ac710d5ab0fcf652037d4b8e_channels.tsv'...
Converting data files to EDF format
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-0train/sub-1_ses-0train_scans.tsv'...
Wrote /home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-0train/sub-1_ses-0train_scans.tsv entry with eeg/sub-1_ses-0train_task-imagery_run-1_desc-a14c6715ac710d5ab0fcf652037d4b8e_eeg.edf.
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-0train/eeg/sub-1_ses-0train_task-imagery_run-1_desc-a14c6715ac710d5ab0fcf652037d4b8e_eeg.json'...
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/participants.tsv'...
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/participants.json'...
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-0train/eeg/sub-1_ses-0train_space-CapTrak_electrodes.tsv'...
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-0train/eeg/sub-1_ses-0train_space-CapTrak_coordsystem.json'...
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-0train/eeg/sub-1_ses-0train_space-CapTrak_electrodes.json'...
The provided raw data contains annotations, but you did not pass an "event_id" mapping from annotation descriptions to event codes. We will generate arbitrary event codes. To specify custom event codes, please pass "event_id".
Used Annotations descriptions: ['feet', 'left_hand', 'right_hand', 'tongue']
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-0train/eeg/sub-1_ses-0train_task-imagery_run-2_desc-a14c6715ac710d5ab0fcf652037d4b8e_events.tsv'...
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-0train/eeg/sub-1_ses-0train_task-imagery_run-2_desc-a14c6715ac710d5ab0fcf652037d4b8e_events.json'...
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/dataset_description.json'...
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-0train/eeg/sub-1_ses-0train_task-imagery_run-2_desc-a14c6715ac710d5ab0fcf652037d4b8e_eeg.json'...
Copying data files to sub-1_ses-0train_task-imagery_run-2_desc-a14c6715ac710d5ab0fcf652037d4b8e_eeg.edf
Found no extension for raw file, assuming "BTi" format and appending extension .pdf
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-0train/eeg/sub-1_ses-0train_task-imagery_run-2_desc-a14c6715ac710d5ab0fcf652037d4b8e_channels.tsv'...
Converting data files to EDF format
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-0train/sub-1_ses-0train_scans.tsv'...
Wrote /home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-0train/sub-1_ses-0train_scans.tsv entry with eeg/sub-1_ses-0train_task-imagery_run-2_desc-a14c6715ac710d5ab0fcf652037d4b8e_eeg.edf.
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-0train/eeg/sub-1_ses-0train_task-imagery_run-2_desc-a14c6715ac710d5ab0fcf652037d4b8e_eeg.json'...
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/participants.tsv'...
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/participants.json'...
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-0train/eeg/sub-1_ses-0train_space-CapTrak_electrodes.tsv'...
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-0train/eeg/sub-1_ses-0train_space-CapTrak_coordsystem.json'...
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-0train/eeg/sub-1_ses-0train_space-CapTrak_electrodes.json'...
The provided raw data contains annotations, but you did not pass an "event_id" mapping from annotation descriptions to event codes. We will generate arbitrary event codes. To specify custom event codes, please pass "event_id".
Used Annotations descriptions: ['feet', 'left_hand', 'right_hand', 'tongue']
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-0train/eeg/sub-1_ses-0train_task-imagery_run-3_desc-a14c6715ac710d5ab0fcf652037d4b8e_events.tsv'...
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-0train/eeg/sub-1_ses-0train_task-imagery_run-3_desc-a14c6715ac710d5ab0fcf652037d4b8e_events.json'...
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/dataset_description.json'...
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-0train/eeg/sub-1_ses-0train_task-imagery_run-3_desc-a14c6715ac710d5ab0fcf652037d4b8e_eeg.json'...
Copying data files to sub-1_ses-0train_task-imagery_run-3_desc-a14c6715ac710d5ab0fcf652037d4b8e_eeg.edf
Found no extension for raw file, assuming "BTi" format and appending extension .pdf
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-0train/eeg/sub-1_ses-0train_task-imagery_run-3_desc-a14c6715ac710d5ab0fcf652037d4b8e_channels.tsv'...
Converting data files to EDF format
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-0train/sub-1_ses-0train_scans.tsv'...
Wrote /home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-0train/sub-1_ses-0train_scans.tsv entry with eeg/sub-1_ses-0train_task-imagery_run-3_desc-a14c6715ac710d5ab0fcf652037d4b8e_eeg.edf.
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-0train/eeg/sub-1_ses-0train_task-imagery_run-3_desc-a14c6715ac710d5ab0fcf652037d4b8e_eeg.json'...
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/participants.tsv'...
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/participants.json'...
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-0train/eeg/sub-1_ses-0train_space-CapTrak_electrodes.tsv'...
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-0train/eeg/sub-1_ses-0train_space-CapTrak_coordsystem.json'...
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-0train/eeg/sub-1_ses-0train_space-CapTrak_electrodes.json'...
The provided raw data contains annotations, but you did not pass an "event_id" mapping from annotation descriptions to event codes. We will generate arbitrary event codes. To specify custom event codes, please pass "event_id".
Used Annotations descriptions: ['feet', 'left_hand', 'right_hand', 'tongue']
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-0train/eeg/sub-1_ses-0train_task-imagery_run-4_desc-a14c6715ac710d5ab0fcf652037d4b8e_events.tsv'...
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-0train/eeg/sub-1_ses-0train_task-imagery_run-4_desc-a14c6715ac710d5ab0fcf652037d4b8e_events.json'...
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/dataset_description.json'...
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-0train/eeg/sub-1_ses-0train_task-imagery_run-4_desc-a14c6715ac710d5ab0fcf652037d4b8e_eeg.json'...
Copying data files to sub-1_ses-0train_task-imagery_run-4_desc-a14c6715ac710d5ab0fcf652037d4b8e_eeg.edf
Found no extension for raw file, assuming "BTi" format and appending extension .pdf
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-0train/eeg/sub-1_ses-0train_task-imagery_run-4_desc-a14c6715ac710d5ab0fcf652037d4b8e_channels.tsv'...
Converting data files to EDF format
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-0train/sub-1_ses-0train_scans.tsv'...
Wrote /home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-0train/sub-1_ses-0train_scans.tsv entry with eeg/sub-1_ses-0train_task-imagery_run-4_desc-a14c6715ac710d5ab0fcf652037d4b8e_eeg.edf.
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-0train/eeg/sub-1_ses-0train_task-imagery_run-4_desc-a14c6715ac710d5ab0fcf652037d4b8e_eeg.json'...
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/participants.tsv'...
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/participants.json'...
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-0train/eeg/sub-1_ses-0train_space-CapTrak_electrodes.tsv'...
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-0train/eeg/sub-1_ses-0train_space-CapTrak_coordsystem.json'...
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-0train/eeg/sub-1_ses-0train_space-CapTrak_electrodes.json'...
The provided raw data contains annotations, but you did not pass an "event_id" mapping from annotation descriptions to event codes. We will generate arbitrary event codes. To specify custom event codes, please pass "event_id".
Used Annotations descriptions: ['feet', 'left_hand', 'right_hand', 'tongue']
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-0train/eeg/sub-1_ses-0train_task-imagery_run-5_desc-a14c6715ac710d5ab0fcf652037d4b8e_events.tsv'...
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-0train/eeg/sub-1_ses-0train_task-imagery_run-5_desc-a14c6715ac710d5ab0fcf652037d4b8e_events.json'...
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/dataset_description.json'...
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-0train/eeg/sub-1_ses-0train_task-imagery_run-5_desc-a14c6715ac710d5ab0fcf652037d4b8e_eeg.json'...
Copying data files to sub-1_ses-0train_task-imagery_run-5_desc-a14c6715ac710d5ab0fcf652037d4b8e_eeg.edf
Found no extension for raw file, assuming "BTi" format and appending extension .pdf
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-0train/eeg/sub-1_ses-0train_task-imagery_run-5_desc-a14c6715ac710d5ab0fcf652037d4b8e_channels.tsv'...
Converting data files to EDF format
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-0train/sub-1_ses-0train_scans.tsv'...
Wrote /home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-0train/sub-1_ses-0train_scans.tsv entry with eeg/sub-1_ses-0train_task-imagery_run-5_desc-a14c6715ac710d5ab0fcf652037d4b8e_eeg.edf.
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-0train/eeg/sub-1_ses-0train_task-imagery_run-5_desc-a14c6715ac710d5ab0fcf652037d4b8e_eeg.json'...
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/README'...
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/participants.tsv'...
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/participants.json'...
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-1test/eeg/sub-1_ses-1test_space-CapTrak_electrodes.tsv'...
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-1test/eeg/sub-1_ses-1test_space-CapTrak_coordsystem.json'...
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-1test/eeg/sub-1_ses-1test_space-CapTrak_electrodes.json'...
The provided raw data contains annotations, but you did not pass an "event_id" mapping from annotation descriptions to event codes. We will generate arbitrary event codes. To specify custom event codes, please pass "event_id".
Used Annotations descriptions: ['feet', 'left_hand', 'right_hand', 'tongue']
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-1test/eeg/sub-1_ses-1test_task-imagery_run-0_desc-a14c6715ac710d5ab0fcf652037d4b8e_events.tsv'...
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-1test/eeg/sub-1_ses-1test_task-imagery_run-0_desc-a14c6715ac710d5ab0fcf652037d4b8e_events.json'...
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/dataset_description.json'...
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-1test/eeg/sub-1_ses-1test_task-imagery_run-0_desc-a14c6715ac710d5ab0fcf652037d4b8e_eeg.json'...
Copying data files to sub-1_ses-1test_task-imagery_run-0_desc-a14c6715ac710d5ab0fcf652037d4b8e_eeg.edf
Found no extension for raw file, assuming "BTi" format and appending extension .pdf
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-1test/eeg/sub-1_ses-1test_task-imagery_run-0_desc-a14c6715ac710d5ab0fcf652037d4b8e_channels.tsv'...
Converting data files to EDF format
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-1test/sub-1_ses-1test_scans.tsv'...
Wrote /home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-1test/sub-1_ses-1test_scans.tsv entry with eeg/sub-1_ses-1test_task-imagery_run-0_desc-a14c6715ac710d5ab0fcf652037d4b8e_eeg.edf.
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-1test/eeg/sub-1_ses-1test_task-imagery_run-0_desc-a14c6715ac710d5ab0fcf652037d4b8e_eeg.json'...
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/participants.tsv'...
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/participants.json'...
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-1test/eeg/sub-1_ses-1test_space-CapTrak_electrodes.tsv'...
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-1test/eeg/sub-1_ses-1test_space-CapTrak_coordsystem.json'...
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-1test/eeg/sub-1_ses-1test_space-CapTrak_electrodes.json'...
The provided raw data contains annotations, but you did not pass an "event_id" mapping from annotation descriptions to event codes. We will generate arbitrary event codes. To specify custom event codes, please pass "event_id".
Used Annotations descriptions: ['feet', 'left_hand', 'right_hand', 'tongue']
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-1test/eeg/sub-1_ses-1test_task-imagery_run-1_desc-a14c6715ac710d5ab0fcf652037d4b8e_events.tsv'...
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-1test/eeg/sub-1_ses-1test_task-imagery_run-1_desc-a14c6715ac710d5ab0fcf652037d4b8e_events.json'...
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/dataset_description.json'...
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-1test/eeg/sub-1_ses-1test_task-imagery_run-1_desc-a14c6715ac710d5ab0fcf652037d4b8e_eeg.json'...
Copying data files to sub-1_ses-1test_task-imagery_run-1_desc-a14c6715ac710d5ab0fcf652037d4b8e_eeg.edf
Found no extension for raw file, assuming "BTi" format and appending extension .pdf
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-1test/eeg/sub-1_ses-1test_task-imagery_run-1_desc-a14c6715ac710d5ab0fcf652037d4b8e_channels.tsv'...
Converting data files to EDF format
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-1test/sub-1_ses-1test_scans.tsv'...
Wrote /home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-1test/sub-1_ses-1test_scans.tsv entry with eeg/sub-1_ses-1test_task-imagery_run-1_desc-a14c6715ac710d5ab0fcf652037d4b8e_eeg.edf.
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-1test/eeg/sub-1_ses-1test_task-imagery_run-1_desc-a14c6715ac710d5ab0fcf652037d4b8e_eeg.json'...
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/participants.tsv'...
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/participants.json'...
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-1test/eeg/sub-1_ses-1test_space-CapTrak_electrodes.tsv'...
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-1test/eeg/sub-1_ses-1test_space-CapTrak_coordsystem.json'...
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-1test/eeg/sub-1_ses-1test_space-CapTrak_electrodes.json'...
The provided raw data contains annotations, but you did not pass an "event_id" mapping from annotation descriptions to event codes. We will generate arbitrary event codes. To specify custom event codes, please pass "event_id".
Used Annotations descriptions: ['feet', 'left_hand', 'right_hand', 'tongue']
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-1test/eeg/sub-1_ses-1test_task-imagery_run-2_desc-a14c6715ac710d5ab0fcf652037d4b8e_events.tsv'...
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-1test/eeg/sub-1_ses-1test_task-imagery_run-2_desc-a14c6715ac710d5ab0fcf652037d4b8e_events.json'...
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/dataset_description.json'...
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-1test/eeg/sub-1_ses-1test_task-imagery_run-2_desc-a14c6715ac710d5ab0fcf652037d4b8e_eeg.json'...
Copying data files to sub-1_ses-1test_task-imagery_run-2_desc-a14c6715ac710d5ab0fcf652037d4b8e_eeg.edf
Found no extension for raw file, assuming "BTi" format and appending extension .pdf
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-1test/eeg/sub-1_ses-1test_task-imagery_run-2_desc-a14c6715ac710d5ab0fcf652037d4b8e_channels.tsv'...
Converting data files to EDF format
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-1test/sub-1_ses-1test_scans.tsv'...
Wrote /home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-1test/sub-1_ses-1test_scans.tsv entry with eeg/sub-1_ses-1test_task-imagery_run-2_desc-a14c6715ac710d5ab0fcf652037d4b8e_eeg.edf.
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-1test/eeg/sub-1_ses-1test_task-imagery_run-2_desc-a14c6715ac710d5ab0fcf652037d4b8e_eeg.json'...
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/participants.tsv'...
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/participants.json'...
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-1test/eeg/sub-1_ses-1test_space-CapTrak_electrodes.tsv'...
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-1test/eeg/sub-1_ses-1test_space-CapTrak_coordsystem.json'...
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-1test/eeg/sub-1_ses-1test_space-CapTrak_electrodes.json'...
The provided raw data contains annotations, but you did not pass an "event_id" mapping from annotation descriptions to event codes. We will generate arbitrary event codes. To specify custom event codes, please pass "event_id".
Used Annotations descriptions: ['feet', 'left_hand', 'right_hand', 'tongue']
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-1test/eeg/sub-1_ses-1test_task-imagery_run-3_desc-a14c6715ac710d5ab0fcf652037d4b8e_events.tsv'...
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-1test/eeg/sub-1_ses-1test_task-imagery_run-3_desc-a14c6715ac710d5ab0fcf652037d4b8e_events.json'...
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/dataset_description.json'...
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-1test/eeg/sub-1_ses-1test_task-imagery_run-3_desc-a14c6715ac710d5ab0fcf652037d4b8e_eeg.json'...
Copying data files to sub-1_ses-1test_task-imagery_run-3_desc-a14c6715ac710d5ab0fcf652037d4b8e_eeg.edf
Found no extension for raw file, assuming "BTi" format and appending extension .pdf
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-1test/eeg/sub-1_ses-1test_task-imagery_run-3_desc-a14c6715ac710d5ab0fcf652037d4b8e_channels.tsv'...
Converting data files to EDF format
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-1test/sub-1_ses-1test_scans.tsv'...
Wrote /home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-1test/sub-1_ses-1test_scans.tsv entry with eeg/sub-1_ses-1test_task-imagery_run-3_desc-a14c6715ac710d5ab0fcf652037d4b8e_eeg.edf.
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-1test/eeg/sub-1_ses-1test_task-imagery_run-3_desc-a14c6715ac710d5ab0fcf652037d4b8e_eeg.json'...
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/participants.tsv'...
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/participants.json'...
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-1test/eeg/sub-1_ses-1test_space-CapTrak_electrodes.tsv'...
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-1test/eeg/sub-1_ses-1test_space-CapTrak_coordsystem.json'...
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-1test/eeg/sub-1_ses-1test_space-CapTrak_electrodes.json'...
The provided raw data contains annotations, but you did not pass an "event_id" mapping from annotation descriptions to event codes. We will generate arbitrary event codes. To specify custom event codes, please pass "event_id".
Used Annotations descriptions: ['feet', 'left_hand', 'right_hand', 'tongue']
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-1test/eeg/sub-1_ses-1test_task-imagery_run-4_desc-a14c6715ac710d5ab0fcf652037d4b8e_events.tsv'...
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-1test/eeg/sub-1_ses-1test_task-imagery_run-4_desc-a14c6715ac710d5ab0fcf652037d4b8e_events.json'...
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/dataset_description.json'...
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-1test/eeg/sub-1_ses-1test_task-imagery_run-4_desc-a14c6715ac710d5ab0fcf652037d4b8e_eeg.json'...
Copying data files to sub-1_ses-1test_task-imagery_run-4_desc-a14c6715ac710d5ab0fcf652037d4b8e_eeg.edf
Found no extension for raw file, assuming "BTi" format and appending extension .pdf
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-1test/eeg/sub-1_ses-1test_task-imagery_run-4_desc-a14c6715ac710d5ab0fcf652037d4b8e_channels.tsv'...
Converting data files to EDF format
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-1test/sub-1_ses-1test_scans.tsv'...
Wrote /home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-1test/sub-1_ses-1test_scans.tsv entry with eeg/sub-1_ses-1test_task-imagery_run-4_desc-a14c6715ac710d5ab0fcf652037d4b8e_eeg.edf.
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-1test/eeg/sub-1_ses-1test_task-imagery_run-4_desc-a14c6715ac710d5ab0fcf652037d4b8e_eeg.json'...
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/participants.tsv'...
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/participants.json'...
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-1test/eeg/sub-1_ses-1test_space-CapTrak_electrodes.tsv'...
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-1test/eeg/sub-1_ses-1test_space-CapTrak_coordsystem.json'...
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-1test/eeg/sub-1_ses-1test_space-CapTrak_electrodes.json'...
The provided raw data contains annotations, but you did not pass an "event_id" mapping from annotation descriptions to event codes. We will generate arbitrary event codes. To specify custom event codes, please pass "event_id".
Used Annotations descriptions: ['feet', 'left_hand', 'right_hand', 'tongue']
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-1test/eeg/sub-1_ses-1test_task-imagery_run-5_desc-a14c6715ac710d5ab0fcf652037d4b8e_events.tsv'...
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-1test/eeg/sub-1_ses-1test_task-imagery_run-5_desc-a14c6715ac710d5ab0fcf652037d4b8e_events.json'...
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/dataset_description.json'...
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-1test/eeg/sub-1_ses-1test_task-imagery_run-5_desc-a14c6715ac710d5ab0fcf652037d4b8e_eeg.json'...
Copying data files to sub-1_ses-1test_task-imagery_run-5_desc-a14c6715ac710d5ab0fcf652037d4b8e_eeg.edf
Found no extension for raw file, assuming "BTi" format and appending extension .pdf
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-1test/eeg/sub-1_ses-1test_task-imagery_run-5_desc-a14c6715ac710d5ab0fcf652037d4b8e_channels.tsv'...
Converting data files to EDF format
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-1test/sub-1_ses-1test_scans.tsv'...
Wrote /home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-1test/sub-1_ses-1test_scans.tsv entry with eeg/sub-1_ses-1test_task-imagery_run-5_desc-a14c6715ac710d5ab0fcf652037d4b8e_eeg.edf.
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-1test/eeg/sub-1_ses-1test_task-imagery_run-5_desc-a14c6715ac710d5ab0fcf652037d4b8e_eeg.json'...
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/dataset_description.json'...
Failed to save 'BNCI2014-001' sub-1 suffix-epo desc-b61d8eb to BIDS format:
################### Pipeline: ####################
FixedPipeline([Raw: SetRawAnnotations -> Raw: NamedFunctionTransformer -> Epochs: FixedPipeline])
################### Exception: ###################
Traceback (most recent call last):
File "/opt/hostedtoolcache/Python/3.11.16/x64/lib/python3.11/site-packages/moabb/datasets/base.py", line 1525, in _get_single_subject_data_using_cache
interface.save(sessions_data)
File "/opt/hostedtoolcache/Python/3.11.16/x64/lib/python3.11/site-packages/moabb/datasets/bids_interface.py", line 2428, in save
self._write_file(bids_path, obj)
File "/opt/hostedtoolcache/Python/3.11.16/x64/lib/python3.11/site-packages/moabb/datasets/bids_interface.py", line 2795, in _write_file
epochs.save(bids_path.fpath, overwrite=False, verbose=self.verbose)
File "<decorator-gen-162>", line 12, in save
File "/opt/hostedtoolcache/Python/3.11.16/x64/lib/python3.11/site-packages/mne/epochs.py", line 2377, in save
_check_fname(
File "<decorator-gen-0>", line 12, in _check_fname
File "/opt/hostedtoolcache/Python/3.11.16/x64/lib/python3.11/site-packages/mne/utils/check.py", line 325, in _check_fname
raise FileExistsError(
FileExistsError: Destination file exists. Please use option "overwrite=True" to force overwriting of: fname
##################################################
Executing the following operations:
Delete:
/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-0train/eeg/sub-1_ses-0train_task-imagery_run-0_desc-b61d8eb786bf5624b52021e7654e727c_epo.fif
/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-0train/eeg/sub-1_ses-0train_task-imagery_run-1_desc-b61d8eb786bf5624b52021e7654e727c_epo.fif
/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-0train/eeg/sub-1_ses-0train_task-imagery_run-2_desc-b61d8eb786bf5624b52021e7654e727c_epo.fif
/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-0train/eeg/sub-1_ses-0train_task-imagery_run-3_desc-b61d8eb786bf5624b52021e7654e727c_epo.fif
/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-0train/eeg/sub-1_ses-0train_task-imagery_run-4_desc-b61d8eb786bf5624b52021e7654e727c_epo.fif
/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-0train/eeg/sub-1_ses-0train_task-imagery_run-5_desc-b61d8eb786bf5624b52021e7654e727c_epo.fif
Update:
/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-0train/sub-1_ses-0train_scans.tsv
Executing the following operations:
Delete:
/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-1test/eeg/sub-1_ses-1test_task-imagery_run-0_desc-b61d8eb786bf5624b52021e7654e727c_epo.fif
/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-1test/eeg/sub-1_ses-1test_task-imagery_run-1_desc-b61d8eb786bf5624b52021e7654e727c_epo.fif
/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-1test/eeg/sub-1_ses-1test_task-imagery_run-2_desc-b61d8eb786bf5624b52021e7654e727c_epo.fif
/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-1test/eeg/sub-1_ses-1test_task-imagery_run-3_desc-b61d8eb786bf5624b52021e7654e727c_epo.fif
/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-1test/eeg/sub-1_ses-1test_task-imagery_run-4_desc-b61d8eb786bf5624b52021e7654e727c_epo.fif
/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-1test/eeg/sub-1_ses-1test_task-imagery_run-5_desc-b61d8eb786bf5624b52021e7654e727c_epo.fif
Update:
/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-1test/sub-1_ses-1test_scans.tsv
Failed to save 'BNCI2014-001' sub-1 suffix-array desc-84bb082 to BIDS format:
################### Pipeline: ####################
FixedPipeline([Raw: SetRawAnnotations -> Raw: NamedFunctionTransformer -> Epochs: FixedPipeline -> Array: ForkPipelines])
################### Exception: ###################
Traceback (most recent call last):
File "/opt/hostedtoolcache/Python/3.11.16/x64/lib/python3.11/site-packages/moabb/datasets/base.py", line 1525, in _get_single_subject_data_using_cache
interface.save(sessions_data)
File "/opt/hostedtoolcache/Python/3.11.16/x64/lib/python3.11/site-packages/moabb/datasets/bids_interface.py", line 2428, in save
self._write_file(bids_path, obj)
File "/opt/hostedtoolcache/Python/3.11.16/x64/lib/python3.11/site-packages/moabb/datasets/bids_interface.py", line 2839, in _write_file
mne.write_events(
File "<decorator-gen-102>", line 12, in write_events
File "/opt/hostedtoolcache/Python/3.11.16/x64/lib/python3.11/site-packages/mne/event.py", line 350, in write_events
filename = _check_fname(filename, overwrite=overwrite)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "<decorator-gen-0>", line 12, in _check_fname
File "/opt/hostedtoolcache/Python/3.11.16/x64/lib/python3.11/site-packages/mne/utils/check.py", line 325, in _check_fname
raise FileExistsError(
FileExistsError: Destination file exists. Please use option "overwrite=True" to force overwriting of: File
##################################################
Executing the following operations:
Delete:
/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-0train/eeg/sub-1_ses-0train_task-imagery_run-0_desc-84bb0827238f9c72ce9020d88c7ef11c_array.npy
/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-0train/eeg/sub-1_ses-0train_task-imagery_run-0_desc-84bb0827238f9c72ce9020d88c7ef11c_events.eve
/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-0train/eeg/sub-1_ses-0train_task-imagery_run-1_desc-84bb0827238f9c72ce9020d88c7ef11c_array.npy
/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-0train/eeg/sub-1_ses-0train_task-imagery_run-1_desc-84bb0827238f9c72ce9020d88c7ef11c_events.eve
/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-0train/eeg/sub-1_ses-0train_task-imagery_run-2_desc-84bb0827238f9c72ce9020d88c7ef11c_array.npy
/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-0train/eeg/sub-1_ses-0train_task-imagery_run-2_desc-84bb0827238f9c72ce9020d88c7ef11c_events.eve
/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-0train/eeg/sub-1_ses-0train_task-imagery_run-3_desc-84bb0827238f9c72ce9020d88c7ef11c_array.npy
/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-0train/eeg/sub-1_ses-0train_task-imagery_run-3_desc-84bb0827238f9c72ce9020d88c7ef11c_events.eve
/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-0train/eeg/sub-1_ses-0train_task-imagery_run-4_desc-84bb0827238f9c72ce9020d88c7ef11c_array.npy
/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-0train/eeg/sub-1_ses-0train_task-imagery_run-4_desc-84bb0827238f9c72ce9020d88c7ef11c_events.eve
/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-0train/eeg/sub-1_ses-0train_task-imagery_run-5_desc-84bb0827238f9c72ce9020d88c7ef11c_array.npy
/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-0train/eeg/sub-1_ses-0train_task-imagery_run-5_desc-84bb0827238f9c72ce9020d88c7ef11c_events.eve
Update:
/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-0train/sub-1_ses-0train_scans.tsv
Executing the following operations:
Delete:
/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-1test/eeg/sub-1_ses-1test_task-imagery_run-0_desc-84bb0827238f9c72ce9020d88c7ef11c_array.npy
/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-1test/eeg/sub-1_ses-1test_task-imagery_run-0_desc-84bb0827238f9c72ce9020d88c7ef11c_events.eve
/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-1test/eeg/sub-1_ses-1test_task-imagery_run-1_desc-84bb0827238f9c72ce9020d88c7ef11c_array.npy
/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-1test/eeg/sub-1_ses-1test_task-imagery_run-1_desc-84bb0827238f9c72ce9020d88c7ef11c_events.eve
/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-1test/eeg/sub-1_ses-1test_task-imagery_run-2_desc-84bb0827238f9c72ce9020d88c7ef11c_array.npy
/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-1test/eeg/sub-1_ses-1test_task-imagery_run-2_desc-84bb0827238f9c72ce9020d88c7ef11c_events.eve
/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-1test/eeg/sub-1_ses-1test_task-imagery_run-3_desc-84bb0827238f9c72ce9020d88c7ef11c_array.npy
/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-1test/eeg/sub-1_ses-1test_task-imagery_run-3_desc-84bb0827238f9c72ce9020d88c7ef11c_events.eve
/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-1test/eeg/sub-1_ses-1test_task-imagery_run-4_desc-84bb0827238f9c72ce9020d88c7ef11c_array.npy
/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-1test/eeg/sub-1_ses-1test_task-imagery_run-4_desc-84bb0827238f9c72ce9020d88c7ef11c_events.eve
/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-1test/eeg/sub-1_ses-1test_task-imagery_run-5_desc-84bb0827238f9c72ce9020d88c7ef11c_array.npy
/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-1test/eeg/sub-1_ses-1test_task-imagery_run-5_desc-84bb0827238f9c72ce9020d88c7ef11c_events.eve
Update:
/home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-1test/sub-1_ses-1test_scans.tsv
Data loaded for Subject 1:
Shape: (576, 22, 1001) (trials, channels, timepoints)
Sampling rate: 250 Hz
Epoch length: 4.0 seconds
Classes: ['feet' 'left_hand' 'right_hand' 'tongue']
Data split:
Training (Session 1): 288 trials
Testing (Session 2): 288 trials
Model Selection Guide#
SPD Learn provides several models optimized for different scenarios. Here’s a guide to help you choose:
SPDNet [Huang and Van Gool, 2017] - The Classic Choice
Best for: Simple pipelines, pre-computed covariance matrices
Pros: Simple architecture, fast training, interpretable
Cons: No temporal feature learning
Use when: You want a baseline or have limited data
TSMNet [Kobler et al., 2022] - Best for Session Transfer
Best for: Cross-session/cross-subject scenarios
Pros: SPDBatchNormMeanVar enables domain adaptation without labels
Cons: More parameters, requires more data
Use when: You need to transfer to new sessions/subjects
EEGSPDNet [Wilson et al., 2025] - Channel-Specific Processing
Best for: When spatial information is important
Pros: Learns channel-specific temporal filters
Cons: More memory intensive
Use when: Channels have different characteristics
TensorCSPNet [Ju and Guan, 2023] - Multi-Frequency Analysis
Best for: Filter bank approaches with multiple frequency bands
Pros: Captures frequency-specific spatial patterns
Cons: Requires FilterBankMotorImagery paradigm
Use when: Different frequency bands carry complementary information
Note
For this tutorial, we’ll compare SPDNet, TSMNet, and EEGSPDNet on standard (single-band) motor imagery data.
n_chans = X.shape[1] # 22 channels
n_outputs = len(le.classes_) # 4 classes
print("\n" + "=" * 60)
print("Model Architectures")
print("=" * 60)
# SPDNet: Simple but effective
spdnet = SPDNet(
n_chans=n_chans,
n_outputs=n_outputs,
subspacedim=n_chans, # Keep full dimensionality
threshold=1e-4, # ReEig threshold
)
print("\nSPDNet:")
print(f" Parameters: {sum(p.numel() for p in spdnet.parameters()):,}")
print(" Architecture: CovLayer -> BiMap -> ReEig -> LogEig -> Linear")
# TSMNet: With built-in feature extraction and batch normalization
tsmnet = TSMNet(
n_chans=n_chans,
n_outputs=n_outputs,
n_temp_filters=8, # Temporal filters
temp_kernel_length=50, # ~200ms at 250Hz
n_spatiotemp_filters=32, # Spatiotemporal features
n_bimap_filters=16, # BiMap output dimension
reeig_threshold=1e-4,
)
print("\nTSMNet:")
print(f" Parameters: {sum(p.numel() for p in tsmnet.parameters()):,}")
print(
" Architecture: TempConv -> SpatialConv -> CovLayer -> BiMap -> SPDBatchNormMeanVar -> LogEig"
)
# EEGSPDNet: Channel-specific convolution
eegspdnet = EEGSPDNet(
n_chans=n_chans,
n_outputs=n_outputs,
n_filters=4, # 4 filters per channel
bimap_sizes=(2, 2), # Scaling factor and depth
filter_time_length=25, # ~100ms at 250Hz
spd_drop_prob=0.0, # Disable SPD dropout for stability
)
print("\nEEGSPDNet:")
print(f" Parameters: {sum(p.numel() for p in eegspdnet.parameters()):,}")
print(" Architecture: GroupedConv1D -> CovPool -> BiMap -> ReEig -> LogEig -> Linear")
============================================================
Model Architectures
============================================================
SPDNet:
Parameters: 1,500
Architecture: CovLayer -> BiMap -> ReEig -> LogEig -> Linear
TSMNet:
Parameters: 7,389
Architecture: TempConv -> SpatialConv -> CovLayer -> BiMap -> SPDBatchNormMeanVar -> LogEig
EEGSPDNet:
Parameters: 8,144
Architecture: GroupedConv1D -> CovPool -> BiMap -> ReEig -> LogEig -> Linear
Training Configuration#
SPD networks require careful hyperparameter selection for stable training. Here are the key settings:
Learning Rate
Use low learning rates (1e-4 to 5e-4)
Riemannian optimization is sensitive to step size
Too high: Training diverges, NaN losses
Too low: Slow convergence
Gradient Clipping
Essential for SPD networks
Prevents exploding gradients during eigenvalue operations
Recommended: gradient_clip_value=1.0
Optimizer
Adam works well (adaptive learning rate)
SGD with momentum can also work but needs more tuning
AdamW with weight decay for regularization
Batch Size
16-64 trials typically works well
Larger batches give more stable covariance estimates
Limited by GPU memory
Early Stopping
Monitor validation loss
Patience of 10-20 epochs is reasonable
# Training hyperparameters
batch_size = 32
max_epochs = 10 # Reduced for documentation build speed (was 200)
learning_rate = 1e-4 # CRITICAL: Use low learning rate
# Device selection
device = "cuda" if torch.cuda.is_available() else "cpu"
print(f"\n{'=' * 60}")
print("Training Configuration")
print("=" * 60)
print(f"Device: {device}")
print(f"Batch size: {batch_size}")
print(f"Max epochs: {max_epochs}")
print(f"Learning rate: {learning_rate}")
print("Gradient clipping: 1.0")
============================================================
Training Configuration
============================================================
Device: cpu
Batch size: 32
Max epochs: 10
Learning rate: 0.0001
Gradient clipping: 1.0
Training the Models#
We’ll train each model and compare their performance. The key components of our training setup:
EEGClassifier: Braindecode wrapper for scikit-learn compatibility
GradientNormClipping: Prevents gradient explosion
EarlyStopping: Stops training when validation loss plateaus
EpochScoring: Tracks accuracy during training
def create_classifier(model, learning_rate=1e-4, max_epochs=100, batch_size=32):
"""Create an EEGClassifier with proper settings for SPD networks.
Parameters
----------
model : nn.Module
PyTorch model to train.
learning_rate : float
Learning rate (use low values like 1e-4).
max_epochs : int
Maximum number of training epochs.
batch_size : int
Batch size for training.
Returns
-------
EEGClassifier
Configured classifier ready for training.
"""
clf = EEGClassifier(
model,
criterion=torch.nn.CrossEntropyLoss,
optimizer=torch.optim.Adam,
optimizer__lr=learning_rate,
# Validation split for early stopping and monitoring
train_split=ValidSplit(0.2, stratified=True, random_state=42),
batch_size=batch_size,
max_epochs=max_epochs,
callbacks=[
# Track training accuracy
(
"train_acc",
EpochScoring(
"accuracy", lower_is_better=False, on_train=True, name="train_acc"
),
),
# CRITICAL: Gradient clipping for SPD network stability
("gradient_clip", GradientNormClipping(gradient_clip_value=1.0)),
# Early stopping to prevent overfitting
(
"early_stop",
EarlyStopping(
monitor="valid_loss",
patience=15,
threshold=1e-4,
lower_is_better=True,
),
),
],
device=device,
verbose=0, # Set to 1 for training progress
)
return clf
# Store results for comparison
results = {}
Training SPDNet#
print("\n" + "=" * 60)
print("Training SPDNet")
print("=" * 60)
# Create fresh model instance
spdnet = SPDNet(
n_chans=n_chans,
n_outputs=n_outputs,
subspacedim=n_chans,
threshold=1e-4,
)
clf_spdnet = create_classifier(spdnet, learning_rate=1e-4)
clf_spdnet.fit(X[train_idx], y[train_idx])
# Evaluate
y_pred_train_spdnet = clf_spdnet.predict(X[train_idx])
y_pred_test_spdnet = clf_spdnet.predict(X[test_idx])
results["SPDNet"] = {
"train_acc": accuracy_score(y[train_idx], y_pred_train_spdnet),
"test_acc": accuracy_score(y[test_idx], y_pred_test_spdnet),
"test_bal_acc": balanced_accuracy_score(y[test_idx], y_pred_test_spdnet),
"y_pred": y_pred_test_spdnet,
"history": clf_spdnet.history,
}
print(f"Train Accuracy: {results['SPDNet']['train_acc'] * 100:.2f}%")
print(f"Test Accuracy: {results['SPDNet']['test_acc'] * 100:.2f}%")
print(f"Test Balanced: {results['SPDNet']['test_bal_acc'] * 100:.2f}%")
============================================================
Training SPDNet
============================================================
Train Accuracy: 69.79%
Test Accuracy: 63.54%
Test Balanced: 63.54%
Training TSMNet#
print("\n" + "=" * 60)
print("Training TSMNet")
print("=" * 60)
# Create fresh model instance
tsmnet = TSMNet(
n_chans=n_chans,
n_outputs=n_outputs,
n_temp_filters=8,
temp_kernel_length=50,
n_spatiotemp_filters=32,
n_bimap_filters=16,
reeig_threshold=1e-4,
)
clf_tsmnet = create_classifier(tsmnet, learning_rate=1e-4)
clf_tsmnet.fit(X[train_idx], y[train_idx])
# Evaluate
y_pred_train_tsmnet = clf_tsmnet.predict(X[train_idx])
y_pred_test_tsmnet = clf_tsmnet.predict(X[test_idx])
results["TSMNet"] = {
"train_acc": accuracy_score(y[train_idx], y_pred_train_tsmnet),
"test_acc": accuracy_score(y[test_idx], y_pred_test_tsmnet),
"test_bal_acc": balanced_accuracy_score(y[test_idx], y_pred_test_tsmnet),
"y_pred": y_pred_test_tsmnet,
"history": clf_tsmnet.history,
}
print(f"Train Accuracy: {results['TSMNet']['train_acc'] * 100:.2f}%")
print(f"Test Accuracy: {results['TSMNet']['test_acc'] * 100:.2f}%")
print(f"Test Balanced: {results['TSMNet']['test_bal_acc'] * 100:.2f}%")
============================================================
Training TSMNet
============================================================
Train Accuracy: 68.40%
Test Accuracy: 64.93%
Test Balanced: 64.93%
Training EEGSPDNet#
print("\n" + "=" * 60)
print("Training EEGSPDNet")
print("=" * 60)
# Create fresh model instance
eegspdnet = EEGSPDNet(
n_chans=n_chans,
n_outputs=n_outputs,
n_filters=4,
bimap_sizes=(2, 2),
filter_time_length=25,
spd_drop_prob=0.0,
)
clf_eegspdnet = create_classifier(eegspdnet, learning_rate=1e-4)
clf_eegspdnet.fit(X[train_idx], y[train_idx])
# Evaluate
y_pred_train_eegspdnet = clf_eegspdnet.predict(X[train_idx])
y_pred_test_eegspdnet = clf_eegspdnet.predict(X[test_idx])
results["EEGSPDNet"] = {
"train_acc": accuracy_score(y[train_idx], y_pred_train_eegspdnet),
"test_acc": accuracy_score(y[test_idx], y_pred_test_eegspdnet),
"test_bal_acc": balanced_accuracy_score(y[test_idx], y_pred_test_eegspdnet),
"y_pred": y_pred_test_eegspdnet,
"history": clf_eegspdnet.history,
}
print(f"Train Accuracy: {results['EEGSPDNet']['train_acc'] * 100:.2f}%")
print(f"Test Accuracy: {results['EEGSPDNet']['test_acc'] * 100:.2f}%")
print(f"Test Balanced: {results['EEGSPDNet']['test_bal_acc'] * 100:.2f}%")
============================================================
Training EEGSPDNet
============================================================
Train Accuracy: 72.92%
Test Accuracy: 70.83%
Test Balanced: 70.83%
Visualization of Results#
Let’s visualize the training progress and compare model performance.
fig = plt.figure(figsize=(16, 10))
# 1. Model Comparison Bar Chart
ax1 = fig.add_subplot(2, 3, 1)
models = list(results.keys())
test_accs = [results[m]["test_acc"] * 100 for m in models]
train_accs = [results[m]["train_acc"] * 100 for m in models]
x = np.arange(len(models))
width = 0.35
bars1 = ax1.bar(
x - width / 2, train_accs, width, label="Train", color="#3498db", alpha=0.8
)
bars2 = ax1.bar(
x + width / 2, test_accs, width, label="Test", color="#e74c3c", alpha=0.8
)
ax1.set_ylabel("Accuracy (%)", fontsize=12)
ax1.set_title("Model Comparison", fontsize=14, fontweight="bold")
ax1.set_xticks(x)
ax1.set_xticklabels(models, fontsize=11)
ax1.legend(fontsize=10)
ax1.set_ylim([0, 100])
ax1.axhline(y=25, color="gray", linestyle="--", alpha=0.5, label="Chance level")
ax1.grid(True, alpha=0.3, axis="y")
# Add value labels
for bar in bars1:
height = bar.get_height()
ax1.annotate(
f"{height:.1f}",
xy=(bar.get_x() + bar.get_width() / 2, height),
xytext=(0, 3),
textcoords="offset points",
ha="center",
va="bottom",
fontsize=9,
)
for bar in bars2:
height = bar.get_height()
ax1.annotate(
f"{height:.1f}",
xy=(bar.get_x() + bar.get_width() / 2, height),
xytext=(0, 3),
textcoords="offset points",
ha="center",
va="bottom",
fontsize=9,
)
# 2-4. Training Loss Curves
for idx, (model_name, color) in enumerate(
[("SPDNet", "#3498db"), ("TSMNet", "#2ecc71"), ("EEGSPDNet", "#9b59b6")]
):
ax = fig.add_subplot(2, 3, idx + 2)
history = results[model_name]["history"]
epochs = range(1, len(history) + 1)
ax.plot(
epochs, history[:, "train_loss"], "-", color=color, label="Train", linewidth=2
)
ax.plot(
epochs,
history[:, "valid_loss"],
"--",
color=color,
alpha=0.7,
label="Valid",
linewidth=2,
)
ax.set_xlabel("Epoch", fontsize=11)
ax.set_ylabel("Loss", fontsize=11)
ax.set_title(f"{model_name} Training Curves", fontsize=12, fontweight="bold")
ax.legend(fontsize=10)
ax.grid(True, alpha=0.3)
# 5-6. Confusion Matrices (best and worst performing models)
sorted_models = sorted(
results.keys(), key=lambda m: results[m]["test_acc"], reverse=True
)
best_model = sorted_models[0]
ax5 = fig.add_subplot(2, 3, 5)
cm = confusion_matrix(y[test_idx], results[best_model]["y_pred"])
disp = ConfusionMatrixDisplay(confusion_matrix=cm, display_labels=le.classes_)
disp.plot(ax=ax5, cmap="Blues", values_format="d")
ax5.set_title(
f"Best: {best_model}\nTest Acc: {results[best_model]['test_acc'] * 100:.1f}%",
fontsize=12,
fontweight="bold",
)
# Summary statistics
ax6 = fig.add_subplot(2, 3, 6)
ax6.axis("off")
summary_text = "Results Summary\n" + "=" * 30 + "\n\n"
for model in sorted_models:
summary_text += f"{model}:\n"
summary_text += f" Train: {results[model]['train_acc'] * 100:.1f}%\n"
summary_text += f" Test: {results[model]['test_acc'] * 100:.1f}%\n"
summary_text += f" Balanced: {results[model]['test_bal_acc'] * 100:.1f}%\n\n"
summary_text += "=" * 30 + "\n"
summary_text += "Chance level: 25.0%"
ax6.text(
0.1, 0.5, summary_text, fontsize=12, family="monospace", verticalalignment="center"
)
plt.tight_layout()
plt.show()

Cross-Validation Evaluation#
For a more robust evaluation, we can use cross-validation. Since we have session information, we use session-based splits which better simulate real-world scenarios.
Note
Due to computation time, we demonstrate with the simplest model. For production, evaluate all models with proper cross-validation.
print("\n" + "=" * 60)
print("Cross-Validation Evaluation")
print("=" * 60)
def evaluate_cross_session(model_class, model_kwargs, subjects=[1, 2, 3]):
"""Evaluate model using cross-session validation.
Parameters
----------
model_class : class
Model class to instantiate.
model_kwargs : dict
Keyword arguments for model initialization.
subjects : list
List of subject IDs to evaluate.
Returns
-------
dict
Dictionary with mean and std accuracy across subjects.
"""
accuracies = []
for subj in subjects:
# Load data
X_subj, labels_subj, meta_subj = paradigm.get_data(
dataset=dataset, subjects=[subj], cache_config=cache_config
)
y_subj = le.fit_transform(labels_subj)
# Split by session
train_idx_subj = meta_subj.query("session == '0train'").index.to_numpy()
test_idx_subj = meta_subj.query("session == '1test'").index.to_numpy()
# Create fresh model
model = model_class(**model_kwargs)
clf = create_classifier(model, max_epochs=50) # Fewer epochs for speed
# Train and evaluate
clf.fit(X_subj[train_idx_subj], y_subj[train_idx_subj])
y_pred = clf.predict(X_subj[test_idx_subj])
acc = accuracy_score(y_subj[test_idx_subj], y_pred)
accuracies.append(acc)
print(f" Subject {subj}: {acc * 100:.2f}%")
return {"mean": np.mean(accuracies), "std": np.std(accuracies), "all": accuracies}
# Evaluate SPDNet on multiple subjects
print("\nSPDNet Cross-Session Evaluation:")
spdnet_cv = evaluate_cross_session(
SPDNet,
{
"n_chans": n_chans,
"n_outputs": n_outputs,
"subspacedim": n_chans,
"threshold": 1e-4,
},
subjects=[1], # Reduced for documentation build speed (was [1, 2, 3])
)
print(f"\nSPDNet: {spdnet_cv['mean'] * 100:.1f}% +/- {spdnet_cv['std'] * 100:.1f}%")
============================================================
Cross-Validation Evaluation
============================================================
SPDNet Cross-Session Evaluation:
Extracting EDF parameters from /home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-0train/eeg/sub-1_ses-0train_task-imagery_run-0_desc-a14c6715ac710d5ab0fcf652037d4b8e_eeg.edf...
Setting channel info structure...
Creating raw.info structure...
Reading channel info from /home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-0train/eeg/sub-1_ses-0train_task-imagery_run-0_desc-a14c6715ac710d5ab0fcf652037d4b8e_channels.tsv.
Reading electrode coords from /home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-0train/eeg/sub-1_ses-0train_space-CapTrak_electrodes.tsv.
Not fully anonymizing info - keeping hand, his_id, sex of subject_info
Extracting EDF parameters from /home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-0train/eeg/sub-1_ses-0train_task-imagery_run-1_desc-a14c6715ac710d5ab0fcf652037d4b8e_eeg.edf...
Setting channel info structure...
Creating raw.info structure...
Reading channel info from /home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-0train/eeg/sub-1_ses-0train_task-imagery_run-1_desc-a14c6715ac710d5ab0fcf652037d4b8e_channels.tsv.
Reading electrode coords from /home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-0train/eeg/sub-1_ses-0train_space-CapTrak_electrodes.tsv.
Not fully anonymizing info - keeping hand, his_id, sex of subject_info
Extracting EDF parameters from /home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-0train/eeg/sub-1_ses-0train_task-imagery_run-2_desc-a14c6715ac710d5ab0fcf652037d4b8e_eeg.edf...
Setting channel info structure...
Creating raw.info structure...
Reading channel info from /home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-0train/eeg/sub-1_ses-0train_task-imagery_run-2_desc-a14c6715ac710d5ab0fcf652037d4b8e_channels.tsv.
Reading electrode coords from /home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-0train/eeg/sub-1_ses-0train_space-CapTrak_electrodes.tsv.
Not fully anonymizing info - keeping hand, his_id, sex of subject_info
Extracting EDF parameters from /home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-0train/eeg/sub-1_ses-0train_task-imagery_run-3_desc-a14c6715ac710d5ab0fcf652037d4b8e_eeg.edf...
Setting channel info structure...
Creating raw.info structure...
Reading channel info from /home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-0train/eeg/sub-1_ses-0train_task-imagery_run-3_desc-a14c6715ac710d5ab0fcf652037d4b8e_channels.tsv.
Reading electrode coords from /home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-0train/eeg/sub-1_ses-0train_space-CapTrak_electrodes.tsv.
Not fully anonymizing info - keeping hand, his_id, sex of subject_info
Extracting EDF parameters from /home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-0train/eeg/sub-1_ses-0train_task-imagery_run-4_desc-a14c6715ac710d5ab0fcf652037d4b8e_eeg.edf...
Setting channel info structure...
Creating raw.info structure...
Reading channel info from /home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-0train/eeg/sub-1_ses-0train_task-imagery_run-4_desc-a14c6715ac710d5ab0fcf652037d4b8e_channels.tsv.
Reading electrode coords from /home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-0train/eeg/sub-1_ses-0train_space-CapTrak_electrodes.tsv.
Not fully anonymizing info - keeping hand, his_id, sex of subject_info
Extracting EDF parameters from /home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-0train/eeg/sub-1_ses-0train_task-imagery_run-5_desc-a14c6715ac710d5ab0fcf652037d4b8e_eeg.edf...
Setting channel info structure...
Creating raw.info structure...
Reading channel info from /home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-0train/eeg/sub-1_ses-0train_task-imagery_run-5_desc-a14c6715ac710d5ab0fcf652037d4b8e_channels.tsv.
Reading electrode coords from /home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-0train/eeg/sub-1_ses-0train_space-CapTrak_electrodes.tsv.
Not fully anonymizing info - keeping hand, his_id, sex of subject_info
Extracting EDF parameters from /home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-1test/eeg/sub-1_ses-1test_task-imagery_run-0_desc-a14c6715ac710d5ab0fcf652037d4b8e_eeg.edf...
Setting channel info structure...
Creating raw.info structure...
Reading channel info from /home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-1test/eeg/sub-1_ses-1test_task-imagery_run-0_desc-a14c6715ac710d5ab0fcf652037d4b8e_channels.tsv.
Reading electrode coords from /home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-1test/eeg/sub-1_ses-1test_space-CapTrak_electrodes.tsv.
Not fully anonymizing info - keeping hand, his_id, sex of subject_info
Extracting EDF parameters from /home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-1test/eeg/sub-1_ses-1test_task-imagery_run-1_desc-a14c6715ac710d5ab0fcf652037d4b8e_eeg.edf...
Setting channel info structure...
Creating raw.info structure...
Reading channel info from /home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-1test/eeg/sub-1_ses-1test_task-imagery_run-1_desc-a14c6715ac710d5ab0fcf652037d4b8e_channels.tsv.
Reading electrode coords from /home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-1test/eeg/sub-1_ses-1test_space-CapTrak_electrodes.tsv.
Not fully anonymizing info - keeping hand, his_id, sex of subject_info
Extracting EDF parameters from /home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-1test/eeg/sub-1_ses-1test_task-imagery_run-2_desc-a14c6715ac710d5ab0fcf652037d4b8e_eeg.edf...
Setting channel info structure...
Creating raw.info structure...
Reading channel info from /home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-1test/eeg/sub-1_ses-1test_task-imagery_run-2_desc-a14c6715ac710d5ab0fcf652037d4b8e_channels.tsv.
Reading electrode coords from /home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-1test/eeg/sub-1_ses-1test_space-CapTrak_electrodes.tsv.
Not fully anonymizing info - keeping hand, his_id, sex of subject_info
Extracting EDF parameters from /home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-1test/eeg/sub-1_ses-1test_task-imagery_run-3_desc-a14c6715ac710d5ab0fcf652037d4b8e_eeg.edf...
Setting channel info structure...
Creating raw.info structure...
Reading channel info from /home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-1test/eeg/sub-1_ses-1test_task-imagery_run-3_desc-a14c6715ac710d5ab0fcf652037d4b8e_channels.tsv.
Reading electrode coords from /home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-1test/eeg/sub-1_ses-1test_space-CapTrak_electrodes.tsv.
Not fully anonymizing info - keeping hand, his_id, sex of subject_info
Extracting EDF parameters from /home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-1test/eeg/sub-1_ses-1test_task-imagery_run-4_desc-a14c6715ac710d5ab0fcf652037d4b8e_eeg.edf...
Setting channel info structure...
Creating raw.info structure...
Reading channel info from /home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-1test/eeg/sub-1_ses-1test_task-imagery_run-4_desc-a14c6715ac710d5ab0fcf652037d4b8e_channels.tsv.
Reading electrode coords from /home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-1test/eeg/sub-1_ses-1test_space-CapTrak_electrodes.tsv.
Not fully anonymizing info - keeping hand, his_id, sex of subject_info
Extracting EDF parameters from /home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-1test/eeg/sub-1_ses-1test_task-imagery_run-5_desc-a14c6715ac710d5ab0fcf652037d4b8e_eeg.edf...
Setting channel info structure...
Creating raw.info structure...
Reading channel info from /home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-1test/eeg/sub-1_ses-1test_task-imagery_run-5_desc-a14c6715ac710d5ab0fcf652037d4b8e_channels.tsv.
Reading electrode coords from /home/runner/mne_data/MNE-BIDS-bnci2014-001/sub-1/ses-1test/eeg/sub-1_ses-1test_space-CapTrak_electrodes.tsv.
Not fully anonymizing info - keeping hand, his_id, sex of subject_info
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/dataset_description.json'...
Writing '/home/runner/mne_data/MNE-BIDS-bnci2014-001/dataset_description.json'...
Subject 1: 57.29%
SPDNet: 57.3% +/- 0.0%
Troubleshooting Tips#
Here are common issues and solutions when training SPD networks:
Problem: NaN losses or diverging training
Solution 1: Reduce learning rate (try 1e-5)
Solution 2: Increase gradient clipping threshold
Solution 3: Check for NaN/Inf in input data
Solution 4: Add small epsilon to covariance matrices for numerical stability
Problem: Model performs at chance level
Solution 1: Verify data loading and label encoding
Solution 2: Check that train/test split is correct
Solution 3: Increase model capacity (more filters)
Solution 4: Try longer training with early stopping
Problem: Large gap between train and test accuracy (overfitting)
Solution 1: Add regularization (weight decay in optimizer)
Solution 2: Use dropout (final_layer_drop_prob for EEGSPDNet)
Solution 3: Reduce model complexity
Solution 4: Use data augmentation
Problem: Training is too slow
Solution 1: Use GPU (set device=”cuda”)
Solution 2: Reduce batch size
Solution 3: Use mixed precision training
Solution 4: Reduce model complexity
Problem: Out of memory errors
Solution 1: Reduce batch size
Solution 2: Use gradient accumulation
Solution 3: Use a simpler model
print("\n" + "=" * 60)
print("Troubleshooting Checklist")
print("=" * 60)
print("""
If your model isn't working:
1. Data Quality
[ ] Check for NaN/Inf values: np.any(np.isnan(X)) should be False
[ ] Verify shapes: X should be (n_trials, n_channels, n_timepoints)
[ ] Ensure proper filtering (8-35 Hz for motor imagery)
2. Training Settings
[ ] Learning rate is low (1e-4 or lower)
[ ] Gradient clipping is enabled (1.0)
[ ] Batch size is reasonable (16-64)
3. Model Selection
[ ] SPDNet: For simple baselines
[ ] TSMNet: For session transfer (has SPDBatchNormMeanVar)
[ ] EEGSPDNet: For channel-specific processing
[ ] TensorCSPNet: For multi-frequency (filter bank)
4. Numerical Stability
[ ] ReEig threshold > 0 (default 1e-4)
[ ] SPD dropout disabled if unstable (spd_drop_prob=0.0)
""")
============================================================
Troubleshooting Checklist
============================================================
If your model isn't working:
1. Data Quality
[ ] Check for NaN/Inf values: np.any(np.isnan(X)) should be False
[ ] Verify shapes: X should be (n_trials, n_channels, n_timepoints)
[ ] Ensure proper filtering (8-35 Hz for motor imagery)
2. Training Settings
[ ] Learning rate is low (1e-4 or lower)
[ ] Gradient clipping is enabled (1.0)
[ ] Batch size is reasonable (16-64)
3. Model Selection
[ ] SPDNet: For simple baselines
[ ] TSMNet: For session transfer (has SPDBatchNormMeanVar)
[ ] EEGSPDNet: For channel-specific processing
[ ] TensorCSPNet: For multi-frequency (filter bank)
4. Numerical Stability
[ ] ReEig threshold > 0 (default 1e-4)
[ ] SPD dropout disabled if unstable (spd_drop_prob=0.0)
Summary and Best Practices#
In this tutorial, we covered:
Data Loading: Using MOABB for standardized EEG data access
Preprocessing: Default filtering for motor imagery (8-35 Hz)
Model Selection: SPDNet (simple), TSMNet (transfer), EEGSPDNet (spatial)
Training: Low learning rate (1e-4), gradient clipping, Adam optimizer
Evaluation: Cross-session validation for realistic estimates
Best Practices Summary:
Always use gradient clipping with SPD networks
Start with low learning rates (1e-4) and increase if needed
Monitor both training and validation loss for overfitting
Use cross-session/cross-subject evaluation for realistic estimates
Consider TSMNet for session transfer scenarios
Next Steps:
Try TensorCSPNet with FilterBankMotorImagery for multi-frequency analysis
Explore domain adaptation with TSMNet’s SPDBatchNormMeanVar
Implement your own preprocessing pipeline for specific needs
Total running time of the script: (2 minutes 46.444 seconds)