qlsdk2 0.6.0__py3-none-any.whl → 0.6.0a2__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/rsc/device/base.py CHANGED
@@ -5,30 +5,13 @@ from typing import Any, Dict, Literal
5
5
 
6
6
  from loguru import logger
7
7
  import numpy as np
8
- from qlsdk.core.entity import RscPacket, ImpedancePacket
8
+ from qlsdk.core.entity import RscPacket
9
9
  from qlsdk.core.utils import to_bytes
10
10
  from qlsdk.rsc.interface import IDevice, IParser
11
- from qlsdk.rsc.command import SetImpedanceParamCommand, StartImpedanceCommand, StopImpedanceCommand, StartStimulationCommand, StopStimulationCommand, SetAcquisitionParamCommand, StartAcquisitionCommand, StopAcquisitionCommand
12
- from qlsdk.rsc.paradigm import StimulationParadigm
11
+ from qlsdk.rsc.command import StartImpedanceCommand, StopImpedanceCommand, StartStimulationCommand, StopStimulationCommand, SetAcquisitionParamCommand, StartAcquisitionCommand, StopAcquisitionCommand
13
12
  from qlsdk.rsc.parser.base import TcpMessageParser
14
13
 
15
-
16
- def intersection_positions(A, B):
17
- setB = set(B)
18
- seen = set()
19
- return [idx for idx, elem in enumerate(A)
20
- if elem in setB and elem not in seen and not seen.add(elem)]
21
-
22
14
  class QLBaseDevice(IDevice):
23
-
24
- __TRIGGER_MAPPING = {
25
- 0x3E8: "Start of stimulation",
26
- 0x3E9: "End of stimulation",
27
- 0x3EA: "Ascending end of stimulation",
28
- 0x3EB: "Descending start of stimulation ",
29
- 0x3EC: "刺激参数有误",
30
- 0x3ED: "End of stimulation (by force)",
31
- }
32
15
  def __init__(self, socket):
33
16
  self.socket = socket
34
17
 
@@ -78,11 +61,52 @@ class QLBaseDevice(IDevice):
78
61
  "channels": [],
79
62
  }
80
63
 
81
- self._stim_param = None
82
-
83
- self._impedance_channels = []
64
+ self._stim_param = {
65
+ "stim_type": 0, # 刺激类型:0-所有通道参数相同, 1: 通道参数不同
66
+ "channels": [],
67
+ "param": [{
68
+ "channel_id": 0, #通道号 从0开始 -- 必填
69
+ "waveform": 3, #波形类型:0-直流,1-交流 2-方波 3-脉冲 -- 必填
70
+ "current": 1, #电流强度(mA) -- 必填
71
+ "duration": 30, #平稳阶段持续时间(s) -- 必填
72
+ "ramp_up": 5, #上升时间(s) 默认0
73
+ "ramp_down": 5, #下降时间(s) 默认0
74
+ "frequency": 500, #频率(Hz) -- 非直流必填
75
+ "phase_position": 0, #相位 -- 默认0
76
+ "duration_delay": "0", #延迟启动时间(s) -- 默认0
77
+ "pulse_width": 0, #脉冲宽度(us) -- 仅脉冲类型电流有效, 默认100us
78
+ },
79
+ {
80
+ "channel_id": 1, #通道号 从0开始 -- 必填
81
+ "waveform": 3, #波形类型:0-直流,1-交流 2-方波 3-脉冲 -- 必填
82
+ "current": 1, #电流强度(mA) -- 必填
83
+ "duration": 30, #平稳阶段持续时间(s) -- 必填
84
+ "ramp_up": 5, #上升时间(s) 默认0
85
+ "ramp_down": 5, #下降时间(s) 默认0
86
+ "frequency": 500, #频率(Hz) -- 非直流必填
87
+ "phase_position": 0, #相位 -- 默认0
88
+ "duration_delay": "0", #延迟启动时间(s) -- 默认0
89
+ "pulse_width": 0, #脉冲宽度(us) -- 仅脉冲类型电流有效, 默认100us
90
+ }
91
+ ]
92
+ }
84
93
 
