Skip to content

Commit 444bcb3

Browse files
committed
[FIX] Replace statsmodels.robust.scale.mad with scipy.stats.median_abs_deviation to avoid enabling the GIL and remove statsmodels from dependencies
1 parent 6b6c1d7 commit 444bcb3

4 files changed

Lines changed: 29 additions & 5 deletions

File tree

‎.github/workflows/testing.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ jobs:
1414
strategy:
1515
max-parallel: 4
1616
matrix:
17-
python-version: ["3.10", "3.11", "3.12", "3.13"]
17+
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14", "3.14t"]
1818

1919
steps:
2020
- uses: actions/checkout@v7

‎meegkit/asr.py‎

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,7 @@
44
import numpy as np
55
import pyriemann
66
from pyriemann.geometry.mean import gmean as mean_covariance
7-
from scipy import linalg, signal
8-
from statsmodels.robust.scale import mad
7+
from scipy import linalg, signal, stats
98

109
from .utils import block_covariance, nonlinear_eigenspace
1110
from .utils.asr import (
@@ -407,7 +406,7 @@ def clean_windows(X, sfreq, max_bad_chans=0.2, zthresholds=[-3.5, 5],
407406

408407
# extra meegkit-specific criterion: drop windows whose across-channel
409408
# z-scores are nearly flat (very low MAD or std)
410-
bad_by_mad = mad(wz, c=1, axis=0) < .1
409+
bad_by_mad = stats.median_abs_deviation(wz, axis=0) < .1
411410
bad_by_std = np.std(wz, axis=0) < .1
412411
mask3 = np.logical_or(bad_by_mad, bad_by_std)
413412

‎requirements.txt‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,4 @@ scikit-learn
55
pandas
66
joblib
77
tqdm
8-
statsmodels
98
pyriemann>=0.12

‎tests/test_freethreading.py‎

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import subprocess
2+
import sys
3+
import sysconfig
4+
5+
import pytest
6+
7+
pytestmark = pytest.mark.skipif(
8+
not sysconfig.get_config_var("Py_GIL_DISABLED"),
9+
reason="requires a free-threaded CPython build",
10+
)
11+
12+
13+
def test_import_does_not_reenable_gil():
14+
# test dependencies (such as pytest extensions) could re-enable the GIL
15+
# without making the library non-freethreaded so we need to run this in
16+
# its own subprocess.
17+
proc = subprocess.run(
18+
[
19+
sys.executable,
20+
"-c",
21+
"import sys, meegkit; raise SystemExit(int(sys._is_gil_enabled()))",
22+
],
23+
capture_output=True,
24+
text=True,
25+
)
26+
assert proc.returncode == 0, f"stdout:\n{proc.stdout}\nstderr:\n{proc.stderr}"

0 commit comments

Comments
 (0)