fix(eeg): guard bandpass_filter against inverted l_freq/h_freq; document picks=all
Browse files
src/pipelines/eeg_pipeline.py
CHANGED
|
@@ -52,13 +52,24 @@ def bandpass_filter(
|
|
| 52 |
|
| 53 |
Args:
|
| 54 |
raw: Loaded `mne.io.BaseRaw` (call `.load_data()` first if from disk).
|
| 55 |
-
l_freq: Low-cut frequency in Hz.
|
| 56 |
h_freq: High-cut frequency in Hz.
|
| 57 |
|
| 58 |
Returns:
|
| 59 |
A filtered copy of `raw`.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 60 |
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 61 |
out = raw.copy()
|
|
|
|
|
|
|
| 62 |
out.filter(l_freq=l_freq, h_freq=h_freq, picks="all", verbose="ERROR")
|
| 63 |
logger.info("Bandpass filter applied: %.1f-%.1f Hz", l_freq, h_freq)
|
| 64 |
return out
|
|
|
|
| 52 |
|
| 53 |
Args:
|
| 54 |
raw: Loaded `mne.io.BaseRaw` (call `.load_data()` first if from disk).
|
| 55 |
+
l_freq: Low-cut frequency in Hz. Must be strictly less than `h_freq`.
|
| 56 |
h_freq: High-cut frequency in Hz.
|
| 57 |
|
| 58 |
Returns:
|
| 59 |
A filtered copy of `raw`.
|
| 60 |
+
|
| 61 |
+
Raises:
|
| 62 |
+
ValueError: if `l_freq >= h_freq`. MNE silently produces a corrupted
|
| 63 |
+
band-stop-like result on inverted inputs, so we guard up front.
|
| 64 |
"""
|
| 65 |
+
if l_freq >= h_freq:
|
| 66 |
+
raise ValueError(
|
| 67 |
+
f"l_freq ({l_freq}) must be strictly less than h_freq ({h_freq})"
|
| 68 |
+
)
|
| 69 |
+
|
| 70 |
out = raw.copy()
|
| 71 |
+
# picks="all" includes the EOG channel so the ICA step in
|
| 72 |
+
# remove_artifacts_with_ica sees a consistently-filtered EOG reference.
|
| 73 |
out.filter(l_freq=l_freq, h_freq=h_freq, picks="all", verbose="ERROR")
|
| 74 |
logger.info("Bandpass filter applied: %.1f-%.1f Hz", l_freq, h_freq)
|
| 75 |
return out
|
tests/pipelines/test_eeg_pipeline.py
CHANGED
|
@@ -81,3 +81,11 @@ class TestBandpassFilter:
|
|
| 81 |
original_mean = raw.get_data().mean()
|
| 82 |
_ = bandpass_filter(raw, l_freq=1.0, h_freq=40.0)
|
| 83 |
assert raw.get_data().mean() == pytest.approx(original_mean, rel=1e-12)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 81 |
original_mean = raw.get_data().mean()
|
| 82 |
_ = bandpass_filter(raw, l_freq=1.0, h_freq=40.0)
|
| 83 |
assert raw.get_data().mean() == pytest.approx(original_mean, rel=1e-12)
|
| 84 |
+
|
| 85 |
+
def test_rejects_inverted_frequency_range(self) -> None:
|
| 86 |
+
"""l_freq must be strictly < h_freq; otherwise raise instead of silently corrupting data."""
|
| 87 |
+
raw = self._load()
|
| 88 |
+
with pytest.raises(ValueError, match="must be strictly less than"):
|
| 89 |
+
bandpass_filter(raw, l_freq=40.0, h_freq=1.0)
|
| 90 |
+
with pytest.raises(ValueError, match="must be strictly less than"):
|
| 91 |
+
bandpass_filter(raw, l_freq=10.0, h_freq=10.0)
|