biosignal-device-interface 0.2.4__py3-none-any.whl → 0.2.6__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 +4 -0
- biosignal_device_interface/constants/devices/otb/otb_sessantaquattro_constants.py +227 -0
- biosignal_device_interface/devices/__init__.py +2 -0
- biosignal_device_interface/devices/otb/__init__.py +6 -0
- biosignal_device_interface/devices/otb/otb_muovi.py +1 -0
- biosignal_device_interface/devices/otb/otb_quattrocento.py +1 -1
- biosignal_device_interface/devices/otb/otb_quattrocento_light.py +1 -1
- biosignal_device_interface/devices/otb/otb_sessantaquattro.py +363 -0
- biosignal_device_interface/gui/device_template_widgets/all_devices_widget.py +4 -0
- biosignal_device_interface/gui/device_template_widgets/otb/otb_devices_widget.py +4 -0
- biosignal_device_interface/gui/device_template_widgets/otb/otb_sessantaquattro_widget.py +265 -0
- biosignal_device_interface/gui/ui/otb_sessantaquattro_template_widget.ui +317 -0
- biosignal_device_interface/gui/ui_compiled/otb_sessantaquattro_template_widget.py +241 -0
- {biosignal_device_interface-0.2.4.dist-info → biosignal_device_interface-0.2.6.dist-info}/METADATA +11 -18
- {biosignal_device_interface-0.2.4.dist-info → biosignal_device_interface-0.2.6.dist-info}/RECORD +17 -12
- {biosignal_device_interface-0.2.4.dist-info → biosignal_device_interface-0.2.6.dist-info}/WHEEL +1 -1
- {biosignal_device_interface-0.2.4.dist-info → biosignal_device_interface-0.2.6.dist-info}/licenses/LICENSE +0 -0
|
@@ -21,6 +21,7 @@ class DeviceType(Enum):
|
|
|
21
21
|
OTB_MUOVI = auto(), "OT Bioelettronica Muovi"
|
|
22
22
|
OTB_MUOVI_PLUS = auto(), "OT Bioelettronica Muovi Plus"
|
|
23
23
|
OTB_SYNCSTATION = auto(), "OT Bioelettronica SyncStation"
|
|
24
|
+
OTB_SESSANTAQUATTRO = auto(), "OT Bioelettronica Sessantaquattro"
|
|
24
25
|
|
|
25
26
|
|
|
26
27
|
class OTBDeviceType(Enum):
|
|
@@ -36,6 +37,7 @@ class OTBDeviceType(Enum):
|
|
|
36
37
|
MUOVI = auto(), "Muovi"
|
|
37
38
|
MUOVI_PLUS = auto(), "Muovi Plus"
|
|
38
39
|
SYNCSTATION = auto(), "SyncStation"
|
|
40
|
+
SESSANTAQUATTRO = auto(), "Sessantaquattro"
|
|
39
41
|
|
|
40
42
|
|
|
41
43
|
class DeviceChannelTypes(Enum):
|
|
@@ -58,4 +60,6 @@ DEVICE_NAME_DICT: dict[DeviceType | OTBDeviceType, str] = {
|
|
|
58
60
|
OTBDeviceType.MUOVI_PLUS: "Muovi Plus",
|
|
59
61
|
DeviceType.OTB_SYNCSTATION: "SyncStation",
|
|
60
62
|
OTBDeviceType.SYNCSTATION: "SyncStation",
|
|
63
|
+
DeviceType.OTB_SESSANTAQUATTRO: "Sessantaquattro",
|
|
64
|
+
OTBDeviceType.SESSANTAQUATTRO: "Sessantaquattro",
|
|
61
65
|
}
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Constants for the Sessantaquattro device.
|
|
3
|
+
|
|
4
|
+
Developer: Dominik I. Braun
|
|
5
|
+
Contact: dome.braun@fau.de
|
|
6
|
+
Last Update: 2025-09-16
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from typing import Dict
|
|
10
|
+
from aenum import Enum, auto
|
|
11
|
+
|
|
12
|
+
from biosignal_device_interface.constants.devices.core.base_device_constants import (
|
|
13
|
+
DeviceChannelTypes,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class SessantaquattroSamplingFrequencyMode(Enum):
|
|
18
|
+
"""Enum class for the sampling frequency of the Sessantaquattro device."""
|
|
19
|
+
|
|
20
|
+
_init_ = "value __doc__"
|
|
21
|
+
|
|
22
|
+
NONE = 0, "No sampling frequency set."
|
|
23
|
+
LOW = auto(), "500 Hz (2000 Hz - Accelerometer)"
|
|
24
|
+
MEDIUM = auto(), "1000 Hz (4000 Hz - Accelerometer)"
|
|
25
|
+
HIGH = auto(), "2000 Hz (8000 Hz - Accelerometer)"
|
|
26
|
+
ULTRA = auto(), "4000 Hz (16000 Hz - Accelerometer)"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class SessantaquattroDetectionMode(Enum):
|
|
30
|
+
"""
|
|
31
|
+
Enum class for the working mode of the Sessantaquattro device.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
_init_ = "value __doc__"
|
|
35
|
+
|
|
36
|
+
NONE = 0, "No working mode set."
|
|
37
|
+
MONOPOLAR = auto(), ("Monopolar Mode")
|
|
38
|
+
BIPOLAR = auto(), ("Bipolar Mode")
|
|
39
|
+
DIFFERENTIAL = auto(), ("Differential Mode")
|
|
40
|
+
ACCELEROMETER = auto(), ("Accelerometer Mode")
|
|
41
|
+
UNDEFINED = auto(), ("Undefined Mode")
|
|
42
|
+
IMPEDANCE_ADVANCED = auto(), ("Impedance Check Advanced")
|
|
43
|
+
IMPEDANCE = auto(), ("Impedance Check")
|
|
44
|
+
TEST = auto(), ("Test Mode")
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class SessantaquattroChannelMode(Enum):
|
|
48
|
+
"""
|
|
49
|
+
Enum class for the channel mode of the Sessantaquattro device.
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
_init_ = "value __doc__"
|
|
53
|
+
|
|
54
|
+
NONE = 0, "No channel mode set."
|
|
55
|
+
LOW = auto(), (
|
|
56
|
+
"8 bioelec. + 2 AUX + 2 accessory (if MODE=001: 4 bio + 2 AUX + 2 acc)"
|
|
57
|
+
)
|
|
58
|
+
MEDIUM = auto(), (
|
|
59
|
+
"16 bioelec. + 2 AUX + 2 accessory (if MODE=001: 8 bio + 2 AUX + 2 acc)"
|
|
60
|
+
)
|
|
61
|
+
HIGH = auto(), (
|
|
62
|
+
"32 bioelec. + 2 AUX + 2 accessory (if MODE=001: 16 bio + 2 AUX + 2 acc)"
|
|
63
|
+
)
|
|
64
|
+
ULTRA = auto(), (
|
|
65
|
+
"64 bioelec. + 2 AUX + 2 accessory (if MODE=001: 32 bio + 2 AUX + 2 acc)"
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class SessantaquattroResolutionMode(Enum):
|
|
70
|
+
"""
|
|
71
|
+
Enum class for the resolution mode of the Sessantaquattro device.
|
|
72
|
+
"""
|
|
73
|
+
|
|
74
|
+
_init_ = "value __doc__"
|
|
75
|
+
|
|
76
|
+
NONE = 0, "No resolution mode set."
|
|
77
|
+
LOW = auto(), ("16 Bits Resolution")
|
|
78
|
+
HIGH = auto(), ("24 Bits Resolution")
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class SessantaquattroGainMode(Enum):
|
|
82
|
+
"""
|
|
83
|
+
Enum class for the gain mode of the Sessantaquattro device.
|
|
84
|
+
"""
|
|
85
|
+
|
|
86
|
+
_init_ = "value __doc__"
|
|
87
|
+
|
|
88
|
+
NONE = 0, "No gain mode set."
|
|
89
|
+
DEFAULT = auto(), (
|
|
90
|
+
"Gain x2 (for 24 bits resolution) / Gain x8 (for 16 bits resolution)"
|
|
91
|
+
)
|
|
92
|
+
LOW = auto(), ("Gain x4")
|
|
93
|
+
MEDIUM = auto(), ("Gain x6")
|
|
94
|
+
HIGH = auto(), ("Gain x8")
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class SessantaquattroTriggerMode(Enum):
|
|
98
|
+
"""
|
|
99
|
+
Enum class for the trigger mode of the Sessantaquattro device.
|
|
100
|
+
"""
|
|
101
|
+
|
|
102
|
+
_init_ = "value __doc__"
|
|
103
|
+
|
|
104
|
+
NONE = 0, "No trigger mode set."
|
|
105
|
+
DEFAULT = auto(), (
|
|
106
|
+
"The data transfer is controlled from GO/STOP bit, REC has no effect."
|
|
107
|
+
)
|
|
108
|
+
INTERNAL = auto(), (
|
|
109
|
+
"The data transfer ist triggered by the internal signal (phototransistor)"
|
|
110
|
+
)
|
|
111
|
+
EXTERNAL = auto(), (
|
|
112
|
+
"The data transfer is triggered by the external signal (from the adapter)"
|
|
113
|
+
)
|
|
114
|
+
SDCARD = auto(), (
|
|
115
|
+
"SDCARD: SD card acquisition starts/stops with the hardware button or with the REC bit."
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
class SessantaquattroRecordingMode(Enum):
|
|
120
|
+
"""
|
|
121
|
+
Enum class for the recording mode of the Sessantaquattro device.
|
|
122
|
+
"""
|
|
123
|
+
|
|
124
|
+
_init_ = "value __doc__"
|
|
125
|
+
|
|
126
|
+
NONE = 0, "No recording mode set."
|
|
127
|
+
STOP = auto(), ("Stop the recording. Works only if TRIG = 3 (SDCARD).")
|
|
128
|
+
START = auto(), ("Start the recording. Works only if TRIG = 3 (SDCARD).")
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def _get_sampling_frequency(
|
|
132
|
+
detection_mode: SessantaquattroDetectionMode,
|
|
133
|
+
sampling_freq_mode: SessantaquattroSamplingFrequencyMode,
|
|
134
|
+
) -> int:
|
|
135
|
+
"""Get sampling frequency for given detection and sampling frequency modes."""
|
|
136
|
+
if detection_mode == SessantaquattroDetectionMode.ACCELEROMETER:
|
|
137
|
+
base_freq = 2000
|
|
138
|
+
else:
|
|
139
|
+
base_freq = 500
|
|
140
|
+
|
|
141
|
+
multiplier = 2 ** (sampling_freq_mode.value - 1)
|
|
142
|
+
return base_freq * multiplier
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def _get_biosignal_channel_count(
|
|
146
|
+
sampling_freq_mode: SessantaquattroSamplingFrequencyMode,
|
|
147
|
+
detection_mode: SessantaquattroDetectionMode,
|
|
148
|
+
) -> int:
|
|
149
|
+
"""Get biosignal channel count for given modes."""
|
|
150
|
+
base_channels = 8 * (2 ** (sampling_freq_mode.value - 1))
|
|
151
|
+
|
|
152
|
+
# Bipolar mode has half the channels
|
|
153
|
+
if detection_mode == SessantaquattroDetectionMode.BIPOLAR:
|
|
154
|
+
return base_channels // 2
|
|
155
|
+
|
|
156
|
+
return base_channels
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
# Generate dictionaries using functions
|
|
160
|
+
SESSANTAQUATTRO_DETECTION_MODE_CHARACTERISTICS_DICT: Dict[
|
|
161
|
+
SessantaquattroDetectionMode, Dict[SessantaquattroSamplingFrequencyMode, int]
|
|
162
|
+
] = {
|
|
163
|
+
detection_mode: {
|
|
164
|
+
sampling_freq: _get_sampling_frequency(detection_mode, sampling_freq)
|
|
165
|
+
for sampling_freq in SessantaquattroSamplingFrequencyMode
|
|
166
|
+
if sampling_freq != SessantaquattroSamplingFrequencyMode.NONE
|
|
167
|
+
}
|
|
168
|
+
for detection_mode in SessantaquattroDetectionMode
|
|
169
|
+
if detection_mode != SessantaquattroDetectionMode.NONE
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
SESSANTAQUATTRO_CHANNEL_MODE_CHARACTERISTICS_DICT: Dict[
|
|
173
|
+
SessantaquattroSamplingFrequencyMode,
|
|
174
|
+
Dict[SessantaquattroDetectionMode, Dict[DeviceChannelTypes, int]],
|
|
175
|
+
] = {
|
|
176
|
+
sampling_freq: {
|
|
177
|
+
detection_mode: {
|
|
178
|
+
DeviceChannelTypes.BIOSIGNAL: _get_biosignal_channel_count(
|
|
179
|
+
sampling_freq, detection_mode
|
|
180
|
+
),
|
|
181
|
+
DeviceChannelTypes.AUXILIARY: 4,
|
|
182
|
+
}
|
|
183
|
+
for detection_mode in SessantaquattroDetectionMode
|
|
184
|
+
if detection_mode != SessantaquattroDetectionMode.NONE
|
|
185
|
+
}
|
|
186
|
+
for sampling_freq in SessantaquattroSamplingFrequencyMode
|
|
187
|
+
if sampling_freq != SessantaquattroSamplingFrequencyMode.NONE
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
SESSANTAQUATTRO_GAIN_MODE_CHARACTERISTICS_DICT: Dict[
|
|
191
|
+
SessantaquattroResolutionMode, Dict[SessantaquattroGainMode, float]
|
|
192
|
+
] = {
|
|
193
|
+
SessantaquattroResolutionMode.LOW: {
|
|
194
|
+
SessantaquattroGainMode.DEFAULT: 286.1e-6, # Gain8, 16-bit (in mV, originally 286.1 nV)
|
|
195
|
+
SessantaquattroGainMode.LOW: 572.2e-6, # Gain4, 16-bit (in mV, originally 572.2 nV)
|
|
196
|
+
SessantaquattroGainMode.MEDIUM: 381.5e-6, # Gain6, 16-bit (in mV, originally 381.5 nV)
|
|
197
|
+
SessantaquattroGainMode.HIGH: 286.1e-6, # Gain8, 16-bit (in mV, originally 286.1 nV)
|
|
198
|
+
},
|
|
199
|
+
SessantaquattroResolutionMode.HIGH: {
|
|
200
|
+
SessantaquattroGainMode.DEFAULT: 71.5e-6, # Gain8, 24-bit (in mV, originally 71.5 nV)
|
|
201
|
+
SessantaquattroGainMode.LOW: 143.0e-6, # Gain4, 24-bit (in mV, originally 143.0 nV)
|
|
202
|
+
SessantaquattroGainMode.MEDIUM: 95.4e-6, # Gain6, 24-bit (in mV, originally 95.4 nV)
|
|
203
|
+
SessantaquattroGainMode.HIGH: 71.5e-6, # Gain8, 24-bit (in mV, originally 71.5 nV)
|
|
204
|
+
},
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
SESSANTAQUATTRO_AUXILIARY_LSB_DICT: Dict[SessantaquattroResolutionMode, float] = {
|
|
208
|
+
SessantaquattroResolutionMode.LOW: 146.48e-6, # in mV (originally 146.48 nV)
|
|
209
|
+
SessantaquattroResolutionMode.HIGH: 572.2e-6, # in mV (originally 572.2 nV)
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
SESSANTAQUATTRO_SAMPLES_PER_FRAME_DICT: Dict[SessantaquattroChannelMode, int] = {
|
|
213
|
+
SessantaquattroChannelMode.LOW: 48,
|
|
214
|
+
SessantaquattroChannelMode.MEDIUM: 28,
|
|
215
|
+
SessantaquattroChannelMode.HIGH: 16,
|
|
216
|
+
SessantaquattroChannelMode.ULTRA: 8,
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if __name__ == "__main__":
|
|
220
|
+
# Print the generated dictionaries for verification
|
|
221
|
+
import pprint
|
|
222
|
+
|
|
223
|
+
print("Sessantaquattro Detection Mode Characteristics Dictionary:")
|
|
224
|
+
pprint.pprint(SESSANTAQUATTRO_DETECTION_MODE_CHARACTERISTICS_DICT)
|
|
225
|
+
|
|
226
|
+
print("\nSessantaquattro Channel Mode Characteristics Dictionary:")
|
|
227
|
+
pprint.pprint(SESSANTAQUATTRO_CHANNEL_MODE_CHARACTERISTICS_DICT)
|
|
@@ -5,9 +5,11 @@ from biosignal_device_interface.devices.otb import (
|
|
|
5
5
|
OTBMuoviWidget,
|
|
6
6
|
OTBMuoviPlusWidget,
|
|
7
7
|
OTBQuattrocentoLightWidget,
|
|
8
|
+
OTBSessantaquattroWidget,
|
|
8
9
|
OTBMuovi,
|
|
9
10
|
OTBQuattrocento,
|
|
10
11
|
OTBQuattrocentoLight,
|
|
12
|
+
OTBSessantaquattro,
|
|
11
13
|
OTBSyncStationWidget,
|
|
12
14
|
OTBDevicesWidget,
|
|
13
15
|
)
|
|
@@ -4,6 +4,9 @@ from biosignal_device_interface.devices.otb.otb_quattrocento_light import (
|
|
|
4
4
|
)
|
|
5
5
|
from biosignal_device_interface.devices.otb.otb_muovi import OTBMuovi
|
|
6
6
|
from biosignal_device_interface.devices.otb.otb_syncstation import OTBSyncStation
|
|
7
|
+
from biosignal_device_interface.devices.otb.otb_sessantaquattro import (
|
|
8
|
+
OTBSessantaquattro,
|
|
9
|
+
)
|
|
7
10
|
|
|
8
11
|
# Widgets
|
|
9
12
|
# All OTB Devices Widget
|
|
@@ -27,3 +30,6 @@ from biosignal_device_interface.gui.device_template_widgets.otb.otb_quattrocento
|
|
|
27
30
|
from biosignal_device_interface.gui.device_template_widgets.otb.otb_syncstation_widget import (
|
|
28
31
|
OTBSyncStationWidget,
|
|
29
32
|
)
|
|
33
|
+
from biosignal_device_interface.gui.device_template_widgets.otb.otb_sessantaquattro_widget import (
|
|
34
|
+
OTBSessantaquattroWidget,
|
|
35
|
+
)
|
|
@@ -57,7 +57,7 @@ class OTBQuattrocento(BaseDevice):
|
|
|
57
57
|
|
|
58
58
|
# Device Information
|
|
59
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
|
|
60
|
+
self._conversion_factor_auxiliary: float = 5 / (2**16) / 0.5 # in V
|
|
61
61
|
self._number_of_streamed_channels: int = None
|
|
62
62
|
|
|
63
63
|
# Connection Parameters
|
|
@@ -60,7 +60,7 @@ class OTBQuattrocentoLight(BaseDevice):
|
|
|
60
60
|
self._auxiliary_channel_start_index: int = 384 # Fix value
|
|
61
61
|
self._number_of_auxiliary_channels: int = 16 # Fix value
|
|
62
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
|
|
63
|
+
self._conversion_factor_auxiliary: float = 5 / (2**16) / 0.5 # in V
|
|
64
64
|
self._bytes_per_sample: int = 2 # Fix value
|
|
65
65
|
# Quattrocento unique parameters
|
|
66
66
|
self._streaming_frequency: int | None = None
|
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Device class for real-time interfacing the Sessantaquattro device.
|
|
3
|
+
Developer: Dominik I. Braun
|
|
4
|
+
Contact: dome.braun@fau.de
|
|
5
|
+
Last Update: 2025-09-16
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
from typing import TYPE_CHECKING, Union, Dict
|
|
10
|
+
from PySide6.QtNetwork import QTcpSocket, QTcpServer, QHostAddress
|
|
11
|
+
import numpy as np
|
|
12
|
+
|
|
13
|
+
# Local Libraries
|
|
14
|
+
from biosignal_device_interface.devices.core.base_device import BaseDevice
|
|
15
|
+
from biosignal_device_interface.constants.devices.core.base_device_constants import (
|
|
16
|
+
DeviceType,
|
|
17
|
+
DeviceChannelTypes,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
# Constants
|
|
21
|
+
from biosignal_device_interface.constants.devices.otb.otb_sessantaquattro_constants import (
|
|
22
|
+
SESSANTAQUATTRO_AUXILIARY_LSB_DICT,
|
|
23
|
+
SESSANTAQUATTRO_CHANNEL_MODE_CHARACTERISTICS_DICT,
|
|
24
|
+
SESSANTAQUATTRO_DETECTION_MODE_CHARACTERISTICS_DICT,
|
|
25
|
+
SESSANTAQUATTRO_GAIN_MODE_CHARACTERISTICS_DICT,
|
|
26
|
+
SESSANTAQUATTRO_SAMPLES_PER_FRAME_DICT,
|
|
27
|
+
SessantaquattroChannelMode,
|
|
28
|
+
SessantaquattroDetectionMode,
|
|
29
|
+
SessantaquattroGainMode,
|
|
30
|
+
SessantaquattroRecordingMode,
|
|
31
|
+
SessantaquattroResolutionMode,
|
|
32
|
+
SessantaquattroSamplingFrequencyMode,
|
|
33
|
+
SessantaquattroTriggerMode,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
if TYPE_CHECKING:
|
|
37
|
+
from PySide6.QtWidgets import QMainWindow, QWidget
|
|
38
|
+
from aenum import Enum
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class OTBSessantaquattro(BaseDevice):
|
|
42
|
+
"""
|
|
43
|
+
Device class for real-time interfacing the Sessantaquattro device.
|
|
44
|
+
|
|
45
|
+
parent (Union[QMainWindow, QWidget], optional):
|
|
46
|
+
Parent widget to which the device is assigned to.
|
|
47
|
+
Defaults to None.
|
|
48
|
+
|
|
49
|
+
The Sessantaquattro class is using a TCP/IP protocol to communicate with the device.
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
def __init__(
|
|
53
|
+
self,
|
|
54
|
+
parent: Union[QMainWindow, QWidget],
|
|
55
|
+
) -> None:
|
|
56
|
+
"""Initialize the OTBSessantaquattro device.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
parent (Union[QMainWindow, QWidget]): Parent widget to which the device is assigned to.
|
|
60
|
+
"""
|
|
61
|
+
super().__init__(parent=parent)
|
|
62
|
+
|
|
63
|
+
# Device Parameters
|
|
64
|
+
self._device_type: DeviceType = DeviceType.OTB_SESSANTAQUATTRO
|
|
65
|
+
|
|
66
|
+
# Connection Parameters
|
|
67
|
+
self._interface: QTcpServer = None
|
|
68
|
+
self._client_socket: QTcpSocket = None
|
|
69
|
+
|
|
70
|
+
# Configuration Parameters
|
|
71
|
+
## Control Byte 0
|
|
72
|
+
self._sampling_frequency_mode: SessantaquattroSamplingFrequencyMode = (
|
|
73
|
+
SessantaquattroSamplingFrequencyMode.NONE
|
|
74
|
+
)
|
|
75
|
+
self._channel_mode: SessantaquattroChannelMode = SessantaquattroChannelMode.NONE
|
|
76
|
+
self._detection_mode: SessantaquattroDetectionMode = (
|
|
77
|
+
SessantaquattroDetectionMode.NONE
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
## Control Byte 1
|
|
81
|
+
self._resolution_mode: SessantaquattroResolutionMode = (
|
|
82
|
+
SessantaquattroResolutionMode.NONE
|
|
83
|
+
)
|
|
84
|
+
self._gain_mode: SessantaquattroGainMode = SessantaquattroGainMode.NONE
|
|
85
|
+
self._trigger_mode: SessantaquattroTriggerMode = SessantaquattroTriggerMode.NONE
|
|
86
|
+
self._recording_mode: SessantaquattroRecordingMode = (
|
|
87
|
+
SessantaquattroRecordingMode.NONE
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
def _connect_to_device(self):
|
|
91
|
+
super()._connect_to_device()
|
|
92
|
+
|
|
93
|
+
self._interface = QTcpServer(self)
|
|
94
|
+
self._received_bytes: bytearray = bytearray()
|
|
95
|
+
|
|
96
|
+
if not self._interface.listen(
|
|
97
|
+
QHostAddress(self._connection_settings[0]), self._connection_settings[1]
|
|
98
|
+
):
|
|
99
|
+
return False
|
|
100
|
+
|
|
101
|
+
self._interface.newConnection.connect(self._make_request)
|
|
102
|
+
|
|
103
|
+
self._connection_timeout_timer.start()
|
|
104
|
+
|
|
105
|
+
return True
|
|
106
|
+
|
|
107
|
+
def _make_request(self) -> bool:
|
|
108
|
+
super()._make_request()
|
|
109
|
+
self._client_socket = self._interface.nextPendingConnection()
|
|
110
|
+
|
|
111
|
+
if self._client_socket:
|
|
112
|
+
|
|
113
|
+
self._client_socket.readyRead.connect(self._read_data)
|
|
114
|
+
|
|
115
|
+
if not self.is_connected:
|
|
116
|
+
self.is_connected = True
|
|
117
|
+
self.connect_toggled.emit(self.is_connected)
|
|
118
|
+
self._connection_timeout_timer.stop()
|
|
119
|
+
return True
|
|
120
|
+
|
|
121
|
+
elif not self._is_configured:
|
|
122
|
+
self._is_configured = True
|
|
123
|
+
self.configure_toggled.emit(self._is_configured)
|
|
124
|
+
return True
|
|
125
|
+
|
|
126
|
+
def _disconnect_from_device(self) -> bool:
|
|
127
|
+
super()._disconnect_from_device()
|
|
128
|
+
|
|
129
|
+
if self._client_socket is not None:
|
|
130
|
+
self._client_socket.readyRead.disconnect(self._read_data)
|
|
131
|
+
self._client_socket.disconnectFromHost()
|
|
132
|
+
self._client_socket.close()
|
|
133
|
+
|
|
134
|
+
if self._interface is not None:
|
|
135
|
+
self._interface.close()
|
|
136
|
+
|
|
137
|
+
return True
|
|
138
|
+
|
|
139
|
+
def configure_device(
|
|
140
|
+
self, params: Dict[str, Union[Enum, Dict[str, Enum]]] # type: ignore
|
|
141
|
+
) -> None:
|
|
142
|
+
super().configure_device(params)
|
|
143
|
+
|
|
144
|
+
if not self.is_connected or self._client_socket is None:
|
|
145
|
+
return
|
|
146
|
+
|
|
147
|
+
# Set Configuration Parameters
|
|
148
|
+
self._sampling_frequency = SESSANTAQUATTRO_DETECTION_MODE_CHARACTERISTICS_DICT[
|
|
149
|
+
self._detection_mode
|
|
150
|
+
][self._sampling_frequency_mode]
|
|
151
|
+
|
|
152
|
+
channels = SESSANTAQUATTRO_CHANNEL_MODE_CHARACTERISTICS_DICT[
|
|
153
|
+
self._sampling_frequency_mode
|
|
154
|
+
][self._detection_mode]
|
|
155
|
+
|
|
156
|
+
self._number_of_biosignal_channels = channels[DeviceChannelTypes.BIOSIGNAL]
|
|
157
|
+
self._biosignal_channel_indices = np.arange(
|
|
158
|
+
0, self._number_of_biosignal_channels
|
|
159
|
+
)
|
|
160
|
+
self._number_of_auxiliary_channels = channels[DeviceChannelTypes.AUXILIARY]
|
|
161
|
+
self._auxiliary_channel_indices = np.arange(
|
|
162
|
+
self._number_of_biosignal_channels,
|
|
163
|
+
self._number_of_biosignal_channels + self._number_of_auxiliary_channels,
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
self._number_of_channels = (
|
|
167
|
+
self._number_of_biosignal_channels + self._number_of_auxiliary_channels
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
self._bytes_per_sample = (
|
|
171
|
+
2 if self._resolution_mode == SessantaquattroResolutionMode.LOW else 3
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
self._conversion_factor_biosignal = SESSANTAQUATTRO_GAIN_MODE_CHARACTERISTICS_DICT[
|
|
175
|
+
self._resolution_mode
|
|
176
|
+
][self._gain_mode]
|
|
177
|
+
|
|
178
|
+
self._conversion_factor_auxiliary = SESSANTAQUATTRO_AUXILIARY_LSB_DICT[
|
|
179
|
+
self._resolution_mode
|
|
180
|
+
]
|
|
181
|
+
|
|
182
|
+
self._samples_per_frame = SESSANTAQUATTRO_SAMPLES_PER_FRAME_DICT[
|
|
183
|
+
self._channel_mode
|
|
184
|
+
]
|
|
185
|
+
|
|
186
|
+
# Prepare streaming
|
|
187
|
+
self._buffer_size = (
|
|
188
|
+
self._number_of_channels * self._samples_per_frame * self._bytes_per_sample
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
self._received_bytes = bytearray()
|
|
192
|
+
|
|
193
|
+
self._configure_command()
|
|
194
|
+
self._send_configuration_to_device()
|
|
195
|
+
|
|
196
|
+
def _send_configuration_to_device(self) -> None:
|
|
197
|
+
configuration_bytes = int(self._configuration_command).to_bytes(
|
|
198
|
+
2, byteorder="big"
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
success = self._client_socket.write(configuration_bytes)
|
|
202
|
+
|
|
203
|
+
if success == -1:
|
|
204
|
+
self._disconnect_from_device()
|
|
205
|
+
|
|
206
|
+
def _configure_command(self) -> None:
|
|
207
|
+
self._configuration_command = 0
|
|
208
|
+
|
|
209
|
+
# Control Byte 0
|
|
210
|
+
# Bit 0 - Transmission Mode | Handled by Start / Stop Streaming
|
|
211
|
+
# Bit 1
|
|
212
|
+
if self._recording_mode is None or self._recording_mode == SessantaquattroRecordingMode.NONE:
|
|
213
|
+
self._recording_mode = SessantaquattroRecordingMode.STOP
|
|
214
|
+
self._configuration_command += (self._recording_mode.value - 1) << 1
|
|
215
|
+
|
|
216
|
+
# Bit 3-2
|
|
217
|
+
if self._trigger_mode is None or self._trigger_mode == SessantaquattroTriggerMode.NONE:
|
|
218
|
+
self._trigger_mode = SessantaquattroTriggerMode.DEFAULT
|
|
219
|
+
self._configuration_command += (self._trigger_mode.value - 1) << 2
|
|
220
|
+
|
|
221
|
+
# Bit 5-4
|
|
222
|
+
if self._gain_mode is None or self._gain_mode == SessantaquattroGainMode.NONE:
|
|
223
|
+
self._gain_mode = SessantaquattroGainMode.DEFAULT
|
|
224
|
+
self._configuration_command += (self._gain_mode.value - 1) << 4
|
|
225
|
+
|
|
226
|
+
# Bit 6 - Filter mode: enable high pass filter
|
|
227
|
+
self._configuration_command += 1 << 6
|
|
228
|
+
|
|
229
|
+
# Bit 7 - Resolution mode
|
|
230
|
+
if self._resolution_mode is None or self._resolution_mode == SessantaquattroResolutionMode.NONE:
|
|
231
|
+
self._resolution_mode = SessantaquattroResolutionMode.LOW
|
|
232
|
+
self._configuration_command += (self._resolution_mode.value - 1) << 7
|
|
233
|
+
|
|
234
|
+
# Control Byte 1
|
|
235
|
+
# Bit 2-0 - Detection Mode
|
|
236
|
+
if self._detection_mode is None or self._detection_mode == SessantaquattroDetectionMode.NONE:
|
|
237
|
+
self._detection_mode = SessantaquattroDetectionMode.MONOPOLAR
|
|
238
|
+
self._configuration_command += (self._detection_mode.value - 1) << 8
|
|
239
|
+
|
|
240
|
+
# Bit 4-3 - Channel Mode
|
|
241
|
+
if self._channel_mode is None or self._channel_mode == SessantaquattroChannelMode.NONE:
|
|
242
|
+
self._channel_mode = SessantaquattroChannelMode.LOW
|
|
243
|
+
self._configuration_command += (self._channel_mode.value - 1) << 11
|
|
244
|
+
|
|
245
|
+
# Bit 6-5 - Sampling Frequency Mode
|
|
246
|
+
if self._sampling_frequency_mode is None or self._sampling_frequency_mode == SessantaquattroSamplingFrequencyMode.NONE:
|
|
247
|
+
self._sampling_frequency_mode = SessantaquattroSamplingFrequencyMode.HIGH
|
|
248
|
+
self._configuration_command += (self._sampling_frequency_mode.value - 1) << 13
|
|
249
|
+
|
|
250
|
+
# Bit 7 - GETSET - always 0 for setting the configuration
|
|
251
|
+
self._configuration_command += 0 << 15
|
|
252
|
+
|
|
253
|
+
def _start_streaming(self) -> None:
|
|
254
|
+
super()._start_streaming()
|
|
255
|
+
|
|
256
|
+
if self._configuration_command is None:
|
|
257
|
+
return
|
|
258
|
+
|
|
259
|
+
self._configuration_command += 1
|
|
260
|
+
self._send_configuration_to_device()
|
|
261
|
+
|
|
262
|
+
def _stop_streaming(self) -> None:
|
|
263
|
+
super()._stop_streaming()
|
|
264
|
+
|
|
265
|
+
if self._configuration_command is None:
|
|
266
|
+
return
|
|
267
|
+
|
|
268
|
+
self._configuration_command -= 1
|
|
269
|
+
self._send_configuration_to_device()
|
|
270
|
+
|
|
271
|
+
def clear_socket(self) -> None:
|
|
272
|
+
if self._client_socket is not None:
|
|
273
|
+
self._client_socket.readAll()
|
|
274
|
+
self._received_bytes = bytearray()
|
|
275
|
+
|
|
276
|
+
def _read_data(self) -> None:
|
|
277
|
+
super()._read_data()
|
|
278
|
+
|
|
279
|
+
if self._client_socket is None:
|
|
280
|
+
return
|
|
281
|
+
|
|
282
|
+
if not self._is_streaming:
|
|
283
|
+
buffer_size: int = 13
|
|
284
|
+
config_bytes = self._client_socket.read(
|
|
285
|
+
buffer_size
|
|
286
|
+
) # TODO: Catches configuration settings, has to be checked
|
|
287
|
+
|
|
288
|
+
self._process_configuration_data(config_bytes)
|
|
289
|
+
return
|
|
290
|
+
|
|
291
|
+
while self._client_socket.bytesAvailable() > self._buffer_size:
|
|
292
|
+
packet = self._client_socket.read(self._buffer_size)
|
|
293
|
+
if not packet:
|
|
294
|
+
continue
|
|
295
|
+
|
|
296
|
+
self._received_bytes.extend(packet)
|
|
297
|
+
|
|
298
|
+
while len(self._received_bytes) >= self._buffer_size:
|
|
299
|
+
data_to_process = self._received_bytes[: self._buffer_size]
|
|
300
|
+
self._process_data(data_to_process)
|
|
301
|
+
self._received_bytes = self._received_bytes[self._buffer_size :]
|
|
302
|
+
|
|
303
|
+
def _process_configuration_data(self, input: bytearray) -> None:
|
|
304
|
+
# TODO: Implement configuration data processing
|
|
305
|
+
...
|
|
306
|
+
|
|
307
|
+
def _process_data(self, input: bytearray) -> None:
|
|
308
|
+
super()._process_data(input)
|
|
309
|
+
|
|
310
|
+
decoded_data = self._bytes_to_integers(input)
|
|
311
|
+
|
|
312
|
+
processed_data = decoded_data.reshape(
|
|
313
|
+
self._number_of_channels, -1, order="F"
|
|
314
|
+
).astype(np.float32)
|
|
315
|
+
|
|
316
|
+
self.data_available.emit(processed_data)
|
|
317
|
+
self.biosignal_data_available.emit(self._extract_biosignal_data(processed_data))
|
|
318
|
+
self.auxiliary_data_available.emit(self._extract_auxiliary_data(processed_data))
|
|
319
|
+
|
|
320
|
+
# Convert integer to bytes
|
|
321
|
+
def _integer_to_bytes(self, command: int) -> bytes:
|
|
322
|
+
return command.to_bytes(2, byteorder="big")
|
|
323
|
+
|
|
324
|
+
# Convert channels from bytes to integers
|
|
325
|
+
def _bytes_to_integers(
|
|
326
|
+
self,
|
|
327
|
+
data: bytearray,
|
|
328
|
+
) -> np.ndarray:
|
|
329
|
+
channel_values = []
|
|
330
|
+
# Separate channels from byte-string. One channel has
|
|
331
|
+
# "bytes_in_sample" many bytes in it.
|
|
332
|
+
for channel_index in range(len(data) // 2):
|
|
333
|
+
channel_start = channel_index * self._bytes_per_sample
|
|
334
|
+
channel_end = (channel_index + 1) * self._bytes_per_sample
|
|
335
|
+
channel = data[channel_start:channel_end]
|
|
336
|
+
|
|
337
|
+
# Convert channel's byte value to integer
|
|
338
|
+
if self._resolution_mode == SessantaquattroResolutionMode.LOW:
|
|
339
|
+
value = self._decode_int16(channel)
|
|
340
|
+
else:
|
|
341
|
+
value = self._decode_int24(channel)
|
|
342
|
+
channel_values.append(value)
|
|
343
|
+
|
|
344
|
+
return np.array(channel_values)
|
|
345
|
+
|
|
346
|
+
def _decode_int16(self, bytes_value: bytearray) -> int:
|
|
347
|
+
value = None
|
|
348
|
+
# Combine 2 bytes to a 16 bit integer value
|
|
349
|
+
value = bytes_value[0] * 256 + bytes_value[1]
|
|
350
|
+
# See if the value is negative and make the two's complement
|
|
351
|
+
if value >= 32768:
|
|
352
|
+
value -= 65536
|
|
353
|
+
return value
|
|
354
|
+
|
|
355
|
+
# Convert byte-array value to an integer value and apply two's complement
|
|
356
|
+
def _decode_int24(self, bytes_value: bytearray) -> int:
|
|
357
|
+
value = None
|
|
358
|
+
# Combine 3 bytes to a 24 bit integer value
|
|
359
|
+
value = bytes_value[0] * 65536 + bytes_value[1] * 256 + bytes_value[2]
|
|
360
|
+
# See if the value is negative and make the two's complement
|
|
361
|
+
if value >= 8388608:
|
|
362
|
+
value -= 16777216
|
|
363
|
+
return value
|
|
@@ -29,6 +29,9 @@ from biosignal_device_interface.gui.device_template_widgets.otb.otb_quattrocento
|
|
|
29
29
|
from biosignal_device_interface.gui.device_template_widgets.otb.otb_syncstation_widget import (
|
|
30
30
|
OTBSyncStationWidget,
|
|
31
31
|
)
|
|
32
|
+
from biosignal_device_interface.gui.device_template_widgets.otb.otb_sessantaquattro_widget import (
|
|
33
|
+
OTBSessantaquattroWidget,
|
|
34
|
+
)
|
|
32
35
|
|
|
33
36
|
if TYPE_CHECKING:
|
|
34
37
|
from PySide6.QtWidgets import QWidget, QMainWindow
|
|
@@ -47,5 +50,6 @@ class AllDevicesWidget(BaseMultipleDevicesWidget):
|
|
|
47
50
|
DeviceType.OTB_MUOVI: OTBMuoviWidget(self),
|
|
48
51
|
DeviceType.OTB_MUOVI_PLUS: OTBMuoviPlusWidget(self),
|
|
49
52
|
DeviceType.OTB_SYNCSTATION: OTBSyncStationWidget(self),
|
|
53
|
+
DeviceType.OTB_SESSANTAQUATTRO: OTBSessantaquattroWidget(self),
|
|
50
54
|
}
|
|
51
55
|
self._set_devices(self._device_selection)
|