qlsdk2 0.4.0a3__py3-none-any.whl → 0.4.2__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 (41) hide show
  1. qlsdk/ar4/__init__.py +9 -5
  2. qlsdk/core/device.py +10 -1
  3. qlsdk/core/entity/__init__.py +1 -0
  4. qlsdk/core/message/command.py +56 -35
  5. qlsdk/core/utils.py +20 -15
  6. qlsdk/persist/__init__.py +2 -1
  7. qlsdk/persist/ars_edf.py +278 -0
  8. qlsdk/persist/edf.py +21 -2
  9. qlsdk/persist/rsc_edf.py +187 -124
  10. qlsdk/persist/stream.py +161 -0
  11. qlsdk/rsc/__init__.py +4 -2
  12. qlsdk/rsc/command/__init__.py +177 -61
  13. qlsdk/rsc/command/message.py +171 -74
  14. qlsdk/rsc/device/__init__.py +2 -0
  15. qlsdk/rsc/device/arskindling.py +384 -0
  16. qlsdk/rsc/device/base.py +413 -0
  17. qlsdk/rsc/device/c256_rs.py +364 -0
  18. qlsdk/rsc/device/c64_rs.py +358 -0
  19. qlsdk/rsc/device/c64s1.py +364 -0
  20. qlsdk/rsc/device/device_factory.py +33 -0
  21. qlsdk/rsc/entity.py +63 -24
  22. qlsdk/rsc/interface/__init__.py +3 -0
  23. qlsdk/rsc/interface/command.py +10 -0
  24. qlsdk/rsc/interface/device.py +114 -0
  25. qlsdk/rsc/interface/handler.py +9 -0
  26. qlsdk/rsc/interface/parser.py +12 -0
  27. qlsdk/rsc/manager/__init__.py +2 -0
  28. qlsdk/rsc/manager/container.py +126 -0
  29. qlsdk/rsc/manager/search.py +0 -0
  30. qlsdk/rsc/network/__init__.py +1 -0
  31. qlsdk/rsc/network/discover.py +87 -0
  32. qlsdk/rsc/paradigm.py +1 -1
  33. qlsdk/rsc/parser/__init__.py +1 -0
  34. qlsdk/rsc/parser/base.py +69 -0
  35. qlsdk/sdk/ar4sdk.py +13 -4
  36. qlsdk/x8/__init__.py +4 -0
  37. {qlsdk2-0.4.0a3.dist-info → qlsdk2-0.4.2.dist-info}/METADATA +1 -1
  38. qlsdk2-0.4.2.dist-info/RECORD +63 -0
  39. qlsdk2-0.4.0a3.dist-info/RECORD +0 -42
  40. {qlsdk2-0.4.0a3.dist-info → qlsdk2-0.4.2.dist-info}/WHEEL +0 -0
  41. {qlsdk2-0.4.0a3.dist-info → qlsdk2-0.4.2.dist-info}/top_level.txt +0 -0
qlsdk/persist/rsc_edf.py CHANGED
@@ -1,5 +1,6 @@
1
1
  from datetime import datetime
2
2
  from multiprocessing import Lock, Queue
3
+ from time import time_ns
3
4
  from pyedflib import FILETYPE_BDFPLUS, FILETYPE_EDFPLUS, EdfWriter
4
5
  from threading import Thread
5
6
  from loguru import logger
@@ -7,116 +8,188 @@ import numpy as np
7
8
  import os
8
9
  from qlsdk.core import RscPacket
9
10
 
10
- class EDFWriterThread(Thread):
11
- def __init__(self, edf_writer : EdfWriter):
11
+ EDF_FILE_TYPE = {
12
+ "bdf": FILETYPE_BDFPLUS,
13
+ "edf": FILETYPE_EDFPLUS
14
+ }
15
+
16
+ class EDFStreamWriter(Thread):
17
+ def __init__(self, channels, sample_frequency, physical_max, digital_min, file_type, file_path):
12
18
  super().__init__()
13
- self._edf_writer : EdfWriter = edf_writer
19
+ self._writer : EdfWriter = None
14
20
  self.data_queue : Queue = Queue()
