py-neuromodulation 0.0.5__py3-none-any.whl → 0.0.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- py_neuromodulation/__init__.py +16 -10
- py_neuromodulation/{nm_RMAP.py → analysis/RMAP.py} +2 -2
- py_neuromodulation/analysis/__init__.py +4 -0
- py_neuromodulation/{nm_decode.py → analysis/decode.py} +4 -4
- py_neuromodulation/{nm_analysis.py → analysis/feature_reader.py} +21 -20
- py_neuromodulation/{nm_plots.py → analysis/plots.py} +54 -12
- py_neuromodulation/{nm_stats.py → analysis/stats.py} +2 -8
- py_neuromodulation/{nm_settings.yaml → default_settings.yaml} +6 -9
- py_neuromodulation/features/__init__.py +31 -0
- py_neuromodulation/features/bandpower.py +165 -0
- py_neuromodulation/{nm_bispectra.py → features/bispectra.py} +8 -5
- py_neuromodulation/{nm_bursts.py → features/bursts.py} +14 -9
- py_neuromodulation/{nm_coherence.py → features/coherence.py} +17 -13
- py_neuromodulation/{nm_features.py → features/feature_processor.py} +30 -53
- py_neuromodulation/{nm_fooof.py → features/fooof.py} +11 -8
- py_neuromodulation/{nm_hjorth_raw.py → features/hjorth_raw.py} +10 -5
- py_neuromodulation/{nm_linelength.py → features/linelength.py} +1 -1
- py_neuromodulation/{nm_mne_connectivity.py → features/mne_connectivity.py} +5 -6
- py_neuromodulation/{nm_nolds.py → features/nolds.py} +5 -7
- py_neuromodulation/{nm_oscillatory.py → features/oscillatory.py} +7 -181
- py_neuromodulation/{nm_sharpwaves.py → features/sharpwaves.py} +13 -4
- py_neuromodulation/filter/__init__.py +3 -0
- py_neuromodulation/{nm_kalmanfilter.py → filter/kalman_filter.py} +67 -71
- py_neuromodulation/filter/kalman_filter_external.py +1890 -0
- py_neuromodulation/{nm_filter.py → filter/mne_filter.py} +128 -219
- py_neuromodulation/filter/notch_filter.py +93 -0
- py_neuromodulation/processing/__init__.py +10 -0
- py_neuromodulation/{nm_artifacts.py → processing/artifacts.py} +2 -3
- py_neuromodulation/{nm_preprocessing.py → processing/data_preprocessor.py} +19 -25
- py_neuromodulation/{nm_filter_preprocessing.py → processing/filter_preprocessing.py} +3 -4
- py_neuromodulation/{nm_normalization.py → processing/normalization.py} +9 -7
- py_neuromodulation/{nm_projection.py → processing/projection.py} +14 -14
- py_neuromodulation/{nm_rereference.py → processing/rereference.py} +13 -13
- py_neuromodulation/{nm_resample.py → processing/resample.py} +1 -4
- py_neuromodulation/stream/__init__.py +3 -0
- py_neuromodulation/{nm_run_analysis.py → stream/data_processor.py} +42 -42
- py_neuromodulation/stream/generator.py +53 -0
- py_neuromodulation/{nm_mnelsl_generator.py → stream/mnelsl_player.py} +10 -6
- py_neuromodulation/{nm_mnelsl_stream.py → stream/mnelsl_stream.py} +13 -9
- py_neuromodulation/{nm_settings.py → stream/settings.py} +27 -24
- py_neuromodulation/{nm_stream.py → stream/stream.py} +217 -188
- py_neuromodulation/utils/__init__.py +2 -0
- py_neuromodulation/{nm_define_nmchannels.py → utils/channels.py} +14 -9
- py_neuromodulation/{nm_database.py → utils/database.py} +2 -2
- py_neuromodulation/{nm_IO.py → utils/io.py} +42 -77
- py_neuromodulation/utils/keyboard.py +52 -0
- py_neuromodulation/{nm_logger.py → utils/logging.py} +3 -3
- py_neuromodulation/{nm_types.py → utils/types.py} +72 -14
- {py_neuromodulation-0.0.5.dist-info → py_neuromodulation-0.0.6.dist-info}/METADATA +3 -11
- py_neuromodulation-0.0.6.dist-info/RECORD +89 -0
- py_neuromodulation/FieldTrip.py +0 -589
- py_neuromodulation/_write_example_dataset_helper.py +0 -83
- py_neuromodulation/nm_generator.py +0 -45
- py_neuromodulation/nm_stream_abc.py +0 -166
- py_neuromodulation-0.0.5.dist-info/RECORD +0 -83
- {py_neuromodulation-0.0.5.dist-info → py_neuromodulation-0.0.6.dist-info}/WHEEL +0 -0
- {py_neuromodulation-0.0.5.dist-info → py_neuromodulation-0.0.6.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,219 +1,128 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
:,
|
|
130
|
-
middle_index - data.shape[1] // 2 : middle_index + data.shape[1] // 2,
|
|
131
|
-
]
|
|
132
|
-
|
|
133
|
-
return filtered
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
class NotchFilter(NMPreprocessor):
|
|
137
|
-
def __init__(
|
|
138
|
-
self,
|
|
139
|
-
sfreq: float,
|
|
140
|
-
line_noise: float | None = None,
|
|
141
|
-
freqs: np.ndarray | None = None,
|
|
142
|
-
notch_widths: int | np.ndarray | None = 3,
|
|
143
|
-
trans_bandwidth: float = 6.8,
|
|
144
|
-
) -> None:
|
|
145
|
-
if line_noise is None and freqs is None:
|
|
146
|
-
raise ValueError(
|
|
147
|
-
"Either line_noise or freqs must be defined if notch_filter is"
|
|
148
|
-
"activated."
|
|
149
|
-
)
|
|
150
|
-
|
|
151
|
-
if freqs is None:
|
|
152
|
-
freqs = np.arange(line_noise, sfreq / 2, line_noise, dtype=int)
|
|
153
|
-
|
|
154
|
-
if freqs.size > 0 and freqs[-1] >= sfreq / 2:
|
|
155
|
-
freqs = freqs[:-1]
|
|
156
|
-
|
|
157
|
-
# Code is copied from filter.py notch_filter
|
|
158
|
-
if freqs.size == 0:
|
|
159
|
-
self.filter_bank = None
|
|
160
|
-
logger.warning(
|
|
161
|
-
"WARNING: notch_filter is activated but data is not being"
|
|
162
|
-
" filtered. This may be due to a low sampling frequency or"
|
|
163
|
-
" incorrect specifications. Make sure your settings are"
|
|
164
|
-
f" correct. Got: {sfreq = }, {line_noise = }, {freqs = }."
|
|
165
|
-
)
|
|
166
|
-
return
|
|
167
|
-
|
|
168
|
-
filter_length = int(sfreq - 1)
|
|
169
|
-
if notch_widths is None:
|
|
170
|
-
notch_widths = freqs / 200.0
|
|
171
|
-
elif np.any(notch_widths < 0):
|
|
172
|
-
raise ValueError("notch_widths must be >= 0")
|
|
173
|
-
else:
|
|
174
|
-
notch_widths = np.atleast_1d(notch_widths)
|
|
175
|
-
if len(notch_widths) == 1:
|
|
176
|
-
notch_widths = notch_widths[0] * np.ones_like(freqs)
|
|
177
|
-
elif len(notch_widths) != len(freqs):
|
|
178
|
-
raise ValueError(
|
|
179
|
-
"notch_widths must be None, scalar, or the " "same length as freqs"
|
|
180
|
-
)
|
|
181
|
-
notch_widths = cast(np.ndarray, notch_widths) # For MyPy only, no runtime cost
|
|
182
|
-
|
|
183
|
-
# Speed this up by computing the fourier coefficients once
|
|
184
|
-
tb_half = trans_bandwidth / 2.0
|
|
185
|
-
lows = [freq - nw / 2.0 - tb_half for freq, nw in zip(freqs, notch_widths)]
|
|
186
|
-
highs = [freq + nw / 2.0 + tb_half for freq, nw in zip(freqs, notch_widths)]
|
|
187
|
-
|
|
188
|
-
self.filter_bank = create_filter(
|
|
189
|
-
data=None,
|
|
190
|
-
sfreq=sfreq,
|
|
191
|
-
l_freq=highs,
|
|
192
|
-
h_freq=lows,
|
|
193
|
-
filter_length=filter_length, # type: ignore
|
|
194
|
-
l_trans_bandwidth=tb_half, # type: ignore
|
|
195
|
-
h_trans_bandwidth=tb_half, # type: ignore
|
|
196
|
-
method="fir",
|
|
197
|
-
iir_params=None,
|
|
198
|
-
phase="zero",
|
|
199
|
-
fir_window="hamming",
|
|
200
|
-
fir_design="firwin",
|
|
201
|
-
verbose=False,
|
|
202
|
-
)
|
|
203
|
-
|
|
204
|
-
def process(self, data: np.ndarray) -> np.ndarray:
|
|
205
|
-
if self.filter_bank is None:
|
|
206
|
-
return data
|
|
207
|
-
|
|
208
|
-
from mne.filter import _overlap_add_filter
|
|
209
|
-
|
|
210
|
-
return _overlap_add_filter(
|
|
211
|
-
x=data,
|
|
212
|
-
h=self.filter_bank,
|
|
213
|
-
n_fft=None,
|
|
214
|
-
phase="zero",
|
|
215
|
-
picks=None,
|
|
216
|
-
n_jobs=1,
|
|
217
|
-
copy=True,
|
|
218
|
-
pad="reflect_limited",
|
|
219
|
-
)
|
|
1
|
+
import numpy as np
|
|
2
|
+
from collections.abc import Sequence
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class MNEFilter:
|
|
6
|
+
"""mne.filter wrapper
|
|
7
|
+
|
|
8
|
+
This class stores for given frequency band ranges the filter
|
|
9
|
+
coefficients with length "filter_len".
|
|
10
|
+
The filters can then be used sequentially for band power estimation with
|
|
11
|
+
apply_filter().
|
|
12
|
+
Note that this filter can be a bandpass, bandstop, lowpass, or highpass filter
|
|
13
|
+
depending on the frequency ranges given (see further details in mne.filter.create_filter).
|
|
14
|
+
|
|
15
|
+
Parameters
|
|
16
|
+
----------
|
|
17
|
+
f_ranges : list[tuple[float | None, float | None]]
|
|
18
|
+
sfreq : float
|
|
19
|
+
Sampling frequency.
|
|
20
|
+
filter_length : str, optional
|
|
21
|
+
Filter length. Human readable (e.g. "1000ms", "1s"), by default "999ms"
|
|
22
|
+
l_trans_bandwidth : float | str, optional
|
|
23
|
+
Length of the lower transition band or "auto", by default 4
|
|
24
|
+
h_trans_bandwidth : float | str, optional
|
|
25
|
+
Length of the higher transition band or "auto", by default 4
|
|
26
|
+
verbose : bool | None, optional
|
|
27
|
+
Verbosity level, by default None
|
|
28
|
+
|
|
29
|
+
Attributes
|
|
30
|
+
----------
|
|
31
|
+
filter_bank: np.ndarray shape (n,)
|
|
32
|
+
Factor to upsample by.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
def __init__(
|
|
36
|
+
self,
|
|
37
|
+
f_ranges: Sequence[tuple[float | None, float | None]],
|
|
38
|
+
sfreq: float,
|
|
39
|
+
filter_length: str | float = "999ms",
|
|
40
|
+
l_trans_bandwidth: float | str = 4,
|
|
41
|
+
h_trans_bandwidth: float | str = 4,
|
|
42
|
+
verbose: bool | int | str | None = None,
|
|
43
|
+
) -> None:
|
|
44
|
+
from mne.filter import create_filter
|
|
45
|
+
|
|
46
|
+
filter_bank = []
|
|
47
|
+
# mne create_filter function only accepts str and int for filter_length
|
|
48
|
+
if isinstance(filter_length, float):
|
|
49
|
+
filter_length = int(filter_length)
|
|
50
|
+
|
|
51
|
+
for f_range in f_ranges:
|
|
52
|
+
try:
|
|
53
|
+
filt = create_filter(
|
|
54
|
+
None,
|
|
55
|
+
sfreq,
|
|
56
|
+
l_freq=f_range[0],
|
|
57
|
+
h_freq=f_range[1],
|
|
58
|
+
fir_design="firwin",
|
|
59
|
+
l_trans_bandwidth=l_trans_bandwidth, # type: ignore
|
|
60
|
+
h_trans_bandwidth=h_trans_bandwidth, # type: ignore
|
|
61
|
+
filter_length=filter_length, # type: ignore
|
|
62
|
+
verbose=verbose,
|
|
63
|
+
)
|
|
64
|
+
except ValueError:
|
|
65
|
+
filt = create_filter(
|
|
66
|
+
None,
|
|
67
|
+
sfreq,
|
|
68
|
+
l_freq=f_range[0],
|
|
69
|
+
h_freq=f_range[1],
|
|
70
|
+
fir_design="firwin",
|
|
71
|
+
verbose=verbose,
|
|
72
|
+
# filter_length=filter_length,
|
|
73
|
+
)
|
|
74
|
+
filter_bank.append(filt)
|
|
75
|
+
|
|
76
|
+
self.num_filters = len(filter_bank)
|
|
77
|
+
self.filter_bank = np.vstack(filter_bank)
|
|
78
|
+
|
|
79
|
+
self.filters: np.ndarray
|
|
80
|
+
self.num_channels = -1
|
|
81
|
+
|
|
82
|
+
def filter_data(self, data: np.ndarray) -> np.ndarray:
|
|
83
|
+
"""Apply previously calculated (bandpass) filters to data.
|
|
84
|
+
|
|
85
|
+
Parameters
|
|
86
|
+
----------
|
|
87
|
+
data : np.ndarray (n_samples, ) or (n_channels, n_samples)
|
|
88
|
+
Data to be filtered
|
|
89
|
+
filter_bank : np.ndarray, shape (n_fbands, filter_len)
|
|
90
|
+
Output of calc_bandpass_filters.
|
|
91
|
+
|
|
92
|
+
Returns
|
|
93
|
+
-------
|
|
94
|
+
np.ndarray, shape (n_channels, n_fbands, n_samples)
|
|
95
|
+
Filtered data.
|
|
96
|
+
|
|
97
|
+
"""
|
|
98
|
+
from scipy.signal import fftconvolve
|
|
99
|
+
|
|
100
|
+
if data.ndim > 2:
|
|
101
|
+
raise ValueError(
|
|
102
|
+
f"Data must have one or two dimensions. Got:"
|
|
103
|
+
f" {data.ndim} dimensions."
|
|
104
|
+
)
|
|
105
|
+
if data.ndim == 1:
|
|
106
|
+
data = np.expand_dims(data, axis=0)
|
|
107
|
+
|
|
108
|
+
if self.num_channels == -1:
|
|
109
|
+
self.num_channels = data.shape[0]
|
|
110
|
+
self.filters = np.tile(
|
|
111
|
+
self.filter_bank[None, :, :], (self.num_channels, 1, 1)
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
data_tiled = np.tile(data[:, None, :], (1, self.num_filters, 1))
|
|
115
|
+
|
|
116
|
+
filtered = fftconvolve(data_tiled, self.filters, axes=2, mode="same")
|
|
117
|
+
|
|
118
|
+
# ensure here that the output dimension matches the input dimension
|
|
119
|
+
if data.shape[1] != filtered.shape[-1]:
|
|
120
|
+
# select the middle part of the filtered data
|
|
121
|
+
middle_index = filtered.shape[-1] // 2
|
|
122
|
+
filtered = filtered[
|
|
123
|
+
:,
|
|
124
|
+
:,
|
|
125
|
+
middle_index - data.shape[1] // 2 : middle_index + data.shape[1] // 2,
|
|
126
|
+
]
|
|
127
|
+
|
|
128
|
+
return filtered
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from typing import cast
|
|
3
|
+
|
|
4
|
+
from py_neuromodulation.utils.types import NMPreprocessor
|
|
5
|
+
from py_neuromodulation import logger
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class NotchFilter(NMPreprocessor):
|
|
9
|
+
def __init__(
|
|
10
|
+
self,
|
|
11
|
+
sfreq: float,
|
|
12
|
+
line_noise: float | None = None,
|
|
13
|
+
freqs: np.ndarray | None = None,
|
|
14
|
+
notch_widths: int | np.ndarray | None = 3,
|
|
15
|
+
trans_bandwidth: float = 6.8,
|
|
16
|
+
) -> None:
|
|
17
|
+
from mne.filter import create_filter
|
|
18
|
+
|
|
19
|
+
if line_noise is None and freqs is None:
|
|
20
|
+
raise ValueError(
|
|
21
|
+
"Either line_noise or freqs must be defined if notch_filter is"
|
|
22
|
+
"activated."
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
if freqs is None:
|
|
26
|
+
freqs = np.arange(line_noise, sfreq / 2, line_noise, dtype=int)
|
|
27
|
+
|
|
28
|
+
if freqs.size > 0 and freqs[-1] >= sfreq / 2:
|
|
29
|
+
freqs = freqs[:-1]
|
|
30
|
+
|
|
31
|
+
# Code is copied from filter.py notch_filter
|
|
32
|
+
if freqs.size == 0:
|
|
33
|
+
self.filter_bank = None
|
|
34
|
+
logger.warning(
|
|
35
|
+
"WARNING: notch_filter is activated but data is not being"
|
|
36
|
+
" filtered. This may be due to a low sampling frequency or"
|
|
37
|
+
" incorrect specifications. Make sure your settings are"
|
|
38
|
+
f" correct. Got: {sfreq = }, {line_noise = }, {freqs = }."
|
|
39
|
+
)
|
|
40
|
+
return
|
|
41
|
+
|
|
42
|
+
filter_length = int(sfreq - 1)
|
|
43
|
+
if notch_widths is None:
|
|
44
|
+
notch_widths = freqs / 200.0
|
|
45
|
+
elif np.any(notch_widths < 0):
|
|
46
|
+
raise ValueError("notch_widths must be >= 0")
|
|
47
|
+
else:
|
|
48
|
+
notch_widths = np.atleast_1d(notch_widths)
|
|
49
|
+
if len(notch_widths) == 1:
|
|
50
|
+
notch_widths = notch_widths[0] * np.ones_like(freqs)
|
|
51
|
+
elif len(notch_widths) != len(freqs):
|
|
52
|
+
raise ValueError(
|
|
53
|
+
"notch_widths must be None, scalar, or the " "same length as freqs"
|
|
54
|
+
)
|
|
55
|
+
notch_widths = cast(np.ndarray, notch_widths) # For MyPy only, no runtime cost
|
|
56
|
+
|
|
57
|
+
# Speed this up by computing the fourier coefficients once
|
|
58
|
+
tb_half = trans_bandwidth / 2.0
|
|
59
|
+
lows = [freq - nw / 2.0 - tb_half for freq, nw in zip(freqs, notch_widths)]
|
|
60
|
+
highs = [freq + nw / 2.0 + tb_half for freq, nw in zip(freqs, notch_widths)]
|
|
61
|
+
|
|
62
|
+
self.filter_bank = create_filter(
|
|
63
|
+
data=None,
|
|
64
|
+
sfreq=sfreq,
|
|
65
|
+
l_freq=highs,
|
|
66
|
+
h_freq=lows,
|
|
67
|
+
filter_length=filter_length, # type: ignore
|
|
68
|
+
l_trans_bandwidth=tb_half, # type: ignore
|
|
69
|
+
h_trans_bandwidth=tb_half, # type: ignore
|
|
70
|
+
method="fir",
|
|
71
|
+
iir_params=None,
|
|
72
|
+
phase="zero",
|
|
73
|
+
fir_window="hamming",
|
|
74
|
+
fir_design="firwin",
|
|
75
|
+
verbose=False,
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
def process(self, data: np.ndarray) -> np.ndarray:
|
|
79
|
+
if self.filter_bank is None:
|
|
80
|
+
return data
|
|
81
|
+
|
|
82
|
+
from mne.filter import _overlap_add_filter
|
|
83
|
+
|
|
84
|
+
return _overlap_add_filter(
|
|
85
|
+
x=data,
|
|
86
|
+
h=self.filter_bank,
|
|
87
|
+
n_fft=None,
|
|
88
|
+
phase="zero",
|
|
89
|
+
picks=None,
|
|
90
|
+
n_jobs=1,
|
|
91
|
+
copy=True,
|
|
92
|
+
pad="reflect_limited",
|
|
93
|
+
)
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
from .artifacts import PARRMArtifactRejection
|
|
2
|
+
from .data_preprocessor import DataPreprocessor
|
|
3
|
+
from .projection import Projection, ProjectionSettings
|
|
4
|
+
from .normalization import FeatureNormalizer, RawNormalizer, NormalizationSettings
|
|
5
|
+
from .resample import Resampler, ResamplerSettings
|
|
6
|
+
from .rereference import ReReferencer
|
|
7
|
+
from .filter_preprocessing import PreprocessingFilter, FilterSettings
|
|
8
|
+
|
|
9
|
+
# Expose Notch filter also in the processing module, as it is used as a data preprocessing step
|
|
10
|
+
from py_neuromodulation.filter import NotchFilter
|
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
from pyparrm import PARRM
|
|
2
|
-
|
|
3
|
-
|
|
4
1
|
class PARRMArtifactRejection:
|
|
5
2
|
"""
|
|
6
3
|
This module enables training of a PARRM filter before computation,
|
|
@@ -8,6 +5,8 @@ class PARRMArtifactRejection:
|
|
|
8
5
|
https://pyparrm.readthedocs.io/en/stable/
|
|
9
6
|
"""
|
|
10
7
|
def __init__(self, data, sampling_freq, artefact_freq, verbose=False):
|
|
8
|
+
from pyparrm import PARRM
|
|
9
|
+
|
|
11
10
|
self.data = data
|
|
12
11
|
self.sampling_freq = sampling_freq
|
|
13
12
|
self.artefact_freq = artefact_freq
|
|
@@ -1,45 +1,37 @@
|
|
|
1
|
-
from typing import
|
|
2
|
-
from
|
|
3
|
-
from typing import Type
|
|
4
|
-
from py_neuromodulation.nm_types import ImportDetails, get_class, PreprocessorName
|
|
1
|
+
from typing import TYPE_CHECKING, Type
|
|
2
|
+
from py_neuromodulation.utils.types import PreprocessorName, NMPreprocessor
|
|
5
3
|
|
|
6
4
|
if TYPE_CHECKING:
|
|
7
5
|
import numpy as np
|
|
8
6
|
import pandas as pd
|
|
9
|
-
from py_neuromodulation.
|
|
7
|
+
from py_neuromodulation.stream.settings import NMSettings
|
|
10
8
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
PREPROCESSOR_DICT: dict[PreprocessorName, ImportDetails] = {
|
|
19
|
-
"preprocessing_filter": ImportDetails(
|
|
20
|
-
"nm_filter_preprocessing", "PreprocessingFilter"
|
|
21
|
-
),
|
|
22
|
-
"notch_filter": ImportDetails("nm_filter", "NotchFilter"),
|
|
23
|
-
"raw_resampling": ImportDetails("nm_resample", "Resampler"),
|
|
24
|
-
"re_referencing": ImportDetails("nm_rereference", "ReReferencer"),
|
|
25
|
-
"raw_normalization": ImportDetails("nm_normalization", "RawNormalizer"),
|
|
9
|
+
PREPROCESSOR_DICT: dict[PreprocessorName, str] = {
|
|
10
|
+
"preprocessing_filter": "PreprocessingFilter",
|
|
11
|
+
"notch_filter": "NotchFilter",
|
|
12
|
+
"raw_resampling": "Resampler",
|
|
13
|
+
"re_referencing": "ReReferencer",
|
|
14
|
+
"raw_normalization": "RawNormalizer",
|
|
26
15
|
}
|
|
27
16
|
|
|
28
17
|
|
|
29
|
-
class
|
|
18
|
+
class DataPreprocessor:
|
|
30
19
|
"Class for initializing and holding data preprocessing classes"
|
|
31
20
|
|
|
32
21
|
def __init__(
|
|
33
22
|
self,
|
|
34
23
|
settings: "NMSettings",
|
|
35
|
-
|
|
24
|
+
channels: "pd.DataFrame",
|
|
36
25
|
sfreq: float,
|
|
37
26
|
line_noise: float | None = None,
|
|
38
27
|
) -> None:
|
|
28
|
+
from importlib import import_module
|
|
29
|
+
from inspect import getfullargspec
|
|
30
|
+
|
|
39
31
|
possible_arguments = {
|
|
40
32
|
"sfreq": sfreq,
|
|
41
33
|
"settings": settings,
|
|
42
|
-
"
|
|
34
|
+
"channels": channels,
|
|
43
35
|
"line_noise": line_noise,
|
|
44
36
|
}
|
|
45
37
|
|
|
@@ -51,8 +43,10 @@ class NMPreprocessors:
|
|
|
51
43
|
|
|
52
44
|
# Get needed preprocessor classes from settings
|
|
53
45
|
preprocessor_classes: dict[str, Type[NMPreprocessor]] = {
|
|
54
|
-
preprocessor_name:
|
|
55
|
-
|
|
46
|
+
preprocessor_name: getattr(
|
|
47
|
+
import_module("py_neuromodulation.processing"), class_name
|
|
48
|
+
)
|
|
49
|
+
for preprocessor_name, class_name in PREPROCESSOR_DICT.items()
|
|
56
50
|
if preprocessor_name in settings.preprocessing
|
|
57
51
|
}
|
|
58
52
|
|
|
@@ -3,11 +3,10 @@ import numpy as np
|
|
|
3
3
|
from pydantic import Field
|
|
4
4
|
from typing import TYPE_CHECKING
|
|
5
5
|
|
|
6
|
-
from py_neuromodulation.
|
|
7
|
-
from py_neuromodulation.nm_preprocessing import NMPreprocessor
|
|
6
|
+
from py_neuromodulation.utils.types import BoolSelector, FrequencyRange, NMPreprocessor
|
|
8
7
|
|
|
9
8
|
if TYPE_CHECKING:
|
|
10
|
-
from py_neuromodulation
|
|
9
|
+
from py_neuromodulation import NMSettings
|
|
11
10
|
|
|
12
11
|
|
|
13
12
|
FILTER_SETTINGS_MAP = {
|
|
@@ -50,7 +49,7 @@ class FilterSettings(BoolSelector):
|
|
|
50
49
|
|
|
51
50
|
class PreprocessingFilter(NMPreprocessor):
|
|
52
51
|
def __init__(self, settings: "NMSettings", sfreq: float) -> None:
|
|
53
|
-
from py_neuromodulation.
|
|
52
|
+
from py_neuromodulation.filter import MNEFilter
|
|
54
53
|
|
|
55
54
|
self.filters: list[MNEFilter] = [
|
|
56
55
|
MNEFilter(
|
|
@@ -3,11 +3,15 @@
|
|
|
3
3
|
import numpy as np
|
|
4
4
|
from typing import TYPE_CHECKING, Callable, Literal, get_args
|
|
5
5
|
|
|
6
|
-
from py_neuromodulation.
|
|
7
|
-
|
|
6
|
+
from py_neuromodulation.utils.types import (
|
|
7
|
+
NMBaseModel,
|
|
8
|
+
Field,
|
|
9
|
+
NormMethod,
|
|
10
|
+
NMPreprocessor,
|
|
11
|
+
)
|
|
8
12
|
|
|
9
13
|
if TYPE_CHECKING:
|
|
10
|
-
from py_neuromodulation
|
|
14
|
+
from py_neuromodulation import NMSettings
|
|
11
15
|
|
|
12
16
|
NormalizerType = Literal["raw", "feature"]
|
|
13
17
|
|
|
@@ -29,7 +33,6 @@ class Normalizer(NMPreprocessor):
|
|
|
29
33
|
settings: "NMSettings",
|
|
30
34
|
type: NormalizerType,
|
|
31
35
|
) -> None:
|
|
32
|
-
|
|
33
36
|
self.type = type
|
|
34
37
|
self.settings: NormalizationSettings
|
|
35
38
|
|
|
@@ -86,7 +89,6 @@ class Normalizer(NMPreprocessor):
|
|
|
86
89
|
if self.settings.clip:
|
|
87
90
|
data = data.clip(min=-self.settings.clip, max=self.settings.clip)
|
|
88
91
|
|
|
89
|
-
|
|
90
92
|
self.previous = self.previous[-self.num_samples_normalize + 1 :]
|
|
91
93
|
|
|
92
94
|
data = np.nan_to_num(data)
|
|
@@ -143,13 +145,13 @@ def norm_median(current, previous):
|
|
|
143
145
|
|
|
144
146
|
def norm_zscore(current, previous):
|
|
145
147
|
std = nan_std(previous, axis=0)
|
|
146
|
-
std[std == 0] = 1
|
|
148
|
+
std[std == 0] = 1 # same behavior as sklearn
|
|
147
149
|
return (current - nan_mean(previous, axis=0)) / std
|
|
148
150
|
|
|
149
151
|
|
|
150
152
|
def norm_zscore_median(current, previous):
|
|
151
153
|
std = nan_std(previous, axis=0)
|
|
152
|
-
std[std == 0] = 1
|
|
154
|
+
std[std == 0] = 1 # same behavior as sklearn
|
|
153
155
|
return (current - nan_median(previous, axis=0)) / std
|
|
154
156
|
|
|
155
157
|
|