py-neuromodulation 0.0.3__py3-none-any.whl → 0.0.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (176) hide show
  1. py_neuromodulation/ConnectivityDecoding/Automated Anatomical Labeling 3 (Rolls 2020).nii +0 -0
  2. py_neuromodulation/ConnectivityDecoding/_get_grid_hull.m +34 -0
  3. py_neuromodulation/ConnectivityDecoding/_get_grid_whole_brain.py +106 -0
  4. py_neuromodulation/ConnectivityDecoding/_helper_write_connectome.py +119 -0
  5. py_neuromodulation/ConnectivityDecoding/mni_coords_cortical_surface.mat +0 -0
  6. py_neuromodulation/ConnectivityDecoding/mni_coords_whole_brain.mat +0 -0
  7. py_neuromodulation/ConnectivityDecoding/rmap_func_all.nii +0 -0
  8. py_neuromodulation/ConnectivityDecoding/rmap_struc.nii +0 -0
  9. py_neuromodulation/data/README +6 -0
  10. py_neuromodulation/data/dataset_description.json +8 -0
  11. py_neuromodulation/data/participants.json +32 -0
  12. py_neuromodulation/data/participants.tsv +2 -0
  13. py_neuromodulation/data/sub-testsub/ses-EphysMedOff/ieeg/sub-testsub_ses-EphysMedOff_space-mni_coordsystem.json +5 -0
  14. py_neuromodulation/data/sub-testsub/ses-EphysMedOff/ieeg/sub-testsub_ses-EphysMedOff_space-mni_electrodes.tsv +11 -0
  15. py_neuromodulation/data/sub-testsub/ses-EphysMedOff/ieeg/sub-testsub_ses-EphysMedOff_task-gripforce_run-0_channels.tsv +11 -0
  16. py_neuromodulation/data/sub-testsub/ses-EphysMedOff/ieeg/sub-testsub_ses-EphysMedOff_task-gripforce_run-0_ieeg.eeg +0 -0
  17. py_neuromodulation/data/sub-testsub/ses-EphysMedOff/ieeg/sub-testsub_ses-EphysMedOff_task-gripforce_run-0_ieeg.json +18 -0
  18. py_neuromodulation/data/sub-testsub/ses-EphysMedOff/ieeg/sub-testsub_ses-EphysMedOff_task-gripforce_run-0_ieeg.vhdr +35 -0
  19. py_neuromodulation/data/sub-testsub/ses-EphysMedOff/ieeg/sub-testsub_ses-EphysMedOff_task-gripforce_run-0_ieeg.vmrk +13 -0
  20. py_neuromodulation/data/sub-testsub/ses-EphysMedOff/sub-testsub_ses-EphysMedOff_scans.tsv +2 -0
  21. py_neuromodulation/grid_cortex.tsv +40 -0
  22. py_neuromodulation/grid_subcortex.tsv +1429 -0
  23. py_neuromodulation/nm_settings.json +338 -0
  24. py_neuromodulation/nm_stream_offline.py +7 -6
  25. py_neuromodulation/plots/STN_surf.mat +0 -0
  26. py_neuromodulation/plots/Vertices.mat +0 -0
  27. py_neuromodulation/plots/faces.mat +0 -0
  28. py_neuromodulation/plots/grid.mat +0 -0
  29. {py_neuromodulation-0.0.3.dist-info → py_neuromodulation-0.0.4.dist-info}/METADATA +182 -182
  30. py_neuromodulation-0.0.4.dist-info/RECORD +72 -0
  31. {py_neuromodulation-0.0.3.dist-info → py_neuromodulation-0.0.4.dist-info}/WHEEL +1 -2
  32. docs/build/_downloads/09df217f95985497f45d69e2d4bdc5b1/plot_2_example_add_feature.py +0 -68
  33. docs/build/_downloads/3b4900a2b2818ff30362215b76f7d5eb/plot_1_example_BIDS.py +0 -233
  34. docs/build/_downloads/7e92dd2e6cc86b239d14cafad972ae4f/plot_3_example_sharpwave_analysis.py +0 -219
  35. docs/build/_downloads/c2db0bf2b334d541b00662b991682256/plot_6_real_time_demo.py +0 -97
  36. docs/build/_downloads/ce3914826f782cbd1ea8fd024eaf0ac3/plot_5_example_rmap_computing.py +0 -64
  37. docs/build/_downloads/da36848a41e6a3235d91fb7cfb6d59b4/plot_0_first_demo.py +0 -192
  38. docs/build/_downloads/eaa4305c75b19a1e2eea941f742a6331/plot_4_example_gridPointProjection.py +0 -210
  39. docs/build/html/_downloads/09df217f95985497f45d69e2d4bdc5b1/plot_2_example_add_feature.py +0 -68
  40. docs/build/html/_downloads/3b4900a2b2818ff30362215b76f7d5eb/plot_1_example_BIDS.py +0 -239
  41. docs/build/html/_downloads/7e92dd2e6cc86b239d14cafad972ae4f/plot_3_example_sharpwave_analysis.py +0 -219
  42. docs/build/html/_downloads/c2db0bf2b334d541b00662b991682256/plot_6_real_time_demo.py +0 -97
  43. docs/build/html/_downloads/ce3914826f782cbd1ea8fd024eaf0ac3/plot_5_example_rmap_computing.py +0 -64
  44. docs/build/html/_downloads/da36848a41e6a3235d91fb7cfb6d59b4/plot_0_first_demo.py +0 -192
  45. docs/build/html/_downloads/eaa4305c75b19a1e2eea941f742a6331/plot_4_example_gridPointProjection.py +0 -210
  46. docs/source/_build/html/_downloads/09df217f95985497f45d69e2d4bdc5b1/plot_2_example_add_feature.py +0 -76
  47. docs/source/_build/html/_downloads/0d0d0a76e8f648d5d3cbc47da6351932/plot_real_time_demo.py +0 -97
  48. docs/source/_build/html/_downloads/3b4900a2b2818ff30362215b76f7d5eb/plot_1_example_BIDS.py +0 -240
  49. docs/source/_build/html/_downloads/5d73cadc59a8805c47e3b84063afc157/plot_example_BIDS.py +0 -233
  50. docs/source/_build/html/_downloads/7660317fa5a6bfbd12fcca9961457fc4/plot_example_rmap_computing.py +0 -63
  51. docs/source/_build/html/_downloads/7e92dd2e6cc86b239d14cafad972ae4f/plot_3_example_sharpwave_analysis.py +0 -219
  52. docs/source/_build/html/_downloads/839e5b319379f7fd9e867deb00fd797f/plot_example_gridPointProjection.py +0 -210
  53. docs/source/_build/html/_downloads/ae8be19afe5e559f011fc9b138968ba0/plot_first_demo.py +0 -192
  54. docs/source/_build/html/_downloads/b8b06cacc17969d3725a0b6f1d7741c5/plot_example_sharpwave_analysis.py +0 -219
  55. docs/source/_build/html/_downloads/c2db0bf2b334d541b00662b991682256/plot_6_real_time_demo.py +0 -121
  56. docs/source/_build/html/_downloads/c31a86c0b68cb4167d968091ace8080d/plot_example_add_feature.py +0 -68
  57. docs/source/_build/html/_downloads/ce3914826f782cbd1ea8fd024eaf0ac3/plot_5_example_rmap_computing.py +0 -64
  58. docs/source/_build/html/_downloads/da36848a41e6a3235d91fb7cfb6d59b4/plot_0_first_demo.py +0 -189
  59. docs/source/_build/html/_downloads/eaa4305c75b19a1e2eea941f742a6331/plot_4_example_gridPointProjection.py +0 -210
  60. docs/source/auto_examples/plot_0_first_demo.py +0 -189
  61. docs/source/auto_examples/plot_1_example_BIDS.py +0 -240
  62. docs/source/auto_examples/plot_2_example_add_feature.py +0 -76
  63. docs/source/auto_examples/plot_3_example_sharpwave_analysis.py +0 -219
  64. docs/source/auto_examples/plot_4_example_gridPointProjection.py +0 -210
  65. docs/source/auto_examples/plot_5_example_rmap_computing.py +0 -64
  66. docs/source/auto_examples/plot_6_real_time_demo.py +0 -121
  67. docs/source/conf.py +0 -105
  68. examples/plot_0_first_demo.py +0 -189
  69. examples/plot_1_example_BIDS.py +0 -240
  70. examples/plot_2_example_add_feature.py +0 -76
  71. examples/plot_3_example_sharpwave_analysis.py +0 -219
  72. examples/plot_4_example_gridPointProjection.py +0 -210
  73. examples/plot_5_example_rmap_computing.py +0 -64
  74. examples/plot_6_real_time_demo.py +0 -121
  75. packages/realtime_decoding/build/lib/realtime_decoding/__init__.py +0 -4
  76. packages/realtime_decoding/build/lib/realtime_decoding/decoder.py +0 -104
  77. packages/realtime_decoding/build/lib/realtime_decoding/features.py +0 -163
  78. packages/realtime_decoding/build/lib/realtime_decoding/helpers.py +0 -15
  79. packages/realtime_decoding/build/lib/realtime_decoding/run_decoding.py +0 -345
  80. packages/realtime_decoding/build/lib/realtime_decoding/trainer.py +0 -54
  81. packages/tmsi/build/lib/TMSiFileFormats/__init__.py +0 -37
  82. packages/tmsi/build/lib/TMSiFileFormats/file_formats/__init__.py +0 -36
  83. packages/tmsi/build/lib/TMSiFileFormats/file_formats/lsl_stream_writer.py +0 -200
  84. packages/tmsi/build/lib/TMSiFileFormats/file_formats/poly5_file_writer.py +0 -496
  85. packages/tmsi/build/lib/TMSiFileFormats/file_formats/poly5_to_edf_converter.py +0 -236
  86. packages/tmsi/build/lib/TMSiFileFormats/file_formats/xdf_file_writer.py +0 -977
  87. packages/tmsi/build/lib/TMSiFileFormats/file_readers/__init__.py +0 -35
  88. packages/tmsi/build/lib/TMSiFileFormats/file_readers/edf_reader.py +0 -116
  89. packages/tmsi/build/lib/TMSiFileFormats/file_readers/poly5reader.py +0 -294
  90. packages/tmsi/build/lib/TMSiFileFormats/file_readers/xdf_reader.py +0 -229
  91. packages/tmsi/build/lib/TMSiFileFormats/file_writer.py +0 -102
  92. packages/tmsi/build/lib/TMSiPlotters/__init__.py +0 -2
  93. packages/tmsi/build/lib/TMSiPlotters/gui/__init__.py +0 -39
  94. packages/tmsi/build/lib/TMSiPlotters/gui/_plotter_gui.py +0 -234
  95. packages/tmsi/build/lib/TMSiPlotters/gui/plotting_gui.py +0 -440
  96. packages/tmsi/build/lib/TMSiPlotters/plotters/__init__.py +0 -44
  97. packages/tmsi/build/lib/TMSiPlotters/plotters/hd_emg_plotter.py +0 -446
  98. packages/tmsi/build/lib/TMSiPlotters/plotters/impedance_plotter.py +0 -589
  99. packages/tmsi/build/lib/TMSiPlotters/plotters/signal_plotter.py +0 -1326
  100. packages/tmsi/build/lib/TMSiSDK/__init__.py +0 -54
  101. packages/tmsi/build/lib/TMSiSDK/device.py +0 -588
  102. packages/tmsi/build/lib/TMSiSDK/devices/__init__.py +0 -34
  103. packages/tmsi/build/lib/TMSiSDK/devices/saga/TMSi_Device_API.py +0 -1764
  104. packages/tmsi/build/lib/TMSiSDK/devices/saga/__init__.py +0 -34
  105. packages/tmsi/build/lib/TMSiSDK/devices/saga/saga_device.py +0 -1366
  106. packages/tmsi/build/lib/TMSiSDK/devices/saga/saga_types.py +0 -520
  107. packages/tmsi/build/lib/TMSiSDK/devices/saga/xml_saga_config.py +0 -165
  108. packages/tmsi/build/lib/TMSiSDK/error.py +0 -95
  109. packages/tmsi/build/lib/TMSiSDK/sample_data.py +0 -63
  110. packages/tmsi/build/lib/TMSiSDK/sample_data_server.py +0 -99
  111. packages/tmsi/build/lib/TMSiSDK/settings.py +0 -45
  112. packages/tmsi/build/lib/TMSiSDK/tmsi_device.py +0 -111
  113. packages/tmsi/build/lib/__init__.py +0 -4
  114. packages/tmsi/build/lib/apex_sdk/__init__.py +0 -34
  115. packages/tmsi/build/lib/apex_sdk/device/__init__.py +0 -41
  116. packages/tmsi/build/lib/apex_sdk/device/devices/apex/apex_API.py +0 -1009
  117. packages/tmsi/build/lib/apex_sdk/device/devices/apex/apex_API_enums.py +0 -239
  118. packages/tmsi/build/lib/apex_sdk/device/devices/apex/apex_API_structures.py +0 -668
  119. packages/tmsi/build/lib/apex_sdk/device/devices/apex/apex_device.py +0 -1611
  120. packages/tmsi/build/lib/apex_sdk/device/devices/apex/apex_dongle.py +0 -38
  121. packages/tmsi/build/lib/apex_sdk/device/devices/apex/apex_event_reader.py +0 -57
  122. packages/tmsi/build/lib/apex_sdk/device/devices/apex/apex_structures/apex_channel.py +0 -44
  123. packages/tmsi/build/lib/apex_sdk/device/devices/apex/apex_structures/apex_config.py +0 -150
  124. packages/tmsi/build/lib/apex_sdk/device/devices/apex/apex_structures/apex_const.py +0 -36
  125. packages/tmsi/build/lib/apex_sdk/device/devices/apex/apex_structures/apex_impedance_channel.py +0 -48
  126. packages/tmsi/build/lib/apex_sdk/device/devices/apex/apex_structures/apex_info.py +0 -108
  127. packages/tmsi/build/lib/apex_sdk/device/devices/apex/apex_structures/dongle_info.py +0 -39
  128. packages/tmsi/build/lib/apex_sdk/device/devices/apex/measurements/download_measurement.py +0 -77
  129. packages/tmsi/build/lib/apex_sdk/device/devices/apex/measurements/eeg_measurement.py +0 -150
  130. packages/tmsi/build/lib/apex_sdk/device/devices/apex/measurements/impedance_measurement.py +0 -129
  131. packages/tmsi/build/lib/apex_sdk/device/threads/conversion_thread.py +0 -59
  132. packages/tmsi/build/lib/apex_sdk/device/threads/sampling_thread.py +0 -57
  133. packages/tmsi/build/lib/apex_sdk/device/tmsi_channel.py +0 -83
  134. packages/tmsi/build/lib/apex_sdk/device/tmsi_device.py +0 -201
  135. packages/tmsi/build/lib/apex_sdk/device/tmsi_device_enums.py +0 -103
  136. packages/tmsi/build/lib/apex_sdk/device/tmsi_dongle.py +0 -43
  137. packages/tmsi/build/lib/apex_sdk/device/tmsi_event_reader.py +0 -50
  138. packages/tmsi/build/lib/apex_sdk/device/tmsi_measurement.py +0 -118
  139. packages/tmsi/build/lib/apex_sdk/sample_data_server/__init__.py +0 -33
  140. packages/tmsi/build/lib/apex_sdk/sample_data_server/event_data.py +0 -44
  141. packages/tmsi/build/lib/apex_sdk/sample_data_server/sample_data.py +0 -50
  142. packages/tmsi/build/lib/apex_sdk/sample_data_server/sample_data_server.py +0 -136
  143. packages/tmsi/build/lib/apex_sdk/tmsi_errors/error.py +0 -126
  144. packages/tmsi/build/lib/apex_sdk/tmsi_sdk.py +0 -113
  145. packages/tmsi/build/lib/apex_sdk/tmsi_utilities/apex/apex_structure_generator.py +0 -134
  146. packages/tmsi/build/lib/apex_sdk/tmsi_utilities/decorators.py +0 -60
  147. packages/tmsi/build/lib/apex_sdk/tmsi_utilities/logger_filter.py +0 -42
  148. packages/tmsi/build/lib/apex_sdk/tmsi_utilities/singleton.py +0 -42
  149. packages/tmsi/build/lib/apex_sdk/tmsi_utilities/support_functions.py +0 -72
  150. packages/tmsi/build/lib/apex_sdk/tmsi_utilities/tmsi_logger.py +0 -98
  151. py_neuromodulation-0.0.3.dist-info/RECORD +0 -188
  152. py_neuromodulation-0.0.3.dist-info/top_level.txt +0 -5
  153. tests/__init__.py +0 -0
  154. tests/conftest.py +0 -117
  155. tests/test_all_examples.py +0 -10
  156. tests/test_all_features.py +0 -63
  157. tests/test_bispectra.py +0 -70
  158. tests/test_bursts.py +0 -105
  159. tests/test_feature_sampling_rates.py +0 -143
  160. tests/test_fooof.py +0 -16
  161. tests/test_initalization_offline_stream.py +0 -41
  162. tests/test_multiprocessing.py +0 -58
  163. tests/test_nan_values.py +0 -29
  164. tests/test_nm_filter.py +0 -95
  165. tests/test_nm_resample.py +0 -63
  166. tests/test_normalization_settings.py +0 -146
  167. tests/test_notch_filter.py +0 -31
  168. tests/test_osc_features.py +0 -424
  169. tests/test_preprocessing_filter.py +0 -151
  170. tests/test_rereference.py +0 -171
  171. tests/test_sampling.py +0 -57
  172. tests/test_settings_change_after_init.py +0 -76
  173. tests/test_sharpwave.py +0 -165
  174. tests/test_target_channel_add.py +0 -100
  175. tests/test_timing.py +0 -80
  176. {py_neuromodulation-0.0.3.dist-info → py_neuromodulation-0.0.4.dist-info/licenses}/LICENSE +0 -0