15
- self._stop_event : bool = False
16
21
  self._recording = False
17
- self._chunk = np.array([])
18
22
  self._points = 0
19
23
  self._duration = 0
20
- self._sample_frequency = 0
21
- self._total_packets = 0
22
- self._channels = []
23
- self._sample_rate = 0
24
+ self._buffer = None
25
+
26
+ # signal info
27
+ self._channels = channels
28
+ self._n_channels = len(channels)
29
+ self.sample_frequency = sample_frequency
30
+ self.physical_max = physical_max
31
+ self.physical_min = digital_min
32
+ # 和位数相关,edf 16 bits/bdf 24 bits
33
+ self.digital_max = 8388607 if file_type == EDF_FILE_TYPE['bdf'] else 32767
34
+ self.digital_min = -8388608 if file_type == EDF_FILE_TYPE['bdf'] else -32768
35
+ self.file_type = file_type
36
+ self.file_path = file_path
37
+
38
+ # 记录开始时间
39
+ self.start_time = None
40
+
41
+ # header info
42
+ self.equipment = "equipment"
43
+ self.patient_code = "patient_code"
44
+ self.patient_name = "patient_name"
24
45
 
25
- def stop(self):
26
- self._stop_event = True
46
+ def set_channels(self, channels):
47
+ self._channels = channels
48
+ self._n_channels = len(channels)
49
+
50
+ def set_sample_rate(self, sample_rate):
51
+ self._sample_rate = sample_rate
52
+
53
+ def set_start_time(self, time):
54
+ self.start_time = time
55
+
56
+ def stop_recording(self):
57
+ self._recording = False
27
58
 
28
59
  def append(self, data):
29
- # 数据
30
- self.data_queue.put(data)
60
+ if data:
61
+ # 数据
62
+ self.data_queue.put(data)
63
+
64
+ def trigger(self, onset, desc):
65
+ if self._writer:
66
+ self._writer.writeAnnotation(onset, 1, desc)
67
+ else:
68
+ logger.warning("未创建文件,无法写入Trigger标记")
69
+
31
70
 
32
71
  def run(self):
33
- logger.debug(f"开始消费数据 _consumer: {self.data_queue.qsize()}")
72
+ logger.debug(f"启动bdf文件写入线程,写入数据到文件 {self.file_path}")
73
+ # 记录状态
74
+ self._recording = True
75
+
76
+ # 初始化
77
+ if self._writer is None:
78
+ self._init_writer()
79
+
34
80
  while True:
35
81
  if self._recording or (not self.data_queue.empty()):
36
82
  try:
37
- data = self.data_queue.get(timeout=10)
83
+ data = self.data_queue.get(timeout=30)
38
84
  if data is None:
85
+ logger.debug("收到结束信号,停止写入数据")
39
86
  break
40
87
  # 处理数据
41
- self._points += len(data)
88
+ self._points += len(data[1])
89
+ logger.trace(f"已处理数据点数:{self._points}")
42
90
  self._write_file(data)
43
91
  except Exception as e:
44
- logger.error("数据队列为空,超时(10s)结束")
92
+ logger.error(f"异常或超时(30s)结束: {str(e)}")
45
93
  break
46
94
  else:
95
+ logger.debug("数据记录完成")
47
96
  break
48
97
 
49
98
  self.close()
50
99
 
100
+ def _init_writer(self):
101
+
102
+ # 创建EDF+写入器
103
+ self._writer = EdfWriter(
104
+ self.file_path,
105
+ self._n_channels,
106
+ file_type=self.file_type
107
+ )
108
+
109
+ # 设置头信息
110
+ self._writer.setPatientCode(self.patient_code)
111
+ self._writer.setPatientName(self.patient_name)
112
+ self._writer.setEquipment(self.equipment)
113
+ self._writer.setStartdatetime(self.start_time if self.start_time else datetime.now())
114
+
115
+ # 配置通道参数
116
+ signal_headers = []
117
+ for ch in range(self._n_channels):
118
+ signal_headers.append({
119
+ "label": f'channels {self._channels[ch]}',
120
+ "dimension": 'uV',
121
+ "sample_frequency": self.sample_frequency,
122
+ "physical_min": self.physical_min,
123
+ "physical_max": self.physical_max,
124
+ "digital_min": self.digital_min,
125
+ "digital_max": self.digital_max
126
+ })
127
+
128
+ self._writer.setSignalHeaders(signal_headers)
129
+
51
130
  def _write_file(self, eeg_data):
