py-neuromodulation 0.0.5__py3-none-any.whl → 0.0.7__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} +7 -9
- py_neuromodulation/features/__init__.py +31 -0
- py_neuromodulation/features/bandpower.py +165 -0
- py_neuromodulation/{nm_bispectra.py → features/bispectra.py} +11 -12
- py_neuromodulation/{nm_bursts.py → features/bursts.py} +14 -9
- py_neuromodulation/{nm_coherence.py → features/coherence.py} +28 -19
- 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.7.dist-info}/METADATA +12 -29
- py_neuromodulation-0.0.7.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.7.dist-info}/WHEEL +0 -0
- {py_neuromodulation-0.0.5.dist-info → py_neuromodulation-0.0.7.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
from collections.abc import Iterable
|
|
2
2
|
from pydantic import field_validator
|
|
3
|
-
from py_neuromodulation.nm_types import NMBaseModel
|
|
4
3
|
from typing import TYPE_CHECKING, Callable
|
|
5
4
|
|
|
6
5
|
import numpy as np
|
|
7
6
|
|
|
8
|
-
from py_neuromodulation.
|
|
9
|
-
|
|
7
|
+
from py_neuromodulation.utils.types import (
|
|
8
|
+
NMBaseModel,
|
|
9
|
+
NMFeature,
|
|
10
|
+
BoolSelector,
|
|
11
|
+
FrequencyRange,
|
|
12
|
+
)
|
|
10
13
|
|
|
11
14
|
if TYPE_CHECKING:
|
|
12
|
-
from py_neuromodulation
|
|
15
|
+
from py_neuromodulation import NMSettings
|
|
13
16
|
|
|
14
17
|
|
|
15
18
|
class BispectraComponents(BoolSelector):
|
|
@@ -67,7 +70,7 @@ class Bispectra(NMFeature):
|
|
|
67
70
|
self.sfreq = sfreq
|
|
68
71
|
self.ch_names = ch_names
|
|
69
72
|
self.frequency_ranges_hz = settings.frequency_ranges_hz
|
|
70
|
-
self.settings: BispectraSettings = settings.
|
|
73
|
+
self.settings: BispectraSettings = settings.bispectrum_settings
|
|
71
74
|
|
|
72
75
|
assert all(
|
|
73
76
|
f_band_bispectrum in settings.frequency_ranges_hz
|
|
@@ -93,11 +96,8 @@ class Bispectra(NMFeature):
|
|
|
93
96
|
def calc_feature(self, data: np.ndarray) -> dict:
|
|
94
97
|
from pybispectra import compute_fft, WaveShape
|
|
95
98
|
|
|
96
|
-
# PyBispectra's compute_fft uses PQDM to parallelize the calculation per channel
|
|
97
|
-
# Is this necessary? Maybe the overhead of parallelization is not worth it
|
|
98
|
-
# considering that we incur in it once per batch of data
|
|
99
99
|
fft_coeffs, freqs = compute_fft(
|
|
100
|
-
data=np.expand_dims(data, axis=
|
|
100
|
+
data=np.expand_dims(data, axis=0),
|
|
101
101
|
sampling_freq=self.sfreq,
|
|
102
102
|
n_points=data.shape[1],
|
|
103
103
|
verbose=False,
|
|
@@ -124,12 +124,11 @@ class Bispectra(NMFeature):
|
|
|
124
124
|
f1s=tuple(self.settings.f1s), # type: ignore
|
|
125
125
|
f2s=tuple(self.settings.f2s), # type: ignore
|
|
126
126
|
)
|
|
127
|
+
waveshape = waveshape.results.get_results(copy=False) # can overwrite obj with array
|
|
127
128
|
|
|
128
129
|
feature_results = {}
|
|
129
130
|
for ch_idx, ch_name in enumerate(self.ch_names):
|
|
130
|
-
bispectrum = waveshape
|
|
131
|
-
ch_idx
|
|
132
|
-
] # Same as waveshape.results._data, skips a copy
|
|
131
|
+
bispectrum = waveshape[ch_idx]
|
|
133
132
|
|
|
134
133
|
for component in self.settings.components.get_enabled():
|
|
135
134
|
spectrum_ch = COMPONENT_DICT[component](bispectrum)
|
|
@@ -8,13 +8,13 @@ from collections.abc import Sequence
|
|
|
8
8
|
from itertools import product
|
|
9
9
|
|
|
10
10
|
from pydantic import Field, field_validator
|
|
11
|
-
from py_neuromodulation.
|
|
12
|
-
from py_neuromodulation.nm_features import NMFeature
|
|
11
|
+
from py_neuromodulation.utils.types import BoolSelector, NMBaseModel, NMFeature
|
|
13
12
|
|
|
14
13
|
from typing import TYPE_CHECKING, Callable
|
|
14
|
+
from py_neuromodulation.utils.types import create_validation_error
|
|
15
15
|
|
|
16
16
|
if TYPE_CHECKING:
|
|
17
|
-
from py_neuromodulation
|
|
17
|
+
from py_neuromodulation import NMSettings
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
LARGE_NUM = 2**24
|
|
@@ -45,7 +45,7 @@ class BurstFeatures(BoolSelector):
|
|
|
45
45
|
in_burst: bool = True
|
|
46
46
|
|
|
47
47
|
|
|
48
|
-
class
|
|
48
|
+
class BurstsSettings(NMBaseModel):
|
|
49
49
|
threshold: float = Field(default=75, ge=0, le=100)
|
|
50
50
|
time_duration_s: float = Field(default=30, ge=0)
|
|
51
51
|
frequency_bands: list[str] = ["low_beta", "high_beta", "low_gamma"]
|
|
@@ -56,17 +56,22 @@ class BurstSettings(NMBaseModel):
|
|
|
56
56
|
return [f.replace(" ", "_") for f in frequency_bands]
|
|
57
57
|
|
|
58
58
|
|
|
59
|
-
class
|
|
59
|
+
class Bursts(NMFeature):
|
|
60
60
|
def __init__(
|
|
61
61
|
self, settings: "NMSettings", ch_names: Sequence[str], sfreq: float
|
|
62
62
|
) -> None:
|
|
63
63
|
# Test settings
|
|
64
|
+
settings.validate()
|
|
65
|
+
|
|
66
|
+
# Validate that all frequency bands are defined in the settings
|
|
64
67
|
for fband_burst in settings.burst_settings.frequency_bands:
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
+
if fband_burst not in list(settings.frequency_ranges_hz.keys()):
|
|
69
|
+
raise create_validation_error(
|
|
70
|
+
f"bursting {fband_burst} needs to be defined in settings['frequency_ranges_hz']",
|
|
71
|
+
loc=["burst_settings", "frequency_bands"],
|
|
72
|
+
)
|
|
68
73
|
|
|
69
|
-
from py_neuromodulation.
|
|
74
|
+
from py_neuromodulation.filter import MNEFilter
|
|
70
75
|
|
|
71
76
|
self.settings = settings.burst_settings
|
|
72
77
|
self.sfreq = sfreq
|
|
@@ -5,12 +5,16 @@ from collections.abc import Iterable
|
|
|
5
5
|
from typing import TYPE_CHECKING, Annotated
|
|
6
6
|
from pydantic import Field, field_validator
|
|
7
7
|
|
|
8
|
-
from py_neuromodulation.
|
|
9
|
-
|
|
8
|
+
from py_neuromodulation.utils.types import (
|
|
9
|
+
NMFeature,
|
|
10
|
+
BoolSelector,
|
|
11
|
+
FrequencyRange,
|
|
12
|
+
NMBaseModel,
|
|
13
|
+
)
|
|
10
14
|
from py_neuromodulation import logger
|
|
11
15
|
|
|
12
16
|
if TYPE_CHECKING:
|
|
13
|
-
from py_neuromodulation
|
|
17
|
+
from py_neuromodulation import NMSettings
|
|
14
18
|
|
|
15
19
|
|
|
16
20
|
class CoherenceMethods(BoolSelector):
|
|
@@ -22,7 +26,7 @@ class CoherenceFeatures(BoolSelector):
|
|
|
22
26
|
mean_fband: bool = True
|
|
23
27
|
max_fband: bool = True
|
|
24
28
|
max_allfbands: bool = True
|
|
25
|
-
|
|
29
|
+
|
|
26
30
|
|
|
27
31
|
ListOfTwoStr = Annotated[list[str], Field(min_length=2, max_length=2)]
|
|
28
32
|
|
|
@@ -31,6 +35,7 @@ class CoherenceSettings(NMBaseModel):
|
|
|
31
35
|
features: CoherenceFeatures = CoherenceFeatures()
|
|
32
36
|
method: CoherenceMethods = CoherenceMethods()
|
|
33
37
|
channels: list[ListOfTwoStr] = []
|
|
38
|
+
nperseg: int = Field(default=128, ge=0)
|
|
34
39
|
frequency_bands: list[str] = Field(default=["high_beta"], min_length=1)
|
|
35
40
|
|
|
36
41
|
@field_validator("frequency_bands")
|
|
@@ -45,6 +50,7 @@ class CoherenceObject:
|
|
|
45
50
|
window: str,
|
|
46
51
|
fbands: list[FrequencyRange],
|
|
47
52
|
fband_names: list[str],
|
|
53
|
+
nperseg: int,
|
|
48
54
|
ch_1_name: str,
|
|
49
55
|
ch_2_name: str,
|
|
50
56
|
ch_1_idx: int,
|
|
@@ -61,6 +67,7 @@ class CoherenceObject:
|
|
|
61
67
|
self.ch_2 = ch_2_name
|
|
62
68
|
self.ch_1_idx = ch_1_idx
|
|
63
69
|
self.ch_2_idx = ch_2_idx
|
|
70
|
+
self.nperseg = nperseg
|
|
64
71
|
self.coh = coh
|
|
65
72
|
self.icoh = icoh
|
|
66
73
|
self.features_coh = features_coh
|
|
@@ -75,14 +82,15 @@ class CoherenceObject:
|
|
|
75
82
|
def get_coh(self, feature_results, x, y):
|
|
76
83
|
from scipy.signal import welch, csd
|
|
77
84
|
|
|
78
|
-
self.f, self.Pxx = welch(x, self.sfreq, self.window, nperseg=
|
|
79
|
-
self.Pyy = welch(y, self.sfreq, self.window, nperseg=
|
|
80
|
-
self.Pxy = csd(x, y, self.sfreq, self.window, nperseg=
|
|
85
|
+
self.f, self.Pxx = welch(x, self.sfreq, self.window, nperseg=self.nperseg)
|
|
86
|
+
self.Pyy = welch(y, self.sfreq, self.window, nperseg=self.nperseg)[1]
|
|
87
|
+
self.Pxy = csd(x, y, self.sfreq, self.window, nperseg=self.nperseg)[1]
|
|
81
88
|
|
|
82
89
|
if self.coh:
|
|
83
|
-
|
|
90
|
+
# XXX: gives different output to abs(Sxy) / sqrt(Sxx * Syy)
|
|
91
|
+
self.coh_val = np.abs(self.Pxy) ** 2 / (self.Pxx * self.Pyy)
|
|
84
92
|
if self.icoh:
|
|
85
|
-
self.icoh_val =
|
|
93
|
+
self.icoh_val = self.Pxy.imag / np.sqrt(self.Pxx * self.Pyy)
|
|
86
94
|
|
|
87
95
|
for coh_idx, coh_type in enumerate([self.coh, self.icoh]):
|
|
88
96
|
if coh_type:
|
|
@@ -140,11 +148,11 @@ class CoherenceObject:
|
|
|
140
148
|
return feature_results
|
|
141
149
|
|
|
142
150
|
|
|
143
|
-
class
|
|
151
|
+
class Coherence(NMFeature):
|
|
144
152
|
def __init__(
|
|
145
153
|
self, settings: "NMSettings", ch_names: list[str], sfreq: float
|
|
146
154
|
) -> None:
|
|
147
|
-
self.settings = settings.
|
|
155
|
+
self.settings = settings.coherence_settings
|
|
148
156
|
self.frequency_ranges_hz = settings.frequency_ranges_hz
|
|
149
157
|
self.sfreq = sfreq
|
|
150
158
|
self.ch_names = ch_names
|
|
@@ -176,6 +184,7 @@ class NMCoherence(NMFeature):
|
|
|
176
184
|
"hann",
|
|
177
185
|
fband_specs,
|
|
178
186
|
fband_names,
|
|
187
|
+
self.settings.nperseg,
|
|
179
188
|
ch_1_name,
|
|
180
189
|
ch_2_name,
|
|
181
190
|
ch_1_idx,
|
|
@@ -193,7 +202,7 @@ class NMCoherence(NMFeature):
|
|
|
193
202
|
sfreq: float,
|
|
194
203
|
):
|
|
195
204
|
flat_channels = [
|
|
196
|
-
ch for ch_pair in settings.
|
|
205
|
+
ch for ch_pair in settings.coherence_settings.channels for ch in ch_pair
|
|
197
206
|
]
|
|
198
207
|
|
|
199
208
|
valid_coh_channel = [
|
|
@@ -203,37 +212,37 @@ class NMCoherence(NMFeature):
|
|
|
203
212
|
if valid_coh_channel[ch_idx] == 0:
|
|
204
213
|
raise RuntimeError(
|
|
205
214
|
f"Coherence selected channel {ch_coh} does not match any channel name: \n"
|
|
206
|
-
f" - settings.
|
|
215
|
+
f" - settings.coherence_settings.channels: {settings.coherence_settings.channels}\n"
|
|
207
216
|
f" - ch_names: {ch_names} \n"
|
|
208
217
|
)
|
|
209
218
|
|
|
210
219
|
if valid_coh_channel[ch_idx] > 1:
|
|
211
220
|
raise RuntimeError(
|
|
212
221
|
f"Coherence selected channel {ch_coh} is ambigous and matches more than one channel name: \n"
|
|
213
|
-
f" - settings.
|
|
222
|
+
f" - settings.coherence_settings.channels: {settings.coherence_settings.channels}\n"
|
|
214
223
|
f" - ch_names: {ch_names} \n"
|
|
215
224
|
)
|
|
216
225
|
|
|
217
226
|
assert all(
|
|
218
227
|
f_band_coh in settings.frequency_ranges_hz
|
|
219
|
-
for f_band_coh in settings.
|
|
228
|
+
for f_band_coh in settings.coherence_settings.frequency_bands
|
|
220
229
|
), (
|
|
221
230
|
"coherence selected frequency bands don't match the ones"
|
|
222
231
|
"specified in s['frequency_ranges_hz']"
|
|
223
|
-
f"coherence frequency bands: {settings.
|
|
232
|
+
f"coherence frequency bands: {settings.coherence_settings.frequency_bands}"
|
|
224
233
|
f"specified frequency_ranges_hz: {settings.frequency_ranges_hz}"
|
|
225
234
|
)
|
|
226
235
|
|
|
227
236
|
assert all(
|
|
228
237
|
settings.frequency_ranges_hz[fb][0] < sfreq / 2
|
|
229
238
|
and settings.frequency_ranges_hz[fb][1] < sfreq / 2
|
|
230
|
-
for fb in settings.
|
|
239
|
+
for fb in settings.coherence_settings.frequency_bands
|
|
231
240
|
), (
|
|
232
241
|
"the coherence frequency band ranges need to be smaller than the Nyquist frequency"
|
|
233
|
-
f"got sfreq = {sfreq} and fband ranges {settings.
|
|
242
|
+
f"got sfreq = {sfreq} and fband ranges {settings.coherence_settings.frequency_bands}"
|
|
234
243
|
)
|
|
235
244
|
|
|
236
|
-
if not settings.
|
|
245
|
+
if not settings.coherence_settings.method.get_enabled():
|
|
237
246
|
logger.warn(
|
|
238
247
|
"feature coherence enabled, but no coherence['method'] selected"
|
|
239
248
|
)
|
|
@@ -1,51 +1,27 @@
|
|
|
1
|
-
from typing import
|
|
2
|
-
from collections.abc import Sequence
|
|
3
|
-
import numpy as np
|
|
1
|
+
from typing import Type, TYPE_CHECKING
|
|
4
2
|
|
|
5
|
-
|
|
6
|
-
from nm_settings import NMSettings
|
|
7
|
-
|
|
8
|
-
from py_neuromodulation.nm_types import ImportDetails, get_class, FeatureName
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
@runtime_checkable
|
|
12
|
-
class NMFeature(Protocol):
|
|
13
|
-
def __init__(
|
|
14
|
-
self, settings: "NMSettings", ch_names: Sequence[str], sfreq: int | float
|
|
15
|
-
) -> None: ...
|
|
3
|
+
from py_neuromodulation.utils.types import NMFeature, FeatureName
|
|
16
4
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
"bandpass_filter": ImportDetails("nm_oscillatory", "BandPower"),
|
|
38
|
-
"stft": ImportDetails("nm_oscillatory", "STFT"),
|
|
39
|
-
"fft": ImportDetails("nm_oscillatory", "FFT"),
|
|
40
|
-
"welch": ImportDetails("nm_oscillatory", "Welch"),
|
|
41
|
-
"sharpwave_analysis": ImportDetails("nm_sharpwaves", "SharpwaveAnalyzer"),
|
|
42
|
-
"fooof": ImportDetails("nm_fooof", "FooofAnalyzer"),
|
|
43
|
-
"nolds": ImportDetails("nm_nolds", "Nolds"),
|
|
44
|
-
"coherence": ImportDetails("nm_coherence", "NMCoherence"),
|
|
45
|
-
"bursts": ImportDetails("nm_bursts", "Burst"),
|
|
46
|
-
"linelength": ImportDetails("nm_linelength", "LineLength"),
|
|
47
|
-
"mne_connectivity": ImportDetails("nm_mne_connectivity", "MNEConnectivity"),
|
|
48
|
-
"bispectrum": ImportDetails("nm_bispectra", "Bispectra"),
|
|
5
|
+
if TYPE_CHECKING:
|
|
6
|
+
import numpy as np
|
|
7
|
+
from py_neuromodulation import NMSettings
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
FEATURE_DICT: dict[FeatureName | str, str] = {
|
|
11
|
+
"raw_hjorth": "Hjorth",
|
|
12
|
+
"return_raw": "Raw",
|
|
13
|
+
"bandpass_filter": "BandPower",
|
|
14
|
+
"stft": "STFT",
|
|
15
|
+
"fft": "FFT",
|
|
16
|
+
"welch": "Welch",
|
|
17
|
+
"sharpwave_analysis": "SharpwaveAnalyzer",
|
|
18
|
+
"fooof": "FooofAnalyzer",
|
|
19
|
+
"nolds": "Nolds",
|
|
20
|
+
"coherence": "Coherence",
|
|
21
|
+
"bursts": "Bursts",
|
|
22
|
+
"linelength": "LineLength",
|
|
23
|
+
"mne_connectivity": "MNEConnectivity",
|
|
24
|
+
"bispectrum": "Bispectra",
|
|
49
25
|
}
|
|
50
26
|
|
|
51
27
|
|
|
@@ -63,12 +39,13 @@ class FeatureProcessors:
|
|
|
63
39
|
sfreq (float): sampling frequency in Hz
|
|
64
40
|
"""
|
|
65
41
|
from py_neuromodulation import user_features
|
|
42
|
+
from importlib import import_module
|
|
66
43
|
|
|
67
44
|
# Accept 'str' for custom features
|
|
68
45
|
self.features: dict[FeatureName | str, NMFeature] = {
|
|
69
|
-
feature_name:
|
|
70
|
-
|
|
71
|
-
)
|
|
46
|
+
feature_name: getattr(
|
|
47
|
+
import_module("py_neuromodulation.features"), FEATURE_DICT[feature_name]
|
|
48
|
+
)(settings, ch_names, sfreq)
|
|
72
49
|
for feature_name in settings.features.get_enabled()
|
|
73
50
|
}
|
|
74
51
|
|
|
@@ -80,12 +57,12 @@ class FeatureProcessors:
|
|
|
80
57
|
|
|
81
58
|
Parameters
|
|
82
59
|
----------
|
|
83
|
-
feature :
|
|
60
|
+
feature : features_abc.Feature
|
|
84
61
|
New feature to add to feature list
|
|
85
62
|
"""
|
|
86
63
|
self.features[feature_name] = feature # type: ignore
|
|
87
64
|
|
|
88
|
-
def estimate_features(self, data: np.ndarray) -> dict:
|
|
65
|
+
def estimate_features(self, data: "np.ndarray") -> dict:
|
|
89
66
|
"""Calculate features, as defined in settings.json
|
|
90
67
|
Features are based on bandpower, raw Hjorth parameters and sharp wave
|
|
91
68
|
characteristics.
|
|
@@ -125,7 +102,7 @@ def add_custom_feature(feature_name: str, new_feature: Type[NMFeature]):
|
|
|
125
102
|
in this file).
|
|
126
103
|
"""
|
|
127
104
|
from py_neuromodulation import user_features
|
|
128
|
-
from py_neuromodulation
|
|
105
|
+
from py_neuromodulation import NMSettings
|
|
129
106
|
|
|
130
107
|
user_features[feature_name] = new_feature
|
|
131
108
|
NMSettings._add_feature(feature_name)
|
|
@@ -138,7 +115,7 @@ def remove_custom_feature(feature_name: str):
|
|
|
138
115
|
feature_name (str): Name of the feature to remove
|
|
139
116
|
"""
|
|
140
117
|
from py_neuromodulation import user_features
|
|
141
|
-
from py_neuromodulation
|
|
118
|
+
from py_neuromodulation import NMSettings
|
|
142
119
|
|
|
143
120
|
user_features.pop(feature_name)
|
|
144
121
|
NMSettings._remove_feature(feature_name)
|
|
@@ -2,13 +2,16 @@ from collections.abc import Iterable
|
|
|
2
2
|
import numpy as np
|
|
3
3
|
|
|
4
4
|
from typing import TYPE_CHECKING
|
|
5
|
-
from py_neuromodulation.nm_types import NMBaseModel
|
|
6
5
|
|
|
7
|
-
from py_neuromodulation.
|
|
8
|
-
|
|
6
|
+
from py_neuromodulation.utils.types import (
|
|
7
|
+
NMBaseModel,
|
|
8
|
+
NMFeature,
|
|
9
|
+
BoolSelector,
|
|
10
|
+
FrequencyRange,
|
|
11
|
+
)
|
|
9
12
|
|
|
10
13
|
if TYPE_CHECKING:
|
|
11
|
-
from py_neuromodulation
|
|
14
|
+
from py_neuromodulation import NMSettings
|
|
12
15
|
|
|
13
16
|
|
|
14
17
|
class FooofAperiodicSettings(BoolSelector):
|
|
@@ -48,7 +51,7 @@ class FooofAnalyzer(NMFeature):
|
|
|
48
51
|
def __init__(
|
|
49
52
|
self, settings: "NMSettings", ch_names: Iterable[str], sfreq: float
|
|
50
53
|
) -> None:
|
|
51
|
-
self.settings = settings.
|
|
54
|
+
self.settings = settings.fooof_settings
|
|
52
55
|
self.sfreq = sfreq
|
|
53
56
|
self.ch_names = ch_names
|
|
54
57
|
|
|
@@ -59,12 +62,12 @@ class FooofAnalyzer(NMFeature):
|
|
|
59
62
|
self.f_vec = np.arange(0, int(self.num_samples / 2) + 1, 1)
|
|
60
63
|
|
|
61
64
|
assert (
|
|
62
|
-
settings.
|
|
65
|
+
self.settings.windowlength_ms <= settings.segment_length_features_ms
|
|
63
66
|
), f"fooof windowlength_ms ({settings.fooof.windowlength_ms}) needs to be smaller equal than segment_length_features_ms ({settings.segment_length_features_ms})."
|
|
64
67
|
|
|
65
68
|
assert (
|
|
66
|
-
settings.
|
|
67
|
-
and settings.
|
|
69
|
+
self.settings.freq_range_hz[0] < sfreq
|
|
70
|
+
and self.settings.freq_range_hz[1] < sfreq
|
|
68
71
|
), f"fooof frequency range needs to be below sfreq, got {settings.fooof.freq_range_hz}"
|
|
69
72
|
|
|
70
73
|
from fooof import FOOOFGroup
|
|
@@ -5,16 +5,19 @@ Reference: B Hjorth
|
|
|
5
5
|
DOI: 10.1016/0013-4694(70)90143-4
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
+
from typing import TYPE_CHECKING
|
|
8
9
|
import numpy as np
|
|
9
|
-
from collections.abc import
|
|
10
|
+
from collections.abc import Sequence
|
|
10
11
|
|
|
11
|
-
from py_neuromodulation.
|
|
12
|
-
|
|
12
|
+
from py_neuromodulation.utils.types import NMFeature
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from py_neuromodulation import NMSettings
|
|
13
16
|
|
|
14
17
|
|
|
15
18
|
class Hjorth(NMFeature):
|
|
16
19
|
def __init__(
|
|
17
|
-
self, settings: NMSettings, ch_names:
|
|
20
|
+
self, settings: "NMSettings", ch_names: Sequence[str], sfreq: float
|
|
18
21
|
) -> None:
|
|
19
22
|
self.ch_names = ch_names
|
|
20
23
|
|
|
@@ -40,7 +43,9 @@ class Hjorth(NMFeature):
|
|
|
40
43
|
|
|
41
44
|
|
|
42
45
|
class Raw(NMFeature):
|
|
43
|
-
def __init__(
|
|
46
|
+
def __init__(
|
|
47
|
+
self, settings: "NMSettings", ch_names: Sequence[str], sfreq: float
|
|
48
|
+
) -> None:
|
|
44
49
|
self.ch_names = ch_names
|
|
45
50
|
|
|
46
51
|
def calc_feature(self, data: np.ndarray) -> dict:
|
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
from collections.abc import Iterable
|
|
2
2
|
import numpy as np
|
|
3
|
-
import pandas as pd
|
|
4
3
|
from typing import TYPE_CHECKING
|
|
5
4
|
|
|
6
|
-
from py_neuromodulation.
|
|
7
|
-
from py_neuromodulation.nm_types import NMBaseModel
|
|
5
|
+
from py_neuromodulation.utils.types import NMFeature, NMBaseModel
|
|
8
6
|
|
|
9
7
|
if TYPE_CHECKING:
|
|
10
|
-
from py_neuromodulation
|
|
8
|
+
from py_neuromodulation import NMSettings
|
|
11
9
|
from mne.io import RawArray
|
|
12
10
|
from mne import Epochs
|
|
13
11
|
|
|
@@ -32,8 +30,8 @@ class MNEConnectivity(NMFeature):
|
|
|
32
30
|
self.sfreq = sfreq
|
|
33
31
|
|
|
34
32
|
# Params used by spectral_connectivity_epochs
|
|
35
|
-
self.mode = settings.
|
|
36
|
-
self.method = settings.
|
|
33
|
+
self.mode = settings.mne_connectivity_settings.mode
|
|
34
|
+
self.method = settings.mne_connectivity_settings.method
|
|
37
35
|
|
|
38
36
|
self.fbands = settings.frequency_ranges_hz
|
|
39
37
|
self.fband_ranges: list = []
|
|
@@ -48,6 +46,7 @@ class MNEConnectivity(NMFeature):
|
|
|
48
46
|
from mne.io import RawArray
|
|
49
47
|
from mne import Epochs
|
|
50
48
|
from mne_connectivity import spectral_connectivity_epochs
|
|
49
|
+
import pandas as pd
|
|
51
50
|
|
|
52
51
|
time_samples_s = data.shape[1] / self.sfreq
|
|
53
52
|
epoch_length: float = 1 # TODO: Make this a parameter?
|
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
import numpy as np
|
|
2
2
|
from collections.abc import Iterable
|
|
3
3
|
|
|
4
|
-
from py_neuromodulation.nm_types import NMBaseModel
|
|
5
4
|
from typing import TYPE_CHECKING
|
|
6
5
|
|
|
7
|
-
from py_neuromodulation.
|
|
8
|
-
from py_neuromodulation.nm_types import BoolSelector
|
|
6
|
+
from py_neuromodulation.utils.types import NMFeature, BoolSelector, NMBaseModel
|
|
9
7
|
|
|
10
8
|
from pydantic import field_validator
|
|
11
9
|
|
|
12
10
|
if TYPE_CHECKING:
|
|
13
|
-
from py_neuromodulation
|
|
11
|
+
from py_neuromodulation import NMSettings
|
|
14
12
|
|
|
15
13
|
|
|
16
14
|
class NoldsFeatures(BoolSelector):
|
|
@@ -35,16 +33,16 @@ class Nolds(NMFeature):
|
|
|
35
33
|
def __init__(
|
|
36
34
|
self, settings: "NMSettings", ch_names: Iterable[str], sfreq: float
|
|
37
35
|
) -> None:
|
|
38
|
-
self.settings = settings.
|
|
36
|
+
self.settings = settings.nolds_settings
|
|
39
37
|
self.ch_names = ch_names
|
|
40
38
|
|
|
41
39
|
if len(self.settings.frequency_bands) > 0:
|
|
42
|
-
from py_neuromodulation.
|
|
40
|
+
from py_neuromodulation.features.bandpower import BandPower
|
|
43
41
|
|
|
44
42
|
self.bp_filter = BandPower(settings, ch_names, sfreq, use_kf=False)
|
|
45
43
|
|
|
46
44
|
# Check if the selected frequency bands are defined in the global settings
|
|
47
|
-
for fb in settings.
|
|
45
|
+
for fb in settings.nolds_settings.frequency_bands:
|
|
48
46
|
assert (
|
|
49
47
|
fb in settings.frequency_ranges_hz
|
|
50
48
|
), f"{fb} selected in nolds_features, but not defined in s['frequency_ranges_hz']"
|