85
- self.stim_paradigm: StimulationParadigm = None
94
+ self.stim_paradigm = None
95
+
96
+ signal_info = {
97
+ "param" : None,
98
+ "start_time" : None,
99
+ "finished_time" : None,
100
+ "packet_total" : None,
101
+ "last_packet_time" : None,
102
+ "state" : 0
103
+ }
104
+ stim_info = {
105
+
106
+ }
107
+ Impedance_info = {
108
+
109
+ }
86
110
  # 信号采集状态
87
111
  # 信号数据包总数(一个信号采集周期内)
88
112
  # 信号采集参数
@@ -103,9 +127,6 @@ class QLBaseDevice(IDevice):
103
127
  self.storage_enable = True
104
128
  self._listening = False
105
129
  # self.ready()
106
- self._signal_cache: Queue = None
107
- self._recording = False
108
-
109
130
 
110
131
  def parser(self) -> IParser:
111
132
  return self._parser
@@ -115,54 +136,24 @@ class QLBaseDevice(IDevice):
115
136
 
116
137
  # 数据包处理
117
138
  def produce(self, body: bytes, type:Literal['signal', 'impedance']="signal"):
118
- if body is None: return
119
-
120
- if type == "signal":
121
- self._produce_signal(body)
122
- elif type == "impedance":
123
- self._produce_impedance(body)
124
-
125
- def _produce_impedance(self, body: bytes):
126
- # 分发阻抗数据包给订阅者
127
- if len(self._impedance_consumer) > 0:
128
- packet = self._impedance_wrapper(body)
129
- for topic, q in self._impedance_consumer.items():
130
- try:
131
- # 队列满了就丢弃最早的数据
132
- if q.full():
133
- q.get()
134
- q.put(packet, timeout=1)
135
- except Exception as e:
136
- logger.error(f"impedance data put to queue exception: {str(e)}")
137
-
138
- def _produce_signal(self, body: bytes):
139
+ if body is None: return
139
140
 
140
141
  # 处理信号数据
141
- data = self._signal_wrapper(body)
142
+ data = self._signal_wrapper(body)
142
143
  # logger.debug("pkg_id: {}, eeg len: {}".format(data.pkg_id, len(data.eeg)))
143
144
  #
144
145
  trigger_positions = [index for index, value in enumerate(data.trigger) if value != 0]
145
146
  if len(trigger_positions) > 0:
146
147
  # logger.debug(f"Trigger触发点位置: {trigger_positions}, 触发点时间戳: {[data.time_stamp + int(pos * 1000 / data.sample_rate) for pos in trigger_positions]}")
147
148
  for pos in trigger_positions:
148
- self.trigger(self.trigger_info(data.trigger[pos]))
149
+ self.trigger(data.trigger[pos])
149
150
  # 存储
150
- if self.storage_enable:
151
- # 确保记录线程启动
152
- if self._recording is False:
153
- self._start_recording()
154
-
155
- # 写入文件的缓存队列
156
- if self._signal_cache is None:
157
- self._signal_cache = Queue(256 * 1024 * 1024) # 256MB缓存
158
- tmp = data.copy()
159
- self._signal_cache.put(tmp)
160
-
151
+ if self.storage_enable:
152
+ self._write_signal(data)
153
+
161
154
  if len(self.signal_consumers) > 0 :
162
- logger.trace(f"dg eeg: {data.eeg}")
163
155
  # 信号数字值转物理值
164
156
  data.eeg = self.eeg2phy(np.array(data.eeg))
165
- logger.trace(f"ph eeg: {data.eeg}")
166
157
 
167
158
  # 发送数据包到订阅者
168
159
  for q in list(self.signal_consumers.values()):
