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,401 +0,0 @@
1
- import numpy as np
2
- from scipy import signal
3
- from mne.filter import create_filter
4
- from typing import Iterable
5
-
6
- from py_neuromodulation import nm_features_abc
7
-
8
-
9
- class NoValidTroughException(Exception):
10
- pass
11
-
12
-
13
- class SharpwaveAnalyzer(nm_features_abc.Feature):
14
- def __init__(
15
- self, settings: dict, ch_names: Iterable[str], sfreq: float
16
- ) -> None:
17
-
18
- self.sw_settings = settings["sharpwave_analysis_settings"]
19
- self.sfreq = sfreq
20
- self.ch_names = ch_names
21
- self.list_filter = []
22
- for filter_range in settings["sharpwave_analysis_settings"][
23
- "filter_ranges_hz"
24
- ]:
25
-
26
- if filter_range[0] is None:
27
- self.list_filter.append(("no_filter", None))
28
- else:
29
- self.list_filter.append(
30
- (
31
- f"range_{filter_range[0]}_{filter_range[1]}",
32
- create_filter(
33
- None,
34
- sfreq,
35
- l_freq=filter_range[0],
36
- h_freq=filter_range[1],
37
- fir_design="firwin",
38
- # l_trans_bandwidth=None,
39
- # h_trans_bandwidth=None,
40
- # filter_length=str(sfreq) + "ms",
41
- verbose=False,
42
- ),
43
- )
44
- )
45
-
46
- # initialize used features
47
- self.used_features = list()
48
- for feature_name, val in self.sw_settings["sharpwave_features"].items():
49
- if val is True:
50
- self.used_features.append(feature_name)
51
-
52
- # initialize attributes
53
- self._initialize_sw_features()
54
-
55
- # initializing estimator functions, respecitive for all sharpwave features
56
- fun_names = []
57
- for used_feature in self.used_features:
58
- estimator_list_feature = (
59
- []
60
- ) # one feature can have multiple estimators
61
- for estimator, est_features in self.sw_settings[
62
- "estimator"
63
- ].items():
64
- if est_features is not None:
65
- for est_feature in est_features:
66
- if used_feature == est_feature:
67
- estimator_list_feature.append(estimator)
68
- fun_names.append(estimator_list_feature)
69
-
70
- self.estimator_names = fun_names
71
- self.estimator_functions = [
72
- [
73
- getattr(np, est_name)
74
- for est_name in self.estimator_names[feature_idx]
75
- ]
76
- for feature_idx in range(len(self.estimator_names))
77
- ]
78
-
79
- def _initialize_sw_features(self) -> None:
80
- """Resets used attributes to empty lists"""
81
- for feature_name in self.used_features:
82
- setattr(self, feature_name, list())
83
- if "trough" not in self.used_features:
84
- # trough attribute is still necessary, even if it is not specified in settings
85
- self.trough = list()
86
- self.troughs_idx = list()
87
-
88
- def _get_peaks_around(self, trough_ind, arr_ind_peaks, filtered_dat):
89
- """Find the closest peaks to the right and left side a given trough.
90
-
91
- Parameters
92
- ----------
93
- trough_ind (int): index of trough
94
- arr_ind_peaks (np.ndarray): array of peak indices
95
- filtered_dat (np.ndarray): raw data batch
96
-
97
- Raises:
98
- NoValidTroughException: Returned if no adjacent peak can be found
99
-
100
- Returns
101
- -------
102
- peak_left_idx (np.ndarray): index of left peak
103
- peak_right_idx (np.ndarray): index of right peak
104
- peak_left_val (np.ndarray): value of left peak
105
- peak_right_val (np.ndarray): value of righ peak
106
- """
107
-
108
- try: peak_right_idx = arr_ind_peaks[arr_ind_peaks > trough_ind][0]
109
- except IndexError: raise NoValidTroughException("No valid trough")
110
-
111
- try: peak_left_idx = arr_ind_peaks[arr_ind_peaks < trough_ind][-1]
112
- except IndexError: raise NoValidTroughException("No valid trough")
113
-
114
- return (
115
- peak_left_idx,
116
- peak_right_idx,
117
- filtered_dat[peak_left_idx],
118
- filtered_dat[peak_right_idx],
119
- )
120
-
121
- def calc_feature(
122
- self,
123
- data: np.array,
124
- features_compute: dict,
125
- ) -> dict:
126
- """Given a new data batch, the peaks, troughs and sharpwave features
127
- are estimated. Importantly only new data is being analyzed here. In
128
- steps of 1/settings["sampling_rate_features] analyzed and returned.
129
- Pre-initialized filters are applied to each channel.
130
-
131
- Parameters
132
- ----------
133
- data (np.ndarray): 2d data array with shape [num_channels, samples]
134
- features_compute (dict): Features.py estimated features
135
-
136
- Returns
137
- -------
138
- features_compute (dict): set features for Features.py object
139
- """
140
- for ch_idx, ch_name in enumerate(self.ch_names):
141
- for filter_name, filter in self.list_filter:
142
- self.data_process_sw = (data[ch_idx, :]
143
- if filter_name == "no_filter"
144
- else signal.fftconvolve(data[ch_idx, :], filter, mode="same")
145
- )
146
-
147
- # check settings if troughs and peaks are analyzed
148
-
149
- dict_ch_features = {}
150
-
151
- for detect_troughs in [False, True]:
152
-
153
- if detect_troughs is False:
154
- if (
155
- self.sw_settings["detect_peaks"]["estimate"]
156
- is False
157
- ):
158
- continue
159
- key_name_pt = "Peak"
160
- # the detect_troughs loop start with peaks, s.t. data does not
161
- # need to be flipped
162
-
163
- if detect_troughs is True:
164
- if (
165
- self.sw_settings["detect_troughs"]["estimate"]
166
- is False
167
- ):
168
- continue
169
- key_name_pt = "Trough"
170
-
171
- self.data_process_sw = -self.data_process_sw
172
-
173
- self._initialize_sw_features() # reset sharpwave feature attriubtes to empty lists
174
- self.analyze_waveform()
175
-
176
- # for each feature take the respective fun.
177
- for feature_idx, feature_name in enumerate(
178
- self.used_features
179
- ):
180
- for est_idx, estimator_name in enumerate(
181
- self.estimator_names[feature_idx]
182
- ):
183
- key_name = "_".join(
184
- [
185
- ch_name,
186
- "Sharpwave",
187
- self.estimator_names[feature_idx][
188
- est_idx
189
- ].title(),
190
- feature_name,
191
- filter_name,
192
- ]
193
- )
194
- # zero check because no peaks can be also detected
195
- val = (
196
- self.estimator_functions[feature_idx][est_idx](
197
- getattr(self, feature_name)
198
- )
199
- if len(getattr(self, feature_name)) != 0
200
- else 0
201
- )
202
- if key_name not in dict_ch_features:
203
- dict_ch_features[key_name] = {}
204
- dict_ch_features[key_name][key_name_pt] = val
205
-
206
- if self.sw_settings[
207
- "apply_estimator_between_peaks_and_troughs"
208
- ]:
209
- # apply between 'Trough' and 'Peak' the respective function again
210
- # save only the 'est_fun' (e.g. max) between them
211
-
212
- for idx, key_name in enumerate(dict_ch_features):
213
- # the key_name stays, since the estimator function stays between peaks and troughs
214
- # this array needs to be flattened
215
- features_compute[key_name] = np.concatenate(
216
- self.estimator_functions
217
- )[idx](
218
- [
219
- list(dict_ch_features[key_name].values())[0],
220
- list(dict_ch_features[key_name].values())[1],
221
- ]
222
- )
223
-
224
- else:
225
- # otherwise, save all
226
- # write all "flatted" key value pairs in features_
227
- for key, value in dict_ch_features.items():
228
- for key_sub, value_sub in dict_ch_features[key].items():
229
- features_compute[
230
- key + "_analyze_" + key_sub
231
- ] = value_sub
232
-
233
- return features_compute
234
-
235
- def analyze_waveform(self) -> None:
236
- """Given the scipy.signal.find_peaks trough/peak distance
237
- settings specified sharpwave features are estimated.
238
-
239
- Parameters
240
- ----------
241
-
242
- Raises:
243
- NoValidTroughException: Return if no adjacent peak can be found
244
- NoValidTroughException: Return if no adjacent peak can be found
245
-
246
- """
247
-
248
- peaks = signal.find_peaks(
249
- self.data_process_sw,
250
- distance=self.sw_settings["detect_troughs"]["distance_peaks_ms"],
251
- )[0]
252
- troughs = signal.find_peaks(
253
- -self.data_process_sw,
254
- distance=self.sw_settings["detect_troughs"]["distance_troughs_ms"],
255
- )[0]
256
-
257
- """ Find left and right peak indexes for each trough """
258
- peak_pointer = 0
259
- peak_idx_left = []
260
- peak_idx_right = []
261
- first_valid = last_valid = 0
262
-
263
- for i, trough_idx in enumerate(troughs):
264
-
265
- # Locate peak right of current trough
266
- while peak_pointer < peaks.size and peaks[peak_pointer] < trough_idx:
267
- peak_pointer += 1
268
-
269
- if peak_pointer - 1 < 0:
270
- # If trough has no peak to it's left, it's not valid
271
- first_valid = i + 1 # Try with next one
272
- continue
273
-
274
- if peak_pointer == peaks.size:
275
- # If we went past the end of the peaks list, trough had no peak to its right
276
- continue
277
-
278
- last_valid = i
279
- peak_idx_left.append(peaks[peak_pointer - 1])
280
- peak_idx_right.append(peaks[peak_pointer])
281
-
282
- troughs = troughs[first_valid:last_valid + 1] # Remove non valid troughs
283
-
284
- peak_idx_left = np.array(peak_idx_left, dtype=np.integer)
285
- peak_idx_right = np.array(peak_idx_right, dtype=np.integer)
286
-
287
- peak_left = self.data_process_sw[peak_idx_left]
288
- peak_right = self.data_process_sw[peak_idx_right]
289
- trough_values = self.data_process_sw[troughs]
290
-
291
- # No need to store trough data as it is not used anywhere else in the program
292
- # self.trough.append(trough)
293
- # self.troughs_idx.append(trough_idx)
294
-
295
- """ Calculate features (vectorized) """
296
-
297
- if self.sw_settings["sharpwave_features"]["interval"]:
298
- self.interval = np.concatenate(([0], np.diff(troughs))) * (1000 / self.sfreq)
299
-
300
- if self.sw_settings["sharpwave_features"]["peak_left"]:
301
- self.peak_left = peak_left
302
-
303
- if self.sw_settings["sharpwave_features"]["peak_right"]:
304
- self.peak_right = peak_right
305
-
306
- if self.sw_settings["sharpwave_features"]["sharpness"]:
307
- # sharpess is calculated on a +- 5 ms window
308
- # valid troughs need 5 ms of margin on both siddes
309
- troughs_valid = troughs[np.logical_and(
310
- troughs - int(5 * (1000 / self.sfreq)) > 0,
311
- troughs + int(5 * (1000 / self.sfreq)) < self.data_process_sw.shape[0])]
312
-
313
- self.sharpness = (
314
- (self.data_process_sw[troughs_valid] - self.data_process_sw[troughs_valid - int(5 * (1000 / self.sfreq))]) +
315
- (self.data_process_sw[troughs_valid] - self.data_process_sw[troughs_valid + int(5 * (1000 / self.sfreq))])
316
- ) / 2
317
-
318
- if (self.sw_settings["sharpwave_features"]["rise_steepness"] or
319
- self.sw_settings["sharpwave_features"]["decay_steepness"]):
320
-
321
- # steepness is calculated as the first derivative
322
- steepness = np.concatenate(([0],np.diff(self.data_process_sw)))
323
-
324
- if self.sw_settings["sharpwave_features"]["rise_steepness"]: # left peak -> trough
325
- # + 1 due to python syntax, s.t. the last element is included
326
- self.rise_steepness = np.array([
327
- np.max(np.abs(steepness[peak_idx_left[i] : troughs[i] + 1]))
328
- for i in range(trough_idx.size)
329
- ])
330
-
331
- if self.sw_settings["sharpwave_features"]["decay_steepness"]: # trough -> right peak
332
- self.decay_steepness = np.array([
333
- np.max(np.abs(steepness[troughs[i] : peak_idx_right[i] + 1]))
334
- for i in range(trough_idx.size)
335
- ])
336
-
337
- if (self.sw_settings["sharpwave_features"]["rise_steepness"] and
338
- self.sw_settings["sharpwave_features"]["decay_steepness"] and
339
- self.sw_settings["sharpwave_features"]["slope_ratio"]):
340
- self.slope_ratio = self.rise_steepness - self.decay_steepness
341
-
342
- if self.sw_settings["sharpwave_features"]["prominence"]:
343
- self.prominence = np.abs((peak_right + peak_left) / 2 - trough_values)
344
-
345
- if self.sw_settings["sharpwave_features"]["decay_time"]:
346
- self.decay_time = (peak_idx_left - troughs) * (1000 / self.sfreq) # ms
347
-
348
- if self.sw_settings["sharpwave_features"]["rise_time"]:
349
- self.rise_time = (peak_idx_right - troughs) * (1000 / self.sfreq) # ms
350
-
351
- if self.sw_settings["sharpwave_features"]["width"]:
352
- self.width = peak_idx_right - peak_idx_left # ms
353
-
354
- @staticmethod
355
- def test_settings(
356
- s: dict,
357
- ch_names: Iterable[str],
358
- sfreq: int | float,
359
- ):
360
- for filter_range in s["sharpwave_analysis_settings"][
361
- "filter_ranges_hz"
362
- ]:
363
- assert isinstance(
364
- filter_range[0],
365
- int,
366
- ), f"filter range needs to be of type int, got {filter_range[0]}"
367
- assert isinstance(
368
- filter_range[1],
369
- int,
370
- ), f"filter range needs to be of type int, got {filter_range[1]}"
371
- assert (
372
- filter_range[1] > filter_range[0]
373
- ), f"second filter value needs to be higher than first one, got {filter_range}"
374
- assert filter_range[0] < sfreq and filter_range[1] < sfreq, (
375
- "filter range has to be smaller than sfreq, "
376
- f"got sfreq {sfreq} and filter range {filter_range}"
377
- )
378
- # check if all features are also enbled via an estimator
379
- used_features = list()
380
- for feature_name, val in s["sharpwave_analysis_settings"][
381
- "sharpwave_features"
382
- ].items():
383
- assert isinstance(
384
- val, bool
385
- ), f"sharpwave_feature type {feature_name} has to be of type bool, got {val}"
386
- if val is True:
387
- used_features.append(feature_name)
388
- for used_feature in used_features:
389
- estimator_list_feature = (
390
- []
391
- ) # one feature can have multiple estimators
392
- for estimator, est_features in s["sharpwave_analysis_settings"][
393
- "estimator"
394
- ].items():
395
- if est_features is not None:
396
- for est_feature in est_features:
397
- if used_feature == est_feature:
398
- estimator_list_feature.append(estimator)
399
- assert (
400
- len(estimator_list_feature) > 0
401
- ), f"add estimator key for {used_feature}"
@@ -1,218 +0,0 @@
1
- """Module that contains PNStream ABC."""
2
-
3
- from abc import ABC, abstractmethod
4
- import os
5
- import pathlib
6
- import _pickle as cPickle
7
-
8
- from .utils import _logging # logger initialization
9
-
10
- # Logger use in different modules: logger = logging.getLogger("PynmLogger")
11
-
12
-
13
- import pandas as pd
14
- from sklearn import base
15
-
16
- from py_neuromodulation import (
17
- nm_features,
18
- nm_IO,
19
- nm_plots,
20
- nm_run_analysis,
21
- nm_settings,
22
- )
23
-
24
- _PathLike = str | os.PathLike
25
-
26
-
27
- class PNStream(ABC):
28
-
29
- settings: dict
30
- nm_channels: pd.DataFrame
31
- run_analysis: nm_run_analysis.DataProcessor
32
- features: nm_features.Features
33
- coords: dict
34
- sfreq: int | float
35
- sfreq_feature: int | float = None
36
- path_grids: _PathLike | None
37
- model: base.BaseEstimator | None
38
- sess_right: bool | None
39
- verbose: bool
40
- PATH_OUT: _PathLike | None
41
- PATH_OUT_folder_name: _PathLike | None
42
-
43
- def __init__(
44
- self,
45
- sfreq: int | float,
46
- nm_channels: pd.DataFrame | _PathLike,
47
- settings: dict | _PathLike | None = None,
48
- line_noise: int | float | None = 50,
49
- sampling_rate_features_hz: int | float | None = None,
50
- path_grids: _PathLike | None = None,
51
- coord_names: list | None = None,
52
- coord_list: list | None = None,
53
- verbose: bool = True,
54
- ) -> None:
55
- """Stream initialization
56
-
57
- Parameters
58
- ----------
59
- sfreq : int | float
60
- sampling frequency of data in Hertz
61
- nm_channels : pd.DataFrame | _PathLike
62
- parametrization of channels (see nm_define_channels.py for initialization)
63
- settings : dict | _PathLike | None, optional
64
- features settings can be a dictionary or path to the nm_settings.json, by default the py_neuromodulation/nm_settings.json are read
65
- line_noise : int | float | None, optional
66
- line noise, by default 50
67
- sampling_rate_features_hz : int | float | None, optional
68
- feature sampling rate, by default None
69
- path_grids : _PathLike | None, optional
70
- path to grid_cortex.tsv and/or gird_subcortex.tsv, by default Non
71
- coord_names : list | None, optional
72
- coordinate name in the form [coord_1_name, coord_2_name, etc], by default None
73
- coord_list : list | None, optional
74
- coordinates in the form [[coord_1_x, coord_1_y, coord_1_z], [coord_2_x, coord_2_y, coord_2_z],], by default None
75
- verbose : bool, optional
76
- print out stream computation time information, by default True
77
- """
78
- self.settings = self._load_settings(settings)
79
-
80
- if sampling_rate_features_hz is not None:
81
- self.settings["sampling_rate_features_hz"] = (
82
- sampling_rate_features_hz
83
- )
84
-
85
- self.nm_channels = self._load_nm_channels(nm_channels)
86
- if path_grids is None:
87
- path_grids = pathlib.Path(__file__).parent.resolve()
88
- self.path_grids = path_grids
89
- self.verbose = verbose
90
- self.sfreq = sfreq
91
- self.line_noise = line_noise
92
- self.coord_names = coord_names
93
- self.coord_list = coord_list
94
- self.sess_right = None
95
- self.projection = None
96
- self.model = None
97
-
98
- self.run_analysis = nm_run_analysis.DataProcessor(
99
- sfreq=self.sfreq,
100
- settings=self.settings,
101
- nm_channels=self.nm_channels,
102
- path_grids=self.path_grids,
103
- coord_names=coord_names,
104
- coord_list=coord_list,
105
- line_noise=line_noise,
106
- verbose=self.verbose,
107
- )
108
-
109
- @abstractmethod
110
- def run(self):
111
- """Reinitialize the stream
112
- This might be handy in case the nm_channels or nm_settings changed
113
- """
114
-
115
- self.run_analysis = nm_run_analysis.DataProcessor(
116
- sfreq=self.sfreq,
117
- settings=self.settings,
118
- nm_channels=self.nm_channels,
119
- path_grids=self.path_grids,
120
- coord_names=self.coord_names,
121
- coord_list=self.coord_list,
122
- line_noise=self.line_noise,
123
- verbose=self.verbose,
124
- )
125
-
126
- @abstractmethod
127
- def _add_timestamp(
128
- self, feature_series: pd.Series, idx: int | None = None
129
- ) -> pd.Series:
130
- """Add to feature_series "time" keyword
131
- For Bids specify with fs_features, for real time analysis with current time stamp
132
- """
133
-
134
- @staticmethod
135
- def _get_sess_lat(coords: dict) -> bool:
136
- if len(coords["cortex_left"]["positions"]) == 0:
137
- return True
138
- if len(coords["cortex_right"]["positions"]) == 0:
139
- return False
140
- raise ValueError(
141
- "Either cortex_left or cortex_right positions must be provided."
142
- )
143
-
144
- @staticmethod
145
- def _load_nm_channels(
146
- nm_channels: pd.DataFrame | _PathLike,
147
- ) -> pd.DataFrame:
148
- if not isinstance(nm_channels, pd.DataFrame):
149
- nm_channels = nm_IO.load_nm_channels(nm_channels)
150
-
151
- if nm_channels.query("used == 1 and target == 0").shape[0] == 0:
152
- raise ValueError(
153
- "No channels selected for analysis that have column 'used' = 1 and 'target' = 0. Please check your nm_channels"
154
- )
155
-
156
- return nm_channels
157
-
158
- @staticmethod
159
- def _load_settings(settings: dict | _PathLike | None) -> dict:
160
- if isinstance(settings, dict):
161
- return settings
162
- if settings is None:
163
- return nm_settings.get_default_settings()
164
- return nm_IO.read_settings(str(settings))
165
-
166
- def load_model(self, model_name: _PathLike) -> None:
167
- """Load sklearn model, that utilizes predict"""
168
- with open(model_name, "rb") as fid:
169
- self.model = cPickle.load(fid)
170
-
171
- def save_after_stream(
172
- self,
173
- out_path_root: _PathLike | None = None,
174
- folder_name: str = "sub",
175
- feature_arr: pd.DataFrame | None = None,
176
- ) -> None:
177
- """Save features, settings, nm_channels and sidecar after run"""
178
-
179
- if out_path_root is None:
180
- out_path_root = os.getcwd()
181
- # create derivate folder_name output folder if doesn't exist
182
- if os.path.exists(os.path.join(out_path_root, folder_name)) is False:
183
- os.makedirs(os.path.join(out_path_root, folder_name))
184
-
185
- self.PATH_OUT = out_path_root
186
- self.PATH_OUT_folder_name = folder_name
187
- self.save_sidecar(out_path_root, folder_name)
188
-
189
- if feature_arr is not None:
190
- self.save_features(out_path_root, folder_name, feature_arr)
191
-
192
- self.save_settings(out_path_root, folder_name)
193
-
194
- self.save_nm_channels(out_path_root, folder_name)
195
-
196
- def save_features(
197
- self,
198
- out_path_root: _PathLike,
199
- folder_name: str,
200
- feature_arr: pd.DataFrame,
201
- ) -> None:
202
- nm_IO.save_features(feature_arr, out_path_root, folder_name)
203
-
204
- def save_nm_channels(
205
- self, out_path_root: _PathLike, folder_name: str
206
- ) -> None:
207
- self.run_analysis.save_nm_channels(out_path_root, folder_name)
208
-
209
- def save_settings(self, out_path_root: _PathLike, folder_name: str) -> None:
210
- self.run_analysis.save_settings(out_path_root, folder_name)
211
-
212
- def save_sidecar(self, out_path_root: _PathLike, folder_name: str) -> None:
213
- """Save sidecar incduing fs, coords, sess_right to
214
- out_path_root and subfolder 'folder_name'"""
215
- additional_args = {"sess_right": self.sess_right}
216
- self.run_analysis.save_sidecar(
217
- out_path_root, folder_name, additional_args
218
- )