biosignal-device-interface 0.2.1a1__py3-none-any.whl → 0.2.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. biosignal_device_interface/constants/devices/__init__.py +3 -3
  2. biosignal_device_interface/constants/devices/core/base_device_constants.py +61 -61
  3. biosignal_device_interface/constants/devices/otb/otb_muovi_constants.py +129 -129
  4. biosignal_device_interface/constants/devices/otb/otb_quattrocento_constants.py +313 -313
  5. biosignal_device_interface/constants/devices/otb/otb_quattrocento_light_constants.py +59 -59
  6. biosignal_device_interface/constants/devices/otb/otb_syncstation_constants.py +233 -233
  7. biosignal_device_interface/constants/plots/color_palette.py +59 -59
  8. biosignal_device_interface/devices/__init__.py +17 -17
  9. biosignal_device_interface/devices/core/base_device.py +424 -412
  10. biosignal_device_interface/devices/otb/__init__.py +29 -29
  11. biosignal_device_interface/devices/otb/otb_muovi.py +290 -290
  12. biosignal_device_interface/devices/otb/otb_quattrocento.py +332 -332
  13. biosignal_device_interface/devices/otb/otb_quattrocento_light.py +210 -210
  14. biosignal_device_interface/devices/otb/otb_syncstation.py +407 -407
  15. biosignal_device_interface/gui/device_template_widgets/all_devices_widget.py +51 -51
  16. biosignal_device_interface/gui/device_template_widgets/core/base_device_widget.py +130 -130
  17. biosignal_device_interface/gui/device_template_widgets/core/base_multiple_devices_widget.py +108 -108
  18. biosignal_device_interface/gui/device_template_widgets/otb/otb_devices_widget.py +44 -44
  19. biosignal_device_interface/gui/device_template_widgets/otb/otb_muovi_plus_widget.py +158 -158
  20. biosignal_device_interface/gui/device_template_widgets/otb/otb_muovi_widget.py +158 -158
  21. biosignal_device_interface/gui/device_template_widgets/otb/otb_quattrocento_light_widget.py +174 -174
  22. biosignal_device_interface/gui/device_template_widgets/otb/otb_quattrocento_widget.py +260 -260
  23. biosignal_device_interface/gui/device_template_widgets/otb/otb_syncstation_widget.py +262 -262
  24. biosignal_device_interface/gui/plot_widgets/biosignal_plot_widget.py +500 -501
  25. biosignal_device_interface/gui/ui/devices_template_widget.ui +38 -38
  26. biosignal_device_interface/gui/ui/otb_muovi_plus_template_widget.ui +171 -171
  27. biosignal_device_interface/gui/ui/otb_muovi_template_widget.ui +171 -171
  28. biosignal_device_interface/gui/ui/otb_quattrocento_light_template_widget.ui +266 -266
  29. biosignal_device_interface/gui/ui/otb_quattrocento_template_widget.ui +415 -415
  30. biosignal_device_interface/gui/ui/otb_syncstation_template_widget.ui +732 -732
  31. biosignal_device_interface/gui/ui_compiled/devices_template_widget.py +56 -56
  32. biosignal_device_interface/gui/ui_compiled/otb_muovi_plus_template_widget.py +153 -153
  33. biosignal_device_interface/gui/ui_compiled/otb_muovi_template_widget.py +153 -153
  34. biosignal_device_interface/gui/ui_compiled/otb_quattrocento_light_template_widget.py +217 -217
  35. biosignal_device_interface/gui/ui_compiled/otb_quattrocento_template_widget.py +318 -318
  36. biosignal_device_interface/gui/ui_compiled/otb_syncstation_template_widget.py +495 -495
  37. {biosignal_device_interface-0.2.1a1.dist-info → biosignal_device_interface-0.2.2.dist-info}/METADATA +3 -2
  38. biosignal_device_interface-0.2.2.dist-info/RECORD +46 -0
  39. {biosignal_device_interface-0.2.1a1.dist-info → biosignal_device_interface-0.2.2.dist-info}/WHEEL +1 -1
  40. {biosignal_device_interface-0.2.1a1.dist-info → biosignal_device_interface-0.2.2.dist-info/licenses}/LICENSE +675 -675
  41. biosignal_device_interface-0.2.1a1.dist-info/RECORD +0 -46