@@ -170,51 +161,20 @@ class QLBaseDevice(IDevice):
170
161
  if q.full():
171
162
  q.get()
172
163
 
173
- q.put(data)
174
-
175
- def _impedance_wrapper(self, body: bytes):
176
- packet = ImpedancePacket().transfer(body)
177
- if self._impedance_channels is not None and len(self._impedance_channels) > 0:
178
- # 只保留设置的阻抗通道
179
- channel_pos = intersection_positions(packet.channels, self._impedance_channels)
180
- packet.impedance = [packet.impedance[i] for i in channel_pos]
181
- packet.channels = [packet.channels[i] for i in channel_pos]
182
-
183
- return packet
164
+ q.put(data)
184
165
 
185
166
  # 信号数据转换
186
167
  def _signal_wrapper(self, body: bytes):
187
168
  return RscPacket().transfer(body)
188
169
 
189
- def _write_signal(self):
170
+ def _write_signal(self, data: RscPacket):
190
171
  # 文件写入到edf
191
172
  if self._edf_handler is None:
192
173
  logger.debug("Initializing EDF handler for data storage")
193
174
  self.init_edf_handler()
194
175
 
195
- while self._recording:
196
- data = self._signal_cache.get()
176
+ if self._edf_handler:
197
177
  self._edf_handler.write(data)
198
- if data is None:
199
- break
200
-
201
- self._recording = False
202
- def _start_recording(self):
203
- if self.storage_enable is False:
204
- logger.trace("Storage is disabled, will not start recording")
205
- return
206
-
207
- if self._signal_cache is None:
208
- self._signal_cache = Queue(256 * 1024 * 1024) # 256MB缓存
209
-
210
- try:
211
- self._recording = True
212
- t = Thread(target=self._write_signal, daemon=True)
213
- t.start()
214
- logger.info(f"开启记录")
215
- except Exception as e:
216
- logger.error(f"开启记录失败: {str(e)}")
217
- return
218
178
 
219
179
  def start_listening(self):
220
180
 
@@ -254,12 +214,6 @@ class QLBaseDevice(IDevice):
254
214
  def device_type(self) -> int:
255
215
  return self._device_type
256
216
 
257
- def set_impedance_channels(self, channels):
258
- self._impedance_channels = channels
259
-
260
- def get_impedance_channels(self):
261
- return self._impedance_channels
262
-
263
217
  def start_message_parser(self) -> None:
264
218
  self._parser = TcpMessageParser(self)
265
219
  self._parser.start()
@@ -331,7 +285,7 @@ class QLBaseDevice(IDevice):
331
285
  @property
332
286
  def edf_handler(self):
333
287
  if not self.storage_enable:
334
- logger.info("已关闭文件记录,不会生成edf/bdf文件")
288
+ logger.warning("EDF storage is disabled, no edf handler available")
335
289
  return None
336
290
 
337
291
  if self._edf_handler is None:
@@ -365,12 +319,8 @@ class QLBaseDevice(IDevice):
365
319
  return self._impedance_consumer
366
320
 
367
321
  # 设置记录文件路径
368
- def set_storage_path(self, dir: str):
369
- import os
370
-
371
- abs_path = os.path.abspath(dir)
372
- os.makedirs(abs_path, exist_ok=True)
373
- self._storage_path = abs_path
322
+ def set_storage_path(self, path):
323
+ self._storage_path = path
374
324
 
375
325
  # 设置记录文件名称前缀
376
326
  def set_file_prefix(self, prefix):
@@ -385,27 +335,23 @@ class QLBaseDevice(IDevice):
385
335
  self.stim_paradigm = param
386
336
 
387
337
  # 设置采集参数
388
- def set_acq_param(self, channels, sample_rate:Literal[188, 375, 563, 750, 1125, 2250, 4500] = 500, sample_range:Literal[250, 500, 1000, 2000, 4000, 8000, 16000, 32000] = 188):
338
+ def set_acq_param(self, channels, sample_rate = 500, sample_range = 188):
389
339
  self._acq_param["channels"] = channels