52
131
  try:
53
- if (self._chunk.size == 0):
54
- self._chunk = np.asarray(eeg_data)
132
+ if self._buffer is None or self._buffer.size == 0:
133
+ self._buffer = np.asarray(eeg_data)
55
134
  else:
56
- self._chunk = np.hstack((self._chunk, eeg_data))
57
-
58
- if self._chunk.size >= self._sample_rate * self._channels:
59
- self._write_chunk(self._chunk[:self._sample_rate])
60
- self._chunk = self._chunk[self._sample_rate:]
135
+ self._buffer = np.hstack((self._buffer, eeg_data))
136
+
137
+ if self._buffer.shape[1] >= self.sample_frequency:
138
+ block = self._buffer[:, :self.sample_frequency]
139
+ self._write_block(block)
140
+ self._buffer = self._buffer[:, self.sample_frequency:]
61
141
 
62
142
  except Exception as e:
63
143
  logger.error(f"写入数据异常: {str(e)}")
64
144
 
65
145
  def close(self):
66
146
  self._recording = False
67
- if self._edf_writer:
68
- self._end_time = datetime.now().timestamp()
69
- self._edf_writer.writeAnnotation(0, 1, "start recording ")
70
- self._edf_writer.writeAnnotation(self._duration, 1, "recording end")
71
- self._edf_writer.close()
72
-
73
- # logger.info(f"文件: {self.file_name}完成记录, 总点数: {self._points}, 总时长: {self._duration}秒 丢包数: {self._lost_packets}/{self._total_packets + self._lost_packets}")
74
- # logger.info(f"文件: 完成记录, 总点数: {self._points}, 总时长: {self._duration}秒 丢包数: {self._lost_packets}/{self._total_packets + self._lost_packets}")
147
+ if self._writer:
148
+ self._writer.writeAnnotation(0, 1, "recording start")
149
+ self._writer.writeAnnotation(self._duration, 1, "recording end")
150
+ self._writer.close()
75
151
 
152
+ logger.info(f"文件: {self.file_path}完成记录, 总点数: {self._points}, 总时长: {self._duration}秒")
76
153
 
77
-
78
- def _write_chunk(self, chunk):
79
- logger.debug(f"写入数据: {chunk}")
80
- # 转换数据类型为float64(pyedflib要求)
81
- data_float64 = chunk.astype(np.float64)
154
+ # 写入1秒的数据
155
+ def _write_block(self, block):
156
+ logger.trace(f"写入数据: {block}")
157
+ # 转换数据类型为float64
158
+ data_float64 = block.astype(np.float64)
82
159
  # 写入时转置为(样本数, 通道数)格式
83
- self._edf_writer.writeSamples(data_float64)
160
+ self._writer.writeSamples(data_float64)
84
161
  self._duration += 1
85
162
 
163
+ # 用作数据结构一致化处理,通过调用公共类写入edf文件
164
+ # 入参包含写入edf的全部前置参数
165
+ # 实时数据包为个性化数据包,含有eeg数据部分
86
166
  class RscEDFHandler(object):
87
167
  '''
88
168
  Rsc EDF Handler
89
169
  处理EDF文件的读写
90
170
  RSC设备通道数根据选择变化,不同通道采样频率相同
91
- sample_frequency: 采样频率
92
- physical_max: 物理最大值
93
- physical_min: 物理最小值
94
- digital_max: 数字最大值
95
- digital_min: 数字最小值
171
+ eeg_sample_rate: 采样频率
172
+ physical_max: 物理最大值 (uV)
173
+ physical_min: 物理最小值 (uV)
96
174
  resolution: 分辨率
97
175
  storage_path: 存储路径
98
176
 
99
177
  @author: qlsdk
100
178
  @since: 0.4.0
101
179
  '''
