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.
Files changed (109) hide show
  1. py_neuromodulation/ConnectivityDecoding/_get_grid_hull.m +34 -34
  2. py_neuromodulation/ConnectivityDecoding/_get_grid_whole_brain.py +95 -106
  3. py_neuromodulation/ConnectivityDecoding/_helper_write_connectome.py +107 -119
  4. py_neuromodulation/__init__.py +80 -13
  5. py_neuromodulation/{nm_RMAP.py → analysis/RMAP.py} +496 -531
  6. py_neuromodulation/analysis/__init__.py +4 -0
  7. py_neuromodulation/{nm_decode.py → analysis/decode.py} +918 -992
  8. py_neuromodulation/{nm_analysis.py → analysis/feature_reader.py} +994 -1074
  9. py_neuromodulation/{nm_plots.py → analysis/plots.py} +627 -612
  10. py_neuromodulation/{nm_stats.py → analysis/stats.py} +458 -480
  11. py_neuromodulation/data/README +6 -6
  12. py_neuromodulation/data/dataset_description.json +8 -8
  13. py_neuromodulation/data/participants.json +32 -32
  14. py_neuromodulation/data/participants.tsv +2 -2
  15. py_neuromodulation/data/sub-testsub/ses-EphysMedOff/ieeg/sub-testsub_ses-EphysMedOff_space-mni_coordsystem.json +5 -5
  16. py_neuromodulation/data/sub-testsub/ses-EphysMedOff/ieeg/sub-testsub_ses-EphysMedOff_space-mni_electrodes.tsv +11 -11
  17. py_neuromodulation/data/sub-testsub/ses-EphysMedOff/ieeg/sub-testsub_ses-EphysMedOff_task-gripforce_run-0_channels.tsv +11 -11
  18. py_neuromodulation/data/sub-testsub/ses-EphysMedOff/ieeg/sub-testsub_ses-EphysMedOff_task-gripforce_run-0_ieeg.json +18 -18
  19. py_neuromodulation/data/sub-testsub/ses-EphysMedOff/ieeg/sub-testsub_ses-EphysMedOff_task-gripforce_run-0_ieeg.vhdr +35 -35
  20. py_neuromodulation/data/sub-testsub/ses-EphysMedOff/ieeg/sub-testsub_ses-EphysMedOff_task-gripforce_run-0_ieeg.vmrk +13 -13
  21. py_neuromodulation/data/sub-testsub/ses-EphysMedOff/sub-testsub_ses-EphysMedOff_scans.tsv +2 -2
  22. py_neuromodulation/default_settings.yaml +241 -0
  23. py_neuromodulation/features/__init__.py +31 -0
  24. py_neuromodulation/features/bandpower.py +165 -0
  25. py_neuromodulation/features/bispectra.py +157 -0
  26. py_neuromodulation/features/bursts.py +297 -0
  27. py_neuromodulation/features/coherence.py +255 -0
  28. py_neuromodulation/features/feature_processor.py +121 -0
  29. py_neuromodulation/features/fooof.py +142 -0
  30. py_neuromodulation/features/hjorth_raw.py +57 -0
  31. py_neuromodulation/features/linelength.py +21 -0
  32. py_neuromodulation/features/mne_connectivity.py +148 -0
  33. py_neuromodulation/features/nolds.py +94 -0
  34. py_neuromodulation/features/oscillatory.py +249 -0
  35. py_neuromodulation/features/sharpwaves.py +432 -0
  36. py_neuromodulation/filter/__init__.py +3 -0
  37. py_neuromodulation/filter/kalman_filter.py +67 -0
  38. py_neuromodulation/filter/kalman_filter_external.py +1890 -0
  39. py_neuromodulation/filter/mne_filter.py +128 -0
  40. py_neuromodulation/filter/notch_filter.py +93 -0
  41. py_neuromodulation/grid_cortex.tsv +40 -40
  42. py_neuromodulation/liblsl/libpugixml.so.1.12 +0 -0
  43. py_neuromodulation/liblsl/linux/bionic_amd64/liblsl.1.16.2.so +0 -0
  44. py_neuromodulation/liblsl/linux/bookworm_amd64/liblsl.1.16.2.so +0 -0
  45. py_neuromodulation/liblsl/linux/focal_amd46/liblsl.1.16.2.so +0 -0
  46. py_neuromodulation/liblsl/linux/jammy_amd64/liblsl.1.16.2.so +0 -0
  47. py_neuromodulation/liblsl/linux/jammy_x86/liblsl.1.16.2.so +0 -0
  48. py_neuromodulation/liblsl/linux/noble_amd64/liblsl.1.16.2.so +0 -0
  49. py_neuromodulation/liblsl/macos/amd64/liblsl.1.16.2.dylib +0 -0
  50. py_neuromodulation/liblsl/macos/arm64/liblsl.1.16.0.dylib +0 -0
  51. py_neuromodulation/liblsl/windows/amd64/liblsl.1.16.2.dll +0 -0
  52. py_neuromodulation/liblsl/windows/x86/liblsl.1.16.2.dll +0 -0
  53. py_neuromodulation/processing/__init__.py +10 -0
  54. py_neuromodulation/{nm_artifacts.py → processing/artifacts.py} +29 -25
  55. py_neuromodulation/processing/data_preprocessor.py +77 -0
  56. py_neuromodulation/processing/filter_preprocessing.py +78 -0
  57. py_neuromodulation/processing/normalization.py +175 -0
  58. py_neuromodulation/{nm_projection.py → processing/projection.py} +370 -394
  59. py_neuromodulation/{nm_rereference.py → processing/rereference.py} +97 -95
  60. py_neuromodulation/{nm_resample.py → processing/resample.py} +56 -50
  61. py_neuromodulation/stream/__init__.py +3 -0
  62. py_neuromodulation/stream/data_processor.py +325 -0
  63. py_neuromodulation/stream/generator.py +53 -0
  64. py_neuromodulation/stream/mnelsl_player.py +94 -0
  65. py_neuromodulation/stream/mnelsl_stream.py +120 -0
  66. py_neuromodulation/stream/settings.py +292 -0
  67. py_neuromodulation/stream/stream.py +427 -0
  68. py_neuromodulation/utils/__init__.py +2 -0
  69. py_neuromodulation/{nm_define_nmchannels.py → utils/channels.py} +305 -302
  70. py_neuromodulation/utils/database.py +149 -0
  71. py_neuromodulation/utils/io.py +378 -0
  72. py_neuromodulation/utils/keyboard.py +52 -0
  73. py_neuromodulation/utils/logging.py +66 -0
  74. py_neuromodulation/utils/types.py +251 -0
  75. {py_neuromodulation-0.0.4.dist-info → py_neuromodulation-0.0.6.dist-info}/METADATA +28 -33
  76. py_neuromodulation-0.0.6.dist-info/RECORD +89 -0
  77. {py_neuromodulation-0.0.4.dist-info → py_neuromodulation-0.0.6.dist-info}/WHEEL +1 -1
  78. {py_neuromodulation-0.0.4.dist-info → py_neuromodulation-0.0.6.dist-info}/licenses/LICENSE +21 -21
  79. py_neuromodulation/FieldTrip.py +0 -589
  80. py_neuromodulation/_write_example_dataset_helper.py +0 -65
  81. py_neuromodulation/nm_EpochStream.py +0 -92
  82. py_neuromodulation/nm_IO.py +0 -417
  83. py_neuromodulation/nm_across_patient_decoding.py +0 -927
  84. py_neuromodulation/nm_bispectra.py +0 -168
  85. py_neuromodulation/nm_bursts.py +0 -198
  86. py_neuromodulation/nm_coherence.py +0 -205
  87. py_neuromodulation/nm_cohortwrapper.py +0 -435
  88. py_neuromodulation/nm_eval_timing.py +0 -239
  89. py_neuromodulation/nm_features.py +0 -116
  90. py_neuromodulation/nm_features_abc.py +0 -39
  91. py_neuromodulation/nm_filter.py +0 -219
  92. py_neuromodulation/nm_filter_preprocessing.py +0 -91
  93. py_neuromodulation/nm_fooof.py +0 -159
  94. py_neuromodulation/nm_generator.py +0 -37
  95. py_neuromodulation/nm_hjorth_raw.py +0 -73
  96. py_neuromodulation/nm_kalmanfilter.py +0 -58
  97. py_neuromodulation/nm_linelength.py +0 -33
  98. py_neuromodulation/nm_mne_connectivity.py +0 -112
  99. py_neuromodulation/nm_nolds.py +0 -93
  100. py_neuromodulation/nm_normalization.py +0 -214
  101. py_neuromodulation/nm_oscillatory.py +0 -448
  102. py_neuromodulation/nm_run_analysis.py +0 -435
  103. py_neuromodulation/nm_settings.json +0 -338
  104. py_neuromodulation/nm_settings.py +0 -68
  105. py_neuromodulation/nm_sharpwaves.py +0 -401
  106. py_neuromodulation/nm_stream_abc.py +0 -218
  107. py_neuromodulation/nm_stream_offline.py +0 -359
  108. py_neuromodulation/utils/_logging.py +0 -24
  109. 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
@@ -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