From 05f8c27bbc9b473b96ea48baf53df4b48f5fe0fa Mon Sep 17 00:00:00 2001 From: Stefan Appelhoff Date: Mon, 13 Jul 2026 21:58:55 +0200 Subject: [PATCH] [DOC] ASR: fix numpydoc docstring rendering Three malformed numpydoc constructs rendered incorrectly in the Sphinx API docs: - clean_windows had a free-text paragraph inside the Parameters block, which numpydoc parsed as four garbage "parameters" (tuned., want, consider, data.); moved it to a Notes section. - ASR Attributes wrapped the names in double backticks (``zi_`` etc.), so the literal backticks showed in the rendered attribute headings. - cutoff in ASR and asr_calibrate used "cutoff:" (no space before the colon), which is not valid numpydoc name/type syntax. Add a regression test that parses the docstrings with numpydoc and checks that documented parameters match the signature and attribute names carry no markup. --- meegkit/asr.py | 24 +++++++++++++----------- tests/test_asr.py | 18 ++++++++++++++++++ 2 files changed, 31 insertions(+), 11 deletions(-) diff --git a/meegkit/asr.py b/meegkit/asr.py index 797d488c..75ea41aa 100755 --- a/meegkit/asr.py +++ b/meegkit/asr.py @@ -24,7 +24,7 @@ class ASR: ---------- sfreq : float Sampling rate of the data, in Hz. - cutoff: float + cutoff : float Standard deviation cutoff for rejection. X portions whose variance is larger than this threshold relative to the calibration data are considered missing data and will be removed. The most aggressive value @@ -63,18 +63,18 @@ class ASR: Attributes ---------- - ``zi_``: array, shape=(n_channels, filter_order) + zi_ : array, shape=(n_channels, filter_order) Filter initial conditions. - ``ab_``: 2-tuple + ab_ : 2-tuple Coefficients of an IIR filter that is used to shape the spectrum of the signal when calculating artifact statistics. The output signal does not go through this filter. This is an optional way to tune the sensitivity of the algorithm to each frequency component of the signal. The default filter is less sensitive at alpha and beta frequencies and more sensitive at delta (blinks) and gamma (muscle) frequencies. - ``cov_`` : array, shape=(channels, channels) + cov_ : array, shape=(channels, channels) Previous covariance matrix. - ``state_`` : dict + state_ : dict Previous ASR parameters (as derived by :func:`asr_calibrate`) for successive calls to :meth:`transform`. Required fields are: @@ -286,11 +286,6 @@ def clean_windows(X, sfreq, max_bad_chans=0.2, zthresholds=[-3.5, 5], a channel must lie (relative to a robust estimate of the clean EEG power distribution in the channel) for it to be considered "not bad". (default=[-3.5, 5]). - - The following are detail parameters that usually do not have to be tuned. - If you can't get the function to do what you want, you might consider - adapting these to your data. - win_len : float Window length that is used to check the data for artifact content. This is ideally as long as the expected time scale of the artifacts @@ -316,6 +311,13 @@ def clean_windows(X, sfreq, max_bad_chans=0.2, zthresholds=[-3.5, 5], sample_mask : boolean array, shape=(1, n_samples) Mask of retained samples (logical array). + Notes + ----- + ``win_len``, ``win_overlap``, ``min_clean_fraction`` and + ``max_dropout_fraction`` are detail parameters that usually do not have to + be tuned. If you can't get the function to do what you want, you might + consider adapting these to your data. + """ assert 0 < max_bad_chans < 1, "max_bad_chans must be a fraction !" @@ -432,7 +434,7 @@ def asr_calibrate(X, sfreq, cutoff=5, blocksize=100, win_len=0.5, or more). sfreq : float Sampling rate of the data, in Hz. - cutoff: float + cutoff : float Standard deviation cutoff for rejection. X portions whose variance is larger than this threshold relative to the calibration data are considered missing data and will be removed. The most aggressive value diff --git a/tests/test_asr.py b/tests/test_asr.py index 91f57502..811bcb38 100644 --- a/tests/test_asr.py +++ b/tests/test_asr.py @@ -1,4 +1,5 @@ """ASR test.""" +import inspect import os import matplotlib.pyplot as plt @@ -22,6 +23,23 @@ rng = np.random.default_rng(9) +@pytest.mark.parametrize("obj", (ASR, clean_windows, asr_calibrate, asr_process)) +def test_docstring_parameters(obj): + """Documented parameters must match the signature (no stray prose).""" + docscrape = pytest.importorskip("numpydoc.docscrape") + doc = docscrape.ClassDoc(obj) if inspect.isclass(obj) else docscrape.FunctionDoc(obj) + documented = {p.name for p in doc["Parameters"]} + signature = set(inspect.signature(obj).parameters) + assert documented <= signature, documented - signature + + +def test_docstring_attributes(): + """ASR attribute names must not carry literal RST markup.""" + docscrape = pytest.importorskip("numpydoc.docscrape") + for attr in docscrape.ClassDoc(ASR)["Attributes"]: + assert "`" not in attr.name, attr.name + + @pytest.mark.parametrize(argnames="sfreq", argvalues=(125, 250, 256, 2048)) def test_yulewalk(sfreq, show=False): """Test that my version of yulewelk works just like MATLAB's."""