@@ -1,332 +1,332 @@
1
- """
2
- Quattrocento class for real-time direct interface to
3
- Quattrocento.
4
-
5
- Developer: Dominik I. Braun
6
- Contact: dome.braun@fau.de
7
- Last Update: 2025-01-14
8
- """
9
-
10
- # Python Libraries
11
- from __future__ import annotations
12
- from typing import TYPE_CHECKING, Union, Dict
13
- from PySide6.QtNetwork import QTcpSocket, QHostAddress
14
- from PySide6.QtCore import QIODevice
15
- import numpy as np
16
-
17
-
18
- from biosignal_device_interface.devices.core.base_device import BaseDevice
19
- from biosignal_device_interface.constants.devices.core.base_device_constants import (
20
- DeviceType,
21
- )
22
- from biosignal_device_interface.constants.devices.otb.otb_quattrocento_constants import (
23
- QUATTROCENTO_AUXILIARY_CHANNELS,
24
- QUATTROCENTO_BYTES_PER_SAMPLE,
25
- QUATTROCENTO_SAMPLES_PER_FRAME,
26
- QUATTROCENTO_SUPPLEMENTARY_CHANNELS,
27
- QuattrocentoAcqSettByte,
28
- QuattrocentoINXConf2Byte,
29
- QuattrocentoRecordingMode,
30
- QuattrocentoSamplingFrequencyMode,
31
- QuattrocentoNumberOfChannelsMode,
32
- QuattrocentoLowPassFilterMode,
33
- QuattrocentoHighPassFilterMode,
34
- QuattrocentoDetectionMode,
35
- )
36
-
37
- if TYPE_CHECKING:
38
- # Python Libraries
39
- from PySide6.QtWidgets import QMainWindow, QWidget
40
- from aenum import Enum
41
-
42
-
43
- class OTBQuattrocento(BaseDevice):
44
- """
45
- Quattrocento device class derived from BaseDevice class.
46
- The Quattrocento is using a TCP/IP protocol to communicate with the device.
47
-
48
- This class directly interfaces with the Quattrocento from
49
- OT Bioelettronica.
50
- """
51
-
52
- def __init__(self, parent: Union[QMainWindow, QWidget] = None):
53
- super().__init__(parent)
54
-
55
- # Device Paramters
56
- self._device_type: DeviceType = DeviceType.OTB_QUATTROCENTO
57
-
58
- # Device Information
59
- self._conversion_factor_biosignal: float = 5 / (2**16) / 150 * 1000 # in mV
60
- self._conversion_factor_auxiliary: float = 5 / (2**16) / 0.5 # in mV
61
- self._number_of_streamed_channels: int = None
62
-
63
- # Connection Parameters
64
- self._interface: QTcpSocket = QTcpSocket()
65
-
66
- # Configuration parameters
67
- self._acq_sett_configuration: QuattrocentoAcqSettByte = (
68
- QuattrocentoAcqSettByte()
69
- )
70
- self._input_top_left_configuration: QuattrocentoINXConf2Byte = (
71
- QuattrocentoINXConf2Byte()
72
- )
73
- self._input_top_right_configuration: QuattrocentoINXConf2Byte = (
74
- QuattrocentoINXConf2Byte()
75
- )
76
- self._multiple_input_one_configuration: QuattrocentoINXConf2Byte = (
77
- QuattrocentoINXConf2Byte()
78
- )
79
- self._multiple_input_two_configuration: QuattrocentoINXConf2Byte = (
80
- QuattrocentoINXConf2Byte()
81
- )
82
- self._multiple_input_three_configuration: QuattrocentoINXConf2Byte = (
83
- QuattrocentoINXConf2Byte()
84
- )
85
- self._multiple_input_four_configuration: QuattrocentoINXConf2Byte = (
86
- QuattrocentoINXConf2Byte()
87
- )
88
- self._grid_size: int = 64 # TODO: This is only valid for the big electrodes
89
- self._grids: list[int] | None = None
90
-
91
- self._configuration_command: bytearray = bytearray(40)
92
-
93
- def _connect_to_device(self) -> bool:
94
- super()._connect_to_device()
95
-
96
- self._received_bytes: bytearray = bytearray()
97
- return self._make_request()
98
-
99
- def _make_request(self) -> bool:
100
- super()._make_request()
101
-
102
- self._interface.connectToHost(
103
- QHostAddress(self._connection_settings[0]), self._connection_settings[1]
104
- )
105
-
106
- if not self._interface.waitForConnected(1000):
107
- self._disconnect_from_device()
108
- return False
109
-
110
- self._interface.readyRead.connect(self._read_data)
111
-
112
- self.is_connected = True
113
- self.connect_toggled.emit(self.is_connected)
114
-
115
- return True
116
-
117
- def _disconnect_from_device(self) -> None:
118
- super()._disconnect_from_device()
119
-
120
- self._interface.disconnectFromHost()
121
- self._interface.readyRead.disconnect(self._read_data)
122
- self._interface.close()
123
-
124
- def configure_device(
125
- self, params: Dict[str, Union[Enum, Dict[str, Enum]]] # type: ignore
126
- ):
127
- super().configure_device(params)
128
-
129
- self._sampling_frequency = self._acq_sett_configuration.get_sampling_frequency()
130
- self._number_of_streamed_channels = (
131
- self._acq_sett_configuration.get_number_of_channels()
132
- )
133
-
134
- self._configuration_command = bytearray(40)
135
- # Configure the device
136
- # Byte 1: ACQ_SETT
137
- self._configuration_command[0] = int(self._acq_sett_configuration)
138
-
139
- # Byte 2: Configure AN_OUT_IN_SEL
140
- self._configuration_command[1] = 0 # TODO:
141
-
142
- # Byte 3: Configure AN_OUT_CH_SEL
143
- self._configuration_command[2] = 0 # TODO:
144
-
145
- # Byte 4-15: Configure IN1-4 -> TODO: change that to individual configuration
146
- for i in range(4):
147
- config = int(self._input_top_left_configuration)
148
- self._configuration_command[3 + i * 3] = (config >> 16) & 0xFF
149
- self._configuration_command[4 + i * 3] = (config >> 8) & 0xFF
150
- self._configuration_command[5 + i * 3] = config & 0xFF
151
-
152
- # Byte 16-27: Configure IN5-8 -> TODO: change that to individual configuration
153
- for i in range(4):
154
- config = int(self._input_top_right_configuration)
155
- self._configuration_command[15 + i * 3] = (config >> 16) & 0xFF
156
- self._configuration_command[16 + i * 3] = (config >> 8) & 0xFF
157
- self._configuration_command[17 + i * 3] = config & 0xFF
158
-
159
- # Byte 28-30: Configure MULTIPLE IN 1
160
- config = int(self._multiple_input_one_configuration)
161
- self._configuration_command[27] = (config >> 16) & 0xFF
162
- self._configuration_command[28] = (config >> 8) & 0xFF
163
- self._configuration_command[29] = config & 0xFF
164
-
165
- # Byte 31-33: Configure MULTIPLE IN 2
166
- config = int(self._multiple_input_two_configuration)
167
- self._configuration_command[30] = (config >> 16) & 0xFF
168
- self._configuration_command[31] = (config >> 8) & 0xFF
169
- self._configuration_command[32] = config & 0xFF
170
-
171
- # Byte 34-36: Configure MULTIPLE IN 3
172
- config = int(self._multiple_input_three_configuration)
173
- self._configuration_command[33] = (config >> 16) & 0xFF
174
- self._configuration_command[34] = (config >> 8) & 0xFF
175
- self._configuration_command[35] = config & 0xFF
176
-
177
- # Byte 37-39: Configure MULTIPLE IN 4
178
- config = int(self._multiple_input_four_configuration)
179
- self._configuration_command[36] = (config >> 16) & 0xFF
180
- self._configuration_command[37] = (config >> 8) & 0xFF
181
- self._configuration_command[38] = config & 0xFF
182
-
183
- # Control Byte
184
- self._configuration_command[39] = self._crc_check(
185
- self._configuration_command, 39
186
- )
187
-
188
- self._number_of_channels = (
189
- len(self._grids) * self._grid_size
190
- + QUATTROCENTO_AUXILIARY_CHANNELS
191
- + QUATTROCENTO_SUPPLEMENTARY_CHANNELS
192
- )
193
-
194
- self._number_of_biosignal_channels = len(self._grids) * self._grid_size
195
- self._biosignal_channel_indices = np.array(
196
- [
197
- i * self._grid_size + j
198
- for i in self._grids
199
- for j in range(self._grid_size)
200
- ]
201
- )
202
- self._number_of_auxiliary_channels = QUATTROCENTO_AUXILIARY_CHANNELS
203
- self._auxiliary_channel_indices = np.arange(
204
- self._number_of_streamed_channels
205
- - self._number_of_auxiliary_channels
206
- - QUATTROCENTO_SUPPLEMENTARY_CHANNELS,
207
- self._number_of_streamed_channels - QUATTROCENTO_SUPPLEMENTARY_CHANNELS,
208
- )
209
-
210
- self._samples_per_frame = QUATTROCENTO_SAMPLES_PER_FRAME
211
- self._bytes_per_sample = QUATTROCENTO_BYTES_PER_SAMPLE
212
- self._buffer_size = (
213
- self._number_of_streamed_channels * self._bytes_per_sample
214
- ) * self._samples_per_frame
215
-
216
- self._send_configuration_to_device()
217
-
218
- def _crc_check(self, command_bytes: bytearray, command_length: int) -> bytes:
219
- """
220
- Performs the Cyclic Redundancy Check (CRC) of the transmitted bytes.
221
-
222
- Translated function from example code provided by OT Bioelettronica.
223
-
224
- Args:
225
- command_bytes (bytearray):
226
- Bytearray of the transmitted bytes.
227
-
228
- command_length (int):
229
- Length of the transmitted bytes.
230
-
231
- Returns:
232
- bytes:
233
- CRC of the transmitted bytes.
234
- """
235
-
236
- crc = 0
237
- j = 0
238
-
239
- while command_length > 0:
240
- extracted_byte = command_bytes[j]
241
- for i in range(8, 0, -1):
242
- sum = crc % 2 ^ extracted_byte % 2
243
- crc = crc // 2
244
-
245
- if sum > 0:
246
- crc_bin = format(crc, "08b")
247
- a_bin = format(140, "08b")
248
-
249
- str_list = []
250
-
251
- for k in range(8):
252
- str_list.append("0" if crc_bin[k] == a_bin[k] else "1")
253
-
254
- crc = int("".join(str_list), 2)
255
-
256
- extracted_byte = extracted_byte // 2
257
-
258
- command_length -= 1
259
- j += 1
260
-
261
- return crc
262
-
263
- def _send_configuration_to_device(self) -> None:
264
- success = self._interface.write(self._configuration_command)
265
-
266
- if success == -1:
267
- print("Error while sending configuration to device")
268
-
269
- self._is_configured = True
270
- self.configure_toggled.emit(self._is_configured)
271
-
272
- def _stop_streaming(self) -> None:
273
- super()._stop_streaming()
274
-
275
- self._configuration_command[0] -= 1
276
- self._configuration_command[39] = self._crc_check(
277
- self._configuration_command, 39
278
- )
279
- self._send_configuration_to_device()
280
-
281
- def _start_streaming(self) -> None:
282
- super()._start_streaming()
283
-
284
- self._configuration_command[0] += 1
285
- self._configuration_command[39] = self._crc_check(
286
- self._configuration_command, 39
287
- )
288
- self._send_configuration_to_device()
289
-
290
- self._is_streaming = True
291
- self.stream_toggled.emit(self._is_streaming)
292
-
293
- def clear_socket(self) -> None:
294
- if self._interface is not None:
295
- self._interface.readAll()
296
-
297
- def _read_data(self) -> None:
298
- super()._read_data()
299
-
300
- if not self._is_streaming:
301
- self.clear_socket()
302
- return
303
-
304
- while self._interface.bytesAvailable() > self._buffer_size:
305
- packet = self._interface.readAll()
306
- if not packet:
307
- continue
308
-
309
- self._received_bytes.extend(packet)
310
-
311
- while len(self._received_bytes) >= self._buffer_size:
312
- data_to_process = self._received_bytes[: self._buffer_size]
313
- self._process_data(data_to_process)
314
- self._received_bytes = self._received_bytes[self._buffer_size :]
315
-
316
- def _process_data(self, input: bytearray) -> None:
317
- super()._process_data(input)
318
-
319
- data = np.frombuffer(input, dtype="<i2")
320
- reshaped_data = data.reshape(
321
- (self._number_of_streamed_channels, -1), order="F"
322
- ).astype(np.float32)
323
-
324
- biosignal_data = self._extract_biosignal_data(reshaped_data)
325
- auxiliary_data = self._extract_auxiliary_data(reshaped_data)
326
- supplementary_data = reshaped_data[-QUATTROCENTO_SUPPLEMENTARY_CHANNELS:]
327
-
328
- processed_data = np.vstack((biosignal_data, auxiliary_data, supplementary_data))
329
-
330
- self.data_available.emit(processed_data)
331
- self.biosignal_data_available.emit(biosignal_data)
332
- self.auxiliary_data_available.emit(auxiliary_data)
1
+ """
2
+ Quattrocento class for real-time direct interface to
3
+ Quattrocento.
4
+
5
+ Developer: Dominik I. Braun
6
+ Contact: dome.braun@fau.de
7
+ Last Update: 2025-01-14
8
+ """
9
+
10
+ # Python Libraries
11
+ from __future__ import annotations
12
+ from typing import TYPE_CHECKING, Union, Dict
13
+ from PySide6.QtNetwork import QTcpSocket, QHostAddress
14
+ from PySide6.QtCore import QIODevice
15
+ import numpy as np
16
+
17
+
18
+ from biosignal_device_interface.devices.core.base_device import BaseDevice
19
+ from biosignal_device_interface.constants.devices.core.base_device_constants import (
20
+ DeviceType,
21
+ )
22
+ from biosignal_device_interface.constants.devices.otb.otb_quattrocento_constants import (
23
+ QUATTROCENTO_AUXILIARY_CHANNELS,
24
+ QUATTROCENTO_BYTES_PER_SAMPLE,
25
+ QUATTROCENTO_SAMPLES_PER_FRAME,
26
+ QUATTROCENTO_SUPPLEMENTARY_CHANNELS,
27
+ QuattrocentoAcqSettByte,
28
+ QuattrocentoINXConf2Byte,
29
+ QuattrocentoRecordingMode,
30
+ QuattrocentoSamplingFrequencyMode,
31
+ QuattrocentoNumberOfChannelsMode,
32
+ QuattrocentoLowPassFilterMode,
33
+ QuattrocentoHighPassFilterMode,
34
+ QuattrocentoDetectionMode,
35
+ )
36
+
37
+ if TYPE_CHECKING:
38
+ # Python Libraries
39
+ from PySide6.QtWidgets import QMainWindow, QWidget
40
+ from aenum import Enum
41
+
42
+
43
+ class OTBQuattrocento(BaseDevice):
44
+ """
45
+ Quattrocento device class derived from BaseDevice class.
46
+ The Quattrocento is using a TCP/IP protocol to communicate with the device.
47
+
48
+ This class directly interfaces with the Quattrocento from
49
+ OT Bioelettronica.
50
+ """
51
+
52
+ def __init__(self, parent: Union[QMainWindow, QWidget] = None):
53
+ super().__init__(parent)
54
+
55
+ # Device Paramters
56
+ self._device_type: DeviceType = DeviceType.OTB_QUATTROCENTO
57
+
58
+ # Device Information
59
+ self._conversion_factor_biosignal: float = 5 / (2**16) / 150 * 1000 # in mV
60
+ self._conversion_factor_auxiliary: float = 5 / (2**16) / 0.5 # in mV
61
+ self._number_of_streamed_channels: int = None
62
+
63
+ # Connection Parameters
64
+ self._interface: QTcpSocket = QTcpSocket()
65
+
66
+ # Configuration parameters
67
+ self._acq_sett_configuration: QuattrocentoAcqSettByte = (
68
+ QuattrocentoAcqSettByte()
69
+ )
70
+ self._input_top_left_configuration: QuattrocentoINXConf2Byte = (
71
+ QuattrocentoINXConf2Byte()
72
+ )
73
+ self._input_top_right_configuration: QuattrocentoINXConf2Byte = (
74
+ QuattrocentoINXConf2Byte()
75
+ )
76
+ self._multiple_input_one_configuration: QuattrocentoINXConf2Byte = (
77
+ QuattrocentoINXConf2Byte()
78
+ )
79
+ self._multiple_input_two_configuration: QuattrocentoINXConf2Byte = (
80
+ QuattrocentoINXConf2Byte()
81
+ )
82
+ self._multiple_input_three_configuration: QuattrocentoINXConf2Byte = (
83
+ QuattrocentoINXConf2Byte()
84
+ )
85
+ self._multiple_input_four_configuration: QuattrocentoINXConf2Byte = (
86
+ QuattrocentoINXConf2Byte()
87
+ )
88
+ self._grid_size: int = 64 # TODO: This is only valid for the big electrodes
89
+ self._grids: list[int] | None = None
90
+
91
+ self._configuration_command: bytearray = bytearray(40)
92
+
93
+ def _connect_to_device(self) -> bool:
94
+ super()._connect_to_device()
95
+
96
+ self._received_bytes: bytearray = bytearray()
97
+ return self._make_request()
98
+
99
+ def _make_request(self) -> bool:
100
+ super()._make_request()
101
+
102
+ self._interface.connectToHost(
103
+ QHostAddress(self._connection_settings[0]), self._connection_settings[1]
104
+ )
105
+
106
+ if not self._interface.waitForConnected(1000):
107
+ self._disconnect_from_device()
108
+ return False
109
+
110
+ self._interface.readyRead.connect(self._read_data)
111
+
112
+ self.is_connected = True
113
+ self.connect_toggled.emit(self.is_connected)
114
+
115
+ return True
116
+
117
+ def _disconnect_from_device(self) -> None:
118
+ super()._disconnect_from_device()
119
+
120
+ self._interface.disconnectFromHost()
121
+ self._interface.readyRead.disconnect(self._read_data)
122
+ self._interface.close()
123
+
124
+ def configure_device(
125
+ self, params: Dict[str, Union[Enum, Dict[str, Enum]]] # type: ignore
126
+ ):
127
+ super().configure_device(params)
128
+
129
+ self._sampling_frequency = self._acq_sett_configuration.get_sampling_frequency()
130
+ self._number_of_streamed_channels = (
131
+ self._acq_sett_configuration.get_number_of_channels()
132
+ )
133
+
134
+ self._configuration_command = bytearray(40)
135
+ # Configure the device
136
+ # Byte 1: ACQ_SETT
137
+ self._configuration_command[0] = int(self._acq_sett_configuration)
138
+
139
+ # Byte 2: Configure AN_OUT_IN_SEL
140
+ self._configuration_command[1] = 0 # TODO:
141
+
142
+ # Byte 3: Configure AN_OUT_CH_SEL
143
+ self._configuration_command[2] = 0 # TODO:
144
+
145
+ # Byte 4-15: Configure IN1-4 -> TODO: change that to individual configuration
146
+ for i in range(4):
147
+ config = int(self._input_top_left_configuration)
148
+ self._configuration_command[3 + i * 3] = (config >> 16) & 0xFF
149
+ self._configuration_command[4 + i * 3] = (config >> 8) & 0xFF
150
+ self._configuration_command[5 + i * 3] = config & 0xFF
151
+
152
+ # Byte 16-27: Configure IN5-8 -> TODO: change that to individual configuration
153
+ for i in range(4):
154
+ config = int(self._input_top_right_configuration)
155
+ self._configuration_command[15 + i * 3] = (config >> 16) & 0xFF
156
+ self._configuration_command[16 + i * 3] = (config >> 8) & 0xFF
157
+ self._configuration_command[17 + i * 3] = config & 0xFF
158
+
159
+ # Byte 28-30: Configure MULTIPLE IN 1
160
+ config = int(self._multiple_input_one_configuration)
161
+ self._configuration_command[27] = (config >> 16) & 0xFF
162
+ self._configuration_command[28] = (config >> 8) & 0xFF
163
+ self._configuration_command[29] = config & 0xFF
164
+
165
+ # Byte 31-33: Configure MULTIPLE IN 2
166
+ config = int(self._multiple_input_two_configuration)
167
+ self._configuration_command[30] = (config >> 16) & 0xFF
168
+ self._configuration_command[31] = (config >> 8) & 0xFF
169
+ self._configuration_command[32] = config & 0xFF
170
+
171
+ # Byte 34-36: Configure MULTIPLE IN 3
172
+ config = int(self._multiple_input_three_configuration)
173
+ self._configuration_command[33] = (config >> 16) & 0xFF
174
+ self._configuration_command[34] = (config >> 8) & 0xFF
175
+ self._configuration_command[35] = config & 0xFF
176
+
177
+ # Byte 37-39: Configure MULTIPLE IN 4
178
+ config = int(self._multiple_input_four_configuration)
179
+ self._configuration_command[36] = (config >> 16) & 0xFF
180
+ self._configuration_command[37] = (config >> 8) & 0xFF
181
+ self._configuration_command[38] = config & 0xFF
182
+
183
+ # Control Byte
184
+ self._configuration_command[39] = self._crc_check(
185
+ self._configuration_command, 39
186
+ )
187
+
188
+ self._number_of_channels = (
189
+ len(self._grids) * self._grid_size
190
+ + QUATTROCENTO_AUXILIARY_CHANNELS
191
+ + QUATTROCENTO_SUPPLEMENTARY_CHANNELS
192
+ )
193
+
194
+ self._number_of_biosignal_channels = len(self._grids) * self._grid_size
195
+ self._biosignal_channel_indices = np.array(
196
+ [
197
+ i * self._grid_size + j
198
+ for i in self._grids
199
+ for j in range(self._grid_size)
200
+ ]
201
+ )
202
+ self._number_of_auxiliary_channels = QUATTROCENTO_AUXILIARY_CHANNELS
203
+ self._auxiliary_channel_indices = np.arange(
204
+ self._number_of_streamed_channels
205
+ - self._number_of_auxiliary_channels
206
+ - QUATTROCENTO_SUPPLEMENTARY_CHANNELS,
207
+ self._number_of_streamed_channels - QUATTROCENTO_SUPPLEMENTARY_CHANNELS,
208
+ )
209
+
210
+ self._samples_per_frame = QUATTROCENTO_SAMPLES_PER_FRAME
211
+ self._bytes_per_sample = QUATTROCENTO_BYTES_PER_SAMPLE
212
+ self._buffer_size = (
213
+ self._number_of_streamed_channels * self._bytes_per_sample
214
+ ) * self._samples_per_frame
215
+
216
+ self._send_configuration_to_device()
217
+
218
+ def _crc_check(self, command_bytes: bytearray, command_length: int) -> bytes:
219
+ """
220
+ Performs the Cyclic Redundancy Check (CRC) of the transmitted bytes.
221
+
222
+ Translated function from example code provided by OT Bioelettronica.
223
+
224
+ Args:
225
+ command_bytes (bytearray):
226
+ Bytearray of the transmitted bytes.
227
+
228
+ command_length (int):
229
+ Length of the transmitted bytes.
230
+
231
+ Returns:
232
+ bytes:
233
+ CRC of the transmitted bytes.
234
+ """
235
+
236
+ crc = 0
237
+ j = 0
238
+
239
+ while command_length > 0:
240
+ extracted_byte = command_bytes[j]
241
+ for i in range(8, 0, -1):
242
+ sum = crc % 2 ^ extracted_byte % 2
243
+ crc = crc // 2
244
+
245
+ if sum > 0:
246
+ crc_bin = format(crc, "08b")
247
+ a_bin = format(140, "08b")
248
+
249
+ str_list = []
250
+
251
+ for k in range(8):
252
+ str_list.append("0" if crc_bin[k] == a_bin[k] else "1")
253
+
254
+ crc = int("".join(str_list), 2)
255
+
256
+ extracted_byte = extracted_byte // 2
257
+
258
+ command_length -= 1
259
+ j += 1
260
+
261
+ return crc
262
+
263
+ def _send_configuration_to_device(self) -> None:
264
+ success = self._interface.write(self._configuration_command)
265
+
266
+ if success == -1:
267
+ print("Error while sending configuration to device")
268
+
269
+ self._is_configured = True
270
+ self.configure_toggled.emit(self._is_configured)
271
+
272
+ def _stop_streaming(self) -> None:
273
+ super()._stop_streaming()
274
+
275
+ self._configuration_command[0] -= 1
276
+ self._configuration_command[39] = self._crc_check(
277
+ self._configuration_command, 39
278
+ )
279
+ self._send_configuration_to_device()
280
+
281
+ def _start_streaming(self) -> None:
282
+ super()._start_streaming()
283
+
284
+ self._configuration_command[0] += 1
285
+ self._configuration_command[39] = self._crc_check(
286
+ self._configuration_command, 39
287
+ )
288
+ self._send_configuration_to_device()
289
+
290
+ self._is_streaming = True
291
+ self.stream_toggled.emit(self._is_streaming)
292
+
293
+ def clear_socket(self) -> None:
294
+ if self._interface is not None:
295
+ self._interface.readAll()
296
+
297
+ def _read_data(self) -> None:
298
+ super()._read_data()
299
+
300
+ if not self._is_streaming:
301
+ self.clear_socket()
302
+ return
303
+
304
+ while self._interface.bytesAvailable() > self._buffer_size:
305
+ packet = self._interface.readAll()
306
+ if not packet:
307
+ continue
308
+
309
+ self._received_bytes.extend(packet)
310
+
311
+ while len(self._received_bytes) >= self._buffer_size:
312
+ data_to_process = self._received_bytes[: self._buffer_size]
313
+ self._process_data(data_to_process)
314
+ self._received_bytes = self._received_bytes[self._buffer_size :]
315
+
316
+ def _process_data(self, input: bytearray) -> None:
317
+ super()._process_data(input)
318
+
319
+ data = np.frombuffer(input, dtype="<i2")
320
+ reshaped_data = data.reshape(
321
+ (self._number_of_streamed_channels, -1), order="F"
322
+ ).astype(np.float32)
323
+
324
+ biosignal_data = self._extract_biosignal_data(reshaped_data)
325
+ auxiliary_data = self._extract_auxiliary_data(reshaped_data)
326
+ supplementary_data = reshaped_data[-QUATTROCENTO_SUPPLEMENTARY_CHANNELS:]
327
+
328
+ processed_data = np.vstack((biosignal_data, auxiliary_data, supplementary_data))
329
+
330
+ self.data_available.emit(processed_data)
331
+ self.biosignal_data_available.emit(biosignal_data)
332
+ self.auxiliary_data_available.emit(auxiliary_data)