biosignal-device-interface 0.1.2__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/constants/devices/core/base_device_constants.py +10 -0
- biosignal_device_interface/constants/devices/otb/otb_quattrocento_constants.py +313 -0
- biosignal_device_interface/constants/devices/otb/otb_syncstation_constants.py +233 -0
- biosignal_device_interface/devices/__init__.py +2 -0
- biosignal_device_interface/devices/core/base_device.py +5 -3
- biosignal_device_interface/devices/otb/__init__.py +8 -0
- biosignal_device_interface/devices/otb/otb_muovi.py +4 -5
- biosignal_device_interface/devices/otb/otb_quattrocento.py +332 -0
- biosignal_device_interface/devices/otb/otb_quattrocento_light.py +4 -7
- biosignal_device_interface/devices/otb/otb_syncstation.py +407 -0
- biosignal_device_interface/gui/device_template_widgets/all_devices_widget.py +8 -0
- biosignal_device_interface/gui/device_template_widgets/core/base_device_widget.py +17 -8
- biosignal_device_interface/gui/device_template_widgets/otb/otb_devices_widget.py +8 -0
- biosignal_device_interface/gui/device_template_widgets/otb/otb_muovi_plus_widget.py +9 -9
- biosignal_device_interface/gui/device_template_widgets/otb/otb_muovi_widget.py +9 -9
- biosignal_device_interface/gui/device_template_widgets/otb/otb_quattrocento_light_widget.py +59 -55
- 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 +7 -2
- 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.2.dist-info → biosignal_device_interface-0.1.4a1.dist-info}/METADATA +5 -3
- {biosignal_device_interface-0.1.2.dist-info → biosignal_device_interface-0.1.4a1.dist-info}/RECORD +27 -17
- biosignal_device_interface-0.1.2.dist-info/LICENSE +0 -395
- {biosignal_device_interface-0.1.2.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
|
|
@@ -23,6 +23,12 @@ from biosignal_device_interface.gui.device_template_widgets.otb.otb_muovi_widget
|
|
|
23
23
|
from biosignal_device_interface.gui.device_template_widgets.otb.otb_quattrocento_light_widget import (
|
|
24
24
|
OTBQuattrocentoLightWidget,
|
|
25
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,
|
|
31
|
+
)
|
|
26
32
|
|
|
27
33
|
if TYPE_CHECKING:
|
|
28
34
|
from PySide6.QtWidgets import QWidget, QMainWindow
|
|
@@ -36,8 +42,10 @@ class AllDevicesWidget(BaseMultipleDevicesWidget):
|
|
|
36
42
|
super().__init__(parent)
|
|
37
43
|
|
|
38
44
|
self._device_selection: Dict[DeviceType, BaseDeviceWidget] = {
|
|
45
|
+
DeviceType.OTB_QUATTROCENTO: OTBQuattrocentoWidget(self),
|
|
39
46
|
DeviceType.OTB_QUATTROCENTO_LIGHT: OTBQuattrocentoLightWidget(self),
|
|
40
47
|
DeviceType.OTB_MUOVI: OTBMuoviWidget(self),
|
|
41
48
|
DeviceType.OTB_MUOVI_PLUS: OTBMuoviPlusWidget(self),
|
|
49
|
+
DeviceType.OTB_SYNCSTATION: OTBSyncStationWidget(self),
|
|
42
50
|
}
|
|
43
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()
|
|
@@ -16,6 +16,12 @@ from biosignal_device_interface.gui.device_template_widgets.otb.otb_muovi_widget
|
|
|
16
16
|
from biosignal_device_interface.gui.device_template_widgets.otb.otb_quattrocento_light_widget import (
|
|
17
17
|
OTBQuattrocentoLightWidget,
|
|
18
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,
|
|
24
|
+
)
|
|
19
25
|
|
|
20
26
|
if TYPE_CHECKING:
|
|
21
27
|
from PySide6.QtWidgets import QWidget, QMainWindow
|
|
@@ -29,8 +35,10 @@ class OTBDevicesWidget(BaseMultipleDevicesWidget):
|
|
|
29
35
|
super().__init__(parent)
|
|
30
36
|
|
|
31
37
|
self._device_selection: Dict[DeviceType, BaseDeviceWidget] = {
|
|
38
|
+
DeviceType.OTB_QUATTROCENTO: OTBQuattrocentoWidget(self),
|
|
32
39
|
DeviceType.OTB_QUATTROCENTO_LIGHT: OTBQuattrocentoLightWidget(self),
|
|
33
40
|
DeviceType.OTB_MUOVI: OTBMuoviWidget(self),
|
|
34
41
|
DeviceType.OTB_MUOVI_PLUS: OTBMuoviPlusWidget(self),
|
|
42
|
+
DeviceType.OTB_SYNCSTATION: OTBSyncStationWidget(self),
|
|
35
43
|
}
|
|
36
44
|
self._set_devices(self._device_selection)
|
|
@@ -34,10 +34,10 @@ class OTBMuoviPlusWidget(BaseDeviceWidget):
|
|
|
34
34
|
self._set_device(OTBMuovi(parent=self, is_muovi_plus=True))
|
|
35
35
|
|
|
36
36
|
def _toggle_connection(self) -> None:
|
|
37
|
-
if not self.
|
|
37
|
+
if not self._device.is_connected:
|
|
38
38
|
self.connect_push_button.setEnabled(False)
|
|
39
39
|
|
|
40
|
-
self.
|
|
40
|
+
self._device.toggle_connection(
|
|
41
41
|
(
|
|
42
42
|
self.connection_ip_combo_box.currentText(),
|
|
43
43
|
int(self.connection_port_label.text()),
|
|
@@ -68,7 +68,7 @@ class OTBMuoviPlusWidget(BaseDeviceWidget):
|
|
|
68
68
|
self.input_detection_mode_combo_box.currentIndex() + 1
|
|
69
69
|
)
|
|
70
70
|
|
|
71
|
-
self.
|
|
71
|
+
self._device.configure_device(self._device_params)
|
|
72
72
|
|
|
73
73
|
def _configuration_toggled(self, is_configured: bool) -> None:
|
|
74
74
|
if is_configured:
|
|
@@ -82,7 +82,7 @@ class OTBMuoviPlusWidget(BaseDeviceWidget):
|
|
|
82
82
|
|
|
83
83
|
def _toggle_stream(self) -> None:
|
|
84
84
|
self.stream_push_button.setEnabled(False)
|
|
85
|
-
self.
|
|
85
|
+
self._device.toggle_streaming()
|
|
86
86
|
|
|
87
87
|
def _stream_toggled(self, is_streaming: bool) -> None:
|
|
88
88
|
self.stream_push_button.setEnabled(True)
|
|
@@ -112,17 +112,17 @@ class OTBMuoviPlusWidget(BaseDeviceWidget):
|
|
|
112
112
|
# Command Push Buttons
|
|
113
113
|
self.connect_push_button: QPushButton = self.ui.commandConnectionPushButton
|
|
114
114
|
self.connect_push_button.clicked.connect(self._toggle_connection)
|
|
115
|
-
self.
|
|
115
|
+
self._device.connect_toggled.connect(self._connection_toggled)
|
|
116
116
|
|
|
117
117
|
self.configure_push_button: QPushButton = self.ui.commandConfigurationPushButton
|
|
118
118
|
self.configure_push_button.clicked.connect(self._toggle_configuration)
|
|
119
119
|
self.configure_push_button.setEnabled(False)
|
|
120
|
-
self.
|
|
120
|
+
self._device.configure_toggled.connect(self._configuration_toggled)
|
|
121
121
|
|
|
122
122
|
self.stream_push_button: QPushButton = self.ui.commandStreamPushButton
|
|
123
123
|
self.stream_push_button.clicked.connect(self._toggle_stream)
|
|
124
124
|
self.stream_push_button.setEnabled(False)
|
|
125
|
-
self.
|
|
125
|
+
self._device.stream_toggled.connect(self._stream_toggled)
|
|
126
126
|
|
|
127
127
|
# Connection parameters
|
|
128
128
|
self.connection_group_box: QGroupBox = self.ui.connectionGroupBox
|
|
@@ -135,13 +135,13 @@ class OTBMuoviPlusWidget(BaseDeviceWidget):
|
|
|
135
135
|
lambda: (
|
|
136
136
|
self.connection_ip_combo_box.clear(),
|
|
137
137
|
self.connection_ip_combo_box.addItems(
|
|
138
|
-
self.
|
|
138
|
+
self._device.get_server_wifi_ip_address()
|
|
139
139
|
),
|
|
140
140
|
)
|
|
141
141
|
)
|
|
142
142
|
|
|
143
143
|
self.connection_ip_combo_box.clear()
|
|
144
|
-
self.connection_ip_combo_box.addItems(self.
|
|
144
|
+
self.connection_ip_combo_box.addItems(self._device.get_server_wifi_ip_address())
|
|
145
145
|
|
|
146
146
|
self.connection_port_label.setText(str(MUOVI_NETWORK_PORT))
|
|
147
147
|
|
|
@@ -34,10 +34,10 @@ class OTBMuoviWidget(BaseDeviceWidget):
|
|
|
34
34
|
self._set_device(OTBMuovi(parent=self))
|
|
35
35
|
|
|
36
36
|
def _toggle_connection(self) -> None:
|
|
37
|
-
if not self.
|
|
37
|
+
if not self._device.is_connected:
|
|
38
38
|
self.connect_push_button.setEnabled(False)
|
|
39
39
|
|
|
40
|
-
self.
|
|
40
|
+
self._device.toggle_connection(
|
|
41
41
|
(
|
|
42
42
|
self.connection_ip_combo_box.currentText(),
|
|
43
43
|
int(self.connection_port_label.text()),
|
|
@@ -68,7 +68,7 @@ class OTBMuoviWidget(BaseDeviceWidget):
|
|
|
68
68
|
self.input_detection_mode_combo_box.currentIndex() + 1
|
|
69
69
|
)
|
|
70
70
|
|
|
71
|
-
self.
|
|
71
|
+
self._device.configure_device(self._device_params)
|
|
72
72
|
|
|
73
73
|
def _configuration_toggled(self, is_configured: bool) -> None:
|
|
74
74
|
if is_configured:
|
|
@@ -82,7 +82,7 @@ class OTBMuoviWidget(BaseDeviceWidget):
|
|
|
82
82
|
|
|
83
83
|
def _toggle_stream(self) -> None:
|
|
84
84
|
self.stream_push_button.setEnabled(False)
|
|
85
|
-
self.
|
|
85
|
+
self._device.toggle_streaming()
|
|
86
86
|
|
|
87
87
|
def _stream_toggled(self, is_streaming: bool) -> None:
|
|
88
88
|
self.stream_push_button.setEnabled(True)
|
|
@@ -112,17 +112,17 @@ class OTBMuoviWidget(BaseDeviceWidget):
|
|
|
112
112
|
# Command Push Buttons
|
|
113
113
|
self.connect_push_button: QPushButton = self.ui.commandConnectionPushButton
|
|
114
114
|
self.connect_push_button.clicked.connect(self._toggle_connection)
|
|
115
|
-
self.
|
|
115
|
+
self._device.connect_toggled.connect(self._connection_toggled)
|
|
116
116
|
|
|
117
117
|
self.configure_push_button: QPushButton = self.ui.commandConfigurationPushButton
|
|
118
118
|
self.configure_push_button.clicked.connect(self._toggle_configuration)
|
|
119
119
|
self.configure_push_button.setEnabled(False)
|
|
120
|
-
self.
|
|
120
|
+
self._device.configure_toggled.connect(self._configuration_toggled)
|
|
121
121
|
|
|
122
122
|
self.stream_push_button: QPushButton = self.ui.commandStreamPushButton
|
|
123
123
|
self.stream_push_button.clicked.connect(self._toggle_stream)
|
|
124
124
|
self.stream_push_button.setEnabled(False)
|
|
125
|
-
self.
|
|
125
|
+
self._device.stream_toggled.connect(self._stream_toggled)
|
|
126
126
|
|
|
127
127
|
# Connection parameters
|
|
128
128
|
self.connection_group_box: QGroupBox = self.ui.connectionGroupBox
|
|
@@ -135,13 +135,13 @@ class OTBMuoviWidget(BaseDeviceWidget):
|
|
|
135
135
|
lambda: (
|
|
136
136
|
self.connection_ip_combo_box.clear(),
|
|
137
137
|
self.connection_ip_combo_box.addItems(
|
|
138
|
-
self.
|
|
138
|
+
self._device.get_server_wifi_ip_address()
|
|
139
139
|
),
|
|
140
140
|
)
|
|
141
141
|
)
|
|
142
142
|
|
|
143
143
|
self.connection_ip_combo_box.clear()
|
|
144
|
-
self.connection_ip_combo_box.addItems(self.
|
|
144
|
+
self.connection_ip_combo_box.addItems(self._device.get_server_wifi_ip_address())
|
|
145
145
|
|
|
146
146
|
self.connection_port_label.setText(str(MUOVI_NETWORK_PORT))
|
|
147
147
|
|