qlsdk2 0.6.0a13__py3-none-any.whl → 0.7.0a1__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.
@@ -54,6 +54,22 @@ class RscPacket(Packet):
54
54
  logger.trace(packet)
55
55
 
56
56
  return packet
57
+
58
+ def copy(self) -> 'RscPacket':
59
+ packet = RscPacket()
60
+ packet.time_stamp = self.time_stamp
61
+ packet.pkg_id = self.pkg_id
62
+ packet.result = self.result
63
+ packet.channels = self.channels
64
+ packet.origin_sample_rate = self.origin_sample_rate
65
+ packet.sample_rate = self.sample_rate
66
+ packet.sample_num = self.sample_num
67
+ packet.resolution = self.resolution
68
+ packet.filter = self.filter
69
+ packet.data_len = self.data_len
70
+ packet.trigger = self.trigger
71
+ packet.eeg = self.eeg
72
+ return packet
57
73
 
58
74
  def __str__(self):
59
75
  return f"""[
qlsdk/persist/rsc_edf.py CHANGED
@@ -131,7 +131,7 @@ class EDFStreamWriter(Thread):
131
131
  logger.trace(f"sf: {self.sample_frequency}, pm: {self.physical_max}, pn: {self.physical_min}, dm: {self.digital_max}, dn: {self.digital_min}")
132
132
  for ch in range(self._n_channels):
133
133
  signal_headers.append({
134
- "label": f'channels {self._channels[ch]}',
134
+ "label": f'channel {self._channels[ch]}',
135
135
  "dimension": 'uV',
136
136
  "sample_frequency": self.sample_frequency,
137
137
  "physical_min": self.physical_min,
@@ -296,6 +296,8 @@ class RscEDFHandler(object):
296
296
  logger.info(f"收到结束信号,即将停止写入数据:{self.file_name}")
297
297
  self._edf_writer_thread.stop_recording()
298
298
  return
299
+
300
+ logger.debug(f"packet: {packet}")
299
301
 
300
302
  with self._lock:
301
303
  if self.channels is None:
@@ -6,7 +6,6 @@ from loguru import logger
6
6
 
7
7
 
8
8
  from qlsdk.core.crc import crc16
9
- from qlsdk.core.entity import RscPacket, ImpedancePacket
10
9
  from qlsdk.core.utils import to_channels, to_bytes
11
10
  from qlsdk.rsc.interface import IDevice
12
11
 
@@ -57,6 +56,7 @@ class DeviceCommand(abc.ABC):
57
56
  if device_type > 0xFF:
58
57
  b_device_type = int.to_bytes(device_type, 2, 'little')
59
58
 
59
+ packet_len = DeviceCommand.HEADER_LEN + body_len + 2
60
60
  if b_device_type is None:
61
61
  return (
62
62
  DeviceCommand.HEADER_PREFIX
@@ -69,10 +69,11 @@ class DeviceCommand(abc.ABC):
69
69
  else:
70
70
  return (
71
71
  DeviceCommand.HEADER_PREFIX
72
- + b_device_type[0].to_bytes(1, 'little') # pkgType
73
72
  + b_device_type[1].to_bytes(1, 'little')
74
- + device_id.to_bytes(4, 'little')
75
- + (DeviceCommand.HEADER_LEN + body_len + 2).to_bytes(4, 'little') # +1 for checksum
73
+ + b_device_type[0].to_bytes(1, 'little') # pkgType
74
+ # + device_id.to_bytes(4, 'little')
75
+ + device_id
76
+ + packet_len.to_bytes(4, 'little') # +1 for checksum
76
77
  + self.cmd_code.to_bytes(2, 'little')
77
78
  )
78
79
 
@@ -5,4 +5,8 @@ from .device_factory import DeviceFactory
5
5
  from .c64_rs import C64RS
6
6
  from .c16_rs import C16RS
7
7
  from .arskindling import ARSKindling
8
- from .c256_rs import C256RS
8
+ from .c256_rs import C256RS
9
+ from .c8r import C8R
10
+ from .c16r import C16R
11
+ from .c21r import C21R
12
+ from .c64s1 import C64S1
@@ -1,6 +1,7 @@
1
1
  from multiprocessing import Queue
2
2
  from typing import Literal
3
3
  from loguru import logger
4
+ from qlsdk.core.entity import ImpedancePacket, RscPacket
4
5
  from qlsdk.persist import ARSKindlingEDFHandler
5
6
  from qlsdk.rsc.interface import IDevice
6
7
  from qlsdk.rsc.command import *
@@ -238,7 +239,7 @@ class ARSKindling(QLBaseDevice):
238
239
  # 写入文件的缓存队列
239
240
  if self._signal_cache is None:
240
241
  self._signal_cache = Queue(1000000) # 缓冲队列
241
- tmp = real_data
242
+ tmp = real_data.copy()
242
243
  self._signal_cache.put(tmp)
243
244
 
244
245
  if len(self.signal_consumers) > 0 :
qlsdk/rsc/device/base.py CHANGED
@@ -139,6 +139,10 @@ class QLBaseDevice(IDevice):
139
139
 
140
140
  # 处理信号数据
141
141
  data = self._signal_wrapper(body)
142
+
143
+ if data is None:
144
+ logger.warning("signal data is None")
145
+ return
142
146
  # logger.debug("pkg_id: {}, eeg len: {}".format(data.pkg_id, len(data.eeg)))
143
147
  #
144
148
  trigger_positions = [index for index, value in enumerate(data.trigger) if value != 0]
@@ -155,7 +159,7 @@ class QLBaseDevice(IDevice):
155
159
  # 写入文件的缓存队列
156
160
  if self._signal_cache is None:
157
161
  self._signal_cache = Queue(256 * 1024 * 1024) # 256MB缓存
158
- tmp = data
162
+ tmp = data.copy()
159
163
  self._signal_cache.put(tmp)
160
164
 
161
165
  if len(self.signal_consumers) > 0 :
@@ -173,10 +177,11 @@ class QLBaseDevice(IDevice):
173
177
  q.put(data)
174
178
 
175
179
  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:
180
+ packet = ImpedancePacket().transfer(body)
181
+ impedance_channels = self.get_impedance_channels()
182
+ if impedance_channels is not None and len(impedance_channels) > 0:
178
183
  # 只保留设置的阻抗通道
179
- channel_pos = intersection_positions(packet.channels, self._impedance_channels)
184
+ channel_pos = intersection_positions(packet.channels, impedance_channels)
180
185
  packet.impedance = [packet.impedance[i] for i in channel_pos]
181
186
  packet.channels = [packet.channels[i] for i in channel_pos]
182
187
 
@@ -258,6 +263,8 @@ class QLBaseDevice(IDevice):
258
263
  self._impedance_channels = channels
259
264
 
260
265
  def get_impedance_channels(self):
266
+ if self._impedance_channels is None or len(self._impedance_channels) == 0:
267
+ self._impedance_channels = [i for i in range(1, 63)]
261
268
  return self._impedance_channels
262
269
 
263
270
  def start_message_parser(self) -> None:
@@ -396,15 +403,13 @@ class QLBaseDevice(IDevice):
396
403
  def start_impedance(self):
397
404
  logger.info(f"[设备-{self.device_no}]启动阻抗测量")
398
405
  # 设置数据采集参数
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()}")
406
+ set_param_msg = SetImpedanceParamCommand.build(self).pack()
407
+ # logger.debug(f"set_param_msg message is {set_param_msg.hex()}")
403
408
  self.socket.sendall(set_param_msg)
404
409
  sleep(0.5)
405
410
 
406
411
  impedance_start_msg = StartImpedanceCommand.build(self).pack()
407
- logger.debug(f"start_impedance message is {impedance_start_msg.hex()}")
412
+ # logger.debug(f"start_impedance message is {impedance_start_msg.hex()}")
408
413
  self.socket.sendall(impedance_start_msg)
409
414
 
410
415
  def stop_impedance(self):
@@ -524,15 +529,17 @@ class QLBaseDevice(IDevice):
524
529
  def gen_set_impedance_param(self) -> bytes:
525
530
 
526
531
  # 仅通道生效 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')
532
+ # body = to_bytes(self._impedance_channels)
533
+ # 阻抗参数设置当前是固定长度3f000000, 通道全部指定ffffffffffffffff
534
+ body = bytes.fromhex('ffffffffffffffff')
535
+ # 测量类型-正弦波 不生效 放15字节的0
536
+ body += bytes.fromhex('000000000000000000000000000000')
537
+ # 测量频率-1000Hz 不生效
538
+ # freq = 1000
539
+ # body += freq.to_bytes(4, byteorder='little')# 24 bytes
540
+ body += bytes.fromhex('000000000000000000000000000000000000000000000000')
541
+
542
+ return body
536
543
  def disconnect(self):
537
544
  logger.info(f"[断开设备-{self.device_no}]的连接...")
538
545
  self._listening = False
@@ -5,6 +5,7 @@ from time import sleep, time_ns
5
5
  from typing import Any, Dict, Literal
6
6
 
7
7
  from loguru import logger
8
+ from qlsdk.core.entity import RscPacket
8
9
  from qlsdk.persist import RscEDFHandler
9
10
  from qlsdk.rsc.device.device_factory import DeviceFactory
10
11
  from qlsdk.rsc.interface import IDevice
@@ -17,7 +18,7 @@ from qlsdk.rsc.device.base import QLBaseDevice
17
18
  '''
18
19
  class C16RS(QLBaseDevice):
19
20
 
20
- device_type = 0x339 # C16RS设备类型标识符
21
+ device_type = 0x439 # C16RS设备类型标识符 0x3904
21
22
 
22
23
  def __init__(self, socket):
23
24
 
@@ -0,0 +1,203 @@
1
+ from loguru import logger
2
+ from qlsdk.core.entity import ImpedancePacket, RscPacket
3
+ from qlsdk.persist import RscEDFHandler
4
+ from qlsdk.rsc.device.base import QLBaseDevice, intersection_positions
5
+ from qlsdk.rsc.device.device_factory import DeviceFactory
6
+ from qlsdk.rsc.interface import IDevice
7
+ from qlsdk.rsc.device.base import QLBaseDevice
8
+
9
+ class C16R(QLBaseDevice):
10
+
11
+ device_type = 0x339 # C16R设备类型标识符 0x3903
12
+
13
+ def __init__(self, socket):
14
+ super().__init__(socket)
15
+
16
+ # 存储通道反向映射位置值
17
+ self._reverse_ch_pos = None
18
+
19
+ # 存储通道反向映射位置值
20
+ self._impedance_ch_pos = None
21
+ self._impedance_channels_origin = None
22
+
23
+ self.channel_name_mapping = {
24
+ "FP1": 1,
25
+ "FP2": 2,
26
+ "F7": 3,
27
+ "F8": 4,
28
+ "P3": 5,
29
+ "P4": 6,
30
+ "O1": 7,
31
+ "O2": 8,
32
+ "CZ": 9,
33
+ "A1": 10,
34
+ "A2": 11,
35
+ }
36
+
37
+ self.channel_mapping = {
38
+ "1": 59,
39
+ "2": 60,
40
+ "3": 53,
41
+ "4": 50,
42
+ "5": 63,
43
+ "6": 24,
44
+ "7": 51,
45
+ "8": 64,
46
+ "9": 49,
47
+ "10": 14,
48
+ "11": 10,
49
+ }
50
+
51
+ self.channel_display_mapping = {
52
+ 59: 1,
53
+ 60: 2,
54
+ 53: 3,
55
+ 50: 4,
56
+ 63: 5,
57
+ 24: 6,
58
+ 51: 7,
59
+ 64: 8,
60
+ 49: 9,
61
+ 14: 10,
62
+ 10: 11,
63
+ }
64
+
65
+ @classmethod
66
+ def from_parent(cls, parent:IDevice) -> IDevice:
67
+ rlt = cls(parent.socket)
68
+ rlt.device_id = parent.device_id
69
+ rlt._device_no = parent.device_no
70
+ return rlt
71
+
72
+ def init_edf_handler(self):
73
+ self._edf_handler = RscEDFHandler(self.sample_rate, self.sample_range * 1000 , - self.sample_range * 1000, self.resolution)
74
+ self._edf_handler.set_device_type(self.device_type)
75
+ self._edf_handler.set_device_no(self.device_no)
76
+ self._edf_handler.set_storage_path(self._storage_path)
77
+ self._edf_handler.set_file_prefix(self._file_prefix if self._file_prefix else 'C8R')
78
+ logger.debug(f"EDF Handler initialized")
79
+ # 设置采集参数
80
+ def set_acq_param(self, channels:list, sample_rate: int = 500, sample_range:int = 188):
81
+ # 通道合法性校验
82
+ self._channel_validate(channels)
83
+ # 保存原始通道参数
84
+ self._acq_param["original_channels"] = channels
85
+
86
+ # 名称转换为数字通道
87
+ channels = [self.channel_name_mapping.get(str(i).upper(), i) for i in channels]
88
+
89
+ # 根据映射关系做通道转换-没有映射的默认到第一个通道
90
+ # 先设置不存在的通道为-1,再把-1替换为第一个通道,避免第一个通道也不合法的情况
91
+ channels = [self.channel_mapping.get(str(i), -1) for i in channels]
92
+ channels = [i if i != -1 else channels[0] for i in channels]
93
+
94
+ # 更新采集参数
95
+ self._acq_param["channels"] = channels
96
+ self._acq_param["sample_rate"] = sample_rate
97
+ self._acq_param["sample_range"] = sample_range
98
+ self._acq_channels = channels
99
+ self._sample_rate = sample_rate
100
+ self._sample_range = sample_range
101
+
102
+ logger.debug(f"C16RS: set_acq_param: {self._acq_param}")
103
+
104
+ # 参数改变后,重置通道位置映射
105
+ self._reverse_ch_pos = None
106
+
107
+ @property
108
+ def acq_channels(self):
109
+ if self._acq_channels is None:
110
+ # 初始化通道参数为1-11
111
+ self.set_acq_param([i for i in range(1, 12)])
112
+ return self._acq_channels
113
+
114
+ def set_impedance_channels(self, channels:list):
115
+ # 通道合法性校验
116
+ self._channel_validate(channels)
117
+ # 保存原始通道参数
118
+ self._impedance_channels_origin = channels
119
+
120
+ # 名称转换为数字通道
121
+ channels = [self.channel_name_mapping.get(str(i).upper(), i) for i in channels]
122
+
123
+ # 根据映射关系做通道转换-没有映射的默认到第一个通道
124
+ # 先设置不存在的通道为-1,再把-1替换为第一个通道,避免第一个通道也不合法的情况
125
+ channels = [self.channel_mapping.get(str(i), -1) for i in channels]
126
+ channels = [i if i != -1 else channels[0] for i in channels]
127
+ self._impedance_channels = channels
128
+
129
+ # 初始化位置映射
130
+ self._impedance_ch_pos = None
131
+
132
+ def get_impedance_channels(self):
133
+ if self._impedance_channels is None or len(self._impedance_channels) == 0:
134
+ self.set_impedance_channels([i for i in range(1, 12)])
135
+ return self._impedance_channels
136
+
137
+ def _channel_validate(self, channels: list):
138
+ # 名称转换为数字通道
139
+ temp_channels = [self.channel_name_mapping.get(str(i).upper(), i) for i in channels]
140
+
141
+ # 根据映射关系做通道转换-没有映射的默认到第一个通道
142
+ # 先设置不存在的通道为-1,再把-1替换为第一个通道,避免第一个通道也不合法的情况
143
+ temp_channels = [self.channel_mapping.get(str(i), -1) for i in temp_channels]
144
+ for i in range(len(temp_channels)):
145
+ if temp_channels[i] == -1:
146
+ raise ValueError(f"通道参数有误,通道应为1-11的数字或对应名称,如:1、2或FP1、FP2等")
147
+
148
+ def _impedance_wrapper(self, body: bytes):
149
+ packet = ImpedancePacket().transfer(body)
150
+ impedance_channels = self.get_impedance_channels()
151
+ if impedance_channels is not None and len(impedance_channels) > 0:
152
+ # 只保留设置的阻抗通道
153
+ channel_pos = intersection_positions(packet.channels, impedance_channels)
154
+ packet.impedance = [packet.impedance[i] for i in channel_pos]
155
+ packet.channels = [packet.channels[i] for i in channel_pos]
156
+
157
+ # 升级为类变量,减少计算
158
+ if self._impedance_ch_pos is None:
159
+ self._impedance_ch_pos = map_indices(impedance_channels, packet.channels)
160
+
161
+ packet.channels = self._impedance_channels_origin
162
+ packet.impedance = [packet.impedance[i] for i in self._impedance_ch_pos]
163
+
164
+ return packet
165
+
166
+ # 信号数据转换(默认不处理)
167
+ def _signal_wrapper(self, body: bytes):
168
+ if body is None:
169
+ return
170
+ data = RscPacket().transfer(body)
171
+ # 根据映射关系做通道转换-(注意数据和通道的一致性)
172
+ # data.channels = [self.channel_display_mapping.get(i, i) for i in data.channels]
173
+
174
+ # 升级为类变量,减少计算
175
+ if self._reverse_ch_pos is None:
176
+ self._reverse_ch_pos = map_indices(self._acq_param["channels"], data.channels)
177
+
178
+ # 更新通道(数据)顺序和输入一致
179
+ data.channels = self._acq_param["original_channels"]
180
+ data.eeg = [data.eeg[i] for i in self._reverse_ch_pos]
181
+
182
+ return data
183
+
184
+ def start_stimulation(self):
185
+ raise NotImplementedError("Stimulation function is not supported")
186
+
187
+ def map_indices(A, B):
188
+ """
189
+
190
+ 参数:
191
+ A: 源数组(无重复值)
192
+ B: 目标数组(无重复值)
193
+
194
+ 返回:
195
+ C: 与A长度相同的数组,元素为A中对应值在B中的索引(不存在则为-1)
196
+ """
197
+ # 创建B的值到索引的映射字典(O(n)操作)
198
+ b_map = {value: idx for idx, value in enumerate(B)}
199
+
200
+ # 遍历A,获取每个元素在B中的位置(O(m)操作)
201
+ return [b_map.get(a, -1) for a in A]
202
+
203
+ DeviceFactory.register(C16R.device_type, C16R)
@@ -0,0 +1,239 @@
1
+ from loguru import logger
2
+ from qlsdk.core.entity import ImpedancePacket, RscPacket
3
+ from qlsdk.persist import RscEDFHandler
4
+ from qlsdk.rsc.device.base import QLBaseDevice, intersection_positions
5
+ from qlsdk.rsc.device.device_factory import DeviceFactory
6
+ from qlsdk.rsc.interface import IDevice
7
+ from qlsdk.rsc.device.base import QLBaseDevice
8
+
9
+ class C21R(QLBaseDevice):
10
+
11
+ device_type = 0x839 # C21R设备类型标识符 0x3908
12
+
13
+ def __init__(self, socket):
14
+ super().__init__(socket)
15
+
16
+ # 存储通道反向映射位置值
17
+ self._reverse_ch_pos = None
18
+
19
+ # 存储通道反向映射位置值
20
+ self._impedance_ch_pos = None
21
+ self._impedance_channels_origin = None
22
+
23
+ self.channel_name_mapping = {
24
+ "FPz": 1,
25
+ "FP1": 2,
26
+ "FP2": 3,
27
+ "Fz": 4,
28
+ "F3": 5,
29
+ "F7": 6,
30
+ "F4": 7,
31
+ "F8": 8,
32
+ "Cz": 9,
33
+ "C3": 10,
34
+ "T3": 11,
35
+ "C4": 12,
36
+ "T4": 13,
37
+ "Pz": 14,
38
+ "P3": 15,
39
+ "T5": 16,
40
+ "P4": 17,
41
+ "T6": 18,
42
+ "Qz": 19,
43
+ "O1": 20,
44
+ "O2": 21,
45
+ "M1": 22,
46
+ "M2": 23,
47
+ }
48
+
49
+ self.channel_mapping = {
50
+ "1": 48,
51
+ "2": 59,
52
+ "3": 60,
53
+ "4": 16,
54
+ "5": 11,
55
+ "6": 55,
56
+ "7": 57,
57
+ "8": 15,
58
+ "9": 49,
59
+ "10": 53,
60
+ "11": 63,
61
+ "12": 50,
62
+ "13": 24,
63
+ "14": 40,
64
+ "15": 37,
65
+ "16": 34,
66
+ "17": 18,
67
+ "18": 47,
68
+ "19": 38,
69
+ "20": 51,
70
+ "21": 62,
71
+ "22": 14,
72
+ "23": 10
73
+ }
74
+
75
+ self.channel_display_mapping = {
76
+ 48: 1,
77
+ 59: 2,
78
+ 60: 3,
79
+ 16: 4,
80
+ 11: 5,
81
+ 55: 6,
82
+ 57: 7,
83
+ 15: 8,
84
+ 49: 9,
85
+ 53: 10,
86
+ 63: 11,
87
+ 50: 12,
88
+ 24: 13,
89
+ 40: 14,
90
+ 37: 15,
91
+ 34: 16,
92
+ 18: 17,
93
+ 47: 18,
94
+ 38: 19,
95
+ 51: 20,
96
+ 62: 21,
97
+ 14: 22,
98
+ 10: 23
99
+ }
100
+
101
+ @classmethod
102
+ def from_parent(cls, parent:IDevice) -> IDevice:
103
+ rlt = cls(parent.socket)
104
+ rlt.device_id = parent.device_id
105
+ rlt._device_no = parent.device_no
106
+ return rlt
107
+
108
+ def init_edf_handler(self):
109
+ self._edf_handler = RscEDFHandler(self.sample_rate, self.sample_range * 1000 , - self.sample_range * 1000, self.resolution)
110
+ self._edf_handler.set_device_type(self.device_type)
111
+ self._edf_handler.set_device_no(self.device_no)
112
+ self._edf_handler.set_storage_path(self._storage_path)
113
+ self._edf_handler.set_file_prefix(self._file_prefix if self._file_prefix else 'C21R')
114
+ logger.debug(f"EDF Handler initialized")
115
+ # 设置采集参数
116
+ def set_acq_param(self, channels:list, sample_rate: int = 500, sample_range:int = 188):
117
+ # 通道合法性校验
118
+ self._channel_validate(channels)
119
+ # 保存原始通道参数
120
+ self._acq_param["original_channels"] = channels
121
+
122
+ # 名称转换为数字通道
123
+ channels = [self.channel_name_mapping.get(str(i).upper(), i) for i in channels]
124
+
125
+ # 根据映射关系做通道转换-没有映射的默认到第一个通道
126
+ # 先设置不存在的通道为-1,再把-1替换为第一个通道,避免第一个通道也不合法的情况
127
+ channels = [self.channel_mapping.get(str(i), -1) for i in channels]
128
+ channels = [i if i != -1 else channels[0] for i in channels]
129
+
130
+ # 更新采集参数
131
+ self._acq_param["channels"] = channels
132
+ self._acq_param["sample_rate"] = sample_rate
133
+ self._acq_param["sample_range"] = sample_range
134
+ self._acq_channels = channels
135
+ self._sample_rate = sample_rate
136
+ self._sample_range = sample_range
137
+
138
+ logger.debug(f"C16RS: set_acq_param: {self._acq_param}")
139
+
140
+ # 参数改变后,重置通道位置映射
141
+ self._reverse_ch_pos = None
142
+
143
+ @property
144
+ def acq_channels(self):
145
+ if self._acq_channels is None:
146
+ # 初始化通道参数为1-11
147
+ self.set_acq_param([i for i in range(1, 12)])
148
+ return self._acq_channels
149
+
150
+ def set_impedance_channels(self, channels:list):
151
+ # 通道合法性校验
152
+ self._channel_validate(channels)
153
+ # 保存原始通道参数
154
+ self._impedance_channels_origin = channels
155
+
156
+ # 名称转换为数字通道
157
+ channels = [self.channel_name_mapping.get(str(i).upper(), i) for i in channels]
158
+
159
+ # 根据映射关系做通道转换-没有映射的默认到第一个通道
160
+ # 先设置不存在的通道为-1,再把-1替换为第一个通道,避免第一个通道也不合法的情况
161
+ channels = [self.channel_mapping.get(str(i), -1) for i in channels]
162
+ channels = [i if i != -1 else channels[0] for i in channels]
163
+ self._impedance_channels = channels
164
+
165
+ # 初始化位置映射
166
+ self._impedance_ch_pos = None
167
+
168
+ def get_impedance_channels(self):
169
+ if self._impedance_channels is None or len(self._impedance_channels) == 0:
170
+ self.set_impedance_channels([i for i in range(1, 12)])
171
+ return self._impedance_channels
172
+
173
+ def _channel_validate(self, channels: list):
174
+ # 名称转换为数字通道
175
+ temp_channels = [self.channel_name_mapping.get(str(i).upper(), i) for i in channels]
176
+
177
+ # 根据映射关系做通道转换-没有映射的默认到第一个通道
178
+ # 先设置不存在的通道为-1,再把-1替换为第一个通道,避免第一个通道也不合法的情况
179
+ temp_channels = [self.channel_mapping.get(str(i), -1) for i in temp_channels]
180
+ for i in range(len(temp_channels)):
181
+ if temp_channels[i] == -1:
182
+ raise ValueError(f"通道参数有误,通道应为1-23的数字或对应名称,如:1、2或FP1、FP2等")
183
+
184
+ def _impedance_wrapper(self, body: bytes):
185
+ packet = ImpedancePacket().transfer(body)
186
+ impedance_channels = self.get_impedance_channels()
187
+ if impedance_channels is not None and len(impedance_channels) > 0:
188
+ # 只保留设置的阻抗通道
189
+ channel_pos = intersection_positions(packet.channels, impedance_channels)
190
+ packet.impedance = [packet.impedance[i] for i in channel_pos]
191
+ packet.channels = [packet.channels[i] for i in channel_pos]
192
+
193
+ # 升级为类变量,减少计算
194
+ if self._impedance_ch_pos is None:
195
+ self._impedance_ch_pos = map_indices(impedance_channels, packet.channels)
196
+
197
+ packet.channels = self._impedance_channels_origin
198
+ packet.impedance = [packet.impedance[i] for i in self._impedance_ch_pos]
199
+
200
+ return packet
201
+
202
+ # 信号数据转换(默认不处理)
203
+ def _signal_wrapper(self, body: bytes):
204
+ if body is None:
205
+ return
206
+ data = RscPacket().transfer(body)
207
+ # 根据映射关系做通道转换-(注意数据和通道的一致性)
208
+ # data.channels = [self.channel_display_mapping.get(i, i) for i in data.channels]
209
+
210
+ # 升级为类变量,减少计算
211
+ if self._reverse_ch_pos is None:
212
+ self._reverse_ch_pos = map_indices(self._acq_param["channels"], data.channels)
213
+
214
+ # 更新通道(数据)顺序和输入一致
215
+ data.channels = self._acq_param["original_channels"]
216
+ data.eeg = [data.eeg[i] for i in self._reverse_ch_pos]
217
+
218
+ return data
219
+
220
+ def start_stimulation(self):
221
+ raise NotImplementedError("Stimulation function is not supported")
222
+
223
+ def map_indices(A, B):
224
+ """
225
+
226
+ 参数:
227
+ A: 源数组(无重复值)
228
+ B: 目标数组(无重复值)
229
+
230
+ 返回:
231
+ C: 与A长度相同的数组,元素为A中对应值在B中的索引(不存在则为-1)
232
+ """
233
+ # 创建B的值到索引的映射字典(O(n)操作)
234
+ b_map = {value: idx for idx, value in enumerate(B)}
235
+
236
+ # 遍历A,获取每个元素在B中的位置(O(m)操作)
237
+ return [b_map.get(a, -1) for a in A]
238
+
239
+ DeviceFactory.register(C21R.device_type, C21R)