-
-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Description
Describe the new feature or enhancement
What
Add an adaptive, component-based line-noise remover to the MNE ecosystem, based on the Zapline-plus method (Klug & Kloosterman, 2022) implemented in clean-room MIT Python. The method can:
- auto-detects narrowband peaks at power-line frequency and harmonics (50/60 Hz; handles slow drift),
- chunks the data by spatial stability to handle non-stationarity,
- removes the minimal number of DSS/PCA components needed per peak,
- aims to preserve rank and minimize broadband distortion.
Why
mne.filter.notch_filter is robust but fixed-frequency; repeated or wide notches can cause spectral collateral damage and may alter rank when interference drifts or changes spatially (common in MoBI/mobile EEG and some MEG/iEEG/LFP). An adaptive approach reduces narrowband artifacts while protecting broadband content.
Paper.
MATLAB refrence: MariusKlug/zapline-plus.
My Python library (MIT): PyZapline_plus.
Describe your proposed implementation
Implement an adaptive, component-based line-noise remover as a new public function in mne.preprocessing, with an optional thin Raw/Epochs/Evoked method wrapper. The function mirrors MNE APIs (inst, picks, copy, verbose) and returns the cleaned instance (plus optional diagnostics) while preserving info, annotations, events, and rank.
The proposed public API looks like this :
mne.preprocessing.zapline(
inst,
picks="eeg",
freqs="line", # "line" => auto-detect 50/60 Hz (+ harmonics) with drift
n_remove="auto", # int | "auto" | list[int] per harmonic
chunk_dur=None, # seconds | "auto"; handles non-stationarity
dss="auto", # "auto" | dict (DSS/PCA config)
guard=1.0, # Hz around each peak to protect
n_jobs=None,
random_state=None,
return_info=False,
copy=True,
verbose=None,
)
Algorithm
-
Peak detection
- Compute a robust PSD aggregated across picks (e.g., median of channel PSDs via Welch).
- If freqs="line", locate the dominant peak in 45–65 Hz, snap to 50 or 60 Hz, then generate harmonics < sfreq/2.
- If freqs is float/list, use provided peaks directly.
- Apply a guard band (±guard Hz) around each peak to quantify pre/post power and to avoid over-cleaning.
-
Chunking for non-stationarity
- If chunk_dur is None/"auto", segment the data where spatial patterns are stable (e.g., windowed channel-covariance similarity; minimum length enforced).
- If chunk_dur is a float, segment by duration. Chunk edges are cross-faded to avoid discontinuities.
-
Component selection & removal (per chunk × harmonic)
- Build a DSS/PCA transform emphasizing narrowband energy around the target peak (whitened data + band-focused criterion).
- Rank components by narrowband-to-broadband ratio (z-score).
- n_remove="auto" chooses the minimal number of components whose cumulative removal reduces the peak below a conservative threshold; otherwise use the user-given n_remove.
- Remove selected components and invert the transform to reconstruct the cleaned chunk.
- Stitch & finalize
- Concatenate chunks with cross-fades if needed; preserve inst.info, bads, annotations, projections, and event timing.
- Compute diagnostics (pre/post PSD at peaks, components removed, chunk map).
Describe possible alternatives
From an API perspective, there are two packaging alternatives. One is to keep this as an official extension (e.g., mne-zapline). That gives faster releases and iteration but lives outside core. The other is to augment notch_filter with auto-peak detection; although convenient, it would mix fundamentally different concepts (filtering vs. spatial decomposition) and make diagnostics harder to expose. A dedicated mne.preprocessing.zapline function (or a thin apply_zapline method) keeps behavior clear and discoverable, surfaces diagnostics (peaks found, chunks, components removed, before/after PSD), and aligns with MNE’s preprocessing style.
Additional context
I have already implemented the method in Python as a clean-room library, PyZapline_plus (MIT), available here: PyZapline_plus
. The implementation follows the algorithmic ideas from Zapline-plus (Klug & Kloosterman, 2022)
I’m happy to adapt naming, defaults, thresholds, diagnostics, and API shape to match MNE conventions, and to refactor internals to meet core standards (docstrings, tests, tutorial, “What’s new”). I’m also open to either path—starting as an official extension (mne-zapline) or merging directly into core under mne.preprocessing—and I will revise the code based on maintainer feedback in this issue. If needed for a core merge, I’m willing to relicense my contribution under BSD-3 to align with MNE-Python. For reviewers’ convenience, I attached before/after PSD screenshots, a short synthetic benchmark notebook, and links to small test fixtures demonstrating peak detection, chunking for non-stationarity, and minimal DSS/PCA component removal.


