py-neuromodulation 0.0.2__py3-none-any.whl → 0.0.3__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.
- docs/build/_downloads/09df217f95985497f45d69e2d4bdc5b1/plot_2_example_add_feature.py +68 -0
- docs/build/_downloads/3b4900a2b2818ff30362215b76f7d5eb/plot_1_example_BIDS.py +233 -0
- docs/build/_downloads/7e92dd2e6cc86b239d14cafad972ae4f/plot_3_example_sharpwave_analysis.py +219 -0
- docs/build/_downloads/c2db0bf2b334d541b00662b991682256/plot_6_real_time_demo.py +97 -0
- docs/build/_downloads/ce3914826f782cbd1ea8fd024eaf0ac3/plot_5_example_rmap_computing.py +64 -0
- docs/build/_downloads/da36848a41e6a3235d91fb7cfb6d59b4/plot_0_first_demo.py +192 -0
- docs/build/_downloads/eaa4305c75b19a1e2eea941f742a6331/plot_4_example_gridPointProjection.py +210 -0
- docs/build/html/_downloads/09df217f95985497f45d69e2d4bdc5b1/plot_2_example_add_feature.py +68 -0
- docs/build/html/_downloads/3b4900a2b2818ff30362215b76f7d5eb/plot_1_example_BIDS.py +239 -0
- docs/build/html/_downloads/7e92dd2e6cc86b239d14cafad972ae4f/plot_3_example_sharpwave_analysis.py +219 -0
- docs/build/html/_downloads/c2db0bf2b334d541b00662b991682256/plot_6_real_time_demo.py +97 -0
- docs/build/html/_downloads/ce3914826f782cbd1ea8fd024eaf0ac3/plot_5_example_rmap_computing.py +64 -0
- docs/build/html/_downloads/da36848a41e6a3235d91fb7cfb6d59b4/plot_0_first_demo.py +192 -0
- docs/build/html/_downloads/eaa4305c75b19a1e2eea941f742a6331/plot_4_example_gridPointProjection.py +210 -0
- docs/source/_build/html/_downloads/09df217f95985497f45d69e2d4bdc5b1/plot_2_example_add_feature.py +76 -0
- docs/source/_build/html/_downloads/0d0d0a76e8f648d5d3cbc47da6351932/plot_real_time_demo.py +97 -0
- docs/source/_build/html/_downloads/3b4900a2b2818ff30362215b76f7d5eb/plot_1_example_BIDS.py +240 -0
- docs/source/_build/html/_downloads/5d73cadc59a8805c47e3b84063afc157/plot_example_BIDS.py +233 -0
- docs/source/_build/html/_downloads/7660317fa5a6bfbd12fcca9961457fc4/plot_example_rmap_computing.py +63 -0
- docs/source/_build/html/_downloads/7e92dd2e6cc86b239d14cafad972ae4f/plot_3_example_sharpwave_analysis.py +219 -0
- docs/source/_build/html/_downloads/839e5b319379f7fd9e867deb00fd797f/plot_example_gridPointProjection.py +210 -0
- docs/source/_build/html/_downloads/ae8be19afe5e559f011fc9b138968ba0/plot_first_demo.py +192 -0
- docs/source/_build/html/_downloads/b8b06cacc17969d3725a0b6f1d7741c5/plot_example_sharpwave_analysis.py +219 -0
- docs/source/_build/html/_downloads/c2db0bf2b334d541b00662b991682256/plot_6_real_time_demo.py +121 -0
- docs/source/_build/html/_downloads/c31a86c0b68cb4167d968091ace8080d/plot_example_add_feature.py +68 -0
- docs/source/_build/html/_downloads/ce3914826f782cbd1ea8fd024eaf0ac3/plot_5_example_rmap_computing.py +64 -0
- docs/source/_build/html/_downloads/da36848a41e6a3235d91fb7cfb6d59b4/plot_0_first_demo.py +189 -0
- docs/source/_build/html/_downloads/eaa4305c75b19a1e2eea941f742a6331/plot_4_example_gridPointProjection.py +210 -0
- docs/source/auto_examples/plot_0_first_demo.py +189 -0
- docs/source/auto_examples/plot_1_example_BIDS.py +240 -0
- docs/source/auto_examples/plot_2_example_add_feature.py +76 -0
- docs/source/auto_examples/plot_3_example_sharpwave_analysis.py +219 -0
- docs/source/auto_examples/plot_4_example_gridPointProjection.py +210 -0
- docs/source/auto_examples/plot_5_example_rmap_computing.py +64 -0
- docs/source/auto_examples/plot_6_real_time_demo.py +121 -0
- docs/source/conf.py +105 -0
- examples/plot_0_first_demo.py +189 -0
- examples/plot_1_example_BIDS.py +240 -0
- examples/plot_2_example_add_feature.py +76 -0
- examples/plot_3_example_sharpwave_analysis.py +219 -0
- examples/plot_4_example_gridPointProjection.py +210 -0
- examples/plot_5_example_rmap_computing.py +64 -0
- examples/plot_6_real_time_demo.py +121 -0
- packages/realtime_decoding/build/lib/realtime_decoding/__init__.py +4 -0
- packages/realtime_decoding/build/lib/realtime_decoding/decoder.py +104 -0
- packages/realtime_decoding/build/lib/realtime_decoding/features.py +163 -0
- packages/realtime_decoding/build/lib/realtime_decoding/helpers.py +15 -0
- packages/realtime_decoding/build/lib/realtime_decoding/run_decoding.py +345 -0
- packages/realtime_decoding/build/lib/realtime_decoding/trainer.py +54 -0
- packages/tmsi/build/lib/TMSiFileFormats/__init__.py +37 -0
- packages/tmsi/build/lib/TMSiFileFormats/file_formats/__init__.py +36 -0
- packages/tmsi/build/lib/TMSiFileFormats/file_formats/lsl_stream_writer.py +200 -0
- packages/tmsi/build/lib/TMSiFileFormats/file_formats/poly5_file_writer.py +496 -0
- packages/tmsi/build/lib/TMSiFileFormats/file_formats/poly5_to_edf_converter.py +236 -0
- packages/tmsi/build/lib/TMSiFileFormats/file_formats/xdf_file_writer.py +977 -0
- packages/tmsi/build/lib/TMSiFileFormats/file_readers/__init__.py +35 -0
- packages/tmsi/build/lib/TMSiFileFormats/file_readers/edf_reader.py +116 -0
- packages/tmsi/build/lib/TMSiFileFormats/file_readers/poly5reader.py +294 -0
- packages/tmsi/build/lib/TMSiFileFormats/file_readers/xdf_reader.py +229 -0
- packages/tmsi/build/lib/TMSiFileFormats/file_writer.py +102 -0
- packages/tmsi/build/lib/TMSiPlotters/__init__.py +2 -0
- packages/tmsi/build/lib/TMSiPlotters/gui/__init__.py +39 -0
- packages/tmsi/build/lib/TMSiPlotters/gui/_plotter_gui.py +234 -0
- packages/tmsi/build/lib/TMSiPlotters/gui/plotting_gui.py +440 -0
- packages/tmsi/build/lib/TMSiPlotters/plotters/__init__.py +44 -0
- packages/tmsi/build/lib/TMSiPlotters/plotters/hd_emg_plotter.py +446 -0
- packages/tmsi/build/lib/TMSiPlotters/plotters/impedance_plotter.py +589 -0
- packages/tmsi/build/lib/TMSiPlotters/plotters/signal_plotter.py +1326 -0
- packages/tmsi/build/lib/TMSiSDK/__init__.py +54 -0
- packages/tmsi/build/lib/TMSiSDK/device.py +588 -0
- packages/tmsi/build/lib/TMSiSDK/devices/__init__.py +34 -0
- packages/tmsi/build/lib/TMSiSDK/devices/saga/TMSi_Device_API.py +1764 -0
- packages/tmsi/build/lib/TMSiSDK/devices/saga/__init__.py +34 -0
- packages/tmsi/build/lib/TMSiSDK/devices/saga/saga_device.py +1366 -0
- packages/tmsi/build/lib/TMSiSDK/devices/saga/saga_types.py +520 -0
- packages/tmsi/build/lib/TMSiSDK/devices/saga/xml_saga_config.py +165 -0
- packages/tmsi/build/lib/TMSiSDK/error.py +95 -0
- packages/tmsi/build/lib/TMSiSDK/sample_data.py +63 -0
- packages/tmsi/build/lib/TMSiSDK/sample_data_server.py +99 -0
- packages/tmsi/build/lib/TMSiSDK/settings.py +45 -0
- packages/tmsi/build/lib/TMSiSDK/tmsi_device.py +111 -0
- packages/tmsi/build/lib/__init__.py +4 -0
- packages/tmsi/build/lib/apex_sdk/__init__.py +34 -0
- packages/tmsi/build/lib/apex_sdk/device/__init__.py +41 -0
- packages/tmsi/build/lib/apex_sdk/device/devices/apex/apex_API.py +1009 -0
- packages/tmsi/build/lib/apex_sdk/device/devices/apex/apex_API_enums.py +239 -0
- packages/tmsi/build/lib/apex_sdk/device/devices/apex/apex_API_structures.py +668 -0
- packages/tmsi/build/lib/apex_sdk/device/devices/apex/apex_device.py +1611 -0
- packages/tmsi/build/lib/apex_sdk/device/devices/apex/apex_dongle.py +38 -0
- packages/tmsi/build/lib/apex_sdk/device/devices/apex/apex_event_reader.py +57 -0
- packages/tmsi/build/lib/apex_sdk/device/devices/apex/apex_structures/apex_channel.py +44 -0
- packages/tmsi/build/lib/apex_sdk/device/devices/apex/apex_structures/apex_config.py +150 -0
- packages/tmsi/build/lib/apex_sdk/device/devices/apex/apex_structures/apex_const.py +36 -0
- packages/tmsi/build/lib/apex_sdk/device/devices/apex/apex_structures/apex_impedance_channel.py +48 -0
- packages/tmsi/build/lib/apex_sdk/device/devices/apex/apex_structures/apex_info.py +108 -0
- packages/tmsi/build/lib/apex_sdk/device/devices/apex/apex_structures/dongle_info.py +39 -0
- packages/tmsi/build/lib/apex_sdk/device/devices/apex/measurements/download_measurement.py +77 -0
- packages/tmsi/build/lib/apex_sdk/device/devices/apex/measurements/eeg_measurement.py +150 -0
- packages/tmsi/build/lib/apex_sdk/device/devices/apex/measurements/impedance_measurement.py +129 -0
- packages/tmsi/build/lib/apex_sdk/device/threads/conversion_thread.py +59 -0
- packages/tmsi/build/lib/apex_sdk/device/threads/sampling_thread.py +57 -0
- packages/tmsi/build/lib/apex_sdk/device/tmsi_channel.py +83 -0
- packages/tmsi/build/lib/apex_sdk/device/tmsi_device.py +201 -0
- packages/tmsi/build/lib/apex_sdk/device/tmsi_device_enums.py +103 -0
- packages/tmsi/build/lib/apex_sdk/device/tmsi_dongle.py +43 -0
- packages/tmsi/build/lib/apex_sdk/device/tmsi_event_reader.py +50 -0
- packages/tmsi/build/lib/apex_sdk/device/tmsi_measurement.py +118 -0
- packages/tmsi/build/lib/apex_sdk/sample_data_server/__init__.py +33 -0
- packages/tmsi/build/lib/apex_sdk/sample_data_server/event_data.py +44 -0
- packages/tmsi/build/lib/apex_sdk/sample_data_server/sample_data.py +50 -0
- packages/tmsi/build/lib/apex_sdk/sample_data_server/sample_data_server.py +136 -0
- packages/tmsi/build/lib/apex_sdk/tmsi_errors/error.py +126 -0
- packages/tmsi/build/lib/apex_sdk/tmsi_sdk.py +113 -0
- packages/tmsi/build/lib/apex_sdk/tmsi_utilities/apex/apex_structure_generator.py +134 -0
- packages/tmsi/build/lib/apex_sdk/tmsi_utilities/decorators.py +60 -0
- packages/tmsi/build/lib/apex_sdk/tmsi_utilities/logger_filter.py +42 -0
- packages/tmsi/build/lib/apex_sdk/tmsi_utilities/singleton.py +42 -0
- packages/tmsi/build/lib/apex_sdk/tmsi_utilities/support_functions.py +72 -0
- packages/tmsi/build/lib/apex_sdk/tmsi_utilities/tmsi_logger.py +98 -0
- py_neuromodulation/{helper.py → _write_example_dataset_helper.py} +1 -1
- py_neuromodulation/nm_EpochStream.py +2 -3
- py_neuromodulation/nm_IO.py +43 -70
- py_neuromodulation/nm_RMAP.py +308 -11
- py_neuromodulation/nm_analysis.py +1 -1
- py_neuromodulation/nm_artifacts.py +25 -0
- py_neuromodulation/nm_bispectra.py +64 -29
- py_neuromodulation/nm_bursts.py +44 -30
- py_neuromodulation/nm_coherence.py +2 -1
- py_neuromodulation/nm_features.py +4 -2
- py_neuromodulation/nm_filter.py +63 -32
- py_neuromodulation/nm_filter_preprocessing.py +91 -0
- py_neuromodulation/nm_fooof.py +47 -29
- py_neuromodulation/nm_mne_connectivity.py +1 -1
- py_neuromodulation/nm_normalization.py +50 -74
- py_neuromodulation/nm_oscillatory.py +151 -31
- py_neuromodulation/nm_plots.py +13 -10
- py_neuromodulation/nm_rereference.py +10 -8
- py_neuromodulation/nm_run_analysis.py +28 -13
- py_neuromodulation/nm_sharpwaves.py +103 -136
- py_neuromodulation/nm_stats.py +44 -30
- py_neuromodulation/nm_stream_abc.py +18 -10
- py_neuromodulation/nm_stream_offline.py +181 -40
- py_neuromodulation/utils/_logging.py +24 -0
- {py_neuromodulation-0.0.2.dist-info → py_neuromodulation-0.0.3.dist-info}/METADATA +182 -142
- py_neuromodulation-0.0.3.dist-info/RECORD +188 -0
- {py_neuromodulation-0.0.2.dist-info → py_neuromodulation-0.0.3.dist-info}/WHEEL +2 -1
- py_neuromodulation-0.0.3.dist-info/top_level.txt +5 -0
- tests/__init__.py +0 -0
- tests/conftest.py +117 -0
- tests/test_all_examples.py +10 -0
- tests/test_all_features.py +63 -0
- tests/test_bispectra.py +70 -0
- tests/test_bursts.py +105 -0
- tests/test_feature_sampling_rates.py +143 -0
- tests/test_fooof.py +16 -0
- tests/test_initalization_offline_stream.py +41 -0
- tests/test_multiprocessing.py +58 -0
- tests/test_nan_values.py +29 -0
- tests/test_nm_filter.py +95 -0
- tests/test_nm_resample.py +63 -0
- tests/test_normalization_settings.py +146 -0
- tests/test_notch_filter.py +31 -0
- tests/test_osc_features.py +424 -0
- tests/test_preprocessing_filter.py +151 -0
- tests/test_rereference.py +171 -0
- tests/test_sampling.py +57 -0
- tests/test_settings_change_after_init.py +76 -0
- tests/test_sharpwave.py +165 -0
- tests/test_target_channel_add.py +100 -0
- tests/test_timing.py +80 -0
- py_neuromodulation/data/README +0 -6
- py_neuromodulation/data/dataset_description.json +0 -8
- py_neuromodulation/data/derivatives/sub-testsub_ses-EphysMedOff_task-gripforce_run-0/MOV_aligned_features_ch_ECOG_RIGHT_0_all.png +0 -0
- py_neuromodulation/data/derivatives/sub-testsub_ses-EphysMedOff_task-gripforce_run-0/all_feature_plt.pdf +0 -0
- py_neuromodulation/data/derivatives/sub-testsub_ses-EphysMedOff_task-gripforce_run-0/sub-testsub_ses-EphysMedOff_task-gripforce_run-0_FEATURES.csv +0 -182
- py_neuromodulation/data/derivatives/sub-testsub_ses-EphysMedOff_task-gripforce_run-0/sub-testsub_ses-EphysMedOff_task-gripforce_run-0_LM_ML_RES.p +0 -0
- py_neuromodulation/data/derivatives/sub-testsub_ses-EphysMedOff_task-gripforce_run-0/sub-testsub_ses-EphysMedOff_task-gripforce_run-0_SETTINGS.json +0 -273
- py_neuromodulation/data/derivatives/sub-testsub_ses-EphysMedOff_task-gripforce_run-0/sub-testsub_ses-EphysMedOff_task-gripforce_run-0_SIDECAR.json +0 -6
- py_neuromodulation/data/derivatives/sub-testsub_ses-EphysMedOff_task-gripforce_run-0/sub-testsub_ses-EphysMedOff_task-gripforce_run-0_decoding_performance.png +0 -0
- py_neuromodulation/data/derivatives/sub-testsub_ses-EphysMedOff_task-gripforce_run-0/sub-testsub_ses-EphysMedOff_task-gripforce_run-0_nm_channels.csv +0 -11
- py_neuromodulation/data/participants.json +0 -32
- py_neuromodulation/data/participants.tsv +0 -2
- py_neuromodulation/data/sub-testsub/ses-EphysMedOff/ieeg/sub-testsub_ses-EphysMedOff_space-mni_coordsystem.json +0 -5
- py_neuromodulation/data/sub-testsub/ses-EphysMedOff/ieeg/sub-testsub_ses-EphysMedOff_space-mni_electrodes.tsv +0 -11
- py_neuromodulation/data/sub-testsub/ses-EphysMedOff/ieeg/sub-testsub_ses-EphysMedOff_task-gripforce_run-0_channels.tsv +0 -11
- 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 +0 -18
- py_neuromodulation/data/sub-testsub/ses-EphysMedOff/ieeg/sub-testsub_ses-EphysMedOff_task-gripforce_run-0_ieeg.vhdr +0 -35
- py_neuromodulation/data/sub-testsub/ses-EphysMedOff/ieeg/sub-testsub_ses-EphysMedOff_task-gripforce_run-0_ieeg.vmrk +0 -13
- py_neuromodulation/data/sub-testsub/ses-EphysMedOff/sub-testsub_ses-EphysMedOff_scans.tsv +0 -2
- py_neuromodulation/grid_cortex.tsv +0 -40
- py_neuromodulation/grid_subcortex.tsv +0 -1429
- py_neuromodulation/nm_settings.json +0 -290
- 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/py_neuromodulation.egg-info/PKG-INFO +0 -104
- py_neuromodulation/py_neuromodulation.egg-info/dependency_links.txt +0 -1
- py_neuromodulation/py_neuromodulation.egg-info/requires.txt +0 -26
- py_neuromodulation/py_neuromodulation.egg-info/top_level.txt +0 -1
- py_neuromodulation-0.0.2.dist-info/RECORD +0 -73
- /py_neuromodulation/{py_neuromodulation.egg-info/SOURCES.txt → utils/__init__.py} +0 -0
- {py_neuromodulation-0.0.2.dist-info → py_neuromodulation-0.0.3.dist-info}/LICENSE +0 -0
|
@@ -0,0 +1,446 @@
|
|
|
1
|
+
'''
|
|
2
|
+
(c) 2022 Twente Medical Systems International B.V., Oldenzaal The Netherlands
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
|
|
16
|
+
####### # # ##### #
|
|
17
|
+
# ## ## #
|
|
18
|
+
# # # # # # #
|
|
19
|
+
# # # # ##### #
|
|
20
|
+
# # # # #
|
|
21
|
+
# # # # #
|
|
22
|
+
# # # ##### #
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @file ${hd_emg_plotter.py}
|
|
26
|
+
* @brief Plotter object that displays an heatmap based on real-time computed
|
|
27
|
+
* RMS values, designed for HD-EMG applications.
|
|
28
|
+
*
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
'''
|
|
33
|
+
|
|
34
|
+
from PySide2 import QtGui, QtCore, QtWidgets
|
|
35
|
+
import numpy as np
|
|
36
|
+
import pyqtgraph as pg
|
|
37
|
+
import time
|
|
38
|
+
import queue
|
|
39
|
+
from scipy import signal, interpolate
|
|
40
|
+
import sys
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
from os.path import join, dirname, realpath, normpath
|
|
44
|
+
|
|
45
|
+
Plotter_dir = dirname(realpath(__file__)) # directory of this file
|
|
46
|
+
measurements_dir = join(Plotter_dir, '../../measurements') # directory with all measurements
|
|
47
|
+
modules_dir = normpath(join(Plotter_dir, '../..')) # directory with all modules
|
|
48
|
+
|
|
49
|
+
from TMSiSDK import tmsi_device
|
|
50
|
+
from TMSiSDK import sample_data_server
|
|
51
|
+
|
|
52
|
+
from TMSiSDK.device import DeviceInterfaceType, ChannelType
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class HeatMapViewer():
|
|
56
|
+
""" Class that creates a GUI to display the impedance values in a gridded
|
|
57
|
+
layout.
|
|
58
|
+
"""
|
|
59
|
+
def __init__(self, gui_handle, device, tail_orientation, signal_lim, grid_type = 'flex pcb grid'):
|
|
60
|
+
""" Setting up the GUI's elements
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
if sys.platform == "linux" or sys.platform == "linux2":
|
|
64
|
+
print('This plotter is not compatible with the current version of the TMSi Python Interface on Linux (Ubuntu 18.04 LTS)\n')
|
|
65
|
+
return
|
|
66
|
+
|
|
67
|
+
# Pass the device handle to the GUI
|
|
68
|
+
self.device = device
|
|
69
|
+
|
|
70
|
+
self.gui_handle = gui_handle
|
|
71
|
+
self.RealTimePlotWidget = self.gui_handle.RealTimePlotWidget
|
|
72
|
+
|
|
73
|
+
# Determine used number of channels
|
|
74
|
+
if self.device.config.num_channels > 64:
|
|
75
|
+
if '4-8' in grid_type or grid_type[-1] == '1' or grid_type[-1] == '2':
|
|
76
|
+
self._EMG_chans = 32
|
|
77
|
+
else:
|
|
78
|
+
self._EMG_chans = 64
|
|
79
|
+
else:
|
|
80
|
+
self._EMG_chans = 32
|
|
81
|
+
|
|
82
|
+
self._preprocess_wifi = False
|
|
83
|
+
if self.device.info.dr_interface == DeviceInterfaceType.wifi:
|
|
84
|
+
self._preprocess_wifi = True
|
|
85
|
+
|
|
86
|
+
self.sample_rate = self.device.config.get_sample_rate(ChannelType.counter)
|
|
87
|
+
|
|
88
|
+
# Orientation of the grid on the body
|
|
89
|
+
self.tail_orientation = tail_orientation
|
|
90
|
+
|
|
91
|
+
# Upper limit colorbar
|
|
92
|
+
self.signal_lim = signal_lim
|
|
93
|
+
|
|
94
|
+
# Type of used grid
|
|
95
|
+
self.grid_type = grid_type
|
|
96
|
+
if not '11' in self.grid_type:
|
|
97
|
+
self.n_x = int(self._EMG_chans/8)
|
|
98
|
+
self.n_y = 8
|
|
99
|
+
else:
|
|
100
|
+
self.n_x = int(np.ceil(self._EMG_chans/11))
|
|
101
|
+
self.n_y = 11
|
|
102
|
+
|
|
103
|
+
# Set up UI and thread
|
|
104
|
+
self.initUI()
|
|
105
|
+
self.setupThread()
|
|
106
|
+
|
|
107
|
+
def initUI(self):
|
|
108
|
+
""" Method responsible for constructing the basic elements in the plot.
|
|
109
|
+
All viewboxes have a set size so that the information can be displayed
|
|
110
|
+
correctly.
|
|
111
|
+
"""
|
|
112
|
+
# Set view settings
|
|
113
|
+
self.RealTimePlotWidget.setBackground('w')
|
|
114
|
+
# self.showMaximized()
|
|
115
|
+
|
|
116
|
+
# Add plot window for the channels
|
|
117
|
+
self.RealTimePlotWidget.window = self.RealTimePlotWidget.addPlot()
|
|
118
|
+
self.RealTimePlotWidget.window.setAspectLocked(lock=True, ratio = 1)
|
|
119
|
+
|
|
120
|
+
# Write the ticks to the plot
|
|
121
|
+
self.RealTimePlotWidget.window.hideAxis('left')
|
|
122
|
+
self.RealTimePlotWidget.window.hideAxis('bottom')
|
|
123
|
+
|
|
124
|
+
# Disable auto-scaling and menu
|
|
125
|
+
self.RealTimePlotWidget.window.hideButtons()
|
|
126
|
+
self.RealTimePlotWidget.window.setMenuEnabled(False)
|
|
127
|
+
self.RealTimePlotWidget.window.setMouseEnabled(x = False, y = False)
|
|
128
|
+
|
|
129
|
+
# Delete CREF channel from channel conversion list
|
|
130
|
+
self.channel_conversion_list = np.delete(self.gui_handle.channel_conversion_list[:self._EMG_chans+1], 0)
|
|
131
|
+
|
|
132
|
+
# Detect disabled channels
|
|
133
|
+
self.channel_insertion_list = []
|
|
134
|
+
offset=0
|
|
135
|
+
for ch in range(self._EMG_chans+1):
|
|
136
|
+
if not self.device.channels[ch-offset].name == self.device._config._channels[ch].alt_name:
|
|
137
|
+
offset =offset + 1
|
|
138
|
+
self.channel_insertion_list.append(ch)
|
|
139
|
+
|
|
140
|
+
# Insert dummy channels
|
|
141
|
+
self.dummy_chan = []
|
|
142
|
+
if '11' in self.grid_type:
|
|
143
|
+
if not self.grid_type[-1] == '2':
|
|
144
|
+
self.dummy_chan.append(10)
|
|
145
|
+
self.channel_conversion_list = np.insert(self.channel_conversion_list, 10, max(self.channel_conversion_list)+1)
|
|
146
|
+
if not self.grid_type[-1] == '1':
|
|
147
|
+
dummy_chan=max(self.channel_conversion_list)+1
|
|
148
|
+
self.dummy_chan.append(dummy_chan)
|
|
149
|
+
self.channel_conversion_list = np.append(self.channel_conversion_list, dummy_chan)
|
|
150
|
+
|
|
151
|
+
# Ratio is slightly different between 32/64 channel setup
|
|
152
|
+
# Number of rows/columns depends on grid-type
|
|
153
|
+
if not '11' in self.grid_type:
|
|
154
|
+
if self._EMG_chans == 32:
|
|
155
|
+
self._x_interpolate = np.arange(0, int(self._EMG_chans/8) - 1 + .1, 0.1)
|
|
156
|
+
self._y_interpolate = np.arange(0, int(self._EMG_chans/(self._EMG_chans/8)) - 1 + .1, 0.1)
|
|
157
|
+
else:
|
|
158
|
+
self._x_interpolate = np.arange(0, int(self._EMG_chans/8) - 1 + .2, 0.1)
|
|
159
|
+
self._y_interpolate = np.arange(0, int(self._EMG_chans/(self._EMG_chans/8)) - 1 + .1, 0.1)
|
|
160
|
+
else:
|
|
161
|
+
self._x_interpolate = np.arange(0, self.n_x - 1 + .1, 0.1)
|
|
162
|
+
self._y_interpolate = np.arange(0, self.n_y - 1 + .1, 0.1)
|
|
163
|
+
self._dummy_val = np.zeros((len(self._x_interpolate), len(self._y_interpolate)))
|
|
164
|
+
|
|
165
|
+
if self.tail_orientation == 'Right' or self.tail_orientation == 'right':
|
|
166
|
+
self._dummy_val = np.rot90(self._dummy_val, 1)
|
|
167
|
+
elif self.tail_orientation == 'Left' or self.tail_orientation == 'left':
|
|
168
|
+
self._dummy_val = np.rot90(self._dummy_val, 3)
|
|
169
|
+
|
|
170
|
+
# Create image item
|
|
171
|
+
self.img = pg.ImageItem(image = self._dummy_val)
|
|
172
|
+
self.RealTimePlotWidget.window.addItem(self.img)
|
|
173
|
+
|
|
174
|
+
# Prepare a linear color map
|
|
175
|
+
cm = pg.colormap.get('CET-R4')
|
|
176
|
+
|
|
177
|
+
# Insert a Colorbar, non-interactive with a label
|
|
178
|
+
self.bar = pg.ColorBarItem(values = (0, self.signal_lim), colorMap=cm, interactive = False, label = 'RMS (\u03BCVolt)', )
|
|
179
|
+
self.bar.setImageItem(self.img, insert_in = self.RealTimePlotWidget.window)
|
|
180
|
+
|
|
181
|
+
# Scale factor (number of interpolation points to cover a width of n columns)
|
|
182
|
+
corr_x = len(self._x_interpolate) / max(self._x_interpolate)
|
|
183
|
+
|
|
184
|
+
# Scale factor (number of interpolation points to cover a width of n rows)
|
|
185
|
+
corr_y = len(self._y_interpolate) / max(self._y_interpolate)
|
|
186
|
+
|
|
187
|
+
# Number of rows/columns depends on grid-type
|
|
188
|
+
locs = np.array([[(i%self.n_y) * corr_y, int(i/self.n_y) * corr_x] for i in range(self.n_x*self.n_y)])
|
|
189
|
+
|
|
190
|
+
# Text object required for the tail orientation
|
|
191
|
+
tail_text = pg.TextItem('TAIL', (128, 128, 128), anchor=(0, 0))
|
|
192
|
+
tail_text.setFont(QtGui.QFont("Times", 16, QtGui.QFont.ExtraBold))
|
|
193
|
+
|
|
194
|
+
# Initialise the standard format for the different indicators
|
|
195
|
+
if self.tail_orientation == 'Left' or self.tail_orientation == 'left':
|
|
196
|
+
self.spots = [{'pos': locs[i], 'size': 5, 'pen': 'k', 'brush': QtGui.QBrush(QtGui.QColor(128, 128, 128))} \
|
|
197
|
+
for i in range(self._EMG_chans+len(self.dummy_chan)) if self.channel_conversion_list[i] <= self._EMG_chans]
|
|
198
|
+
tail_text.setPos(self.spots[0]['pos'][0] - 1 * corr_y, self.spots[0]['pos'][1] + 1.5 * corr_x)
|
|
199
|
+
|
|
200
|
+
elif self.tail_orientation == 'Up' or self.tail_orientation == 'up':
|
|
201
|
+
self.spots = [{'pos': [locs.T[1][-i-1], locs[::-1].T[0][-i-1]], 'size': 5, 'pen': 'k', 'brush': QtGui.QBrush(QtGui.QColor(128, 128, 128))} \
|
|
202
|
+
for i in range(self._EMG_chans+len(self.dummy_chan)) if self.channel_conversion_list[i] <= self._EMG_chans]
|
|
203
|
+
tail_text.setPos(self.spots[0]['pos'][0] - 2 * corr_y, self.spots[0]['pos'][1] - 0.5 * corr_x)
|
|
204
|
+
|
|
205
|
+
elif self.tail_orientation == 'Right' or self.tail_orientation == 'right':
|
|
206
|
+
self.spots = [{'pos': locs[::-1][i], 'size': 5, 'pen': 'k', 'brush': QtGui.QBrush(QtGui.QColor(128, 128, 128))} \
|
|
207
|
+
for i in range(self._EMG_chans+len(self.dummy_chan)) if self.channel_conversion_list[i] <= self._EMG_chans]
|
|
208
|
+
tail_text.setPos(self.spots[0]['pos'][0] + 0.5 * corr_y, self.spots[0]['pos'][1] - 1.5 * corr_x)
|
|
209
|
+
|
|
210
|
+
elif self.tail_orientation == 'Down' or self.tail_orientation == 'down':
|
|
211
|
+
self.spots = [{'pos': [locs[::-1].T[1][-i-1], locs.T[0][-i-1]], 'size': 5, 'pen': 'k', 'brush': QtGui.QBrush(QtGui.QColor(128, 128, 128))} \
|
|
212
|
+
for i in range(self._EMG_chans+len(self.dummy_chan)) if self.channel_conversion_list[i] <= self._EMG_chans]
|
|
213
|
+
tail_text.setPos(self.spots[0]['pos'][0] + 1 *corr_y, self.spots[0]['pos'][1] + 0.5 * corr_x)
|
|
214
|
+
|
|
215
|
+
# Add the text object to the plot
|
|
216
|
+
self.RealTimePlotWidget.window.addItem(tail_text)
|
|
217
|
+
|
|
218
|
+
i_corr = 0
|
|
219
|
+
# Set the position for each indicator
|
|
220
|
+
for i in range(len(self.device._config._channels)):
|
|
221
|
+
if i > (self.n_x*self.n_y) - 1:
|
|
222
|
+
break
|
|
223
|
+
# Select channel from conversion list and correct for dummy channels
|
|
224
|
+
i_ch = self.channel_conversion_list[i]
|
|
225
|
+
if i_ch <= (self._EMG_chans):
|
|
226
|
+
# Place the name of each channel below the respective indicator
|
|
227
|
+
# Text colour is red for disabled channels
|
|
228
|
+
text = f'{self.device._config._channels[i_ch].alt_name: ^10}'
|
|
229
|
+
if i_ch in self.channel_insertion_list:
|
|
230
|
+
t_item = pg.TextItem(text, (255, 0, 0), anchor=(0, 0))
|
|
231
|
+
else:
|
|
232
|
+
t_item = pg.TextItem(text, (128, 128, 128), anchor=(0, 0))
|
|
233
|
+
t_item.setPos(self.spots[i-i_corr]['pos'][0] -.25, self.spots[i-i_corr]['pos'][1] + .1)
|
|
234
|
+
self.RealTimePlotWidget.window.addItem(t_item)
|
|
235
|
+
else:
|
|
236
|
+
i_corr = i_corr + 1
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
# Add all indicators to the plot
|
|
240
|
+
self.c = pg.ScatterPlotItem(self.spots)
|
|
241
|
+
self.RealTimePlotWidget.window.addItem(self.c)
|
|
242
|
+
|
|
243
|
+
self.RealTimePlotWidget.window.invertY(True)
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
@QtCore.Slot(object)
|
|
247
|
+
def update_plot(self, data):
|
|
248
|
+
""" Method that updates the indicators according to the measured impedance values
|
|
249
|
+
"""
|
|
250
|
+
self.img.setImage(data, autoRange=False, autoLevels=False)
|
|
251
|
+
|
|
252
|
+
def _update_scale(self, type_flag):
|
|
253
|
+
"""Method that creates a different range in which the heatmap is presented"""
|
|
254
|
+
|
|
255
|
+
data = self.img.image
|
|
256
|
+
if type_flag == 'scale':
|
|
257
|
+
self.signal_lim = np.nanmax(data)
|
|
258
|
+
elif type_flag == 'range':
|
|
259
|
+
self.signal_lim = int(self.gui_handle.set_range_box.currentText())
|
|
260
|
+
|
|
261
|
+
self.bar.setLevels(low = 0, high = self.signal_lim)
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def setupThread(self):
|
|
265
|
+
""" Method that initialises the sampling thread of the device
|
|
266
|
+
"""
|
|
267
|
+
# Create a Thread
|
|
268
|
+
self.thread = QtCore.QThread()
|
|
269
|
+
# Instantiate the worker class
|
|
270
|
+
self.worker = SamplingThread(self)
|
|
271
|
+
|
|
272
|
+
# Move the worker to a Thread
|
|
273
|
+
self.worker.moveToThread(self.thread)
|
|
274
|
+
|
|
275
|
+
# Connect signals to slots
|
|
276
|
+
self.thread.started.connect(self.worker.update_samples)
|
|
277
|
+
self.worker.output.connect(self.update_plot)
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
class SamplingThread(QtCore.QObject):
|
|
281
|
+
""" Class responsible for sampling the data from the device
|
|
282
|
+
"""
|
|
283
|
+
# Initialise the ouptut object
|
|
284
|
+
output = QtCore.Signal(object)
|
|
285
|
+
def __init__(self, main_class):
|
|
286
|
+
QtCore.QObject.__init__(self)
|
|
287
|
+
|
|
288
|
+
# Access initialised values from the GUI class
|
|
289
|
+
self.device = main_class.device
|
|
290
|
+
self.sample_rate = main_class.sample_rate
|
|
291
|
+
self._EMG_chans = main_class._EMG_chans
|
|
292
|
+
self.grid_type = main_class.grid_type
|
|
293
|
+
self.channel_conversion_list = main_class.channel_conversion_list
|
|
294
|
+
self._preprocess_wifi = main_class._preprocess_wifi
|
|
295
|
+
|
|
296
|
+
self.window_buffer = np.zeros((self._EMG_chans+len(main_class.dummy_chan), self.sample_rate * 5))
|
|
297
|
+
self.window_rms_size = self.sample_rate // 4
|
|
298
|
+
self._add_final = 0
|
|
299
|
+
|
|
300
|
+
self.sos = signal.butter(2, 10, 'highpass', fs=self.sample_rate, output='sos')
|
|
301
|
+
z_sos0 = signal.sosfilt_zi(self.sos)
|
|
302
|
+
self.z_sos = np.repeat(z_sos0[:, np.newaxis, :], (self._EMG_chans+len(main_class.dummy_chan)), axis=1)
|
|
303
|
+
|
|
304
|
+
self.n_x=main_class.n_x
|
|
305
|
+
self.n_y=main_class.n_y
|
|
306
|
+
self._x_grid = np.arange(0, self.n_x, dtype=int)
|
|
307
|
+
self._y_grid = np.arange(0, self.n_y, dtype=int)
|
|
308
|
+
|
|
309
|
+
self._x_interpolate = main_class._x_interpolate
|
|
310
|
+
self._y_interpolate = main_class._y_interpolate
|
|
311
|
+
|
|
312
|
+
self.tail_orientation = main_class.tail_orientation
|
|
313
|
+
self.channel_insertion_list = main_class.channel_insertion_list
|
|
314
|
+
self.dummy_chan = main_class.dummy_chan
|
|
315
|
+
|
|
316
|
+
# Prepare Queue
|
|
317
|
+
self.q_sample_sets = queue.Queue(1000)
|
|
318
|
+
|
|
319
|
+
# Register the consumer to the sample server
|
|
320
|
+
sample_data_server.registerConsumer(self.device.id, self.q_sample_sets)
|
|
321
|
+
|
|
322
|
+
# Start measurement
|
|
323
|
+
self.sampling = True
|
|
324
|
+
|
|
325
|
+
@QtCore.Slot()
|
|
326
|
+
def update_samples(self):
|
|
327
|
+
""" Method that retrieves the sample data from the device. The method
|
|
328
|
+
gives the impedance value as output
|
|
329
|
+
"""
|
|
330
|
+
|
|
331
|
+
while self.sampling:
|
|
332
|
+
while not self.q_sample_sets.empty():
|
|
333
|
+
|
|
334
|
+
# Retrieve sample data from the sample_data_server queue
|
|
335
|
+
sd = self.q_sample_sets.get()
|
|
336
|
+
self.q_sample_sets.task_done()
|
|
337
|
+
|
|
338
|
+
# Reshape the samples retrieved from the queue
|
|
339
|
+
samples = np.reshape(sd.samples, (sd.num_samples_per_sample_set, sd.num_sample_sets), order = 'F')
|
|
340
|
+
self.new_samples = sd.num_sample_sets
|
|
341
|
+
|
|
342
|
+
conversion_list = self.channel_conversion_list
|
|
343
|
+
# Missing samples are registered as NaN. This crashes the filter.
|
|
344
|
+
# Therefore, copies are inserted for the filtered data
|
|
345
|
+
if self._preprocess_wifi:
|
|
346
|
+
find_nan = np.isnan(samples)
|
|
347
|
+
if find_nan.any():
|
|
348
|
+
idx_nan = np.where(np.isnan(samples))
|
|
349
|
+
samples[idx_nan] = samples[idx_nan[0], idx_nan[1][0]-1]
|
|
350
|
+
|
|
351
|
+
# Insert disabled channels to maintain proper channel positions
|
|
352
|
+
if len(self.channel_insertion_list) == 1:
|
|
353
|
+
samples = np.insert(samples, self.channel_insertion_list, 0, axis=0)
|
|
354
|
+
else:
|
|
355
|
+
for ch in self.channel_insertion_list:
|
|
356
|
+
samples = np.insert(samples, ch, 0, axis=0)
|
|
357
|
+
|
|
358
|
+
# Insert dummy channels
|
|
359
|
+
for ch in self.dummy_chan:
|
|
360
|
+
samples = np.insert(samples, self._EMG_chans+1, np.nan, axis=0)
|
|
361
|
+
|
|
362
|
+
# Fill the window buffer with the reshaped sample set
|
|
363
|
+
self.window_buffer[:, self._add_final:(self._add_final + self.new_samples)] = samples[conversion_list,:]
|
|
364
|
+
|
|
365
|
+
if self._add_final + self.new_samples > self.window_rms_size:
|
|
366
|
+
filt_data, self.z_sos = signal.sosfilt(self.sos, self.window_buffer[:, 0:self.window_rms_size], zi = self.z_sos)
|
|
367
|
+
|
|
368
|
+
if self._preprocess_wifi:
|
|
369
|
+
if np.isnan(filt_data).any():
|
|
370
|
+
print('The filter crashed due to lost samples; resetting filter...')
|
|
371
|
+
self.sos = signal.butter(2, 10, 'highpass', fs=self.sample_rate, output='sos')
|
|
372
|
+
z_sos0 = signal.sosfilt_zi(self.sos)
|
|
373
|
+
self.z_sos = np.repeat(z_sos0[:, np.newaxis, :], self._EMG_chans, axis=1)
|
|
374
|
+
|
|
375
|
+
rms_data = np.sqrt(np.mean(filt_data**2, axis = 1))
|
|
376
|
+
|
|
377
|
+
# Reshape to 2d
|
|
378
|
+
rms_data = np.reshape(rms_data, (self.n_x,self.n_y)).T
|
|
379
|
+
|
|
380
|
+
# Interpolation functions and mapping of dummy channels
|
|
381
|
+
# Dummy channels are NaN, apply dubble filter to handle this
|
|
382
|
+
nan_map = np.zeros_like(rms_data)
|
|
383
|
+
nan_map[ np.isnan(rms_data) ] = 1
|
|
384
|
+
|
|
385
|
+
rms_filled = rms_data.copy()
|
|
386
|
+
rms_filled[ np.isnan(rms_data) ] = 0
|
|
387
|
+
|
|
388
|
+
f = interpolate.interp2d(self._x_grid, self._y_grid, rms_filled, kind='linear')
|
|
389
|
+
f_nan = interpolate.interp2d(self._x_grid, self._y_grid, nan_map, kind='linear')
|
|
390
|
+
|
|
391
|
+
heatmap = f(self._x_interpolate, self._y_interpolate)
|
|
392
|
+
nan_heatmap = f_nan( self._x_interpolate, self._y_interpolate )
|
|
393
|
+
heatmap[ nan_heatmap>0 ] = np.nan
|
|
394
|
+
|
|
395
|
+
# Rotate heatmap based on tail orientation
|
|
396
|
+
if self.tail_orientation == 'Left' or self.tail_orientation == 'left':
|
|
397
|
+
output_heatmap = heatmap
|
|
398
|
+
elif self.tail_orientation == 'Up' or self.tail_orientation == 'up':
|
|
399
|
+
output_heatmap = np.rot90(heatmap, 1)
|
|
400
|
+
elif self.tail_orientation == 'Right' or self.tail_orientation == 'right':
|
|
401
|
+
output_heatmap = np.rot90(heatmap, 2)
|
|
402
|
+
elif self.tail_orientation == 'Down' or self.tail_orientation == 'down':
|
|
403
|
+
output_heatmap = np.rot90(heatmap, 3)
|
|
404
|
+
|
|
405
|
+
self._add_final = 0
|
|
406
|
+
|
|
407
|
+
self.window_buffer = np.hstack((self.window_buffer[:,self.window_rms_size:], np.zeros(((self._EMG_chans+len(self.dummy_chan)), self.window_rms_size)) ))
|
|
408
|
+
self.output.emit(output_heatmap)
|
|
409
|
+
|
|
410
|
+
else:
|
|
411
|
+
self._add_final += self.new_samples
|
|
412
|
+
|
|
413
|
+
# Pause the thread so that the update does not happen too fast
|
|
414
|
+
time.sleep(0.01)
|
|
415
|
+
|
|
416
|
+
def stop(self):
|
|
417
|
+
""" Method that is executed when the thread is terminated.
|
|
418
|
+
This stop event stops the measurement and closes the connection to
|
|
419
|
+
the device.
|
|
420
|
+
"""
|
|
421
|
+
# self.device.stop_measurement()
|
|
422
|
+
self.sampling = False
|
|
423
|
+
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
if __name__ == "__main__":
|
|
427
|
+
# Initialise the TMSi-SDK first before starting using it
|
|
428
|
+
tmsi_device.initialize()
|
|
429
|
+
|
|
430
|
+
# Create the device object to interface with the SAGA-system.
|
|
431
|
+
dev = tmsi_device.create(tmsi_device.DeviceType.saga, DeviceInterfaceType.docked, DeviceInterfaceType.usb)
|
|
432
|
+
|
|
433
|
+
# Find and open a connection to the SAGA-system and print its serial number
|
|
434
|
+
dev.open()
|
|
435
|
+
print("handle 1 " + str(dev.info.ds_serial_number))
|
|
436
|
+
|
|
437
|
+
# Initialise the application
|
|
438
|
+
app = QtWidgets.QApplication(sys.argv)
|
|
439
|
+
# Define the GUI object and show it
|
|
440
|
+
window = HDEMGPlot(figurename = 'An HDEMG Heatmap Plot', device = dev, tail_orientation='up')
|
|
441
|
+
window.show()
|
|
442
|
+
|
|
443
|
+
# Enter the event loop
|
|
444
|
+
# sys.exit(app.exec_())
|
|
445
|
+
app.exec_()
|
|
446
|
+
dev.close()
|