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.
Files changed (37) hide show
  1. biosignal_device_interface/__init__.py +0 -4
  2. biosignal_device_interface/constants/devices/__init__.py +3 -0
  3. biosignal_device_interface/constants/devices/core/base_device_constants.py +10 -0
  4. biosignal_device_interface/constants/devices/otb/otb_constants.py +0 -0
  5. biosignal_device_interface/constants/devices/otb/otb_muovi_constants.py +3 -3
  6. biosignal_device_interface/constants/devices/otb/otb_quattrocento_constants.py +289 -35
  7. biosignal_device_interface/constants/devices/otb/otb_quattrocento_light_constants.py +59 -0
  8. biosignal_device_interface/constants/devices/otb/otb_syncstation_constants.py +233 -0
  9. biosignal_device_interface/devices/__init__.py +11 -3
  10. biosignal_device_interface/devices/core/base_device.py +8 -9
  11. biosignal_device_interface/devices/otb/__init__.py +27 -7
  12. biosignal_device_interface/devices/otb/otb_muovi.py +9 -10
  13. biosignal_device_interface/devices/otb/otb_quattrocento.py +215 -118
  14. biosignal_device_interface/devices/otb/otb_quattrocento_light.py +210 -0
  15. biosignal_device_interface/devices/otb/otb_syncstation.py +407 -0
  16. biosignal_device_interface/gui/device_template_widgets/__init__.py +0 -6
  17. biosignal_device_interface/gui/device_template_widgets/all_devices_widget.py +19 -7
  18. biosignal_device_interface/gui/device_template_widgets/core/base_device_widget.py +17 -8
  19. biosignal_device_interface/gui/device_template_widgets/core/base_multiple_devices_widget.py +7 -4
  20. biosignal_device_interface/gui/device_template_widgets/otb/__init__.py +0 -10
  21. biosignal_device_interface/gui/device_template_widgets/otb/otb_devices_widget.py +20 -8
  22. biosignal_device_interface/gui/device_template_widgets/otb/otb_muovi_plus_widget.py +12 -12
  23. biosignal_device_interface/gui/device_template_widgets/otb/otb_muovi_widget.py +12 -12
  24. biosignal_device_interface/gui/device_template_widgets/otb/otb_quattrocento_light_widget.py +61 -57
  25. biosignal_device_interface/gui/device_template_widgets/otb/otb_quattrocento_widget.py +260 -0
  26. biosignal_device_interface/gui/device_template_widgets/otb/otb_syncstation_widget.py +262 -0
  27. biosignal_device_interface/gui/plot_widgets/biosignal_plot_widget.py +9 -4
  28. biosignal_device_interface/gui/ui/otb_quattrocento_template_widget.ui +415 -0
  29. biosignal_device_interface/gui/ui/otb_syncstation_template_widget.ui +732 -0
  30. biosignal_device_interface/gui/ui_compiled/otb_quattrocento_template_widget.py +318 -0
  31. biosignal_device_interface/gui/ui_compiled/otb_syncstation_template_widget.py +495 -0
  32. biosignal_device_interface-0.1.4a1.dist-info/LICENSE +675 -0
  33. {biosignal_device_interface-0.1.1a0.dist-info → biosignal_device_interface-0.1.4a1.dist-info}/METADATA +7 -17
  34. biosignal_device_interface-0.1.4a1.dist-info/RECORD +46 -0
  35. biosignal_device_interface-0.1.1a0.dist-info/LICENSE +0 -395
  36. biosignal_device_interface-0.1.1a0.dist-info/RECORD +0 -35
  37. {biosignal_device_interface-0.1.1a0.dist-info → biosignal_device_interface-0.1.4a1.dist-info}/WHEEL +0 -0
@@ -1,13 +1,10 @@
1
1
  """
2
- 1) Quattrocento Light class for real-time interface to
3
- Quattrocento using OT Biolab Light.
4
-
5
- 2) Quattrocento class for direct real-time interface to
6
- Quattrocento without using OT Biolab Light.
2
+ Quattrocento class for real-time direct interface to
3
+ Quattrocento.
7
4
 
8
5
  Developer: Dominik I. Braun
9
6
  Contact: dome.braun@fau.de
10
- Last Update: 2023-06-05
7
+ Last Update: 2025-01-14
11
8
  """
12
9
 
13
10
  # Python Libraries
@@ -23,61 +20,75 @@ from biosignal_device_interface.constants.devices.core.base_device_constants imp
23
20
  DeviceType,
24
21
  )
