qlsdk2 0.5.1__py3-none-any.whl → 0.6.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.
- qlsdk/core/entity/__init__.py +175 -42
- qlsdk/core/message/udp.py +9 -1
- qlsdk/entity/__init__.py +0 -0
- qlsdk/entity/message.py +0 -0
- qlsdk/entity/signal.py +0 -0
- qlsdk/interface/__init__.py +10 -0
- qlsdk/interface/analyzer.py +2 -0
- qlsdk/interface/collector.py +10 -0
- qlsdk/interface/device.py +2 -0
- qlsdk/interface/parser.py +13 -0
- qlsdk/interface/stimulator.py +2 -0
- qlsdk/interface/store.py +2 -0
- qlsdk/persist/ars_edf.py +80 -101
- qlsdk/persist/rsc_edf.py +23 -13
- qlsdk/rsc/command/__init__.py +30 -16
- qlsdk/rsc/device/__init__.py +2 -1
- qlsdk/rsc/device/arskindling.py +248 -310
- qlsdk/rsc/device/base.py +214 -105
- qlsdk/rsc/device/c16_rs.py +1 -5
- qlsdk/rsc/device/c256_rs.py +17 -338
- qlsdk/rsc/device/c64_rs.py +2 -332
- qlsdk/rsc/device/c64s1.py +4 -340
- qlsdk/rsc/device/device_factory.py +1 -0
- qlsdk/rsc/interface/device.py +38 -80
- qlsdk/rsc/interface/parser.py +6 -0
- qlsdk/rsc/manager/container.py +10 -2
- qlsdk/rsc/network/discover.py +2 -0
- qlsdk/rsc/paradigm.py +127 -6
- qlsdk/rsc/parser/__init__.py +2 -1
- qlsdk/rsc/parser/base.py +11 -6
- qlsdk/rsc/parser/rsc.py +130 -0
- {qlsdk2-0.5.1.dist-info → qlsdk2-0.6.0.dist-info}/METADATA +25 -15
- {qlsdk2-0.5.1.dist-info → qlsdk2-0.6.0.dist-info}/RECORD +35 -24
- {qlsdk2-0.5.1.dist-info → qlsdk2-0.6.0.dist-info}/WHEEL +0 -0
- {qlsdk2-0.5.1.dist-info → qlsdk2-0.6.0.dist-info}/top_level.txt +0 -0
qlsdk/rsc/device/arskindling.py
CHANGED
|
@@ -1,133 +1,55 @@
|
|
|
1
|
-
|
|
2
1
|
from multiprocessing import Queue
|
|
3
|
-
from
|
|
4
|
-
from time import sleep, time_ns
|
|
5
|
-
from typing import Any, Dict, Literal
|
|
6
|
-
|
|
2
|
+
from typing import Literal
|
|
7
3
|
from loguru import logger
|
|
8
4
|
from qlsdk.persist import ARSKindlingEDFHandler
|
|
9
|
-
from qlsdk.rsc.interface import IDevice
|
|
5
|
+
from qlsdk.rsc.interface import IDevice
|
|
10
6
|
from qlsdk.rsc.command import *
|
|
11
|
-
from qlsdk.rsc.parser.base import TcpMessageParser
|
|
12
7
|
from qlsdk.rsc.device.base import QLBaseDevice
|
|
13
8
|
|
|
9
|
+
|
|
10
|
+
def intersection_positions(A, B):
|
|
11
|
+
setB = set(B)
|
|
12
|
+
seen = set()
|
|
13
|
+
return [idx for idx, elem in enumerate(A)
|
|
14
|
+
if elem in setB and elem not in seen and not seen.add(elem)]
|
|
15
|
+
|
|
16
|
+
def get_sorted_indices_basic(A, B):
|
|
17
|
+
"""
|
|
18
|
+
找出数组A中存在于数组B的元素,并返回这些元素在A中的位置,且顺序与它们在B中的顺序一致。
|
|
19
|
+
|
|
20
|
+
参数:
|
|
21
|
+
A (list): 待查找的数组。
|
|
22
|
+
B (list): 作为参考的数组。
|
|
23
|
+
|
|
24
|
+
返回:
|
|
25
|
+
list: 一个列表,包含符合条件的元素在A中的索引,顺序与这些元素在B中的出现顺序一致。
|
|
26
|
+
"""
|
|
27
|
+
# 1. 创建B中元素到其索引的映射
|
|
28
|
+
b_index_map = {value: idx for idx, value in enumerate(B)}
|
|
29
|
+
|
|
30
|
+
# 2. 筛选A中存在于B的元素,并记录其在A中的索引及在B中的位置
|
|
31
|
+
# 使用列表推导式,同时避免重复元素干扰(如果B有重复,以第一次出现为准)
|
|
32
|
+
found_elements = []
|
|
33
|
+
for idx_a, value in enumerate(A):
|
|
34
|
+
if value in b_index_map:
|
|
35
|
+
# 记录: (该元素在A中的索引, 该元素在B中的索引)
|
|
36
|
+
found_elements.append((idx_a, b_index_map[value]))
|
|
37
|
+
|
|
38
|
+
# 3. 根据元素在B中的位置进行排序
|
|
39
|
+
# 排序的依据是元组的第二个元素,即 b_index_map[value]
|
|
40
|
+
found_elements_sorted = sorted(found_elements, key=lambda x: x[1])
|
|
41
|
+
|
|
42
|
+
# 4. 提取排序后在A中的索引,形成结果数组C
|
|
43
|
+
array_C = [item[0] for item in found_elements_sorted]
|
|
44
|
+
|
|
45
|
+
return array_C
|
|
46
|
+
|
|
14
47
|
class ARSKindling(QLBaseDevice):
|
|
15
48
|
|
|
16
49
|
device_type = 0x60 # C64RS设备类型标识符
|
|
17
50
|
|
|
18
51
|
def __init__(self, socket):
|
|
19
52
|
super().__init__(socket)
|
|
20
|
-
# self.socket = socket
|
|
21
|
-
|
|
22
|
-
self._id = None
|
|
23
|
-
|
|
24
|
-
# 设备信息
|
|
25
|
-
# self.device_id = None
|
|
26
|
-
# self.device_name = None
|
|
27
|
-
self._device_no = None
|
|
28
|
-
# self.software_version = None
|
|
29
|
-
# self.hardware_version = None
|
|
30
|
-
# self.connect_time = None
|
|
31
|
-
# self.current_time = None
|
|
32
|
-
# # mV
|
|
33
|
-
# self.voltage = None
|
|
34
|
-
# # %
|
|
35
|
-
# self.battery_remain = None
|
|
36
|
-
# # %
|
|
37
|
-
# self.battery_total = None
|
|
38
|
-
# persist
|
|
39
|
-
self._recording = False
|
|
40
|
-
self._storage_path = None
|
|
41
|
-
self._file_prefix = None
|
|
42
|
-
|
|
43
|
-
# 可设置参数
|
|
44
|
-
# 采集:采样量程、采样率、采样通道
|
|
45
|
-
# 刺激:刺激电流、刺激频率、刺激时间、刺激通道
|
|
46
|
-
# 采样量程(mV):188、375、563、750、1125、2250、4500
|
|
47
|
-
self._sample_range:Literal[188, 375, 563, 750, 1125, 2250, 4500] = 188
|
|
48
|
-
# 采样率(Hz):250、500、1000、2000、4000、8000、16000、32000
|
|
49
|
-
self._sample_rate:Literal[250, 500, 1000, 2000, 4000, 8000, 16000, 32000] = 500
|
|
50
|
-
self._physical_max = self._sample_range * 1000 # 物理最大值(uV)
|
|
51
|
-
self._physical_min = -self._sample_range * 1000 # 物理最小值(uV)
|
|
52
|
-
self._digital_max = 8388607
|
|
53
|
-
self._digital_min = -8388608
|
|
54
|
-
self._physical_range = self._physical_max - self._physical_min
|
|
55
|
-
self._digital_range = 16777215
|
|
56
|
-
self._acq_channels = None
|
|
57
|
-
self._acq_param = {
|
|
58
|
-
"sample_range": 188,
|
|
59
|
-
"sample_rate": 500,
|
|
60
|
-
"channels": [],
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
self._stim_param = {
|
|
64
|
-
"stim_type": 0, # 刺激类型:0-所有通道参数相同, 1: 通道参数不同
|
|
65
|
-
"channels": [],
|
|
66
|
-
"param": [{
|
|
67
|
-
"channel_id": 0, #通道号 从0开始 -- 必填
|
|
68
|
-
"waveform": 3, #波形类型:0-直流,1-交流 2-方波 3-脉冲 -- 必填
|
|
69
|
-
"current": 1, #电流强度(mA) -- 必填
|
|
70
|
-
"duration": 30, #平稳阶段持续时间(s) -- 必填
|
|
71
|
-
"ramp_up": 5, #上升时间(s) 默认0
|
|
72
|
-
"ramp_down": 5, #下降时间(s) 默认0
|
|
73
|
-
"frequency": 500, #频率(Hz) -- 非直流必填
|
|
74
|
-
"phase_position": 0, #相位 -- 默认0
|
|
75
|
-
"duration_delay": "0", #延迟启动时间(s) -- 默认0
|
|
76
|
-
"pulse_width": 0, #脉冲宽度(us) -- 仅脉冲类型电流有效, 默认100us
|
|
77
|
-
},
|
|
78
|
-
{
|
|
79
|
-
"channel_id": 1, #通道号 从0开始 -- 必填
|
|
80
|
-
"waveform": 3, #波形类型:0-直流,1-交流 2-方波 3-脉冲 -- 必填
|
|
81
|
-
"current": 1, #电流强度(mA) -- 必填
|
|
82
|
-
"duration": 30, #平稳阶段持续时间(s) -- 必填
|
|
83
|
-
"ramp_up": 5, #上升时间(s) 默认0
|
|
84
|
-
"ramp_down": 5, #下降时间(s) 默认0
|
|
85
|
-
"frequency": 500, #频率(Hz) -- 非直流必填
|
|
86
|
-
"phase_position": 0, #相位 -- 默认0
|
|
87
|
-
"duration_delay": "0", #延迟启动时间(s) -- 默认0
|
|
88
|
-
"pulse_width": 0, #脉冲宽度(us) -- 仅脉冲类型电流有效, 默认100us
|
|
89
|
-
}
|
|
90
|
-
]
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
self.stim_paradigm = None
|
|
94
|
-
|
|
95
|
-
signal_info = {
|
|
96
|
-
"param" : None,
|
|
97
|
-
"start_time" : None,
|
|
98
|
-
"finished_time" : None,
|
|
99
|
-
"packet_total" : None,
|
|
100
|
-
"last_packet_time" : None,
|
|
101
|
-
"state" : 0
|
|
102
|
-
}
|
|
103
|
-
stim_info = {
|
|
104
|
-
|
|
105
|
-
}
|
|
106
|
-
Impedance_info = {
|
|
107
|
-
|
|
108
|
-
}
|
|
109
|
-
# 信号采集状态
|
|
110
|
-
# 信号数据包总数(一个信号采集周期内)
|
|
111
|
-
# 信号采集参数
|
|
112
|
-
# 电刺激状态
|
|
113
|
-
# 电刺激开始时间(最近一次)
|
|
114
|
-
# 电刺激结束时间(最近一次)
|
|
115
|
-
# 电刺激参数
|
|
116
|
-
# 启动数据解析线程
|
|
117
|
-
# 数据存储状态
|
|
118
|
-
# 存储目录
|
|
119
|
-
|
|
120
|
-
#
|
|
121
|
-
self.__signal_consumer: Dict[str, Queue[Any]]={}
|
|
122
|
-
self.__impedance_consumer: Dict[str, Queue[Any]]={}
|
|
123
|
-
|
|
124
|
-
# EDF文件处理器
|
|
125
|
-
self._edf_handler = None
|
|
126
|
-
self.storage_enable = True
|
|
127
|
-
|
|
128
|
-
# self.start_message_listening()
|
|
129
|
-
# self.start()
|
|
130
|
-
# self.parser.set_device(self)
|
|
131
53
|
|
|
132
54
|
self.channel_mapping = {
|
|
133
55
|
"A1": 55, "A2": 56, "A3": 53, "A4": 54, "A5": 51, "A6": 52, "A7": 49, "A8": 50,
|
|
@@ -143,18 +65,75 @@ class ARSKindling(QLBaseDevice):
|
|
|
143
65
|
"D9": 9, "D10": 10, "D11": 11, "D12": 12, "D13": 13, "D14": 14,
|
|
144
66
|
}
|
|
145
67
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
68
|
+
self._channel_spilt = {
|
|
69
|
+
"A" : [55, 56, 53, 54, 51, 52, 49, 50, 57, 58, 59, 60, 61, 62],
|
|
70
|
+
"B" : [39, 40, 37, 38, 35, 36, 33, 34, 41, 42, 43, 44, 45, 46],
|
|
71
|
+
"C" : [23, 24, 21, 22, 19, 20, 17, 18, 25, 26, 27, 28, 29, 30],
|
|
72
|
+
"D" : [7, 8, 5, 6, 3, 4, 1, 2, 9, 10, 11, 12, 13, 14],
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
self._channel_mapping = {
|
|
76
|
+
1: "7",
|
|
77
|
+
2: "8",
|
|
78
|
+
3: "5",
|
|
79
|
+
4: "6",
|
|
80
|
+
5: "3",
|
|
81
|
+
6: "4",
|
|
82
|
+
7: "1",
|
|
83
|
+
8: "2",
|
|
84
|
+
9: "9",
|
|
85
|
+
10: "10",
|
|
86
|
+
11: "11",
|
|
87
|
+
12: "12",
|
|
88
|
+
13: "13",
|
|
89
|
+
14: "14",
|
|
90
|
+
|
|
91
|
+
17: "7",
|
|
92
|
+
18: "8",
|
|
93
|
+
19: "5",
|
|
94
|
+
20: "6",
|
|
95
|
+
21: "3",
|
|
96
|
+
22: "4",
|
|
97
|
+
23: "1",
|
|
98
|
+
24: "2",
|
|
99
|
+
25: "9",
|
|
100
|
+
26: "10",
|
|
101
|
+
27: "11",
|
|
102
|
+
28: "12",
|
|
103
|
+
29: "13",
|
|
104
|
+
30: "14",
|
|
105
|
+
|
|
106
|
+
33: "7",
|
|
107
|
+
34: "8",
|
|
108
|
+
35: "5",
|
|
109
|
+
36: "6",
|
|
110
|
+
37: "3",
|
|
111
|
+
38: "4",
|
|
112
|
+
39: "1",
|
|
113
|
+
40: "2",
|
|
114
|
+
41: "9",
|
|
115
|
+
42: "10",
|
|
116
|
+
43: "11",
|
|
117
|
+
44: "12",
|
|
118
|
+
45: "13",
|
|
119
|
+
46: "14",
|
|
120
|
+
|
|
121
|
+
49: "7",
|
|
122
|
+
50: "8",
|
|
123
|
+
51: "5",
|
|
124
|
+
52: "6",
|
|
125
|
+
53: "3",
|
|
126
|
+
54: "4",
|
|
127
|
+
55: "1",
|
|
128
|
+
56: "2",
|
|
129
|
+
57: "9",
|
|
130
|
+
58: "10",
|
|
131
|
+
59: "11",
|
|
132
|
+
60: "12",
|
|
133
|
+
61: "13",
|
|
134
|
+
62: "14"
|
|
135
|
+
}
|
|
136
|
+
|
|
158
137
|
@classmethod
|
|
159
138
|
def from_parent(cls, parent:IDevice) -> IDevice:
|
|
160
139
|
rlt = cls(parent.socket)
|
|
@@ -168,68 +147,8 @@ class ARSKindling(QLBaseDevice):
|
|
|
168
147
|
self._edf_handler.set_device_no(self.device_no)
|
|
169
148
|
self._edf_handler.set_storage_path(self._storage_path)
|
|
170
149
|
self._edf_handler.set_file_prefix(self._file_prefix if self._file_prefix else 'ARS')
|
|
150
|
+
logger.debug(f"EDF Handler initialized")
|
|
171
151
|
|
|
172
|
-
@property
|
|
173
|
-
def edf_handler(self):
|
|
174
|
-
if not self.storage_enable:
|
|
175
|
-
return None
|
|
176
|
-
|
|
177
|
-
if self._edf_handler is None:
|
|
178
|
-
self.init_edf_handler()
|
|
179
|
-
|
|
180
|
-
return self._edf_handler
|
|
181
|
-
|
|
182
|
-
@property
|
|
183
|
-
def acq_channels(self):
|
|
184
|
-
if self._acq_channels is None:
|
|
185
|
-
self._acq_channels = [i for i in range(1, 63)]
|
|
186
|
-
return self._acq_channels
|
|
187
|
-
@property
|
|
188
|
-
def sample_range(self):
|
|
189
|
-
return self._sample_range if self._sample_range else 188
|
|
190
|
-
@property
|
|
191
|
-
def sample_rate(self):
|
|
192
|
-
return self._sample_rate if self._sample_rate else 500
|
|
193
|
-
@property
|
|
194
|
-
def resolution(self):
|
|
195
|
-
return 24
|
|
196
|
-
|
|
197
|
-
@property
|
|
198
|
-
def signal_consumers(self):
|
|
199
|
-
return self.__signal_consumer
|
|
200
|
-
|
|
201
|
-
@property
|
|
202
|
-
def impedance_consumers(self):
|
|
203
|
-
return self.__impedance_consumer
|
|
204
|
-
|
|
205
|
-
# 设置记录文件路径
|
|
206
|
-
def set_storage_path(self, path):
|
|
207
|
-
self._storage_path = path
|
|
208
|
-
|
|
209
|
-
# 设置记录文件名称前缀
|
|
210
|
-
def set_file_prefix(self, prefix):
|
|
211
|
-
self._file_prefix = prefix
|
|
212
|
-
|
|
213
|
-
# 接收socket消息
|
|
214
|
-
def accept(self):
|
|
215
|
-
while True:
|
|
216
|
-
# 缓冲去4M
|
|
217
|
-
data = self.socket.recv(4096*1024)
|
|
218
|
-
if not data:
|
|
219
|
-
logger.warning(f"设备{self.device_no}连接结束")
|
|
220
|
-
break
|
|
221
|
-
|
|
222
|
-
self._parser.append(data)
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
# socket发送数据
|
|
226
|
-
def send(self, data):
|
|
227
|
-
self.socket.sendall(data)
|
|
228
|
-
|
|
229
|
-
# 设置刺激参数
|
|
230
|
-
def set_stim_param(self, param):
|
|
231
|
-
self.stim_paradigm = param
|
|
232
|
-
|
|
233
152
|
# 设置采集参数
|
|
234
153
|
def set_acq_param(self, channels, sample_rate = 500, sample_range = 188):
|
|
235
154
|
self._acq_param["original_channels"] = channels
|
|
@@ -249,138 +168,157 @@ class ARSKindling(QLBaseDevice):
|
|
|
249
168
|
self._acq_param["sample_range"] = sample_range
|
|
250
169
|
self._acq_channels = channels
|
|
251
170
|
self._sample_rate = sample_rate
|
|
252
|
-
self._sample_range = sample_range
|
|
171
|
+
self._sample_range = sample_range
|
|
253
172
|
|
|
254
|
-
#
|
|
255
|
-
|
|
256
|
-
|
|
173
|
+
# 设置阻抗通道
|
|
174
|
+
# {'A': [1,2,3], 'B':[1,2,3]}
|
|
175
|
+
def set_impedance_channels(self, channels):
|
|
257
176
|
|
|
258
|
-
|
|
259
|
-
logger.info("启动阻抗测量")
|
|
260
|
-
msg = StartImpedanceCommand.build(self).pack()
|
|
261
|
-
logger.debug(f"start_impedance message is {msg.hex()}")
|
|
262
|
-
self.socket.sendall(msg)
|
|
263
|
-
|
|
264
|
-
def stop_impedance(self):
|
|
265
|
-
logger.info("停止阻抗测量")
|
|
266
|
-
msg = StopImpedanceCommand.build(self).pack()
|
|
267
|
-
logger.debug(f"stop_impedance message is {msg.hex()}")
|
|
268
|
-
self.socket.sendall(msg)
|
|
269
|
-
|
|
270
|
-
def start_stimulation(self):
|
|
271
|
-
if self.stim_paradigm is None:
|
|
272
|
-
logger.warning("刺激参数未设置,请先设置刺激参数")
|
|
273
|
-
return
|
|
274
|
-
logger.info("启动电刺激")
|
|
275
|
-
msg = StartStimulationCommand.build(self).pack()
|
|
276
|
-
logger.debug(f"start_stimulation message is {msg.hex()}")
|
|
277
|
-
self.socket.sendall(msg)
|
|
278
|
-
t = Thread(target=self._stop_stimulation_trigger, args=(self.stim_paradigm.duration,))
|
|
279
|
-
t.start()
|
|
177
|
+
arr = []
|
|
280
178
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
logger.warning("stop stim trigger fail. no edf writer alive")
|
|
179
|
+
# 根据映射关系做通道转换
|
|
180
|
+
for k in channels.keys():
|
|
181
|
+
if isinstance(channels[k], list):
|
|
182
|
+
temp = [k + str(i) for i in channels[k]]
|
|
183
|
+
arr += [self.channel_mapping.get(c, 1) for c in temp]
|
|
184
|
+
else:
|
|
185
|
+
arr += [k + str(channels[k])]
|
|
186
|
+
|
|
187
|
+
self._impedance_channels = arr
|
|
291
188
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
189
|
+
@property
|
|
190
|
+
def acq_channels(self):
|
|
191
|
+
if self._acq_channels is None:
|
|
192
|
+
self._acq_channels = [i for i in range(1, 63)]
|
|
193
|
+
|
|
194
|
+
arr = []
|
|
195
|
+
for k in self._acq_channels.keys():
|
|
196
|
+
arr = list(arr + self._acq_channels[k])
|
|
197
|
+
|
|
198
|
+
return list(set(arr))
|
|
199
|
+
|
|
200
|
+
def _produce_impedance(self, body: bytes):
|
|
201
|
+
# 分发阻抗数据包给订阅者
|
|
202
|
+
if len(self._impedance_consumer) > 0:
|
|
203
|
+
packet = self._impedance_wrapper(body)
|
|
204
|
+
real_data = self.__impedance_transfer(packet)
|
|
205
|
+
for topic, q in self._impedance_consumer.items():
|
|
206
|
+
try:
|
|
207
|
+
# 队列满了就丢弃最早的数据
|
|
208
|
+
if q.full():
|
|
209
|
+
q.get()
|
|
210
|
+
q.put(real_data, timeout=1)
|
|
211
|
+
except Exception as e:
|
|
212
|
+
logger.error(f"impedance data put to queue exception: {str(e)}")
|
|
213
|
+
|
|
214
|
+
def _produce_signal(self, body: bytes):
|
|
297
215
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
logger.
|
|
301
|
-
self._recording = recording
|
|
302
|
-
# 设置数据采集参数
|
|
303
|
-
param_bytes = SetAcquisitionParamCommand.build(self).pack()
|
|
304
|
-
# 启动数据采集
|
|
305
|
-
start_bytes = StartAcquisitionCommand.build(self).pack()
|
|
306
|
-
msg = param_bytes + start_bytes
|
|
307
|
-
logger.debug(f"start_acquisition message is {msg.hex()}")
|
|
308
|
-
self.socket.sendall(msg)
|
|
216
|
+
# 处理信号数据
|
|
217
|
+
data = self._signal_wrapper(body)
|
|
218
|
+
# logger.debug("pkg_id: {}, eeg len: {}".format(data.pkg_id, len(data.eeg)))
|
|
309
219
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
self.socket.sendall(msg)
|
|
316
|
-
if self._edf_handler:
|
|
317
|
-
# 发送结束标识
|
|
318
|
-
self.edf_handler.write(None)
|
|
220
|
+
trigger_positions = [index for index, value in enumerate(data.trigger) if value != 0]
|
|
221
|
+
if len(trigger_positions) > 0:
|
|
222
|
+
# logger.debug(f"Trigger触发点位置: {trigger_positions}, 触发点时间戳: {[data.time_stamp + int(pos * 1000 / data.sample_rate) for pos in trigger_positions]}")
|
|
223
|
+
for pos in trigger_positions:
|
|
224
|
+
self.trigger(self.trigger_info(data.trigger[pos]))
|
|
319
225
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
# 数据队列
|
|
324
|
-
if q is None:
|
|
325
|
-
q = Queue(maxsize=1000)
|
|
326
|
-
|
|
327
|
-
# 队列名称
|
|
328
|
-
if topic is None:
|
|
329
|
-
topic = f"{type}_{time_ns()}"
|
|
226
|
+
if len(self.signal_consumers) > 0 :
|
|
227
|
+
# 信号数字值转物理值
|
|
228
|
+
data.eeg_p = self.eeg2phy(np.array(data.eeg))
|
|
330
229
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
self.
|
|
230
|
+
real_data = self.__signal_transfer(data)
|
|
231
|
+
|
|
232
|
+
# 存储
|
|
233
|
+
if self.storage_enable:
|
|
234
|
+
# 确保记录线程启动
|
|
235
|
+
if self._recording is False:
|
|
236
|
+
self._start_recording()
|
|
237
|
+
|
|
238
|
+
# 写入文件的缓存队列
|
|
239
|
+
if self._signal_cache is None:
|
|
240
|
+
self._signal_cache = Queue(1000000) # 缓冲队列
|
|
241
|
+
tmp = real_data.copy()
|
|
242
|
+
self._signal_cache.put(tmp)
|
|
243
|
+
|
|
244
|
+
if len(self.signal_consumers) > 0 :
|
|
245
|
+
# 订阅只返回物理值
|
|
246
|
+
real_data.eeg = real_data.eeg_p
|
|
247
|
+
# 发送数据包到订阅者
|
|
248
|
+
for q in list(self.signal_consumers.values()):
|
|
249
|
+
# 队列满了就丢弃最早的数据
|
|
250
|
+
if q.full():
|
|
251
|
+
q.get()
|
|
252
|
+
|
|
253
|
+
q.put(real_data)
|
|
254
|
+
|
|
255
|
+
# 信号数据转换
|
|
256
|
+
def __impedance_transfer(self, packet: ImpedancePacket):
|
|
257
|
+
channels = {}
|
|
258
|
+
impedance = {}
|
|
259
|
+
#按分区拆分数据格式
|
|
260
|
+
for k in self._channel_spilt.keys():
|
|
261
|
+
logger.trace(f'分区{k}的阻抗数据')
|
|
262
|
+
c, d, p = self.__packet_filter(packet, self._channel_spilt[k], type='impedance')
|
|
263
|
+
if c is not None:
|
|
264
|
+
channels[k] = c
|
|
265
|
+
impedance[k] = d
|
|
266
|
+
packet.channels = channels
|
|
267
|
+
packet.impedance = impedance
|
|
338
268
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
269
|
+
return packet
|
|
270
|
+
|
|
271
|
+
# 信号数据转换
|
|
272
|
+
def __signal_transfer(self, packet: RscPacket):
|
|
273
|
+
channels = {}
|
|
274
|
+
eeg = {}
|
|
275
|
+
eeg_p = {}
|
|
276
|
+
#按分区拆分数据格式
|
|
277
|
+
for k in self._channel_spilt.keys():
|
|
278
|
+
logger.trace(f'分区{k}, {self._channel_spilt[k]}')
|
|
279
|
+
c, d, p = self.__packet_filter(packet, self._channel_spilt[k])
|
|
280
|
+
if c is not None:
|
|
281
|
+
channels[k] = c
|
|
282
|
+
eeg[k] = d
|
|
283
|
+
if p is not None:
|
|
284
|
+
eeg_p[k] = p
|
|
285
|
+
packet.channels = channels
|
|
286
|
+
packet.eeg = eeg
|
|
287
|
+
#物理值
|
|
288
|
+
if packet.eeg_p is not None:
|
|
289
|
+
packet.eeg_p = eeg_p
|
|
345
290
|
|
|
346
|
-
return
|
|
291
|
+
return packet
|
|
347
292
|
|
|
348
|
-
def
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
293
|
+
def __packet_filter(self, packet, channel_filter=None, type:Literal['signal','impedance']='signal'):
|
|
294
|
+
# 参数检查
|
|
295
|
+
if packet is None or channel_filter is None:
|
|
296
|
+
logger.warning("空数据,忽略")
|
|
297
|
+
return None, None, None
|
|
353
298
|
|
|
354
|
-
|
|
355
|
-
def gen_set_acquirement_param(self) -> bytes:
|
|
299
|
+
channel_pos = get_sorted_indices_basic(packet.channels, channel_filter)
|
|
356
300
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
301
|
+
if channel_pos is None or len(channel_pos) == 0 :
|
|
302
|
+
logger.trace(f"没有指定分区的通道,跳过")
|
|
303
|
+
return None, None, None
|
|
304
|
+
|
|
305
|
+
# 分区数据包写入
|
|
306
|
+
|
|
307
|
+
# 保留本分区的通道和数据
|
|
308
|
+
channels = [packet.channels[p] for p in channel_pos]
|
|
309
|
+
|
|
310
|
+
#阻抗数据
|
|
311
|
+
if type == 'impedance':
|
|
312
|
+
impedance = [packet.impedance[p] for p in channel_pos]
|
|
313
|
+
return [self._channel_mapping.get(channel_id, []) for channel_id in channels], impedance, None
|
|
314
|
+
|
|
315
|
+
# 信号数据
|
|
316
|
+
eeg = [packet.eeg[p] for p in channel_pos]
|
|
317
|
+
eeg_p = None
|
|
318
|
+
if packet.eeg_p is not None:
|
|
319
|
+
eeg_p = [packet.eeg_p[p] for p in channel_pos]
|
|
320
|
+
|
|
321
|
+
return [self._channel_mapping.get(channel_id, []) for channel_id in channels], eeg, eeg_p
|
|
368
322
|
|
|
369
|
-
return body
|
|
370
|
-
|
|
371
|
-
def __str__(self):
|
|
372
|
-
return f'''
|
|
373
|
-
Device:
|
|
374
|
-
Name: {self.device_no},
|
|
375
|
-
Type: {hex(self.device_type) if self.device_type else None},
|
|
376
|
-
ID: {self.device_id if self.device_id else None},
|
|
377
|
-
Software: {self.software_version},
|
|
378
|
-
Hardware: {self.hardware_version},
|
|
379
|
-
Connect Time: {self.connect_time},
|
|
380
|
-
Current Time: {self.current_time},
|
|
381
|
-
Voltage: {str(self.voltage) + "mV" if self.voltage else None},
|
|
382
|
-
Battery Remain: {str(self.battery_remain)+ "%" if self.battery_remain else None},
|
|
383
|
-
Battery Total: {str(self.battery_total) + "%" if self.battery_total else None}
|
|
384
|
-
'''
|
|
385
323
|
|
|
386
324
|
|