✦ Day 04 · Preprocessing

Downsampling, Bad Channels
& Segment Rejection

Why we downsample, how to catch broken electrodes, and when to cut corrupted time windows — the essential quality control pipeline every EEG analysis begins with.

🔍 Visual Inspection 📉 Nyquist Theorem 🛠 raw.resample() 🧹 Interpolation 📊 Peak-to-Peak Rejection

Before any analysis, raw EEG data is almost always contaminated — noisy channels, movement artifacts, and often sampled at rates far higher than necessary. This day covers the three foundational cleaning operations that every preprocessing pipeline begins with. Think of it as quality control for your neural data: reduce redundancy (downsample), identify broken sensors (bad channels), and excise corrupted time windows (bad segments).


1

Why Downsample?
Nyquist, Aliasing & the Anti-Alias Filter

Most EEG amplifiers record at 500–2048 Hz, but neural oscillations of interest rarely exceed 100 Hz. Carrying all those extra samples wastes memory and slows every downstream computation. Downsampling reduces the sampling rate — but there is a critical catch.

The Nyquist theorem states that you can only faithfully represent frequencies up to half the sampling rate (fs/2). If you downsample from 1000 Hz to 250 Hz without first removing frequencies above 125 Hz, those high-frequency components “fold back” into your signal as phantom oscillations — this is aliasing, and it is irreversible.

Python
# ALWAYS low-pass filter BEFORE downsampling
raw.load_data()
raw.filter(l_freq=None, h_freq=100)    # anti-alias: remove >100 Hz
raw.resample(sfreq=250)                   # safe to downsample now

# Check the new sampling rate
print(f"New sfreq: {raw.info['sfreq']} Hz")
print(f"Nyquist:   {raw.info['sfreq'] / 2} Hz")

Cohen’s intuition (ANTS Ch. 6): Think of aliasing like a wagon wheel in a movie — when the camera’s frame rate is too low, the wheel appears to spin backward. The anti-alias filter is your insurance policy: apply it before you reduce the frame rate.


2

Identifying Bad Channels
Flat, Noisy & Bridged Electrodes

A “bad” channel is any electrode whose signal no longer reflects the underlying neural activity. Common culprits: flat channels (dead amplifier, dried gel), noisy channels (broken wire, excessive impedance), and bridged channels (conductive gel connecting two electrodes, making them nearly identical).

Bad channels propagate errors into every subsequent step — re-referencing, ICA, source localization — so catching them early is critical. MNE lets you mark them manually or use automated detection.

Python
# Manual marking after visual inspection
raw.info['bads'] = ['Fp1', 'T8']

# Automated: RANSAC-based detection (requires autoreject)
from autoreject import Ransac
ransac = Ransac(verbose=False, n_jobs=1)
ransac.fit(epochs)
print(f"Bad channels detected: {ransac.bad_chs_}")

# Interpolate: reconstruct from neighbours using spherical splines
raw.interpolate_bads(reset_bads=True)

Mental model: Interpolation replaces the bad channel’s data with a weighted average of its spatial neighbours — imagine filling a hole in a topographic map by smoothly blending the surrounding elevation contours. It recovers the spatial pattern but not the original unique neural information at that site.

Pitfall: If you interpolate too many channels (>10–15% of your montage), your data becomes a smooth spatial average rather than genuine EEG. Consider whether the recording should simply be excluded.


3

Marking Bad Segments
Annotations & reject_by_annotation

Even after fixing bad channels, stretches of the continuous recording can be corrupted by movement, muscle clenching, or equipment bumps. MNE uses the Annotations system — any annotation whose description starts with "bad" is treated as a bad segment and automatically excluded from epoching, filtering, and ICA fitting.

Python
import mne

# Interactive: click-drag in the raw plot to mark bad segments
raw.plot(block=True)

# Programmatic: mark a 5-second artifact starting at t=42s
annot = mne.Annotations(onset=[42.0], duration=[5.0],
                        description=['bad_muscle'])
raw.set_annotations(raw.annotations + annot)

# Later, epoching will automatically skip bad segments
epochs = mne.Epochs(raw, events, reject_by_annotation=True)

Why “bad_” prefix? MNE treats the description prefix as a semantic tag. bad_blink, bad_muscle, bad_movement — all are excluded automatically, but their distinct names let you audit why data was rejected later.


4

Peak-to-Peak Epoch Rejection
Amplitude Thresholds for Quality Control

After epoching, the simplest artifact rejection strategy is peak-to-peak (PTP) thresholding: if any channel in an epoch swings more than a defined voltage range, the entire epoch is dropped. Typical thresholds: ~100–150 µV for EEG, ~250 µV for EOG.

This is a blunt instrument — it catches large artifacts but misses subtle ones and wastes data by rejecting entire epochs when only one channel is bad. More sophisticated approaches (ICA, autoreject) are covered in later days.

Python
# Define PTP amplitude thresholds (in Volts)
reject = dict(eeg=150e-6,    # 150 uV
               eog=250e-6)    # 250 uV

# Also define minimum thresholds to catch flat channels
flat = dict(eeg=1e-6)       # 1 uV (practically flat)

epochs = mne.Epochs(raw, events, tmin=-0.2, tmax=0.5,
                    reject=reject, flat=flat, preload=True)

# See how many epochs survived
print(epochs.drop_log_stats())

Rule of thumb: If you are rejecting more than ~25% of your epochs, your thresholds may be too aggressive, or the recording quality needs addressing upstream (bad channels, filtering).


5

The Preprocessing Pipeline Order
When to Do What

Order matters enormously in preprocessing. Here is the canonical pipeline that most EEG labs follow:

Step 1

Import &
Montage

Step 2

Mark Bad
Channels

Step 3

Filter
(HP + LP)

Step 4

Downsample

Step 5

ICA / ASR
Artifact Fix

Key dependencies: Filter before downsampling (anti-alias). Mark bad channels before ICA (bad channels corrupt unmixing). Interpolate after ICA (preserve rank for decomposition).