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
py_neuromodulation/nm_IO.py
CHANGED
|
@@ -1,417 +1,413 @@
|
|
|
1
|
-
import json
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
import
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
import
|
|
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
|
-
raw_arr :
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
-
else:
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
return
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
def
|
|
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
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
""
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
path_out
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
) -> None:
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
def
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
"""
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
""
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
""
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
dict[
|
|
397
|
-
return dict
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
def
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
def _pathlike_to_str(path: _PathLike) -> str:
|
|
415
|
-
if isinstance(path, str):
|
|
416
|
-
return path
|
|
417
|
-
return str(path)
|
|
1
|
+
import json
|
|
2
|
+
from pathlib import PurePath, Path
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
import pandas as pd
|
|
7
|
+
|
|
8
|
+
from py_neuromodulation.nm_types import _PathLike
|
|
9
|
+
from py_neuromodulation import logger, PYNM_DIR
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from mne_bids import BIDSPath
|
|
13
|
+
from mne import io as mne_io
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def load_nm_channels(
|
|
17
|
+
nm_channels: pd.DataFrame | _PathLike,
|
|
18
|
+
) -> "pd.DataFrame":
|
|
19
|
+
"""Read nm_channels from path or specify via BIDS arguments.
|
|
20
|
+
Necessary parameters are then ch_names (list), ch_types (list), bads (list), used_types (list),
|
|
21
|
+
target_keywords (list) and reference Union[list, str].
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
if isinstance(nm_channels, pd.DataFrame):
|
|
25
|
+
nm_ch_return = nm_channels
|
|
26
|
+
elif nm_channels:
|
|
27
|
+
if not Path(nm_channels).is_file():
|
|
28
|
+
raise ValueError(
|
|
29
|
+
"PATH_NM_CHANNELS is not a valid file. Got: " f"{nm_channels}"
|
|
30
|
+
)
|
|
31
|
+
nm_ch_return = pd.read_csv(nm_channels)
|
|
32
|
+
|
|
33
|
+
return nm_ch_return
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def read_BIDS_data(
|
|
37
|
+
PATH_RUN: "_PathLike | BIDSPath",
|
|
38
|
+
line_noise: int = 50,
|
|
39
|
+
) -> tuple["mne_io.Raw", np.ndarray, float, int, list | None, list | None]:
|
|
40
|
+
"""Given a run path and bids data path, read the respective data
|
|
41
|
+
|
|
42
|
+
Parameters
|
|
43
|
+
----------
|
|
44
|
+
PATH_RUN : path to bids run file
|
|
45
|
+
supported formats: https://bids-specification.readthedocs.io/en/v1.2.1/04-modality-specific-files/04-intracranial-electroencephalography.html#ieeg-recording-data
|
|
46
|
+
line_noise: int, optional
|
|
47
|
+
by default 50
|
|
48
|
+
|
|
49
|
+
Returns
|
|
50
|
+
-------
|
|
51
|
+
raw_arr : mne.io.RawArray
|
|
52
|
+
raw_arr_data : np.ndarray
|
|
53
|
+
sfreq : float
|
|
54
|
+
line_noise : int
|
|
55
|
+
coord_list : list | None
|
|
56
|
+
coord_names : list | None
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
from mne_bids import read_raw_bids, get_bids_path_from_fname
|
|
60
|
+
|
|
61
|
+
bids_path = get_bids_path_from_fname(PATH_RUN)
|
|
62
|
+
|
|
63
|
+
raw_arr = read_raw_bids(bids_path)
|
|
64
|
+
coord_list, coord_names = get_coord_list(raw_arr)
|
|
65
|
+
if raw_arr.info["line_freq"] is not None:
|
|
66
|
+
line_noise = int(raw_arr.info["line_freq"])
|
|
67
|
+
else:
|
|
68
|
+
logger.info(
|
|
69
|
+
f"Line noise is not available in the data, using value of {line_noise} Hz."
|
|
70
|
+
)
|
|
71
|
+
return (
|
|
72
|
+
raw_arr,
|
|
73
|
+
raw_arr.get_data(),
|
|
74
|
+
raw_arr.info["sfreq"],
|
|
75
|
+
line_noise,
|
|
76
|
+
coord_list,
|
|
77
|
+
coord_names,
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def read_mne_data(
|
|
82
|
+
PATH_RUN: "_PathLike | BIDSPath",
|
|
83
|
+
line_noise: int = 50,
|
|
84
|
+
):
|
|
85
|
+
"""Read data in the mne.io.read_raw supported format.
|
|
86
|
+
|
|
87
|
+
Parameters
|
|
88
|
+
----------
|
|
89
|
+
PATH_RUN : _PathLike | BIDSPath
|
|
90
|
+
Path to mne.io.read_raw supported types https://mne.tools/stable/generated/mne.io.read_raw.html
|
|
91
|
+
line_noise : int, optional
|
|
92
|
+
line noise, by default 50
|
|
93
|
+
|
|
94
|
+
Returns
|
|
95
|
+
-------
|
|
96
|
+
raw : mne.io.Raw
|
|
97
|
+
sfreq : float
|
|
98
|
+
ch_names : list[str]
|
|
99
|
+
ch_type : list[str]
|
|
100
|
+
bads : list[str]
|
|
101
|
+
"""
|
|
102
|
+
|
|
103
|
+
from mne import io as mne_io
|
|
104
|
+
|
|
105
|
+
raw_arr = mne_io.read_raw(PATH_RUN)
|
|
106
|
+
sfreq = raw_arr.info["sfreq"]
|
|
107
|
+
ch_names = raw_arr.info["ch_names"]
|
|
108
|
+
ch_types = raw_arr.get_channel_types()
|
|
109
|
+
logger.info(
|
|
110
|
+
"Channel data is read using mne.io.read_raw function. Channel types might not be correct"
|
|
111
|
+
" and set to 'eeg' by default"
|
|
112
|
+
)
|
|
113
|
+
bads = raw_arr.info["bads"]
|
|
114
|
+
|
|
115
|
+
if raw_arr.info["line_freq"] is not None:
|
|
116
|
+
line_noise = int(raw_arr.info["line_freq"])
|
|
117
|
+
else:
|
|
118
|
+
logger.info(
|
|
119
|
+
f"Line noise is not available in the data, using value of {line_noise} Hz."
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
return raw_arr.get_data(), sfreq, ch_names, ch_types, bads
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def get_coord_list(
|
|
126
|
+
raw: "mne_io.BaseRaw",
|
|
127
|
+
) -> tuple[list, list] | tuple[None, None]:
|
|
128
|
+
"""Return the coordinate list and names from mne RawArray
|
|
129
|
+
|
|
130
|
+
Parameters
|
|
131
|
+
----------
|
|
132
|
+
raw : mne_io.BaseRaw
|
|
133
|
+
|
|
134
|
+
Returns
|
|
135
|
+
-------
|
|
136
|
+
coord_list[list, list] | coord_names[None, None]
|
|
137
|
+
"""
|
|
138
|
+
montage = raw.get_montage()
|
|
139
|
+
if montage is not None:
|
|
140
|
+
coord_list = np.array(
|
|
141
|
+
list(dict(montage.get_positions()["ch_pos"]).values())
|
|
142
|
+
).tolist()
|
|
143
|
+
coord_names = np.array(
|
|
144
|
+
list(dict(montage.get_positions()["ch_pos"]).keys())
|
|
145
|
+
).tolist()
|
|
146
|
+
else:
|
|
147
|
+
coord_list = None
|
|
148
|
+
coord_names = None
|
|
149
|
+
|
|
150
|
+
return coord_list, coord_names
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def read_grid(PATH_GRIDS: _PathLike | None, grid_str: str) -> pd.DataFrame:
|
|
154
|
+
"""Read grid file from path or PYNM_DIR
|
|
155
|
+
|
|
156
|
+
Parameters
|
|
157
|
+
----------
|
|
158
|
+
PATH_GRIDS : _PathLike | None
|
|
159
|
+
path to grid file, by default None
|
|
160
|
+
grid_str : str
|
|
161
|
+
grid name
|
|
162
|
+
|
|
163
|
+
Returns
|
|
164
|
+
-------
|
|
165
|
+
pd.DataFrame
|
|
166
|
+
pd.DataFrame including mni x,y,z coordinates for each grid point
|
|
167
|
+
"""
|
|
168
|
+
if PATH_GRIDS is None:
|
|
169
|
+
grid = pd.read_csv(PYNM_DIR / ("grid_" + grid_str.lower() + ".tsv"), sep="\t")
|
|
170
|
+
else:
|
|
171
|
+
grid = pd.read_csv(
|
|
172
|
+
PurePath(PATH_GRIDS, "grid_" + grid_str.lower() + ".tsv"), sep="\t"
|
|
173
|
+
)
|
|
174
|
+
return grid
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def get_annotations(PATH_ANNOTATIONS: str, PATH_RUN: str, raw_arr: "mne_io.RawArray"):
|
|
178
|
+
filepath = PurePath(PATH_ANNOTATIONS, PurePath(PATH_RUN).name[:-5] + ".txt")
|
|
179
|
+
from mne import read_annotations
|
|
180
|
+
|
|
181
|
+
try:
|
|
182
|
+
annot = read_annotations(filepath)
|
|
183
|
+
raw_arr.set_annotations(annot)
|
|
184
|
+
|
|
185
|
+
# annotations starting with "BAD" are omitted with reject_by_annotations 'omit' param
|
|
186
|
+
annot_data = raw_arr.get_data(reject_by_annotation="omit")
|
|
187
|
+
except FileNotFoundError:
|
|
188
|
+
logger.critical(f"Annotations file could not be found: {filepath}")
|
|
189
|
+
|
|
190
|
+
return annot, annot_data, raw_arr
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def read_plot_modules(
|
|
194
|
+
PATH_PLOT: _PathLike = PYNM_DIR / "plots",
|
|
195
|
+
):
|
|
196
|
+
"""Read required .mat files for plotting
|
|
197
|
+
|
|
198
|
+
Parameters
|
|
199
|
+
----------
|
|
200
|
+
PATH_PLOT : regexp, optional
|
|
201
|
+
path to plotting files, by default
|
|
202
|
+
"""
|
|
203
|
+
|
|
204
|
+
faces = loadmat(PurePath(PATH_PLOT, "faces.mat"))
|
|
205
|
+
vertices = loadmat(PurePath(PATH_PLOT, "Vertices.mat"))
|
|
206
|
+
grid = loadmat(PurePath(PATH_PLOT, "grid.mat"))["grid"]
|
|
207
|
+
stn_surf = loadmat(PurePath(PATH_PLOT, "STN_surf.mat"))
|
|
208
|
+
x_ver = stn_surf["vertices"][::2, 0]
|
|
209
|
+
y_ver = stn_surf["vertices"][::2, 1]
|
|
210
|
+
x_ecog = vertices["Vertices"][::1, 0]
|
|
211
|
+
y_ecog = vertices["Vertices"][::1, 1]
|
|
212
|
+
z_ecog = vertices["Vertices"][::1, 2]
|
|
213
|
+
x_stn = stn_surf["vertices"][::1, 0]
|
|
214
|
+
y_stn = stn_surf["vertices"][::1, 1]
|
|
215
|
+
z_stn = stn_surf["vertices"][::1, 2]
|
|
216
|
+
|
|
217
|
+
return (
|
|
218
|
+
faces,
|
|
219
|
+
vertices,
|
|
220
|
+
grid,
|
|
221
|
+
stn_surf,
|
|
222
|
+
x_ver,
|
|
223
|
+
y_ver,
|
|
224
|
+
x_ecog,
|
|
225
|
+
y_ecog,
|
|
226
|
+
z_ecog,
|
|
227
|
+
x_stn,
|
|
228
|
+
y_stn,
|
|
229
|
+
z_stn,
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def write_csv(df, path_out):
|
|
234
|
+
"""
|
|
235
|
+
Function to save Pandas dataframes to disk as CSV using
|
|
236
|
+
PyArrow (almost 10x faster than Pandas)
|
|
237
|
+
Difference with pandas.df.to_csv() is that it does not
|
|
238
|
+
write an index column by default
|
|
239
|
+
"""
|
|
240
|
+
from pyarrow import csv, Table
|
|
241
|
+
|
|
242
|
+
csv.write_csv(Table.from_pandas(df), path_out)
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def save_nm_channels(
|
|
246
|
+
nmchannels: pd.DataFrame,
|
|
247
|
+
out_dir: _PathLike,
|
|
248
|
+
prefix: str = "",
|
|
249
|
+
) -> None:
|
|
250
|
+
filename = f"{prefix}_nm_channels.csv" if prefix else "nm_channels.csv"
|
|
251
|
+
path_out = PurePath(out_dir, filename)
|
|
252
|
+
write_csv(nmchannels, path_out)
|
|
253
|
+
logger.info(f"nm_channels.csv saved to {path_out}")
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def save_features(
|
|
257
|
+
df_features: pd.DataFrame,
|
|
258
|
+
out_dir: _PathLike,
|
|
259
|
+
prefix: str = "",
|
|
260
|
+
) -> None:
|
|
261
|
+
filename = f"{prefix}_FEATURES.csv" if prefix else "_FEATURES.csv"
|
|
262
|
+
out_dir = PurePath(out_dir, filename)
|
|
263
|
+
write_csv(df_features, out_dir)
|
|
264
|
+
logger.info(f"FEATURES.csv saved to {str(out_dir)}")
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def save_sidecar(
|
|
268
|
+
sidecar: dict,
|
|
269
|
+
out_dir: _PathLike,
|
|
270
|
+
prefix: str = "",
|
|
271
|
+
) -> None:
|
|
272
|
+
save_general_dict(sidecar, out_dir, prefix, "_SIDECAR.json")
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def save_general_dict(
|
|
276
|
+
dict_: dict,
|
|
277
|
+
out_dir: _PathLike,
|
|
278
|
+
prefix: str = "",
|
|
279
|
+
str_add: str = "",
|
|
280
|
+
) -> None:
|
|
281
|
+
# We should change this to a proper experiment name
|
|
282
|
+
|
|
283
|
+
path_out = PurePath(out_dir, f"{prefix}{str_add}")
|
|
284
|
+
|
|
285
|
+
with open(path_out, "w") as f:
|
|
286
|
+
json.dump(
|
|
287
|
+
dict_,
|
|
288
|
+
f,
|
|
289
|
+
default=default_json_convert,
|
|
290
|
+
indent=4,
|
|
291
|
+
separators=(",", ": "),
|
|
292
|
+
)
|
|
293
|
+
logger.info(f"{str_add} saved to {path_out}")
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
def default_json_convert(obj) -> list | float:
|
|
297
|
+
if isinstance(obj, np.ndarray):
|
|
298
|
+
return obj.tolist()
|
|
299
|
+
if isinstance(obj, pd.DataFrame):
|
|
300
|
+
return obj.to_numpy().tolist()
|
|
301
|
+
if isinstance(obj, np.integer):
|
|
302
|
+
return int(obj)
|
|
303
|
+
if isinstance(obj, np.floating):
|
|
304
|
+
return float(obj)
|
|
305
|
+
raise TypeError("Not serializable")
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
def read_sidecar(PATH: _PathLike) -> dict:
|
|
309
|
+
with open(PurePath(str(PATH) + "_SIDECAR.json")) as f:
|
|
310
|
+
return json.load(f)
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
def read_features(PATH: _PathLike) -> pd.DataFrame:
|
|
314
|
+
return pd.read_csv(str(PATH) + "_FEATURES.csv", engine="pyarrow")
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def read_nm_channels(PATH: _PathLike) -> pd.DataFrame:
|
|
318
|
+
return pd.read_csv(str(PATH) + "_nm_channels.csv")
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
def get_run_list_indir(PATH: _PathLike) -> list:
|
|
322
|
+
from os import walk
|
|
323
|
+
|
|
324
|
+
f_files = []
|
|
325
|
+
# for dirpath, _, files in Path(PATH).walk(): # Only works in python >=3.12
|
|
326
|
+
for dirpath, _, files in walk(PATH):
|
|
327
|
+
for x in files:
|
|
328
|
+
if "FEATURES" in x:
|
|
329
|
+
f_files.append(PurePath(dirpath).name)
|
|
330
|
+
return f_files
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
def loadmat(filename) -> dict:
|
|
334
|
+
"""
|
|
335
|
+
this function should be called instead of direct spio.loadmat
|
|
336
|
+
as it cures the problem of not properly recovering python dictionaries
|
|
337
|
+
from mat files. It calls the function check keys to cure all entries
|
|
338
|
+
which are still mat-objects
|
|
339
|
+
"""
|
|
340
|
+
from scipy.io import loadmat as sio_loadmat
|
|
341
|
+
|
|
342
|
+
data = sio_loadmat(filename, struct_as_record=False, squeeze_me=True)
|
|
343
|
+
return _check_keys(data)
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
def get_paths_example_data():
|
|
347
|
+
"""
|
|
348
|
+
This function should provide RUN_NAME, PATH_RUN, PATH_BIDS, PATH_OUT and datatype for the example
|
|
349
|
+
dataset used in most examples.
|
|
350
|
+
"""
|
|
351
|
+
|
|
352
|
+
sub = "testsub"
|
|
353
|
+
ses = "EphysMedOff"
|
|
354
|
+
task = "gripforce"
|
|
355
|
+
run = 0
|
|
356
|
+
datatype = "ieeg"
|
|
357
|
+
|
|
358
|
+
# Define run name and access paths in the BIDS format.
|
|
359
|
+
RUN_NAME = f"sub-{sub}_ses-{ses}_task-{task}_run-{run}"
|
|
360
|
+
|
|
361
|
+
PATH_BIDS = PYNM_DIR / "data"
|
|
362
|
+
|
|
363
|
+
PATH_RUN = PYNM_DIR / "data" / f"sub-{sub}" / f"ses-{ses}" / datatype / RUN_NAME
|
|
364
|
+
|
|
365
|
+
# Provide a path for the output data.
|
|
366
|
+
PATH_OUT = PATH_BIDS / "derivatives"
|
|
367
|
+
|
|
368
|
+
return RUN_NAME, PATH_RUN, PATH_BIDS, PATH_OUT, datatype
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
def _check_keys(dict):
|
|
372
|
+
"""
|
|
373
|
+
checks if entries in dictionary are mat-objects. If yes
|
|
374
|
+
todict is called to change them to nested dictionaries
|
|
375
|
+
"""
|
|
376
|
+
from scipy.io.matlab import mat_struct
|
|
377
|
+
|
|
378
|
+
for key in dict:
|
|
379
|
+
if isinstance(dict[key], mat_struct):
|
|
380
|
+
dict[key] = _todict(dict[key])
|
|
381
|
+
return dict
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
def _todict(matobj) -> dict:
|
|
385
|
+
"""
|
|
386
|
+
A recursive function which constructs from matobjects nested dictionaries
|
|
387
|
+
"""
|
|
388
|
+
from scipy.io.matlab import mat_struct
|
|
389
|
+
|
|
390
|
+
dict = {}
|
|
391
|
+
for strg in matobj._fieldnames:
|
|
392
|
+
elem = matobj.__dict__[strg]
|
|
393
|
+
if isinstance(elem, mat_struct):
|
|
394
|
+
dict[strg] = _todict(elem)
|
|
395
|
+
else:
|
|
396
|
+
dict[strg] = elem
|
|
397
|
+
return dict
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
def generate_unique_filename(path: _PathLike):
|
|
401
|
+
path = Path(path)
|
|
402
|
+
|
|
403
|
+
dir = path.parent
|
|
404
|
+
filename = path.stem
|
|
405
|
+
extension = path.suffix
|
|
406
|
+
|
|
407
|
+
counter = 1
|
|
408
|
+
while True:
|
|
409
|
+
new_filename = f"{filename}_{counter}{extension}"
|
|
410
|
+
new_file_path = dir / new_filename
|
|
411
|
+
if not new_file_path.exists():
|
|
412
|
+
return Path(new_file_path)
|
|
413
|
+
counter += 1
|