py-neuromodulation 0.0.3__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/Automated Anatomical Labeling 3 (Rolls 2020).nii +0 -0
- py_neuromodulation/ConnectivityDecoding/_get_grid_hull.m +34 -0
- py_neuromodulation/ConnectivityDecoding/_get_grid_whole_brain.py +95 -0
- py_neuromodulation/ConnectivityDecoding/_helper_write_connectome.py +107 -0
- py_neuromodulation/ConnectivityDecoding/mni_coords_cortical_surface.mat +0 -0
- py_neuromodulation/ConnectivityDecoding/mni_coords_whole_brain.mat +0 -0
- py_neuromodulation/ConnectivityDecoding/rmap_func_all.nii +0 -0
- py_neuromodulation/ConnectivityDecoding/rmap_struc.nii +0 -0
- 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 -0
- py_neuromodulation/data/dataset_description.json +8 -0
- py_neuromodulation/data/participants.json +32 -0
- py_neuromodulation/data/participants.tsv +2 -0
- py_neuromodulation/data/sub-testsub/ses-EphysMedOff/ieeg/sub-testsub_ses-EphysMedOff_space-mni_coordsystem.json +5 -0
- py_neuromodulation/data/sub-testsub/ses-EphysMedOff/ieeg/sub-testsub_ses-EphysMedOff_space-mni_electrodes.tsv +11 -0
- py_neuromodulation/data/sub-testsub/ses-EphysMedOff/ieeg/sub-testsub_ses-EphysMedOff_task-gripforce_run-0_channels.tsv +11 -0
- py_neuromodulation/data/sub-testsub/ses-EphysMedOff/ieeg/sub-testsub_ses-EphysMedOff_task-gripforce_run-0_ieeg.eeg +0 -0
- py_neuromodulation/data/sub-testsub/ses-EphysMedOff/ieeg/sub-testsub_ses-EphysMedOff_task-gripforce_run-0_ieeg.json +18 -0
- py_neuromodulation/data/sub-testsub/ses-EphysMedOff/ieeg/sub-testsub_ses-EphysMedOff_task-gripforce_run-0_ieeg.vhdr +35 -0
- py_neuromodulation/data/sub-testsub/ses-EphysMedOff/ieeg/sub-testsub_ses-EphysMedOff_task-gripforce_run-0_ieeg.vmrk +13 -0
- py_neuromodulation/data/sub-testsub/ses-EphysMedOff/sub-testsub_ses-EphysMedOff_scans.tsv +2 -0
- py_neuromodulation/grid_cortex.tsv +40 -0
- py_neuromodulation/grid_subcortex.tsv +1429 -0
- 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/plots/STN_surf.mat +0 -0
- py_neuromodulation/plots/Vertices.mat +0 -0
- py_neuromodulation/plots/faces.mat +0 -0
- py_neuromodulation/plots/grid.mat +0 -0
- {py_neuromodulation-0.0.3.dist-info → py_neuromodulation-0.0.5.dist-info}/METADATA +185 -182
- py_neuromodulation-0.0.5.dist-info/RECORD +83 -0
- {py_neuromodulation-0.0.3.dist-info → py_neuromodulation-0.0.5.dist-info}/WHEEL +1 -2
- {py_neuromodulation-0.0.3.dist-info → py_neuromodulation-0.0.5.dist-info/licenses}/LICENSE +21 -21
- docs/build/_downloads/09df217f95985497f45d69e2d4bdc5b1/plot_2_example_add_feature.py +0 -68
- docs/build/_downloads/3b4900a2b2818ff30362215b76f7d5eb/plot_1_example_BIDS.py +0 -233
- docs/build/_downloads/7e92dd2e6cc86b239d14cafad972ae4f/plot_3_example_sharpwave_analysis.py +0 -219
- docs/build/_downloads/c2db0bf2b334d541b00662b991682256/plot_6_real_time_demo.py +0 -97
- docs/build/_downloads/ce3914826f782cbd1ea8fd024eaf0ac3/plot_5_example_rmap_computing.py +0 -64
- docs/build/_downloads/da36848a41e6a3235d91fb7cfb6d59b4/plot_0_first_demo.py +0 -192
- docs/build/_downloads/eaa4305c75b19a1e2eea941f742a6331/plot_4_example_gridPointProjection.py +0 -210
- docs/build/html/_downloads/09df217f95985497f45d69e2d4bdc5b1/plot_2_example_add_feature.py +0 -68
- docs/build/html/_downloads/3b4900a2b2818ff30362215b76f7d5eb/plot_1_example_BIDS.py +0 -239
- docs/build/html/_downloads/7e92dd2e6cc86b239d14cafad972ae4f/plot_3_example_sharpwave_analysis.py +0 -219
- docs/build/html/_downloads/c2db0bf2b334d541b00662b991682256/plot_6_real_time_demo.py +0 -97
- docs/build/html/_downloads/ce3914826f782cbd1ea8fd024eaf0ac3/plot_5_example_rmap_computing.py +0 -64
- docs/build/html/_downloads/da36848a41e6a3235d91fb7cfb6d59b4/plot_0_first_demo.py +0 -192
- docs/build/html/_downloads/eaa4305c75b19a1e2eea941f742a6331/plot_4_example_gridPointProjection.py +0 -210
- docs/source/_build/html/_downloads/09df217f95985497f45d69e2d4bdc5b1/plot_2_example_add_feature.py +0 -76
- docs/source/_build/html/_downloads/0d0d0a76e8f648d5d3cbc47da6351932/plot_real_time_demo.py +0 -97
- docs/source/_build/html/_downloads/3b4900a2b2818ff30362215b76f7d5eb/plot_1_example_BIDS.py +0 -240
- docs/source/_build/html/_downloads/5d73cadc59a8805c47e3b84063afc157/plot_example_BIDS.py +0 -233
- docs/source/_build/html/_downloads/7660317fa5a6bfbd12fcca9961457fc4/plot_example_rmap_computing.py +0 -63
- docs/source/_build/html/_downloads/7e92dd2e6cc86b239d14cafad972ae4f/plot_3_example_sharpwave_analysis.py +0 -219
- docs/source/_build/html/_downloads/839e5b319379f7fd9e867deb00fd797f/plot_example_gridPointProjection.py +0 -210
- docs/source/_build/html/_downloads/ae8be19afe5e559f011fc9b138968ba0/plot_first_demo.py +0 -192
- docs/source/_build/html/_downloads/b8b06cacc17969d3725a0b6f1d7741c5/plot_example_sharpwave_analysis.py +0 -219
- docs/source/_build/html/_downloads/c2db0bf2b334d541b00662b991682256/plot_6_real_time_demo.py +0 -121
- docs/source/_build/html/_downloads/c31a86c0b68cb4167d968091ace8080d/plot_example_add_feature.py +0 -68
- docs/source/_build/html/_downloads/ce3914826f782cbd1ea8fd024eaf0ac3/plot_5_example_rmap_computing.py +0 -64
- docs/source/_build/html/_downloads/da36848a41e6a3235d91fb7cfb6d59b4/plot_0_first_demo.py +0 -189
- docs/source/_build/html/_downloads/eaa4305c75b19a1e2eea941f742a6331/plot_4_example_gridPointProjection.py +0 -210
- docs/source/auto_examples/plot_0_first_demo.py +0 -189
- docs/source/auto_examples/plot_1_example_BIDS.py +0 -240
- docs/source/auto_examples/plot_2_example_add_feature.py +0 -76
- docs/source/auto_examples/plot_3_example_sharpwave_analysis.py +0 -219
- docs/source/auto_examples/plot_4_example_gridPointProjection.py +0 -210
- docs/source/auto_examples/plot_5_example_rmap_computing.py +0 -64
- docs/source/auto_examples/plot_6_real_time_demo.py +0 -121
- docs/source/conf.py +0 -105
- examples/plot_0_first_demo.py +0 -189
- examples/plot_1_example_BIDS.py +0 -240
- examples/plot_2_example_add_feature.py +0 -76
- examples/plot_3_example_sharpwave_analysis.py +0 -219
- examples/plot_4_example_gridPointProjection.py +0 -210
- examples/plot_5_example_rmap_computing.py +0 -64
- examples/plot_6_real_time_demo.py +0 -121
- packages/realtime_decoding/build/lib/realtime_decoding/__init__.py +0 -4
- packages/realtime_decoding/build/lib/realtime_decoding/decoder.py +0 -104
- packages/realtime_decoding/build/lib/realtime_decoding/features.py +0 -163
- packages/realtime_decoding/build/lib/realtime_decoding/helpers.py +0 -15
- packages/realtime_decoding/build/lib/realtime_decoding/run_decoding.py +0 -345
- packages/realtime_decoding/build/lib/realtime_decoding/trainer.py +0 -54
- packages/tmsi/build/lib/TMSiFileFormats/__init__.py +0 -37
- packages/tmsi/build/lib/TMSiFileFormats/file_formats/__init__.py +0 -36
- packages/tmsi/build/lib/TMSiFileFormats/file_formats/lsl_stream_writer.py +0 -200
- packages/tmsi/build/lib/TMSiFileFormats/file_formats/poly5_file_writer.py +0 -496
- packages/tmsi/build/lib/TMSiFileFormats/file_formats/poly5_to_edf_converter.py +0 -236
- packages/tmsi/build/lib/TMSiFileFormats/file_formats/xdf_file_writer.py +0 -977
- packages/tmsi/build/lib/TMSiFileFormats/file_readers/__init__.py +0 -35
- packages/tmsi/build/lib/TMSiFileFormats/file_readers/edf_reader.py +0 -116
- packages/tmsi/build/lib/TMSiFileFormats/file_readers/poly5reader.py +0 -294
- packages/tmsi/build/lib/TMSiFileFormats/file_readers/xdf_reader.py +0 -229
- packages/tmsi/build/lib/TMSiFileFormats/file_writer.py +0 -102
- packages/tmsi/build/lib/TMSiPlotters/__init__.py +0 -2
- packages/tmsi/build/lib/TMSiPlotters/gui/__init__.py +0 -39
- packages/tmsi/build/lib/TMSiPlotters/gui/_plotter_gui.py +0 -234
- packages/tmsi/build/lib/TMSiPlotters/gui/plotting_gui.py +0 -440
- packages/tmsi/build/lib/TMSiPlotters/plotters/__init__.py +0 -44
- packages/tmsi/build/lib/TMSiPlotters/plotters/hd_emg_plotter.py +0 -446
- packages/tmsi/build/lib/TMSiPlotters/plotters/impedance_plotter.py +0 -589
- packages/tmsi/build/lib/TMSiPlotters/plotters/signal_plotter.py +0 -1326
- packages/tmsi/build/lib/TMSiSDK/__init__.py +0 -54
- packages/tmsi/build/lib/TMSiSDK/device.py +0 -588
- packages/tmsi/build/lib/TMSiSDK/devices/__init__.py +0 -34
- packages/tmsi/build/lib/TMSiSDK/devices/saga/TMSi_Device_API.py +0 -1764
- packages/tmsi/build/lib/TMSiSDK/devices/saga/__init__.py +0 -34
- packages/tmsi/build/lib/TMSiSDK/devices/saga/saga_device.py +0 -1366
- packages/tmsi/build/lib/TMSiSDK/devices/saga/saga_types.py +0 -520
- packages/tmsi/build/lib/TMSiSDK/devices/saga/xml_saga_config.py +0 -165
- packages/tmsi/build/lib/TMSiSDK/error.py +0 -95
- packages/tmsi/build/lib/TMSiSDK/sample_data.py +0 -63
- packages/tmsi/build/lib/TMSiSDK/sample_data_server.py +0 -99
- packages/tmsi/build/lib/TMSiSDK/settings.py +0 -45
- packages/tmsi/build/lib/TMSiSDK/tmsi_device.py +0 -111
- packages/tmsi/build/lib/__init__.py +0 -4
- packages/tmsi/build/lib/apex_sdk/__init__.py +0 -34
- packages/tmsi/build/lib/apex_sdk/device/__init__.py +0 -41
- packages/tmsi/build/lib/apex_sdk/device/devices/apex/apex_API.py +0 -1009
- packages/tmsi/build/lib/apex_sdk/device/devices/apex/apex_API_enums.py +0 -239
- packages/tmsi/build/lib/apex_sdk/device/devices/apex/apex_API_structures.py +0 -668
- packages/tmsi/build/lib/apex_sdk/device/devices/apex/apex_device.py +0 -1611
- packages/tmsi/build/lib/apex_sdk/device/devices/apex/apex_dongle.py +0 -38
- packages/tmsi/build/lib/apex_sdk/device/devices/apex/apex_event_reader.py +0 -57
- packages/tmsi/build/lib/apex_sdk/device/devices/apex/apex_structures/apex_channel.py +0 -44
- packages/tmsi/build/lib/apex_sdk/device/devices/apex/apex_structures/apex_config.py +0 -150
- packages/tmsi/build/lib/apex_sdk/device/devices/apex/apex_structures/apex_const.py +0 -36
- packages/tmsi/build/lib/apex_sdk/device/devices/apex/apex_structures/apex_impedance_channel.py +0 -48
- packages/tmsi/build/lib/apex_sdk/device/devices/apex/apex_structures/apex_info.py +0 -108
- packages/tmsi/build/lib/apex_sdk/device/devices/apex/apex_structures/dongle_info.py +0 -39
- packages/tmsi/build/lib/apex_sdk/device/devices/apex/measurements/download_measurement.py +0 -77
- packages/tmsi/build/lib/apex_sdk/device/devices/apex/measurements/eeg_measurement.py +0 -150
- packages/tmsi/build/lib/apex_sdk/device/devices/apex/measurements/impedance_measurement.py +0 -129
- packages/tmsi/build/lib/apex_sdk/device/threads/conversion_thread.py +0 -59
- packages/tmsi/build/lib/apex_sdk/device/threads/sampling_thread.py +0 -57
- packages/tmsi/build/lib/apex_sdk/device/tmsi_channel.py +0 -83
- packages/tmsi/build/lib/apex_sdk/device/tmsi_device.py +0 -201
- packages/tmsi/build/lib/apex_sdk/device/tmsi_device_enums.py +0 -103
- packages/tmsi/build/lib/apex_sdk/device/tmsi_dongle.py +0 -43
- packages/tmsi/build/lib/apex_sdk/device/tmsi_event_reader.py +0 -50
- packages/tmsi/build/lib/apex_sdk/device/tmsi_measurement.py +0 -118
- packages/tmsi/build/lib/apex_sdk/sample_data_server/__init__.py +0 -33
- packages/tmsi/build/lib/apex_sdk/sample_data_server/event_data.py +0 -44
- packages/tmsi/build/lib/apex_sdk/sample_data_server/sample_data.py +0 -50
- packages/tmsi/build/lib/apex_sdk/sample_data_server/sample_data_server.py +0 -136
- packages/tmsi/build/lib/apex_sdk/tmsi_errors/error.py +0 -126
- packages/tmsi/build/lib/apex_sdk/tmsi_sdk.py +0 -113
- packages/tmsi/build/lib/apex_sdk/tmsi_utilities/apex/apex_structure_generator.py +0 -134
- packages/tmsi/build/lib/apex_sdk/tmsi_utilities/decorators.py +0 -60
- packages/tmsi/build/lib/apex_sdk/tmsi_utilities/logger_filter.py +0 -42
- packages/tmsi/build/lib/apex_sdk/tmsi_utilities/singleton.py +0 -42
- packages/tmsi/build/lib/apex_sdk/tmsi_utilities/support_functions.py +0 -72
- packages/tmsi/build/lib/apex_sdk/tmsi_utilities/tmsi_logger.py +0 -98
- 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_stream_offline.py +0 -358
- py_neuromodulation/utils/_logging.py +0 -24
- py_neuromodulation-0.0.3.dist-info/RECORD +0 -188
- py_neuromodulation-0.0.3.dist-info/top_level.txt +0 -5
- tests/__init__.py +0 -0
- tests/conftest.py +0 -117
- tests/test_all_examples.py +0 -10
- tests/test_all_features.py +0 -63
- tests/test_bispectra.py +0 -70
- tests/test_bursts.py +0 -105
- tests/test_feature_sampling_rates.py +0 -143
- tests/test_fooof.py +0 -16
- tests/test_initalization_offline_stream.py +0 -41
- tests/test_multiprocessing.py +0 -58
- tests/test_nan_values.py +0 -29
- tests/test_nm_filter.py +0 -95
- tests/test_nm_resample.py +0 -63
- tests/test_normalization_settings.py +0 -146
- tests/test_notch_filter.py +0 -31
- tests/test_osc_features.py +0 -424
- tests/test_preprocessing_filter.py +0 -151
- tests/test_rereference.py +0 -171
- tests/test_sampling.py +0 -57
- tests/test_settings_change_after_init.py +0 -76
- tests/test_sharpwave.py +0 -165
- tests/test_target_channel_add.py +0 -100
- tests/test_timing.py +0 -80
|
@@ -1,401 +1,423 @@
|
|
|
1
|
-
|
|
2
|
-
from
|
|
3
|
-
from
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
from
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
if self.
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
if self.
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
if self.
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
if self.sw_settings
|
|
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
|
-
|
|
1
|
+
from collections.abc import Sequence
|
|
2
|
+
from collections import defaultdict
|
|
3
|
+
from itertools import product
|
|
4
|
+
|
|
5
|
+
from py_neuromodulation.nm_types import NMBaseModel
|
|
6
|
+
from pydantic import model_validator
|
|
7
|
+
from typing import TYPE_CHECKING, Any, Callable
|
|
8
|
+
|
|
9
|
+
import numpy as np
|
|
10
|
+
|
|
11
|
+
if np.__version__ >= "2.0.0":
|
|
12
|
+
from numpy._core._methods import _mean as np_mean # type: ignore
|
|
13
|
+
else:
|
|
14
|
+
from numpy.core._methods import _mean as np_mean
|
|
15
|
+
|
|
16
|
+
from py_neuromodulation.nm_features import NMFeature
|
|
17
|
+
from py_neuromodulation.nm_types import BoolSelector, FrequencyRange
|
|
18
|
+
|
|
19
|
+
if TYPE_CHECKING:
|
|
20
|
+
from py_neuromodulation.nm_settings import NMSettings
|
|
21
|
+
|
|
22
|
+
# Using low-level numpy mean function for performance, could do the same for the other estimators
|
|
23
|
+
ESTIMATOR_DICT = {
|
|
24
|
+
"mean": np_mean,
|
|
25
|
+
"median": np.median,
|
|
26
|
+
"max": np.max,
|
|
27
|
+
"min": np.min,
|
|
28
|
+
"var": np.var,
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class PeakDetectionSettings(NMBaseModel):
|
|
33
|
+
estimate: bool = True
|
|
34
|
+
distance_troughs_ms: float = 10
|
|
35
|
+
distance_peaks_ms: float = 5
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class SharpwaveFeatures(BoolSelector):
|
|
39
|
+
peak_left: bool = False
|
|
40
|
+
peak_right: bool = False
|
|
41
|
+
trough: bool = False
|
|
42
|
+
width: bool = False
|
|
43
|
+
prominence: bool = True
|
|
44
|
+
interval: bool = True
|
|
45
|
+
decay_time: bool = False
|
|
46
|
+
rise_time: bool = False
|
|
47
|
+
sharpness: bool = True
|
|
48
|
+
rise_steepness: bool = False
|
|
49
|
+
decay_steepness: bool = False
|
|
50
|
+
slope_ratio: bool = False
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class SharpwaveEstimators(NMBaseModel):
|
|
54
|
+
mean: list[str] = ["interval"]
|
|
55
|
+
median: list[str] = []
|
|
56
|
+
max: list[str] = ["prominence", "sharpness"]
|
|
57
|
+
min: list[str] = []
|
|
58
|
+
var: list[str] = []
|
|
59
|
+
|
|
60
|
+
def keys(self):
|
|
61
|
+
return ["mean", "median", "max", "min", "var"]
|
|
62
|
+
|
|
63
|
+
def values(self):
|
|
64
|
+
return [self.mean, self.median, self.max, self.min, self.var]
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class SharpwaveSettings(NMBaseModel):
|
|
68
|
+
sharpwave_features: SharpwaveFeatures = SharpwaveFeatures()
|
|
69
|
+
filter_ranges_hz: list[FrequencyRange] = [
|
|
70
|
+
FrequencyRange(5, 80),
|
|
71
|
+
FrequencyRange(5, 30),
|
|
72
|
+
]
|
|
73
|
+
detect_troughs: PeakDetectionSettings = PeakDetectionSettings()
|
|
74
|
+
detect_peaks: PeakDetectionSettings = PeakDetectionSettings()
|
|
75
|
+
estimator: SharpwaveEstimators = SharpwaveEstimators()
|
|
76
|
+
apply_estimator_between_peaks_and_troughs: bool = True
|
|
77
|
+
|
|
78
|
+
def disable_all_features(self):
|
|
79
|
+
self.sharpwave_features.disable_all()
|
|
80
|
+
for est in self.estimator.keys():
|
|
81
|
+
self.estimator[est] = []
|
|
82
|
+
|
|
83
|
+
@model_validator(mode="after")
|
|
84
|
+
def test_settings(cls, settings):
|
|
85
|
+
# check if all features are also enabled via an estimator
|
|
86
|
+
estimator_list = [est for list_ in settings.estimator.values() for est in list_]
|
|
87
|
+
|
|
88
|
+
for used_feature in settings.sharpwave_features.get_enabled():
|
|
89
|
+
assert (
|
|
90
|
+
used_feature in estimator_list
|
|
91
|
+
), f"Add estimator key for {used_feature}"
|
|
92
|
+
|
|
93
|
+
return settings
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class SharpwaveAnalyzer(NMFeature):
|
|
97
|
+
def __init__(
|
|
98
|
+
self, settings: "NMSettings", ch_names: Sequence[str], sfreq: float
|
|
99
|
+
) -> None:
|
|
100
|
+
self.sw_settings = settings.sharpwave_analysis_settings
|
|
101
|
+
self.sfreq = sfreq
|
|
102
|
+
self.ch_names = ch_names
|
|
103
|
+
self.list_filter: list[tuple[str, Any]] = []
|
|
104
|
+
self.trough: list = []
|
|
105
|
+
self.troughs_idx: list = []
|
|
106
|
+
|
|
107
|
+
settings.validate()
|
|
108
|
+
|
|
109
|
+
# FrequencyRange's are already ensured to have high > low
|
|
110
|
+
# Test that the higher frequency is smaller than the sampling frequency
|
|
111
|
+
for filter_range in settings.sharpwave_analysis_settings.filter_ranges_hz:
|
|
112
|
+
assert filter_range[1] < sfreq, (
|
|
113
|
+
"Filter range has to be smaller than sfreq, "
|
|
114
|
+
f"got sfreq {sfreq} and filter range {filter_range}"
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
for filter_range in settings.sharpwave_analysis_settings.filter_ranges_hz:
|
|
118
|
+
# Test settings
|
|
119
|
+
# TODO: handle None values
|
|
120
|
+
if filter_range[0] is None:
|
|
121
|
+
self.list_filter.append(("no_filter", None))
|
|
122
|
+
else:
|
|
123
|
+
from mne.filter import create_filter
|
|
124
|
+
|
|
125
|
+
self.list_filter.append(
|
|
126
|
+
(
|
|
127
|
+
f"range_{filter_range[0]:.0f}_{filter_range[1]:.0f}",
|
|
128
|
+
create_filter(
|
|
129
|
+
None,
|
|
130
|
+
sfreq,
|
|
131
|
+
l_freq=filter_range[0],
|
|
132
|
+
h_freq=filter_range[1],
|
|
133
|
+
fir_design="firwin",
|
|
134
|
+
# l_trans_bandwidth=None,
|
|
135
|
+
# h_trans_bandwidth=None,
|
|
136
|
+
# filter_length=str(sfreq) + "ms",
|
|
137
|
+
verbose=False,
|
|
138
|
+
),
|
|
139
|
+
)
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
self.filter_names = [name for name, _ in self.list_filter]
|
|
143
|
+
self.filters = np.vstack([filter for _, filter in self.list_filter])
|
|
144
|
+
self.filters = np.tile(self.filters[None, :, :], (len(self.ch_names), 1, 1))
|
|
145
|
+
|
|
146
|
+
self.used_features = self.sw_settings.sharpwave_features.get_enabled()
|
|
147
|
+
|
|
148
|
+
# initializing estimator functions, respecitive for all sharpwave features
|
|
149
|
+
self.estimator_dict: dict[str, dict[str, Callable]] = {
|
|
150
|
+
feat: {
|
|
151
|
+
est: ESTIMATOR_DICT[est]
|
|
152
|
+
for est in self.sw_settings.estimator.keys()
|
|
153
|
+
if feat in self.sw_settings.estimator[est]
|
|
154
|
+
}
|
|
155
|
+
for feat_list in self.sw_settings.estimator.values()
|
|
156
|
+
for feat in feat_list
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
estimator_combinations = [
|
|
160
|
+
(feature_name, estimator_name, estimator)
|
|
161
|
+
for feature_name in self.used_features
|
|
162
|
+
for estimator_name, estimator in self.estimator_dict[feature_name].items()
|
|
163
|
+
]
|
|
164
|
+
|
|
165
|
+
filter_combinations = list(
|
|
166
|
+
product(
|
|
167
|
+
enumerate(self.ch_names), enumerate(self.filter_names), [False, True]
|
|
168
|
+
)
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
self.estimator_key_map: dict[str, Callable] = {}
|
|
172
|
+
self.combinations = []
|
|
173
|
+
for (ch_idx, ch_name), (
|
|
174
|
+
filter_idx,
|
|
175
|
+
filter_name,
|
|
176
|
+
), detect_troughs in filter_combinations:
|
|
177
|
+
for feature_name, estimator_name, estimator in estimator_combinations:
|
|
178
|
+
key_name = f"{ch_name}_Sharpwave_{estimator_name.title()}_{feature_name}_{filter_name}"
|
|
179
|
+
self.estimator_key_map[key_name] = estimator
|
|
180
|
+
self.combinations.append(
|
|
181
|
+
(
|
|
182
|
+
(ch_idx, ch_name),
|
|
183
|
+
(filter_idx, filter_name),
|
|
184
|
+
detect_troughs,
|
|
185
|
+
estimator_combinations,
|
|
186
|
+
)
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
# Check required feature computations according to settings
|
|
190
|
+
self.need_peak_left = (
|
|
191
|
+
self.sw_settings.sharpwave_features.peak_left
|
|
192
|
+
or self.sw_settings.sharpwave_features.prominence
|
|
193
|
+
)
|
|
194
|
+
self.need_peak_right = (
|
|
195
|
+
self.sw_settings.sharpwave_features.peak_right
|
|
196
|
+
or self.sw_settings.sharpwave_features.prominence
|
|
197
|
+
)
|
|
198
|
+
self.need_trough = (
|
|
199
|
+
self.sw_settings.sharpwave_features.trough
|
|
200
|
+
or self.sw_settings.sharpwave_features.prominence
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
self.need_decay_steepness = (
|
|
204
|
+
self.sw_settings.sharpwave_features.decay_steepness
|
|
205
|
+
or self.sw_settings.sharpwave_features.slope_ratio
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
self.need_rise_steepness = (
|
|
209
|
+
self.sw_settings.sharpwave_features.rise_steepness
|
|
210
|
+
or self.sw_settings.sharpwave_features.slope_ratio
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
self.need_steepness = self.need_rise_steepness or self.need_decay_steepness
|
|
214
|
+
|
|
215
|
+
def calc_feature(self, data: np.ndarray) -> dict:
|
|
216
|
+
"""Given a new data batch, the peaks, troughs and sharpwave features
|
|
217
|
+
are estimated. Importantly only new data is being analyzed here. In
|
|
218
|
+
steps of 1/settings["sampling_rate_features] analyzed and returned.
|
|
219
|
+
Pre-initialized filters are applied to each channel.
|
|
220
|
+
|
|
221
|
+
Parameters
|
|
222
|
+
----------
|
|
223
|
+
data (np.ndarray): 2d data array with shape [num_channels, samples]
|
|
224
|
+
feature_results (dict): Features.py estimated features
|
|
225
|
+
|
|
226
|
+
Returns
|
|
227
|
+
-------
|
|
228
|
+
feature_results (dict): set features for Features.py object
|
|
229
|
+
"""
|
|
230
|
+
dict_ch_features: dict[str, dict[str, float]] = defaultdict(lambda: {})
|
|
231
|
+
|
|
232
|
+
from scipy.signal import fftconvolve
|
|
233
|
+
|
|
234
|
+
data = np.tile(data[:, None, :], (1, len(self.list_filter), 1))
|
|
235
|
+
data = fftconvolve(data, self.filters, axes=2, mode="same")
|
|
236
|
+
|
|
237
|
+
self.filtered_data = (
|
|
238
|
+
data # TONI: Expose filtered data for example 3, need a better way
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
feature_results = {}
|
|
242
|
+
|
|
243
|
+
for (
|
|
244
|
+
(ch_idx, ch_name),
|
|
245
|
+
(filter_idx, filter_name),
|
|
246
|
+
detect_troughs,
|
|
247
|
+
estimator_combinations,
|
|
248
|
+
) in self.combinations:
|
|
249
|
+
sub_data = data[ch_idx, filter_idx, :]
|
|
250
|
+
|
|
251
|
+
key_name_pt = "Trough" if detect_troughs else "Peak"
|
|
252
|
+
|
|
253
|
+
if (not detect_troughs and not self.sw_settings.detect_peaks.estimate) or (
|
|
254
|
+
detect_troughs and not self.sw_settings.detect_troughs.estimate
|
|
255
|
+
):
|
|
256
|
+
continue
|
|
257
|
+
|
|
258
|
+
# the detect_troughs loop start with peaks, s.t. data does not need to be flipped
|
|
259
|
+
sub_data = -sub_data if detect_troughs else sub_data
|
|
260
|
+
# sub_data *= 1 - 2 * detect_troughs # branchless version
|
|
261
|
+
|
|
262
|
+
waveform_results = self.analyze_waveform(sub_data)
|
|
263
|
+
|
|
264
|
+
# for each feature take the respective fun.
|
|
265
|
+
for feature_name, estimator_name, estimator in estimator_combinations:
|
|
266
|
+
feature_data = waveform_results[feature_name]
|
|
267
|
+
key_name = f"{ch_name}_Sharpwave_{estimator_name.title()}_{feature_name}_{filter_name}"
|
|
268
|
+
|
|
269
|
+
# zero check because no peaks can be also detected
|
|
270
|
+
feature_data = estimator(feature_data) if len(feature_data) != 0 else 0
|
|
271
|
+
dict_ch_features[key_name][key_name_pt] = feature_data
|
|
272
|
+
|
|
273
|
+
if self.sw_settings.apply_estimator_between_peaks_and_troughs:
|
|
274
|
+
# apply between 'Trough' and 'Peak' the respective function again
|
|
275
|
+
# save only the 'est_fun' (e.g. max) between them
|
|
276
|
+
|
|
277
|
+
# the key_name stays, since the estimator function stays between peaks and troughs
|
|
278
|
+
for key_name, estimator in self.estimator_key_map.items():
|
|
279
|
+
feature_results[key_name] = estimator(
|
|
280
|
+
[
|
|
281
|
+
list(dict_ch_features[key_name].values())[0],
|
|
282
|
+
list(dict_ch_features[key_name].values())[1],
|
|
283
|
+
]
|
|
284
|
+
)
|
|
285
|
+
else:
|
|
286
|
+
# otherwise, save all write all "flattened" key value pairs in feature_results
|
|
287
|
+
for key, subdict in dict_ch_features.items():
|
|
288
|
+
for key_sub, value_sub in subdict.items():
|
|
289
|
+
feature_results[key + "_analyze_" + key_sub] = value_sub
|
|
290
|
+
|
|
291
|
+
return feature_results
|
|
292
|
+
|
|
293
|
+
def analyze_waveform(self, data) -> dict:
|
|
294
|
+
"""Given the scipy.signal.find_peaks trough/peak distance
|
|
295
|
+
settings specified sharpwave features are estimated.
|
|
296
|
+
"""
|
|
297
|
+
|
|
298
|
+
from scipy.signal import find_peaks
|
|
299
|
+
|
|
300
|
+
# TODO: find peaks is actually not that big a performance hit, but the rest
|
|
301
|
+
# of this function is. Perhaps find_peaks can be put in a loop and the rest optimized somehow?
|
|
302
|
+
peak_idx: np.ndarray = find_peaks(
|
|
303
|
+
data, distance=self.sw_settings.detect_troughs.distance_peaks_ms
|
|
304
|
+
)[0]
|
|
305
|
+
trough_idx: np.ndarray = find_peaks(
|
|
306
|
+
-data, distance=self.sw_settings.detect_troughs.distance_troughs_ms
|
|
307
|
+
)[0]
|
|
308
|
+
|
|
309
|
+
""" Find left and right peak indexes for each trough """
|
|
310
|
+
peak_pointer = first_valid = last_valid = 0
|
|
311
|
+
peak_idx_left_list: list[int] = []
|
|
312
|
+
peak_idx_right_list: list[int] = []
|
|
313
|
+
|
|
314
|
+
for i in range(len(trough_idx)):
|
|
315
|
+
# Locate peak right of current trough
|
|
316
|
+
while (
|
|
317
|
+
peak_pointer < peak_idx.size and peak_idx[peak_pointer] < trough_idx[i]
|
|
318
|
+
):
|
|
319
|
+
peak_pointer += 1
|
|
320
|
+
|
|
321
|
+
if peak_pointer - 1 < 0:
|
|
322
|
+
# If trough has no peak to it's left, it's not valid
|
|
323
|
+
first_valid = i + 1 # Try with next one
|
|
324
|
+
continue
|
|
325
|
+
|
|
326
|
+
if peak_pointer == peak_idx.size:
|
|
327
|
+
# If we went past the end of the peaks list, trough had no peak to its right
|
|
328
|
+
continue
|
|
329
|
+
|
|
330
|
+
last_valid = i
|
|
331
|
+
peak_idx_left_list.append(peak_idx[peak_pointer - 1])
|
|
332
|
+
peak_idx_right_list.append(peak_idx[peak_pointer])
|
|
333
|
+
|
|
334
|
+
# Remove non valid troughs and make array of left and right peaks for each trough
|
|
335
|
+
trough_idx = trough_idx[first_valid : last_valid + 1]
|
|
336
|
+
peak_idx_left = np.array(peak_idx_left_list, dtype=int)
|
|
337
|
+
peak_idx_right = np.array(peak_idx_right_list, dtype=int)
|
|
338
|
+
|
|
339
|
+
""" Calculate features (vectorized) """
|
|
340
|
+
results: dict = {}
|
|
341
|
+
|
|
342
|
+
if self.need_peak_left:
|
|
343
|
+
results["peak_left"] = data[peak_idx_left]
|
|
344
|
+
|
|
345
|
+
if self.need_peak_right:
|
|
346
|
+
results["peak_right"] = data[peak_idx_right]
|
|
347
|
+
|
|
348
|
+
if self.need_trough:
|
|
349
|
+
results["trough"] = data[trough_idx]
|
|
350
|
+
|
|
351
|
+
if self.sw_settings.sharpwave_features.interval:
|
|
352
|
+
results["interval"] = np.concatenate((np.zeros(1), np.diff(trough_idx))) * (
|
|
353
|
+
1000 / self.sfreq
|
|
354
|
+
)
|
|
355
|
+
|
|
356
|
+
if self.sw_settings.sharpwave_features.sharpness:
|
|
357
|
+
# sharpess is calculated on a +- 5 ms window
|
|
358
|
+
# valid troughs need 5 ms of margin on both sides
|
|
359
|
+
troughs_valid = trough_idx[
|
|
360
|
+
np.logical_and(
|
|
361
|
+
trough_idx - int(5 * (1000 / self.sfreq)) > 0,
|
|
362
|
+
trough_idx + int(5 * (1000 / self.sfreq)) < data.shape[0],
|
|
363
|
+
)
|
|
364
|
+
]
|
|
365
|
+
trough_height = data[troughs_valid]
|
|
366
|
+
left_height = data[troughs_valid - int(5 * (1000 / self.sfreq))]
|
|
367
|
+
right_height = data[troughs_valid + int(5 * (1000 / self.sfreq))]
|
|
368
|
+
# results["sharpness"] = ((trough_height - left_height) + (trough_height - right_height)) / 2
|
|
369
|
+
results["sharpness"] = trough_height - 0.5 * (left_height + right_height)
|
|
370
|
+
|
|
371
|
+
if self.need_steepness:
|
|
372
|
+
# steepness is calculated as the first derivative
|
|
373
|
+
steepness: np.ndarray = np.concatenate((np.zeros(1), np.diff(data)))
|
|
374
|
+
|
|
375
|
+
# Create an array with the rise and decay steepness for each trough
|
|
376
|
+
# 0th dimension for rise/decay, 1st for trough index, 2nd for timepoint
|
|
377
|
+
steepness_troughs = np.zeros((2, trough_idx.shape[0], steepness.shape[0]))
|
|
378
|
+
if self.need_rise_steepness or self.need_decay_steepness:
|
|
379
|
+
for i in range(len(trough_idx)):
|
|
380
|
+
steepness_troughs[
|
|
381
|
+
0, i, 0 : trough_idx[i] - peak_idx_left[i] + 1
|
|
382
|
+
] = steepness[peak_idx_left[i] : trough_idx[i] + 1]
|
|
383
|
+
steepness_troughs[
|
|
384
|
+
1, i, 0 : peak_idx_right[i] - trough_idx[i] + 1
|
|
385
|
+
] = steepness[trough_idx[i] : peak_idx_right[i] + 1]
|
|
386
|
+
|
|
387
|
+
if self.need_rise_steepness:
|
|
388
|
+
# left peak -> trough
|
|
389
|
+
# + 1 due to python syntax, s.t. the last element is included
|
|
390
|
+
results["rise_steepness"] = np.max(
|
|
391
|
+
np.abs(steepness_troughs[0, :, :]), axis=1
|
|
392
|
+
)
|
|
393
|
+
|
|
394
|
+
if self.need_decay_steepness:
|
|
395
|
+
# trough -> right peak
|
|
396
|
+
results["decay_steepness"] = np.max(
|
|
397
|
+
np.abs(steepness_troughs[1, :, :]), axis=1
|
|
398
|
+
)
|
|
399
|
+
|
|
400
|
+
if self.sw_settings.sharpwave_features.slope_ratio:
|
|
401
|
+
results["slope_ratio"] = (
|
|
402
|
+
results["rise_steepness"] - results["decay_steepness"]
|
|
403
|
+
)
|
|
404
|
+
|
|
405
|
+
if self.sw_settings.sharpwave_features.prominence:
|
|
406
|
+
results["prominence"] = np.abs(
|
|
407
|
+
(results["peak_right"] + results["peak_left"]) / 2 - results["trough"]
|
|
408
|
+
)
|
|
409
|
+
|
|
410
|
+
if self.sw_settings.sharpwave_features.decay_time:
|
|
411
|
+
results["decay_time"] = (peak_idx_left - trough_idx) * (
|
|
412
|
+
1000 / self.sfreq
|
|
413
|
+
) # ms
|
|
414
|
+
|
|
415
|
+
if self.sw_settings.sharpwave_features.rise_time:
|
|
416
|
+
results["rise_time"] = (peak_idx_right - trough_idx) * (
|
|
417
|
+
1000 / self.sfreq
|
|
418
|
+
) # ms
|
|
419
|
+
|
|
420
|
+
if self.sw_settings.sharpwave_features.width:
|
|
421
|
+
results["width"] = peak_idx_right - peak_idx_left # ms
|
|
422
|
+
|
|
423
|
+
return results
|