# Overview
Starting from raw EDF files downloaded off PhysioNet, the pipeline cleans 64-channel EEG recordings from 109 subjects, segments them into 5-second motor imagery trials, projects them through Common Spatial Pattern filters that isolate motor cortex activity, and feeds the resulting features to a Linear Discriminant classifier — reaching 67.3% mean accuracy across subjects on the hands vs feet task, clearing the 60% project threshold. No deep learning, no pre-trained weights. Just covariance matrices, spatial filters, and a linear boundary.
# Dataset & Experiments
PhysioNet's EEG Motor Movement/Imagery (EEGMMI) dataset contains 109 subjects performing six distinct motor tasks. Each task maps to a set of EDF run files:
| Experiment | Runs |
|---|---|
| Motor execution: left vs right hand | [3, 7, 11] |
| Motor imagery: left vs right hand | [4, 8, 12] |
| Motor execution: hands vs feet | [5, 9, 13] |
| Motor imagery: hands vs feet | [6, 10, 14] |
| Motor execution: combined | [3, 7, 11, 5, 9, 13] |
| Motor imagery: combined | [4, 8, 12, 6, 10, 14] |
The primary target is the Motor imagery: hands vs feet task (runs 6, 10, 14). Each
subject's three run files are loaded, concatenated into a single continuous Raw object, and processed
identically. Data are cached under ./data.
# Preprocessing pipeline
The preprocessing chain executes six deterministic steps before any ML code runs:
1. Channel standardisation. PhysioNet ships with malformed channel names like
Fc4. and T7.. These are renamed in-place to proper 10-20 labels (FC4,
T7) before any spatial operation — montage lookup fails otherwise.
2. Montage attachment. The 10-05 electrode layout is applied, attaching 3D Cartesian coordinates to each channel. This is required for topographic plots and source-level analysis later.
3. Annotation renaming. PhysioNet encodes events as opaque T1/T2
markers in the EDF annotations. These are mapped to human-readable labels
(T1 → "hands", T2 → "feet") so downstream code can reference classes by name.
4. Band-pass filtering. A causal FIR filter retains only the Alpha (8–13 Hz) and Beta (13–30 Hz) bands — the frequency ranges where motor imagery modulates cortical oscillations via event-related desynchronisation (ERD). Slow drifts below 7 Hz and high-frequency muscle artefacts above 30 Hz are discarded.
5. Channel selection. pick_types(meg=False, eeg=True, stim=False, eog=False,
exclude="bads") retains pure EEG channels and drops the stimulus track and EOG channels. Without
this, the stimulus square wave and eye-movement artefacts contaminate the covariance matrices and corrupt
CSP spatial filters.
6. Epoching. The continuous signal is segmented into fixed-length trials time-locked to
each event. The epoch window is −1 s to +4 s relative to the cue onset — one second of
pre-stimulus baseline followed by four seconds of full motor imagery. This yields an
(n_trials, n_channels, n_times) tensor ready for the scikit-learn pipeline. A deep copy is
kept for the sliding-window evaluation while the training slice crops to the active motor period.
# Common Spatial Patterns
CSP finds spatial filters W that simultaneously diagonalise the two class covariance matrices. Formally it solves the generalised eigenvalue problem:
Σ₁ w = λ Σ₂ w
The filters maximise the variance ratio between classes. With n_components = 4 (2 extreme
filters per class), each epoch is projected to a four-dimensional feature vector of log-band-power values.
The resulting components are interpretable: the top-ranked filter loads heavily on contralateral motor
cortex electrodes and is directly visible as a lateralised scalp topography.
# Scikit-learn pipeline
CSP and LDA are chained into a single Pipeline([("CSP", csp), ("LDA", lda)]) estimator.
During fit, CSP learns spatial filters from class covariance matrices, transforms the epochs
to log-variance features, and LDA learns the separating hyperplane in that four-dimensional space. During
predict, the same learned filters and boundary are applied in order. Wrapping both steps in a
Pipeline guarantees no data leakage across cross-validation folds — CSP never sees
test-fold data during fitting.
# Cross-validation strategy
Monte-Carlo (ShuffleSplit) cross-validation is used instead of k-fold: 10 independent
random 80/20 splits, each with random_state=42 for reproducibility. With EEG data — typically
few trials and high noise — overlapping splits give a more stable accuracy estimate than a single
train/test split or strict k-fold. cross_val_score runs the full Pipeline over all 10 folds
automatically, appending per-fold accuracy before the final test-set evaluation.
# Results
Mean cross-validated accuracy across 109 subjects: 67.3% for left vs. right hand
imagery using 4 CSP components + LDA. Subject-level variance is high (range: 51%–84%), reflecting genuine
neurophysiological differences — not model error. The pipeline is evaluated across all six experiment
types via evaluate_experiments(), which loops every subject and task, fitting the same
Pipeline and aggregating scores into a summary DataFrame.
# Notebook
The full exploration — including raw vs filtered signal visualisations, epoch shape inspection, per-fold score printouts, and the complete experiment sweep — is available in the annotated Jupyter notebook: src/physionet.ipynb.