102
- def __init__(self, sample_frequency, physical_max, physical_min, digital_max, digital_min, resolution=24, storage_path = None):
180
+ def __init__(self, eeg_sample_rate, physical_max, physical_min, resolution=24, storage_path = None):
103
181
  # edf文件参数
104
182
  self.physical_max = physical_max
105
183
  self.physical_min = physical_min
106
- self.digital_max = digital_max
107
- self.digital_min = digital_min
184
+ self.digital_max = 8388607 if resolution == 24 else 32767
185
+ self.digital_min = -8388607 if resolution == 24 else - 32768
186
+ self.file_type = EDF_FILE_TYPE["bdf"] if resolution == 24 else EDF_FILE_TYPE["edf"]
108
187
  # 点分辨率
109
188
  self.resolution = resolution
110
189
  # eeg通道数
111
- self.eeg_channels = None
190
+ self.channels = None
112
191
  # eeg采样率
113
- self.eeg_sample_rate = 500
114
- self.acc_channels = None
115
- self.acc_sample_rate = 50
116
- # 缓存
117
- self._cache = Queue()
118
- # 采样频率
119
- self.sample_frequency = sample_frequency
192
+ self.sample_rate = eeg_sample_rate
120
193
  # bytes per second
121
194
  self.bytes_per_second = 0
122
195
  self._edf_writer = None
@@ -126,116 +199,106 @@ class RscEDFHandler(object):
126
199
  self.annotations = None
127
200
  # 每个数据块大小
128
201
  self._chunk = np.array([])
129
- self._Lock = Lock()
130
202
  self._duration = 0
131
203
  self._points = 0
132
204
  self._first_pkg_id = None
133
205
  self._last_pkg_id = None
134
206
  self._first_timestamp = None
207
+ self._start_time = None
135
208
  self._end_time = None
136
209
  self._patient_code = "patient_code"
137
210
  self._patient_name = "patient_name"
138
211
  self._device_type = "24130032"
212
+ self._device_no = "24130032"
139
213
  self._total_packets = 0
140
214
  self._lost_packets = 0
141
215
  self._storage_path = storage_path
142
216
  self._edf_writer_thread = None
217
+ self._file_prefix = None
218
+
219
+ self._lock = Lock()
143
220
 
144
221
  @property
145
222
  def file_name(self):
223
+ suffix = "bdf" if self.resolution == 24 else "edf"
224
+
225
+ # 文件名称
226
+ file_name = f"{self._file_prefix}_{self._device_no}_{self._start_time.strftime('%y%m%d%H%I%M')}.{suffix}" if self._file_prefix else f"{self._device_no}_{self._start_time.strftime('%y%m%d%H%I%M')}.{suffix}"
227
+
146
228
  if self._storage_path:
147
229
  try:
148
- os.makedirs(self._storage_path, exist_ok=True) # 自动创建目录,存在则忽略
149
- return f"{self._storage_path}/{self._device_type}_{self._first_timestamp}.edf"
230
+ # 自动创建目录,存在则忽略
231
+ os.makedirs(self._storage_path, exist_ok=True)
232
+
233
+ return f"{self._storage_path}/{file_name}"
150
234
  except Exception as e:
151
- logger.error(f"创建目录[{self._storage_path}]失败: {e}")
152
-
153
- return f"{self._device_type}_{self._first_timestamp}.edf"
154
-
155
- @property
156
- def file_type(self):
157
- return FILETYPE_BDFPLUS if self.resolution == 24 else FILETYPE_EDFPLUS
235
+ logger.error(f"创建目录[{self._storage_path}]失败: {e}")
236
+
237
+ return file_name
158
238
 
159
239
  def set_device_type(self, device_type):
