# 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:

ExperimentRuns
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.