pybemobil.preproc.EEGPreprocessor#

class pybemobil.preproc.EEGPreprocessor(loader, *, channel_types=None, rename_channels=None, pre_hook=None, line_noise_freq='europe', line_noise_method='adaptive', line_noise_kwargs=None, get_bad_chs_kwargs=None, annotate_breaks=False, annotate_break_kwargs=None, filter_bands=(0.1, 100.0), asr=False, filter_bands_ica=(1.75, None), downsample_ica=250.0, ica_method='jamica', jamica_kwargs=None, fit_ica=True, reject_by_annotation=True, thresh=-1, exclude_labels=None, include_labels=None, fit_dipoles=False, trans=None, rv_thresh=None, remove_outside_head=False, rng_seed=None, event_id=None, skip_if_exists=False, make_report=True, save_bids_raw=True, bids_root=None, overwrite_bids_raw=False, verbose=True)[source]#

Bases: object

Preprocess EEG.

preprocessing pipeline: ZapLine → bad channels → filter → ASR → ICA → dipole fitting → average re-reference → interpolate → save.

Three recordings come out of the pipeline:

  • raw_source: the input exactly as passed to run_raw() (nothing done). In BIDS this is considered ‘raw’ format; it is written to the raw part of the BIDS dataset (save_bids_raw), not to the derivatives.

  • raw_pre_ica: every step except ICA (renaming, pre-hook, line-noise removal, bad-channel detection, break annotation, bandpass, average reference projection, ASR). This is the data ICA is fitted on and applied to. Bad channels are marked but not yet interpolated, and the average reference is still an unapplied projection, so that reapply_ica() can redo the final stage.

  • raw_clean: everything done (ICA applied, average reference applied, bad channels interpolated).

Parameters are listed in pipeline order.

Parameters:
loaderXDFLoader | None

Configured loader used by run() to read raw files. Not required when calling run_raw() directly.

channel_typesdict | None

Channel name → MNE type mapping applied right after loading (only used by run()).

rename_channelsdict | str | None

Channel renaming applied at the very start of run_raw().

  • dict: explicit {old_name: new_name} mapping.

  • str: strip this prefix from every channel name that starts with it (e.g. "BrainVision RDA_").

  • None (default): no renaming.

pre_hookcallable() | None

Arbitrary transformation applied to the raw object after channel renaming and before any signal processing.

The callable receives the mne.io.Raw object as its only argument and must return one of:

  • the modified raw object, or

  • a (raw, description) tuple, where description is a short string (≤ 120 characters) describing what the hook did. The description is appended to the provenance metadata stored on the raw object and saved with the pipeline outputs.

Use pre_hook for one-off operations that do not belong in the general pipeline but must happen before filtering, such as cropping the recording, injecting custom annotations, correcting a known hardware artefact, or converting units. The hook runs before ZapLine, bad-channel detection, and all subsequent steps, so any changes it makes are seen by the entire pipeline.

Example:

def my_hook(raw):
    raw.crop(tmin=5.0)          # drop the first 5 s
    return raw, "cropped first 5 s"

preprocessor = EEGPreprocessor(loader, pre_hook=my_hook)
line_noise_freqfloat | "europe" | "usa"

Fundamental line-noise frequency in Hz. Harmonics are computed automatically up to (but not exceeding) the Nyquist frequency of the recording. Accepted values:

  • float: explicit fundamental (e.g. 50.0 or 60.0).

  • "europe": shortcut for 50 Hz (default).

  • "usa": shortcut for 60 Hz.

The fundamental is passed to the line-noise removal step (when line_noise_method is not None), which removes its harmonics itself; the full harmonic array is used by get_bad_chs() for notch-filtered bad-channel detection.

line_noise_methodstr | None

DSS-based spectral cleaning algorithm applied before bandpass filtering. None skips line-noise removal. Default is "adaptive" (matching BeMoBIL). One of:

"adaptive"

ZapLine-plus (mne-denoise) with adaptive frequency detection.

"zapline"

Standard ZapLine (mne-denoise), fixed-frequency.

"dss_line"

Single-pass DSS (meegkit).

"dss_line_iter"

Iterative DSS (meegkit).

line_noise_kwargsdict | None

Extra keyword arguments forwarded to the backend of line_noise_method via remove_line()’s method_kwargs (e.g. {"threshold": 4.0} for ZapLine or {"nremove": 2} for "dss_line"). None uses defaults.

get_bad_chs_kwargsdict | None