160
- self._device_type = device_type
240
+ if device_type == 0x39:
241
+ self._device_type = "C64RS"
242
+ elif device_type == 0x40:
243
+ self._device_type = "LJ64S1"
244
+ elif device_type == 0x60:
245
+ self._device_type = "ARSKindling"
246
+ else:
247
+ self._device_type = device_type
248
+
249
+ def set_device_no(self, device_no):
250
+ self._device_no = device_no
161
251
 
162
252
  def set_storage_path(self, storage_path):
163
253
  self._storage_path = storage_path
164
254
 
255
+ def set_file_prefix(self, file_prefix):
256
+ self._file_prefix = file_prefix
257
+
165
258
  def set_patient_code(self, patient_code):
166
259
  self._patient_code = patient_code
167
260
 
168
261
  def set_patient_name(self, patient_name):
169
262
  self._patient_name = patient_name
170
263
 
171
- def append(self, data: RscPacket):
172
-
173
- if data:
174
- if self.eeg_channels is None:
264
+ def write(self, packet: RscPacket):
265
+ # logger.trace(f"packet: {packet}")
266
+ if packet is None:
267
+ self._edf_writer_thread.stop_recording()
268
+ return
269
+
270
+ with self._lock:
271
+ if self.channels is None:
175
272
  logger.info(f"开始记录数据到文件...")
176
- self.eeg_channels = data.channels
177
- self._first_pkg_id = data.pkg_id if self._first_pkg_id is None else self._first_pkg_id
178
- self._first_timestamp = data.time_stamp if self._first_timestamp is None else self._first_timestamp
273
+ self.channels = packet.channels
274
+ self._first_pkg_id = packet.pkg_id if self._first_pkg_id is None else self._first_pkg_id
275
+ self._first_timestamp = packet.time_stamp if self._first_timestamp is None else self._first_timestamp
276
+ self._start_time = datetime.now()
277
+ logger.info(f"第一个包id: {self._first_pkg_id }, 时间戳:{self._first_timestamp}, 当前时间:{datetime.now().timestamp()} offset: {datetime.now().timestamp() - self._first_timestamp}")
179
278
 
180
- if self._last_pkg_id and self._last_pkg_id != data.pkg_id - 1:
181
- self._lost_packets += data.pkg_id - self._last_pkg_id - 1
182
- logger.warning(f"数据包丢失: {self._last_pkg_id} -> {data.pkg_id}, 丢包数: {data.pkg_id - self._last_pkg_id - 1}")
279
+ if self._last_pkg_id and self._last_pkg_id != packet.pkg_id - 1:
280
+ self._lost_packets += packet.pkg_id - self._last_pkg_id - 1
281
+ logger.warning(f"数据包丢失: {self._last_pkg_id} -> {packet.pkg_id}, 丢包数: {packet.pkg_id - self._last_pkg_id - 1}")
183
282
 
184
- self._last_pkg_id = data.pkg_id
283
+ self._last_pkg_id = packet.pkg_id
185
284
  self._total_packets += 1
186
285
 
187
-
188
-
189
- # 通道数变化、采样频率、信号放大幅度变化时应生成新的edf文件,handler内部不关注,外部调用方自行控制
190
- # elif len(self.eeg_channels) != len(data.channels):
191
-
192
286
  if self._edf_writer_thread is None:
193
- self._edf_writer_thread = EDFWriterThread(self.init_edf_writer())
287
+ self._edf_writer_thread = EDFStreamWriter(self.channels, self.sample_rate, self.physical_max, self.physical_min, self.file_type, self.file_name)
288
+ self._edf_writer_thread.set_start_time(self._start_time)
194
289
  self._edf_writer_thread.start()
195
- self._recording = True
196
- self._edf_writer_thread._recording = True
197
290
  logger.info(f"开始写入数据: {self.file_name}")
198
291
 
199
- self._edf_writer_thread.append(data)
292
+ self._edf_writer_thread.append(packet.eeg)
200
293
 
201
- # 数据
202
- # self._cache.put(data)
203
- # self._edf_writer_thread.append(data)
204
- # if not self._recording:
205
- # self.start()
206
294
 