@@ -1,1326 +0,0 @@
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 ${signal_plotter.py}
26
- * @brief Plotter object that displays incoming sample data in real-time.
27
- *
28
- */
29
-
30
-
31
- """
32
-
33
- from PySide2 import QtGui, QtCore, QtWidgets
34
- from PySide2.QtCore import Qt
35
- import numpy as np
36
- import pyqtgraph as pg
37
- import time
38
- import queue
39
- from copy import copy
40
- import sys
41
-
42
- from os.path import join, dirname, realpath, normpath, exists
43
-
44
- Plotter_dir = dirname(realpath(__file__)) # directory of this file
45
- measurements_dir = join(
46
- Plotter_dir, "../../measurements"
47
- ) # directory with all measurements
48
- modules_dir = normpath(
49
- join(Plotter_dir, "../..")
50
- ) # directory with all modules
51
-
52
- from TMSiSDK import tmsi_device
53
- from TMSiSDK import sample_data_server
54
- from TMSiSDK.device import DeviceInterfaceType, ChannelType
55
-
56
- from apex_sdk.device.tmsi_device import TMSiDevice
57
- from apex_sdk.sample_data_server.sample_data_server import (
58
- SampleDataServer as ApexSampleDataServer,
59
- )
60
-
61
-
62
- class SignalViewer:
63
- """A GUI that displays the signals on the screen. The GUI handles the
64
- incoming data and is able to apply scaling. Furthermore, the GUI handles
65
- closing the device when it is closed.
66
- """
67
-
68
- def __init__(self, window, device, filter_app=None, grid_type="none"):
69
- """Setting up the GUI's elements."""
70
- self.gui_handle = window
71
- self.RealTimePlotWidget = self.gui_handle.RealTimePlotWidget
72
-
73
- # Pass the device handle so that it is accesible to the GUI
74
- self.device = device
75
- self.filter_app = filter_app
76
- self.grid_type = grid_type
77
-
78
- # Set up UI and thread
79
- self.initUI()
80
- self.live_impedance = False
81
- if isinstance(self.device, TMSiDevice):
82
- if self.device.get_device_sampling_config().LiveImpedance:
83
- self.init_impedance_table()
84
- self.live_impedance = True
85
- self.setupThread()
86
-
87
- def initUI(self):
88
- """Method responsible for constructing the basic elements in the plot"""
89
- # Set view settings
90
- self.RealTimePlotWidget.setBackground("w")
91
- self.RealTimePlotWidget.window = self.RealTimePlotWidget.addPlot()
92
- self.RealTimePlotWidget.window.showGrid(x=True, y=True, alpha=0.5)
93
- self.RealTimePlotWidget.window.setLabel("bottom", "Time", units="sec")
94
- self.RealTimePlotWidget.window.getViewBox().invertY(True)
95
-
96
- self.channel_conversion_list = (
97
- self.gui_handle.active_channel_conversion_list
98
- )
99
-
100
- # Configuration settings
101
- self.num_channels = np.size(self.gui_handle._channel_selection, 0)
102
- if isinstance(self.device, TMSiDevice):
103
- self.active_channels = np.size(
104
- self.device.get_device_channels(), 0
105
- )
106
- self.sample_rate = self.device.get_device_sampling_frequency()
107
- else:
108
- self.active_channels = np.size(self.device.channels, 0)
109
- self.sample_rate = self.device.config.get_sample_rate(
110
- ChannelType.counter
111
- )
112
- self.window_size = 5 # seconds
113
-
114
- self._downsampling_factor = int(self.sample_rate / 500)
115
-
116
- # Virtual offset for all channels
117
- self._plot_offset = 3
118
- # Dictionary used for storing the scaling factors
119
- self._plot_diff = [
120
- {"mean": 0, "diff": 2**31} for i in range(self.num_channels)
121
- ]
122
- self._plot_diff[-1]["diff"] = 1
123
- if isinstance(self.device, TMSiDevice):
124
- self._plot_diff[-1]["mean"] = 32
125
- else:
126
- self._plot_diff[-1]["mean"] = 1024
127
-
128
- if isinstance(self.device, TMSiDevice):
129
- self.chs = [
130
- [
131
- i.get_channel_name(),
132
- i.get_channel_unit_name(),
133
- i.get_channel_type(),
134
- ]
135
- for i in self.device.get_device_active_channels()
136
- ]
137
-
138
- tick_list_left = [[]]
139
- # Create the first instance of the y-axis ticks
140
- if isinstance(self.device, TMSiDevice):
141
- for i in range(self.num_channels):
142
- for j in [-1, 0, 1]:
143
- if i == self.num_channels - 1:
144
- if not bool(j):
145
- tick_list_left[0].append(
146
- (
147
- int(self._plot_offset * i),
148
- f"{self.chs[self.gui_handle._channel_selection[i]][0]: <25}",
149
- )
150
- )
151
- else:
152
- tick_list_left[0].append(
153
- (
154
- int(
155
- self._plot_offset * i
156
- + -j * self._plot_offset / 3
157
- ),
158
- f'{ self._plot_diff[i]["mean"] + (self._plot_diff[i]["diff"] * j ) : .2g}',
159
- )
160
- )
161
- else:
162
- if not bool(j):
163
- tick_list_left[0].append(
164
- (
165
- int(self._plot_offset * i),
166
- f"{self.chs[self.channel_conversion_list[self.gui_handle._channel_selection[i]]][0]: <25}",
167
- )
168
- )
169
- else:
170
- tick_list_left[0].append(
171
- (
172
- int(
173
- self._plot_offset * i
174
- + -j * self._plot_offset / 3
175
- ),
176
- f'{ self._plot_diff[i]["mean"] + (self._plot_diff[i]["diff"] * j ) : .2g}',
177
- )
178
- )
179
-
180
- # Display the unit name on the right-side y-axis
181
- tick_list_right = [
182
- [
183
- (
184
- self._plot_offset * i,
185
- self.chs[self.gui_handle._channel_selection[i]][1],
186
- )
187
- for i in range(self.num_channels)
188
- ]
189
- ]
190
- else:
191
- for i in range(self.num_channels):
192
- for j in [-1, 0, 1]:
193
- if i == self.num_channels - 1:
194
- if not bool(j):
195
- tick_list_left[0].append(
196
- (
197
- int(self._plot_offset * i),
198
- f"{self.device.channels[self.gui_handle._channel_selection[i]].name: <25}",
199
- )
200
- )
201
- else:
202
- tick_list_left[0].append(
203
- (
204
- int(
205
- self._plot_offset * i
206
- + -j * self._plot_offset / 3
207
- ),
208
- f'{ self._plot_diff[i]["mean"] + (self._plot_diff[i]["diff"] * j ) : .2g}',
209
- )
210
- )
211
- else:
212
- if not bool(j):
213
- tick_list_left[0].append(
214
- (
215
- int(self._plot_offset * i),
216
- f"{self.device.channels[self.channel_conversion_list[self.gui_handle._channel_selection[i]]].name: <25}",
217
- )
218
- )
219
- else:
220
- tick_list_left[0].append(
221
- (
222
- int(
223
- self._plot_offset * i
224
- + -j * self._plot_offset / 3
225
- ),
226
- f'{ self._plot_diff[i]["mean"] + (self._plot_diff[i]["diff"] * j ) : .2g}',
227
- )
228
- )
229
-
230
- # Display the unit name on the right-side y-axis
231
- tick_list_right = [
232
- [
233
- (
234
- self._plot_offset * i,
235
- self.device.channels[
236
- self.gui_handle._channel_selection[i]
237
- ].unit_name,
238
- )
239
- for i in range(self.num_channels)
240
- ]
241
- ]
242
-
243
- # Write the ticks to the plot
244
- self.RealTimePlotWidget.window.getAxis("left").setTicks(tick_list_left)
245
- self.RealTimePlotWidget.window.getAxis("right").setTicks(
246
- tick_list_right
247
- )
248
-
249
- # Disable auto-scaling and menu
250
- self.RealTimePlotWidget.window.hideButtons()
251
- self.RealTimePlotWidget.window.setMenuEnabled(False)
252
- self.RealTimePlotWidget.window.setMouseEnabled(x=False, y=False)
253
-
254
- # Update the ranges to be shown
255
- self.RealTimePlotWidget.window.showAxis("right")
256
- self.RealTimePlotWidget.window.setYRange(
257
- -self._plot_offset / 3,
258
- (self.num_channels - 2 / 3) * self._plot_offset,
259
- )
260
- self.RealTimePlotWidget.window.setXRange(
261
- 0.02 * self.window_size, 0.97 * self.window_size
262
- )
263
-
264
- # Create curves and set the position of the curve on the y-axis for each channel
265
- self.curve = []
266
- for i in range(self.num_channels):
267
- self.c = pg.PlotCurveItem()
268
- self.c.setPen(
269
- "black",
270
- cosmetic=True,
271
- joinStyle=QtCore.Qt.MiterJoin,
272
- )
273
- self.RealTimePlotWidget.window.addItem(self.c)
274
- self.c.setPos(0, (i) * self._plot_offset)
275
- self.curve.append(self.c)
276
-
277
- # Initialise buffer for the plotter (maximum of 10 seconds)
278
- self._buffer_size = 10 # seconds
279
- self.window_buffer = (
280
- np.ones(
281
- (
282
- self.active_channels,
283
- int(np.ceil(self.sample_rate * self._buffer_size)),
284
- )
285
- )
286
- * np.nan
287
- )
288
- self.samples_seen = 0
289
-
290
- def init_impedance_table(self):
291
- self.gui_handle.table_live_impedance.setColumnCount(2)
292
- self.gui_handle.table_live_impedance.setHorizontalHeaderLabels(
293
- ["Name", "Impedance"]
294
- )
295
- self.gui_handle.table_live_impedance.setRowCount(len(self.chs) - 5)
296
-
297
- for i, channel in enumerate(self.chs):
298
- if i < len(self.chs) - 5:
299
- alt_name = QtWidgets.QTableWidgetItem(
300
- self.chs[self.channel_conversion_list[i]][0]
301
- )
302
- alt_name.setFlags(Qt.ItemIsEnabled)
303
- real = QtWidgets.QTableWidgetItem("kOhm")
304
- real.setFlags(Qt.ItemIsEnabled)
305
- self.gui_handle.table_live_impedance.setItem(i, 0, alt_name)
306
- self.gui_handle.table_live_impedance.setItem(i, 1, real)
307
-
308
- def _decrease_time_range(self):
309
- """Method that decreases the amount of time that is displayed within the
310
- window.
311
- """
312
- # Minimum window size is 1 second
313
- if self.window_size > 1:
314
- self.window_size -= 1
315
- self.worker.window_size -= 1
316
-
317
- self.RealTimePlotWidget.window.setXRange(
318
- 0.02 * self.window_size, 0.97 * self.window_size
319
- )
320
-
321
- self.gui_handle.increase_time_button.setEnabled(True)
322
- self.gui_handle.increase_time_button.setText(
323
- "Increase time range: " + str(self.window_size + 1) + "s"
324
- )
325
- if self.window_size == 1:
326
- self.gui_handle.decrease_time_button.setEnabled(False)
327
- else:
328
- self.gui_handle.decrease_time_button.setEnabled(True)
329
- self.gui_handle.decrease_time_button.setText(
330
- "Decrease time range: " + str(self.window_size - 1) + "s"
331
- )
332
-
333
- def _increase_time_range(self):
334
- """Method that increases the amount of time that is displayed within the
335
- window.
336
- """
337
- # Maximum window size is defined by the _buffer_size parameter
338
- if self.window_size < self._buffer_size:
339
- self.window_size += 1
340
- self.worker.window_size += 1
341
-
342
- self.RealTimePlotWidget.window.setXRange(
343
- 0.02 * self.window_size, 0.97 * self.window_size
344
- )
345
-
346
- self.gui_handle.decrease_time_button.setEnabled(True)
347
- self.gui_handle.decrease_time_button.setText(
348
- "Decrease time range: " + str(self.window_size - 1) + "s"
349
- )
350
- if self.window_size == self._buffer_size:
351
- self.gui_handle.increase_time_button.setEnabled(False)
352
- else:
353
- self.gui_handle.increase_time_button.setEnabled(True)
354
- self.gui_handle.increase_time_button.setText(
355
- "Increase time range: " + str(self.window_size + 1) + "s"
356
- )
357
-
358
- def _update_scale(self, type_flag):
359
- """Method responsible for updating the scale whenever user input is
360
- provided to do so.
361
- """
362
- # Find white-out region using the Status channel (can't be 0)
363
- x, y = self.curve[-1].getData()
364
- # Transformed data is plotted so convert back to original values for correct
365
- # display of the values on the y-axis
366
- y = y * -1 * self._plot_diff[-1]["diff"] + self._plot_diff[-1]["mean"]
367
-
368
- # The white-out region is entered in the plot as zeros, which have to be
369
- # omitted from the scaling calculation
370
- idx_data = np.ones(np.size(y), dtype=bool)
371
- idx_whiteout = np.where(np.abs(y) == 0)[0]
372
- idx_data[idx_whiteout] = False
373
-
374
- # Loop over all channels to find individual scaling factors
375
- for i in range(self.num_channels):
376
- x, y = self.curve[i].getData()
377
-
378
- # Convert to original sample data
379
- y = (
380
- y * -1 * self._plot_diff[i]["diff"]
381
- + self._plot_diff[i]["mean"]
382
- )
383
-
384
- # Find the mean and the difference between minimum and maximum
385
- self._plot_diff[i]["mean"] = (
386
- np.max(y[idx_data]) + np.min(y[idx_data])
387
- ) / 2
388
- if type_flag == "scale":
389
- self._plot_diff[i]["diff"] = np.abs(
390
- np.max(y[idx_data]) - np.min(y[idx_data])
391
- )
392
- elif type_flag == "range":
393
- self._plot_diff[i]["diff"] = int(
394
- self.gui_handle.set_range_box.currentText()
395
- )
396
-
397
- # Whenever there is no difference, the difference is reset to 2^31
398
- if self._plot_diff[i]["diff"] == 0:
399
- self._plot_diff[i]["diff"] = 2**31
400
- if i == self.num_channels - 1:
401
- self._plot_diff[i]["diff"] = 1
402
-
403
- # Update the range of the displayed y-axis range
404
- self.RealTimePlotWidget.window.setYRange(
405
- -self._plot_offset / 3,
406
- (self.num_channels - 2 / 3) * self._plot_offset,
407
- )
408
-
409
- # Update the list of ticks on the y-axis for all channels
410
- tick_list_left = [[]]
411
-
412
- if isinstance(self.device, TMSiDevice):
413
- for i in range(self.num_channels):
414
- for j in [-1, 0, 1]:
415
- if i == self.num_channels - 1:
416
- if not bool(j):
417
- tick_list_left[0].append(
418
- (
419
- int(self._plot_offset * i),
420
- f"{self.chs[self.gui_handle._channel_selection[i]][0]: <25}",
421
- )
422
- )
423
- else:
424
- tick_list_left[0].append(
425
- (
426
- int(
427
- self._plot_offset * i
428
- + -j * self._plot_offset / 3
429
- ),
430
- f'{ self._plot_diff[i]["mean"] + (self._plot_diff[i]["diff"] * j ) : .2g}',
431
- )
432
- )
433
- else:
434
- if not bool(j):
435
- tick_list_left[0].append(
436
- (
437
- int(self._plot_offset * i),
438
- f"{self.chs[self.channel_conversion_list[self.gui_handle._channel_selection[i]]][0]: <25}",
439
- )
440
- )
441
- else:
442
- tick_list_left[0].append(
443
- (
444
- int(
445
- self._plot_offset * i
446
- + -j * self._plot_offset / 3
447
- ),
448
- f'{ self._plot_diff[i]["mean"] + (self._plot_diff[i]["diff"] * j ) : .2g}',
449
- )
450
- )
451
- else:
452
- for i in range(self.num_channels):
453
- for j in [-1, 0, 1]:
454
- if i == self.num_channels - 1:
455
- if not bool(j):
456
- tick_list_left[0].append(
457
- (
458
- int(self._plot_offset * i),
459
- f"{self.device.channels[self.gui_handle._channel_selection[i]].name: <25}",
460
- )
461
- )
462
- else:
463
- tick_list_left[0].append(
464
- (
465
- int(
466
- self._plot_offset * i
467
- + -j * self._plot_offset / 3
468
- ),
469
- f'{ self._plot_diff[i]["mean"] + (self._plot_diff[i]["diff"] * j ) : .2g}',
470
- )
471
- )
472
- else:
473
- if not bool(j):
474
- tick_list_left[0].append(
475
- (
476
- int(self._plot_offset * i),
477
- f"{self.device.channels[self.channel_conversion_list[self.gui_handle._channel_selection[i]]].name: <25}",
478
- )
479
- )
480
- else:
481
- tick_list_left[0].append(
482
- (
483
- int(
484
- self._plot_offset * i
485
- + -j * self._plot_offset / 3
486
- ),
487
- f'{ self._plot_diff[i]["mean"] + (self._plot_diff[i]["diff"] * j ) : .2g}',
488
- )
489
- )
490
-
491
- self.RealTimePlotWidget.window.getAxis("left").setTicks(tick_list_left)
492
-
493
- def _update_channel_display(self):
494
- """Method that updates which channels are displayed."""
495
-
496
- # Make a copy of the current channel indices that are displayed
497
- _idx_curve_list = np.copy(self.gui_handle._channel_selection)
498
-
499
- # Update the channel selection based on the clicked checkboxes
500
- for i in range(np.size(self.gui_handle._checkboxes, 0)):
501
- if self.gui_handle._checkboxes[i][0].isChecked():
502
- if i in self.gui_handle._channel_selection:
503
- pass
504
- else:
505
- # Create a flag that states if the number of channels displayed increases
506
- _increase = True
507
- self.gui_handle._channel_selection = np.hstack(
508
- (self.gui_handle._channel_selection, i)
509
- )
510
- else:
511
- if i in self.gui_handle._channel_selection:
512
- # Create a flag that states if the number of channels displayed decreases
513
- _increase = False
514
- self.gui_handle._channel_selection = np.delete(
515
- self.gui_handle._channel_selection,
516
- self.gui_handle._channel_selection == i,
517
- )
518
- else:
519
- pass
520
-
521
- # Sort the indices from small to large, to update them appropriately in the plot
522
- self.gui_handle._channel_selection.sort()
523
-
524
- # Update the num_channels parameter that keeps track of the amount of channels that are displayed in the plot
525
- self.num_channels = np.size(self.gui_handle._channel_selection, 0)
526
-
527
- # Make a copy of the dictionary containing the scaling parameters of all channels
528
- copy_plot_diff = self._plot_diff.copy()
529
-
530
- # Initialise a counter parameter required for passing the scaling factors
531
- count = 0
532
-
533
- # Create a new dictionary with scaling parameters, which is updated with the old values in the next step
534
- self._plot_diff = [
535
- {"mean": 0, "diff": 2**31} for i in range(self.num_channels)
536
- ]
537
-
538
- # Depending on whether the displayed number of channels increases or decreases, pass the old scaling factors appropriately
539
- if _increase:
540
- _idx_overlap = [
541
- np.where(i == self.gui_handle._channel_selection)[0][0]
542
- for idx, i in enumerate(_idx_curve_list)
543
- if i in self.gui_handle._channel_selection
544
- ]
545
-
546
- for i in range(self.num_channels):
547
- if i in _idx_overlap:
548
- self._plot_diff[i]["mean"] = copy_plot_diff[count]["mean"]
549
- self._plot_diff[i]["diff"] = copy_plot_diff[count]["diff"]
550
- count += 1
551
- else:
552
- _idx_overlap = [
553
- np.where(i == _idx_curve_list)[0][0]
554
- for idx, i in enumerate(_idx_curve_list)
555
- if i in self.gui_handle._channel_selection
556
- ]
557
-
558
- for i in range(self.num_channels):
559
- self._plot_diff[i]["mean"] = copy_plot_diff[
560
- _idx_overlap[count]
561
- ]["mean"]
562
- self._plot_diff[i]["diff"] = copy_plot_diff[
563
- _idx_overlap[count]
564
- ]["diff"]
565
- count += 1
566
-
567
- # Reset the parameter keeping track of the plotted graphis items and clear the window
568
- self.curve = []
569
- self.RealTimePlotWidget.window.clear()
570
-
571
- # Generate the new graphics objects required for plotting
572
- for i in range(self.num_channels):
573
- self.c = pg.PlotCurveItem(pen="black")
574
- self.RealTimePlotWidget.window.addItem(self.c)
575
- self.c.setPos(0, (i) * self._plot_offset)
576
- self.curve.append(self.c)
577
-
578
- # Update the range of the displayed y-axis range so that all channels are included in the plot
579
- self.RealTimePlotWidget.window.setYRange(
580
- -self._plot_offset / 3,
581
- (self.num_channels - 2 / 3) * self._plot_offset,
582
- )
583
-
584
- # Update the list of ticks on the y-axis for all channels
585
- tick_list_left = [[]]
586
-
587
- if isinstance(self.device, TMSiDevice):
588
- for i in range(self.num_channels):
589
- for j in [-1, 0, 1]:
590
- if i == self.num_channels - 1:
591
- if not bool(j):
592
- tick_list_left[0].append(
593
- (
594
- int(self._plot_offset * i),
595
- f"{self.chs[self.gui_handle._channel_selection[i]][0]: <25}",
596
- )
597
- )
598
- else:
599
- tick_list_left[0].append(
600
- (
601
- int(
602
- self._plot_offset * i
603
- + -j * self._plot_offset / 3
604
- ),
605
- f'{ self._plot_diff[i]["mean"] + (self._plot_diff[i]["diff"] * j ) : .2g}',
606
- )
607
- )
608
- else:
609
- if not bool(j):
610
- tick_list_left[0].append(
611
- (
612
- int(self._plot_offset * i),
613
- f"{self.chs[self.channel_conversion_list[self.gui_handle._channel_selection[i]]][0]: <25}",
614
- )
615
- )
616
- else:
617
- tick_list_left[0].append(
618
- (
619
- int(
620
- self._plot_offset * i
621
- + -j * self._plot_offset / 3
622
- ),
623
- f'{ self._plot_diff[i]["mean"] + (self._plot_diff[i]["diff"] * j ) : .2g}',
624
- )
625
- )
626
- else:
627
- for i in range(self.num_channels):
628
- for j in [-1, 0, 1]:
629
- if i == self.num_channels - 1:
630
- if not bool(j):
631
- tick_list_left[0].append(
632
- (
633
- int(self._plot_offset * i),
634
- f"{self.device.channels[self.gui_handle._channel_selection[i]].name: <25}",
635
- )
636
- )
637
- else:
638
- tick_list_left[0].append(
639
- (
640
- int(
641
- self._plot_offset * i
642
- + -j * self._plot_offset / 3
643
- ),
644
- f'{ self._plot_diff[i]["mean"] + (self._plot_diff[i]["diff"] * j ) : .2g}',
645
- )
646
- )
647
- else:
648
- if not bool(j):
649
- tick_list_left[0].append(
650
- (
651
- int(self._plot_offset * i),
652
- f"{self.device.channels[self.channel_conversion_list[self.gui_handle._channel_selection[i]]].name: <25}",
653
- )
654
- )
655
- else:
656
- tick_list_left[0].append(
657
- (
658
- int(
659
- self._plot_offset * i
660
- + -j * self._plot_offset / 3
661
- ),
662
- f'{ self._plot_diff[i]["mean"] + (self._plot_diff[i]["diff"] * j ) : .2g}',
663
- )
664
- )
665
-
666
- self.RealTimePlotWidget.window.getAxis("left").setTicks(tick_list_left)
667
-
668
- def _show_all_UNI(self):
669
- """Method that checks all channels of type UNI so that they are displayed
670
- in the plot
671
- """
672
-
673
- # Find all channel indices that are of type UNI
674
- UNI_lst = [
675
- i
676
- for i in range(np.size(self.gui_handle._checkboxes, 0))
677
- if self.gui_handle._checkboxes[i][1].value == ChannelType.UNI.value
678
- ]
679
-
680
- # When all UNI channels are already checked, do not update the plot
681
- if all(self.gui_handle._checkboxes[i][0].isChecked() for i in UNI_lst):
682
- return
683
-
684
- # Set all UNI channels' checkboxes to the Checked state
685
- for i in range(np.size(self.gui_handle._checkboxes, 0)):
686
- if (
687
- self.gui_handle._checkboxes[i][1].value
688
- == ChannelType.UNI.value
689
- ):
690
- self.gui_handle._checkboxes[i][0].setChecked(True)
691
-
692
- # Call the update function (normally called by individual checkboxes)
693
- self._update_channel_display()
694
-
695
- def _hide_all_UNI(self):
696
- """Method that unchecks all channels of type UNI so that they are not
697
- displayed in the plot
698
- """
699
-
700
- # Find all channel indices that are of type UNI
701
- UNI_lst = [
702
- i
703
- for i in range(np.size(self.gui_handle._checkboxes, 0))
704
- if self.gui_handle._checkboxes[i][1].value == ChannelType.UNI.value
705
- ]
706
-
707
- # When all UNI channels are already unchecked, do not update the plot
708
- if all(
709
- not self.gui_handle._checkboxes[i][0].isChecked() for i in UNI_lst
710
- ):
711
- return
712
-
713
- # Set all UNI channels' checkboxes to the Checked state
714
- for i in range(np.size(self.gui_handle._checkboxes, 0)):
715
- if (
716
- self.gui_handle._checkboxes[i][1].value
717
- == ChannelType.UNI.value
718
- ):
719
- self.gui_handle._checkboxes[i][0].setChecked(False)
720
-
721
- # Call the update function (normally called by individual checkboxes)
722
- self._update_channel_display()
723
-
724
- def _show_all_BIP(self):
725
- """Method that checks all channels of type BIP so that they are displayed
726
- in the plot
727
- """
728
-
729
- # Find all channel indices that are of type BIP
730
- BIP_lst = [
731
- i
732
- for i in range(np.size(self.gui_handle._checkboxes, 0))
733
- if self.gui_handle._checkboxes[i][1].value == ChannelType.BIP.value
734
- ]
735
-
736
- # When all BIP channels are already checked, do not update the plot
737
- if all(self.gui_handle._checkboxes[i][0].isChecked() for i in BIP_lst):
738
- return
739
-
740
- # Set all BIP channels' checkboxes to the Checked state
741
- for i in range(np.size(self.gui_handle._checkboxes, 0)):
742
- if (
743
- self.gui_handle._checkboxes[i][1].value
744
- == ChannelType.BIP.value
745
- ):
746
- self.gui_handle._checkboxes[i][0].setChecked(True)
747
-
748
- # Call the update function (normally called by individual checkboxes)
749
- self._update_channel_display()
750
-
751
- def _hide_all_BIP(self):
752
- """Method that unchecks all channels of type BIP so that they are not
753
- displayed in the plot
754
- """
755
-
756
- # Find all channel indices that are of type BIP
757
- BIP_lst = [
758
- i
759
- for i in range(np.size(self.gui_handle._checkboxes, 0))
760
- if self.gui_handle._checkboxes[i][1].value == ChannelType.BIP.value
761
- ]
762
-
763
- # When all BIP channels are already unchecked, do not update the plot
764
- if all(
765
- not self.gui_handle._checkboxes[i][0].isChecked() for i in BIP_lst
766
- ):
767
- return
768
-
769
- # Set all BIP channels' checkboxes to the Checked state
770
- for i in range(np.size(self.gui_handle._checkboxes, 0)):
771
- if (
772
- self.gui_handle._checkboxes[i][1].value
773
- == ChannelType.BIP.value
774
- ):
775
- self.gui_handle._checkboxes[i][0].setChecked(False)
776
-
777
- # Call the update function (normally called by individual checkboxes)
778
- self._update_channel_display()
779
-
780
- def _show_all_AUX(self):
781
- """Method that checks all channels of type AUX so that they are displayed
782
- in the plot
783
- """
784
-
785
- # Find all channel indices that are of type AUX
786
- AUX_lst = [
787
- i
788
- for i in range(np.size(self.gui_handle._checkboxes, 0))
789
- if self.gui_handle._checkboxes[i][1].value == ChannelType.AUX.value
790
- ]
791
-
792
- # When all AUX channels are already checked, do not update the plot
793
- if all(self.gui_handle._checkboxes[i][0].isChecked() for i in AUX_lst):
794
- return
795
-
796
- # Set all AUX channels' checkboxes to the Checked state
797
- for i in range(np.size(self.gui_handle._checkboxes, 0)):
798
- if (
799
- self.gui_handle._checkboxes[i][1].value
800
- == ChannelType.AUX.value
801
- ):
802
- self.gui_handle._checkboxes[i][0].setChecked(True)
803
-
804
- # Call the update function (normally called by individual checkboxes)
805
- self._update_channel_display()
806
-
807
- def _hide_all_AUX(self):
808
- """Method that unchecks all channels of type AUX so that they are not
809
- displayed in the plot
810
- """
811
-
812
- # Find all channel indices that are of type AUX
813
- AUX_lst = [
814
- i
815
- for i in range(np.size(self.gui_handle._checkboxes, 0))
816
- if self.gui_handle._checkboxes[i][1].value == ChannelType.AUX.value
817
- ]
818
-
819
- # When all AUX channels are already unchecked, do not update the plot
820
- if all(
821
- not self.gui_handle._checkboxes[i][0].isChecked() for i in AUX_lst
822
- ):
823
- return
824
-
825
- # Set all AUX channels' checkboxes to the Checked state
826
- for i in range(np.size(self.gui_handle._checkboxes, 0)):
827
- if (
828
- self.gui_handle._checkboxes[i][1].value
829
- == ChannelType.AUX.value
830
- ):
831
- self.gui_handle._checkboxes[i][0].setChecked(False)
832
-
833
- # Call the update function (normally called by individual checkboxes)
834
- self._update_channel_display()
835
-
836
- def _show_all_DIGI(self):
837
- """Method that checks all channels of type sensor so that they are displayed
838
- in the plot
839
- """
840
-
841
- # Find all channel indices that are of type sensor
842
- DIGI_lst = [
843
- i
844
- for i in range(np.size(self.gui_handle._checkboxes, 0))
845
- if self.gui_handle._checkboxes[i][1].value
846
- == ChannelType.sensor.value
847
- ]
848
-
849
- # When all sensor channels are already checked, do not update the plot
850
- if all(
851
- self.gui_handle._checkboxes[i][0].isChecked() for i in DIGI_lst
852
- ):
853
- return
854
-
855
- # Set all sensor channels' checkboxes to the Checked state
856
- for i in range(np.size(self.gui_handle._checkboxes, 0)):
857
- if (
858
- self.gui_handle._checkboxes[i][1].value
859
- == ChannelType.sensor.value
860
- ):
861
- self.gui_handle._checkboxes[i][0].setChecked(True)
862
-
863
- # Call the update function (normally called by individual checkboxes)
864
- self._update_channel_display()
865
-
866
- def _hide_all_DIGI(self):
867
- """Method that unchecks all channels of type sensor so that they are not
868
- displayed in the plot
869
- """
870
-
871
- # Find all channel indices that are of type DIGI
872
- DIGI_lst = [
873
- i
874
- for i in range(np.size(self.gui_handle._checkboxes, 0))
875
- if self.gui_handle._checkboxes[i][1].value
876
- == ChannelType.sensor.value
877
- ]
878
-
879
- # When all DIGI channels are already unchecked, do not update the plot
880
- if all(
881
- not self.gui_handle._checkboxes[i][0].isChecked() for i in DIGI_lst
882
- ):
883
- return
884
-
885
- # Set all DIGI channels' checkboxes to the Checked state
886
- for i in range(np.size(self.gui_handle._checkboxes, 0)):
887
- if (
888
- self.gui_handle._checkboxes[i][1].value
889
- == ChannelType.sensor.value
890
- ):
891
- self.gui_handle._checkboxes[i][0].setChecked(False)
892
-
893
- # Call the update function (normally called by individual checkboxes)
894
- self._update_channel_display()
895
-
896
- @QtCore.Slot(object)
897
- def update_plot(self, data):
898
- """Method that receives the data from the sampling thread and writes
899
- it to the GUI window.
900
- """
901
-
902
- # PyQtGraph can't handle NaN-values, therefore the WhiteOut has to be implemented differently.
903
- # This is done using a boolean array that states which points should not be connected (NaN values)
904
- con = np.isfinite(data)
905
- data[~con] = 0
906
-
907
- # Find final index for updating values in right-side yticks list
908
- idx_final = np.where(con[0, :] == False)
909
- # Check whether the vector is not empty (can occur upon rescaling) and
910
- # stop the plot update when this is the case
911
- if not idx_final[0].any():
912
- return
913
-
914
- # The last added value is the value before the White-out region.
915
- # As the white-out region resets each time the end of the window is reached,
916
- # different logic is needed when the white-out region lies around the wrapping points of the window
917
- if idx_final[0][0] != 0:
918
- idx_final = idx_final[0][0] - 1
919
- elif (
920
- idx_final[0][0] == 0
921
- and idx_final[0][-1]
922
- != (self.window_size * self.sample_rate)
923
- / self._downsampling_factor
924
- - 1
925
- ):
926
- idx_final = (
927
- self.window_size * self.sample_rate
928
- ) / self._downsampling_factor - 1
929
- else:
930
- dummy_idx = np.where(
931
- idx_final[0][:]
932
- > 0.9
933
- * (self.window_size * self.sample_rate)
934
- / self._downsampling_factor
935
- )
936
- idx_final = idx_final[0][dummy_idx[0][0]] - 1
937
-
938
- # Update the x-axis ticks so that the time base is reflected correctly on the x-axis
939
- t_end = int(np.nanmax(data[-1, :] / self.sample_rate))
940
- bottom_ticks = [
941
- [
942
- (val % self.window_size, str(val))
943
- for val in np.arange(
944
- t_end - (self.window_size - 1), t_end + 1, dtype=int
945
- )
946
- ]
947
- ]
948
- self.RealTimePlotWidget.window.getAxis("bottom").setTicks(bottom_ticks)
949
-
950
- # Ensure right amount of data points are plotted (window_size * sample_rate)
951
- time_axis = np.arange(
952
- 0, self.window_size, self._downsampling_factor / self.sample_rate
953
- )
954
-
955
- # Try to update the plot, due to user actions plotting might result in a warning
956
- # for that specific plot instance, hence the try-except statement
957
- try:
958
- for i in range(self.num_channels):
959
- # Draw data (apply scaling and multiply with negative 1 (needed due to inverted y axis))
960
- self.curve[i].setData(
961
- time_axis,
962
- (
963
- data[self.gui_handle._channel_selection[i], :]
964
- - self._plot_diff[i]["mean"]
965
- )
966
- / self._plot_diff[i]["diff"]
967
- * -1,
968
- connect=np.logical_and(con[i, :], np.roll(con[i, :], -1)),
969
- )
970
-
971
- # Update the ticks on the right side of the plot
972
- if isinstance(self.device, TMSiDevice):
973
- tick_list_right = [
974
- [
975
- (
976
- int(self._plot_offset * i),
977
- f"{data[self.gui_handle._channel_selection[i],int(idx_final)]:< 10.2f} {self.chs[self.gui_handle._channel_selection[i]][1]}",
978
- )
979
- for i in range(self.num_channels)
980
- ]
981
- ]
982
- else:
983
- tick_list_right = [
984
- [
985
- (
986
- int(self._plot_offset * i),
987
- f"{data[self.gui_handle._channel_selection[i],int(idx_final)]:< 10.2f} {self.device.channels[self.gui_handle._channel_selection[i]].unit_name}",
988
- )
989
- for i in range(self.num_channels)
990
- ]
991
- ]
992
- self.RealTimePlotWidget.window.getAxis("right").setTicks(
993
- tick_list_right
994
- )
995
- except Exception:
996
- pass
997
-
998
- @QtCore.Slot(object)
999
- def update_impedance_table(self, live_impedances):
1000
- for i, channel in enumerate(self.chs):
1001
- if i < len(self.chs) - 5:
1002
- real = QtWidgets.QTableWidgetItem(
1003
- f'{live_impedances[i+1]["Re"]} kOhm'
1004
- )
1005
- real.setFlags(Qt.ItemIsEnabled)
1006
- self.gui_handle.table_live_impedance.setItem(i, 1, real)
1007
-
1008
- def setupThread(self):
1009
- """Method that initialises the sampling thread of the device"""
1010
-
1011
- # Create a Thread
1012
- self.thread = QtCore.QThread()
1013
- # Instantiate the Sampling class
1014
- self.worker = SamplingThread(self)
1015
-
1016
- # Move the worker to a Thread
1017
- self.worker.moveToThread(self.thread)
1018
-
1019
- # Connect signals to slots depending on whether the plotter is active
1020
- if self.filter_app:
1021
- self.thread.started.connect(self.worker.update_filtered_samples)
1022
- else:
1023
- self.thread.started.connect(self.worker.update_samples)
1024
- self.worker.output.connect(self.update_plot)
1025
- self.worker.output_impedance_table.connect(self.update_impedance_table)
1026
-
1027
-
1028
- class SamplingThread(QtCore.QObject):
1029
- """Class responsible for sampling and preparing data for the GUI window."""
1030
-
1031
- # Initialise the ouptut object
1032
- output = QtCore.Signal(object)
1033
- output_impedance_table = QtCore.Signal(object)
1034
-
1035
- def __init__(self, main_class):
1036
- """Setting up the class' properties that were passed from the GUI thread"""
1037
- QtCore.QObject.__init__(self)
1038
- # Access initialised values from the GUI class
1039
- self.num_channels = main_class.num_channels
1040
- self.sample_rate = main_class.sample_rate
1041
- self.window_size = main_class.window_size
1042
- self.window_buffer = main_class.window_buffer
1043
- self._buffer_size = main_class._buffer_size
1044
- self.samples_seen = main_class.samples_seen
1045
- self.device = main_class.device
1046
- self._downsampling_factor = main_class._downsampling_factor
1047
- self.filter_app = main_class.filter_app
1048
- self.channel_conversion_list = main_class.channel_conversion_list
1049
- self.grid_type = main_class.grid_type
1050
- self.live_impedance = main_class.live_impedance
1051
- if self.live_impedance:
1052
- self._cycling_impedance = dict()
1053
- for index in range(1, len(main_class.chs) - 4):
1054
- self._cycling_impedance[index] = dict()
1055
- self._cycling_impedance[index]["Re"] = 1000
1056
- self._cycling_impedance[index]["Im"] = 1000
1057
-
1058
- # Register to filter_app or sample data server and start measurement
1059
- if not self.filter_app:
1060
- # Prepare Queue
1061
- _QUEUE_SIZE = 1000
1062
- self.q_sample_sets = queue.Queue(_QUEUE_SIZE)
1063
- if isinstance(self.device, TMSiDevice):
1064
- ApexSampleDataServer().register_consumer(
1065
- self.device.get_id(), self.q_sample_sets
1066
- )
1067
- else:
1068
- # Register the consumer to the sample data server
1069
- sample_data_server.registerConsumer(
1070
- self.device.id, self.q_sample_sets
1071
- )
1072
-
1073
- # Set sampling to true
1074
- self.sampling = True
1075
-
1076
- @QtCore.Slot()
1077
- def update_samples(self):
1078
- """Method that retrieves samples from the queue and processes the samples.
1079
- Processing includes reshaping the data into the desired format, adding
1080
- white out region to show the refresh rate of the plot.
1081
- """
1082
- lag = False
1083
- while self.sampling:
1084
- while not self.q_sample_sets.empty():
1085
- # If the sample_data_server queue contains more than 10 items, the queue is trimmed so that the plotter
1086
- # has a lower delay
1087
- if self.q_sample_sets.qsize() > 10:
1088
- lag = True
1089
- print(
1090
- "The plotter skipped some samples to compensate for lag.."
1091
- )
1092
-
1093
- elif self.q_sample_sets.qsize() < 6:
1094
- lag = False
1095
-
1096
- # Retrieve sample data from the sample_data_server queue
1097
- sd = self.q_sample_sets.get()
1098
- self.q_sample_sets.task_done()
1099
-
1100
- # Reshape the samples retrieved from the queue
1101
- samples = np.reshape(
1102
- sd.samples,
1103
- (sd.num_samples_per_sample_set, sd.num_sample_sets),
1104
- order="F",
1105
- )
1106
-
1107
- # Add a White out region to show the update of the samples
1108
- white_out = int(
1109
- np.floor(self.window_size * self.sample_rate * 0.04)
1110
- )
1111
- plot_indices = (
1112
- self.samples_seen
1113
- + (np.arange(np.size(samples, 1) + white_out))
1114
- ) % (self.sample_rate * self._buffer_size)
1115
-
1116
- # Write sample data to the buffer
1117
- self.window_buffer[:, plot_indices[:-white_out]] = samples
1118
-
1119
- # Apply the conversion strategy
1120
- self.window_buffer[:, plot_indices[:-white_out]] = samples[
1121
- self.channel_conversion_list, :
1122
- ]
1123
-
1124
- self.window_buffer[:, plot_indices[-white_out:]] = np.nan
1125
-
1126
- # Update number of samples seen by the plotter
1127
- self.samples_seen += np.size(samples, 1)
1128
-
1129
- # When the plotter lags, don't output the sample data to the plotter until (most of) the lag is gone
1130
- if lag:
1131
- time.sleep(0.001)
1132
- else:
1133
-
1134
- # The indices to be plotted ranges until the final index of the white out region,
1135
- # and has a total number of samples equal to the window size
1136
- indices = np.arange(
1137
- (
1138
- (self.samples_seen + white_out)
1139
- - (self.window_size * self.sample_rate)
1140
- ),
1141
- (self.samples_seen + white_out),
1142
- dtype=int,
1143
- )
1144
-
1145
- # The indices have to match with the indices of the window buffer
1146
- scroll_idx = (indices) % (
1147
- self.sample_rate * self._buffer_size
1148
- )
1149
-
1150
- # The buffer indices have a wrapping point (e.g. in a buffer of 4000 samples, the wrapping point is from 3999 to 0)
1151
- # As the window size might not line up with this exactly,
1152
- # the wrapping point needs to be identified to ensure that the plot does not 'jump'.
1153
- split_idx = np.where(
1154
- indices % (self.sample_rate * self.window_size) == 1
1155
- )[0][0]
1156
-
1157
- # Retrieve the correct sample data that needs to be plotted
1158
- plot_data = np.hstack(
1159
- (
1160
- self.window_buffer[:, scroll_idx[split_idx:]],
1161
- self.window_buffer[:, scroll_idx[0:split_idx]],
1162
- )
1163
- )
1164
-
1165
- plot_data = plot_data[:, :: self._downsampling_factor]
1166
-
1167
- # Output sample data
1168
- self.output.emit(plot_data)
1169
-
1170
- # Update live impedance data
1171
- if self.live_impedance:
1172
- self.update_impedances(samples)
1173
- self.output_impedance_table.emit(
1174
- self._cycling_impedance
1175
- )
1176
-
1177
- # Pause the thread for a small time so that plot can be updated before receiving next data chunk
1178
- # Pause should be long enough to have the screen update itself
1179
- time.sleep(0.03)
1180
-
1181
- @QtCore.Slot()
1182
- def update_filtered_samples(self):
1183
- """Method that retrieves samples from the queue and processes the samples.
1184
- Processing includes reshaping the data into the desired format, adding
1185
- white out region to show the refresh rate of the plot.
1186
- """
1187
- lag = False
1188
- while self.sampling:
1189
- while not self.filter_app.q_filtered_sample_sets.empty():
1190
- if self.filter_app.q_filtered_sample_sets.qsize() > 10:
1191
- lag = True
1192
- print(
1193
- "The plotter skipped some samples to compensate for lag.."
1194
- )
1195
-
1196
- elif self.filter_app.q_filtered_sample_sets.qsize() < 6:
1197
- lag = False
1198
-
1199
- sd = self.filter_app.q_filtered_sample_sets.get()
1200
- self.filter_app.q_filtered_sample_sets.task_done()
1201
- samples = copy(sd)
1202
-
1203
- # Add a White out region to show the update of the samples
1204
- white_out = int(
1205
- np.floor(self.window_size * self.sample_rate * 0.04)
1206
- )
1207
- plot_indices = (
1208
- self.samples_seen
1209
- + (np.arange(np.size(samples, 1) + white_out))
1210
- ) % (self.sample_rate * self._buffer_size)
1211
-
1212
- # Write sample data to the plot buffer
1213
- self.window_buffer[:, plot_indices[:-white_out]] = samples
1214
-
1215
- # Apply the conversion strategy
1216
- self.window_buffer[:, plot_indices[:-white_out]] = samples[
1217
- self.channel_conversion_list, :
1218
- ]
1219
-
1220
- self.window_buffer[:, plot_indices[-white_out:]] = np.nan
1221
-
1222
- # Update number of samples seen by the plotter
1223
- self.samples_seen += np.size(samples, 1)
1224
-
1225
- if lag:
1226
- time.sleep(0.001)
1227
- else:
1228
- # The indices to be plotted ranges until the final index of the white out region,
1229
- # and has a total number of samples equal to the window size
1230
- indices = np.arange(
1231
- (
1232
- (self.samples_seen + white_out)
1233
- - (self.window_size * self.sample_rate)
1234
- ),
1235
- (self.samples_seen + white_out),
1236
- dtype=int,
1237
- )
1238
-
1239
- # The indices have to match with the indices of the window buffer
1240
- scroll_idx = (indices) % (
1241
- self.sample_rate * self._buffer_size
1242
- )
1243
-
1244
- # The buffer indices have a wrapping point (e.g. in a buffer of 4000 samples, the wrapping point is from 3999 to 0)
1245
- # As the window size might not line up with this exactly,
1246
- # the wrapping point needs to be identified to ensure that the plot does not 'jump'.
1247
- split_idx = np.where(
1248
- indices % (self.sample_rate * self.window_size) == 1
1249
- )[0][0]
1250
-
1251
- # Retrieve the correct sample data that needs to be plotted
1252
- plot_data = np.hstack(
1253
- (
1254
- self.window_buffer[:, scroll_idx[split_idx:]],
1255
- self.window_buffer[:, scroll_idx[0:split_idx]],
1256
- )
1257
- )
1258
-
1259
- plot_data = plot_data[:, :: self._downsampling_factor]
1260
-
1261
- # Output sample data
1262
- self.output.emit(plot_data)
1263
-
1264
- # Update live impedance data
1265
- if self.live_impedance:
1266
- self.update_impedances(samples)
1267
- self.output_impedance_table.emit(
1268
- self._cycling_impedance
1269
- )
1270
-
1271
- # Pause the thread for a small time so that plot can be updated before receiving next data chunk
1272
- # Pause should be long enough to have the screen update itself
1273
- time.sleep(0.03)
1274
-
1275
- def update_impedances(self, samples):
1276
- CYCL_IDX = len(samples[:, 0]) - 5
1277
- for idx in range(len(samples[CYCL_IDX, :])):
1278
- index = int(samples[CYCL_IDX, idx]) + 1
1279
- if index in self._cycling_impedance:
1280
- self._cycling_impedance[index]["Re"] = samples[
1281
- CYCL_IDX + 1, idx
1282
- ]
1283
- self._cycling_impedance[index]["Im"] = samples[
1284
- CYCL_IDX + 2, idx
1285
- ]
1286
- else:
1287
- self._cycling_impedance[index] = dict()
1288
- self._cycling_impedance[index]["Re"] = samples[
1289
- CYCL_IDX + 1, idx
1290
- ]
1291
- self._cycling_impedance[index]["Im"] = samples[
1292
- CYCL_IDX + 2, idx
1293
- ]
1294
-
1295
- def stop(self):
1296
- """Method that is executed when the thread is terminated.
1297
- This stop event stops the measurement.
1298
- """
1299
- self.sampling = False
1300
-
1301
-
1302
- if __name__ == "__main__":
1303
- # Initialise the TMSi-SDK first before starting using it
1304
- tmsi_device.initialize()
1305
-
1306
- # Create the device object to interface with the SAGA-system.
1307
- dev = tmsi_device.create(
1308
- tmsi_device.DeviceType.saga,
1309
- DeviceInterfaceType.docked,
1310
- DeviceInterfaceType.network,
1311
- )
1312
-
1313
- # Find and open a connection to the SAGA-system and print its serial number
1314
- dev.open()
1315
- print("handle 1 " + str(dev.info.ds_serial_number))
1316
-
1317
- # Initialise the application
1318
- app = QtGui.QApplication(sys.argv)
1319
- # Define the GUI object and show it
1320
- window = RealTimePlot(figurename="A RealTimePlot", device=dev)
1321
- window.show()
1322
-
1323
- # Enter the event loop
1324
- app.exec_()
1325
-
1326
- dev.close()