390
340
  self._acq_param["sample_rate"] = sample_rate
391
341
  self._acq_param["sample_range"] = sample_range
392
342
  self._acq_channels = channels
393
343
  self._sample_rate = sample_rate
394
344
  self._sample_range = sample_range
345
+
346
+ # 通用配置-TODO
347
+ def set_config(self, key:str, val: str):
348
+ pass
395
349
 
396
350
  def start_impedance(self):
397
351
  logger.info(f"[设备-{self.device_no}]启动阻抗测量")
398
- # 设置数据采集参数
399
- # set_param_msg = SetImpedanceParamCommand.build(self).pack()
400
- device_id = bytes.fromhex(self.device_id)[::-1].hex() if self.device_id else '00000000'
401
- set_param_msg = bytes.fromhex(f'5aa50239{device_id}3f0000001104ffffffffffffffff000000000000000000000000000000000000000000000000e8030000fa00000010000164000000745c5aa50239390045243a00000012040000000000000000000000000000000000000000000000000000000000000000000001000000000000004c2a')
402
- logger.debug(f"set_param_msg message is {set_param_msg.hex()}")
403
- self.socket.sendall(set_param_msg)
404
- sleep(0.5)
405
-
406
- impedance_start_msg = StartImpedanceCommand.build(self).pack()
407
- logger.debug(f"start_impedance message is {impedance_start_msg.hex()}")
408
- self.socket.sendall(impedance_start_msg)
352
+ msg = StartImpedanceCommand.build(self).pack()
353
+ logger.trace(f"start_impedance message is {msg.hex()}")
354
+ self.socket.sendall(msg)
409
355
 
410
356
  def stop_impedance(self):
411
357
  logger.info(f"[设备{self.device_no}]停止阻抗测量")
@@ -421,11 +367,8 @@ class QLBaseDevice(IDevice):
421
367
  msg = StartStimulationCommand.build(self).pack()
422
368
  logger.trace(f"start_stimulation message is {msg.hex()}")
423
369
  self.socket.sendall(msg)
424
- # t = Thread(target=self._stop_stimulation_trigger, args=(self.stim_paradigm.duration,), daemon=True)
425
- # t.start()
426
-
427
- def get_stim_param(self) -> bytes:
428
- return self.stim_paradigm.to_bytes()
370
+ t = Thread(target=self._stop_stimulation_trigger, args=(self.stim_paradigm.duration,), daemon=True)
371
+ t.start()
429
372
 
430
373
  def _stop_stimulation_trigger(self, duration):
431
374
  delay = duration
@@ -433,22 +376,23 @@ class QLBaseDevice(IDevice):
433
376
  sleep(1)
434
377
  delay -= 1
435
378
  logger.debug(f"_stop_stimulation_trigger duration: {duration}")
436
- if self.edf_handler:
437
- self.edf_handler.trigger("stimulation should be stopped")
379
+ if self._edf_handler:
380
+ self._edf_handler.trigger("stimulation should be stopped")
438
381
  else:
439
382
  logger.warning("stop stim trigger fail. no edf writer alive")
440
383
 
441
384
  def stop_stimulation(self):
442
385
  logger.info(f"[设备-{self.device_no}]停止电刺激")
443
- msg = StopStimulationCommand.build(self).pack()
386
+ msg = StopStimulationCommand.pack()
444
387
  logger.trace(f"stop_stimulation message is {msg.hex()}")
445
388
  self.socket.sendall(msg)
446
389
 
447
390
  # 启动采集
448
- def start_acquisition(self):
391
+ def start_acquisition(self, recording = True):
449
392
  logger.info(f"[设备-{self.device_no}]启动信号采集")
450
- # 记录准备
451
- self._start_recording()
393
+ self._recording = recording
394
+ # 初始化EDF处理器
395
+ self.init_edf_handler()
452
396
  # 设置数据采集参数
