Skip to content

Commit 107cd1b

Browse files
sappelhoffnbara
andauthored
[FIX] ASR: reuse full SHAPE_RANGE grid in clean_windows (include beta=3.5) (#115)
`clean_windows` hardcoded `np.arange(1.7, 3.5, 0.15)`, a 12-point grid that excludes the `beta=3.5` endpoint, overriding the correct 13-point `SHAPE_RANGE` that `fit_eeg_distribution` defaults to. Reuse `SHAPE_RANGE` so the KL grid search can select `beta=3.5`. ### Testing Added a regression test that spies on the `shape_range` argument `clean_windows` passes down (now the full 13-point grid incl 3.5), plus a fit-level test showing the dropped endpoint changes the selected shape parameter. Full `tests/test_asr.py` passes; `ruff` clean. Co-authored-by: Nicolas Barascud <10333715+nbara@users.noreply.github.com>
1 parent a4487f2 commit 107cd1b

2 files changed

Lines changed: 47 additions & 3 deletions

File tree

‎meegkit/asr.py‎

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,13 @@
88
from statsmodels.robust.scale import mad
99

1010
from .utils import block_covariance, nonlinear_eigenspace
11-
from .utils.asr import fit_eeg_distribution, geometric_median, yulewalk, yulewalk_filter
11+
from .utils.asr import (
12+
SHAPE_RANGE,
13+
fit_eeg_distribution,
14+
geometric_median,
15+
yulewalk,
16+
yulewalk_filter,
17+
)
1218

1319

1420
class ASR:
@@ -329,7 +335,7 @@ def clean_windows(X, sfreq, max_bad_chans=0.2, zthresholds=[-3.5, 5],
329335
# set internal variables
330336
truncate_quant = [0.0220, 0.6000]
331337
step_sizes = [0.01, 0.01]
332-
shape_range = np.arange(1.7, 3.5, 0.15)
338+
shape_range = SHAPE_RANGE
333339
max_bad_chans = np.round(X.shape[0] * max_bad_chans)
334340

335341
# set data indices

‎tests/test_asr.py‎

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
"""ASR test."""
22
import inspect
33
import os
4+
from unittest.mock import patch
45

56
import matplotlib.pyplot as plt
67
import numpy as np
78
import pytest
89
from scipy import signal
910

1011
from meegkit.asr import ASR, asr_calibrate, asr_process, clean_windows
11-
from meegkit.utils.asr import yulewalk, yulewalk_filter
12+
from meegkit.utils.asr import SHAPE_RANGE, fit_eeg_distribution, yulewalk, yulewalk_filter
1213
from meegkit.utils.matrix import sliding_window
1314

1415
# Data files
@@ -300,6 +301,43 @@ def test_asr_class(method, reref, show=False):
300301
ASR(Sfreq=150)
301302

302303

304+
def test_clean_windows_uses_full_shape_range():
305+
"""clean_windows passes the full 13-point SHAPE_RANGE (incl beta=3.5) down.
306+
307+
Spies on the shape_range argument to fit_eeg_distribution (6th positional).
308+
"""
309+
raw = np.load(os.path.join(THIS_FOLDER, "data", "eeg_raw.npy"))
310+
sfreq = 250
311+
312+
with patch(
313+
"meegkit.asr.fit_eeg_distribution", side_effect=fit_eeg_distribution
314+
) as mock_fit:
315+
clean_windows(raw, sfreq)
316+
317+
assert mock_fit.called
318+
captured = mock_fit.call_args.args[5]
319+
assert len(captured) == 13
320+
assert np.isclose(captured[-1], 3.5)
321+
assert np.allclose(captured, SHAPE_RANGE)
322+
323+
324+
def test_fit_eeg_distribution_shape_range_materiality():
325+
"""Dropping the beta=3.5 endpoint changes the selected shape parameter.
326+
327+
A bounded, near-uniform sample drives the grid search to the largest beta.
328+
"""
329+
# Bounded, near-uniform sample: favors the largest available beta.
330+
Y = np.linspace(-1, 1, 2000)
331+
332+
shape_range_trunc = np.arange(1.7, 3.5, 0.15)
333+
334+
beta_full = fit_eeg_distribution(Y, shape_range=SHAPE_RANGE)[3]
335+
beta_trunc = fit_eeg_distribution(Y, shape_range=shape_range_trunc)[3]
336+
337+
assert np.isclose(beta_full, 3.5)
338+
assert beta_trunc <= np.max(shape_range_trunc) + 1e-9
339+
assert beta_full > beta_trunc
340+
303341
def test_asr_calibrate_nonfinite_input():
304342
"""Non-finite calibration samples must not silently yield NaN M/T."""
305343
raw = np.load(os.path.join(THIS_FOLDER, "data", "eeg_raw.npy"))

0 commit comments

Comments
 (0)