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,168 +0,0 @@
|
|
|
1
|
-
from typing import Iterable
|
|
2
|
-
import numpy as np
|
|
3
|
-
from pybispectra import compute_fft, get_example_data_paths, WaveShape
|
|
4
|
-
|
|
5
|
-
from py_neuromodulation import nm_features_abc
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
class Bispectra(nm_features_abc.Feature):
|
|
9
|
-
def __init__(
|
|
10
|
-
self, settings: dict, ch_names: Iterable[str], sfreq: int | float
|
|
11
|
-
) -> None:
|
|
12
|
-
super().__init__(settings, ch_names, sfreq)
|
|
13
|
-
self.sfreq = sfreq
|
|
14
|
-
self.ch_names = ch_names
|
|
15
|
-
self.s = settings
|
|
16
|
-
self.f1s = settings["bispectrum"]["f1s"]
|
|
17
|
-
self.f2s = settings["bispectrum"]["f2s"]
|
|
18
|
-
|
|
19
|
-
@staticmethod
|
|
20
|
-
def test_settings(
|
|
21
|
-
settings: dict,
|
|
22
|
-
ch_names: Iterable[str],
|
|
23
|
-
sfreq: int | float,
|
|
24
|
-
):
|
|
25
|
-
s = settings
|
|
26
|
-
|
|
27
|
-
def test_range(f_name, filter_range):
|
|
28
|
-
assert isinstance(
|
|
29
|
-
filter_range[0],
|
|
30
|
-
int,
|
|
31
|
-
), f"bispectrum frequency range {f_name} needs to be of type int, got {filter_range[0]}"
|
|
32
|
-
assert isinstance(
|
|
33
|
-
filter_range[1],
|
|
34
|
-
int,
|
|
35
|
-
), f"bispectrum frequency range {f_name} needs to be of type int, got {filter_range[1]}"
|
|
36
|
-
assert (
|
|
37
|
-
filter_range[1] > filter_range[0]
|
|
38
|
-
), f"second frequency range value needs to be higher than first one, got {filter_range}"
|
|
39
|
-
assert filter_range[0] < sfreq and filter_range[1] < sfreq, (
|
|
40
|
-
"filter frequency range has to be smaller than sfreq, "
|
|
41
|
-
f"got sfreq {sfreq} and filter range {filter_range}"
|
|
42
|
-
)
|
|
43
|
-
|
|
44
|
-
test_range("f1s", s["bispectrum"]["f1s"])
|
|
45
|
-
test_range("f2s", s["bispectrum"]["f2s"])
|
|
46
|
-
|
|
47
|
-
for feature_name, val in s["bispectrum"]["components"].items():
|
|
48
|
-
assert isinstance(
|
|
49
|
-
val, bool
|
|
50
|
-
), f"bispectrum component {feature_name} has to be of type bool, got {val}"
|
|
51
|
-
|
|
52
|
-
for feature_name, val in s["bispectrum"]["bispectrum_features"].items():
|
|
53
|
-
assert isinstance(
|
|
54
|
-
val, bool
|
|
55
|
-
), f"bispectrum feature {feature_name} has to be of type bool, got {val}"
|
|
56
|
-
|
|
57
|
-
assert (
|
|
58
|
-
f_band_bispectrum in s["frequency_ranges_hz"]
|
|
59
|
-
for f_band_bispectrum in s["bispectrum"]["frequency_bands"]
|
|
60
|
-
), (
|
|
61
|
-
"bispectrum selected frequency bands don't match the ones"
|
|
62
|
-
"specified in s['frequency_ranges_hz']"
|
|
63
|
-
f"bispectrum frequency bands: {s['bispectrum']['frequency_bands']}"
|
|
64
|
-
f"specified frequency_ranges_hz: {s['frequency_ranges_hz']}"
|
|
65
|
-
)
|
|
66
|
-
|
|
67
|
-
def compute_bs_features(
|
|
68
|
-
self,
|
|
69
|
-
spectrum_ch: np.array,
|
|
70
|
-
features_compute: dict,
|
|
71
|
-
ch_name: str,
|
|
72
|
-
component: str,
|
|
73
|
-
f_band: str,
|
|
74
|
-
) -> dict:
|
|
75
|
-
for bispectrum_feature in self.s["bispectrum"]["bispectrum_features"]:
|
|
76
|
-
if bispectrum_feature == "mean":
|
|
77
|
-
func = np.nanmean
|
|
78
|
-
if bispectrum_feature == "sum":
|
|
79
|
-
func = np.nansum
|
|
80
|
-
if bispectrum_feature == "var":
|
|
81
|
-
func = np.nanvar
|
|
82
|
-
|
|
83
|
-
if f_band is not None:
|
|
84
|
-
str_feature = "_".join(
|
|
85
|
-
[
|
|
86
|
-
ch_name,
|
|
87
|
-
"Bispectrum",
|
|
88
|
-
component,
|
|
89
|
-
bispectrum_feature,
|
|
90
|
-
f_band,
|
|
91
|
-
]
|
|
92
|
-
)
|
|
93
|
-
else:
|
|
94
|
-
str_feature = "_".join(
|
|
95
|
-
[
|
|
96
|
-
ch_name,
|
|
97
|
-
"Bispectrum",
|
|
98
|
-
component,
|
|
99
|
-
bispectrum_feature,
|
|
100
|
-
"whole_fband_range",
|
|
101
|
-
]
|
|
102
|
-
)
|
|
103
|
-
|
|
104
|
-
features_compute[str_feature] = func(spectrum_ch)
|
|
105
|
-
|
|
106
|
-
return features_compute
|
|
107
|
-
|
|
108
|
-
def calc_feature(self, data: np.array, features_compute: dict) -> dict:
|
|
109
|
-
for ch_idx, ch_name in enumerate(self.ch_names):
|
|
110
|
-
fft_coeffs, freqs = compute_fft(
|
|
111
|
-
data=np.expand_dims(data[ch_idx, :], axis=(0, 1)),
|
|
112
|
-
sampling_freq=self.sfreq,
|
|
113
|
-
n_points=data.shape[1],
|
|
114
|
-
verbose=False,
|
|
115
|
-
)
|
|
116
|
-
|
|
117
|
-
f_spectrum_range = freqs[
|
|
118
|
-
np.logical_and(
|
|
119
|
-
freqs >= np.min([self.f1s, self.f2s]),
|
|
120
|
-
freqs <= np.max([self.f1s, self.f2s]),
|
|
121
|
-
)
|
|
122
|
-
]
|
|
123
|
-
|
|
124
|
-
waveshape = WaveShape(
|
|
125
|
-
data=fft_coeffs,
|
|
126
|
-
freqs=freqs,
|
|
127
|
-
sampling_freq=self.sfreq,
|
|
128
|
-
verbose=False,
|
|
129
|
-
)
|
|
130
|
-
|
|
131
|
-
waveshape.compute(
|
|
132
|
-
f1s=(self.f1s[0], self.f1s[-1]), f2s=(self.f2s[0], self.f2s[-1])
|
|
133
|
-
)
|
|
134
|
-
|
|
135
|
-
bispectrum = np.squeeze(waveshape.results._data)
|
|
136
|
-
|
|
137
|
-
for component in self.s["bispectrum"]["components"]:
|
|
138
|
-
if self.s["bispectrum"]["components"][component]:
|
|
139
|
-
if component == "real":
|
|
140
|
-
spectrum_ch = bispectrum.real
|
|
141
|
-
if component == "imag":
|
|
142
|
-
spectrum_ch = bispectrum.imag
|
|
143
|
-
if component == "absolute":
|
|
144
|
-
spectrum_ch = np.abs(bispectrum)
|
|
145
|
-
if component == "phase":
|
|
146
|
-
spectrum_ch = np.angle(bispectrum)
|
|
147
|
-
|
|
148
|
-
for fb in self.s["bispectrum"]["frequency_bands"]:
|
|
149
|
-
range_ = (
|
|
150
|
-
f_spectrum_range >= self.s["frequency_ranges_hz"][fb][0]
|
|
151
|
-
) & (
|
|
152
|
-
f_spectrum_range <= self.s["frequency_ranges_hz"][fb][1]
|
|
153
|
-
)
|
|
154
|
-
# waveshape.results.plot()
|
|
155
|
-
data_bs = spectrum_ch[range_, range_]
|
|
156
|
-
|
|
157
|
-
features_compute = self.compute_bs_features(
|
|
158
|
-
data_bs, features_compute, ch_name, component, fb
|
|
159
|
-
)
|
|
160
|
-
|
|
161
|
-
if self.s["bispectrum"][
|
|
162
|
-
"compute_features_for_whole_fband_range"
|
|
163
|
-
]:
|
|
164
|
-
features_compute = self.compute_bs_features(
|
|
165
|
-
spectrum_ch, features_compute, ch_name, component, None
|
|
166
|
-
)
|
|
167
|
-
|
|
168
|
-
return features_compute
|
py_neuromodulation/nm_bursts.py
DELETED
|
@@ -1,198 +0,0 @@
|
|
|
1
|
-
import enum
|
|
2
|
-
import numpy as np
|
|
3
|
-
from typing import Iterable
|
|
4
|
-
from scipy import signal
|
|
5
|
-
|
|
6
|
-
from py_neuromodulation import nm_features_abc, nm_filter
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
class Burst(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.threshold = self.s["burst_settings"]["threshold"]
|
|
17
|
-
self.time_duration_s = self.s["burst_settings"]["time_duration_s"]
|
|
18
|
-
self.samples_overlap = int(
|
|
19
|
-
self.sfreq
|
|
20
|
-
* (self.s["segment_length_features_ms"] / 1000)
|
|
21
|
-
/ self.s["sampling_rate_features_hz"]
|
|
22
|
-
)
|
|
23
|
-
|
|
24
|
-
self.fband_names = self.s["burst_settings"]["frequency_bands"]
|
|
25
|
-
self.f_ranges = [
|
|
26
|
-
self.s["frequency_ranges_hz"][fband_name]
|
|
27
|
-
for fband_name in self.fband_names
|
|
28
|
-
]
|
|
29
|
-
self.seglengths = np.floor(
|
|
30
|
-
self.sfreq
|
|
31
|
-
/ 1000
|
|
32
|
-
* np.array(
|
|
33
|
-
[
|
|
34
|
-
self.s["bandpass_filter_settings"]["segment_lengths_ms"][
|
|
35
|
-
fband
|
|
36
|
-
]
|
|
37
|
-
for fband in self.fband_names
|
|
38
|
-
]
|
|
39
|
-
)
|
|
40
|
-
).astype(int)
|
|
41
|
-
|
|
42
|
-
self.num_max_samples_ring_buffer = int(
|
|
43
|
-
self.sfreq * self.time_duration_s
|
|
44
|
-
)
|
|
45
|
-
|
|
46
|
-
self.bandpass_filter = nm_filter.MNEFilter(
|
|
47
|
-
f_ranges=self.f_ranges,
|
|
48
|
-
sfreq=self.sfreq,
|
|
49
|
-
filter_length=self.sfreq - 1,
|
|
50
|
-
verbose=False,
|
|
51
|
-
)
|
|
52
|
-
|
|
53
|
-
# create dict with fband, channel specific data store
|
|
54
|
-
# for previous time_duration_s
|
|
55
|
-
def init_ch_fband_dict() -> dict:
|
|
56
|
-
d = {}
|
|
57
|
-
for ch in self.ch_names:
|
|
58
|
-
if ch not in d:
|
|
59
|
-
d[ch] = {}
|
|
60
|
-
for fb in self.fband_names:
|
|
61
|
-
if fb not in d[ch]:
|
|
62
|
-
d[ch][fb] = None
|
|
63
|
-
return d
|
|
64
|
-
|
|
65
|
-
self.data_buffer = init_ch_fband_dict()
|
|
66
|
-
|
|
67
|
-
def test_settings(
|
|
68
|
-
settings: dict,
|
|
69
|
-
ch_names: Iterable[str],
|
|
70
|
-
sfreq: int | float,
|
|
71
|
-
):
|
|
72
|
-
assert isinstance(
|
|
73
|
-
settings["burst_settings"]["threshold"], (float, int)
|
|
74
|
-
), f"burst settings threshold needs to be type int or float, got: {settings['burst_settings']['threshold']}"
|
|
75
|
-
assert (
|
|
76
|
-
0 < settings["burst_settings"]["threshold"] < 100
|
|
77
|
-
), f"burst setting threshold needs to be between 0 and 100, got: {settings['burst_settings']['threshold']}"
|
|
78
|
-
assert isinstance(
|
|
79
|
-
settings["burst_settings"]["time_duration_s"], (float, int)
|
|
80
|
-
), f"burst settings time_duration_s needs to be type int or float, got: {settings['burst_settings']['time_duration_s']}"
|
|
81
|
-
assert (
|
|
82
|
-
settings["burst_settings"]["time_duration_s"] > 0
|
|
83
|
-
), f"burst setting time_duration_s needs to be greater than 0, got: {settings['burst_settings']['time_duration_s']}"
|
|
84
|
-
|
|
85
|
-
for fband_burst in settings["burst_settings"]["frequency_bands"]:
|
|
86
|
-
assert fband_burst in list(
|
|
87
|
-
settings["frequency_ranges_hz"].keys()
|
|
88
|
-
), f"bursting {fband_burst} needs to be defined in settings['frequency_ranges_hz']"
|
|
89
|
-
|
|
90
|
-
for burst_feature in settings["burst_settings"][
|
|
91
|
-
"burst_features"
|
|
92
|
-
].keys():
|
|
93
|
-
assert isinstance(
|
|
94
|
-
settings["burst_settings"]["burst_features"][burst_feature],
|
|
95
|
-
bool,
|
|
96
|
-
), (
|
|
97
|
-
f"bursting feature {burst_feature} needs to be type bool, "
|
|
98
|
-
f"got: {settings['burst_settings']['burst_features'][burst_feature]}"
|
|
99
|
-
)
|
|
100
|
-
|
|
101
|
-
def calc_feature(self, data: np.array, features_compute: dict) -> dict:
|
|
102
|
-
# filter_data returns (n_channels, n_fbands, n_samples)
|
|
103
|
-
filtered_data = np.abs(
|
|
104
|
-
signal.hilbert(self.bandpass_filter.filter_data(data), axis=2)
|
|
105
|
-
)
|
|
106
|
-
for ch_idx, ch_name in enumerate(self.ch_names):
|
|
107
|
-
for fband_idx, fband_name in enumerate(self.fband_names):
|
|
108
|
-
new_dat = filtered_data[ch_idx, fband_idx, :]
|
|
109
|
-
if self.data_buffer[ch_name][fband_name] is None:
|
|
110
|
-
self.data_buffer[ch_name][fband_name] = new_dat
|
|
111
|
-
else:
|
|
112
|
-
self.data_buffer[ch_name][fband_name] = np.concatenate(
|
|
113
|
-
(
|
|
114
|
-
self.data_buffer[ch_name][fband_name],
|
|
115
|
-
new_dat[-self.samples_overlap :],
|
|
116
|
-
),
|
|
117
|
-
axis=0,
|
|
118
|
-
)[-self.num_max_samples_ring_buffer :]
|
|
119
|
-
|
|
120
|
-
# calc features
|
|
121
|
-
burst_thr = np.percentile(
|
|
122
|
-
self.data_buffer[ch_name][fband_name], q=self.threshold
|
|
123
|
-
)
|
|
124
|
-
|
|
125
|
-
burst_amplitude, burst_length = self.get_burst_amplitude_length(
|
|
126
|
-
new_dat, burst_thr, self.sfreq
|
|
127
|
-
)
|
|
128
|
-
|
|
129
|
-
features_compute[
|
|
130
|
-
f"{ch_name}_bursts_{fband_name}_duration_mean"
|
|
131
|
-
] = (np.mean(burst_length) if len(burst_length) != 0 else 0)
|
|
132
|
-
features_compute[
|
|
133
|
-
f"{ch_name}_bursts_{fband_name}_amplitude_mean"
|
|
134
|
-
] = (
|
|
135
|
-
np.mean([np.mean(a) for a in burst_amplitude])
|
|
136
|
-
if len(burst_length) != 0
|
|
137
|
-
else 0
|
|
138
|
-
)
|
|
139
|
-
|
|
140
|
-
features_compute[
|
|
141
|
-
f"{ch_name}_bursts_{fband_name}_duration_max"
|
|
142
|
-
] = (np.max(burst_length) if len(burst_length) != 0 else 0)
|
|
143
|
-
features_compute[
|
|
144
|
-
f"{ch_name}_bursts_{fband_name}_amplitude_max"
|
|
145
|
-
] = (
|
|
146
|
-
np.max([np.max(a) for a in burst_amplitude])
|
|
147
|
-
if len(burst_amplitude) != 0
|
|
148
|
-
else 0
|
|
149
|
-
)
|
|
150
|
-
|
|
151
|
-
features_compute[
|
|
152
|
-
f"{ch_name}_bursts_{fband_name}_burst_rate_per_s"
|
|
153
|
-
] = (
|
|
154
|
-
np.mean(burst_length)
|
|
155
|
-
/ (self.s["segment_length_features_ms"] / 1000)
|
|
156
|
-
if len(burst_length) != 0
|
|
157
|
-
else 0
|
|
158
|
-
)
|
|
159
|
-
|
|
160
|
-
in_burst = False
|
|
161
|
-
if self.data_buffer[ch_name][fband_name][-1] > burst_thr:
|
|
162
|
-
in_burst = True
|
|
163
|
-
|
|
164
|
-
features_compute[f"{ch_name}_bursts_{fband_name}_in_burst"] = (
|
|
165
|
-
in_burst
|
|
166
|
-
)
|
|
167
|
-
return features_compute
|
|
168
|
-
|
|
169
|
-
@staticmethod
|
|
170
|
-
def get_burst_amplitude_length(
|
|
171
|
-
beta_averp_norm, burst_thr: float, sfreq: float
|
|
172
|
-
):
|
|
173
|
-
"""
|
|
174
|
-
Analysing the duration of beta burst
|
|
175
|
-
"""
|
|
176
|
-
bursts = np.zeros((beta_averp_norm.shape[0] + 1), dtype=bool)
|
|
177
|
-
bursts[1:] = beta_averp_norm >= burst_thr
|
|
178
|
-
deriv = np.diff(bursts)
|
|
179
|
-
burst_length = []
|
|
180
|
-
burst_amplitude = []
|
|
181
|
-
|
|
182
|
-
burst_time_points = np.where(deriv == True)[0]
|
|
183
|
-
|
|
184
|
-
for i in range(burst_time_points.size // 2):
|
|
185
|
-
burst_length.append(
|
|
186
|
-
burst_time_points[2 * i + 1] - burst_time_points[2 * i]
|
|
187
|
-
)
|
|
188
|
-
burst_amplitude.append(
|
|
189
|
-
beta_averp_norm[
|
|
190
|
-
burst_time_points[2 * i] : burst_time_points[2 * i + 1]
|
|
191
|
-
]
|
|
192
|
-
)
|
|
193
|
-
|
|
194
|
-
# the last burst length (in case isburst == True) is omitted,
|
|
195
|
-
# since the true burst length cannot be estimated
|
|
196
|
-
burst_length = np.array(burst_length) / sfreq
|
|
197
|
-
|
|
198
|
-
return burst_amplitude, burst_length
|
|
@@ -1,205 +0,0 @@
|
|
|
1
|
-
from scipy import signal
|
|
2
|
-
import numpy as np
|
|
3
|
-
from typing import Iterable
|
|
4
|
-
import warnings
|
|
5
|
-
|
|
6
|
-
from py_neuromodulation import nm_features_abc
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
class CoherenceObject:
|
|
10
|
-
def __init__(
|
|
11
|
-
self,
|
|
12
|
-
sfreq,
|
|
13
|
-
window,
|
|
14
|
-
fbands,
|
|
15
|
-
fband_names,
|
|
16
|
-
ch_1_name,
|
|
17
|
-
ch_2_name,
|
|
18
|
-
ch_1_idx,
|
|
19
|
-
ch_2_idx,
|
|
20
|
-
coh,
|
|
21
|
-
icoh,
|
|
22
|
-
features_coh,
|
|
23
|
-
) -> None:
|
|
24
|
-
self.sfreq = sfreq
|
|
25
|
-
self.window = window
|
|
26
|
-
self.Pxx = None
|
|
27
|
-
self.Pyy = None
|
|
28
|
-
self.Pxy = None
|
|
29
|
-
self.f = None
|
|
30
|
-
self.coh = coh
|
|
31
|
-
self.icoh = icoh
|
|
32
|
-
self.coh_val = None
|
|
33
|
-
self.icoh_val = None
|
|
34
|
-
self.ch_1 = ch_1_name
|
|
35
|
-
self.ch_2 = ch_2_name
|
|
36
|
-
self.ch_1_idx = ch_1_idx
|
|
37
|
-
self.ch_2_idx = ch_2_idx
|
|
38
|
-
self.fbands = fbands # list of lists, e.g. [[10, 15], [15, 20]]
|
|
39
|
-
self.fband_names = fband_names
|
|
40
|
-
self.features_coh = features_coh
|
|
41
|
-
|
|
42
|
-
def get_coh(self, features_compute, x, y):
|
|
43
|
-
self.f, self.Pxx = signal.welch(x, self.sfreq, self.window, nperseg=128)
|
|
44
|
-
self.Pyy = signal.welch(y, self.sfreq, self.window, nperseg=128)[1]
|
|
45
|
-
self.Pxy = signal.csd(x, y, self.sfreq, self.window, nperseg=128)[1]
|
|
46
|
-
|
|
47
|
-
if self.coh is True:
|
|
48
|
-
self.coh_val = np.abs(self.Pxy**2) / (self.Pxx * self.Pyy)
|
|
49
|
-
if self.icoh is True:
|
|
50
|
-
self.icoh_val = np.array(self.Pxy / (self.Pxx * self.Pyy)).imag
|
|
51
|
-
|
|
52
|
-
for coh_idx, coh_type in enumerate([self.coh, self.icoh]):
|
|
53
|
-
if coh_type is True:
|
|
54
|
-
if coh_idx == 0:
|
|
55
|
-
coh_val = self.coh_val
|
|
56
|
-
coh_name = "coh"
|
|
57
|
-
else:
|
|
58
|
-
coh_val = self.icoh_val
|
|
59
|
-
coh_name = "icoh"
|
|
60
|
-
|
|
61
|
-
for idx, fband in enumerate(self.fbands):
|
|
62
|
-
if self.features_coh["mean_fband"] is True:
|
|
63
|
-
feature_calc = np.mean(
|
|
64
|
-
coh_val[
|
|
65
|
-
np.bitwise_and(self.f > fband[0], self.f < fband[1])
|
|
66
|
-
]
|
|
67
|
-
)
|
|
68
|
-
feature_name = "_".join(
|
|
69
|
-
[
|
|
70
|
-
coh_name,
|
|
71
|
-
self.ch_1,
|
|
72
|
-
"to",
|
|
73
|
-
self.ch_2,
|
|
74
|
-
"mean_fband",
|
|
75
|
-
self.fband_names[idx],
|
|
76
|
-
]
|
|
77
|
-
)
|
|
78
|
-
features_compute[feature_name] = feature_calc
|
|
79
|
-
if self.features_coh["max_fband"] is True:
|
|
80
|
-
feature_calc = np.max(
|
|
81
|
-
coh_val[
|
|
82
|
-
np.bitwise_and(self.f > fband[0], self.f < fband[1])
|
|
83
|
-
]
|
|
84
|
-
)
|
|
85
|
-
feature_name = "_".join(
|
|
86
|
-
[
|
|
87
|
-
coh_name,
|
|
88
|
-
self.ch_1,
|
|
89
|
-
"to",
|
|
90
|
-
self.ch_2,
|
|
91
|
-
"max_fband",
|
|
92
|
-
self.fband_names[idx],
|
|
93
|
-
]
|
|
94
|
-
)
|
|
95
|
-
features_compute[feature_name] = feature_calc
|
|
96
|
-
if self.features_coh["max_allfbands"] is True:
|
|
97
|
-
feature_calc = self.f[np.argmax(coh_val)]
|
|
98
|
-
feature_name = "_".join(
|
|
99
|
-
[
|
|
100
|
-
coh_name,
|
|
101
|
-
self.ch_1,
|
|
102
|
-
"to",
|
|
103
|
-
self.ch_2,
|
|
104
|
-
"max_allfbands",
|
|
105
|
-
self.fband_names[idx],
|
|
106
|
-
]
|
|
107
|
-
)
|
|
108
|
-
features_compute[feature_name] = feature_calc
|
|
109
|
-
return features_compute
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
class NM_Coherence(nm_features_abc.Feature):
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
def __init__(
|
|
117
|
-
self, settings: dict, ch_names: Iterable[str], sfreq: float
|
|
118
|
-
) -> None:
|
|
119
|
-
self.s = settings
|
|
120
|
-
self.sfreq = sfreq
|
|
121
|
-
self.ch_names = ch_names
|
|
122
|
-
self.coherence_objects: Iterable[CoherenceObject] = []
|
|
123
|
-
|
|
124
|
-
for idx_coh in range(len(self.s["coherence"]["channels"])):
|
|
125
|
-
fband_names = self.s["coherence"]["frequency_bands"]
|
|
126
|
-
fband_specs = []
|
|
127
|
-
for band_name in fband_names:
|
|
128
|
-
fband_specs.append(self.s["frequency_ranges_hz"][band_name])
|
|
129
|
-
|
|
130
|
-
ch_1_name = self.s["coherence"]["channels"][idx_coh][0]
|
|
131
|
-
ch_1_name_reref = [
|
|
132
|
-
ch for ch in self.ch_names if ch.startswith(ch_1_name)
|
|
133
|
-
][0]
|
|
134
|
-
ch_1_idx = self.ch_names.index(ch_1_name_reref)
|
|
135
|
-
|
|
136
|
-
ch_2_name = self.s["coherence"]["channels"][idx_coh][1]
|
|
137
|
-
ch_2_name_reref = [
|
|
138
|
-
ch for ch in self.ch_names if ch.startswith(ch_2_name)
|
|
139
|
-
][0]
|
|
140
|
-
ch_2_idx = self.ch_names.index(ch_2_name_reref)
|
|
141
|
-
|
|
142
|
-
self.coherence_objects.append(
|
|
143
|
-
CoherenceObject(
|
|
144
|
-
sfreq,
|
|
145
|
-
"hann",
|
|
146
|
-
fband_specs,
|
|
147
|
-
fband_names,
|
|
148
|
-
ch_1_name,
|
|
149
|
-
ch_2_name,
|
|
150
|
-
ch_1_idx,
|
|
151
|
-
ch_2_idx,
|
|
152
|
-
self.s["coherence"]["method"]["coh"],
|
|
153
|
-
self.s["coherence"]["method"]["icoh"],
|
|
154
|
-
self.s["coherence"]["features"],
|
|
155
|
-
)
|
|
156
|
-
)
|
|
157
|
-
|
|
158
|
-
@staticmethod
|
|
159
|
-
def test_settings(
|
|
160
|
-
s: dict,
|
|
161
|
-
ch_names: Iterable[str],
|
|
162
|
-
sfreq: int | float,
|
|
163
|
-
):
|
|
164
|
-
|
|
165
|
-
assert (
|
|
166
|
-
len(s["coherence"]["frequency_bands"]) > 0
|
|
167
|
-
), "coherence frequency_bands list needs to specify at least one frequency band"
|
|
168
|
-
assert (ch_coh in ch_names for ch_coh in s["coherence"]["channels"]), (
|
|
169
|
-
f"coherence selected channels don't match the ones in nm_channels"
|
|
170
|
-
f"ch_names: {ch_names} settings['coherence']['channels']: {s['coherence']['channels']}"
|
|
171
|
-
)
|
|
172
|
-
|
|
173
|
-
assert (
|
|
174
|
-
f_band_coh in s["frequency_ranges_hz"]
|
|
175
|
-
for f_band_coh in s["coherence"]["frequency_bands"]
|
|
176
|
-
), (
|
|
177
|
-
"coherence selected frequency bands don't match the ones"
|
|
178
|
-
"specified in s['frequency_ranges_hz']"
|
|
179
|
-
f"coherence frequency bands: {s['coherence']['frequency_bands']}"
|
|
180
|
-
f"specified frequency_ranges_hz: {s['frequency_ranges_hz']}"
|
|
181
|
-
)
|
|
182
|
-
|
|
183
|
-
assert (
|
|
184
|
-
s["frequency_ranges_hz"][fb][0] < sfreq / 2
|
|
185
|
-
and s["frequency_ranges_hz"][fb][1] < sfreq / 2
|
|
186
|
-
for fb in s["coherence"]["frequency_bands"]
|
|
187
|
-
), (
|
|
188
|
-
"the coherence frequency band ranges need to be smaller than the nyquist frequency"
|
|
189
|
-
f"got sfreq = {sfreq} and fband ranges {s['coherence']['frequency_bands']}"
|
|
190
|
-
)
|
|
191
|
-
|
|
192
|
-
if sum(list(s["coherence"]["method"].values())) == 0:
|
|
193
|
-
warnings.warn(
|
|
194
|
-
"feature coherence enabled, but no coherence['method'] selected"
|
|
195
|
-
)
|
|
196
|
-
|
|
197
|
-
def calc_feature(self, data: np.array, features_compute: dict) -> dict:
|
|
198
|
-
for coh_obj in self.coherence_objects:
|
|
199
|
-
features_compute = coh_obj.get_coh(
|
|
200
|
-
features_compute,
|
|
201
|
-
data[coh_obj.ch_1_idx, :],
|
|
202
|
-
data[coh_obj.ch_2_idx, :],
|
|
203
|
-
)
|
|
204
|
-
|
|
205
|
-
return features_compute
|