biosignal-device-interface 0.1.1a0__py3-none-any.whl → 0.1.4a1__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.
- biosignal_device_interface/__init__.py +0 -4
- biosignal_device_interface/constants/devices/__init__.py +3 -0
- biosignal_device_interface/constants/devices/core/base_device_constants.py +10 -0
- biosignal_device_interface/constants/devices/otb/otb_constants.py +0 -0
- biosignal_device_interface/constants/devices/otb/otb_muovi_constants.py +3 -3
- biosignal_device_interface/constants/devices/otb/otb_quattrocento_constants.py +289 -35
- biosignal_device_interface/constants/devices/otb/otb_quattrocento_light_constants.py +59 -0
- biosignal_device_interface/constants/devices/otb/otb_syncstation_constants.py +233 -0
- biosignal_device_interface/devices/__init__.py +11 -3
- biosignal_device_interface/devices/core/base_device.py +8 -9
- biosignal_device_interface/devices/otb/__init__.py +27 -7
- biosignal_device_interface/devices/otb/otb_muovi.py +9 -10
- biosignal_device_interface/devices/otb/otb_quattrocento.py +215 -118
- biosignal_device_interface/devices/otb/otb_quattrocento_light.py +210 -0
- biosignal_device_interface/devices/otb/otb_syncstation.py +407 -0
- biosignal_device_interface/gui/device_template_widgets/__init__.py +0 -6
- biosignal_device_interface/gui/device_template_widgets/all_devices_widget.py +19 -7
- biosignal_device_interface/gui/device_template_widgets/core/base_device_widget.py +17 -8
- biosignal_device_interface/gui/device_template_widgets/core/base_multiple_devices_widget.py +7 -4
- biosignal_device_interface/gui/device_template_widgets/otb/__init__.py +0 -10
- biosignal_device_interface/gui/device_template_widgets/otb/otb_devices_widget.py +20 -8
- biosignal_device_interface/gui/device_template_widgets/otb/otb_muovi_plus_widget.py +12 -12
- biosignal_device_interface/gui/device_template_widgets/otb/otb_muovi_widget.py +12 -12
- biosignal_device_interface/gui/device_template_widgets/otb/otb_quattrocento_light_widget.py +61 -57
- biosignal_device_interface/gui/device_template_widgets/otb/otb_quattrocento_widget.py +260 -0
- biosignal_device_interface/gui/device_template_widgets/otb/otb_syncstation_widget.py +262 -0
- biosignal_device_interface/gui/plot_widgets/biosignal_plot_widget.py +9 -4
- biosignal_device_interface/gui/ui/otb_quattrocento_template_widget.ui +415 -0
- biosignal_device_interface/gui/ui/otb_syncstation_template_widget.ui +732 -0
- biosignal_device_interface/gui/ui_compiled/otb_quattrocento_template_widget.py +318 -0
- biosignal_device_interface/gui/ui_compiled/otb_syncstation_template_widget.py +495 -0
- biosignal_device_interface-0.1.4a1.dist-info/LICENSE +675 -0
- {biosignal_device_interface-0.1.1a0.dist-info → biosignal_device_interface-0.1.4a1.dist-info}/METADATA +7 -17
- biosignal_device_interface-0.1.4a1.dist-info/RECORD +46 -0
- biosignal_device_interface-0.1.1a0.dist-info/LICENSE +0 -395
- biosignal_device_interface-0.1.1a0.dist-info/RECORD +0 -35
- {biosignal_device_interface-0.1.1a0.dist-info → biosignal_device_interface-0.1.4a1.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Device class for real-time interfacing the OTB Syncstation device.
|
|
3
|
+
Developer: Dominik I. Braun
|
|
4
|
+
Contact: dome.braun@fau.de
|
|
5
|
+
Last Update: 2025-01-09
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
from typing import TYPE_CHECKING, Union, Dict
|
|
10
|
+
from PySide6.QtNetwork import QTcpSocket, QHostAddress
|
|
11
|
+
from PySide6.QtCore import QIODevice
|
|
12
|
+
import numpy as np
|
|
13
|
+
|
|
14
|
+
# Local Libraries
|
|
15
|
+
from biosignal_device_interface.constants.devices.otb.otb_syncstation_constants import (
|
|
16
|
+
PROBE_CHARACTERISTICS_DICT,
|
|
17
|
+
SYNCSTATION_CHARACTERISTICS_DICT,
|
|
18
|
+
SYNCSTATION_CONVERSION_FACTOR_DICT,
|
|
19
|
+
SyncStationDetectionMode,
|
|
20
|
+
SyncStationProbeConfigMode,
|
|
21
|
+
SyncStationRecOnMode,
|
|
22
|
+
SyncStationWorkingMode,
|
|
23
|
+
)
|
|
24
|
+
from biosignal_device_interface.devices.core.base_device import BaseDevice
|
|
25
|
+
from biosignal_device_interface.constants.devices.core.base_device_constants import (
|
|
26
|
+
DeviceType,
|
|
27
|
+
DeviceChannelTypes,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
if TYPE_CHECKING:
|
|
32
|
+
from PySide6.QtWidgets import QMainWindow, QWidget
|
|
33
|
+
from aenum import Enum
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class OTBSyncStation(BaseDevice):
|
|
37
|
+
"""
|
|
38
|
+
Device class for real-time interfacing the OTB Syncstation device.
|
|
39
|
+
The SyncStation class is using a TCP/IP protocol to communicate with the device.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
def __init__(self, parent: Union[QMainWindow, QWidget] = None) -> None:
|
|
43
|
+
"""
|
|
44
|
+
Constructor of the OTBSyncStation class.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
parent (Union[QMainWindow, QWidget]): The parent object of the device. Defaults to None.
|
|
48
|
+
"""
|
|
49
|
+
super().__init__(parent)
|
|
50
|
+
|
|
51
|
+
# Device Parameters
|
|
52
|
+
self._device_type = DeviceType.OTB_SYNCSTATION
|
|
53
|
+
|
|
54
|
+
# Connection Parameters
|
|
55
|
+
self._interface: QTcpSocket = QTcpSocket()
|
|
56
|
+
|
|
57
|
+
# Configuration Parameters
|
|
58
|
+
self._configuration_command_A: bytearray = None
|
|
59
|
+
self._configuration_command_B: bytearray = None
|
|
60
|
+
|
|
61
|
+
# Configuration Parameters A
|
|
62
|
+
self._rec_on_mode: SyncStationRecOnMode = None
|
|
63
|
+
self._working_mode: SyncStationWorkingMode = None
|
|
64
|
+
self._number_of_probes: int = None
|
|
65
|
+
self._bytes_configuration_A: Dict[
|
|
66
|
+
SyncStationProbeConfigMode, Dict[str, SyncStationDetectionMode | bool]
|
|
67
|
+
] = None
|
|
68
|
+
|
|
69
|
+
# Configuration Parameters B
|
|
70
|
+
self._bytes_configuration_B: Dict[str, int] = None
|
|
71
|
+
|
|
72
|
+
def _connect_to_device(self) -> bool:
|
|
73
|
+
super()._connect_to_device()
|
|
74
|
+
|
|
75
|
+
self._received_bytes: bytearray = bytearray()
|
|
76
|
+
self._make_request()
|
|
77
|
+
|
|
78
|
+
def _make_request(self) -> bool:
|
|
79
|
+
super()._make_request()
|
|
80
|
+
|
|
81
|
+
self._interface.connectToHost(
|
|
82
|
+
QHostAddress(self._connection_settings[0]),
|
|
83
|
+
self._connection_settings[1],
|
|
84
|
+
QIODevice.ReadWrite,
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
if not self._interface.waitForConnected(1000):
|
|
88
|
+
self._disconnect_from_device()
|
|
89
|
+
return False
|
|
90
|
+
|
|
91
|
+
self.is_connected = True
|
|
92
|
+
self.connect_toggled.emit(True)
|
|
93
|
+
|
|
94
|
+
self._interface.readyRead.connect(self._read_data)
|
|
95
|
+
return True
|
|
96
|
+
|
|
97
|
+
def _disconnect_from_device(self) -> None:
|
|
98
|
+
super()._disconnect_from_device()
|
|
99
|
+
|
|
100
|
+
self._interface.disconnectFromHost()
|
|
101
|
+
self._interface.readyRead.disconnect(self._read_data)
|
|
102
|
+
self._interface.close()
|
|
103
|
+
|
|
104
|
+
self.is_connected = False
|
|
105
|
+
self.connect_toggled.emit(False)
|
|
106
|
+
|
|
107
|
+
def configure_device(self, params) -> None:
|
|
108
|
+
super().configure_device(params)
|
|
109
|
+
|
|
110
|
+
success = self._configure_byte_sequence_A()
|
|
111
|
+
|
|
112
|
+
if not success:
|
|
113
|
+
print("Unable to configure device.")
|
|
114
|
+
return
|
|
115
|
+
|
|
116
|
+
self._send_configuration_to_device()
|
|
117
|
+
|
|
118
|
+
self._is_configured = True
|
|
119
|
+
self.configure_toggled.emit(True)
|
|
120
|
+
|
|
121
|
+
def _configure_byte_sequence_A(self) -> None:
|
|
122
|
+
start_byte = 0
|
|
123
|
+
start_byte += (self._rec_on_mode.value - 1) << 6
|
|
124
|
+
|
|
125
|
+
self._sampling_frequency = SYNCSTATION_CHARACTERISTICS_DICT[
|
|
126
|
+
"channel_information"
|
|
127
|
+
][self._working_mode]["sampling_frequency"]
|
|
128
|
+
self._bytes_per_sample = SYNCSTATION_CHARACTERISTICS_DICT[
|
|
129
|
+
"channel_information"
|
|
130
|
+
][self._working_mode]["bytes_per_sample"]
|
|
131
|
+
|
|
132
|
+
self._configuration_command_A = bytearray()
|
|
133
|
+
self._number_of_channels = 0
|
|
134
|
+
self._number_of_bytes = 0
|
|
135
|
+
|
|
136
|
+
self._number_of_biosignal_channels = 0
|
|
137
|
+
self._number_of_auxiliary_channels = 0
|
|
138
|
+
self._biosignal_channel_indices = []
|
|
139
|
+
self._auxiliary_channel_indices = []
|
|
140
|
+
|
|
141
|
+
for key, value in self._bytes_configuration_A.items():
|
|
142
|
+
probe_command = 0
|
|
143
|
+
probe_command += (key.value - 1) << 4
|
|
144
|
+
probe_command += (self._working_mode.value - 1) << 3
|
|
145
|
+
probe_command += (value["detection_mode"].value - 1) << 1
|
|
146
|
+
probe_command += int(value["probe_status"])
|
|
147
|
+
|
|
148
|
+
if value["probe_status"]:
|
|
149
|
+
self._configuration_command_A.append(probe_command)
|
|
150
|
+
channels = PROBE_CHARACTERISTICS_DICT[key][DeviceChannelTypes.ALL]
|
|
151
|
+
biosignal_channels = PROBE_CHARACTERISTICS_DICT[key][
|
|
152
|
+
DeviceChannelTypes.BIOSIGNAL
|
|
153
|
+
]
|
|
154
|
+
auxiliary_channels = PROBE_CHARACTERISTICS_DICT[key][
|
|
155
|
+
DeviceChannelTypes.AUXILIARY
|
|
156
|
+
]
|
|
157
|
+
|
|
158
|
+
self._biosignal_channel_indices.append(
|
|
159
|
+
np.arange(
|
|
160
|
+
self._number_of_channels,
|
|
161
|
+
self._number_of_channels + biosignal_channels,
|
|
162
|
+
)
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
self._auxiliary_channel_indices.append(
|
|
166
|
+
np.arange(
|
|
167
|
+
self._number_of_channels + biosignal_channels,
|
|
168
|
+
self._number_of_channels + channels,
|
|
169
|
+
)
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
self._number_of_channels += channels
|
|
173
|
+
self._number_of_biosignal_channels += biosignal_channels
|
|
174
|
+
self._number_of_auxiliary_channels += auxiliary_channels
|
|
175
|
+
|
|
176
|
+
self._conversion_factor_biosignal = SYNCSTATION_CONVERSION_FACTOR_DICT[
|
|
177
|
+
value["detection_mode"]
|
|
178
|
+
]
|
|
179
|
+
self._conversion_factor_auxiliary = self._conversion_factor_biosignal
|
|
180
|
+
|
|
181
|
+
self._biosignal_channel_indices = np.hstack(self._biosignal_channel_indices)
|
|
182
|
+
self._auxiliary_channel_indices = np.hstack(self._auxiliary_channel_indices)
|
|
183
|
+
self._number_of_bytes = self._number_of_channels * self._bytes_per_sample
|
|
184
|
+
|
|
185
|
+
# Add SyncStation Channels
|
|
186
|
+
self._number_of_channels += SYNCSTATION_CHARACTERISTICS_DICT[
|
|
187
|
+
DeviceChannelTypes.ALL
|
|
188
|
+
]
|
|
189
|
+
self._number_of_auxiliary_channels += SYNCSTATION_CHARACTERISTICS_DICT[
|
|
190
|
+
DeviceChannelTypes.ALL
|
|
191
|
+
]
|
|
192
|
+
|
|
193
|
+
self._number_of_bytes += (
|
|
194
|
+
SYNCSTATION_CHARACTERISTICS_DICT[DeviceChannelTypes.ALL]
|
|
195
|
+
* SYNCSTATION_CHARACTERISTICS_DICT["bytes_per_sample"]
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
self._samples_per_frame = int(
|
|
199
|
+
(1 / SYNCSTATION_CHARACTERISTICS_DICT["FPS"]) * self._sampling_frequency
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
self._buffer_size = int(self._number_of_bytes * self._samples_per_frame)
|
|
203
|
+
|
|
204
|
+
num_probes = len(self._configuration_command_A)
|
|
205
|
+
start_byte += num_probes << 1
|
|
206
|
+
self._configuration_command_A.insert(0, start_byte)
|
|
207
|
+
start_byte_ckc8 = self._crc_check(
|
|
208
|
+
self._configuration_command_A, len(self._configuration_command_A)
|
|
209
|
+
)
|
|
210
|
+
self._configuration_command_A.append(start_byte_ckc8)
|
|
211
|
+
|
|
212
|
+
return True
|
|
213
|
+
|
|
214
|
+
def _crc_check(self, command_bytes: bytearray, command_length: int) -> bytes:
|
|
215
|
+
"""
|
|
216
|
+
Performs the Cyclic Redundancy Check (CRC) of the transmitted bytes.
|
|
217
|
+
|
|
218
|
+
Translated function from example code provided by OT Bioelettronica.
|
|
219
|
+
|
|
220
|
+
Args:
|
|
221
|
+
command_bytes (bytearray):
|
|
222
|
+
Bytearray of the transmitted bytes.
|
|
223
|
+
|
|
224
|
+
command_length (int):
|
|
225
|
+
Length of the transmitted bytes.
|
|
226
|
+
|
|
227
|
+
Returns:
|
|
228
|
+
bytes:
|
|
229
|
+
CRC of the transmitted bytes.
|
|
230
|
+
"""
|
|
231
|
+
|
|
232
|
+
crc = 0
|
|
233
|
+
j = 0
|
|
234
|
+
|
|
235
|
+
while command_length > 0:
|
|
236
|
+
extracted_byte = command_bytes[j]
|
|
237
|
+
for i in range(8, 0, -1):
|
|
238
|
+
sum = crc % 2 ^ extracted_byte % 2
|
|
239
|
+
crc = crc // 2
|
|
240
|
+
|
|
241
|
+
if sum > 0:
|
|
242
|
+
crc_bin = format(crc, "08b")
|
|
243
|
+
a_bin = format(140, "08b")
|
|
244
|
+
|
|
245
|
+
str_list = []
|
|
246
|
+
|
|
247
|
+
for k in range(8):
|
|
248
|
+
str_list.append("0" if crc_bin[k] == a_bin[k] else "1")
|
|
249
|
+
|
|
250
|
+
crc = int("".join(str_list), 2)
|
|
251
|
+
|
|
252
|
+
extracted_byte = extracted_byte // 2
|
|
253
|
+
|
|
254
|
+
command_length -= 1
|
|
255
|
+
j += 1
|
|
256
|
+
|
|
257
|
+
return crc
|
|
258
|
+
|
|
259
|
+
def _configure_byte_sequence_B(self) -> None:
|
|
260
|
+
# TODO: Implement this method
|
|
261
|
+
...
|
|
262
|
+
|
|
263
|
+
def _send_configuration_to_device(self) -> None:
|
|
264
|
+
print(
|
|
265
|
+
f"Device configuration sent: {[int.from_bytes(self._configuration_command_A[i : i + 1], 'big') for i in range(len(self._configuration_command_A))]}"
|
|
266
|
+
)
|
|
267
|
+
self._interface.write(self._configuration_command_A)
|
|
268
|
+
|
|
269
|
+
def _stop_streaming(self):
|
|
270
|
+
self._configuration_command_A[0] -= 1
|
|
271
|
+
self._configuration_command_A[-1] = self._crc_check(
|
|
272
|
+
self._configuration_command_A, len(self._configuration_command_A) - 1
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
self._send_configuration_to_device()
|
|
276
|
+
|
|
277
|
+
self._is_streaming = False
|
|
278
|
+
self.stream_toggled.emit(False)
|
|
279
|
+
|
|
280
|
+
def _start_streaming(self):
|
|
281
|
+
self._configuration_command_A[0] += 1
|
|
282
|
+
self._configuration_command_A[-1] = self._crc_check(
|
|
283
|
+
self._configuration_command_A, len(self._configuration_command_A) - 1
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
self._send_configuration_to_device()
|
|
287
|
+
|
|
288
|
+
self._is_streaming = True
|
|
289
|
+
self.stream_toggled.emit(True)
|
|
290
|
+
|
|
291
|
+
def _clear_socket(self) -> None:
|
|
292
|
+
"""
|
|
293
|
+
Clears the socket from any remaining data.
|
|
294
|
+
"""
|
|
295
|
+
self._interface.readAll()
|
|
296
|
+
self._received_bytes = bytearray()
|
|
297
|
+
|
|
298
|
+
def _read_data(self) -> None:
|
|
299
|
+
if not self._is_streaming:
|
|
300
|
+
packet = self._interface.readAll()
|
|
301
|
+
|
|
302
|
+
else:
|
|
303
|
+
if self._interface.bytesAvailable() > 0:
|
|
304
|
+
|
|
305
|
+
packet = self._interface.readAll()
|
|
306
|
+
packet_bytearray = bytearray(packet.data())
|
|
307
|
+
|
|
308
|
+
if not packet_bytearray:
|
|
309
|
+
return
|
|
310
|
+
|
|
311
|
+
self._received_bytes.extend(packet_bytearray)
|
|
312
|
+
|
|
313
|
+
while len(self._received_bytes) >= self._buffer_size:
|
|
314
|
+
self._process_data(
|
|
315
|
+
bytearray(self._received_bytes)[: self._buffer_size]
|
|
316
|
+
)
|
|
317
|
+
self._received_bytes = bytearray(self._received_bytes)[
|
|
318
|
+
self._buffer_size :
|
|
319
|
+
]
|
|
320
|
+
|
|
321
|
+
def _process_data(self, input: bytearray) -> None:
|
|
322
|
+
data: np.ndarray = np.frombuffer(input, dtype=np.uint8).astype(np.float32)
|
|
323
|
+
|
|
324
|
+
samples = self._samples_per_frame
|
|
325
|
+
data = np.reshape(data, (samples, self._number_of_bytes)).T
|
|
326
|
+
processed_data = self._bytes_to_integers(data)
|
|
327
|
+
|
|
328
|
+
# Emit the data
|
|
329
|
+
self.data_available.emit(processed_data)
|
|
330
|
+
self.biosignal_data_available.emit(self._extract_biosignal_data(processed_data))
|
|
331
|
+
self.auxiliary_data_available.emit(self._extract_auxiliary_data(processed_data))
|
|
332
|
+
|
|
333
|
+
def _integer_to_bytes(self, command: int) -> bytes:
|
|
334
|
+
return int(command).to_bytes(1, byteorder="big")
|
|
335
|
+
|
|
336
|
+
# Convert channels from bytes to integers
|
|
337
|
+
def _bytes_to_integers(
|
|
338
|
+
self,
|
|
339
|
+
data: np.ndarray,
|
|
340
|
+
) -> np.ndarray:
|
|
341
|
+
samples = self._samples_per_frame
|
|
342
|
+
frame_data = np.zeros((self._number_of_channels, samples), dtype=np.float32)
|
|
343
|
+
channels_to_read = 0
|
|
344
|
+
for device in list(SyncStationProbeConfigMode)[1:]:
|
|
345
|
+
if self._bytes_configuration_A[device]["probe_status"]:
|
|
346
|
+
channel_number = PROBE_CHARACTERISTICS_DICT[device][
|
|
347
|
+
DeviceChannelTypes.ALL
|
|
348
|
+
]
|
|
349
|
+
# Convert channel's byte value to integer
|
|
350
|
+
if self._working_mode == SyncStationWorkingMode.EMG:
|
|
351
|
+
channel_indices = (
|
|
352
|
+
np.arange(0, channel_number * 2, 2) + channels_to_read * 2
|
|
353
|
+
)
|
|
354
|
+
data_sub_matrix = self._decode_int16(data, channel_indices)
|
|
355
|
+
frame_data[
|
|
356
|
+
channels_to_read : channels_to_read + channel_number, :
|
|
357
|
+
] = data_sub_matrix
|
|
358
|
+
|
|
359
|
+
elif self._working_mode == SyncStationWorkingMode.EEG:
|
|
360
|
+
channel_indices = (
|
|
361
|
+
np.arange(0, channel_number * 3, 3) + channels_to_read * 2
|
|
362
|
+
)
|
|
363
|
+
data_sub_matrix = self._decode_int24(data, channel_indices)
|
|
364
|
+
frame_data[
|
|
365
|
+
channels_to_read : channels_to_read + channel_number, :
|
|
366
|
+
] = data_sub_matrix
|
|
367
|
+
|
|
368
|
+
channels_to_read += channel_number
|
|
369
|
+
del data_sub_matrix
|
|
370
|
+
del channel_indices
|
|
371
|
+
|
|
372
|
+
syncstation_aux_bytes_number = (
|
|
373
|
+
SYNCSTATION_CHARACTERISTICS_DICT[DeviceChannelTypes.ALL]
|
|
374
|
+
* SYNCSTATION_CHARACTERISTICS_DICT["bytes_per_sample"]
|
|
375
|
+
)
|
|
376
|
+
syncstation_aux_starting_byte = (
|
|
377
|
+
self._number_of_bytes - syncstation_aux_bytes_number
|
|
378
|
+
)
|
|
379
|
+
channel_indices = np.arange(
|
|
380
|
+
syncstation_aux_starting_byte,
|
|
381
|
+
syncstation_aux_starting_byte + syncstation_aux_bytes_number,
|
|
382
|
+
2,
|
|
383
|
+
)
|
|
384
|
+
data_sub_matrix = self._decode_int16(data, channel_indices)
|
|
385
|
+
frame_data[channels_to_read : channels_to_read + 6, :] = data_sub_matrix
|
|
386
|
+
return np.array(frame_data)
|
|
387
|
+
|
|
388
|
+
def _decode_int24(
|
|
389
|
+
self, data: np.ndarray, channel_indices: np.ndarray
|
|
390
|
+
) -> np.ndarray:
|
|
391
|
+
data_sub_matrix = (
|
|
392
|
+
data[channel_indices, :] * 2**16
|
|
393
|
+
+ data[channel_indices + 1, :] * 2**8
|
|
394
|
+
+ data[channel_indices + 2, :]
|
|
395
|
+
)
|
|
396
|
+
negative_indices = np.where(data_sub_matrix >= 2**23)
|
|
397
|
+
data_sub_matrix[negative_indices] -= 2**24
|
|
398
|
+
|
|
399
|
+
return data_sub_matrix
|
|
400
|
+
|
|
401
|
+
def _decode_int16(
|
|
402
|
+
self, data: np.ndarray, channel_indices: np.ndarray
|
|
403
|
+
) -> np.ndarray:
|
|
404
|
+
data_sub_matrix = data[channel_indices, :] * 2**8 + data[channel_indices + 1, :]
|
|
405
|
+
negative_indices = np.where(data_sub_matrix >= 2**15)
|
|
406
|
+
data_sub_matrix[negative_indices] -= 2**16
|
|
407
|
+
return data_sub_matrix
|
|
@@ -14,10 +14,20 @@ from biosignal_device_interface.gui.device_template_widgets.core.base_multiple_d
|
|
|
14
14
|
from biosignal_device_interface.constants.devices.core.base_device_constants import (
|
|
15
15
|
DeviceType,
|
|
16
16
|
)
|
|
17
|
-
from biosignal_device_interface.gui.device_template_widgets import (
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
from biosignal_device_interface.gui.device_template_widgets.otb.otb_muovi_plus_widget import (
|
|
18
|
+
OTBMuoviPlusWidget,
|
|
19
|
+
)
|
|
20
|
+
from biosignal_device_interface.gui.device_template_widgets.otb.otb_muovi_widget import (
|
|
21
|
+
OTBMuoviWidget,
|
|
22
|
+
)
|
|
23
|
+
from biosignal_device_interface.gui.device_template_widgets.otb.otb_quattrocento_light_widget import (
|
|
24
|
+
OTBQuattrocentoLightWidget,
|
|
25
|
+
)
|
|
26
|
+
from biosignal_device_interface.gui.device_template_widgets.otb.otb_quattrocento_widget import (
|
|
27
|
+
OTBQuattrocentoWidget,
|
|
28
|
+
)
|
|
29
|
+
from biosignal_device_interface.gui.device_template_widgets.otb.otb_syncstation_widget import (
|
|
30
|
+
OTBSyncStationWidget,
|
|
21
31
|
)
|
|
22
32
|
|
|
23
33
|
if TYPE_CHECKING:
|
|
@@ -32,8 +42,10 @@ class AllDevicesWidget(BaseMultipleDevicesWidget):
|
|
|
32
42
|
super().__init__(parent)
|
|
33
43
|
|
|
34
44
|
self._device_selection: Dict[DeviceType, BaseDeviceWidget] = {
|
|
35
|
-
DeviceType.
|
|
36
|
-
DeviceType.
|
|
37
|
-
DeviceType.
|
|
45
|
+
DeviceType.OTB_QUATTROCENTO: OTBQuattrocentoWidget(self),
|
|
46
|
+
DeviceType.OTB_QUATTROCENTO_LIGHT: OTBQuattrocentoLightWidget(self),
|
|
47
|
+
DeviceType.OTB_MUOVI: OTBMuoviWidget(self),
|
|
48
|
+
DeviceType.OTB_MUOVI_PLUS: OTBMuoviPlusWidget(self),
|
|
49
|
+
DeviceType.OTB_SYNCSTATION: OTBSyncStationWidget(self),
|
|
38
50
|
}
|
|
39
51
|
self._set_devices(self._device_selection)
|
|
@@ -19,6 +19,7 @@ from biosignal_device_interface.devices.core.base_device import BaseDevice
|
|
|
19
19
|
|
|
20
20
|
if TYPE_CHECKING:
|
|
21
21
|
from enum import Enum
|
|
22
|
+
from PySide6.QtWidgets import QLineEdit
|
|
22
23
|
|
|
23
24
|
|
|
24
25
|
class BaseDeviceWidget(QWidget):
|
|
@@ -36,7 +37,7 @@ class BaseDeviceWidget(QWidget):
|
|
|
36
37
|
self.parent_widget: QWidget | QMainWindow | None = parent
|
|
37
38
|
|
|
38
39
|
# Device Setup
|
|
39
|
-
self.
|
|
40
|
+
self._device: BaseDevice | None = None
|
|
40
41
|
self._device_params: Dict[str, Union[str, int, float]] = {}
|
|
41
42
|
|
|
42
43
|
# GUI setup
|
|
@@ -90,16 +91,16 @@ class BaseDeviceWidget(QWidget):
|
|
|
90
91
|
def _set_device(self, device: BaseDevice) -> None:
|
|
91
92
|
""" """
|
|
92
93
|
# Device Setup
|
|
93
|
-
self.
|
|
94
|
+
self._device: BaseDevice = device
|
|
94
95
|
self._initialize_device_params()
|
|
95
96
|
self._set_signals()
|
|
96
97
|
self._initialize_ui()
|
|
97
98
|
|
|
98
99
|
def _set_signals(self) -> None:
|
|
99
100
|
""" """
|
|
100
|
-
self.
|
|
101
|
-
self.
|
|
102
|
-
self.
|
|
101
|
+
self._device.data_available.connect(self.data_arrived.emit)
|
|
102
|
+
self._device.biosignal_data_available.connect(self.biosignal_data_arrived.emit)
|
|
103
|
+
self._device.auxiliary_data_available.connect(self.auxiliary_data_arrived.emit)
|
|
103
104
|
|
|
104
105
|
def get_device_information(self) -> Dict[str, Enum | int | float | str]:
|
|
105
106
|
"""
|
|
@@ -110,12 +111,20 @@ class BaseDeviceWidget(QWidget):
|
|
|
110
111
|
Dictionary that holds information about the
|
|
111
112
|
current device configuration and status.
|
|
112
113
|
"""
|
|
113
|
-
return self.
|
|
114
|
+
return self._device.get_device_information()
|
|
114
115
|
|
|
115
116
|
def disconnect_device(self) -> None:
|
|
116
117
|
""" """
|
|
117
|
-
if self.
|
|
118
|
-
self.
|
|
118
|
+
if self._device.is_connected or self._device._is_streaming:
|
|
119
|
+
self._device.toggle_connection()
|
|
120
|
+
|
|
121
|
+
def _check_ip_input(self, line_edit: QLineEdit, default: str) -> None:
|
|
122
|
+
if not self._device.check_valid_ip(line_edit.text()):
|
|
123
|
+
line_edit.setText(default)
|
|
124
|
+
|
|
125
|
+
def _check_port_input(self, line_edit: QLineEdit, default: str) -> None:
|
|
126
|
+
if not self._device.check_valid_port(line_edit.text()):
|
|
127
|
+
line_edit.setText(default)
|
|
119
128
|
|
|
120
129
|
def closeEvent(self, event: QCloseEvent) -> None:
|
|
121
130
|
self.disconnect_device()
|
|
@@ -49,9 +49,6 @@ class BaseMultipleDevicesWidget(QWidget):
|
|
|
49
49
|
|
|
50
50
|
self.device_stacked_widget = self.ui.deviceStackedWidget
|
|
51
51
|
self.device_selection_combo_box = self.ui.deviceSelectionComboBox
|
|
52
|
-
self.device_selection_combo_box.currentIndexChanged.connect(
|
|
53
|
-
self._update_stacked_widget
|
|
54
|
-
)
|
|
55
52
|
|
|
56
53
|
def get_device_information(self) -> Dict[str, Union[str, int]]:
|
|
57
54
|
return self._get_current_widget().get_device_information()
|
|
@@ -76,13 +73,16 @@ class BaseMultipleDevicesWidget(QWidget):
|
|
|
76
73
|
current_widget.stream_toggled.disconnect(self.stream_toggled)
|
|
77
74
|
|
|
78
75
|
except (TypeError, RuntimeError):
|
|
79
|
-
|
|
76
|
+
...
|
|
80
77
|
|
|
81
78
|
self.device_stacked_widget.setCurrentIndex(index)
|
|
82
79
|
current_widget = self._get_current_widget()
|
|
83
80
|
|
|
81
|
+
# Data arrived
|
|
84
82
|
current_widget.data_arrived.connect(self.data_arrived.emit)
|
|
83
|
+
# Biosignal data arrived
|
|
85
84
|
current_widget.biosignal_data_arrived.connect(self.biosignal_data_arrived.emit)
|
|
85
|
+
# Auxiliary data arrived
|
|
86
86
|
current_widget.auxiliary_data_arrived.connect(self.auxiliary_data_arrived.emit)
|
|
87
87
|
|
|
88
88
|
current_widget.connect_toggled.connect(self.connect_toggled)
|
|
@@ -97,6 +97,9 @@ class BaseMultipleDevicesWidget(QWidget):
|
|
|
97
97
|
self.device_selection_combo_box.addItem(DEVICE_NAME_DICT[device_type])
|
|
98
98
|
|
|
99
99
|
self._update_stacked_widget(0)
|
|
100
|
+
self.device_selection_combo_box.currentIndexChanged.connect(
|
|
101
|
+
self._update_stacked_widget
|
|
102
|
+
)
|
|
100
103
|
|
|
101
104
|
def _get_current_widget(self) -> BaseDeviceWidget:
|
|
102
105
|
return self.device_stacked_widget.currentWidget()
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
# OTB Devices
|
|
2
|
-
from biosignal_device_interface.gui.device_template_widgets.otb.otb_muovi_widget import (
|
|
3
|
-
MuoviWidget,
|
|
4
|
-
)
|
|
5
|
-
from biosignal_device_interface.gui.device_template_widgets.otb.otb_muovi_plus_widget import (
|
|
6
|
-
OTBMuoviWidget as MuoviPlusWidget,
|
|
7
|
-
)
|
|
8
|
-
from biosignal_device_interface.gui.device_template_widgets.otb.otb_quattrocento_light_widget import (
|
|
9
|
-
QuattrocentoLightWidget,
|
|
10
|
-
)
|
|
@@ -7,10 +7,20 @@ from biosignal_device_interface.gui.device_template_widgets.core.base_multiple_d
|
|
|
7
7
|
from biosignal_device_interface.constants.devices.core.base_device_constants import (
|
|
8
8
|
DeviceType,
|
|
9
9
|
)
|
|
10
|
-
from biosignal_device_interface.gui.device_template_widgets.otb import (
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
from biosignal_device_interface.gui.device_template_widgets.otb.otb_muovi_plus_widget import (
|
|
11
|
+
OTBMuoviPlusWidget,
|
|
12
|
+
)
|
|
13
|
+
from biosignal_device_interface.gui.device_template_widgets.otb.otb_muovi_widget import (
|
|
14
|
+
OTBMuoviWidget,
|
|
15
|
+
)
|
|
16
|
+
from biosignal_device_interface.gui.device_template_widgets.otb.otb_quattrocento_light_widget import (
|
|
17
|
+
OTBQuattrocentoLightWidget,
|
|
18
|
+
)
|
|
19
|
+
from biosignal_device_interface.gui.device_template_widgets.otb.otb_quattrocento_widget import (
|
|
20
|
+
OTBQuattrocentoWidget,
|
|
21
|
+
)
|
|
22
|
+
from biosignal_device_interface.gui.device_template_widgets.otb.otb_syncstation_widget import (
|
|
23
|
+
OTBSyncStationWidget,
|
|
14
24
|
)
|
|
15
25
|
|
|
16
26
|
if TYPE_CHECKING:
|
|
@@ -20,13 +30,15 @@ if TYPE_CHECKING:
|
|
|
20
30
|
)
|
|
21
31
|
|
|
22
32
|
|
|
23
|
-
class
|
|
33
|
+
class OTBDevicesWidget(BaseMultipleDevicesWidget):
|
|
24
34
|
def __init__(self, parent: QWidget | QMainWindow | None = None):
|
|
25
35
|
super().__init__(parent)
|
|
26
36
|
|
|
27
37
|
self._device_selection: Dict[DeviceType, BaseDeviceWidget] = {
|
|
28
|
-
DeviceType.
|
|
29
|
-
DeviceType.
|
|
30
|
-
DeviceType.
|
|
38
|
+
DeviceType.OTB_QUATTROCENTO: OTBQuattrocentoWidget(self),
|
|
39
|
+
DeviceType.OTB_QUATTROCENTO_LIGHT: OTBQuattrocentoLightWidget(self),
|
|
40
|
+
DeviceType.OTB_MUOVI: OTBMuoviWidget(self),
|
|
41
|
+
DeviceType.OTB_MUOVI_PLUS: OTBMuoviPlusWidget(self),
|
|
42
|
+
DeviceType.OTB_SYNCSTATION: OTBSyncStationWidget(self),
|
|
31
43
|
}
|
|
32
44
|
self._set_devices(self._device_selection)
|