py-neuromodulation 0.0.4__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/ConnectivityDecoding/_get_grid_hull.m +34 -34
- py_neuromodulation/ConnectivityDecoding/_get_grid_whole_brain.py +95 -106
- py_neuromodulation/ConnectivityDecoding/_helper_write_connectome.py +107 -119
- py_neuromodulation/__init__.py +80 -13
- py_neuromodulation/{nm_RMAP.py → analysis/RMAP.py} +496 -531
- py_neuromodulation/analysis/__init__.py +4 -0
- py_neuromodulation/{nm_decode.py → analysis/decode.py} +918 -992
- py_neuromodulation/{nm_analysis.py → analysis/feature_reader.py} +994 -1074
- py_neuromodulation/{nm_plots.py → analysis/plots.py} +627 -612
- py_neuromodulation/{nm_stats.py → analysis/stats.py} +458 -480
- py_neuromodulation/data/README +6 -6
- py_neuromodulation/data/dataset_description.json +8 -8
- py_neuromodulation/data/participants.json +32 -32
- py_neuromodulation/data/participants.tsv +2 -2
- py_neuromodulation/data/sub-testsub/ses-EphysMedOff/ieeg/sub-testsub_ses-EphysMedOff_space-mni_coordsystem.json +5 -5
- py_neuromodulation/data/sub-testsub/ses-EphysMedOff/ieeg/sub-testsub_ses-EphysMedOff_space-mni_electrodes.tsv +11 -11
- py_neuromodulation/data/sub-testsub/ses-EphysMedOff/ieeg/sub-testsub_ses-EphysMedOff_task-gripforce_run-0_channels.tsv +11 -11
- py_neuromodulation/data/sub-testsub/ses-EphysMedOff/ieeg/sub-testsub_ses-EphysMedOff_task-gripforce_run-0_ieeg.json +18 -18
- py_neuromodulation/data/sub-testsub/ses-EphysMedOff/ieeg/sub-testsub_ses-EphysMedOff_task-gripforce_run-0_ieeg.vhdr +35 -35
- py_neuromodulation/data/sub-testsub/ses-EphysMedOff/ieeg/sub-testsub_ses-EphysMedOff_task-gripforce_run-0_ieeg.vmrk +13 -13
- py_neuromodulation/data/sub-testsub/ses-EphysMedOff/sub-testsub_ses-EphysMedOff_scans.tsv +2 -2
- py_neuromodulation/default_settings.yaml +241 -0
- py_neuromodulation/features/__init__.py +31 -0
- py_neuromodulation/features/bandpower.py +165 -0
- py_neuromodulation/features/bispectra.py +157 -0
- py_neuromodulation/features/bursts.py +297 -0
- py_neuromodulation/features/coherence.py +255 -0
- py_neuromodulation/features/feature_processor.py +121 -0
- py_neuromodulation/features/fooof.py +142 -0
- py_neuromodulation/features/hjorth_raw.py +57 -0
- py_neuromodulation/features/linelength.py +21 -0
- py_neuromodulation/features/mne_connectivity.py +148 -0
- py_neuromodulation/features/nolds.py +94 -0
- py_neuromodulation/features/oscillatory.py +249 -0
- py_neuromodulation/features/sharpwaves.py +432 -0
- py_neuromodulation/filter/__init__.py +3 -0
- py_neuromodulation/filter/kalman_filter.py +67 -0
- py_neuromodulation/filter/kalman_filter_external.py +1890 -0
- py_neuromodulation/filter/mne_filter.py +128 -0
- py_neuromodulation/filter/notch_filter.py +93 -0
- py_neuromodulation/grid_cortex.tsv +40 -40
- py_neuromodulation/liblsl/libpugixml.so.1.12 +0 -0
- py_neuromodulation/liblsl/linux/bionic_amd64/liblsl.1.16.2.so +0 -0
- py_neuromodulation/liblsl/linux/bookworm_amd64/liblsl.1.16.2.so +0 -0
- py_neuromodulation/liblsl/linux/focal_amd46/liblsl.1.16.2.so +0 -0
- py_neuromodulation/liblsl/linux/jammy_amd64/liblsl.1.16.2.so +0 -0
- py_neuromodulation/liblsl/linux/jammy_x86/liblsl.1.16.2.so +0 -0
- py_neuromodulation/liblsl/linux/noble_amd64/liblsl.1.16.2.so +0 -0
- py_neuromodulation/liblsl/macos/amd64/liblsl.1.16.2.dylib +0 -0
- py_neuromodulation/liblsl/macos/arm64/liblsl.1.16.0.dylib +0 -0
- py_neuromodulation/liblsl/windows/amd64/liblsl.1.16.2.dll +0 -0
- py_neuromodulation/liblsl/windows/x86/liblsl.1.16.2.dll +0 -0
- py_neuromodulation/processing/__init__.py +10 -0
- py_neuromodulation/{nm_artifacts.py → processing/artifacts.py} +29 -25
- py_neuromodulation/processing/data_preprocessor.py +77 -0
- py_neuromodulation/processing/filter_preprocessing.py +78 -0
- py_neuromodulation/processing/normalization.py +175 -0
- py_neuromodulation/{nm_projection.py → processing/projection.py} +370 -394
- py_neuromodulation/{nm_rereference.py → processing/rereference.py} +97 -95
- py_neuromodulation/{nm_resample.py → processing/resample.py} +56 -50
- py_neuromodulation/stream/__init__.py +3 -0
- py_neuromodulation/stream/data_processor.py +325 -0
- py_neuromodulation/stream/generator.py +53 -0
- py_neuromodulation/stream/mnelsl_player.py +94 -0
- py_neuromodulation/stream/mnelsl_stream.py +120 -0
- py_neuromodulation/stream/settings.py +292 -0
- py_neuromodulation/stream/stream.py +427 -0
- py_neuromodulation/utils/__init__.py +2 -0
- py_neuromodulation/{nm_define_nmchannels.py → utils/channels.py} +305 -302
- py_neuromodulation/utils/database.py +149 -0
- py_neuromodulation/utils/io.py +378 -0
- py_neuromodulation/utils/keyboard.py +52 -0
- py_neuromodulation/utils/logging.py +66 -0
- py_neuromodulation/utils/types.py +251 -0
- {py_neuromodulation-0.0.4.dist-info → py_neuromodulation-0.0.6.dist-info}/METADATA +28 -33
- py_neuromodulation-0.0.6.dist-info/RECORD +89 -0
- {py_neuromodulation-0.0.4.dist-info → py_neuromodulation-0.0.6.dist-info}/WHEEL +1 -1
- {py_neuromodulation-0.0.4.dist-info → py_neuromodulation-0.0.6.dist-info}/licenses/LICENSE +21 -21
- py_neuromodulation/FieldTrip.py +0 -589
- py_neuromodulation/_write_example_dataset_helper.py +0 -65
- py_neuromodulation/nm_EpochStream.py +0 -92
- py_neuromodulation/nm_IO.py +0 -417
- py_neuromodulation/nm_across_patient_decoding.py +0 -927
- py_neuromodulation/nm_bispectra.py +0 -168
- py_neuromodulation/nm_bursts.py +0 -198
- py_neuromodulation/nm_coherence.py +0 -205
- py_neuromodulation/nm_cohortwrapper.py +0 -435
- py_neuromodulation/nm_eval_timing.py +0 -239
- py_neuromodulation/nm_features.py +0 -116
- py_neuromodulation/nm_features_abc.py +0 -39
- py_neuromodulation/nm_filter.py +0 -219
- py_neuromodulation/nm_filter_preprocessing.py +0 -91
- py_neuromodulation/nm_fooof.py +0 -159
- py_neuromodulation/nm_generator.py +0 -37
- py_neuromodulation/nm_hjorth_raw.py +0 -73
- py_neuromodulation/nm_kalmanfilter.py +0 -58
- py_neuromodulation/nm_linelength.py +0 -33
- py_neuromodulation/nm_mne_connectivity.py +0 -112
- py_neuromodulation/nm_nolds.py +0 -93
- py_neuromodulation/nm_normalization.py +0 -214
- py_neuromodulation/nm_oscillatory.py +0 -448
- py_neuromodulation/nm_run_analysis.py +0 -435
- py_neuromodulation/nm_settings.json +0 -338
- py_neuromodulation/nm_settings.py +0 -68
- py_neuromodulation/nm_sharpwaves.py +0 -401
- py_neuromodulation/nm_stream_abc.py +0 -218
- py_neuromodulation/nm_stream_offline.py +0 -359
- py_neuromodulation/utils/_logging.py +0 -24
- py_neuromodulation-0.0.4.dist-info/RECORD +0 -72
|
@@ -1,448 +0,0 @@
|
|
|
1
|
-
from typing import Iterable
|
|
2
|
-
|
|
3
|
-
import numpy as np
|
|
4
|
-
from scipy import fft, signal
|
|
5
|
-
|
|
6
|
-
from py_neuromodulation import nm_filter, nm_features_abc, nm_kalmanfilter
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
class OscillatoryFeature(nm_features_abc.Feature):
|
|
10
|
-
def __init__(
|
|
11
|
-
self, settings: dict, ch_names: Iterable[str], sfreq: float
|
|
12
|
-
) -> None:
|
|
13
|
-
self.s = settings
|
|
14
|
-
self.sfreq = sfreq
|
|
15
|
-
self.ch_names = ch_names
|
|
16
|
-
self.KF_dict = {}
|
|
17
|
-
|
|
18
|
-
self.f_ranges_dict = settings["frequency_ranges_hz"]
|
|
19
|
-
self.fband_names = list(settings["frequency_ranges_hz"].keys())
|
|
20
|
-
self.f_ranges = list(settings["frequency_ranges_hz"].values())
|
|
21
|
-
|
|
22
|
-
@staticmethod
|
|
23
|
-
def test_settings_osc(
|
|
24
|
-
s: dict,
|
|
25
|
-
ch_names: Iterable[str],
|
|
26
|
-
sfreq: int | float,
|
|
27
|
-
osc_feature_name: str,
|
|
28
|
-
):
|
|
29
|
-
assert (
|
|
30
|
-
fb[0] < sfreq / 2 and fb[1] < sfreq / 2
|
|
31
|
-
for fb in s["frequency_ranges_hz"].values()
|
|
32
|
-
), (
|
|
33
|
-
"the frequency band ranges need to be smaller than the nyquist frequency"
|
|
34
|
-
f"got sfreq = {sfreq} and fband ranges {s['frequency_ranges_hz']}"
|
|
35
|
-
)
|
|
36
|
-
|
|
37
|
-
if osc_feature_name != "bandpass_filter_settings":
|
|
38
|
-
assert isinstance(
|
|
39
|
-
s[osc_feature_name]["windowlength_ms"], int
|
|
40
|
-
), f"windowlength_ms needs to be type int, got {s[osc_feature_name]['windowlength_ms']}"
|
|
41
|
-
|
|
42
|
-
assert (
|
|
43
|
-
s[osc_feature_name]["windowlength_ms"]
|
|
44
|
-
<= s["segment_length_features_ms"]
|
|
45
|
-
), (
|
|
46
|
-
f"oscillatory feature windowlength_ms = ({s[osc_feature_name]['windowlength_ms']})"
|
|
47
|
-
f"needs to be smaller than"
|
|
48
|
-
f"s['segment_length_features_ms'] = {s['segment_length_features_ms']}",
|
|
49
|
-
)
|
|
50
|
-
else:
|
|
51
|
-
for seg_length in s[osc_feature_name][
|
|
52
|
-
"segment_lengths_ms"
|
|
53
|
-
].values():
|
|
54
|
-
assert isinstance(
|
|
55
|
-
seg_length, int
|
|
56
|
-
), f"segment length has to be type int, got {seg_length}"
|
|
57
|
-
assert isinstance(
|
|
58
|
-
s[osc_feature_name]["log_transform"], bool
|
|
59
|
-
), f"log_transform needs to be type bool, got {s[osc_feature_name]['log_transform']}"
|
|
60
|
-
|
|
61
|
-
assert isinstance(s["frequency_ranges_hz"], dict)
|
|
62
|
-
|
|
63
|
-
assert (
|
|
64
|
-
isinstance(value, list)
|
|
65
|
-
for value in s["frequency_ranges_hz"].values()
|
|
66
|
-
)
|
|
67
|
-
assert (len(value) == 2 for value in s["frequency_ranges_hz"].values())
|
|
68
|
-
|
|
69
|
-
assert (
|
|
70
|
-
isinstance(value[0], list)
|
|
71
|
-
for value in s["frequency_ranges_hz"].values()
|
|
72
|
-
)
|
|
73
|
-
|
|
74
|
-
assert (
|
|
75
|
-
len(value[0]) == 2 for value in s["frequency_ranges_hz"].values()
|
|
76
|
-
)
|
|
77
|
-
|
|
78
|
-
assert (
|
|
79
|
-
isinstance(value[1], (float, int))
|
|
80
|
-
for value in s["frequency_ranges_hz"].values()
|
|
81
|
-
)
|
|
82
|
-
|
|
83
|
-
def init_KF(self, feature: str) -> None:
|
|
84
|
-
for f_band in self.s["kalman_filter_settings"]["frequency_bands"]:
|
|
85
|
-
for channel in self.ch_names:
|
|
86
|
-
self.KF_dict[
|
|
87
|
-
"_".join([channel, feature, f_band])
|
|
88
|
-
] = nm_kalmanfilter.define_KF(
|
|
89
|
-
self.s["kalman_filter_settings"]["Tp"],
|
|
90
|
-
self.s["kalman_filter_settings"]["sigma_w"],
|
|
91
|
-
self.s["kalman_filter_settings"]["sigma_v"],
|
|
92
|
-
)
|
|
93
|
-
|
|
94
|
-
def update_KF(self, feature_calc: float, KF_name: str) -> float:
|
|
95
|
-
if KF_name in self.KF_dict:
|
|
96
|
-
self.KF_dict[KF_name].predict()
|
|
97
|
-
self.KF_dict[KF_name].update(feature_calc)
|
|
98
|
-
feature_calc = self.KF_dict[KF_name].x[0]
|
|
99
|
-
return feature_calc
|
|
100
|
-
|
|
101
|
-
def estimate_osc_features(
|
|
102
|
-
self,
|
|
103
|
-
features_compute: dict,
|
|
104
|
-
data: np.ndarray,
|
|
105
|
-
feature_name: np.ndarray,
|
|
106
|
-
est_name: str,
|
|
107
|
-
):
|
|
108
|
-
for feature_est_name in list(self.s[est_name]["features"].keys()):
|
|
109
|
-
if self.s[est_name]["features"][feature_est_name] is True:
|
|
110
|
-
# switch case for feature_est_name
|
|
111
|
-
match feature_est_name:
|
|
112
|
-
case "mean":
|
|
113
|
-
features_compute[
|
|
114
|
-
f"{feature_name}_{feature_est_name}"
|
|
115
|
-
] = np.nanmean(data)
|
|
116
|
-
case "median":
|
|
117
|
-
features_compute[
|
|
118
|
-
f"{feature_name}_{feature_est_name}"
|
|
119
|
-
] = np.nanmedian(data)
|
|
120
|
-
case "std":
|
|
121
|
-
features_compute[
|
|
122
|
-
f"{feature_name}_{feature_est_name}"
|
|
123
|
-
] = np.nanstd(data)
|
|
124
|
-
case "max":
|
|
125
|
-
features_compute[
|
|
126
|
-
f"{feature_name}_{feature_est_name}"
|
|
127
|
-
] = np.nanmax(data)
|
|
128
|
-
|
|
129
|
-
return features_compute
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
class FFT(OscillatoryFeature):
|
|
133
|
-
def __init__(
|
|
134
|
-
self,
|
|
135
|
-
settings: dict,
|
|
136
|
-
ch_names: Iterable[str],
|
|
137
|
-
sfreq: float,
|
|
138
|
-
) -> None:
|
|
139
|
-
super().__init__(settings, ch_names, sfreq)
|
|
140
|
-
|
|
141
|
-
if self.s["fft_settings"]["log_transform"]:
|
|
142
|
-
self.log_transform = True
|
|
143
|
-
else:
|
|
144
|
-
self.log_transform = False
|
|
145
|
-
|
|
146
|
-
window_ms = self.s["fft_settings"]["windowlength_ms"]
|
|
147
|
-
self.window_samples = int(-np.floor(window_ms / 1000 * sfreq))
|
|
148
|
-
self.freqs = fft.rfftfreq(
|
|
149
|
-
-self.window_samples, 1 / np.floor(self.sfreq)
|
|
150
|
-
)
|
|
151
|
-
|
|
152
|
-
self.feature_params = []
|
|
153
|
-
for ch_idx, ch_name in enumerate(self.ch_names):
|
|
154
|
-
for fband, f_range in self.f_ranges_dict.items():
|
|
155
|
-
idx_range = np.where(
|
|
156
|
-
(self.freqs >= f_range[0]) & (self.freqs < f_range[1])
|
|
157
|
-
)[0]
|
|
158
|
-
feature_name = "_".join([ch_name, "fft", fband])
|
|
159
|
-
self.feature_params.append((ch_idx, feature_name, idx_range))
|
|
160
|
-
|
|
161
|
-
@staticmethod
|
|
162
|
-
def test_settings(s: dict, ch_names: Iterable[str], sfreq: int | float):
|
|
163
|
-
OscillatoryFeature.test_settings_osc(s, ch_names, sfreq, "fft_settings")
|
|
164
|
-
|
|
165
|
-
def calc_feature(self, data: np.ndarray, features_compute: dict) -> dict:
|
|
166
|
-
data = data[:, self.window_samples :]
|
|
167
|
-
Z = np.abs(fft.rfft(data))
|
|
168
|
-
|
|
169
|
-
if self.log_transform:
|
|
170
|
-
Z = np.log10(Z)
|
|
171
|
-
|
|
172
|
-
for ch_idx, feature_name, idx_range in self.feature_params:
|
|
173
|
-
Z_ch = Z[ch_idx, idx_range]
|
|
174
|
-
|
|
175
|
-
features_compute = self.estimate_osc_features(
|
|
176
|
-
features_compute, Z_ch, feature_name, "fft_settings"
|
|
177
|
-
)
|
|
178
|
-
|
|
179
|
-
for ch_idx, ch_name in enumerate(self.ch_names):
|
|
180
|
-
if self.s["fft_settings"]["return_spectrum"]:
|
|
181
|
-
features_compute.update(
|
|
182
|
-
{
|
|
183
|
-
f"{ch_name}_fft_psd_{str(f)}": Z[ch_idx][idx]
|
|
184
|
-
for idx, f in enumerate(self.freqs.astype(int))
|
|
185
|
-
}
|
|
186
|
-
)
|
|
187
|
-
|
|
188
|
-
return features_compute
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
class Welch(OscillatoryFeature):
|
|
192
|
-
def __init__(
|
|
193
|
-
self,
|
|
194
|
-
settings: dict,
|
|
195
|
-
ch_names: Iterable[str],
|
|
196
|
-
sfreq: float,
|
|
197
|
-
) -> None:
|
|
198
|
-
super().__init__(settings, ch_names, sfreq)
|
|
199
|
-
|
|
200
|
-
self.log_transform = self.s["welch_settings"]["log_transform"]
|
|
201
|
-
|
|
202
|
-
self.feature_params = []
|
|
203
|
-
for ch_idx, ch_name in enumerate(self.ch_names):
|
|
204
|
-
for fband, f_range in self.f_ranges_dict.items():
|
|
205
|
-
feature_name = "_".join([ch_name, "welch", fband])
|
|
206
|
-
self.feature_params.append((ch_idx, feature_name, f_range))
|
|
207
|
-
|
|
208
|
-
@staticmethod
|
|
209
|
-
def test_settings(s: dict, ch_names: Iterable[str], sfreq: int | float):
|
|
210
|
-
OscillatoryFeature.test_settings_osc(
|
|
211
|
-
s, ch_names, sfreq, "welch_settings"
|
|
212
|
-
)
|
|
213
|
-
|
|
214
|
-
def calc_feature(self, data: np.ndarray, features_compute: dict) -> dict:
|
|
215
|
-
freqs, Z = signal.welch(
|
|
216
|
-
data,
|
|
217
|
-
fs=self.sfreq,
|
|
218
|
-
window="hann",
|
|
219
|
-
nperseg=self.sfreq,
|
|
220
|
-
noverlap=None,
|
|
221
|
-
)
|
|
222
|
-
|
|
223
|
-
if self.log_transform:
|
|
224
|
-
Z = np.log10(Z)
|
|
225
|
-
|
|
226
|
-
for ch_idx, feature_name, f_range in self.feature_params:
|
|
227
|
-
Z_ch = Z[ch_idx]
|
|
228
|
-
|
|
229
|
-
idx_range = np.where((freqs >= f_range[0]) & (freqs <= f_range[1]))[
|
|
230
|
-
0
|
|
231
|
-
]
|
|
232
|
-
|
|
233
|
-
features_compute = self.estimate_osc_features(
|
|
234
|
-
features_compute,
|
|
235
|
-
Z_ch[idx_range],
|
|
236
|
-
feature_name,
|
|
237
|
-
"welch_settings",
|
|
238
|
-
)
|
|
239
|
-
|
|
240
|
-
for ch_idx, ch_name in enumerate(self.ch_names):
|
|
241
|
-
if self.s["welch_settings"]["return_spectrum"]:
|
|
242
|
-
features_compute.update(
|
|
243
|
-
{
|
|
244
|
-
f"{ch_name}_welch_psd_{str(f)}": Z[ch_idx][idx]
|
|
245
|
-
for idx, f in enumerate(freqs.astype(int))
|
|
246
|
-
}
|
|
247
|
-
)
|
|
248
|
-
|
|
249
|
-
return features_compute
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
class STFT(OscillatoryFeature):
|
|
253
|
-
def __init__(
|
|
254
|
-
self,
|
|
255
|
-
settings: dict,
|
|
256
|
-
ch_names: Iterable[str],
|
|
257
|
-
sfreq: float,
|
|
258
|
-
) -> None:
|
|
259
|
-
super().__init__(settings, ch_names, sfreq)
|
|
260
|
-
|
|
261
|
-
self.nperseg = int(self.s["stft_settings"]["windowlength_ms"])
|
|
262
|
-
self.log_transform = self.s["stft_settings"]["log_transform"]
|
|
263
|
-
|
|
264
|
-
self.feature_params = []
|
|
265
|
-
for ch_idx, ch_name in enumerate(self.ch_names):
|
|
266
|
-
for fband, f_range in self.f_ranges_dict.items():
|
|
267
|
-
feature_name = "_".join([ch_name, "stft", fband])
|
|
268
|
-
self.feature_params.append((ch_idx, feature_name, f_range))
|
|
269
|
-
|
|
270
|
-
@staticmethod
|
|
271
|
-
def test_settings(s: dict, ch_names: Iterable[str], sfreq: int | float):
|
|
272
|
-
OscillatoryFeature.test_settings_osc(
|
|
273
|
-
s, ch_names, sfreq, "stft_settings"
|
|
274
|
-
)
|
|
275
|
-
|
|
276
|
-
def calc_feature(self, data: np.ndarray, features_compute: dict) -> dict:
|
|
277
|
-
freqs, _, Zxx = signal.stft(
|
|
278
|
-
data,
|
|
279
|
-
fs=self.sfreq,
|
|
280
|
-
window="hamming",
|
|
281
|
-
nperseg=self.nperseg,
|
|
282
|
-
boundary="even",
|
|
283
|
-
)
|
|
284
|
-
Z = np.abs(Zxx)
|
|
285
|
-
if self.log_transform:
|
|
286
|
-
Z = np.log10(Z)
|
|
287
|
-
for ch_idx, feature_name, f_range in self.feature_params:
|
|
288
|
-
Z_ch = Z[ch_idx]
|
|
289
|
-
idx_range = np.where((freqs >= f_range[0]) & (freqs <= f_range[1]))[
|
|
290
|
-
0
|
|
291
|
-
]
|
|
292
|
-
|
|
293
|
-
features_compute = self.estimate_osc_features(
|
|
294
|
-
features_compute,
|
|
295
|
-
Z_ch[idx_range, :],
|
|
296
|
-
feature_name,
|
|
297
|
-
"stft_settings",
|
|
298
|
-
)
|
|
299
|
-
|
|
300
|
-
for ch_idx, ch_name in enumerate(self.ch_names):
|
|
301
|
-
if self.s["stft_settings"]["return_spectrum"]:
|
|
302
|
-
Z_ch_mean = Z[ch_idx].mean(axis=1)
|
|
303
|
-
features_compute.update(
|
|
304
|
-
{
|
|
305
|
-
f"{ch_name}_stft_psd_{str(f)}": Z_ch_mean[idx]
|
|
306
|
-
for idx, f in enumerate(freqs.astype(int))
|
|
307
|
-
}
|
|
308
|
-
)
|
|
309
|
-
|
|
310
|
-
return features_compute
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
class BandPower(OscillatoryFeature):
|
|
314
|
-
def __init__(
|
|
315
|
-
self,
|
|
316
|
-
settings: dict,
|
|
317
|
-
ch_names: Iterable[str],
|
|
318
|
-
sfreq: float,
|
|
319
|
-
use_kf: bool = None,
|
|
320
|
-
) -> None:
|
|
321
|
-
super().__init__(settings, ch_names, sfreq)
|
|
322
|
-
bp_settings = self.s["bandpass_filter_settings"]
|
|
323
|
-
|
|
324
|
-
self.bandpass_filter = nm_filter.MNEFilter(
|
|
325
|
-
f_ranges=list(self.f_ranges_dict.values()),
|
|
326
|
-
sfreq=self.sfreq,
|
|
327
|
-
filter_length=self.sfreq - 1,
|
|
328
|
-
verbose=False,
|
|
329
|
-
)
|
|
330
|
-
|
|
331
|
-
self.log_transform = bp_settings["log_transform"]
|
|
332
|
-
|
|
333
|
-
if use_kf is True or (
|
|
334
|
-
use_kf is None and bp_settings["kalman_filter"] is True
|
|
335
|
-
):
|
|
336
|
-
self.init_KF("bandpass_activity")
|
|
337
|
-
|
|
338
|
-
bp_features = ["activity", "mobility", "complexity"]
|
|
339
|
-
seglengths = bp_settings["segment_lengths_ms"]
|
|
340
|
-
|
|
341
|
-
self.feature_params = []
|
|
342
|
-
for ch_idx, ch_name in enumerate(self.ch_names):
|
|
343
|
-
for f_band_idx, f_band in enumerate(self.f_ranges_dict):
|
|
344
|
-
seglength_ms = seglengths[f_band]
|
|
345
|
-
seglen = int(np.floor(self.sfreq / 1000 * seglength_ms))
|
|
346
|
-
for bp_feature, v in bp_settings["bandpower_features"].items():
|
|
347
|
-
if v is True:
|
|
348
|
-
if bp_feature not in bp_features:
|
|
349
|
-
raise ValueError()
|
|
350
|
-
feature_name = "_".join(
|
|
351
|
-
[ch_name, "bandpass", bp_feature, f_band]
|
|
352
|
-
)
|
|
353
|
-
self.feature_params.append(
|
|
354
|
-
(
|
|
355
|
-
ch_idx,
|
|
356
|
-
ch_name,
|
|
357
|
-
f_band,
|
|
358
|
-
f_band_idx,
|
|
359
|
-
seglen,
|
|
360
|
-
bp_feature,
|
|
361
|
-
feature_name,
|
|
362
|
-
)
|
|
363
|
-
)
|
|
364
|
-
|
|
365
|
-
@staticmethod
|
|
366
|
-
def test_settings(s: dict, ch_names: Iterable[str], sfreq: int | float):
|
|
367
|
-
OscillatoryFeature.test_settings_osc(
|
|
368
|
-
s, ch_names, sfreq, "bandpass_filter_settings"
|
|
369
|
-
)
|
|
370
|
-
|
|
371
|
-
assert (
|
|
372
|
-
isinstance(value, bool)
|
|
373
|
-
for value in s["bandpass_filter_settings"][
|
|
374
|
-
"bandpower_features"
|
|
375
|
-
].values()
|
|
376
|
-
)
|
|
377
|
-
|
|
378
|
-
assert any(
|
|
379
|
-
value is True
|
|
380
|
-
for value in s["bandpass_filter_settings"][
|
|
381
|
-
"bandpower_features"
|
|
382
|
-
].values()
|
|
383
|
-
), "Set at least one bandpower_feature to True."
|
|
384
|
-
|
|
385
|
-
for fband_name, seg_length_fband in s["bandpass_filter_settings"][
|
|
386
|
-
"segment_lengths_ms"
|
|
387
|
-
].items():
|
|
388
|
-
assert isinstance(seg_length_fband, int), (
|
|
389
|
-
f"bandpass segment_lengths_ms for {fband_name} "
|
|
390
|
-
f"needs to be of type int, got {seg_length_fband}"
|
|
391
|
-
)
|
|
392
|
-
|
|
393
|
-
assert seg_length_fband <= s["segment_length_features_ms"], (
|
|
394
|
-
f"segment length {seg_length_fband} needs to be smaller than "
|
|
395
|
-
f" s['segment_length_features_ms'] = {s['segment_length_features_ms']}"
|
|
396
|
-
)
|
|
397
|
-
|
|
398
|
-
for fband_name in list(s["frequency_ranges_hz"].keys()):
|
|
399
|
-
assert fband_name in list(
|
|
400
|
-
s["bandpass_filter_settings"]["segment_lengths_ms"].keys()
|
|
401
|
-
), (
|
|
402
|
-
f"frequency range {fband_name} "
|
|
403
|
-
"needs to be defined in s['bandpass_filter_settings']['segment_lengths_ms']"
|
|
404
|
-
)
|
|
405
|
-
|
|
406
|
-
def calc_feature(self, data: np.ndarray, features_compute: dict) -> dict:
|
|
407
|
-
data = self.bandpass_filter.filter_data(data)
|
|
408
|
-
|
|
409
|
-
for (
|
|
410
|
-
ch_idx,
|
|
411
|
-
ch_name,
|
|
412
|
-
f_band,
|
|
413
|
-
f_band_idx,
|
|
414
|
-
seglen,
|
|
415
|
-
bp_feature,
|
|
416
|
-
feature_name,
|
|
417
|
-
) in self.feature_params:
|
|
418
|
-
if bp_feature == "activity":
|
|
419
|
-
if self.log_transform:
|
|
420
|
-
feature_calc = np.log10(
|
|
421
|
-
np.var(data[ch_idx, f_band_idx, -seglen:])
|
|
422
|
-
)
|
|
423
|
-
else:
|
|
424
|
-
feature_calc = np.var(data[ch_idx, f_band_idx, -seglen:])
|
|
425
|
-
elif bp_feature == "mobility":
|
|
426
|
-
deriv_variance = np.var(
|
|
427
|
-
np.diff(data[ch_idx, f_band_idx, -seglen:])
|
|
428
|
-
)
|
|
429
|
-
feature_calc = np.sqrt(
|
|
430
|
-
deriv_variance / np.var(data[ch_idx, f_band_idx, -seglen:])
|
|
431
|
-
)
|
|
432
|
-
elif bp_feature == "complexity":
|
|
433
|
-
dat_deriv = np.diff(data[ch_idx, f_band_idx, -seglen:])
|
|
434
|
-
deriv_variance = np.var(dat_deriv)
|
|
435
|
-
mobility = np.sqrt(
|
|
436
|
-
deriv_variance / np.var(data[ch_idx, f_band_idx, -seglen:])
|
|
437
|
-
)
|
|
438
|
-
dat_deriv_2 = np.diff(dat_deriv)
|
|
439
|
-
dat_deriv_2_var = np.var(dat_deriv_2)
|
|
440
|
-
deriv_mobility = np.sqrt(dat_deriv_2_var / deriv_variance)
|
|
441
|
-
feature_calc = deriv_mobility / mobility
|
|
442
|
-
|
|
443
|
-
if self.KF_dict and (bp_feature == "activity"):
|
|
444
|
-
feature_calc = self.update_KF(feature_calc, feature_name)
|
|
445
|
-
|
|
446
|
-
features_compute[feature_name] = np.nan_to_num(feature_calc)
|
|
447
|
-
|
|
448
|
-
return features_compute
|