Extra keyword arguments forwarded to get_bad_chs(). Supported keys (all optional):

  • "pyprep_kwargs" (dict): passed to PyPREP’s NoisyChannels; random_state is always overwritten with rng_seed.

  • "notch_width" (float, default 1.0): width of the notch filter used during bad-channel detection.

  • "line_noise_crit" (float | None, default None): z-score threshold for the per-channel line-noise criterion; None (default) disables this check - recommended when ZapLine has run.

  • "deviation_threshold" (float, default 3.5): z-score threshold for PyPREP’s amplitude-deviation criterion. Tighter than PyPREP’s built-in default of 5.0 to improve sensitivity on MoBI data; raise to reduce false positives.

  • "ransac" (bool | None, default None): run PyPREP RANSAC when None (auto) or True; auto-detects from montage presence. Set False to disable explicitly.

annotate_breaksbool

If True, run mne.preprocessing.annotate_break() to mark inter-block breaks (and other gaps between events) as BAD_break annotations, which are then excluded (via reject_by_annotation) from bad-channel detection, ICA fitting, and other downstream steps. Break detection can be overzealous on some recordings (e.g. sparse or irregular event structure), flagging most of the recording as “bad” even though the data itself is fine. Default False (skip this step entirely); set True to enable it, tuning behaviour via annotate_break_kwargs if needed.

annotate_break_kwargsdict | None

Forwarded to mne.preprocessing.annotate_break(). Ignored when annotate_breaks=False.

filter_bandstuple of float

(l_freq, h_freq) for the main bandpass filter applied before ASR and ICA.

asrbool | dict

Controls Artifact Subspace Reconstruction (ASR).

  • False (default): skip ASR; raw_pre_ica is the bandpassed, average-reference-projected data.

  • True: run ASR with the default parameters of compute_asr().

  • dict: run ASR and pass the dict as keyword arguments to compute_asr() (e.g. {"cutoff": 10, "estimator": "lwf"}).

Either way, raw_pre_ica is the data ICA is applied to, and it is saved as *_desc-preica_eeg.fif so that ICA can be re-applied with a different set of excluded components via reapply_ica().

filter_bands_icatuple of float

(l_freq, h_freq) for the ICA-specific bandpass filter.

downsample_icafloat | None

Target sampling rate for ICA fitting. None skips downsampling.

ica_methodstr

ICA algorithm. "jamica" (default) uses jamica, a JAX-accelerated reimplementation of AMICA, via jamica.fit_ica(), which returns a standard MNE ICA object directly; falls back to picard if the package is not installed. Any other string is forwarded as the method argument to mne.preprocessing.ICA (e.g. "picard", "fastica"). Ignored when fit_ica=False.

jamica_kwargsdict | None

Extra keyword arguments forwarded to jamica.fit_ica() when ica_method="jamica", e.g. {"max_iter": 2000}. AMICA’s likelihood-based sample rejection is on by default, as in the MATLAB BeMoBIL pipeline ({"fit_params": {"do_reject": True, "rejsig": 3.0, "numrej": 10}}); fit_params you pass are merged over these defaults, so {"fit_params": {"do_reject": False}} disables it. See compute_ica(). Ignored when a non-jamica method is used or fit_ica=False.

fit_icabool

If False, skip ICA entirely (raw_clean equals raw_pre_ica).

reject_by_annotationbool

If True (default), data under BAD_* annotations (e.g. BAD_break from annotate_breaks) are left out of the ICA fit and of ICLabel.

threshfloat

ICLabel decision threshold. Set to -1 (default, matching BeMoBIL) to use popularity-vote mode: each IC is assigned to whichever class has the highest predicted probability; it is excluded if that class is not in include_labels (or is in exclude_labels). Any value in [0, 1] switches to probability-threshold mode: an IC is excluded only when its winning class is one to remove and that class’s probability is at least this value; ICs whose winning class is uncertain are kept.

exclude_labelslist of str | None

ICLabel categories to exclude. Mutually exclusive with include_labels.

include_labelsset of str | None

ICLabel categories to keep; all others are excluded. None (default) means all classes except "eye blink" (matching BeMoBIL’s iclabel_classes = [1 2 4 5 6 7]), unless exclude_labels is given. Mutually exclusive with exclude_labels.

fit_dipolesbool

If True, fit a dipole to each ICA component topography using the fsaverage BEM. Requires a montage with digitisation.

transmne.transforms.Transform | "fit" | "fsaverage" | None

Head→MRI transform for dipole fitting. None and "fsaverage" use the MNE built-in template; "fit" runs automatic coregistration. Ignored when fit_dipoles=False.

rv_threshfloat | None