25
22
  from biosignal_device_interface.constants.devices.otb.otb_quattrocento_constants import (
26
- COMMAND_START_STREAMING,
27
- COMMAND_STOP_STREAMING,
28
- CONNECTION_RESPONSE,
29
- QUATTROCENTO_LIGHT_STREAMING_FREQUENCY_DICT,
30
- QUATTROCENTO_SAMPLING_FREQUENCY_DICT,
31
- QuattrocentoLightSamplingFrequency,
32
- QuattrocentoLightStreamingFrequency,
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,
33
35
  )
34
36
 
35
-
36
37
  if TYPE_CHECKING:
37
38
  # Python Libraries
38
39
  from PySide6.QtWidgets import QMainWindow, QWidget
39
40
  from aenum import Enum
40
41
 
41
42
 
42
- class OTBQuattrocentoLight(BaseDevice):
43
+ class OTBQuattrocento(BaseDevice):
43
44
  """
44
- QuattrocentoLight device class derived from BaseDevice class.
45
- The QuattrocentoLight is using a TCP/IP protocol to communicate with the device.
45
+ Quattrocento device class derived from BaseDevice class.
46
+ The Quattrocento is using a TCP/IP protocol to communicate with the device.
46
47
 
47
- This class directly interfaces with the OT Biolab Light software from
48
- OT Bioelettronica. The configured settings of the device have to
49
- match the settings from the OT Biolab Light software!
48
+ This class directly interfaces with the Quattrocento from
49
+ OT Bioelettronica.
50
50
  """
51
51
 
52
- def __init__(
53
- self,
54
- parent: Union[QMainWindow, QWidget] = None,
55
- ) -> None:
52
+ def __init__(self, parent: Union[QMainWindow, QWidget] = None):
56
53
  super().__init__(parent)
57
54
 
58
- # Device Parameters
59
- self._device_type: DeviceType = DeviceType.OTB_QUATTROCENTO_LIGHT
55
+ # Device Paramters
56
+ self._device_type: DeviceType = DeviceType.OTB_QUATTROCENTO
60
57
 
61
58
  # Device Information
62
- self._number_of_channels: int = 408 # Fix value
63
- self._auxiliary_channel_start_index: int = 384 # Fix value
64
- self._number_of_auxiliary_channels: int = 16 # Fix value
65
59
  self._conversion_factor_biosignal: float = 5 / (2**16) / 150 * 1000 # in mV
66
60
  self._conversion_factor_auxiliary: float = 5 / (2**16) / 0.5 # in mV
67
- self._bytes_per_sample: int = 2 # Fix value
68
- # Quattrocento unique parameters
69
- self._streaming_frequency: int | None = None
61
+ self._number_of_streamed_channels: int = None
70
62
 
71
63
  # Connection Parameters
72
64
  self._interface: QTcpSocket = QTcpSocket()
73
65
 
74
- # Configuration Parameters
75
- self._grids: list[int] | None = None
76
- self._grid_size: int = 64 # TODO: This is only valid for the big electrodes
77
- self._streaming_frequency_mode: QuattrocentoLightStreamingFrequency | None = (
78
- None
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()
79
78
  )
80
- self._sampling_frequency_mode: QuattrocentoLightSamplingFrequency | None = None
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)
81
92
 
82
93
  def _connect_to_device(self) -> bool:
83
94
  super()._connect_to_device()
@@ -87,11 +98,9 @@ class OTBQuattrocentoLight(BaseDevice):
87
98
 
88
99
  def _make_request(self) -> bool:
89
100
  super()._make_request()
90
- # Signal self.connect_toggled is emitted in _read_data
101
+
91
102
  self._interface.connectToHost(
92
- QHostAddress(self._connection_settings[0]),
93
- self._connection_settings[1],
94
- QIODevice.ReadWrite,
103
+ QHostAddress(self._connection_settings[0]), self._connection_settings[1]
95
104
  )
96
105
 
97
106
  if not self._interface.waitForConnected(1000):
@@ -100,6 +109,9 @@ class OTBQuattrocentoLight(BaseDevice):
100
109
 
101
110
  self._interface.readyRead.connect(self._read_data)
102
111
 
112
+ self.is_connected = True
113
+ self.connect_toggled.emit(self.is_connected)
114
+
103
115
  return True
104
116
 
105
117
  def _disconnect_from_device(self) -> None:
@@ -110,11 +122,75 @@ class OTBQuattrocentoLight(BaseDevice):
110
122
  self._interface.close()
111
123
 
112
124
  def configure_device(
113
- self, settings: Dict[str, Union[Enum, Dict[str, Enum]]] # type: ignore
114
- ) -> None:
115
- super().configure_device(settings)
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
+ )
116
133
 