207
- def trigger(self, data):
208
- pass
209
-
210
- def init_edf_writer(self):
211
- # 创建EDF+写入器
212
- edf_writer = EdfWriter(
213
- self.file_name,
214
- len(self.eeg_channels),
215
- file_type=self.file_type
216
- )
217
-
218
- # 设置头信息
219
- edf_writer.setPatientCode(self._patient_code)
220
- edf_writer.setPatientName(self._patient_name)
221
- edf_writer.setEquipment(self._device_type)
222
- edf_writer.setStartdatetime(datetime.now())
223
-
224
- # 配置通道参数
225
- signal_headers = []
226
- for ch in range(len(self.eeg_channels)):
227
- signal_headers.append({
228
- "label": f'channels {ch + 1}',
229
- "dimension": 'uV',
230
- "sample_frequency": self.sample_frequency,
231
- "physical_min": self.physical_min,
232
- "physical_max": self.physical_max,
233
- "digital_min": self.digital_min,
234
- "digital_max": self.digital_max
235
- })
236
-
237
- edf_writer.setSignalHeaders(signal_headers)
238
-
239
- return edf_writer
240
-
295
+ # trigger标记
296
+ # desc: 标记内容
297
+ # cur_time: 设备时间时间戳,非设备发出的trigger不要设置
298
+ def trigger(self, desc, cur_time=None):
299
+ if cur_time is None:
300
+ onset = datetime.now().timestamp() - self._start_time.timestamp()
301
+ else:
302
+ onset = cur_time - self._first_timestamp
303
+ self._edf_writer_thread.trigger(onset, desc)
241
304
 
