py-neuromodulation 0.0.4__py3-none-any.whl → 0.0.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- py_neuromodulation/ConnectivityDecoding/_get_grid_hull.m +34 -34
- py_neuromodulation/ConnectivityDecoding/_get_grid_whole_brain.py +95 -106
- py_neuromodulation/ConnectivityDecoding/_helper_write_connectome.py +107 -119
- py_neuromodulation/FieldTrip.py +589 -589
- py_neuromodulation/__init__.py +74 -13
- py_neuromodulation/_write_example_dataset_helper.py +83 -65
- py_neuromodulation/data/README +6 -6
- py_neuromodulation/data/dataset_description.json +8 -8
- py_neuromodulation/data/participants.json +32 -32
- py_neuromodulation/data/participants.tsv +2 -2
- py_neuromodulation/data/sub-testsub/ses-EphysMedOff/ieeg/sub-testsub_ses-EphysMedOff_space-mni_coordsystem.json +5 -5
- py_neuromodulation/data/sub-testsub/ses-EphysMedOff/ieeg/sub-testsub_ses-EphysMedOff_space-mni_electrodes.tsv +11 -11
- py_neuromodulation/data/sub-testsub/ses-EphysMedOff/ieeg/sub-testsub_ses-EphysMedOff_task-gripforce_run-0_channels.tsv +11 -11
- py_neuromodulation/data/sub-testsub/ses-EphysMedOff/ieeg/sub-testsub_ses-EphysMedOff_task-gripforce_run-0_ieeg.json +18 -18
- py_neuromodulation/data/sub-testsub/ses-EphysMedOff/ieeg/sub-testsub_ses-EphysMedOff_task-gripforce_run-0_ieeg.vhdr +35 -35
- py_neuromodulation/data/sub-testsub/ses-EphysMedOff/ieeg/sub-testsub_ses-EphysMedOff_task-gripforce_run-0_ieeg.vmrk +13 -13
- py_neuromodulation/data/sub-testsub/ses-EphysMedOff/sub-testsub_ses-EphysMedOff_scans.tsv +2 -2
- py_neuromodulation/grid_cortex.tsv +40 -40
- py_neuromodulation/liblsl/libpugixml.so.1.12 +0 -0
- py_neuromodulation/liblsl/linux/bionic_amd64/liblsl.1.16.2.so +0 -0
- py_neuromodulation/liblsl/linux/bookworm_amd64/liblsl.1.16.2.so +0 -0
- py_neuromodulation/liblsl/linux/focal_amd46/liblsl.1.16.2.so +0 -0
- py_neuromodulation/liblsl/linux/jammy_amd64/liblsl.1.16.2.so +0 -0
- py_neuromodulation/liblsl/linux/jammy_x86/liblsl.1.16.2.so +0 -0
- py_neuromodulation/liblsl/linux/noble_amd64/liblsl.1.16.2.so +0 -0
- py_neuromodulation/liblsl/macos/amd64/liblsl.1.16.2.dylib +0 -0
- py_neuromodulation/liblsl/macos/arm64/liblsl.1.16.0.dylib +0 -0
- py_neuromodulation/liblsl/windows/amd64/liblsl.1.16.2.dll +0 -0
- py_neuromodulation/liblsl/windows/x86/liblsl.1.16.2.dll +0 -0
- py_neuromodulation/nm_IO.py +413 -417
- py_neuromodulation/nm_RMAP.py +496 -531
- py_neuromodulation/nm_analysis.py +993 -1074
- py_neuromodulation/nm_artifacts.py +30 -25
- py_neuromodulation/nm_bispectra.py +154 -168
- py_neuromodulation/nm_bursts.py +292 -198
- py_neuromodulation/nm_coherence.py +251 -205
- py_neuromodulation/nm_database.py +149 -0
- py_neuromodulation/nm_decode.py +918 -992
- py_neuromodulation/nm_define_nmchannels.py +300 -302
- py_neuromodulation/nm_features.py +144 -116
- py_neuromodulation/nm_filter.py +219 -219
- py_neuromodulation/nm_filter_preprocessing.py +79 -91
- py_neuromodulation/nm_fooof.py +139 -159
- py_neuromodulation/nm_generator.py +45 -37
- py_neuromodulation/nm_hjorth_raw.py +52 -73
- py_neuromodulation/nm_kalmanfilter.py +71 -58
- py_neuromodulation/nm_linelength.py +21 -33
- py_neuromodulation/nm_logger.py +66 -0
- py_neuromodulation/nm_mne_connectivity.py +149 -112
- py_neuromodulation/nm_mnelsl_generator.py +90 -0
- py_neuromodulation/nm_mnelsl_stream.py +116 -0
- py_neuromodulation/nm_nolds.py +96 -93
- py_neuromodulation/nm_normalization.py +173 -214
- py_neuromodulation/nm_oscillatory.py +423 -448
- py_neuromodulation/nm_plots.py +585 -612
- py_neuromodulation/nm_preprocessing.py +83 -0
- py_neuromodulation/nm_projection.py +370 -394
- py_neuromodulation/nm_rereference.py +97 -95
- py_neuromodulation/nm_resample.py +59 -50
- py_neuromodulation/nm_run_analysis.py +325 -435
- py_neuromodulation/nm_settings.py +289 -68
- py_neuromodulation/nm_settings.yaml +244 -0
- py_neuromodulation/nm_sharpwaves.py +423 -401
- py_neuromodulation/nm_stats.py +464 -480
- py_neuromodulation/nm_stream.py +398 -0
- py_neuromodulation/nm_stream_abc.py +166 -218
- py_neuromodulation/nm_types.py +193 -0
- {py_neuromodulation-0.0.4.dist-info → py_neuromodulation-0.0.5.dist-info}/METADATA +29 -26
- py_neuromodulation-0.0.5.dist-info/RECORD +83 -0
- {py_neuromodulation-0.0.4.dist-info → py_neuromodulation-0.0.5.dist-info}/WHEEL +1 -1
- {py_neuromodulation-0.0.4.dist-info → py_neuromodulation-0.0.5.dist-info}/licenses/LICENSE +21 -21
- py_neuromodulation/nm_EpochStream.py +0 -92
- py_neuromodulation/nm_across_patient_decoding.py +0 -927
- py_neuromodulation/nm_cohortwrapper.py +0 -435
- py_neuromodulation/nm_eval_timing.py +0 -239
- py_neuromodulation/nm_features_abc.py +0 -39
- py_neuromodulation/nm_settings.json +0 -338
- py_neuromodulation/nm_stream_offline.py +0 -359
- py_neuromodulation/utils/_logging.py +0 -24
- py_neuromodulation-0.0.4.dist-info/RECORD +0 -72
|
@@ -1,1074 +1,993 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
import
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
from scipy import
|
|
12
|
-
|
|
13
|
-
from py_neuromodulation 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
|
-
self.
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
)
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
self.
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
self.
|
|
79
|
-
self.
|
|
80
|
-
self.
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
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
|
-
self.
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
def
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
if
|
|
505
|
-
else None,
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
self
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
if
|
|
670
|
-
self.decoder
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
self.decoder
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
)
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
performance_dict
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
if read_all_combined:
|
|
995
|
-
performance_dict[subject_name]["all_ch_combined"] = {}
|
|
996
|
-
write_CV_res_in_performance_dict(
|
|
997
|
-
ML_res.all_ch_results,
|
|
998
|
-
performance_dict[subject_name]["all_ch_combined"],
|
|
999
|
-
read_mov_detection_rates=read_mov_detection_rates,
|
|
1000
|
-
read_bay_opt_params=read_bay_opt_params,
|
|
1001
|
-
)
|
|
1002
|
-
|
|
1003
|
-
if read_grid_points:
|
|
1004
|
-
performance_dict[subject_name][
|
|
1005
|
-
"active_gridpoints"
|
|
1006
|
-
] = ML_res.active_gridpoints
|
|
1007
|
-
|
|
1008
|
-
for project_settings, grid_type in zip(
|
|
1009
|
-
["project_cortex", "project_subcortex"],
|
|
1010
|
-
["gridcortex_", "gridsubcortex_"],
|
|
1011
|
-
):
|
|
1012
|
-
if self.settings["postprocessing"][project_settings] is False:
|
|
1013
|
-
continue
|
|
1014
|
-
|
|
1015
|
-
# the sidecar keys are grid_cortex and subcortex_grid
|
|
1016
|
-
for grid_point in range(
|
|
1017
|
-
len(self.sidecar["grid_" + project_settings.split("_")[1]])
|
|
1018
|
-
):
|
|
1019
|
-
|
|
1020
|
-
gp_str = grid_type + str(grid_point)
|
|
1021
|
-
|
|
1022
|
-
performance_dict[subject_name][gp_str] = {}
|
|
1023
|
-
performance_dict[subject_name][gp_str][
|
|
1024
|
-
"coord"
|
|
1025
|
-
] = self.sidecar["grid_" + project_settings.split("_")[1]][
|
|
1026
|
-
grid_point
|
|
1027
|
-
]
|
|
1028
|
-
|
|
1029
|
-
if gp_str in ML_res.active_gridpoints:
|
|
1030
|
-
write_CV_res_in_performance_dict(
|
|
1031
|
-
ML_res.gridpoint_ind_results[gp_str],
|
|
1032
|
-
performance_dict[subject_name][gp_str],
|
|
1033
|
-
read_mov_detection_rates=read_mov_detection_rates,
|
|
1034
|
-
read_bay_opt_params=read_bay_opt_params,
|
|
1035
|
-
)
|
|
1036
|
-
else:
|
|
1037
|
-
# set non interpolated grid point to default performance
|
|
1038
|
-
performance_dict[subject_name][gp_str][
|
|
1039
|
-
"performance_test"
|
|
1040
|
-
] = DEFAULT_PERFORMANCE
|
|
1041
|
-
performance_dict[subject_name][gp_str][
|
|
1042
|
-
"performance_train"
|
|
1043
|
-
] = DEFAULT_PERFORMANCE
|
|
1044
|
-
|
|
1045
|
-
if save_results:
|
|
1046
|
-
nm_IO.save_general_dict(
|
|
1047
|
-
dict_=performance_dict,
|
|
1048
|
-
path_out=PATH_OUT,
|
|
1049
|
-
str_add=str_add,
|
|
1050
|
-
folder_name=folder_name,
|
|
1051
|
-
)
|
|
1052
|
-
return performance_dict
|
|
1053
|
-
|
|
1054
|
-
@staticmethod
|
|
1055
|
-
def get_dataframe_performances(p: dict) -> pd.DataFrame:
|
|
1056
|
-
performances = []
|
|
1057
|
-
for sub in p.keys():
|
|
1058
|
-
for ch in p[sub].keys():
|
|
1059
|
-
if "active_gridpoints" in ch:
|
|
1060
|
-
continue
|
|
1061
|
-
dict_add = p[sub][ch].copy()
|
|
1062
|
-
dict_add["sub"] = sub
|
|
1063
|
-
dict_add["ch"] = ch
|
|
1064
|
-
|
|
1065
|
-
if "all_ch_" in ch:
|
|
1066
|
-
dict_add["ch_type"] = "all ch combinded"
|
|
1067
|
-
elif "gridcortex" in ch:
|
|
1068
|
-
dict_add["ch_type"] = "cortex grid"
|
|
1069
|
-
else:
|
|
1070
|
-
dict_add["ch_type"] = "electrode ch"
|
|
1071
|
-
performances.append(dict_add)
|
|
1072
|
-
df = pd.DataFrame(performances)
|
|
1073
|
-
|
|
1074
|
-
return df
|
|
1
|
+
from pathlib import PurePath
|
|
2
|
+
|
|
3
|
+
import pickle
|
|
4
|
+
import numpy as np
|
|
5
|
+
import pandas as pd
|
|
6
|
+
|
|
7
|
+
from sklearn.linear_model import LogisticRegression
|
|
8
|
+
from sklearn.metrics import r2_score
|
|
9
|
+
from sklearn.model_selection import KFold
|
|
10
|
+
|
|
11
|
+
from scipy.stats import zscore as scipy_zscore
|
|
12
|
+
|
|
13
|
+
from py_neuromodulation import nm_IO, nm_plots
|
|
14
|
+
from py_neuromodulation.nm_decode import Decoder
|
|
15
|
+
from py_neuromodulation.nm_types import _PathLike
|
|
16
|
+
from py_neuromodulation.nm_settings import NMSettings
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
target_filter_str = {
|
|
20
|
+
"CLEAN",
|
|
21
|
+
"SQUARED_EMG",
|
|
22
|
+
"SQUARED_INTERPOLATED_EMG",
|
|
23
|
+
"SQUARED_ROTAWHEEL",
|
|
24
|
+
"SQUARED_ROTATION" "rota_squared",
|
|
25
|
+
}
|
|
26
|
+
features_reverse_order_plotting = {"stft", "fft", "bandpass"}
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class FeatureReader:
|
|
30
|
+
def __init__(
|
|
31
|
+
self,
|
|
32
|
+
feature_dir: _PathLike,
|
|
33
|
+
feature_file: _PathLike = "",
|
|
34
|
+
binarize_label: bool = True,
|
|
35
|
+
) -> None:
|
|
36
|
+
"""Feature_Reader enables analysis methods on top of NM_reader and NM_Decoder
|
|
37
|
+
|
|
38
|
+
Parameters
|
|
39
|
+
----------
|
|
40
|
+
feature_dir : str, optional
|
|
41
|
+
Path to py_neuromodulation estimated feature runs, where each feature is a folder,
|
|
42
|
+
feature_file : str, optional
|
|
43
|
+
specific feature run, if None it is set to the first feature folder in feature_dir
|
|
44
|
+
binarize_label : bool
|
|
45
|
+
binarize label, by default True
|
|
46
|
+
|
|
47
|
+
"""
|
|
48
|
+
self.feature_dir = feature_dir
|
|
49
|
+
self.feature_list: list[str] = nm_IO.get_run_list_indir(self.feature_dir)
|
|
50
|
+
self.feature_file = feature_file if feature_file else self.feature_list[0]
|
|
51
|
+
|
|
52
|
+
FILE_BASENAME = PurePath(self.feature_file).stem
|
|
53
|
+
PATH_READ_FILE = str(PurePath(self.feature_dir, FILE_BASENAME, FILE_BASENAME))
|
|
54
|
+
|
|
55
|
+
self.settings = NMSettings.from_file(PATH_READ_FILE)
|
|
56
|
+
self.sidecar = nm_IO.read_sidecar(PATH_READ_FILE)
|
|
57
|
+
if self.sidecar["sess_right"] is None:
|
|
58
|
+
if "coords" in self.sidecar:
|
|
59
|
+
if len(self.sidecar["coords"]["cortex_left"]["ch_names"]) > 0:
|
|
60
|
+
self.sidecar["sess_right"] = False
|
|
61
|
+
if len(self.sidecar["coords"]["cortex_right"]["ch_names"]) > 0:
|
|
62
|
+
self.sidecar["sess_right"] = True
|
|
63
|
+
self.sfreq = self.sidecar["sfreq"]
|
|
64
|
+
self.nm_channels = nm_IO.read_nm_channels(PATH_READ_FILE)
|
|
65
|
+
self.feature_arr = nm_IO.read_features(PATH_READ_FILE)
|
|
66
|
+
|
|
67
|
+
self.ch_names = self.nm_channels.new_name
|
|
68
|
+
self.used_chs = list(
|
|
69
|
+
self.nm_channels[
|
|
70
|
+
(self.nm_channels["target"] == 0) & (self.nm_channels["used"] == 1)
|
|
71
|
+
]["new_name"]
|
|
72
|
+
)
|
|
73
|
+
self.ch_names_ECOG = self.nm_channels.query(
|
|
74
|
+
'(type=="ecog") and (used == 1) and (status=="good")'
|
|
75
|
+
).new_name.to_list()
|
|
76
|
+
|
|
77
|
+
# init plotter
|
|
78
|
+
self.nmplotter = nm_plots.NM_Plot()
|
|
79
|
+
if self.nm_channels["target"].sum() > 0:
|
|
80
|
+
self.label_name = self._get_target_ch()
|
|
81
|
+
self.label = self.read_target_ch(
|
|
82
|
+
self.feature_arr,
|
|
83
|
+
self.label_name,
|
|
84
|
+
binarize=binarize_label,
|
|
85
|
+
binarize_th=0.3,
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
def _get_target_ch(self) -> str:
|
|
89
|
+
target_names = list(self.nm_channels[self.nm_channels["target"] == 1]["name"])
|
|
90
|
+
target_clean = [
|
|
91
|
+
target_name
|
|
92
|
+
for target_name in target_names
|
|
93
|
+
for filter_str in target_filter_str
|
|
94
|
+
if filter_str.lower() in target_name.lower()
|
|
95
|
+
]
|
|
96
|
+
|
|
97
|
+
if len(target_clean) == 0:
|
|
98
|
+
if "ARTIFACT" not in target_names[0]:
|
|
99
|
+
target = target_names[0]
|
|
100
|
+
elif len(target_names) > 1:
|
|
101
|
+
target = target_names[1]
|
|
102
|
+
else:
|
|
103
|
+
target = target_names[0]
|
|
104
|
+
else:
|
|
105
|
+
for target_ in target_clean:
|
|
106
|
+
# try to select contralateral label
|
|
107
|
+
if self.sidecar["sess_right"] and "LEFT" in target_:
|
|
108
|
+
target = target_
|
|
109
|
+
continue
|
|
110
|
+
elif not self.sidecar["sess_right"] and "RIGHT" in target_:
|
|
111
|
+
target = target_
|
|
112
|
+
continue
|
|
113
|
+
if target_ == target_clean[-1]:
|
|
114
|
+
target = target_clean[0] # set label to last element
|
|
115
|
+
return target
|
|
116
|
+
|
|
117
|
+
@staticmethod
|
|
118
|
+
def read_target_ch(
|
|
119
|
+
feature_arr: "pd.DataFrame",
|
|
120
|
+
label_name: str,
|
|
121
|
+
binarize: bool = True,
|
|
122
|
+
binarize_th: float = 0.3,
|
|
123
|
+
) -> np.ndarray:
|
|
124
|
+
"""_summary_
|
|
125
|
+
|
|
126
|
+
Parameters
|
|
127
|
+
----------
|
|
128
|
+
feature_arr : pd.DataFrame
|
|
129
|
+
_description_
|
|
130
|
+
label_name : str
|
|
131
|
+
_description_
|
|
132
|
+
binarize : bool, optional
|
|
133
|
+
_description_, by default True
|
|
134
|
+
binarize_th : float, optional
|
|
135
|
+
_description_, by default 0.3
|
|
136
|
+
|
|
137
|
+
Returns
|
|
138
|
+
-------
|
|
139
|
+
_type_
|
|
140
|
+
_description_
|
|
141
|
+
"""
|
|
142
|
+
|
|
143
|
+
label = np.nan_to_num(np.array(feature_arr[label_name]))
|
|
144
|
+
if binarize:
|
|
145
|
+
label = label > binarize_th
|
|
146
|
+
return label
|
|
147
|
+
|
|
148
|
+
@staticmethod
|
|
149
|
+
def filter_features(
|
|
150
|
+
feature_columns: list,
|
|
151
|
+
ch_name: str | None = None,
|
|
152
|
+
list_feature_keywords: list[str] | None = None,
|
|
153
|
+
) -> list:
|
|
154
|
+
"""filters read features by ch_name and/or modality
|
|
155
|
+
|
|
156
|
+
Parameters
|
|
157
|
+
----------
|
|
158
|
+
feature_columns : list
|
|
159
|
+
ch_name : str, optional
|
|
160
|
+
list_feature_keywords : list[str], optional
|
|
161
|
+
list of feature strings that need to be in the columns, by default None
|
|
162
|
+
|
|
163
|
+
Returns
|
|
164
|
+
-------
|
|
165
|
+
features : list
|
|
166
|
+
column list that suffice the ch_name and list_feature_keywords
|
|
167
|
+
"""
|
|
168
|
+
|
|
169
|
+
if ch_name is not None:
|
|
170
|
+
feature_select = [i for i in list(feature_columns) if ch_name in i]
|
|
171
|
+
else:
|
|
172
|
+
feature_select = feature_columns
|
|
173
|
+
|
|
174
|
+
if list_feature_keywords is not None:
|
|
175
|
+
feature_select = [
|
|
176
|
+
f for f in feature_select if any(x in f for x in list_feature_keywords)
|
|
177
|
+
]
|
|
178
|
+
|
|
179
|
+
if (
|
|
180
|
+
len(
|
|
181
|
+
[
|
|
182
|
+
mod
|
|
183
|
+
for mod in features_reverse_order_plotting
|
|
184
|
+
if mod in list_feature_keywords
|
|
185
|
+
]
|
|
186
|
+
)
|
|
187
|
+
> 0
|
|
188
|
+
):
|
|
189
|
+
# flip list s.t. theta band is lowest in subsequent plot
|
|
190
|
+
feature_select = feature_select[::-1]
|
|
191
|
+
|
|
192
|
+
return feature_select
|
|
193
|
+
|
|
194
|
+
def set_target_ch(self, ch_name: str) -> None:
|
|
195
|
+
self.label_name = ch_name
|
|
196
|
+
|
|
197
|
+
def normalize_features(
|
|
198
|
+
self,
|
|
199
|
+
) -> "pd.DataFrame":
|
|
200
|
+
"""Normalize feature_arr feature columns
|
|
201
|
+
|
|
202
|
+
Returns:
|
|
203
|
+
pd.DataFrame: z-scored feature_arr
|
|
204
|
+
"""
|
|
205
|
+
cols_norm = [c for c in self.feature_arr.columns if "time" not in c]
|
|
206
|
+
feature_arr_norm = scipy_zscore(self.feature_arr[cols_norm], nan_policy="omit")
|
|
207
|
+
feature_arr_norm["time"] = self.feature_arr["time"]
|
|
208
|
+
return feature_arr_norm
|
|
209
|
+
|
|
210
|
+
def plot_cort_projection(self) -> None:
|
|
211
|
+
"""_summary_"""
|
|
212
|
+
|
|
213
|
+
if self.sidecar["sess_right"]:
|
|
214
|
+
ecog_strip = np.array(self.sidecar["coords"]["cortex_right"]["positions"])
|
|
215
|
+
else:
|
|
216
|
+
ecog_strip = np.array(self.sidecar["coords"]["cortex_left"]["positions"])
|
|
217
|
+
self.nmplotter.plot_cortex(
|
|
218
|
+
grid_cortex=np.array(self.sidecar["grid_cortex"])
|
|
219
|
+
if "grid_cortex" in self.sidecar
|
|
220
|
+
else None,
|
|
221
|
+
ecog_strip=ecog_strip,
|
|
222
|
+
grid_color=np.array(self.sidecar["proj_matrix_cortex"]).sum(axis=1)
|
|
223
|
+
if "grid_cortex" in self.sidecar
|
|
224
|
+
else None,
|
|
225
|
+
set_clim=False,
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
def plot_target_avg_all_channels(
|
|
229
|
+
self,
|
|
230
|
+
ch_names_ECOG=None,
|
|
231
|
+
list_feature_keywords: list[str] = ["stft"],
|
|
232
|
+
epoch_len: int = 4,
|
|
233
|
+
threshold: float = 0.1,
|
|
234
|
+
):
|
|
235
|
+
"""Wrapper that call plot_features_per_channel
|
|
236
|
+
for every given ECoG channel
|
|
237
|
+
|
|
238
|
+
Parameters
|
|
239
|
+
----------
|
|
240
|
+
ch_names_ECOG : list, optional
|
|
241
|
+
list of ECoG channel to plot features for, by default None
|
|
242
|
+
list_feature_keywords : list[str], optional
|
|
243
|
+
keywords to plot, by default ["stft"]
|
|
244
|
+
epoch_len : int, optional
|
|
245
|
+
epoch length in seconds, by default 4
|
|
246
|
+
threshold : float, optional
|
|
247
|
+
threshold for event detection, by default 0.1
|
|
248
|
+
"""
|
|
249
|
+
|
|
250
|
+
if ch_names_ECOG is None:
|
|
251
|
+
ch_names_ECOG = self.ch_names_ECOG
|
|
252
|
+
for ch_name_ECOG in ch_names_ECOG:
|
|
253
|
+
self.plot_target_averaged_channel(
|
|
254
|
+
ch=ch_name_ECOG,
|
|
255
|
+
list_feature_keywords=list_feature_keywords,
|
|
256
|
+
epoch_len=epoch_len,
|
|
257
|
+
threshold=threshold,
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
def plot_target_averaged_channel(
|
|
261
|
+
self,
|
|
262
|
+
ch: str = "",
|
|
263
|
+
list_feature_keywords: list[str] | None = None,
|
|
264
|
+
features_to_plt: list | None = None,
|
|
265
|
+
epoch_len: int = 4,
|
|
266
|
+
threshold: float = 0.1,
|
|
267
|
+
normalize_data: bool = True,
|
|
268
|
+
show_plot: bool = True,
|
|
269
|
+
title: str = "Movement aligned features",
|
|
270
|
+
ytick_labelsize=None,
|
|
271
|
+
figsize_x: float = 8,
|
|
272
|
+
figsize_y: float = 8,
|
|
273
|
+
) -> None:
|
|
274
|
+
"""_summary_
|
|
275
|
+
|
|
276
|
+
Parameters
|
|
277
|
+
----------
|
|
278
|
+
ch : str, optional
|
|
279
|
+
list_feature_keywords : Optional[list[str]], optional
|
|
280
|
+
features_to_plt : list, optional
|
|
281
|
+
epoch_len : int, optional
|
|
282
|
+
threshold : float, optional
|
|
283
|
+
normalize_data : bool, optional
|
|
284
|
+
show_plot : bool, optional
|
|
285
|
+
title : str, optional
|
|
286
|
+
by default "Movement aligned features"
|
|
287
|
+
ytick_labelsize : _type_, optional
|
|
288
|
+
figsize_x : float, optional
|
|
289
|
+
by default 8
|
|
290
|
+
figsize_y : float, optional
|
|
291
|
+
by default 8
|
|
292
|
+
"""
|
|
293
|
+
|
|
294
|
+
# TODO: This does not work properly when we have bipolar rereferencing
|
|
295
|
+
|
|
296
|
+
if features_to_plt is None:
|
|
297
|
+
filtered_df = self.feature_arr[
|
|
298
|
+
self.filter_features(
|
|
299
|
+
self.feature_arr.columns, ch, list_feature_keywords
|
|
300
|
+
)[::-1]
|
|
301
|
+
]
|
|
302
|
+
else:
|
|
303
|
+
filtered_df = self.feature_arr[features_to_plt]
|
|
304
|
+
|
|
305
|
+
data = np.expand_dims(np.array(filtered_df), axis=1)
|
|
306
|
+
|
|
307
|
+
X_epoch, y_epoch = self.get_epochs(
|
|
308
|
+
data,
|
|
309
|
+
self.label,
|
|
310
|
+
epoch_len=epoch_len,
|
|
311
|
+
sfreq=self.settings.sampling_rate_features_hz,
|
|
312
|
+
threshold=threshold,
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
nm_plots.plot_epochs_avg(
|
|
316
|
+
X_epoch=X_epoch,
|
|
317
|
+
y_epoch=y_epoch,
|
|
318
|
+
epoch_len=epoch_len,
|
|
319
|
+
sfreq=self.settings.sampling_rate_features_hz,
|
|
320
|
+
feature_names=list(filtered_df.columns),
|
|
321
|
+
feature_str_add="_".join(list_feature_keywords)
|
|
322
|
+
if list_feature_keywords is not None
|
|
323
|
+
else "all",
|
|
324
|
+
cut_ch_name_cols=True,
|
|
325
|
+
ch_name=ch,
|
|
326
|
+
label_name=self.label_name,
|
|
327
|
+
normalize_data=normalize_data,
|
|
328
|
+
show_plot=show_plot,
|
|
329
|
+
save=True,
|
|
330
|
+
OUT_PATH=self.feature_dir,
|
|
331
|
+
feature_file=self.feature_file,
|
|
332
|
+
str_title=title,
|
|
333
|
+
ytick_labelsize=ytick_labelsize,
|
|
334
|
+
figsize_x=figsize_x,
|
|
335
|
+
figsize_y=figsize_y,
|
|
336
|
+
)
|
|
337
|
+
|
|
338
|
+
def plot_all_features(
|
|
339
|
+
self,
|
|
340
|
+
ch_used: str | None = None,
|
|
341
|
+
time_limit_low_s: float | None = None,
|
|
342
|
+
time_limit_high_s: float | None = None,
|
|
343
|
+
normalize: bool = True,
|
|
344
|
+
save: bool = False,
|
|
345
|
+
title="all_feature_plt.pdf",
|
|
346
|
+
ytick_labelsize: int = 10,
|
|
347
|
+
clim_low: float | None = None,
|
|
348
|
+
clim_high: float | None = None,
|
|
349
|
+
):
|
|
350
|
+
"""_summary_
|
|
351
|
+
|
|
352
|
+
Parameters
|
|
353
|
+
----------
|
|
354
|
+
ch_used : str, optional
|
|
355
|
+
time_limit_low_s : float, optional
|
|
356
|
+
time_limit_high_s : float, optional
|
|
357
|
+
normalize : bool, optional
|
|
358
|
+
save : bool, optional
|
|
359
|
+
title : str, optional
|
|
360
|
+
default "all_feature_plt.pdf"
|
|
361
|
+
ytick_labelsize : int, optional
|
|
362
|
+
by default 10
|
|
363
|
+
clim_low : float, optional
|
|
364
|
+
by default None
|
|
365
|
+
clim_high : float, optional
|
|
366
|
+
by default None
|
|
367
|
+
"""
|
|
368
|
+
|
|
369
|
+
if ch_used is not None:
|
|
370
|
+
col_used = [
|
|
371
|
+
c
|
|
372
|
+
for c in self.feature_arr.columns
|
|
373
|
+
if c.startswith(ch_used) or c == "time" or "LABEL" in c or "MOV" in c
|
|
374
|
+
]
|
|
375
|
+
df = self.feature_arr[col_used[::-1]]
|
|
376
|
+
else:
|
|
377
|
+
df = self.feature_arr[self.feature_arr.columns[::-1]]
|
|
378
|
+
|
|
379
|
+
nm_plots.plot_all_features(
|
|
380
|
+
df=df,
|
|
381
|
+
time_limit_low_s=time_limit_low_s,
|
|
382
|
+
time_limit_high_s=time_limit_high_s,
|
|
383
|
+
normalize=normalize,
|
|
384
|
+
save=save,
|
|
385
|
+
title=title,
|
|
386
|
+
ytick_labelsize=ytick_labelsize,
|
|
387
|
+
feature_file=self.feature_file,
|
|
388
|
+
OUT_PATH=self.feature_dir,
|
|
389
|
+
clim_low=clim_low,
|
|
390
|
+
clim_high=clim_high,
|
|
391
|
+
)
|
|
392
|
+
|
|
393
|
+
@staticmethod
|
|
394
|
+
def get_performace_sub_strip(performance_sub: dict, plt_grid: bool = False):
|
|
395
|
+
ecog_strip_performance = []
|
|
396
|
+
ecog_coords_strip = []
|
|
397
|
+
cortex_grid = []
|
|
398
|
+
grid_performance = []
|
|
399
|
+
|
|
400
|
+
channels_ = performance_sub.keys()
|
|
401
|
+
|
|
402
|
+
for ch in channels_:
|
|
403
|
+
if "grid" not in ch and "combined" not in ch:
|
|
404
|
+
ecog_coords_strip.append(performance_sub[ch]["coord"])
|
|
405
|
+
ecog_strip_performance.append(performance_sub[ch]["performance_test"])
|
|
406
|
+
elif plt_grid and "gridcortex_" in ch:
|
|
407
|
+
cortex_grid.append(performance_sub[ch]["coord"])
|
|
408
|
+
grid_performance.append(performance_sub[ch]["performance_test"])
|
|
409
|
+
|
|
410
|
+
if len(ecog_coords_strip) > 0:
|
|
411
|
+
ecog_coords_strip = np.vstack(ecog_coords_strip)
|
|
412
|
+
|
|
413
|
+
return (
|
|
414
|
+
ecog_strip_performance,
|
|
415
|
+
ecog_coords_strip,
|
|
416
|
+
cortex_grid,
|
|
417
|
+
grid_performance,
|
|
418
|
+
)
|
|
419
|
+
|
|
420
|
+
def plot_across_subject_grd_ch_performance(
|
|
421
|
+
self,
|
|
422
|
+
performance_dict=None,
|
|
423
|
+
plt_grid=False,
|
|
424
|
+
feature_str_add="performance_allch_allgrid",
|
|
425
|
+
):
|
|
426
|
+
ecog_strip_performance = []
|
|
427
|
+
ecog_coords_strip = []
|
|
428
|
+
grid_performance = []
|
|
429
|
+
for sub in performance_dict.keys():
|
|
430
|
+
(
|
|
431
|
+
ecog_strip_performance_sub,
|
|
432
|
+
ecog_coords_strip_sub,
|
|
433
|
+
_,
|
|
434
|
+
grid_performance_sub,
|
|
435
|
+
) = self.get_performace_sub_strip(performance_dict[sub], plt_grid=plt_grid)
|
|
436
|
+
ecog_strip_performance.extend(ecog_strip_performance_sub)
|
|
437
|
+
ecog_coords_strip.extend(ecog_coords_strip_sub)
|
|
438
|
+
grid_performance.append(grid_performance_sub)
|
|
439
|
+
grid_performance = list(np.vstack(grid_performance).mean(axis=0))
|
|
440
|
+
coords_all = np.array(ecog_coords_strip)
|
|
441
|
+
coords_all[:, 0] = np.abs(coords_all[:, 0])
|
|
442
|
+
|
|
443
|
+
self.nmplotter.plot_cortex(
|
|
444
|
+
grid_cortex=np.array(self.sidecar["grid_cortex"])
|
|
445
|
+
if "grid_cortex" in self.sidecar
|
|
446
|
+
else None,
|
|
447
|
+
ecog_strip=coords_all if len(ecog_coords_strip) > 0 else None,
|
|
448
|
+
grid_color=grid_performance if len(grid_performance) > 0 else None,
|
|
449
|
+
strip_color=np.array(ecog_strip_performance)
|
|
450
|
+
if len(ecog_strip_performance) > 0
|
|
451
|
+
else None,
|
|
452
|
+
sess_right=self.sidecar["sess_right"],
|
|
453
|
+
save=True,
|
|
454
|
+
OUT_PATH=self.feature_dir,
|
|
455
|
+
feature_file=self.feature_file,
|
|
456
|
+
feature_str_add=feature_str_add,
|
|
457
|
+
show_plot=True,
|
|
458
|
+
)
|
|
459
|
+
|
|
460
|
+
def plot_subject_grid_ch_performance(
|
|
461
|
+
self,
|
|
462
|
+
subject_name=None,
|
|
463
|
+
performance_dict=None,
|
|
464
|
+
plt_grid=False,
|
|
465
|
+
feature_str_add="performance_allch_allgrid",
|
|
466
|
+
):
|
|
467
|
+
"""plot subject specific performance for individual channeal and optional grid points
|
|
468
|
+
|
|
469
|
+
Parameters
|
|
470
|
+
----------
|
|
471
|
+
subject_name : string, optional
|
|
472
|
+
used subject, by default None
|
|
473
|
+
performance_dict : dict, optional
|
|
474
|
+
by default None
|
|
475
|
+
plt_grid : bool, optional
|
|
476
|
+
True to plot grid performances, by default False
|
|
477
|
+
feature_str_add : string, optional
|
|
478
|
+
figure output_name
|
|
479
|
+
"""
|
|
480
|
+
|
|
481
|
+
ecog_strip_performance = []
|
|
482
|
+
ecog_coords_strip = []
|
|
483
|
+
cortex_grid = []
|
|
484
|
+
grid_performance = []
|
|
485
|
+
|
|
486
|
+
if subject_name is None:
|
|
487
|
+
subject_name = self.feature_file[
|
|
488
|
+
self.feature_file.find("sub-") : self.feature_file.find("_ses")
|
|
489
|
+
][4:]
|
|
490
|
+
|
|
491
|
+
(
|
|
492
|
+
ecog_strip_performance,
|
|
493
|
+
ecog_coords_strip,
|
|
494
|
+
cortex_grid,
|
|
495
|
+
grid_performance,
|
|
496
|
+
) = self.get_performace_sub_strip(
|
|
497
|
+
performance_dict[subject_name], plt_grid=plt_grid
|
|
498
|
+
)
|
|
499
|
+
|
|
500
|
+
self.nmplotter.plot_cortex(
|
|
501
|
+
grid_cortex=np.array(self.sidecar["grid_cortex"])
|
|
502
|
+
if "grid_cortex" in self.sidecar
|
|
503
|
+
else None,
|
|
504
|
+
ecog_strip=ecog_coords_strip if len(ecog_coords_strip) > 0 else None,
|
|
505
|
+
grid_color=grid_performance if len(grid_performance) > 0 else None,
|
|
506
|
+
strip_color=ecog_strip_performance
|
|
507
|
+
if len(ecog_strip_performance) > 0
|
|
508
|
+
else None,
|
|
509
|
+
sess_right=self.sidecar["sess_right"],
|
|
510
|
+
save=True,
|
|
511
|
+
OUT_PATH=self.feature_dir,
|
|
512
|
+
feature_file=self.feature_file,
|
|
513
|
+
feature_str_add=feature_str_add,
|
|
514
|
+
show_plot=True,
|
|
515
|
+
)
|
|
516
|
+
|
|
517
|
+
def plot_feature_series_time(
|
|
518
|
+
self,
|
|
519
|
+
):
|
|
520
|
+
self.nmplotter.plot_feature_series_time(self.feature_arr)
|
|
521
|
+
|
|
522
|
+
def plot_corr_matrix(
|
|
523
|
+
self,
|
|
524
|
+
):
|
|
525
|
+
return nm_plots.plot_corr_matrix(
|
|
526
|
+
self.feature_arr,
|
|
527
|
+
)
|
|
528
|
+
|
|
529
|
+
@staticmethod
|
|
530
|
+
def get_epochs(
|
|
531
|
+
data, y_, epoch_len, sfreq, threshold=0
|
|
532
|
+
) -> tuple[np.ndarray, np.ndarray]:
|
|
533
|
+
"""Return epoched data.
|
|
534
|
+
|
|
535
|
+
Parameters
|
|
536
|
+
----------
|
|
537
|
+
data : np.ndarray
|
|
538
|
+
array of extracted features of shape (n_samples, n_channels, n_features)
|
|
539
|
+
y_ : np.ndarray
|
|
540
|
+
array of labels e.g. ones for movement and zeros for
|
|
541
|
+
no movement or baseline corr. rotameter data
|
|
542
|
+
epoch_len : int
|
|
543
|
+
length of epoch in seconds
|
|
544
|
+
sfreq : int/float
|
|
545
|
+
sampling frequency of data
|
|
546
|
+
threshold : int/float
|
|
547
|
+
(Optional) threshold to be used for identifying events
|
|
548
|
+
(default=0 for y_tr with only ones
|
|
549
|
+
and zeros)
|
|
550
|
+
|
|
551
|
+
Returns
|
|
552
|
+
-------
|
|
553
|
+
epoch_ : np.ndarray
|
|
554
|
+
array of epoched ieeg data with shape (epochs,samples,channels,features)
|
|
555
|
+
y_arr : np.ndarray
|
|
556
|
+
array of epoched event label data with shape (epochs,samples)
|
|
557
|
+
"""
|
|
558
|
+
|
|
559
|
+
epoch_lim = int(epoch_len * sfreq)
|
|
560
|
+
|
|
561
|
+
ind_mov = np.where(np.diff(np.array(y_ > threshold) * 1) == 1)[0]
|
|
562
|
+
|
|
563
|
+
low_limit = ind_mov > epoch_lim / 2
|
|
564
|
+
up_limit = ind_mov < y_.shape[0] - epoch_lim / 2
|
|
565
|
+
|
|
566
|
+
ind_mov = ind_mov[low_limit & up_limit]
|
|
567
|
+
|
|
568
|
+
epoch_ = np.zeros([ind_mov.shape[0], epoch_lim, data.shape[1], data.shape[2]])
|
|
569
|
+
|
|
570
|
+
y_arr = np.zeros([ind_mov.shape[0], int(epoch_lim)])
|
|
571
|
+
|
|
572
|
+
for idx, i in enumerate(ind_mov):
|
|
573
|
+
epoch_[idx, :, :, :] = data[i - epoch_lim // 2 : i + epoch_lim // 2, :, :]
|
|
574
|
+
|
|
575
|
+
y_arr[idx, :] = y_[i - epoch_lim // 2 : i + epoch_lim // 2]
|
|
576
|
+
|
|
577
|
+
return epoch_, y_arr
|
|
578
|
+
|
|
579
|
+
def set_decoder(
|
|
580
|
+
self,
|
|
581
|
+
decoder: Decoder | None = None,
|
|
582
|
+
TRAIN_VAL_SPLIT=False,
|
|
583
|
+
RUN_BAY_OPT=False,
|
|
584
|
+
save_coef=False,
|
|
585
|
+
model=LogisticRegression,
|
|
586
|
+
eval_method=r2_score,
|
|
587
|
+
cv_method=KFold(n_splits=3, shuffle=False),
|
|
588
|
+
get_movement_detection_rate: bool = False,
|
|
589
|
+
mov_detection_threshold=0.5,
|
|
590
|
+
min_consequent_count=3,
|
|
591
|
+
threshold_score=True,
|
|
592
|
+
bay_opt_param_space: list = [],
|
|
593
|
+
STACK_FEATURES_N_SAMPLES=False,
|
|
594
|
+
time_stack_n_samples=5,
|
|
595
|
+
use_nested_cv=False,
|
|
596
|
+
VERBOSE=False,
|
|
597
|
+
undersampling=False,
|
|
598
|
+
oversampling=False,
|
|
599
|
+
mrmr_select=False,
|
|
600
|
+
pca=False,
|
|
601
|
+
cca=False,
|
|
602
|
+
):
|
|
603
|
+
if decoder is not None:
|
|
604
|
+
self.decoder = decoder
|
|
605
|
+
else:
|
|
606
|
+
self.decoder = Decoder(
|
|
607
|
+
features=self.feature_arr,
|
|
608
|
+
label=self.label,
|
|
609
|
+
label_name=self.label_name,
|
|
610
|
+
used_chs=self.used_chs,
|
|
611
|
+
model=model,
|
|
612
|
+
eval_method=eval_method,
|
|
613
|
+
cv_method=cv_method,
|
|
614
|
+
threshold_score=threshold_score,
|
|
615
|
+
TRAIN_VAL_SPLIT=TRAIN_VAL_SPLIT,
|
|
616
|
+
RUN_BAY_OPT=RUN_BAY_OPT,
|
|
617
|
+
save_coef=save_coef,
|
|
618
|
+
get_movement_detection_rate=get_movement_detection_rate,
|
|
619
|
+
min_consequent_count=min_consequent_count,
|
|
620
|
+
mov_detection_threshold=mov_detection_threshold,
|
|
621
|
+
bay_opt_param_space=bay_opt_param_space,
|
|
622
|
+
STACK_FEATURES_N_SAMPLES=STACK_FEATURES_N_SAMPLES,
|
|
623
|
+
time_stack_n_samples=time_stack_n_samples,
|
|
624
|
+
VERBOSE=VERBOSE,
|
|
625
|
+
use_nested_cv=use_nested_cv,
|
|
626
|
+
undersampling=undersampling,
|
|
627
|
+
oversampling=oversampling,
|
|
628
|
+
mrmr_select=mrmr_select,
|
|
629
|
+
sfreq=self.sfreq,
|
|
630
|
+
pca=pca,
|
|
631
|
+
cca=cca,
|
|
632
|
+
)
|
|
633
|
+
|
|
634
|
+
def run_ML_model(
|
|
635
|
+
self,
|
|
636
|
+
feature_file: str | None = None,
|
|
637
|
+
estimate_gridpoints: bool = False,
|
|
638
|
+
estimate_channels: bool = True,
|
|
639
|
+
estimate_all_channels_combined: bool = False,
|
|
640
|
+
output_name: str = "LM",
|
|
641
|
+
save_results: bool = True,
|
|
642
|
+
):
|
|
643
|
+
"""machine learning model evaluation for ECoG strip channels and/or grid points
|
|
644
|
+
|
|
645
|
+
Parameters
|
|
646
|
+
----------
|
|
647
|
+
feature_file : string, optional
|
|
648
|
+
estimate_gridpoints : bool, optional
|
|
649
|
+
run ML analysis for grid points, by default True
|
|
650
|
+
estimate_channels : bool, optional
|
|
651
|
+
run ML analysis for ECoG strip channel, by default True
|
|
652
|
+
estimate_all_channels_combined : bool, optional
|
|
653
|
+
run ML analysis features of all channels concatenated, by default False
|
|
654
|
+
model : sklearn model, optional
|
|
655
|
+
ML model, needs to obtain fit and predict functions,
|
|
656
|
+
by default linear_model.LogisticRegression(class_weight="balanced")
|
|
657
|
+
eval_method : sklearn.metrics, optional
|
|
658
|
+
evaluation performance metric, by default metrics.balanced_accuracy_score
|
|
659
|
+
cv_method : sklearn.model_selection, optional
|
|
660
|
+
valdation strategy, by default model_selection.KFold(n_splits=3, shuffle=False)
|
|
661
|
+
output_name : str, optional
|
|
662
|
+
saving name, by default "LM"
|
|
663
|
+
save_results : boolean
|
|
664
|
+
if true, save model._coef trained coefficients
|
|
665
|
+
"""
|
|
666
|
+
if feature_file is None:
|
|
667
|
+
feature_file = self.feature_file
|
|
668
|
+
|
|
669
|
+
if estimate_gridpoints:
|
|
670
|
+
self.decoder.set_data_grid_points()
|
|
671
|
+
_ = self.decoder.run_CV_caller("grid_points")
|
|
672
|
+
if estimate_channels:
|
|
673
|
+
self.decoder.set_data_ind_channels()
|
|
674
|
+
_ = self.decoder.run_CV_caller("ind_channels")
|
|
675
|
+
if estimate_all_channels_combined:
|
|
676
|
+
_ = self.decoder.run_CV_caller("all_channels_combined")
|
|
677
|
+
|
|
678
|
+
if save_results:
|
|
679
|
+
self.decoder.save(
|
|
680
|
+
self.feature_dir,
|
|
681
|
+
self.feature_file
|
|
682
|
+
if ".vhdr" in self.feature_file
|
|
683
|
+
else self.feature_file,
|
|
684
|
+
output_name,
|
|
685
|
+
)
|
|
686
|
+
|
|
687
|
+
return self.read_results(
|
|
688
|
+
read_grid_points=estimate_gridpoints,
|
|
689
|
+
read_all_combined=estimate_all_channels_combined,
|
|
690
|
+
read_channels=estimate_channels,
|
|
691
|
+
ML_model_name=output_name,
|
|
692
|
+
read_mov_detection_rates=self.decoder.get_movement_detection_rate,
|
|
693
|
+
read_bay_opt_params=self.decoder.RUN_BAY_OPT,
|
|
694
|
+
read_mrmr=self.decoder.mrmr_select,
|
|
695
|
+
model_save=self.decoder.model_save,
|
|
696
|
+
)
|
|
697
|
+
|
|
698
|
+
def read_results(
|
|
699
|
+
self,
|
|
700
|
+
performance_dict: dict = {},
|
|
701
|
+
subject_name: str | None = None,
|
|
702
|
+
DEFAULT_PERFORMANCE: float = 0.5,
|
|
703
|
+
read_grid_points: bool = True,
|
|
704
|
+
read_channels: bool = True,
|
|
705
|
+
read_all_combined: bool = False,
|
|
706
|
+
ML_model_name: str = "LM",
|
|
707
|
+
read_mov_detection_rates: bool = False,
|
|
708
|
+
read_bay_opt_params: bool = False,
|
|
709
|
+
read_mrmr: bool = False,
|
|
710
|
+
model_save: bool = False,
|
|
711
|
+
save_results: bool = False,
|
|
712
|
+
PATH_OUT: str = "", # Removed None default, save_general_dict does not handle None anyway
|
|
713
|
+
folder_name: str = "",
|
|
714
|
+
str_add: str = "",
|
|
715
|
+
):
|
|
716
|
+
"""Save performances of a given patient into performance_dict from saved nm_decoder
|
|
717
|
+
|
|
718
|
+
Parameters
|
|
719
|
+
----------
|
|
720
|
+
performance_dict : dictionary
|
|
721
|
+
dictionary including decoding performances, by default dictionary
|
|
722
|
+
subject_name : string, optional
|
|
723
|
+
subject name, by default None
|
|
724
|
+
DEFAULT_PERFORMANCE : float, optional
|
|
725
|
+
chance performance, by default 0.5
|
|
726
|
+
read_grid_points : bool, optional
|
|
727
|
+
true if grid point performances are read, by default True
|
|
728
|
+
read_channels : bool, optional
|
|
729
|
+
true if channels performances are read, by default True
|
|
730
|
+
read_all_combined : bool, optional
|
|
731
|
+
true if all combined channel performances are read, by default False
|
|
732
|
+
ML_model_name : str, optional
|
|
733
|
+
machine learning model name, by default 'LM'
|
|
734
|
+
read_mov_detection_rates : boolean, by defaulte False
|
|
735
|
+
if True, read movement detection rates, as well as fpr's and tpr's
|
|
736
|
+
read_bay_opt_params : boolean, by default False
|
|
737
|
+
read_mrmr : boolean, by default False
|
|
738
|
+
model_save : boolean, by default False
|
|
739
|
+
save_results : boolean, by default False
|
|
740
|
+
PATH_OUT : string, by default None
|
|
741
|
+
folder_name : string, by default None
|
|
742
|
+
str_add : string, by default None
|
|
743
|
+
|
|
744
|
+
Returns
|
|
745
|
+
-------
|
|
746
|
+
performance_dict : dictionary
|
|
747
|
+
|
|
748
|
+
"""
|
|
749
|
+
|
|
750
|
+
if ".vhdr" in self.feature_file:
|
|
751
|
+
feature_file = self.feature_file[: -len(".vhdr")]
|
|
752
|
+
else:
|
|
753
|
+
feature_file = self.feature_file
|
|
754
|
+
|
|
755
|
+
if subject_name is None:
|
|
756
|
+
subject_name = feature_file[
|
|
757
|
+
feature_file.find("sub-") : feature_file.find("_ses")
|
|
758
|
+
][4:]
|
|
759
|
+
|
|
760
|
+
PATH_ML_ = PurePath(
|
|
761
|
+
self.feature_dir,
|
|
762
|
+
feature_file,
|
|
763
|
+
feature_file + "_" + ML_model_name + "_ML_RES.p",
|
|
764
|
+
)
|
|
765
|
+
|
|
766
|
+
# read ML results
|
|
767
|
+
with open(PATH_ML_, "rb") as input:
|
|
768
|
+
ML_res = pickle.load(input)
|
|
769
|
+
if self.decoder is None:
|
|
770
|
+
self.decoder = ML_res
|
|
771
|
+
|
|
772
|
+
performance_dict[subject_name] = {}
|
|
773
|
+
|
|
774
|
+
def write_CV_res_in_performance_dict(
|
|
775
|
+
obj_read,
|
|
776
|
+
obj_write,
|
|
777
|
+
read_mov_detection_rates=read_mov_detection_rates,
|
|
778
|
+
read_bay_opt_params=False,
|
|
779
|
+
):
|
|
780
|
+
def transform_list_of_dicts_into_dict_of_lists(l_):
|
|
781
|
+
dict_out = {}
|
|
782
|
+
for key_, _ in l_[0].items():
|
|
783
|
+
key_l = []
|
|
784
|
+
for dict_ in l_:
|
|
785
|
+
key_l.append(dict_[key_])
|
|
786
|
+
dict_out[key_] = key_l
|
|
787
|
+
return dict_out
|
|
788
|
+
|
|
789
|
+
def read_ML_performances(
|
|
790
|
+
obj_read, obj_write, set_inner_CV_res: bool = False
|
|
791
|
+
):
|
|
792
|
+
def set_score(
|
|
793
|
+
key_set: str = "",
|
|
794
|
+
key_get: str = "",
|
|
795
|
+
take_mean: bool = True,
|
|
796
|
+
val=None,
|
|
797
|
+
):
|
|
798
|
+
if set_inner_CV_res:
|
|
799
|
+
key_set = "InnerCV_" + key_set
|
|
800
|
+
key_get = "InnerCV_" + key_get
|
|
801
|
+
if take_mean:
|
|
802
|
+
val = np.mean(obj_read[key_get])
|
|
803
|
+
obj_write[key_set] = val
|
|
804
|
+
|
|
805
|
+
set_score(
|
|
806
|
+
key_set="performance_test",
|
|
807
|
+
key_get="score_test",
|
|
808
|
+
take_mean=True,
|
|
809
|
+
)
|
|
810
|
+
set_score(
|
|
811
|
+
key_set="performance_train",
|
|
812
|
+
key_get="score_train",
|
|
813
|
+
take_mean=True,
|
|
814
|
+
)
|
|
815
|
+
|
|
816
|
+
if "coef" in obj_read:
|
|
817
|
+
set_score(
|
|
818
|
+
key_set="coef",
|
|
819
|
+
key_get="coef",
|
|
820
|
+
take_mean=False,
|
|
821
|
+
val=np.concatenate(obj_read["coef"]),
|
|
822
|
+
)
|
|
823
|
+
|
|
824
|
+
if read_mov_detection_rates:
|
|
825
|
+
set_score(
|
|
826
|
+
key_set="mov_detection_rates_test",
|
|
827
|
+
key_get="mov_detection_rates_test",
|
|
828
|
+
take_mean=True,
|
|
829
|
+
)
|
|
830
|
+
set_score(
|
|
831
|
+
key_set="mov_detection_rates_train",
|
|
832
|
+
key_get="mov_detection_rates_train",
|
|
833
|
+
take_mean=True,
|
|
834
|
+
)
|
|
835
|
+
set_score(
|
|
836
|
+
key_set="fprate_test",
|
|
837
|
+
key_get="fprate_test",
|
|
838
|
+
take_mean=True,
|
|
839
|
+
)
|
|
840
|
+
set_score(
|
|
841
|
+
key_set="fprate_train",
|
|
842
|
+
key_get="fprate_train",
|
|
843
|
+
take_mean=True,
|
|
844
|
+
)
|
|
845
|
+
set_score(
|
|
846
|
+
key_set="tprate_test",
|
|
847
|
+
key_get="tprate_test",
|
|
848
|
+
take_mean=True,
|
|
849
|
+
)
|
|
850
|
+
set_score(
|
|
851
|
+
key_set="tprate_train",
|
|
852
|
+
key_get="tprate_train",
|
|
853
|
+
take_mean=True,
|
|
854
|
+
)
|
|
855
|
+
|
|
856
|
+
if read_bay_opt_params:
|
|
857
|
+
# transform dict into keys for json saving
|
|
858
|
+
dict_to_save = transform_list_of_dicts_into_dict_of_lists(
|
|
859
|
+
obj_read["best_bay_opt_params"]
|
|
860
|
+
)
|
|
861
|
+
set_score(
|
|
862
|
+
key_set="bay_opt_best_params",
|
|
863
|
+
take_mean=False,
|
|
864
|
+
val=dict_to_save,
|
|
865
|
+
)
|
|
866
|
+
|
|
867
|
+
if read_mrmr:
|
|
868
|
+
# transform dict into keys for json saving
|
|
869
|
+
|
|
870
|
+
set_score(
|
|
871
|
+
key_set="mrmr_select",
|
|
872
|
+
take_mean=False,
|
|
873
|
+
val=obj_read["mrmr_select"],
|
|
874
|
+
)
|
|
875
|
+
if model_save:
|
|
876
|
+
set_score(
|
|
877
|
+
key_set="model_save",
|
|
878
|
+
take_mean=False,
|
|
879
|
+
val=obj_read["model_save"],
|
|
880
|
+
)
|
|
881
|
+
|
|
882
|
+
read_ML_performances(obj_read, obj_write)
|
|
883
|
+
|
|
884
|
+
if len([key_ for key_ in obj_read.keys() if "InnerCV_" in key_]) > 0:
|
|
885
|
+
read_ML_performances(obj_read, obj_write, set_inner_CV_res=True)
|
|
886
|
+
|
|
887
|
+
if read_channels:
|
|
888
|
+
ch_to_use = self.ch_names_ECOG
|
|
889
|
+
ch_to_use = self.decoder.used_chs
|
|
890
|
+
for ch in ch_to_use:
|
|
891
|
+
performance_dict[subject_name][ch] = {}
|
|
892
|
+
|
|
893
|
+
if "coords" in self.sidecar:
|
|
894
|
+
if len(self.sidecar["coords"]) > 0: # check if coords are empty
|
|
895
|
+
coords_exist = False
|
|
896
|
+
for cortex_loc in self.sidecar["coords"].keys():
|
|
897
|
+
for ch_name_coord_idx, ch_name_coord in enumerate(
|
|
898
|
+
self.sidecar["coords"][cortex_loc]["ch_names"]
|
|
899
|
+
):
|
|
900
|
+
if ch.startswith(ch_name_coord):
|
|
901
|
+
coords = self.sidecar["coords"][cortex_loc][
|
|
902
|
+
"positions"
|
|
903
|
+
][ch_name_coord_idx]
|
|
904
|
+
coords_exist = (
|
|
905
|
+
True # optimally break out of the two loops...
|
|
906
|
+
)
|
|
907
|
+
if not coords_exist:
|
|
908
|
+
coords = None
|
|
909
|
+
performance_dict[subject_name][ch]["coord"] = coords
|
|
910
|
+
write_CV_res_in_performance_dict(
|
|
911
|
+
ML_res.ch_ind_results[ch],
|
|
912
|
+
performance_dict[subject_name][ch],
|
|
913
|
+
read_mov_detection_rates=read_mov_detection_rates,
|
|
914
|
+
read_bay_opt_params=read_bay_opt_params,
|
|
915
|
+
)
|
|
916
|
+
|
|
917
|
+
if read_all_combined:
|
|
918
|
+
performance_dict[subject_name]["all_ch_combined"] = {}
|
|
919
|
+
write_CV_res_in_performance_dict(
|
|
920
|
+
ML_res.all_ch_results,
|
|
921
|
+
performance_dict[subject_name]["all_ch_combined"],
|
|
922
|
+
read_mov_detection_rates=read_mov_detection_rates,
|
|
923
|
+
read_bay_opt_params=read_bay_opt_params,
|
|
924
|
+
)
|
|
925
|
+
|
|
926
|
+
if read_grid_points:
|
|
927
|
+
performance_dict[subject_name]["active_gridpoints"] = (
|
|
928
|
+
ML_res.active_gridpoints
|
|
929
|
+
)
|
|
930
|
+
|
|
931
|
+
for project_settings, grid_type in zip(
|
|
932
|
+
["project_cortex", "project_subcortex"],
|
|
933
|
+
["gridcortex_", "gridsubcortex_"],
|
|
934
|
+
):
|
|
935
|
+
if not self.settings.postprocessing[project_settings]:
|
|
936
|
+
continue
|
|
937
|
+
|
|
938
|
+
# the sidecar keys are grid_cortex and subcortex_grid
|
|
939
|
+
for grid_point in range(
|
|
940
|
+
len(self.sidecar["grid_" + project_settings.split("_")[1]])
|
|
941
|
+
):
|
|
942
|
+
gp_str = grid_type + str(grid_point)
|
|
943
|
+
|
|
944
|
+
performance_dict[subject_name][gp_str] = {}
|
|
945
|
+
performance_dict[subject_name][gp_str]["coord"] = self.sidecar[
|
|
946
|
+
"grid_" + project_settings.split("_")[1]
|
|
947
|
+
][grid_point]
|
|
948
|
+
|
|
949
|
+
if gp_str in ML_res.active_gridpoints:
|
|
950
|
+
write_CV_res_in_performance_dict(
|
|
951
|
+
ML_res.gridpoint_ind_results[gp_str],
|
|
952
|
+
performance_dict[subject_name][gp_str],
|
|
953
|
+
read_mov_detection_rates=read_mov_detection_rates,
|
|
954
|
+
read_bay_opt_params=read_bay_opt_params,
|
|
955
|
+
)
|
|
956
|
+
else:
|
|
957
|
+
# set non interpolated grid point to default performance
|
|
958
|
+
performance_dict[subject_name][gp_str]["performance_test"] = (
|
|
959
|
+
DEFAULT_PERFORMANCE
|
|
960
|
+
)
|
|
961
|
+
performance_dict[subject_name][gp_str]["performance_train"] = (
|
|
962
|
+
DEFAULT_PERFORMANCE
|
|
963
|
+
)
|
|
964
|
+
|
|
965
|
+
if save_results:
|
|
966
|
+
nm_IO.save_general_dict(
|
|
967
|
+
dict_=performance_dict,
|
|
968
|
+
path_out=PATH_OUT,
|
|
969
|
+
prefix=folder_name,
|
|
970
|
+
str_add=str_add,
|
|
971
|
+
)
|
|
972
|
+
return performance_dict
|
|
973
|
+
|
|
974
|
+
@staticmethod
|
|
975
|
+
def get_dataframe_performances(p: dict) -> "pd.DataFrame":
|
|
976
|
+
performances = []
|
|
977
|
+
for sub in p.keys():
|
|
978
|
+
for ch in p[sub].keys():
|
|
979
|
+
if "active_gridpoints" in ch:
|
|
980
|
+
continue
|
|
981
|
+
dict_add = p[sub][ch].copy()
|
|
982
|
+
dict_add["sub"] = sub
|
|
983
|
+
dict_add["ch"] = ch
|
|
984
|
+
|
|
985
|
+
if "all_ch_" in ch:
|
|
986
|
+
dict_add["ch_type"] = "all ch combinded"
|
|
987
|
+
elif "gridcortex" in ch:
|
|
988
|
+
dict_add["ch_type"] = "cortex grid"
|
|
989
|
+
else:
|
|
990
|
+
dict_add["ch_type"] = "electrode ch"
|
|
991
|
+
performances.append(dict_add)
|
|
992
|
+
|
|
993
|
+
return pd.DataFrame(performances)
|