453
397
  param_bytes = SetAcquisitionParamCommand.build(self).pack()
454
398
  # 启动数据采集
@@ -463,10 +407,9 @@ class QLBaseDevice(IDevice):
463
407
  msg = StopAcquisitionCommand.build(self).pack()
464
408
  logger.trace(f"stop_acquisition message is {msg.hex()}")
465
409
  self.socket.sendall(msg)
466
- # 结束标识
467
- if self._signal_cache:
410
+ if self._edf_handler:
468
411
  # 发送结束标识
469
- self._signal_cache.put(None)
412
+ self.edf_handler.write(None)
470
413
 
471
414
  '''
472
415
  订阅数据
@@ -484,7 +427,7 @@ class QLBaseDevice(IDevice):
484
427
 
485
428
  # 数据队列
486
429
  if q is None:
487
- q = Queue(maxsize=100 * 1024 * 1024)
430
+ q = Queue(maxsize=1000)
488
431
 
489
432
  # 订阅生理电信号数据
490
433
  if type == "signal":
@@ -504,12 +447,11 @@ class QLBaseDevice(IDevice):
504
447
  return topic, q
505
448
 
506
449
  def trigger(self, desc):
507
- if self.edf_handler:
450
+ if self._edf_handler:
508
451
  self.edf_handler.trigger(desc)
509
452
  else:
510
- logger.info("已关闭文件记录,不会记录trigger信息")
453
+ logger.warning("没有开启文件记录时,无法记录trigger信息")
511
454
 
512
- # 设置信号采集参数
513
455
  def gen_set_acquirement_param(self) -> bytes:
514
456
 
515
457
  body = to_bytes(self.acq_channels)
@@ -520,19 +462,7 @@ class QLBaseDevice(IDevice):
520
462
  body += bytes.fromhex('00')
521
463
 
522
464
  return body
523
- # 设置阻抗测量参数
524
- def gen_set_impedance_param(self) -> bytes:
525
-
526
- # 仅通道生效 32字节,其他不生效-272字节,实际73字节
527
- body = to_bytes(self._impedance_channels)
528
- # 100 bytes
529
- # body += bytes.fromhex('00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000')
530
- # # 100 bytes
531
- # body += bytes.fromhex('00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000')
532
- # 73 bytes
533
- body += bytes.fromhex('00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000')
534
-
535
- return bytes.fromhex('ffffffffffffffff000000000000000000000000000000000000000000000000e8030000fa00000010000164000000745c5aa50239390045243a0000001204000000000000000000000000000000000000000000000000000000000000000000000100000000000000')
465
+
536
466
  def disconnect(self):
537
467
  logger.info(f"[断开设备-{self.device_no}]的连接...")
538
468
  self._listening = False
@@ -546,12 +476,6 @@ class QLBaseDevice(IDevice):
546
476
 
547
477
  # 关闭解析器
548
478
  self._parser.stop()
549
-
550
- def enable_storage(self, enable: bool = True):
551
- self.storage_enable = enable
552
-
553
- def trigger_info(self, code: int) -> str:
554
- return QLBaseDevice.__TRIGGER_MAPPING.get(code, hex(code))
555
479
 
556
480
  def __str__(self):
557
481
  return f'''
@@ -115,7 +115,11 @@ class C16RS(QLBaseDevice):
115
115
  self._edf_handler.set_device_no(self.device_no)
116
116
  self._edf_handler.set_storage_path(self._storage_path)
117
117
  self._edf_handler.set_file_prefix(self._file_prefix if self._file_prefix else 'C16R')
118
-
118
+
119
+ # 设置刺激参数
120
+ def set_stim_param(self, param):
121
+ self.stim_paradigm = param
122
+
119
123
  # 设置采集参数
120
124
  def set_acq_param(self, channels, sample_rate = 500, sample_range = 188):
121
125
  # 保存原始通道参数