@@ -0,0 +1,161 @@
1
+
2
+
3
+ from datetime import datetime
4
+ from multiprocessing import Lock, Queue
5
+ from time import time_ns
6
+ from pyedflib import FILETYPE_BDFPLUS, FILETYPE_EDFPLUS, EdfWriter
7
+ from threading import Thread
8
+ from loguru import logger
9
+ import numpy as np
10
+
11
+ EDF_FILE_TYPE = {
12
+ "bdf": FILETYPE_BDFPLUS,
13
+ "edf": FILETYPE_EDFPLUS
14
+ }
15
+
16
+ class EDFStreamWriter(Thread):
17
+ def __init__(self, channels, sample_frequency, physical_max, digital_min, file_type, file_path):
18
+ super().__init__()
19
+ self._writer : EdfWriter = None
20
+ self.data_queue : Queue = Queue()
21
+ self._recording = False
22
+ self._points = 0
23
+ self._duration = 0
24
+ self._buffer = None
25
+
26
+ # signal info
27
+ self._channels = channels
28
+ self._n_channels = len(channels)
29
+ self.sample_frequency = sample_frequency
30
+ self.physical_max = physical_max
31
+ self.physical_min = digital_min
32
+ # 和位数相关,edf 16 bits/bdf 24 bits
33
+ self.digital_max = 8388607 if file_type == EDF_FILE_TYPE['bdf'] else 32767
34
+ self.digital_min = -8388608 if file_type == EDF_FILE_TYPE['bdf'] else -32768
35
+ self.file_type = file_type
36
+ self.file_path = file_path
37
+
38
+ # 记录开始时间
39
+ self.start_time = None
40
+
41
+ # header info
42
+ self.equipment = "equipment"
43
+ self.patient_code = "patient_code"
44
+ self.patient_name = "patient_name"
45
+
46
+ def set_channels(self, channels):
47
+ self._channels = channels
48
+ self._n_channels = len(channels)
49
+
50
+ def set_sample_rate(self, sample_rate):
51
+ self._sample_rate = sample_rate
52
+
53
+ def set_start_time(self, time):
54
+ self.start_time = time
55
+
56
+ def stop_recording(self):
57
+ self._recording = False
58
+
59
+ def append(self, data):
60
+ if data:
61
+ # 数据
62
+ self.data_queue.put(data)
63
+
64
+ def trigger(self, onset, desc):
65
+ if self._writer:
66
+ self._writer.writeAnnotation(onset, 1, desc)
67
+ else:
68
+ logger.warning("未创建文件,无法写入Trigger标记")
69
+
70
+
71
+ def run(self):
72
+ logger.debug(f"启动bdf文件写入线程,写入数据到文件 {self.file_path}")
73
+ # 记录状态
74
+ self._recording = True
75
+
76
+ # 初始化
77
+ if self._writer is None:
78
+ self._init_writer()
79
+
80
+ while True:
81
+ if self._recording or (not self.data_queue.empty()):
82
+ try:
83
+ data = self.data_queue.get(timeout=30)
84
+ if data is None:
85
+ logger.debug("收到结束信号,停止写入数据")
86
+ break
87
+ # 处理数据
88
+ self._points += len(data[1])
89
+ logger.trace(f"已处理数据点数:{self._points}")
90
+ self._write_file(data)
91
+ except Exception as e:
92
+ logger.error(f"异常或超时(30s)结束: {str(e)}")
93
+ break
94
+ else:
95
+ logger.debug("数据记录完成")
96
+ break
97
+
98
+ self.close()
99
+
100
+ def _init_writer(self):
101
+
102
+ # 创建EDF+写入器
103
+ self._writer = EdfWriter(
104
+ self.file_path,
105
+ self._n_channels,
106
+ file_type=self.file_type
107
+ )
108
+
109
+ # 设置头信息
110
+ self._writer.setPatientCode(self.patient_code)
111
+ self._writer.setPatientName(self.patient_name)
112
+ self._writer.setEquipment(self.equipment)
113
+ self._writer.setStartdatetime(self.start_time if self.start_time else datetime.now())
114
+
115
+ # 配置通道参数
116
+ signal_headers = []
117
+ for ch in range(self._n_channels):
118
+ signal_headers.append({
119
+ "label": f'channels {self._channels[ch]}',
120
+ "dimension": 'uV',
121
+ "sample_frequency": self.sample_frequency,
122
+ "physical_min": self.physical_min,
123
+ "physical_max": self.physical_max,
124
+ "digital_min": self.digital_min,
125
+ "digital_max": self.digital_max
126
+ })
127
+
128
+ self._writer.setSignalHeaders(signal_headers)
129
+
130
+ def _write_file(self, eeg_data):
131
+ try:
132
+ if self._buffer is None or self._buffer.size == 0:
133
+ self._buffer = np.asarray(eeg_data)
134
+ else:
135
+ self._buffer = np.hstack((self._buffer, eeg_data))
136
+
137
+ if self._buffer.shape[1] >= self.sample_frequency:
138
+ block = self._buffer[:, :self.sample_frequency]
139
+ self._write_block(block)
140
+ self._buffer = self._buffer[:, self.sample_frequency:]
141
+
142
+ except Exception as e:
143
+ logger.error(f"写入数据异常: {str(e)}")
144
+
145
+ def close(self):
146
+ self._recording = False
147
+ if self._writer:
148
+ self._writer.writeAnnotation(0, 1, "recording start")
149
+ self._writer.writeAnnotation(self._duration, 1, "recording end")
150
+ self._writer.close()
151
+
152
+ logger.info(f"文件: {self.file_path}完成记录, 总点数: {self._points}, 总时长: {self._duration}秒")
153
+
154
+ # 写入1秒的数据
155
+ def _write_block(self, block):
156
+ logger.trace(f"写入数据: {block}")
157
+ # 转换数据类型为float64
158
+ data_float64 = block.astype(np.float64)
159
+ # 写入时转置为(样本数, 通道数)格式
160
+ self._writer.writeSamples(data_float64)
161
+ self._duration += 1
qlsdk/rsc/__init__.py CHANGED
@@ -1,7 +1,9 @@
1
- from .discover import *
1
+ from .network.discover import *
2
2
 
3
3
  from .entity import DeviceParser, QLDevice
4
4
  from .command import *
5
- from .device_manager import DeviceContainer
5
+ # from .device_manager import DeviceContainer
6
6
  from .proxy import DeviceProxy
7
7
  from .paradigm import *
8
+ from .manager import DeviceContainer
9
+ from .network import *