↑↓ move↵ open[] previous / next lesson? all shortcuts
Appearance
Theme
Type size
Reading

Focus mode hides the sidebars. Press f to toggle it anywhere.

Keyboard shortcuts
/
Search
[ ]
Previous / next lesson
t
Jump to the table of contents
r
Review queue
f
Focus mode
?
This panel
ProjectTwo evenings$0Tier 0

Project F: The software pipeline

From a raw file to cleaned epochs to a topographic map, in MNE-Python, on public data. Build the pipeline before you have your own data so you can trust it when you do.

AssumesProject E: The zero-dollar versionPython for signalsSpineDecoding / signal processing / ML
On this pageTwo evenings
  1. The dataset
  2. The pipeline
  3. What each step is for

Every analysis is the same six steps: load, , re-reference, cut into around events, remove , and plot something across the scalp. does each step in a line or two and is what nearly every lab uses. In this project you build the pipeline on the motor dataset, where the events are known, so that by the time your own board produces data the pipeline is already tested and you know what every step does to the signal.

Predict before you look

You cut the recording into one-second epochs and then apply a 0.5 Hz high-pass filter to each epoch. What goes wrong?

The cannot do its job on one-second pieces. A 0.5 Hz high-pass has a time constant of a fraction of a second and needs a long stretch of data to settle; applied to a short it smears the edges and can invent a slope across the whole . the continuous recording first, then cut. This is the single most common ordering mistake and the reason the pipeline’s steps come in the order they do.

The dataset

Motor Movement/Imagery, runs 3, 7, and 11 for a subject: the subject opens and closes the left or right fist when a target appears. Events are annotated in the file. 64 channels at 160 Hz.

The pipeline

Figure 1. The six steps, in the only order that is safe. Two of the arrows are where most published mistakes live: filtering after epoching, and cleaning before you have looked at what you are cleaning.
import mne
from mne.datasets import eegbci

raws = []
for run in (3, 7, 11):
    path = eegbci.load_data(subject=1, runs=[run])[0]
    raw = mne.io.read_raw_edf(path, preload=True)
    eegbci.standardize(raw); raw.set_montage('standard_1005')
    raws.append(raw)
raw = mne.concatenate_raws(raws)

# 1. filter the continuous data
raw.filter(1., 40., fir_design='firwin')

# 2. re-reference to the average of all channels
raw.set_eeg_reference('average', projection=False)

# 3. events and epochs: T1 = left fist, T2 = right fist
events, event_id = mne.events_from_annotations(raw, event_id=dict(T1=1, T2=2))
epochs = mne.Epochs(raw, events, event_id, tmin=-1., tmax=4., baseline=None, preload=True)

# 4. artifact removal with ICA
ica = mne.preprocessing.ICA(n_components=20, random_state=0)
ica.fit(epochs)
ica.plot_components()               # find the blink component by its frontal topography
ica.exclude = [0]                   # whichever index looked like a blink
epochs_clean = ica.apply(epochs.copy())

# 5. something across the scalp: mu-band power during movement vs. rest
mu = epochs_clean.copy().filter(8., 13.).apply_hilbert(envelope=True)
move = mu.copy().crop(0.5, 3.5).get_data().mean(axis=(0, 2))
rest = mu.copy().crop(-1., 0.).get_data().mean(axis=(0, 2))
mne.viz.plot_topomap((move - rest) / rest, epochs.info)

The final map should show a decrease in mu power over the motor cortex, opposite the moving hand: the that decode. If it does, your pipeline works end to end on real data.

What each step is for

first, on continuous data. The high-pass removes drift; the low-pass removes muscle and hum. need long stretches of data; see the prediction above. ’s default is zero-phase, so nothing shifts in time.

Re-reference. The file’s original reference is one , and everything is measured against it. The average reference subtracts the mean of all channels, which approximates a neutral reference when cover the head. The montages explainer is about why this changes every plot.

. Cut a window around each event. Now you can compare what happened after events to what happened before, and average across events to reduce noise.

Clean. unmixes the channels into components; blinks and heartbeat come out as their own components with characteristic topographies, and you subtract them. Look at every component’s map before you exclude it. Never exclude blindly.

Plot across the scalp. The is ’s native picture. Anything you compute per channel can be drawn on the head.

  1. Run the pipeline as written and get a . Check that the mu decrease is over the correct hemisphere (left hand moves, right hemisphere decreases).
  2. Break it on purpose: after epoching and compare the edges. Skip re-referencing and compare the map. Skip and see whether blinks show up in the frontal channels of the average.
  3. Wrap the steps in a function that takes a subject number and returns the . Run it for five subjects. Note which ones show clean and which do not.
  4. Save the cleaned to disk in ’s format. Your Phase 3 projects start from that file.
Recall
Why must filtering happen before epoching?
Low-frequency filters need long stretches of data to settle. Applied to short epochs they distort the edges and can invent slopes. Filter the continuous recording, then cut.
Recall
What does ICA give you for artifact removal that filtering cannot?
It separates the multichannel data into components by statistical independence, so a blink or heartbeat becomes its own component with a recognizable topography that can be subtracted, even though its frequency content overlaps the brain signal.
Recall
What is event-related desynchronization and where should it appear for a right-fist movement?
A drop in mu (8 to 13 Hz) power over motor cortex when it becomes active; for the right fist it appears over the left hemisphere (around C3).