biosignal-device-interface 0.1.0__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 (35) hide show
  1. biosignal_device_interface/__init__.py +4 -0
  2. biosignal_device_interface/constants/__init__.py +0 -0
  3. biosignal_device_interface/constants/devices/__init__.py +0 -0
  4. biosignal_device_interface/constants/devices/core/base_device_constants.py +51 -0
  5. biosignal_device_interface/constants/devices/otb/otb_muovi_constants.py +129 -0
  6. biosignal_device_interface/constants/devices/otb/otb_quattrocento_constants.py +59 -0
  7. biosignal_device_interface/constants/plots/color_palette.py +59 -0
  8. biosignal_device_interface/devices/__init__.py +9 -0
  9. biosignal_device_interface/devices/core/__init__.py +0 -0
  10. biosignal_device_interface/devices/core/base_device.py +413 -0
  11. biosignal_device_interface/devices/otb/__init__.py +9 -0
  12. biosignal_device_interface/devices/otb/otb_muovi.py +291 -0
  13. biosignal_device_interface/devices/otb/otb_quattrocento.py +235 -0
  14. biosignal_device_interface/gui/device_template_widgets/__init__.py +6 -0
  15. biosignal_device_interface/gui/device_template_widgets/all_devices_widget.py +39 -0
  16. biosignal_device_interface/gui/device_template_widgets/core/base_device_widget.py +121 -0
  17. biosignal_device_interface/gui/device_template_widgets/core/base_multiple_devices_widget.py +105 -0
  18. biosignal_device_interface/gui/device_template_widgets/otb/__init__.py +10 -0
  19. biosignal_device_interface/gui/device_template_widgets/otb/otb_devices_widget.py +32 -0
  20. biosignal_device_interface/gui/device_template_widgets/otb/otb_muovi_plus_widget.py +158 -0
  21. biosignal_device_interface/gui/device_template_widgets/otb/otb_muovi_widget.py +158 -0
  22. biosignal_device_interface/gui/device_template_widgets/otb/otb_quattrocento_light_widget.py +170 -0
  23. biosignal_device_interface/gui/plot_widgets/biosignal_plot_widget.py +496 -0
  24. biosignal_device_interface/gui/ui/devices_template_widget.ui +38 -0
  25. biosignal_device_interface/gui/ui/otb_muovi_plus_template_widget.ui +171 -0
  26. biosignal_device_interface/gui/ui/otb_muovi_template_widget.ui +171 -0
  27. biosignal_device_interface/gui/ui/otb_quattrocento_light_template_widget.ui +266 -0
  28. biosignal_device_interface/gui/ui_compiled/devices_template_widget.py +56 -0
  29. biosignal_device_interface/gui/ui_compiled/otb_muovi_plus_template_widget.py +153 -0
  30. biosignal_device_interface/gui/ui_compiled/otb_muovi_template_widget.py +153 -0
  31. biosignal_device_interface/gui/ui_compiled/otb_quattrocento_light_template_widget.py +217 -0
  32. biosignal_device_interface-0.1.0.dist-info/LICENSE +395 -0
  33. biosignal_device_interface-0.1.0.dist-info/METADATA +138 -0
  34. biosignal_device_interface-0.1.0.dist-info/RECORD +35 -0
  35. biosignal_device_interface-0.1.0.dist-info/WHEEL +4 -0