Residual-variance threshold for dipole fitting. Components whose best-fitting dipole has RV >= rv_thresh are set to None in the dipoles and residuals output lists. None keeps all dipoles. Typical value: 0.15 (15 %).

remove_outside_headbool

If True, components whose dipole falls outside the head model (position norm > 0.13 m) are set to None in the output lists.

rng_seedint | None

Random seed for ICA and PyPREP (RANSAC). None draws a fresh seed for each run; the seed actually used is recorded in the provenance descriptor, so any run can be reproduced.

event_iddict | None

Event map recorded in provenance metadata (no effect on processing).

skip_if_existsbool

If True and overwrite=False, skip the entire computation when the primary output file *_desc-clean_eeg.fif already exists and return the previously saved results instead.

make_reportbool

If True (default) and fname_out is provided, generate an mne.Report summarising the preprocessing outputs and save it alongside the other derivatives as *_report.html.

save_bids_rawbool

If True (default) and fname_out is provided, also write raw_source (the untouched input) to the raw part of the BIDS dataset with mne_bids.write_raw_bids(), e.g. <bids_root>/sub-P003/ses-S001/eeg/sub-P003_ses-S001_task-..._eeg.vhdr plus sidecars. Subject, session, task, run and acquisition are taken from the file name of fname_out. This is a one-time conversion: if the BIDS raw already exists it is left alone (see overwrite_bids_raw). A failure here is reported as a warning and does not stop the pipeline.

bids_rootstr | Path | None

Root of the BIDS dataset for save_bids_raw. None (default) infers it as the folder containing the derivatives directory that fname_out is in.

overwrite_bids_rawbool

If True, replace an existing BIDS raw for this run. Independent of the overwrite argument of run() / run_raw(), which only affects the derivatives. Default False.

verbosebool | str | int

MNE verbosity level during processing.

Parameters:
  • channel_types (dict | None)

  • pre_hook (object)

  • line_noise_freq (float | str)

  • line_noise_method (str | None)

  • line_noise_kwargs (dict | None)

  • get_bad_chs_kwargs (dict | None)

  • annotate_breaks (bool)

  • annotate_break_kwargs (dict | None)

  • filter_bands (tuple[float | None, float | None])

  • asr (bool | dict)

  • filter_bands_ica (tuple[float | None, float | None])

  • downsample_ica (float | None)

  • ica_method (str)

  • jamica_kwargs (dict | None)

  • fit_ica (bool)

  • reject_by_annotation (bool)

  • thresh (float)

  • exclude_labels (list | None)

  • include_labels (set | None)

  • fit_dipoles (bool)

  • trans (object)

  • rv_thresh (float | None)

  • remove_outside_head (bool)

  • rng_seed (int | None)

  • event_id (dict | None)

  • skip_if_exists (bool)

  • make_report (bool)

  • save_bids_raw (bool)

  • bids_root (str | Path | None)

  • overwrite_bids_raw (bool)

  • verbose (bool | str | int)

__init__(loader, *, channel_types=None, rename_channels=None, pre_hook=None, line_noise_freq='europe', line_noise_method='adaptive', line_noise_kwargs=None, get_bad_chs_kwargs=None, annotate_breaks=False, annotate_break_kwargs=None, filter_bands=(0.1, 100.0), asr=False, filter_bands_ica=(1.75, None), downsample_ica=250.0, ica_method='jamica', jamica_kwargs=None, fit_ica=True, reject_by_annotation=True, thresh=-1, exclude_labels=None, include_labels=None, fit_dipoles=False, trans=None, rv_thresh=None, remove_outside_head=False, rng_seed=None, event_id=None, skip_if_exists=False, make_report=True, save_bids_raw=True, bids_root=None, overwrite_bids_raw=False, verbose=True)[source]#
Parameters:

Methods

__init__(loader, *[, channel_types, ...])

reapply_ica(fname_out, *[, exclude, add, ...])

Re-apply a saved ICA with a manually edited set of excluded ICs.

run(fname_in[, fname_out, overwrite])

Load fname_in and run the full preprocessing pipeline.

run_raw(raw[, fname_out, overwrite, tier2])

Run the preprocessing pipeline on an already-loaded raw object.

reapply_ica(fname_out, *, exclude=None, add=(), remove=(), save=True, make_report=None)[source]#

Re-apply a saved ICA with a manually edited set of excluded ICs.

Loads the outputs of a previous run_raw() call from disk, changes ica.exclude and repeats only the final pipeline stage (ICA application, average reference, bad-channel interpolation). Bad-channel detection, ASR, ICA fitting and dipole fitting are not re-run.

