diff --git a/.gitattributes b/.gitattributes
index 5b22c54a..4d1b53ff 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -1 +1,2 @@
pyprep/_version.py export-subst
+* text=auto
diff --git a/.github/workflows/python_build.yml b/.github/workflows/python_build.yml
index b14c5ce7..c31ca52e 100644
--- a/.github/workflows/python_build.yml
+++ b/.github/workflows/python_build.yml
@@ -5,9 +5,6 @@ on:
branches: [ main ]
pull_request:
branches: [ main ]
- create:
- branches: [ main ]
- tags: [ '**' ]
schedule:
- cron: "0 4 * * MON"
@@ -18,7 +15,7 @@ jobs:
fail-fast: false
matrix:
os: [ubuntu-latest]
- python-version: ["3.11"]
+ python-version: ["3.12"]
env:
TZ: Europe/Berlin
FORCE_COLOR: true
diff --git a/.github/workflows/python_publish.yml b/.github/workflows/python_publish.yml
index 3831d5ad..01fcd18a 100644
--- a/.github/workflows/python_publish.yml
+++ b/.github/workflows/python_publish.yml
@@ -20,7 +20,7 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v5
with:
- python-version: "3.11"
+ python-version: "3.12"
- name: Install dependencies
run: |
python -m pip install --upgrade pip
diff --git a/.github/workflows/python_tests.yml b/.github/workflows/python_tests.yml
index a22f5960..78ca71d6 100644
--- a/.github/workflows/python_tests.yml
+++ b/.github/workflows/python_tests.yml
@@ -5,9 +5,6 @@ on:
branches: [ main ]
pull_request:
branches: [ main ]
- create:
- branches: [ main ]
- tags: [ '**' ]
schedule:
- cron: "0 4 * * MON"
@@ -17,13 +14,13 @@ jobs:
fail-fast: false
matrix:
platform: [ubuntu-latest, macos-latest, windows-latest]
- python-version: ["3.11"]
+ python-version: ["3.12"]
mne-version: [mne-stable]
include:
# Test mne development version only on ubuntu
- platform: ubuntu-latest
- python-version: "3.11"
+ python-version: "3.12"
mne-version: mne-main
run-as-extra: true
diff --git a/.readthedocs.yml b/.readthedocs.yml
index 9dcf6125..fbcc0676 100644
--- a/.readthedocs.yml
+++ b/.readthedocs.yml
@@ -8,7 +8,7 @@ version: 2
build:
os: ubuntu-22.04
tools:
- python: "3.11"
+ python: "3.12"
# Build documentation in the docs/ directory with Sphinx
sphinx:
diff --git a/README.rst b/README.rst
index a27cb61a..9d6633f0 100644
--- a/README.rst
+++ b/README.rst
@@ -16,7 +16,7 @@
.. image:: https://readthedocs.org/projects/pyprep/badge/?version=latest
- :target: http://pyprep.readthedocs.io/en/latest/?badge=latest
+ :target: https://pyprep.readthedocs.io/en/latest/?badge=latest
:alt: Documentation Status
@@ -37,8 +37,8 @@ pyprep
For documentation, see the:
-- `stable documentation `_
-- `latest (development) documentation `_
+- `stable documentation `_
+- `latest (development) documentation `_
.. docs_readme_include_label
diff --git a/docs/whats_new.rst b/docs/whats_new.rst
index 4bc0696e..f3491c85 100644
--- a/docs/whats_new.rst
+++ b/docs/whats_new.rst
@@ -219,7 +219,7 @@ Changelog
- Initial commit: 2018-04-12
- Miscellaneous changes
-.. _Stefan Appelhoff: http://stefanappelhoff.com/
+.. _Stefan Appelhoff: https://stefanappelhoff.com/
.. _Aamna Lawrence: https://github.com/AamnaLawrence
.. _Adam Li: https://github.com/adam2392/
.. _Christian O'Reilly: https://github.com/christian-oreilly
diff --git a/pyprep/find_noisy_channels.py b/pyprep/find_noisy_channels.py
index d849fd22..4dd7e8a4 100644
--- a/pyprep/find_noisy_channels.py
+++ b/pyprep/find_noisy_channels.py
@@ -1,6 +1,4 @@
"""finds bad channels."""
-from copy import copy
-
import mne
import numpy as np
from mne.utils import check_random_state, logger
@@ -579,7 +577,14 @@ def find_bad_by_ransac(
exclude_from_ransac = (
self.bad_by_correlation + self.bad_by_deviation + self.bad_by_dropout
)
- rng = copy(self.random_state) if self.matlab_strict else self.random_state
+
+ if self.matlab_strict:
+ random_state = self.random_state.get_state()
+ rng = np.random.RandomState()
+ rng.set_state(random_state)
+ else:
+ rng = self.random_state
+
self.bad_by_ransac, ch_correlations_usable = find_bad_by_ransac(
self.EEGFiltered,
self.sample_rate,
diff --git a/pyprep/removeTrend.py b/pyprep/removeTrend.py
index f75af617..1d106781 100644
--- a/pyprep/removeTrend.py
+++ b/pyprep/removeTrend.py
@@ -170,5 +170,5 @@ def runline(y, n, dn):
(np.multiply(np.arange(n + 1, n + npts + 1), a) + b), (npts, 1)
)
for i in range(0, len(y_line)):
- y[i] = y[i] - y_line[i]
+ y[i] = y[i] - y_line[i, 0]
return y
diff --git a/pyprep/utils.py b/pyprep/utils.py
index e32bad5c..ae296789 100644
--- a/pyprep/utils.py
+++ b/pyprep/utils.py
@@ -76,7 +76,7 @@ def _mat_quantile(arr, q, axis=None):
# Sort the array in ascending order along the given axis (any NaNs go to the end)
# Return NaN if array is empty.
if len(arr) == 0:
- return np.NaN
+ return np.nan
arr_sorted = np.sort(arr, axis=axis)
# Ensure array is either 1D or 2D
@@ -182,7 +182,7 @@ def _eeglab_create_highpass(cutoff, srate):
N = order + 1
filt = np.zeros(N)
filt[N // 2] = 1
- filt -= firwin(N, transition, window="hamming", nyq=1)
+ filt -= firwin(N, transition, window="hamming")
return filt
@@ -215,7 +215,7 @@ def _eeglab_fir_filter(data, filt):
pad_len = min(group_delay, n_samples)
# Prepare initial state of filter, using padding at start of data
- start_pad_idx = np.zeros(pad_len, dtype=np.uint8)
+ start_pad_idx = np.zeros(pad_len, dtype=np.uint)
start_padded = np.concatenate((data[:, start_pad_idx], data[:, :pad_len]), axis=1)
zi_init = lfilter_zi(filt, 1) * np.take(start_padded, [0], axis=0)
_, zi = lfilter(filt, 1, start_padded, axis=1, zi=zi_init)
@@ -232,7 +232,7 @@ def _eeglab_fir_filter(data, filt):
)
# Finish filtering data, using padding at end to calculate final values
- end_pad_idx = np.zeros(pad_len, dtype=np.uint8) + (n_samples - 1)
+ end_pad_idx = np.zeros(pad_len, dtype=np.uint) + (n_samples - 1)
end, _ = lfilter(filt, 1, data[:, end_pad_idx], axis=1, zi=zi)
out[:, (n_samples - pad_len) :] = end[:, (group_delay - pad_len) :]
diff --git a/tests/conftest.py b/tests/conftest.py
index 131691bd..5edbf334 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -77,7 +77,7 @@ def make_random_mne_object(
n_freq_comps=5,
freq_range=[10, 60],
scale=1e-6,
- RNG=np.random.RandomState(1337),
+ rng=np.random.default_rng(1337),
):
"""Make a random MNE object to use for testing.
@@ -98,8 +98,9 @@ def make_random_mne_object(
scale : float
Scaling factor applied to the signal in volts. For example 1e-6 to
get microvolts.
- RNG : np.random.RandomState
- Random state seed.
+ rng : np.random.Generator
+ The random number generator object. Must be created with
+ ``np.random.default_rng``.
Returns
-------
@@ -120,8 +121,8 @@ def make_random_mne_object(
high = freq_range[1]
for chan in range(n_chans):
# Each channel signal is a sum of random freq sine waves
- for freq_i in range(n_freq_comps):
- freq = RNG.randint(low, high, signal_len)
+ for _ in range(n_freq_comps):
+ freq = rng.integers(low, high, signal_len)
signal[chan, :] += np.sin(2 * np.pi * times * freq)
signal *= scale # scale
diff --git a/tests/test_find_noisy_channels.py b/tests/test_find_noisy_channels.py
index 315aa421..e29b9f16 100644
--- a/tests/test_find_noisy_channels.py
+++ b/tests/test_find_noisy_channels.py
@@ -1,7 +1,6 @@
"""Test the find_noisy_channels module."""
import numpy as np
import pytest
-from numpy.random import RandomState
from pyprep.find_noisy_channels import NoisyChannels
from pyprep.ransac import find_bad_by_ransac
@@ -9,7 +8,7 @@
# Set a fixed random seed for reproducible test results
-RNG = RandomState(30)
+rng = np.random.default_rng(30)
# Define some fixtures and utility functions for use across multiple tests
@@ -47,7 +46,7 @@ def raw_tmp(raw_clean_detrend):
def _generate_signal(fmin, fmax, timepoints, fcount=1):
"""Generate an EEG signal from one or more sine waves in a frequency range."""
signal = np.zeros_like(timepoints)
- for freq in RNG.randint(fmin, fmax + 1, fcount):
+ for freq in rng.integers(fmin, fmax + 1, fcount):
signal += np.sin(2 * np.pi * timepoints * freq)
return signal * 1e-6
@@ -59,7 +58,7 @@ def test_bad_by_nan(raw_tmp):
"""Test the detection of channels containing any NaN values."""
# Insert a NaN value into a random channel
n_chans = raw_tmp.get_data().shape[0]
- nan_idx = int(RNG.randint(0, n_chans, 1))
+ nan_idx = int(rng.integers(0, n_chans, 1)[0])
raw_tmp._data[nan_idx, 3] = np.nan
# Test automatic detection of NaN channels on NoisyChannels init
@@ -75,7 +74,7 @@ def test_bad_by_flat(raw_tmp):
"""Test the detection of channels with flat or very weak signals."""
# Make the signal for a random channel extremely weak
n_chans = raw_tmp.get_data().shape[0]
- flat_idx = int(RNG.randint(0, n_chans, 1))
+ flat_idx = int(rng.integers(0, n_chans, 1)[0])
raw_tmp._data[flat_idx, :] = raw_tmp.get_data()[flat_idx, :] * 1e-12
# Test automatic detection of flat channels on NoisyChannels init
@@ -100,7 +99,7 @@ def test_bad_by_deviation(raw_tmp):
# Make the signal for a random channel have a very high amplitude
n_chans = raw_tmp.get_data().shape[0]
- high_dev_idx = int(RNG.randint(0, n_chans, 1))
+ high_dev_idx = int(rng.integers(0, n_chans, 1)[0])
raw_tmp._data[high_dev_idx, :] *= high_dev_factor
# Test detection of abnormally high-amplitude channels
@@ -126,7 +125,7 @@ def test_bad_by_hf_noise(raw_tmp):
"""Test detection of channels with high-frequency noise."""
# Add some noise between 70 & 80 Hz to the signal of a random channel
n_chans = raw_tmp.get_data().shape[0]
- hf_noise_idx = int(RNG.randint(0, n_chans, 1))
+ hf_noise_idx = int(rng.integers(0, n_chans, 1)[0])
hf_noise = _generate_signal(70, 80, raw_tmp.times, 5) * 10
raw_tmp._data[hf_noise_idx, :] += hf_noise
@@ -148,7 +147,7 @@ def test_bad_by_dropout(raw_tmp):
"""Test detection of channels with excessive portions of flat signal."""
# Add large dropout portions to the signal of a random channel
n_chans, n_samples = raw_tmp.get_data().shape
- dropout_idx = int(RNG.randint(0, n_chans, 1))
+ dropout_idx = int(rng.integers(0, n_chans, 1)[0])
x1, x2 = (int(n_samples / 10), int(2 * n_samples / 10))
raw_tmp._data[dropout_idx, x1:x2] = 0 # flatten 10% of signal
@@ -162,7 +161,7 @@ def test_bad_by_correlation(raw_tmp):
"""Test detection of channels that correlate poorly with others."""
# Replace a random channel's signal with uncorrelated values
n_chans, n_samples = raw_tmp.get_data().shape
- low_corr_idx = int(RNG.randint(0, n_chans, 1))
+ low_corr_idx = int(rng.integers(0, n_chans, 1)[0])
raw_tmp._data[low_corr_idx, :] = _generate_signal(10, 30, raw_tmp.times, 5)
# Test detection of channels that correlate poorly with others
@@ -187,7 +186,7 @@ def test_bad_by_SNR(raw_tmp):
"""Test detection of channels that have low signal-to-noise ratios."""
# Replace a random channel's signal with uncorrelated values
n_chans = raw_tmp.get_data().shape[0]
- low_snr_idx = int(RNG.randint(0, n_chans, 1))
+ low_snr_idx = int(rng.integers(0, n_chans, 1)[0])
raw_tmp._data[low_snr_idx, :] = _generate_signal(10, 30, raw_tmp.times, 5)
# Add some high-frequency noise to the uncorrelated channel
@@ -203,7 +202,7 @@ def test_bad_by_SNR(raw_tmp):
def test_find_bad_by_ransac(raw_tmp):
"""Test the RANSAC component of NoisyChannels."""
# Set a consistent random seed for all RANSAC runs
- RANSAC_RNG = 435656
+ ransac_rng = 435656
# RANSAC identifies channels that go bad together and are highly correlated.
# Inserting highly correlated signal in channels 0 through 6 at 30 Hz
@@ -222,7 +221,7 @@ def test_find_bad_by_ransac(raw_tmp):
corr = {}
for name, args in test_matrix.items():
nd = NoisyChannels(
- raw_tmp, do_detrend=False, random_state=RANSAC_RNG, matlab_strict=args[0]
+ raw_tmp, do_detrend=False, random_state=ransac_rng, matlab_strict=args[0]
)
nd.find_bad_by_ransac(channel_wise=args[1], max_chunk_size=args[2])
# Save bad channels and RANSAC correlation matrix for later comparison
@@ -247,7 +246,7 @@ def test_find_bad_by_ransac(raw_tmp):
assert not np.allclose(corr["by_window"], corr["by_window_strict"])
# Ensure that RANSAC doesn't change random state if in MATLAB-strict mode
- rng = RandomState(RANSAC_RNG)
+ rng = np.random.RandomState(ransac_rng)
init_state = rng.get_state()[2]
nd = NoisyChannels(raw_tmp, do_detrend=False, random_state=rng, matlab_strict=True)
nd.find_bad_by_ransac()
diff --git a/tests/test_prep_pipeline.py b/tests/test_prep_pipeline.py
index b3f854fa..25acfc88 100644
--- a/tests/test_prep_pipeline.py
+++ b/tests/test_prep_pipeline.py
@@ -223,7 +223,7 @@ def test_prep_pipeline_non_eeg(raw, montage):
ch_types_non_eeg,
times,
sfreq,
- RNG=np.random.RandomState(1337),
+ rng=np.random.default_rng(1337),
)
raw_copy.add_channels([raw_non_eeg], force_update_info=True)
diff --git a/tests/test_utils.py b/tests/test_utils.py
index 65039f6d..03ba2eea 100644
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -1,7 +1,6 @@
"""Test various helper functions."""
import numpy as np
import pytest
-from numpy.random import RandomState
from pyprep.utils import (
_correlate_arrays,
@@ -64,7 +63,7 @@ def test_mat_quantile_iqr():
# Add NaNs to test data
tst_nan = tst.copy()
- tst_nan[0, :] = np.NaN
+ tst_nan[0, :] = np.nan
# Create arrays containing MATLAB results for NaN test case
quantile_expected = np.asarray([0.9712, 0.9880, 0.9807])
@@ -91,7 +90,7 @@ def test_mat_quantile_iqr():
def test_get_random_subset():
"""Test the function for getting random channel subsets."""
# Generate test data
- rng = RandomState(435656)
+ rng = np.random.RandomState(435656)
chans = range(1, 61)
# Compare random subset equivalence with MATLAB