بنقرة واحدة
bandpass-filter
Band-pass filter to isolate frequency range of interest with Nyquist guard
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
القائمة
Band-pass filter to isolate frequency range of interest with Nyquist guard
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
استنادا إلى تصنيف SOC المهني
Neurodata Without Borders (.nwb) — the de-facto standard for in-vivo electrophysiology (Neuropixels / AIBS / DANDI / IBL)
Index of L0 data-format loader skills by family
Index of all L2 paradigm skills by group + analysis_goal → paradigm matrix
Two-phase BCI data preprocessing pipeline: deep-inspect → plan → propose → user confirm → automated code/execute/QC/export
BIDS-iEEG sidecar (*_channels.tsv / *_electrodes.tsv / *_coordsystem.json / *_ieeg.json) — clinical sEEG / ECoG standard
Multiscale Electrophysiology Format v3 (Mayo Clinic) — long-duration encrypted sEEG
| name | bandpass_filter |
| description | Band-pass filter to isolate frequency range of interest with Nyquist guard |
| layer | L3 |
| group | filter |
| metadata | {"tags":["operator","filter","bandpass","highpass","lowpass"],"modalities":["eeg","seeg","ecog","meg","fnirs"],"step_string":"bandpass","analysis_goal_allowed":["classification","source_localization","feature_extraction","clinical_screening","exploratory","generic","connectivity","phase_amplitude_coupling","online_inference"],"analysis_goal_forbidden":[]} |
Gold-standard reference template (T2 Sub-phase A.1). All other operator SKILL.md files should mirror this section structure: Function → Algorithm & Math → Parameter Format & Defaults → Modality-Specific Considerations → When to Use / NOT to Use → Constraints & Ordering → Failure Modes & Detection → Common Issues → Reference Implementation → References.
FIR (or optionally IIR) band-pass filter that isolates frequencies within a
specified [low, high] range, removing slow drifts below low and
high-frequency noise above high. Includes automatic Nyquist guard:
when high >= sfreq/2 the low-pass arm is disabled with a logged warning
rather than silently returning NaN.
Input / Output tensor shape. Operates per-channel on continuous
recordings: (n_channels, n_times) → (n_channels, n_times). The
sampling rate is unchanged; only spectral content is reshaped. Edge
samples are zero-phase compensated by MNE's default filter_length='auto'
phase='zero-double' so the filter does not introduce a group delay.A linear-phase FIR designed by MNE's default
firwin window method has impulse response
h[n] = w[n] · sinc(2 f_c (n - N/2)) − sinc(2 f_l (n - N/2))
with Hamming window w[n], low cutoff f_l, high cutoff f_c, and
length N chosen by mne.filter.next_fast_len to give a transition
band of min(max(low/4, 2), 50) Hz on the high-pass side and
min(max(high/4, 2), 50) Hz on the low-pass side. Stop-band attenuation
is ≥ 53 dB by construction.
When method="iir" is used, the filter is a 4th-order Butterworth
band-pass with bilinear-transform coefficients
H(z) = b(z) / a(z), order 4 per arm, transition slope −24 dB/oct
applied with scipy.signal.sosfiltfilt for zero phase.
| Property | FIR (default) | IIR (Butter, order 4) |
|---|---|---|
| Phase | Linear (no group delay) | Non-linear; warps phase |
| Computational cost | O(N · L) per channel | O(L) per channel (very cheap) |
| Edge artefacts | Mild ringing at filter length boundary | Settling on first ~2/f_l samples |
| Transition steepness | Configurable; long FIR → sharp | 4th-order ≈ −24 dB/oct |
| Phase-sensitive analyses (PAC, CFC, connectivity) | Required | Forbidden |
| Online inference | Pre-roll required (causal FIR alt.) | Required (causal, low latency) |
The default in EasyBCI is FIR for offline; the only legitimate use of IIR
is the online_inference analysis_goal where causal filtering avoids
look-ahead.
bandpass:{low},{high} — comma-separated, both optional but not both
empty:
| Parameter | Type | Default | Description |
|---|---|---|---|
low | float or empty | None | High-pass cutoff (Hz). Empty disables high-pass arm. |
high | float or empty | None | Low-pass cutoff (Hz). Empty disables low-pass arm. |
method (kw) | "fir" | "iir" | "fir" | FIR for analysis, IIR only for online_inference. |
order (kw, IIR only) | int | 4 | Butterworth order per arm. |
Examples:
bandpass:1,40 — standard EEG analysis band.bandpass:0.5,100 — wider band for sEEG/ECoG.bandpass:,40 — low-pass only (drop high-frequency EMG without
touching DC/drift).bandpass:1, — high-pass only (remove drift; keep all high freq).See parameter_uncertainty/bandpass_filter.yaml for per-paradigm
empirical defaults with citations.
Bandpass is one of the most modality-agnostic operators in the registry, but cutoff selection is paradigm- and modality-dependent. Sensible defaults:
| Modality | Paradigm | Band (Hz) | Rationale |
|---|---|---|---|
| EEG | Motor Imagery | 0.5–40 | Preserves mu (8–12), beta (13–30); drops slow drifts. |
| EEG | P300 / ERP | 0.1–30 | Low high-pass preserves slow ERP components (e.g., CNV). Aggressive high-pass distorts P3 amplitude — see Tanner et al. 2015. |
| EEG | SSVEP | 5–45 | Stimulation frequencies are typically 5–40 Hz; harmonics up to 80 Hz. |
| EEG | Sleep | 0.3–35 | Preserves slow oscillations (0.5–4) + spindles (12–14) + theta. |
| EEG | Emotion | 1–50 | All bands except gamma; aggressive high-pass distorts theta-asymmetry. |
| sEEG | Epilepsy / general | 1–200 (or 1–500) | High-gamma (70–150) is the canonical functional marker; HFO (80–500) is the IED biomarker. |
| ECoG | General | 1–200 | High-gamma carries spatially-precise functional information. |
| MEG | General | 1–100 | Wider than EEG; less muscle artifact above 60 Hz. |
| fNIRS | General | 0.01–0.5 | Hemodynamic response is very slow; passband on the order of the HRF time constant. |
| Spike (LFP only) | General | 0.5–300 | Local field potential band; spike waveforms (300–6000) handled by spike_sorting. |
| Spike (waveform) | — | Forbidden | Spike waveforms are sub-millisecond — bandpass would mangle the action-potential shape. Use spike_sorting directly. |
Hard exclusions. Bandpass cannot be applied to:
low or high is below the recording's intrinsic
high-pass (e.g. AC-coupled amplifiers with 0.5 Hz hardware cutoff and
low=0.1 requested).Use when:
resample step (the resample
operator runs its own anti-alias internally, but applying bandpass
first sharpens the cutoff).Don't use when:
phase_amplitude_coupling.md.high >= sfreq/2, the low-pass arm is silently
dropped (logged at WARNING). low is also checked against
sfreq/2 for symmetry but a high low is operationally fine.low < high. Reversed cutoffs raise ValueError from MNE.bandpass:, is rejected.filter_length or a wavelet-based approach (cwt_morlet) when
narrow-band extraction is needed.Ordering relative to other operators.
notch_filter → bandpass_filter → drop_bads → ICA → resample → ...
notch_filter so power-line peaks are gone before the
bandpass shapes the spectrum. Otherwise the notch's residual
side-lobes survive into your analysis band.ICA — ICA convergence is meaningfully better on
band-limited data (FastICA likelihood is shaped by the spectral
envelope).resample — bandpass is a stronger anti-alias than
resample's internal filter when your target rate is close to your
upper cutoff.bipolar_ref for sEEG / ECoG — bipolar reference
flattens common-mode noise; bandpass cleans what remains.| Failure | Symptom | Detection |
|---|---|---|
| Cutoffs above Nyquist | Output looks unfiltered above sfreq/2 | Compute PSD; expect ≥ 53 dB attenuation in stop-band — if absent, the lowpass was silently disabled. |
Reversed cutoffs (low > high) | ValueError raised by MNE | Caught at parse; surfaces EasyBCIOperatorError(recoverable=True) with fallback_step="bandpass:1,40". |
| Narrow band on short data | Ringing at signal start/end | First/last filter_length / sfreq seconds show oscillation. Reject when duration < 4·filter_length / sfreq. |
| Bandpass on AC-coupled data with low < hardware cutoff | High-pass becomes a no-op | Compare PSD slope below low before/after — a non-flat slope means the hardware filter dominates. |
| Bandpass before notch | Power-line peak survives | Compute PSD; peak at 50 / 60 Hz with > 6 dB prominence after the bandpass. |
| Bandpass on spike waveforms | Action potentials become ringing oscillations | Mean spike-triggered amplitude drops > 50%; spike-detection thresholds break. Reject upstream by routing waveforms to spike_sorting. |
Auto-detection helper:
import numpy as np
from scipy.signal import welch
def diagnose_bandpass(data, sfreq, low, high):
"""Return (status, details) — 'ok' / 'nyquist_exceeded' / 'unfiltered'."""
f, p = welch(data, sfreq, nperseg=min(2048, data.shape[-1]))
if high is not None and high >= sfreq / 2:
return "nyquist_exceeded", {"requested_high": high, "nyquist": sfreq / 2}
in_band = (f >= (low or 0)) & (f <= (high or sfreq / 2))
if in_band.any():
in_band_db = 10 * np.log10(np.median(p[..., in_band]) + 1e-20)
out_band_db = 10 * np.log10(np.median(p[..., ~in_band]) + 1e-20)
if (in_band_db - out_band_db) < 20:
return "unfiltered", {"in_db": in_band_db, "out_db": out_band_db}
return "ok", {}
low (≥ 1 Hz) high-pass
introduces phase distortion in the < 5 Hz range that visibly shifts
ERP latencies. Use low=0.1 or zero-phase IIR.sfreq / low. At 2048 Hz with low=0.5, the filter is ≥ 8000
samples per channel — switch to method="iir" for offline analysis
if you don't need linear phase, or process in mne.io.read_raw_*
chunks via preload=False + filter() resume.notch_filter:50 first."""Standalone band-pass filter — drop into any environment."""
from __future__ import annotations
import logging
import numpy as np
import mne
logger = logging.getLogger(__name__)
def bandpass_filter(
data: np.ndarray,
sfreq: float,
low: float | None,
high: float | None,
*,
method: str = "fir",
order: int = 4,
) -> np.ndarray:
"""Band-pass filter ``data``.
Parameters
----------
data : ndarray, shape (n_channels, n_times)
Continuous neural data, float32 or float64.
sfreq : float
Sampling rate (Hz).
low, high : float or None
High-pass / low-pass cutoffs. Both may be None individually but
not simultaneously.
method : {"fir", "iir"}
FIR is the default; IIR is for online_inference only.
Returns
-------
ndarray
Same shape and dtype as input.
"""
if low is None and high is None:
raise ValueError("bandpass_filter: at least one of low/high must be set")
if low is not None and high is not None and low >= high:
raise ValueError(f"bandpass_filter: low ({low}) must be < high ({high})")
if high is not None and high >= sfreq / 2:
logger.warning(
"bandpass_filter: high=%.2f >= Nyquist=%.2f; disabling lowpass",
high, sfreq / 2,
)
high = None
info = mne.create_info(
[f"ch{i}" for i in range(data.shape[0])], sfreq, ch_types="eeg",
)
raw = mne.io.RawArray(data.astype(np.float64, copy=False), info, verbose="ERROR")
raw.filter(l_freq=low, h_freq=high, method=method, iir_params={"order": order} if method == "iir" else None, verbose="ERROR")
return raw.get_data().astype(data.dtype, copy=False)
The framework adapter wraps the standalone implementation in the
data_dict schema and emits step elapsed time for the four-stage
progress tracker:
from typing import Any, Dict
import numpy as np
from easybci_lib.tools.neural_processing.preprocess.step_cache import record_step_elapsed
from easybci_lib.tools.neural_processing.operator_errors import EasyBCIOperatorError
def operator_bandpass_filter(
data_dict: Dict[str, Any],
*,
low: float | None = None,
high: float | None = None,
method: str = "fir",
order: int = 4,
) -> Dict[str, Any]:
"""EasyBCI-adapted bandpass — see Standalone above for parameter semantics.
Modality coverage
-----------------
EEG: yes / MEG: yes / sEEG: yes / ECoG: yes / fNIRS: caveat (apply
AFTER modified_beer_lambert) / spike: NO (waveforms only — use
spike_sorting; LFP band is fine).
"""
import time
if data_dict.get("modality") == "spike_waveform":
raise EasyBCIOperatorError(
operator="bandpass_filter",
reason="bandpass would distort action-potential waveforms; route through spike_sorting instead.",
recoverable=False,
)
t0 = time.monotonic()
try:
new_data = bandpass_filter(
data_dict["data"],
data_dict["frequency"],
low, high,
method=method, order=order,
)
except ValueError as exc:
raise EasyBCIOperatorError(
operator="bandpass_filter",
reason=str(exc),
recoverable=True,
fallback_step=f"bandpass:1,40",
) from exc
elapsed = time.monotonic() - t0
out = dict(data_dict)
out["data"] = new_data
out["elapsed_s"] = elapsed
out["meta"] = {**out.get("meta", {}), "bandpass": {"low": low, "high": high, "method": method}}
record_step_elapsed("bandpass_filter", elapsed, data_dict.get("meta", {}).get("step_cache_key"))
return out
low=0.1 is
the safe default for ERP work.