Parameters:
fname_outstr | Path

The fname_out used for the original run.

excludelist of int | None

Full list of ICs to exclude, replacing the automatic selection. If None, start from the saved ica.exclude and apply add / remove.

addlist of int

ICs to exclude in addition to the saved ones. Ignored when exclude is given.

removelist of int

ICs to keep even though they were excluded before. Ignored when exclude is given.

savebool

If True (default), overwrite the clean recording (*_desc-clean_eeg.fif), the ICA file (*_ica.fif) and, if made, the report.

make_reportbool | None

Regenerate the HTML report. None (default) uses make_report.

Returns:
Same 3-tuple as run_raw(): (raw_clean, report, metadata).
Parameters:
Return type:

tuple

run(fname_in, fname_out=None, *, overwrite=False)[source]#

Load fname_in and run the full preprocessing pipeline.

Uses loader (an XDFLoader), whose load() returns a MultimodalRecording. Only the Tier-1 raw object is preprocessed; tier2 is forwarded to run_raw() for the report’s drop-out plots, and events is discarded (call run_raw() directly if you need it).

The montage set by the loader is kept. Only when the loaded data has no montage is the 10-05 template applied (colin27_1005, named standard_1005 before MNE 1.13); channels not in the template get a warning.

Parameters:
fname_instr | Path

Path to the raw input file (XDF or any MNE-readable format).

fname_outstr | Path | None

Where to save the derivatives, as a BIDS-style path such as <root>/derivatives/pybemobil/sub-01/eeg/sub-01_task-walk_eeg.fif. Its entities name all outputs (see get_derivative_paths()), e.g. sub-01_task-walk_desc-clean_eeg.fif. Pass None to skip saving.

overwritebool

Overwrite existing derivatives. Does not affect the BIDS raw (see overwrite_bids_raw).

Returns:
Same as run_raw().
Parameters:
Return type:

tuple

run_raw(raw, fname_out=None, *, overwrite=False, tier2=None)[source]#

Run the preprocessing pipeline on an already-loaded raw object.

Channel types and montage must already be set by the caller. raw itself is not modified; the pipeline works on a copy.

Parameters:
rawmne.io.BaseRaw

Recording to preprocess.

fname_outstr | Path | None

Where to save the derivatives, as a BIDS-style path such as <root>/derivatives/pybemobil/sub-01/eeg/sub-01_task-walk_eeg.fif. Its entities name all outputs (see get_derivative_paths()), e.g. sub-01_task-walk_desc-clean_eeg.fif. Pass None to skip saving.

overwritebool

Overwrite existing derivatives. Does not affect the BIDS raw (see overwrite_bids_raw).

tier2dict | None

Tier-2 streams from MultimodalRecording (e.g. rec.tier2), kept at native rate and not merged into raw. When provided and make_report=True, each stream is plotted in full (decimated envelope, with drop-outs shaded) in its own report section. None (default) skips this section.

Returns:
raw_cleanmne.io.Raw

Fully processed recording: ICA applied (and ASR, if enabled), average reference applied, bad channels interpolated.

reportmne.Report | None

Quality report (populated when make_report=True, else None).

metadatadict

All other pipeline outputs, keyed by name:

  • raw_source (mne.io.Raw): the input exactly as passed to run_raw() (the same object), nothing done. Written to the BIDS raw folder when save_bids_raw=True; when loaded from cache, read back from there (None if not found).

  • raw_pre_ica (mne.io.Raw): every step except ICA (line-noise removal, bandpass, ASR if enabled). The data ICA is fitted on. Bad channels are marked but not interpolated and the average reference is still an unapplied projection. Saved as *_desc-preica_eeg.fif; input to reapply_ica().

  • ica (mne.preprocessing.ICA): the fitted ICA, with ica.exclude set from the ICLabel classification. An unfitted stub when fit_ica=False.

  • ic_labels (dict): ICLabel output per component ("labels" and "y_pred_proba"). Empty when fit_ica=False.

  • dipoles (list of mne.Dipole | None): one equivalent current dipole per IC, None where removed by rv_thresh or remove_outside_head. Empty unless fit_dipoles=True.

  • residuals (list of mne.Evoked | None): residual field of each dipole fit, aligned with dipoles.

  • trans (mne.transforms.Transform | None): the head-to-MRI transform used for dipole fitting. None unless fit_dipoles=True.

  • bad_ch_dict (dict): detected bad channels. "all_bads" is the combined list; "pyprep", "faster", "bad_by_line_noise" and "bad_by_manual" hold the per-method results.

Parameters:
Return type:

tuple