py-neuromodulation 0.0.4__py3-none-any.whl → 0.0.5__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/FieldTrip.py +589 -589
- py_neuromodulation/__init__.py +74 -13
- py_neuromodulation/_write_example_dataset_helper.py +83 -65
- 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/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/nm_IO.py +413 -417
- py_neuromodulation/nm_RMAP.py +496 -531
- py_neuromodulation/nm_analysis.py +993 -1074
- py_neuromodulation/nm_artifacts.py +30 -25
- py_neuromodulation/nm_bispectra.py +154 -168
- py_neuromodulation/nm_bursts.py +292 -198
- py_neuromodulation/nm_coherence.py +251 -205
- py_neuromodulation/nm_database.py +149 -0
- py_neuromodulation/nm_decode.py +918 -992
- py_neuromodulation/nm_define_nmchannels.py +300 -302
- py_neuromodulation/nm_features.py +144 -116
- py_neuromodulation/nm_filter.py +219 -219
- py_neuromodulation/nm_filter_preprocessing.py +79 -91
- py_neuromodulation/nm_fooof.py +139 -159
- py_neuromodulation/nm_generator.py +45 -37
- py_neuromodulation/nm_hjorth_raw.py +52 -73
- py_neuromodulation/nm_kalmanfilter.py +71 -58
- py_neuromodulation/nm_linelength.py +21 -33
- py_neuromodulation/nm_logger.py +66 -0
- py_neuromodulation/nm_mne_connectivity.py +149 -112
- py_neuromodulation/nm_mnelsl_generator.py +90 -0
- py_neuromodulation/nm_mnelsl_stream.py +116 -0
- py_neuromodulation/nm_nolds.py +96 -93
- py_neuromodulation/nm_normalization.py +173 -214
- py_neuromodulation/nm_oscillatory.py +423 -448
- py_neuromodulation/nm_plots.py +585 -612
- py_neuromodulation/nm_preprocessing.py +83 -0
- py_neuromodulation/nm_projection.py +370 -394
- py_neuromodulation/nm_rereference.py +97 -95
- py_neuromodulation/nm_resample.py +59 -50
- py_neuromodulation/nm_run_analysis.py +325 -435
- py_neuromodulation/nm_settings.py +289 -68
- py_neuromodulation/nm_settings.yaml +244 -0
- py_neuromodulation/nm_sharpwaves.py +423 -401
- py_neuromodulation/nm_stats.py +464 -480
- py_neuromodulation/nm_stream.py +398 -0
- py_neuromodulation/nm_stream_abc.py +166 -218
- py_neuromodulation/nm_types.py +193 -0
- {py_neuromodulation-0.0.4.dist-info → py_neuromodulation-0.0.5.dist-info}/METADATA +29 -26
- py_neuromodulation-0.0.5.dist-info/RECORD +83 -0
- {py_neuromodulation-0.0.4.dist-info → py_neuromodulation-0.0.5.dist-info}/WHEEL +1 -1
- {py_neuromodulation-0.0.4.dist-info → py_neuromodulation-0.0.5.dist-info}/licenses/LICENSE +21 -21
- py_neuromodulation/nm_EpochStream.py +0 -92
- py_neuromodulation/nm_across_patient_decoding.py +0 -927
- py_neuromodulation/nm_cohortwrapper.py +0 -435
- py_neuromodulation/nm_eval_timing.py +0 -239
- py_neuromodulation/nm_features_abc.py +0 -39
- py_neuromodulation/nm_settings.json +0 -338
- 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,25 +1,30 @@
|
|
|
1
|
-
from pyparrm import PARRM
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
class PARRMArtifactRejection:
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
self.
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
1
|
+
from pyparrm import PARRM
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class PARRMArtifactRejection:
|
|
5
|
+
"""
|
|
6
|
+
This module enables training of a PARRM filter before computation,
|
|
7
|
+
that can in real-time then be applied.
|
|
8
|
+
https://pyparrm.readthedocs.io/en/stable/
|
|
9
|
+
"""
|
|
10
|
+
def __init__(self, data, sampling_freq, artefact_freq, verbose=False):
|
|
11
|
+
self.data = data
|
|
12
|
+
self.sampling_freq = sampling_freq
|
|
13
|
+
self.artefact_freq = artefact_freq
|
|
14
|
+
self.verbose = verbose
|
|
15
|
+
|
|
16
|
+
self.parrm = PARRM(
|
|
17
|
+
data=data,
|
|
18
|
+
sampling_freq=sampling_freq,
|
|
19
|
+
artefact_freq=artefact_freq,
|
|
20
|
+
verbose=False,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
def filter_data(self):
|
|
24
|
+
self.parrm.find_period()
|
|
25
|
+
self.parrm.create_filter(
|
|
26
|
+
filter_direction="both",
|
|
27
|
+
)
|
|
28
|
+
filtered_data = self.parrm.filter_data()
|
|
29
|
+
|
|
30
|
+
return filtered_data
|
|
@@ -1,168 +1,154 @@
|
|
|
1
|
-
from
|
|
2
|
-
|
|
3
|
-
from
|
|
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
|
-
self
|
|
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
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
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
|
|
1
|
+
from collections.abc import Iterable
|
|
2
|
+
from pydantic import field_validator
|
|
3
|
+
from py_neuromodulation.nm_types import NMBaseModel
|
|
4
|
+
from typing import TYPE_CHECKING, Callable
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
7
|
+
|
|
8
|
+
from py_neuromodulation.nm_features import NMFeature
|
|
9
|
+
from py_neuromodulation.nm_types import BoolSelector, FrequencyRange
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from py_neuromodulation.nm_settings import NMSettings
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class BispectraComponents(BoolSelector):
|
|
16
|
+
absolute: bool = True
|
|
17
|
+
real: bool = True
|
|
18
|
+
imag: bool = True
|
|
19
|
+
phase: bool = True
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class BispectraFeatures(BoolSelector):
|
|
23
|
+
mean: bool = True
|
|
24
|
+
sum: bool = True
|
|
25
|
+
var: bool = True
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class BispectraSettings(NMBaseModel):
|
|
29
|
+
f1s: FrequencyRange = FrequencyRange(5, 35)
|
|
30
|
+
f2s: FrequencyRange = FrequencyRange(5, 35)
|
|
31
|
+
compute_features_for_whole_fband_range: bool = True
|
|
32
|
+
frequency_bands: list[str] = ["theta", "alpha", "low_beta", "high_beta"]
|
|
33
|
+
|
|
34
|
+
components: BispectraComponents = BispectraComponents()
|
|
35
|
+
bispectrum_features: BispectraFeatures = BispectraFeatures()
|
|
36
|
+
|
|
37
|
+
@field_validator("f1s", "f2s")
|
|
38
|
+
def test_range(cls, filter_range):
|
|
39
|
+
assert (
|
|
40
|
+
filter_range[1] > filter_range[0]
|
|
41
|
+
), f"second frequency range value needs to be higher than first one, got {filter_range}"
|
|
42
|
+
return filter_range
|
|
43
|
+
|
|
44
|
+
@field_validator("frequency_bands")
|
|
45
|
+
def fbands_spaces_to_underscores(cls, frequency_bands):
|
|
46
|
+
return [f.replace(" ", "_") for f in frequency_bands]
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
FEATURE_DICT: dict[str, Callable] = {
|
|
50
|
+
"mean": np.nanmean,
|
|
51
|
+
"sum": np.nansum,
|
|
52
|
+
"var": np.nanvar,
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
COMPONENT_DICT: dict[str, Callable] = {
|
|
56
|
+
"real": lambda obj: getattr(obj, "real"),
|
|
57
|
+
"imag": lambda obj: getattr(obj, "imag"),
|
|
58
|
+
"absolute": np.abs,
|
|
59
|
+
"phase": np.angle,
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class Bispectra(NMFeature):
|
|
64
|
+
def __init__(
|
|
65
|
+
self, settings: "NMSettings", ch_names: Iterable[str], sfreq: float
|
|
66
|
+
) -> None:
|
|
67
|
+
self.sfreq = sfreq
|
|
68
|
+
self.ch_names = ch_names
|
|
69
|
+
self.frequency_ranges_hz = settings.frequency_ranges_hz
|
|
70
|
+
self.settings: BispectraSettings = settings.bispectrum
|
|
71
|
+
|
|
72
|
+
assert all(
|
|
73
|
+
f_band_bispectrum in settings.frequency_ranges_hz
|
|
74
|
+
for f_band_bispectrum in self.settings.frequency_bands
|
|
75
|
+
), (
|
|
76
|
+
"bispectrum selected frequency bands don't match the ones"
|
|
77
|
+
"specified in s['frequency_ranges_hz']"
|
|
78
|
+
f"bispectrum frequency bands: {self.settings.frequency_bands}"
|
|
79
|
+
f"specified frequency_ranges_hz: {settings.frequency_ranges_hz}"
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
self.used_features = self.settings.bispectrum_features.get_enabled()
|
|
83
|
+
|
|
84
|
+
self.min_freq = min(
|
|
85
|
+
self.settings.f1s.frequency_low_hz, self.settings.f2s.frequency_low_hz
|
|
86
|
+
)
|
|
87
|
+
self.max_freq = max(
|
|
88
|
+
self.settings.f1s.frequency_high_hz, self.settings.f2s.frequency_high_hz
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
# self.freqs: np.ndarray = np.array([]) # In case we pre-computed this
|
|
92
|
+
|
|
93
|
+
def calc_feature(self, data: np.ndarray) -> dict:
|
|
94
|
+
from pybispectra import compute_fft, WaveShape
|
|
95
|
+
|
|
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
|
+
fft_coeffs, freqs = compute_fft(
|
|
100
|
+
data=np.expand_dims(data, axis=(0)),
|
|
101
|
+
sampling_freq=self.sfreq,
|
|
102
|
+
n_points=data.shape[1],
|
|
103
|
+
verbose=False,
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
# freqs is batch independent, except for the last batch perhaps (if it has different shape)
|
|
107
|
+
# but it's computed by compute_fft regardless so no advantage in pre-computing it
|
|
108
|
+
# if not self.freqs = self.freqs = np.fft.rfftfreq(n=data.shape[1], d = 1 / sfreq)
|
|
109
|
+
|
|
110
|
+
# fft_coeffs shape: [epochs, channels, frequencies]
|
|
111
|
+
|
|
112
|
+
f_spectrum_range = freqs[
|
|
113
|
+
np.logical_and(freqs >= self.min_freq, freqs <= self.max_freq)
|
|
114
|
+
]
|
|
115
|
+
|
|
116
|
+
waveshape = WaveShape(
|
|
117
|
+
data=fft_coeffs,
|
|
118
|
+
freqs=freqs,
|
|
119
|
+
sampling_freq=self.sfreq,
|
|
120
|
+
verbose=False,
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
waveshape.compute(
|
|
124
|
+
f1s=tuple(self.settings.f1s), # type: ignore
|
|
125
|
+
f2s=tuple(self.settings.f2s), # type: ignore
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
feature_results = {}
|
|
129
|
+
for ch_idx, ch_name in enumerate(self.ch_names):
|
|
130
|
+
bispectrum = waveshape._bicoherence[
|
|
131
|
+
ch_idx
|
|
132
|
+
] # Same as waveshape.results._data, skips a copy
|
|
133
|
+
|
|
134
|
+
for component in self.settings.components.get_enabled():
|
|
135
|
+
spectrum_ch = COMPONENT_DICT[component](bispectrum)
|
|
136
|
+
|
|
137
|
+
for fb in self.settings.frequency_bands:
|
|
138
|
+
range_ = (f_spectrum_range >= self.frequency_ranges_hz[fb][0]) & (
|
|
139
|
+
f_spectrum_range <= self.frequency_ranges_hz[fb][1]
|
|
140
|
+
)
|
|
141
|
+
# waveshape.results.plot()
|
|
142
|
+
data_bs = spectrum_ch[range_, range_]
|
|
143
|
+
|
|
144
|
+
for bispectrum_feature in self.used_features:
|
|
145
|
+
feature_results[
|
|
146
|
+
f"{ch_name}_Bispectrum_{component}_{bispectrum_feature}_{fb}"
|
|
147
|
+
] = FEATURE_DICT[bispectrum_feature](data_bs)
|
|
148
|
+
|
|
149
|
+
if self.settings.compute_features_for_whole_fband_range:
|
|
150
|
+
feature_results[
|
|
151
|
+
f"{ch_name}_Bispectrum_{component}_{bispectrum_feature}_whole_fband_range"
|
|
152
|
+
] = FEATURE_DICT[bispectrum_feature](spectrum_ch)
|
|
153
|
+
|
|
154
|
+
return feature_results
|