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.
- py_neuromodulation/ConnectivityDecoding/Automated Anatomical Labeling 3 (Rolls 2020).nii +0 -0
- py_neuromodulation/ConnectivityDecoding/_get_grid_hull.m +34 -0
- py_neuromodulation/ConnectivityDecoding/_get_grid_whole_brain.py +106 -0
- py_neuromodulation/ConnectivityDecoding/_helper_write_connectome.py +119 -0
- py_neuromodulation/ConnectivityDecoding/mni_coords_cortical_surface.mat +0 -0
- py_neuromodulation/ConnectivityDecoding/mni_coords_whole_brain.mat +0 -0
- py_neuromodulation/ConnectivityDecoding/rmap_func_all.nii +0 -0
- py_neuromodulation/ConnectivityDecoding/rmap_struc.nii +0 -0
- py_neuromodulation/data/README +6 -0
- py_neuromodulation/data/dataset_description.json +8 -0
- py_neuromodulation/data/participants.json +32 -0
- py_neuromodulation/data/participants.tsv +2 -0
- py_neuromodulation/data/sub-testsub/ses-EphysMedOff/ieeg/sub-testsub_ses-EphysMedOff_space-mni_coordsystem.json +5 -0
- py_neuromodulation/data/sub-testsub/ses-EphysMedOff/ieeg/sub-testsub_ses-EphysMedOff_space-mni_electrodes.tsv +11 -0
- py_neuromodulation/data/sub-testsub/ses-EphysMedOff/ieeg/sub-testsub_ses-EphysMedOff_task-gripforce_run-0_channels.tsv +11 -0
- py_neuromodulation/data/sub-testsub/ses-EphysMedOff/ieeg/sub-testsub_ses-EphysMedOff_task-gripforce_run-0_ieeg.eeg +0 -0
- py_neuromodulation/data/sub-testsub/ses-EphysMedOff/ieeg/sub-testsub_ses-EphysMedOff_task-gripforce_run-0_ieeg.json +18 -0
- py_neuromodulation/data/sub-testsub/ses-EphysMedOff/ieeg/sub-testsub_ses-EphysMedOff_task-gripforce_run-0_ieeg.vhdr +35 -0
- py_neuromodulation/data/sub-testsub/ses-EphysMedOff/ieeg/sub-testsub_ses-EphysMedOff_task-gripforce_run-0_ieeg.vmrk +13 -0
- py_neuromodulation/data/sub-testsub/ses-EphysMedOff/sub-testsub_ses-EphysMedOff_scans.tsv +2 -0
- py_neuromodulation/grid_cortex.tsv +40 -0
- py_neuromodulation/grid_subcortex.tsv +1429 -0
- py_neuromodulation/nm_settings.json +338 -0
- py_neuromodulation/nm_stream_offline.py +7 -6
- py_neuromodulation/plots/STN_surf.mat +0 -0
- py_neuromodulation/plots/Vertices.mat +0 -0
- py_neuromodulation/plots/faces.mat +0 -0
- py_neuromodulation/plots/grid.mat +0 -0
- {py_neuromodulation-0.0.3.dist-info → py_neuromodulation-0.0.4.dist-info}/METADATA +182 -182
- py_neuromodulation-0.0.4.dist-info/RECORD +72 -0
- {py_neuromodulation-0.0.3.dist-info → py_neuromodulation-0.0.4.dist-info}/WHEEL +1 -2
- docs/build/_downloads/09df217f95985497f45d69e2d4bdc5b1/plot_2_example_add_feature.py +0 -68
- docs/build/_downloads/3b4900a2b2818ff30362215b76f7d5eb/plot_1_example_BIDS.py +0 -233
- docs/build/_downloads/7e92dd2e6cc86b239d14cafad972ae4f/plot_3_example_sharpwave_analysis.py +0 -219
- docs/build/_downloads/c2db0bf2b334d541b00662b991682256/plot_6_real_time_demo.py +0 -97
- docs/build/_downloads/ce3914826f782cbd1ea8fd024eaf0ac3/plot_5_example_rmap_computing.py +0 -64
- docs/build/_downloads/da36848a41e6a3235d91fb7cfb6d59b4/plot_0_first_demo.py +0 -192
- docs/build/_downloads/eaa4305c75b19a1e2eea941f742a6331/plot_4_example_gridPointProjection.py +0 -210
- docs/build/html/_downloads/09df217f95985497f45d69e2d4bdc5b1/plot_2_example_add_feature.py +0 -68
- docs/build/html/_downloads/3b4900a2b2818ff30362215b76f7d5eb/plot_1_example_BIDS.py +0 -239
- docs/build/html/_downloads/7e92dd2e6cc86b239d14cafad972ae4f/plot_3_example_sharpwave_analysis.py +0 -219
- docs/build/html/_downloads/c2db0bf2b334d541b00662b991682256/plot_6_real_time_demo.py +0 -97
- docs/build/html/_downloads/ce3914826f782cbd1ea8fd024eaf0ac3/plot_5_example_rmap_computing.py +0 -64
- docs/build/html/_downloads/da36848a41e6a3235d91fb7cfb6d59b4/plot_0_first_demo.py +0 -192
- docs/build/html/_downloads/eaa4305c75b19a1e2eea941f742a6331/plot_4_example_gridPointProjection.py +0 -210
- docs/source/_build/html/_downloads/09df217f95985497f45d69e2d4bdc5b1/plot_2_example_add_feature.py +0 -76
- docs/source/_build/html/_downloads/0d0d0a76e8f648d5d3cbc47da6351932/plot_real_time_demo.py +0 -97
- docs/source/_build/html/_downloads/3b4900a2b2818ff30362215b76f7d5eb/plot_1_example_BIDS.py +0 -240
- docs/source/_build/html/_downloads/5d73cadc59a8805c47e3b84063afc157/plot_example_BIDS.py +0 -233
- docs/source/_build/html/_downloads/7660317fa5a6bfbd12fcca9961457fc4/plot_example_rmap_computing.py +0 -63
- docs/source/_build/html/_downloads/7e92dd2e6cc86b239d14cafad972ae4f/plot_3_example_sharpwave_analysis.py +0 -219
- docs/source/_build/html/_downloads/839e5b319379f7fd9e867deb00fd797f/plot_example_gridPointProjection.py +0 -210
- docs/source/_build/html/_downloads/ae8be19afe5e559f011fc9b138968ba0/plot_first_demo.py +0 -192
- docs/source/_build/html/_downloads/b8b06cacc17969d3725a0b6f1d7741c5/plot_example_sharpwave_analysis.py +0 -219
- docs/source/_build/html/_downloads/c2db0bf2b334d541b00662b991682256/plot_6_real_time_demo.py +0 -121
- docs/source/_build/html/_downloads/c31a86c0b68cb4167d968091ace8080d/plot_example_add_feature.py +0 -68
- docs/source/_build/html/_downloads/ce3914826f782cbd1ea8fd024eaf0ac3/plot_5_example_rmap_computing.py +0 -64
- docs/source/_build/html/_downloads/da36848a41e6a3235d91fb7cfb6d59b4/plot_0_first_demo.py +0 -189
- docs/source/_build/html/_downloads/eaa4305c75b19a1e2eea941f742a6331/plot_4_example_gridPointProjection.py +0 -210
- docs/source/auto_examples/plot_0_first_demo.py +0 -189
- docs/source/auto_examples/plot_1_example_BIDS.py +0 -240
- docs/source/auto_examples/plot_2_example_add_feature.py +0 -76
- docs/source/auto_examples/plot_3_example_sharpwave_analysis.py +0 -219
- docs/source/auto_examples/plot_4_example_gridPointProjection.py +0 -210
- docs/source/auto_examples/plot_5_example_rmap_computing.py +0 -64
- docs/source/auto_examples/plot_6_real_time_demo.py +0 -121
- docs/source/conf.py +0 -105
- examples/plot_0_first_demo.py +0 -189
- examples/plot_1_example_BIDS.py +0 -240
- examples/plot_2_example_add_feature.py +0 -76
- examples/plot_3_example_sharpwave_analysis.py +0 -219
- examples/plot_4_example_gridPointProjection.py +0 -210
- examples/plot_5_example_rmap_computing.py +0 -64
- examples/plot_6_real_time_demo.py +0 -121
- packages/realtime_decoding/build/lib/realtime_decoding/__init__.py +0 -4
- packages/realtime_decoding/build/lib/realtime_decoding/decoder.py +0 -104
- packages/realtime_decoding/build/lib/realtime_decoding/features.py +0 -163
- packages/realtime_decoding/build/lib/realtime_decoding/helpers.py +0 -15
- packages/realtime_decoding/build/lib/realtime_decoding/run_decoding.py +0 -345
- packages/realtime_decoding/build/lib/realtime_decoding/trainer.py +0 -54
- packages/tmsi/build/lib/TMSiFileFormats/__init__.py +0 -37
- packages/tmsi/build/lib/TMSiFileFormats/file_formats/__init__.py +0 -36
- packages/tmsi/build/lib/TMSiFileFormats/file_formats/lsl_stream_writer.py +0 -200
- packages/tmsi/build/lib/TMSiFileFormats/file_formats/poly5_file_writer.py +0 -496
- packages/tmsi/build/lib/TMSiFileFormats/file_formats/poly5_to_edf_converter.py +0 -236
- packages/tmsi/build/lib/TMSiFileFormats/file_formats/xdf_file_writer.py +0 -977
- packages/tmsi/build/lib/TMSiFileFormats/file_readers/__init__.py +0 -35
- packages/tmsi/build/lib/TMSiFileFormats/file_readers/edf_reader.py +0 -116
- packages/tmsi/build/lib/TMSiFileFormats/file_readers/poly5reader.py +0 -294
- packages/tmsi/build/lib/TMSiFileFormats/file_readers/xdf_reader.py +0 -229
- packages/tmsi/build/lib/TMSiFileFormats/file_writer.py +0 -102
- packages/tmsi/build/lib/TMSiPlotters/__init__.py +0 -2
- packages/tmsi/build/lib/TMSiPlotters/gui/__init__.py +0 -39
- packages/tmsi/build/lib/TMSiPlotters/gui/_plotter_gui.py +0 -234
- packages/tmsi/build/lib/TMSiPlotters/gui/plotting_gui.py +0 -440
- packages/tmsi/build/lib/TMSiPlotters/plotters/__init__.py +0 -44
- packages/tmsi/build/lib/TMSiPlotters/plotters/hd_emg_plotter.py +0 -446
- packages/tmsi/build/lib/TMSiPlotters/plotters/impedance_plotter.py +0 -589
- packages/tmsi/build/lib/TMSiPlotters/plotters/signal_plotter.py +0 -1326
- packages/tmsi/build/lib/TMSiSDK/__init__.py +0 -54
- packages/tmsi/build/lib/TMSiSDK/device.py +0 -588
- packages/tmsi/build/lib/TMSiSDK/devices/__init__.py +0 -34
- packages/tmsi/build/lib/TMSiSDK/devices/saga/TMSi_Device_API.py +0 -1764
- packages/tmsi/build/lib/TMSiSDK/devices/saga/__init__.py +0 -34
- packages/tmsi/build/lib/TMSiSDK/devices/saga/saga_device.py +0 -1366
- packages/tmsi/build/lib/TMSiSDK/devices/saga/saga_types.py +0 -520
- packages/tmsi/build/lib/TMSiSDK/devices/saga/xml_saga_config.py +0 -165
- packages/tmsi/build/lib/TMSiSDK/error.py +0 -95
- packages/tmsi/build/lib/TMSiSDK/sample_data.py +0 -63
- packages/tmsi/build/lib/TMSiSDK/sample_data_server.py +0 -99
- packages/tmsi/build/lib/TMSiSDK/settings.py +0 -45
- packages/tmsi/build/lib/TMSiSDK/tmsi_device.py +0 -111
- packages/tmsi/build/lib/__init__.py +0 -4
- packages/tmsi/build/lib/apex_sdk/__init__.py +0 -34
- packages/tmsi/build/lib/apex_sdk/device/__init__.py +0 -41
- packages/tmsi/build/lib/apex_sdk/device/devices/apex/apex_API.py +0 -1009
- packages/tmsi/build/lib/apex_sdk/device/devices/apex/apex_API_enums.py +0 -239
- packages/tmsi/build/lib/apex_sdk/device/devices/apex/apex_API_structures.py +0 -668
- packages/tmsi/build/lib/apex_sdk/device/devices/apex/apex_device.py +0 -1611
- packages/tmsi/build/lib/apex_sdk/device/devices/apex/apex_dongle.py +0 -38
- packages/tmsi/build/lib/apex_sdk/device/devices/apex/apex_event_reader.py +0 -57
- packages/tmsi/build/lib/apex_sdk/device/devices/apex/apex_structures/apex_channel.py +0 -44
- packages/tmsi/build/lib/apex_sdk/device/devices/apex/apex_structures/apex_config.py +0 -150
- packages/tmsi/build/lib/apex_sdk/device/devices/apex/apex_structures/apex_const.py +0 -36
- packages/tmsi/build/lib/apex_sdk/device/devices/apex/apex_structures/apex_impedance_channel.py +0 -48
- packages/tmsi/build/lib/apex_sdk/device/devices/apex/apex_structures/apex_info.py +0 -108
- packages/tmsi/build/lib/apex_sdk/device/devices/apex/apex_structures/dongle_info.py +0 -39
- packages/tmsi/build/lib/apex_sdk/device/devices/apex/measurements/download_measurement.py +0 -77
- packages/tmsi/build/lib/apex_sdk/device/devices/apex/measurements/eeg_measurement.py +0 -150
- packages/tmsi/build/lib/apex_sdk/device/devices/apex/measurements/impedance_measurement.py +0 -129
- packages/tmsi/build/lib/apex_sdk/device/threads/conversion_thread.py +0 -59
- packages/tmsi/build/lib/apex_sdk/device/threads/sampling_thread.py +0 -57
- packages/tmsi/build/lib/apex_sdk/device/tmsi_channel.py +0 -83
- packages/tmsi/build/lib/apex_sdk/device/tmsi_device.py +0 -201
- packages/tmsi/build/lib/apex_sdk/device/tmsi_device_enums.py +0 -103
- packages/tmsi/build/lib/apex_sdk/device/tmsi_dongle.py +0 -43
- packages/tmsi/build/lib/apex_sdk/device/tmsi_event_reader.py +0 -50
- packages/tmsi/build/lib/apex_sdk/device/tmsi_measurement.py +0 -118
- packages/tmsi/build/lib/apex_sdk/sample_data_server/__init__.py +0 -33
- packages/tmsi/build/lib/apex_sdk/sample_data_server/event_data.py +0 -44
- packages/tmsi/build/lib/apex_sdk/sample_data_server/sample_data.py +0 -50
- packages/tmsi/build/lib/apex_sdk/sample_data_server/sample_data_server.py +0 -136
- packages/tmsi/build/lib/apex_sdk/tmsi_errors/error.py +0 -126
- packages/tmsi/build/lib/apex_sdk/tmsi_sdk.py +0 -113
- packages/tmsi/build/lib/apex_sdk/tmsi_utilities/apex/apex_structure_generator.py +0 -134
- packages/tmsi/build/lib/apex_sdk/tmsi_utilities/decorators.py +0 -60
- packages/tmsi/build/lib/apex_sdk/tmsi_utilities/logger_filter.py +0 -42
- packages/tmsi/build/lib/apex_sdk/tmsi_utilities/singleton.py +0 -42
- packages/tmsi/build/lib/apex_sdk/tmsi_utilities/support_functions.py +0 -72
- packages/tmsi/build/lib/apex_sdk/tmsi_utilities/tmsi_logger.py +0 -98
- py_neuromodulation-0.0.3.dist-info/RECORD +0 -188
- py_neuromodulation-0.0.3.dist-info/top_level.txt +0 -5
- tests/__init__.py +0 -0
- tests/conftest.py +0 -117
- tests/test_all_examples.py +0 -10
- tests/test_all_features.py +0 -63
- tests/test_bispectra.py +0 -70
- tests/test_bursts.py +0 -105
- tests/test_feature_sampling_rates.py +0 -143
- tests/test_fooof.py +0 -16
- tests/test_initalization_offline_stream.py +0 -41
- tests/test_multiprocessing.py +0 -58
- tests/test_nan_values.py +0 -29
- tests/test_nm_filter.py +0 -95
- tests/test_nm_resample.py +0 -63
- tests/test_normalization_settings.py +0 -146
- tests/test_notch_filter.py +0 -31
- tests/test_osc_features.py +0 -424
- tests/test_preprocessing_filter.py +0 -151
- tests/test_rereference.py +0 -171
- tests/test_sampling.py +0 -57
- tests/test_settings_change_after_init.py +0 -76
- tests/test_sharpwave.py +0 -165
- tests/test_target_channel_add.py +0 -100
- tests/test_timing.py +0 -80
- {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()
|