ワンクリックで
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.