@@ -0,0 +1,291 @@
1
+ # Python Libraries
2
+ """
3
+ Device class for real-time interfacing the Muovi device.
4
+ Developer: Dominik I. Braun
5
+ Contact: dome.braun@fau.de
6
+ Last Update: 2024-06-05
7
+ """
8
+
9
+ from __future__ import annotations
10
+ from typing import TYPE_CHECKING, Union, Dict
11
+ from PySide6.QtNetwork import QTcpSocket, QTcpServer, QHostAddress
12
+ import numpy as np
13
+
14
+ # Local Libraries
15
+ from biosignal_device_interface.devices.core.base_device import BaseDevice
16
+ from biosignal_device_interface.constants.devices.core.base_device_constants import (
17
+ DeviceType,
18
+ DeviceChannelTypes,
19
+ )
20
+
21
+ # Constants
22
+ from biosignal_device_interface.constants.devices.otb_muovi_constants import (
23
+ MUOVI_CONVERSION_FACTOR_DICT,
24
+ MuoviWorkingMode,
25
+ MuoviDetectionMode,
26
+ MUOVI_WORKING_MODE_CHARACTERISTICS_DICT,
27
+ MUOVI_SAMPLES_PER_FRAME_DICT,
28
+ MUOVI_AVAILABLE_CHANNELS_DICT,
29
+ )
30
+
31
+ if TYPE_CHECKING:
32
+ from PySide6.QtWidgets import QMainWindow, QWidget
33
+ from aenum import Enum
34
+
35
+
36
+ class OTBMuovi(BaseDevice):
37
+ """
38
+ Muovi device class derived from BaseDevice class.
39
+
40
+ Args:
41
+ parent (Union[QMainWindow, QWidget], optional):
42
+ Parent widget to which the device is assigned to.
43
+ Defaults to None.
44
+
45
+ is_muovi_plus (bool):
46
+ True if the device is a Muovi Plus, False if not.
47
+
48
+ The Muovi class is using a TCP/IP protocol to communicate with the device.
49
+ """
50
+
51
+ def __init__(
52
+ self,
53
+ parent: Union[QMainWindow, QWidget] = None,
54
+ is_muovi_plus: bool = False,
55
+ ) -> None:
56
+ """
57
+ Initialize the Muovi device.
58
+
59
+ Args:
60
+ parent (Union[QMainWindow, QWidget], optional): Parent widget. Defaults to None.
61
+ is_muovi_plus (bool, optional): Boolean to initialize the Muovi device as Muovi+ (64 biosignal channels) or Muovi (32 biosignal channels). Defaults to False (Muovi).
62
+ """
63
+ super().__init__(parent)
64
+
65
+ # Device Parameters
66
+ self._device_type: DeviceType = (
67
+ DeviceType.OTB_MUOVI_PLUS if is_muovi_plus else DeviceType.OTB_MUOVI
68
+ )
69
+
70
+ # Connection Parameters
71
+ self._interface: QTcpServer = None
72
+ self._client_socket: QTcpSocket | None = None
73
+
74
+ # Configuration Parameters
75
+ self._working_mode: MuoviWorkingMode = MuoviWorkingMode.NONE
76
+ self._detection_mode: MuoviDetectionMode = MuoviDetectionMode.NONE
77
+ self._configuration_command: int | None = None
78
+
79
+ def _connect_to_device(self) -> bool:
80
+ super()._connect_to_device()
81
+
82
+ self._interface = QTcpServer(self)
83
+ self._received_bytes: bytearray = bytearray()
84
+
85
+ if not self._interface.listen(
86
+ QHostAddress(self._connection_settings[0]), self._connection_settings[1]
87
+ ):
88
+ return False
89
+
90
+ self._interface.newConnection.connect(self._make_request)
91
+
92
+ self._connection_timeout_timer.start()
93
+
94
+ return True
95
+
96
+ def _make_request(self) -> bool:
97
+ super()._make_request()
98
+ self._client_socket = self._interface.nextPendingConnection()
99
+
100
+ if self._client_socket:
101
+
102
+ self._client_socket.readyRead.connect(self._read_data)
103
+
104
+ if not self._is_connected:
105
+ self._is_connected = True
106
+ self.connect_toggled.emit(self._is_connected)
107
+ self._connection_timeout_timer.stop()
108
+ return True
109
+
110
+ elif not self._is_configured:
111
+ self._is_configured = True
112
+ self.configure_toggled.emit(self._is_configured)
113
+ return True
114
+
115
+ def _disconnect_from_device(self) -> bool:
116
+ super()._disconnect_from_device()
117
+
118
+ if self._client_socket is not None:
119
+ self._client_socket.readyRead.disconnect(self._read_data)
120
+ self._client_socket.disconnectFromHost()
121
+ self._client_socket.close()
122
+
123
+ if self._interface is not None:
124
+ self._interface.close()
125
+
126
+ return True
127
+
128
+ def configure_device(
129
+ self, settings: Dict[str, Union[Enum, Dict[str, Enum]]] # type: ignore
130
+ ) -> None:
131
+ super().configure_device(settings)
132
+
133
+ if not self._is_connected or self._client_socket is None:
134
+ return
135
+
136
+ # Check if detection mode is valid for working mode (Case EEG -> MONOPOLAR_GAIN_4 => MONOPOLAR_GAIN_8)
137
+ if self._working_mode == MuoviWorkingMode.EEG:
138
+ if self._detection_mode == MuoviDetectionMode.MONOPOLAR_GAIN_4:
139
+ self._detection_mode = MuoviDetectionMode.MONOPOLAR_GAIN_8
140
+
141
+ self._conversion_factor_biosignal = MUOVI_CONVERSION_FACTOR_DICT[
142
+ self._detection_mode
143
+ ]
144
+ self._conversion_factor_auxiliary = self._conversion_factor_biosignal
145
+
146
+ # Set configuration parameters for data transfer
147
+ working_mode_characteristics = MUOVI_WORKING_MODE_CHARACTERISTICS_DICT[
148
+ self._working_mode
149
+ ]
150
+ self._sampling_frequency = working_mode_characteristics["sampling_frequency"]
151
+ self._bytes_per_sample = working_mode_characteristics["bytes_per_sample"]
152
+ self._samples_per_frame = MUOVI_SAMPLES_PER_FRAME_DICT[self._device_type][
153
+ self._working_mode
154
+ ]
155
+
156
+ self._number_of_channels = MUOVI_AVAILABLE_CHANNELS_DICT[self._device_type][
157
+ DeviceChannelTypes.ALL
158
+ ]
159
+ self._number_of_biosignal_channels = MUOVI_AVAILABLE_CHANNELS_DICT[
160
+ self._device_type
161
+ ][DeviceChannelTypes.BIOSIGNAL]
162
+ self._biosignal_channel_indices = np.arange(self._number_of_biosignal_channels)
163
+
164
+ self._number_of_auxiliary_channels = MUOVI_AVAILABLE_CHANNELS_DICT[
165
+ self._device_type
166
+ ][DeviceChannelTypes.AUXILIARY]
167
+ self._auxiliary_channel_indices = np.arange(
168
+ self._number_of_biosignal_channels,
169
+ self._number_of_biosignal_channels + self._number_of_auxiliary_channels,
170
+ )
171
+
172
+ self._buffer_size = (
173
+ self._number_of_channels * self._samples_per_frame * self._bytes_per_sample
174
+ )
175
+
176
+ self._received_bytes = bytearray()
177
+
178
+ self._configure_command()
179
+ self._send_configuration_to_device()
180
+
181
+ def _send_configuration_to_device(self) -> None:
182
+ configuration_bytes = int(self._configuration_command).to_bytes(
183
+ 1, byteorder="big"
184
+ )
185
+
186
+ success = self._client_socket.write(configuration_bytes)
187
+
188
+ if success == -1:
189
+ self._disconnect_from_device()
190
+
191
+ def _configure_command(self) -> None:
192
+ self._configuration_command = self._working_mode.value << 2
193
+ self._configuration_command += self._detection_mode.value
194
+
195
+ def _start_streaming(self) -> None:
196
+ super()._start_streaming()
197
+
198
+ if self._configuration_command is None:
199
+ return
200
+
201
+ self._configuration_command += 1
202
+ self._send_configuration_to_device()
203
+
204
+ def _stop_streaming(self) -> None:
205
+ super()._stop_streaming()
206
+
207
+ if self._configuration_command is None:
208
+ return
209
+
210
+ self._configuration_command -= 1
211
+ self._send_configuration_to_device()
212
+
213
+ def clear_socket(self) -> None:
214
+ if self._client_socket is not None:
215
+ self._client_socket.readAll()
216
+
217
+ def _read_data(self) -> None:
218
+ super()._read_data()
219
+
220
+ if not self._is_streaming:
221
+ self.clear_socket()
222
+ return
223
+
224
+ while self._client_socket.bytesAvailable() > self._buffer_size:
225
+ packet = self._client_socket.read(self._buffer_size)
226
+ if not packet:
227
+ continue
228
+
229
+ self._received_bytes.extend(packet)
230
+
231
+ while len(self._received_bytes) >= self._buffer_size:
232
+ data_to_process = self._received_bytes[: self._buffer_size]
233
+ self._process_data(data_to_process)
234
+ self._received_bytes = self._received_bytes[self._buffer_size :]
235
+
236
+ def _process_data(self, data: bytearray) -> None:
237
+ super()._process_data(data)
238
+
239
+ decoded_data = self._bytes_to_integers(data)
240
+
241
+ processed_data = decoded_data.reshape(
242
+ self._number_of_channels, -1, order="F"
243
+ ).astype(np.float32)
244
+
245
+ # Emit the data
246
+ self.data_available.emit(processed_data)
247
+ self.biosignal_data_available.emit(self._extract_biosignal_data(processed_data))
248
+ self.auxiliary_data_available.emit(self._extract_auxiliary_data(processed_data))
249
+
250
+ # Convert channels from bytes to integers
251
+ def _bytes_to_integers(
252
+ self,
253
+ data: bytearray,
254
+ ) -> np.ndarray:
255
+ channel_values = []
256
+ # Separate channels from byte-string. One channel has
257
+ # "bytes_in_sample" many bytes in it.
258
+ for channel_index in range(len(data) // 2):
259
+ channel_start = channel_index * self._bytes_per_sample
260
+ channel_end = (channel_index + 1) * self._bytes_per_sample
261
+ channel = data[channel_start:channel_end]
262
+
263
+ # Convert channel's byte value to integer
264
+ match self._working_mode:
265
+ case MuoviWorkingMode.EMG:
266
+ value = self._decode_int16(channel)
267
+ case MuoviWorkingMode.EEG:
268
+ value = self._decode_int24(channel)
269
+
270
+ channel_values.append(value)
271
+
272
+ return np.array(channel_values)
273
+
274
+ def _decode_int16(self, bytes_value: bytearray) -> int:
275
+ value = None
276
+ # Combine 2 bytes to a 16 bit integer value
277
+ value = bytes_value[0] * 2**8 + bytes_value[1]
278
+ # See if the value is negative and make the two's complement
279
+ if value >= 2**15:
280
+ value -= 2**16
281
+ return value
282
+
283
+ # Convert byte-array value to an integer value and apply two's complement
284
+ def _decode_int24(self, bytes_value: bytearray) -> int:
285
+ value = None
286
+ # Combine 3 bytes to a 24 bit integer value
287
+ value = bytes_value[0] * 2**16 + bytes_value[1] * 2**8 + bytes_value[2]
288
+ # See if the value is negative and make the two's complement
289
+ if value >= 2**23:
290
+ value -= 2**24
291
+ return value
@@ -0,0 +1,235 @@
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.
7
+
8
+ Developer: Dominik I. Braun
9
+ Contact: dome.braun@fau.de
10
+ Last Update: 2023-06-05
11
+ """
12
+
13
+ # Python Libraries
14
+ from __future__ import annotations
15
+ from typing import TYPE_CHECKING, Union, Dict
16
+ from PySide6.QtNetwork import QTcpSocket, QHostAddress
17
+ from PySide6.QtCore import QIODevice
18
+ import numpy as np
19
+
20
+
21
+ from biosignal_device_interface.devices.core.base_device import BaseDevice
22
+ from biosignal_device_interface.constants.devices.core.base_device_constants import (
23
+ DeviceType,
24
+ )
25
+ from biosignal_device_interface.constants.devices.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,
33
+ )
34
+
35
+
36
+ if TYPE_CHECKING:
37
+ # Python Libraries
38
+ from PySide6.QtWidgets import QMainWindow, QWidget
39
+ from aenum import Enum
40
+
41
+
42
+ class OTBQuattrocentoLight(BaseDevice):
43
+ """
44
+ QuattrocentoLight device class derived from BaseDevice class.
45
+ The QuattrocentoLight is using a TCP/IP protocol to communicate with the device.
46
+
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!
50
+ """
51
+
52
+ def __init__(
53
+ self,
54
+ parent: Union[QMainWindow, QWidget] = None,
55
+ ) -> None:
56
+ super().__init__(parent)
57
+
58
+ # Device Parameters
59
+ self._device_type: DeviceType = DeviceType.OTB_QUATTROCENTO_LIGHT
60
+
61
+ # 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
+ self._conversion_factor_biosignal: float = 5 / (2**16) / 150 * 1000 # in mV
66
+ 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
70
+
71
+ # Connection Parameters
72
+ self._interface: QTcpSocket = QTcpSocket()
73
+
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
79
+ )
80
+ self._sampling_frequency_mode: QuattrocentoLightSamplingFrequency | None = None
81
+
82
+ def _connect_to_device(self) -> bool:
83
+ super()._connect_to_device()
84
+
85
+ self._received_bytes: bytearray = bytearray()
86
+ return self._make_request()
87
+
88
+ def _make_request(self) -> bool:
89
+ super()._make_request()
90
+ # Signal self.connect_toggled is emitted in _read_data
91
+ self._interface.connectToHost(
92
+ QHostAddress(self._connection_settings[0]),
93
+ self._connection_settings[1],
94
+ QIODevice.ReadWrite,
95
+ )
96
+
97
+ if not self._interface.waitForConnected(1000):
98
+ self._disconnect_from_device()
99
+ return False
100
+
101
+ self._interface.readyRead.connect(self._read_data)
102
+
103
+ return True
104
+
105
+ def _disconnect_from_device(self) -> None:
106
+ super()._disconnect_from_device()
107
+
108
+ self._interface.disconnectFromHost()
109
+ self._interface.readyRead.disconnect(self._read_data)
110
+ self._interface.close()
111
+
112
+ def configure_device(
113
+ self, settings: Dict[str, Union[Enum, Dict[str, Enum]]] # type: ignore
114
+ ) -> None:
115
+ super().configure_device(settings)
116
+
117
+ # Configure the device
118
+ self._number_of_biosignal_channels = len(self._grids) * self._grid_size
119
+ self._biosignal_channel_indices = np.array(
120
+ [
121
+ i * self._grid_size + j
122
+ for i in self._grids
123
+ for j in range(self._grid_size)
124
+ ]
125
+ )
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
+ ]
132
+ )
133
+
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
+ ]
140
+
141
+ self._samples_per_frame = self._sampling_frequency // self._streaming_frequency
142
+
143
+ self._buffer_size = (
144
+ self._bytes_per_sample * self._number_of_channels * self._samples_per_frame
145
+ )
146
+
147
+ self._is_configured = True
148
+ self.configure_toggled.emit(True)
149
+
150
+ def _start_streaming(self) -> None:
151
+ super()._start_streaming()
152
+
153
+ self._interface.write(COMMAND_START_STREAMING)
154
+
155
+ def _stop_streaming(self) -> None:
156
+ super()._stop_streaming()
157
+
158
+ self._interface.write(COMMAND_STOP_STREAMING)
159
+ self._interface.waitForBytesWritten(1000)
160
+
161
+ def clear_socket(self) -> None:
162
+ super().clear_socket()
163
+
164
+ self._interface.readAll()
165
+
166
+ def _read_data(self) -> None:
167
+ super()._read_data()
168
+
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:
173
+
174
+ self._is_connected = True
175
+ self.connect_toggled.emit(True)
176
+ return
177
+
178
+ if not self._is_streaming:
179
+ self.clear_socket()
180
+ return
181
+
182
+ while self._interface.bytesAvailable() > self._buffer_size:
183
+ packet = self._interface.read(self._buffer_size)
184
+ if not packet:
185
+ continue
186
+
187
+ self._received_bytes.extend(packet)
188
+
189
+ while len(self._received_bytes) >= self._buffer_size:
190
+ data_to_process = self._received_bytes[: self._buffer_size]
191
+ self._process_data(data_to_process)
192
+ self._received_bytes = self._received_bytes[self._buffer_size :]
193
+
194
+ def _process_data(self, data: bytearray) -> None:
195
+ super()._process_data(data)
196
+
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"
203
+ ).astype(np.float32)
204
+
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
+ """
220
+
221
+ def __init__(
222
+ self,
223
+ parent: Union[QMainWindow, QWidget] = None,
224
+ ) -> None:
225
+ super().__init__(parent)
226
+
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()
@@ -0,0 +1,6 @@
1
+ # OTB Devices
2
+ from biosignal_device_interface.gui.device_template_widgets.otb import (
3
+ MuoviWidget,
4
+ MuoviPlusWidget,
5
+ QuattrocentoLightWidget,
6
+ )
@@ -0,0 +1,39 @@
1
+ """
2
+ Template for the QWidget that enables the user to interact with all available devices.
3
+ Developer: Dominik I. Braun
4
+ Contact: dome.braun@fau.de
5
+ Last Update: 2024-06-05
6
+ """
7
+
8
+ from __future__ import annotations
9
+ from typing import TYPE_CHECKING, Dict
10
+
11
+ from biosignal_device_interface.gui.device_template_widgets.core.base_multiple_devices_widget import (
12
+ BaseMultipleDevicesWidget,
13
+ )
14
+ from biosignal_device_interface.constants.devices.core.base_device_constants import (
15
+ DeviceType,
16
+ )
17
+ from biosignal_device_interface.gui.device_template_widgets import (
18
+ MuoviPlusWidget,
19
+ MuoviWidget,
20
+ QuattrocentoLightWidget,
21
+ )
22
+
23
+ if TYPE_CHECKING:
24
+ from PySide6.QtWidgets import QWidget, QMainWindow
25
+ from biosignal_device_interface.gui.device_template_widgets.core.base_device_widget import (
26
+ BaseDeviceWidget,
27
+ )
28
+
29
+
30
+ class AllDevicesWidget(BaseMultipleDevicesWidget):
31
+ def __init__(self, parent: QWidget | QMainWindow | None = None):
32
+ super().__init__(parent)
33
+
34
+ self._device_selection: Dict[DeviceType, BaseDeviceWidget] = {
35
+ DeviceType.OTB_QUATTROCENTO_LIGHT: QuattrocentoLightWidget(self),
36
+ DeviceType.OTB_MUOVI: MuoviWidget(self),
37
+ DeviceType.OTB_MUOVI_PLUS: MuoviPlusWidget(self),
38
+ }
39
+ self._set_devices(self._device_selection)
@@ -0,0 +1,121 @@
1
+ """
2
+ Base Device class for real-time interfaces to hardware devices.
3
+ Developer: Dominik I. Braun
4
+ Contact: dome.braun@fau.de
5
+ Last Update: 2024-06-05
6
+ """
7
+
8
+ # Python Libraries
9
+ from __future__ import annotations
10
+ from typing import TYPE_CHECKING, Union, Dict
11
+ from abc import abstractmethod
12
+ from PySide6.QtWidgets import QWidget, QMainWindow
13
+ from PySide6.QtGui import QCloseEvent
14
+ from PySide6.QtCore import Signal
15
+ import numpy as np
16
+
17
+ # Import Devices
18
+ from biosignal_device_interface.devices.core.base_device import BaseDevice
19
+
20
+ if TYPE_CHECKING:
21
+ from enum import Enum
22
+
23
+
24
+ class BaseDeviceWidget(QWidget):
25
+ # Signals
26
+ data_arrived: Signal = Signal(np.ndarray)
27
+ biosignal_data_arrived: Signal = Signal(np.ndarray)
28
+ auxiliary_data_arrived: Signal = Signal(np.ndarray)
29
+ connect_toggled: Signal = Signal(bool)
30
+ configure_toggled: Signal = Signal(bool)
31
+ stream_toggled: Signal = Signal(bool)
32
+
33
+ def __init__(self, parent: QWidget | QMainWindow | None = None):
34
+ super().__init__(parent)
35
+
36
+ self.parent_widget: QWidget | QMainWindow | None = parent
37
+
38
+ # Device Setup
39
+ self.device: BaseDevice | None = None
40
+ self._device_params: Dict[str, Union[str, int, float]] = {}
41
+
42
+ # GUI setup
43
+ self.ui: object = None
44
+
45
+ @abstractmethod
46
+ def _toggle_connection(self) -> None:
47
+ """ """
48
+ pass
49
+
50
+ @abstractmethod
51
+ def _connection_toggled(self, is_connected: bool) -> None:
52
+ """ """
53
+ pass
54
+
55
+ @abstractmethod
56
+ def _toggle_configuration(self) -> None:
57
+ """ """
58
+ pass
59
+
60
+ @abstractmethod
61
+ def _configuration_toggled(self, is_configured: bool) -> None:
62
+ """ """
63
+ pass
64
+
65
+ @abstractmethod
66
+ def _toggle_configuration_group_boxes(self) -> None:
67
+ """ """
68
+ pass
69
+
70
+ @abstractmethod
71
+ def _toggle_stream(self) -> None:
72
+ """ """
73
+ pass
74
+
75
+ @abstractmethod
76
+ def _stream_toggled(self, is_streaming: bool) -> None:
77
+ """ """
78
+ self.stream_toggled.emit(is_streaming)
79
+
80
+ @abstractmethod
81
+ def _initialize_device_params(self) -> None:
82
+ """ """
83
+ pass
84
+
85
+ @abstractmethod
86
+ def _initialize_ui(self) -> None:
87
+ """ """
88
+ pass
89
+
90
+ def _set_device(self, device: BaseDevice) -> None:
91
+ """ """
92
+ # Device Setup
93
+ self.device: BaseDevice = device
94
+ self._initialize_device_params()
95
+ self._set_signals()
96
+ self._initialize_ui()
97
+
98
+ def _set_signals(self) -> None:
99
+ """ """
100
+ self.device.data_available.connect(self.data_arrived.emit)
101
+ self.device.biosignal_data_available.connect(self.biosignal_data_arrived.emit)
102
+ self.device.auxiliary_data_available.connect(self.auxiliary_data_arrived.emit)
103
+
104
+ def get_device_information(self) -> Dict[str, Enum | int | float | str]:
105
+ """
106
+ Gets the current configuration of the device.
107
+
108
+ Returns:
109
+ Dict[str, Enum | int | float | str]:
110
+ Dictionary that holds information about the
111
+ current device configuration and status.
112
+ """
113
+ return self.device.get_device_information()
114
+
115
+ def disconnect_device(self) -> None:
116
+ """ """
117
+ if self.device._is_connected or self.device._is_streaming:
118
+ self.device.toggle_connection()
119
+
120
+ def closeEvent(self, event: QCloseEvent) -> None:
121
+ self.disconnect_device()