diff --git a/Makefile b/Makefile index 544480e5..9ff41868 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ # simple makefile to simplify repetetive build env management tasks under posix # caution: testing won't work on windows, see README PYTHON ?= python -PYTESTS ?= py.test +PYTESTS ?= pytest CTAGS ?= ctags CODESPELL_SKIPS ?= "*.html,*.fif,*.eve,*.gz,*.tgz,*.zip,*.mat,*.stc,*.label,*.w,*.bz2,*.annot,*.sulc,*.log,*.local-copy,*.orig_avg,*.inflated_avg,*.gii,*.pyc,*.doctree,*.pickle,*.inv,*.png,*.edf,*.touch,*.thickness,*.nofix,*.volume,*.defect_borders,*.mgh,lh.*,rh.*,COR-*,FreeSurferColorLUT.txt,*.examples,.xdebug_mris_calc,bad.segments,BadChannels,*.hist,empty_file,*.orig,*.js,*.map,*.ipynb,searchindex.dat" CODESPELL_DIRS ?= meegkit/ examples/ @@ -85,21 +85,21 @@ install-dev: # Tests # ============================================================================= -test: in +test: rm -f .coverage - $(PYTESTS) -m 'not ultraslowtest' meegkit + $(PYTESTS) -m 'not ultraslowtest' -test-verbose: in +test-verbose: rm -f .coverage - $(PYTESTS) -m 'not ultraslowtest' meegkit --verbose + $(PYTESTS) -m 'not ultraslowtest' --verbose -test-fast: in +test-fast: rm -f .coverage - $(PYTESTS) -m 'not slowtest' meegkit + $(PYTESTS) -m 'not slowtest' -test-full: in +test-full: rm -f .coverage - $(PYTESTS) meegkit + $(PYTESTS) .PHONY: init test diff --git a/meegkit/asr.py b/meegkit/asr.py index 3f0acf8c..9124b1f1 100755 --- a/meegkit/asr.py +++ b/meegkit/asr.py @@ -541,7 +541,8 @@ def asr_calibrate(X, sfreq, cutoff=5, blocksize=100, win_len=0.5, sig = np.zeros(nc) for ichan in reversed(range(nc)): mu[ichan], sig[ichan], alpha, beta = fit_eeg_distribution( - rms[ichan], min_clean_fraction, max_dropout_fraction) + rms[ichan], min_clean_fraction, max_dropout_fraction, + step_sizes=[0.01, 0.01]) T = np.dot(np.diag(mu + cutoff * sig), V.T) logging.debug("[ASR] Calibration done.") diff --git a/meegkit/utils/asr.py b/meegkit/utils/asr.py index ae2fe0ec..263af8e2 100755 --- a/meegkit/utils/asr.py +++ b/meegkit/utils/asr.py @@ -10,7 +10,7 @@ def fit_eeg_distribution(X, min_clean_fraction=0.25, max_dropout_fraction=0.1, - fit_quantiles=[0.022, 0.6], step_sizes=[0.0220, 0.6000], + fit_quantiles=[0.022, 0.6], step_sizes=[0.01, 0.01], shape_range=SHAPE_RANGE): """Estimate the mean and SD of clean EEG from contaminated data. diff --git a/pyproject.toml b/pyproject.toml index 616281e1..cfe8bf98 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -98,6 +98,7 @@ filterwarnings = [ addopts = """ --color=yes --durations 10 + --noplots """ [tool.coverage.run] diff --git a/tests/test_asr.py b/tests/test_asr.py index 44f38769..d17db8c1 100644 --- a/tests/test_asr.py +++ b/tests/test_asr.py @@ -418,6 +418,39 @@ def test_clean_windows_offset_phase_drift(): assert abs(int(offsets_raw[-1]) - int(offsets_truncated[-1])) >= 10 +def test_fit_eeg_distribution_default_step_sizes(): + """Default step_sizes matches the documented [0.01, 0.01]. + + The old default [0.022, 0.6] (copied from fit_quantiles) collapses the + grid search, so callers that omit step_sizes get a degenerate fit. + """ + raw = np.load(os.path.join(THIS_FOLDER, "data", "eeg_raw.npy")) + sfreq = 250 + + # windowed-RMS vector for one channel, as asr_calibrate builds it + # internally (full recording so there are enough windows to matter) + N = int(np.round(0.5 * sfreq)) + step = int(np.round(N * (1 - 0.66))) + x = raw[0] + offsets = np.arange(0, len(x) - N, step).astype(int) + csum = np.zeros(len(x) + 1) + np.cumsum(x ** 2, out=csum[1:]) + rms = np.sqrt((csum[offsets + N] - csum[offsets]) / N) + + mu_default, sig_default, _, _ = fit_eeg_distribution(rms) + mu_explicit, sig_explicit, _, _ = fit_eeg_distribution( + rms, step_sizes=[0.01, 0.01]) + + # default must match the documented [0.01, 0.01] grid + np.testing.assert_allclose(mu_default, mu_explicit, rtol=1e-9) + np.testing.assert_allclose(sig_default, sig_explicit, rtol=1e-9) + + # the old [0.022, 0.6] default is degenerate -> materially different fit + mu_old, sig_old, _, _ = fit_eeg_distribution(rms, step_sizes=[0.022, 0.6]) + assert not np.allclose(mu_old, mu_explicit, rtol=1e-9) + assert not np.allclose(sig_old, sig_explicit, rtol=1e-9) + + if __name__ == "__main__": pytest.main([__file__]) # test_yulewalk(250, True)