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,435 +0,0 @@
1
- """This module contains the class to process a given batch of data."""
2
-
3
- from enum import Enum
4
- import math
5
- import os
6
- from time import time
7
- from typing import Protocol, Type
8
- import logging
9
-
10
- logger = logging.getLogger("PynmLogger")
11
-
12
- import numpy as np
13
- import pandas as pd
14
-
15
- from py_neuromodulation import (
16
- nm_features,
17
- nm_filter,
18
- nm_IO,
19
- nm_normalization,
20
- nm_projection,
21
- nm_rereference,
22
- nm_resample,
23
- nm_filter_preprocessing,
24
- )
25
-
26
- _PathLike = str | os.PathLike
27
-
28
-
29
- class Preprocessor(Protocol):
30
- def process(self, data: np.ndarray) -> np.ndarray:
31
- pass
32
-
33
- def test_settings(self, settings: dict): ...
34
-
35
-
36
- _PREPROCESSING_CONSTRUCTORS = [
37
- "notch_filter",
38
- "re_referencing",
39
- "raw_normalization",
40
- "raw_resample",
41
- ]
42
-
43
-
44
- class GRIDS(Enum):
45
- """Definition of possible projection grid types"""
46
-
47
- CORTEX = "cortex"
48
- SUBCORTEX = "subcortex"
49
-
50
-
51
- class DataProcessor:
52
- def __init__(
53
- self,
54
- sfreq: int | float,
55
- settings: dict | _PathLike,
56
- nm_channels: pd.DataFrame | _PathLike,
57
- coord_names: list | None = None,
58
- coord_list: list | None = None,
59
- line_noise: int | float | None = None,
60
- path_grids: _PathLike | None = None,
61
- verbose: bool = True,
62
- ) -> None:
63
- """Initialize run class.
64
-
65
- Parameters
66
- ----------
67
- features : features.py object
68
- Feature_df object (needs to be initialized beforehand)
69
- settings : dict
70
- dictionary of settings such as "seglengths" or "frequencyranges"
71
- reference : reference.py object
72
- Rereference object (needs to be initialized beforehand), by default None
73
- projection : projection.py object
74
- projection object (needs to be initialized beforehand), by default None
75
- resample : resample.py object
76
- Resample object (needs to be initialized beforehand), by default None
77
- notch_filter : nm_filter.NotchFilter,
78
- Notch Filter object, needs to be instantiated beforehand
79
- verbose : boolean
80
- if True, log signal processed and computation time
81
- """
82
- self.settings = self._load_settings(settings)
83
- self.nm_channels = self._load_nm_channels(nm_channels)
84
-
85
- self.sfreq_features = self.settings["sampling_rate_features_hz"]
86
- self._sfreq_raw_orig = sfreq
87
- self.sfreq_raw = math.floor(sfreq)
88
- self.line_noise = line_noise
89
- self.path_grids = path_grids
90
- self.verbose = verbose
91
-
92
- self.features_previous = None
93
-
94
- (self.ch_names_used, _, self.feature_idx, _) = self._get_ch_info()
95
-
96
- self.preprocessors: list[Preprocessor] = []
97
- for preprocessing_method in self.settings["preprocessing"]:
98
- settings_str = f"{preprocessing_method}_settings"
99
- match preprocessing_method:
100
- case "raw_resampling":
101
- preprocessor = nm_resample.Resampler(
102
- sfreq=self.sfreq_raw, **self.settings[settings_str]
103
- )
104
- self.sfreq_raw = preprocessor.sfreq_new
105
- self.preprocessors.append(preprocessor)
106
- case "notch_filter":
107
- preprocessor = nm_filter.NotchFilter(
108
- sfreq=self.sfreq_raw,
109
- line_noise=self.line_noise,
110
- **self.settings.get(settings_str, {}),
111
- )
112
- self.preprocessors.append(preprocessor)
113
- case "re_referencing":
114
- preprocessor = nm_rereference.ReReferencer(
115
- sfreq=self.sfreq_raw,
116
- nm_channels=self.nm_channels,
117
- )
118
- self.preprocessors.append(preprocessor)
119
- case "raw_normalization":
120
- preprocessor = nm_normalization.RawNormalizer(
121
- sfreq=self.sfreq_raw,
122
- sampling_rate_features_hz=self.sfreq_features,
123
- **self.settings.get(settings_str, {}),
124
- )
125
- self.preprocessors.append(preprocessor)
126
- case "preprocessing_filter":
127
- preprocessor = nm_filter_preprocessing.PreprocessingFilter(
128
- settings=self.settings,
129
- sfreq=self.sfreq_raw,
130
- )
131
- self.preprocessors.append(preprocessor)
132
- case _:
133
- raise ValueError(
134
- "Invalid preprocessing method. Must be one of"
135
- f" {_PREPROCESSING_CONSTRUCTORS}. Got"
136
- f" {preprocessing_method}"
137
- )
138
-
139
- if self.settings["postprocessing"]["feature_normalization"]:
140
- settings_str = "feature_normalization_settings"
141
- self.feature_normalizer = nm_normalization.FeatureNormalizer(
142
- sampling_rate_features_hz=self.sfreq_features,
143
- **self.settings.get(settings_str, {}),
144
- )
145
-
146
- self.features = nm_features.Features(
147
- s=self.settings,
148
- ch_names=self.ch_names_used,
149
- sfreq=self.sfreq_raw,
150
- )
151
-
152
- if coord_list is not None and coord_names is not None:
153
- self.coords = self._set_coords(
154
- coord_names=coord_names, coord_list=coord_list
155
- )
156
-
157
- self.projection = self._get_projection(self.settings, self.nm_channels)
158
-
159
- self.cnt_samples = 0
160
-
161
- @staticmethod
162
- def _add_coordinates(coord_names: list[str], coord_list: list) -> dict:
163
- """Write cortical and subcortical coordinate information in joint dictionary
164
-
165
- Parameters
166
- ----------
167
- coord_names : list[str]
168
- list of coordinate names
169
- coord_list : list
170
- list of list of 3D coordinates
171
-
172
- Returns
173
- -------
174
- dict with (sub)cortex_left and (sub)cortex_right ch_names and positions
175
- """
176
-
177
- def is_left_coord(val: int | float, coord_region: str) -> bool:
178
- if coord_region.split("_")[1] == "left":
179
- return val < 0
180
- return val > 0
181
-
182
- coords = {}
183
-
184
- for coord_region in [
185
- coord_loc + "_" + lat
186
- for coord_loc in ["cortex", "subcortex"]
187
- for lat in ["left", "right"]
188
- ]:
189
- coords[coord_region] = {}
190
-
191
- ch_type = (
192
- "ECOG" if "cortex" == coord_region.split("_")[0] else "LFP"
193
- )
194
-
195
- coords[coord_region]["ch_names"] = [
196
- coord_name
197
- for coord_name, ch in zip(coord_names, coord_list)
198
- if is_left_coord(ch[0], coord_region)
199
- and (ch_type in coord_name)
200
- ]
201
-
202
- # multiply by 1000 to get m instead of mm
203
- positions = []
204
- for coord, coord_name in zip(coord_list, coord_names):
205
- if is_left_coord(coord[0], coord_region) and (
206
- ch_type in coord_name
207
- ):
208
- positions.append(coord)
209
- positions = np.array(positions, dtype=np.float64) * 1000
210
- coords[coord_region]["positions"] = positions
211
-
212
- return coords
213
-
214
- def _get_ch_info(
215
- self,
216
- ) -> tuple[list[str], list[str], list[int], np.ndarray]:
217
- """Get used feature and label info from nm_channels"""
218
- nm_channels = self.nm_channels
219
- ch_names_used = nm_channels[nm_channels["used"] == 1][
220
- "new_name"
221
- ].tolist()
222
- ch_types_used = nm_channels[nm_channels["used"] == 1]["type"].tolist()
223
-
224
- # used channels for feature estimation
225
- feature_idx = np.where(nm_channels["used"] & ~nm_channels["target"])[
226
- 0
227
- ].tolist()
228
-
229
- # If multiple targets exist, select only the first
230
- label_idx = np.where(nm_channels["target"] == 1)[0]
231
-
232
- return ch_names_used, ch_types_used, feature_idx, label_idx
233
-
234
- @staticmethod
235
- def _get_grids(
236
- settings: dict,
237
- path_grids: str | _PathLike | None,
238
- grid_type: Type[GRIDS],
239
- ) -> tuple[pd.DataFrame | None, pd.DataFrame | None]:
240
- """Read settings specified grids
241
-
242
- Parameters
243
- ----------
244
- settings : dict
245
- path_grids : str
246
- grid_type : GRIDS
247
-
248
- Returns
249
- -------
250
- Tuple
251
- grid_cortex, grid_subcortex,
252
- might be None if not specified in settings
253
- """
254
- if settings["postprocessing"]["project_cortex"] is True:
255
- grid_cortex = nm_IO.read_grid(path_grids, grid_type.CORTEX.name)
256
- else:
257
- grid_cortex = None
258
- if settings["postprocessing"]["project_subcortex"] is True:
259
- grid_subcortex = nm_IO.read_grid(
260
- path_grids, grid_type.SUBCORTEX.name
261
- )
262
- else:
263
- grid_subcortex = None
264
- return grid_cortex, grid_subcortex
265
-
266
- def _get_projection(
267
- self, settings: dict, nm_channels: pd.DataFrame
268
- ) -> nm_projection.Projection | None:
269
- """Return projection of used coordinated and grids"""
270
-
271
- if not any(
272
- (
273
- settings["postprocessing"]["project_cortex"],
274
- settings["postprocessing"]["project_subcortex"],
275
- )
276
- ):
277
- return None
278
-
279
- grid_cortex, grid_subcortex = self._get_grids(
280
- self.settings, self.path_grids, GRIDS
281
- )
282
- projection = nm_projection.Projection(
283
- settings=settings,
284
- grid_cortex=grid_cortex,
285
- grid_subcortex=grid_subcortex,
286
- coords=self.coords,
287
- nm_channels=nm_channels,
288
- plot_projection=False,
289
- )
290
- return projection
291
-
292
- @staticmethod
293
- def _load_nm_channels(
294
- nm_channels: pd.DataFrame | _PathLike,
295
- ) -> pd.DataFrame:
296
- if not isinstance(nm_channels, pd.DataFrame):
297
- return nm_IO.load_nm_channels(nm_channels)
298
- return nm_channels
299
-
300
- @staticmethod
301
- def _load_settings(settings: dict | _PathLike) -> dict:
302
- if not isinstance(settings, dict):
303
- return nm_IO.read_settings(str(settings))
304
- return settings
305
-
306
- def _set_coords(
307
- self, coord_names: list[str] | None, coord_list: list | None
308
- ) -> dict:
309
- if not any(
310
- (
311
- self.settings["postprocessing"]["project_cortex"],
312
- self.settings["postprocessing"]["project_subcortex"],
313
- )
314
- ):
315
- return {}
316
-
317
- if any((coord_list is None, coord_names is None)):
318
- raise ValueError(
319
- "No coordinates could be loaded. Please provide coord_list and"
320
- f" coord_names. Got: {coord_list=}, {coord_names=}."
321
- )
322
-
323
- return self._add_coordinates(
324
- coord_names=coord_names, coord_list=coord_list
325
- )
326
-
327
- def process(self, data: np.ndarray) -> pd.Series:
328
- """Given a new data batch, calculate and return features.
329
-
330
- Parameters
331
- ----------
332
- data : np.ndarray
333
- Current batch of raw data
334
-
335
- Returns
336
- -------
337
- pandas Series
338
- Features calculated from current data
339
- """
340
- start_time = time()
341
-
342
- nan_channels = np.isnan(data).any(axis=1)
343
-
344
- data = np.nan_to_num(data)[self.feature_idx, :]
345
-
346
- for processor in self.preprocessors:
347
- data = processor.process(data)
348
-
349
- # calculate features
350
- features_dict = self.features.estimate_features(data)
351
-
352
- # normalize features
353
- if self.settings["postprocessing"]["feature_normalization"]:
354
- normed_features = self.feature_normalizer.process(
355
- np.fromiter(features_dict.values(), dtype="float")
356
- )
357
- features_dict = {
358
- key: normed_features[idx]
359
- for idx, key in enumerate(features_dict.keys())
360
- }
361
-
362
- features_current = pd.Series(
363
- data=list(features_dict.values()),
364
- index=list(features_dict.keys()),
365
- dtype=np.float64,
366
- )
367
-
368
- # project features to grid
369
- if self.projection:
370
- features_current = self.projection.project_features(
371
- features_current
372
- )
373
-
374
- # check for all features, where the channel had a NaN, that the feature is also put to NaN
375
- if nan_channels.sum() > 0:
376
- for ch in list(np.array(self.ch_names_used)[nan_channels]):
377
- features_current.loc[
378
- features_current.index.str.contains(ch)
379
- ] = np.nan
380
-
381
- if self.verbose is True:
382
- logger.info(
383
- "Last batch took: "
384
- + str(np.round(time() - start_time, 2))
385
- + " seconds"
386
- )
387
-
388
- return features_current
389
-
390
- def save_sidecar(
391
- self,
392
- out_path_root: _PathLike,
393
- folder_name: str,
394
- additional_args: dict | None = None,
395
- ) -> None:
396
- """Save sidecar incuding fs, coords, sess_right to
397
- out_path_root and subfolder 'folder_name'.
398
- """
399
- sidecar = {
400
- "original_fs": self._sfreq_raw_orig,
401
- "final_fs": self.sfreq_raw,
402
- "sfreq": self.sfreq_features,
403
- }
404
- if self.projection:
405
- sidecar["coords"] = self.projection.coords
406
- if self.settings["postprocessing"]["project_cortex"]:
407
- sidecar["grid_cortex"] = self.projection.grid_cortex
408
- sidecar["proj_matrix_cortex"] = (
409
- self.projection.proj_matrix_cortex
410
- )
411
- if self.settings["postprocessing"]["project_subcortex"]:
412
- sidecar["grid_subcortex"] = self.projection.grid_subcortex
413
- sidecar["proj_matrix_subcortex"] = (
414
- self.projection.proj_matrix_subcortex
415
- )
416
- if additional_args is not None:
417
- sidecar = sidecar | additional_args
418
-
419
- nm_IO.save_sidecar(sidecar, out_path_root, folder_name)
420
-
421
- def save_settings(self, out_path_root: _PathLike, folder_name: str) -> None:
422
- nm_IO.save_settings(self.settings, out_path_root, folder_name)
423
-
424
- def save_nm_channels(
425
- self, out_path_root: _PathLike, folder_name: str
426
- ) -> None:
427
- nm_IO.save_nm_channels(self.nm_channels, out_path_root, folder_name)
428
-
429
- def save_features(
430
- self,
431
- out_path_root: _PathLike,
432
- folder_name: str,
433
- feature_arr: pd.DataFrame,
434
- ) -> None:
435
- nm_IO.save_features(feature_arr, out_path_root, folder_name)