134
+ self._configuration_command = bytearray(40)
117
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
+
118
194
  self._number_of_biosignal_channels = len(self._grids) * self._grid_size
119
195
  self._biosignal_channel_indices = np.array(
120
196
  [
@@ -123,64 +199,110 @@ class OTBQuattrocentoLight(BaseDevice):
123
199
  for j in range(self._grid_size)
124
200
  ]
125
201
  )
126
-
127
- self._auxiliary_channel_indices = np.array(
128
- [
129
- i + self._auxiliary_channel_start_index
130
- for i in range(self._number_of_auxiliary_channels)
131
- ]
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,
132
208
  )
133
209
 
134
- self._streaming_frequency = QUATTROCENTO_LIGHT_STREAMING_FREQUENCY_DICT[
135
- self._streaming_frequency_mode
136
- ]
137
- self._sampling_frequency = QUATTROCENTO_SAMPLING_FREQUENCY_DICT[
138
- self._sampling_frequency_mode
139
- ]
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
140
215
 
141
- self._samples_per_frame = self._sampling_frequency // self._streaming_frequency
216
+ self._send_configuration_to_device()
142
217
 
143
- self._buffer_size = (
144
- self._bytes_per_sample * self._number_of_channels * self._samples_per_frame
145
- )
218
+ def _crc_check(self, command_bytes: bytearray, command_length: int) -> bytes:
219
+ """
220
+ Performs the Cyclic Redundancy Check (CRC) of the transmitted bytes.
146
221
 
147
- self._is_configured = True
148
- self.configure_toggled.emit(True)
222
+ Translated function from example code provided by OT Bioelettronica.
149
223
 
150
- def _start_streaming(self) -> None:
151
- super()._start_streaming()
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")
152
248
 
153
- self._interface.write(COMMAND_START_STREAMING)
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)
154
271
 
155
272
  def _stop_streaming(self) -> None:
156
273
  super()._stop_streaming()
157
274
 
158
- self._interface.write(COMMAND_STOP_STREAMING)
159
- self._interface.waitForBytesWritten(1000)
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()
160
280
 
161
- def clear_socket(self) -> None:
162
- super().clear_socket()
281
+ def _start_streaming(self) -> None:
282
+ super()._start_streaming()
163
283
 
164
- self._interface.readAll()
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()
165
289
 
166
- def _read_data(self) -> None:
167
- super()._read_data()
290
+ self._is_streaming = True
291
+ self.stream_toggled.emit(self._is_streaming)
168
292
 
169
- # Wait for connection response
170
- if not self._is_connected:
171
- if self._interface.bytesAvailable() == len(CONNECTION_RESPONSE):
172
- if self._interface.readAll() == CONNECTION_RESPONSE:
293
+ def clear_socket(self) -> None:
294
+ if self._interface is not None:
295
+ self._interface.readAll()
173
296
 
174
- self._is_connected = True
175
- self.connect_toggled.emit(True)
176
- return
297
+ def _read_data(self) -> None:
298
+ super()._read_data()
177
299
 
178
300
  if not self._is_streaming:
179
301
  self.clear_socket()
180
302
  return
181
303
 
182
304
  while self._interface.bytesAvailable() > self._buffer_size:
183
- packet = self._interface.read(self._buffer_size)
305
+ packet = self._interface.readAll()
184
306
  if not packet:
185
307
  continue
186
308
 
@@ -191,45 +313,20 @@ class OTBQuattrocentoLight(BaseDevice):
191
313
  self._process_data(data_to_process)
192
314
  self._received_bytes = self._received_bytes[self._buffer_size :]
193
315
 
194
- def _process_data(self, data: bytearray) -> None:
195
- super()._process_data(data)
316
+ def _process_data(self, input: bytearray) -> None:
317
+ super()._process_data(input)
196
318
 
197
- # Decode the data
198
- decoded_data = np.frombuffer(data, dtype=np.int16)
199
-
200
- # Reshape it to the correct format
201
- processed_data = decoded_data.reshape(
202
- self._number_of_channels, -1, order="F"
319
+ data = np.frombuffer(input, dtype="<i2")
320
+ reshaped_data = data.reshape(
321
+ (self._number_of_streamed_channels, -1), order="F"
203
322
  ).astype(np.float32)
204
323
 
205
- # Emit the data
206
- self.data_available.emit(processed_data)
207
- self.biosignal_data_available.emit(self._extract_biosignal_data(processed_data))
208
- self.auxiliary_data_available.emit(self._extract_auxiliary_data(processed_data))
209
-
210
- def get_device_information(self) -> Dict[str, Enum | int | float | str]:
211
- return super().get_device_information()
212
-
213
-
214
- class OTBQuattrocento(BaseDevice):
215
- """
216
- Quattrocento device class derived from BaseDevice class.
217
-
218
- The Quattrocento class is using a TCP/IP protocol to communicate with the device.
219
- """
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:]
220
327
 
221
- def __init__(
222
- self,
223
- parent: Union[QMainWindow, QWidget] = None,
224
- ) -> None:
225
- super().__init__(parent)
328
+ processed_data = np.vstack((biosignal_data, auxiliary_data, supplementary_data))
226
329
 
227
- # Device Parameters
228
- self._device_type: DeviceType = DeviceType.OTB_QUATTROCENTO
229
-
230
- # Device Information
231
- self._conversion_factor_biosignal: float = 5 / (2**16) / 150 * 1000
232
- self._conversion_factor_auxiliary: float = 5 / (2**16) / 0.5
233
-
234
- # Connection Parameters
235
- self._interface: QTcpSocket = QTcpSocket()
330
+ self.data_available.emit(processed_data)
331
+ self.biosignal_data_available.emit(biosignal_data)
332
+ self.auxiliary_data_available.emit(auxiliary_data)
@@ -0,0 +1,210 @@
1
+ """
2
+ Quattrocento Light class for real-time interface to
3
+ Quattrocento using OT Biolab Light.
4
+
5
+ Developer: Dominik I. Braun
6
+ Contact: dome.braun@fau.de
7
+ Last Update: 2023-06-05
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_light_constants import (
23
+ COMMAND_START_STREAMING,
24
+ COMMAND_STOP_STREAMING,
25
+ CONNECTION_RESPONSE,
26
+ QUATTROCENTO_LIGHT_STREAMING_FREQUENCY_DICT,
27
+ QUATTROCENTO_SAMPLING_FREQUENCY_DICT,
28
+ QuattrocentoLightSamplingFrequency,
29
+ QuattrocentoLightStreamingFrequency,
30
+ )
31
+
32
+
33
+ if TYPE_CHECKING:
34
+ # Python Libraries
35
+ from PySide6.QtWidgets import QMainWindow, QWidget
36
+ from aenum import Enum
37
+
38
+
39
+ class OTBQuattrocentoLight(BaseDevice):
40
+ """
41
+ QuattrocentoLight device class derived from BaseDevice class.
42
+ The QuattrocentoLight is using a TCP/IP protocol to communicate with the device.
43
+
44
+ This class directly interfaces with the OT Biolab Light software from
45
+ OT Bioelettronica. The configured settings of the device have to
46
+ match the settings from the OT Biolab Light software!
47
+ """
48
+
49
+ def __init__(
50
+ self,
51
+ parent: Union[QMainWindow, QWidget] = None,
52
+ ) -> None:
53
+ super().__init__(parent)
54
+
55
+ # Device Parameters
56
+ self._device_type: DeviceType = DeviceType.OTB_QUATTROCENTO_LIGHT
57
+
58
+ # Device Information
59
+ self._number_of_channels: int = 408 # Fix value
60
+ self._auxiliary_channel_start_index: int = 384 # Fix value
61
+ self._number_of_auxiliary_channels: int = 16 # Fix value
62
+ self._conversion_factor_biosignal: float = 5 / (2**16) / 150 * 1000 # in mV
63
+ self._conversion_factor_auxiliary: float = 5 / (2**16) / 0.5 # in mV
64
+ self._bytes_per_sample: int = 2 # Fix value
65
+ # Quattrocento unique parameters
66
+ self._streaming_frequency: int | None = None
67
+
68
+ # Connection Parameters
69
+ self._interface: QTcpSocket = QTcpSocket()
70
+
71
+ # Configuration Parameters
72
+ self._grids: list[int] | None = None
73
+ self._grid_size: int = 64 # TODO: This is only valid for the big electrodes
74
+ self._streaming_frequency_mode: QuattrocentoLightStreamingFrequency | None = (
75
+ None
76
+ )
77
+ self._sampling_frequency_mode: QuattrocentoLightSamplingFrequency | None = None
78
+
79
+ def _connect_to_device(self) -> bool:
80
+ super()._connect_to_device()
81
+
82
+ self._received_bytes: bytearray = bytearray()
83
+ return self._make_request()
84
+
85
+ def _make_request(self) -> bool:
86
+ super()._make_request()
87
+ # Signal self.connect_toggled is emitted in _read_data
88
+ self._interface.connectToHost(
89
+ QHostAddress(self._connection_settings[0]),
90
+ self._connection_settings[1],
91
+ QIODevice.ReadWrite,
92
+ )
93
+
94
+ if not self._interface.waitForConnected(1000):
95
+ self._disconnect_from_device()
96
+ return False
97
+
98
+ self._interface.readyRead.connect(self._read_data)
99
+
100
+ return True
101
+
102
+ def _disconnect_from_device(self) -> None:
103
+ super()._disconnect_from_device()
104
+
105
+ self._interface.disconnectFromHost()
106
+ self._interface.readyRead.disconnect(self._read_data)
107
+ self._interface.close()
108
+
109
+ def configure_device(
110
+ self, params: Dict[str, Union[Enum, Dict[str, Enum]]] # type: ignore
111
+ ) -> None:
112
+ super().configure_device(params)
113
+
114
+ # Configure the device
115
+ self._number_of_biosignal_channels = len(self._grids) * self._grid_size
116
+ self._biosignal_channel_indices = np.array(
117
+ [
118
+ i * self._grid_size + j
119
+ for i in self._grids
120
+ for j in range(self._grid_size)
121
+ ]
122
+ )
123
+
124
+ self._auxiliary_channel_indices = np.array(
125
+ [
126
+ i + self._auxiliary_channel_start_index
127
+ for i in range(self._number_of_auxiliary_channels)
128
+ ]
129
+ )
130
+
131
+ self._streaming_frequency = QUATTROCENTO_LIGHT_STREAMING_FREQUENCY_DICT[
132
+ self._streaming_frequency_mode
133
+ ]
134
+ self._sampling_frequency = QUATTROCENTO_SAMPLING_FREQUENCY_DICT[
135
+ self._sampling_frequency_mode
136
+ ]
137
+
138
+ self._samples_per_frame = self._sampling_frequency // self._streaming_frequency
139
+
140
+ self._buffer_size = (
141
+ self._bytes_per_sample * self._number_of_channels * self._samples_per_frame
142
+ )
143
+
144
+ self._is_configured = True
145
+ self.configure_toggled.emit(True)
146
+
147
+ def _start_streaming(self) -> None:
148
+ super()._start_streaming()
149
+
150
+ self._interface.write(COMMAND_START_STREAMING)
151
+
152
+ def _stop_streaming(self) -> None:
153
+ super()._stop_streaming()
154
+
155
+ self._interface.write(COMMAND_STOP_STREAMING)
156
+ self._interface.waitForBytesWritten(1000)
157
+
158
+ def clear_socket(self) -> None:
159
+ super().clear_socket()
160
+
161
+ self._interface.readAll()
162
+
163
+ def _read_data(self) -> None:
164
+ super()._read_data()
165
+
166
+ # Wait for connection response
167
+ if not self.is_connected and (
168
+ self._interface.bytesAvailable() == len(CONNECTION_RESPONSE)
169
+ and self._interface.readAll() == CONNECTION_RESPONSE
170
+ ):
171
+ self.is_connected = True
172
+ self.connect_toggled.emit(True)
173
+ return
174
+ if not self._is_streaming:
175
+ self.clear_socket()
176
+ return
177
+
178
+ while self._interface.bytesAvailable() > self._buffer_size:
179
+ packet = self._interface.read(self._buffer_size)
180
+ if not packet:
181
+ continue
182
+
183
+ self._received_bytes.extend(packet)
184
+
185
+ while len(self._received_bytes) >= self._buffer_size:
186
+ data_to_process = self._received_bytes[: self._buffer_size]
187
+ self._process_data(data_to_process)
188
+ self._received_bytes = self._received_bytes[self._buffer_size :]
189
+
190
+ def _process_data(self, input: bytearray) -> None:
191
+ super()._process_data(input)
192
+
193
+ # Decode the data
194
+ decoded_data = np.frombuffer(input, dtype=np.int16)
195
+
196
+ # Reshape it to the correct format
197
+ processed_data = decoded_data.reshape(
198
+ self._number_of_channels, -1, order="F"
199
+ ).astype(np.float32)
200
+
201
+ # Emit the data
202
+ self.data_available.emit(processed_data)
203
+
204
+ biosignal_data = self._extract_biosignal_data(processed_data)
205
+ self.biosignal_data_available.emit(biosignal_data)
206
+ auxiliary_data = self._extract_auxiliary_data(processed_data)
207
+ self.auxiliary_data_available.emit(auxiliary_data)
208
+
209
+ def get_device_information(self) -> Dict[str, Enum | int | float | str]: # type: ignore
210
+ return super().get_device_information()