qlsdk2 0.6.0a1__tar.gz → 0.6.0a3__tar.gz

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 (82) hide show
  1. {qlsdk2-0.6.0a1 → qlsdk2-0.6.0a3}/PKG-INFO +7 -11
  2. {qlsdk2-0.6.0a1 → qlsdk2-0.6.0a3}/README.md +5 -0
  3. {qlsdk2-0.6.0a1 → qlsdk2-0.6.0a3}/setup.py +1 -1
  4. {qlsdk2-0.6.0a1 → qlsdk2-0.6.0a3}/src/qlsdk/core/entity/__init__.py +3 -2
  5. {qlsdk2-0.6.0a1 → qlsdk2-0.6.0a3}/src/qlsdk/persist/rsc_edf.py +17 -11
  6. {qlsdk2-0.6.0a1 → qlsdk2-0.6.0a3}/src/qlsdk/rsc/device/base.py +22 -3
  7. {qlsdk2-0.6.0a1 → qlsdk2-0.6.0a3}/src/qlsdk/rsc/device/c256_rs.py +6 -16
  8. {qlsdk2-0.6.0a1 → qlsdk2-0.6.0a3}/src/qlsdk2.egg-info/PKG-INFO +7 -11
  9. {qlsdk2-0.6.0a1 → qlsdk2-0.6.0a3}/src/qlsdk2.egg-info/SOURCES.txt +2 -1
  10. qlsdk2-0.6.0a3/test/test_bdf.py +212 -0
  11. {qlsdk2-0.6.0a1 → qlsdk2-0.6.0a3}/setup.cfg +0 -0
  12. {qlsdk2-0.6.0a1 → qlsdk2-0.6.0a3}/src/qlsdk/__init__.py +0 -0
  13. {qlsdk2-0.6.0a1 → qlsdk2-0.6.0a3}/src/qlsdk/ar4/__init__.py +0 -0
  14. {qlsdk2-0.6.0a1 → qlsdk2-0.6.0a3}/src/qlsdk/ar4m/__init__.py +0 -0
  15. {qlsdk2-0.6.0a1 → qlsdk2-0.6.0a3}/src/qlsdk/core/__init__.py +0 -0
  16. {qlsdk2-0.6.0a1 → qlsdk2-0.6.0a3}/src/qlsdk/core/crc/__init__.py +0 -0
  17. {qlsdk2-0.6.0a1 → qlsdk2-0.6.0a3}/src/qlsdk/core/crc/crctools.py +0 -0
  18. {qlsdk2-0.6.0a1 → qlsdk2-0.6.0a3}/src/qlsdk/core/device.py +0 -0
  19. {qlsdk2-0.6.0a1 → qlsdk2-0.6.0a3}/src/qlsdk/core/exception.py +0 -0
  20. {qlsdk2-0.6.0a1 → qlsdk2-0.6.0a3}/src/qlsdk/core/filter/__init__.py +0 -0
  21. {qlsdk2-0.6.0a1 → qlsdk2-0.6.0a3}/src/qlsdk/core/filter/norch.py +0 -0
  22. {qlsdk2-0.6.0a1 → qlsdk2-0.6.0a3}/src/qlsdk/core/local.py +0 -0
  23. {qlsdk2-0.6.0a1 → qlsdk2-0.6.0a3}/src/qlsdk/core/message/__init__.py +0 -0
  24. {qlsdk2-0.6.0a1 → qlsdk2-0.6.0a3}/src/qlsdk/core/message/command.py +0 -0
  25. {qlsdk2-0.6.0a1 → qlsdk2-0.6.0a3}/src/qlsdk/core/message/tcp.py +0 -0
  26. {qlsdk2-0.6.0a1 → qlsdk2-0.6.0a3}/src/qlsdk/core/message/udp.py +0 -0
  27. {qlsdk2-0.6.0a1 → qlsdk2-0.6.0a3}/src/qlsdk/core/network/__init__.py +0 -0
  28. {qlsdk2-0.6.0a1 → qlsdk2-0.6.0a3}/src/qlsdk/core/network/monitor.py +0 -0
  29. {qlsdk2-0.6.0a1 → qlsdk2-0.6.0a3}/src/qlsdk/core/utils.py +0 -0
  30. {qlsdk2-0.6.0a1 → qlsdk2-0.6.0a3}/src/qlsdk/entity/__init__.py +0 -0
  31. {qlsdk2-0.6.0a1 → qlsdk2-0.6.0a3}/src/qlsdk/entity/message.py +0 -0
  32. {qlsdk2-0.6.0a1 → qlsdk2-0.6.0a3}/src/qlsdk/entity/signal.py +0 -0
  33. {qlsdk2-0.6.0a1 → qlsdk2-0.6.0a3}/src/qlsdk/interface/__init__.py +0 -0
  34. {qlsdk2-0.6.0a1 → qlsdk2-0.6.0a3}/src/qlsdk/interface/analyzer.py +0 -0
  35. {qlsdk2-0.6.0a1 → qlsdk2-0.6.0a3}/src/qlsdk/interface/collector.py +0 -0
  36. {qlsdk2-0.6.0a1 → qlsdk2-0.6.0a3}/src/qlsdk/interface/device.py +0 -0
  37. {qlsdk2-0.6.0a1 → qlsdk2-0.6.0a3}/src/qlsdk/interface/parser.py +0 -0
  38. {qlsdk2-0.6.0a1 → qlsdk2-0.6.0a3}/src/qlsdk/interface/stimulator.py +0 -0
  39. {qlsdk2-0.6.0a1 → qlsdk2-0.6.0a3}/src/qlsdk/interface/store.py +0 -0
  40. {qlsdk2-0.6.0a1 → qlsdk2-0.6.0a3}/src/qlsdk/persist/__init__.py +0 -0
  41. {qlsdk2-0.6.0a1 → qlsdk2-0.6.0a3}/src/qlsdk/persist/ars_edf.py +0 -0
  42. {qlsdk2-0.6.0a1 → qlsdk2-0.6.0a3}/src/qlsdk/persist/edf.py +0 -0
  43. {qlsdk2-0.6.0a1 → qlsdk2-0.6.0a3}/src/qlsdk/persist/stream.py +0 -0
  44. {qlsdk2-0.6.0a1 → qlsdk2-0.6.0a3}/src/qlsdk/rsc/__init__.py +0 -0
  45. {qlsdk2-0.6.0a1 → qlsdk2-0.6.0a3}/src/qlsdk/rsc/command/__init__.py +0 -0
  46. {qlsdk2-0.6.0a1 → qlsdk2-0.6.0a3}/src/qlsdk/rsc/device/__init__.py +0 -0
  47. {qlsdk2-0.6.0a1 → qlsdk2-0.6.0a3}/src/qlsdk/rsc/device/arskindling.py +0 -0
  48. {qlsdk2-0.6.0a1 → qlsdk2-0.6.0a3}/src/qlsdk/rsc/device/c16_rs.py +0 -0
  49. {qlsdk2-0.6.0a1 → qlsdk2-0.6.0a3}/src/qlsdk/rsc/device/c64_rs.py +0 -0
  50. {qlsdk2-0.6.0a1 → qlsdk2-0.6.0a3}/src/qlsdk/rsc/device/c64s1.py +0 -0
  51. {qlsdk2-0.6.0a1 → qlsdk2-0.6.0a3}/src/qlsdk/rsc/device/device_factory.py +0 -0
  52. {qlsdk2-0.6.0a1 → qlsdk2-0.6.0a3}/src/qlsdk/rsc/eegion.py +0 -0
  53. {qlsdk2-0.6.0a1 → qlsdk2-0.6.0a3}/src/qlsdk/rsc/entity.py +0 -0
  54. {qlsdk2-0.6.0a1 → qlsdk2-0.6.0a3}/src/qlsdk/rsc/interface/__init__.py +0 -0
  55. {qlsdk2-0.6.0a1 → qlsdk2-0.6.0a3}/src/qlsdk/rsc/interface/command.py +0 -0
  56. {qlsdk2-0.6.0a1 → qlsdk2-0.6.0a3}/src/qlsdk/rsc/interface/device.py +0 -0
  57. {qlsdk2-0.6.0a1 → qlsdk2-0.6.0a3}/src/qlsdk/rsc/interface/handler.py +0 -0
  58. {qlsdk2-0.6.0a1 → qlsdk2-0.6.0a3}/src/qlsdk/rsc/interface/parser.py +0 -0
  59. {qlsdk2-0.6.0a1 → qlsdk2-0.6.0a3}/src/qlsdk/rsc/manager/__init__.py +0 -0
  60. {qlsdk2-0.6.0a1 → qlsdk2-0.6.0a3}/src/qlsdk/rsc/manager/container.py +0 -0
  61. {qlsdk2-0.6.0a1 → qlsdk2-0.6.0a3}/src/qlsdk/rsc/manager/search.py +0 -0
  62. {qlsdk2-0.6.0a1 → qlsdk2-0.6.0a3}/src/qlsdk/rsc/network/__init__.py +0 -0
  63. {qlsdk2-0.6.0a1 → qlsdk2-0.6.0a3}/src/qlsdk/rsc/network/discover.py +0 -0
  64. {qlsdk2-0.6.0a1 → qlsdk2-0.6.0a3}/src/qlsdk/rsc/paradigm.py +0 -0
  65. {qlsdk2-0.6.0a1 → qlsdk2-0.6.0a3}/src/qlsdk/rsc/parser/__init__.py +0 -0
  66. {qlsdk2-0.6.0a1 → qlsdk2-0.6.0a3}/src/qlsdk/rsc/parser/base-new.py +0 -0
  67. {qlsdk2-0.6.0a1 → qlsdk2-0.6.0a3}/src/qlsdk/rsc/parser/base.py +0 -0
  68. {qlsdk2-0.6.0a1 → qlsdk2-0.6.0a3}/src/qlsdk/rsc/parser/rsc.py +0 -0
  69. {qlsdk2-0.6.0a1 → qlsdk2-0.6.0a3}/src/qlsdk/rsc/proxy.py +0 -0
  70. {qlsdk2-0.6.0a1 → qlsdk2-0.6.0a3}/src/qlsdk/sdk/__init__.py +0 -0
  71. {qlsdk2-0.6.0a1 → qlsdk2-0.6.0a3}/src/qlsdk/sdk/ar4sdk.py +0 -0
  72. {qlsdk2-0.6.0a1 → qlsdk2-0.6.0a3}/src/qlsdk/sdk/hub.py +0 -0
  73. {qlsdk2-0.6.0a1 → qlsdk2-0.6.0a3}/src/qlsdk/sdk/libs/libAr4SDK.dll +0 -0
  74. {qlsdk2-0.6.0a1 → qlsdk2-0.6.0a3}/src/qlsdk/sdk/libs/libwinpthread-1.dll +0 -0
  75. {qlsdk2-0.6.0a1 → qlsdk2-0.6.0a3}/src/qlsdk/x8/__init__.py +0 -0
  76. {qlsdk2-0.6.0a1 → qlsdk2-0.6.0a3}/src/qlsdk/x8m/__init__.py +0 -0
  77. {qlsdk2-0.6.0a1 → qlsdk2-0.6.0a3}/src/qlsdk2.egg-info/dependency_links.txt +0 -0
  78. {qlsdk2-0.6.0a1 → qlsdk2-0.6.0a3}/src/qlsdk2.egg-info/requires.txt +0 -0
  79. {qlsdk2-0.6.0a1 → qlsdk2-0.6.0a3}/src/qlsdk2.egg-info/top_level.txt +0 -0
  80. {qlsdk2-0.6.0a1 → qlsdk2-0.6.0a3}/test/test.222.py +0 -0
  81. {qlsdk2-0.6.0a1 → qlsdk2-0.6.0a3}/test/test.py +0 -0
  82. {qlsdk2-0.6.0a1 → qlsdk2-0.6.0a3}/test/test_ar4m.py +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.1
2
2
  Name: qlsdk2
3
- Version: 0.6.0a1
3
+ Version: 0.6.0a3
4
4
  Summary: SDK for quanlan device
5
5
  Home-page: https://github.com/hehuajun/qlsdk
6
6
  Author: hehuajun
@@ -16,15 +16,11 @@ Requires-Dist: bitarray>=1.5.3
16
16
  Provides-Extra: dev
17
17
  Requires-Dist: pytest>=6.0; extra == "dev"
18
18
  Requires-Dist: twine>=3.0; extra == "dev"
19
- Dynamic: author
20
- Dynamic: author-email
21
- Dynamic: classifier
22
- Dynamic: description
23
- Dynamic: description-content-type
24
- Dynamic: home-page
25
- Dynamic: requires-dist
26
- Dynamic: requires-python
27
- Dynamic: summary
19
+
20
+ ## **v0.6.0 (2025-xx-xx)
21
+ #### 新设备
22
+ - C256RS设备
23
+
28
24
 
29
25
  ## **v0.5.1.1** (2025-08-24)
30
26
  #### 🐛 Bug Fixed
@@ -1,3 +1,8 @@
1
+ ## **v0.6.0 (2025-xx-xx)
2
+ #### 新设备
3
+ - C256RS设备
4
+
5
+
1
6
  ## **v0.5.1.1** (2025-08-24)
2
7
  #### 🐛 Bug Fixed
3
8
  - 修复仅选择一个通道时,自动保存功能异常的问题
@@ -6,7 +6,7 @@ with open("README.md", "r", encoding='utf-8' ) as fh:
6
6
 
7
7
  setuptools.setup(
8
8
  name="qlsdk2",
9
- version="0.6.0a1",
9
+ version="0.6.0a3",
10
10
  author="hehuajun",
11
11
  author_email="hehuajun@eegion.com",
12
12
  description="SDK for quanlan device",
@@ -50,7 +50,8 @@ class RscPacket(Packet):
50
50
  for i in range(ch_num)
51
51
  ]
52
52
 
53
- # logger.trace(self)
53
+ logger.trace(packet)
54
+
54
55
  return packet
55
56
 
56
57
  def __str__(self):
@@ -80,7 +81,7 @@ class ImpedancePacket(Packet):
80
81
  self.pkg_id = int.from_bytes(body[9: 13], 'little')
81
82
  self.channels = to_channels(body[13: 45])
82
83
 
83
- logger.debug(f"impedance: {self}")
84
+ logger.trace(f"impedance: {self}")
84
85
 
85
86
  def __str__(self):
86
87
  return f"""
@@ -15,7 +15,7 @@ EDF_FILE_TYPE = {
15
15
  }
16
16
 
17
17
  class EDFStreamWriter(Thread):
18
- def __init__(self, channels, sample_frequency, physical_max, digital_min, file_type, file_path, record_duration=None):
18
+ def __init__(self, channels, sample_frequency, physical_max, physical_min, file_type, file_path, record_duration=None):
19
19
 
20
20
  super().__init__()
21
21
  self._writer : EdfWriter = None
@@ -32,7 +32,7 @@ class EDFStreamWriter(Thread):
32
32
  self._n_channels = len(channels)
33
33
  self.sample_frequency = sample_frequency
34
34
  self.physical_max = physical_max
35
- self.physical_min = digital_min
35
+ self.physical_min = physical_min
36
36
  # 和位数相关,edf 16 bits/bdf 24 bits
37
37
  self.digital_max = 8388607 if file_type == EDF_FILE_TYPE['bdf'] else 32767
38
38
  self.digital_min = -8388608 if file_type == EDF_FILE_TYPE['bdf'] else -32768
@@ -46,6 +46,7 @@ class EDFStreamWriter(Thread):
46
46
  self.equipment = "equipment"
47
47
  self.patient_code = "patient_code"
48
48
  self.patient_name = "patient_name"
49
+ logger.info(f'digital_max:{self.digital_max}, digital_min:{self.digital_min}, physical_max:{self.physical_max}, physical_min:{self.physical_min}')
49
50
 
50
51
  def set_channels(self, channels):
51
52
  self._channels = channels
@@ -127,6 +128,7 @@ class EDFStreamWriter(Thread):
127
128
 
128
129
  # 配置通道参数
129
130
  signal_headers = []
131
+ logger.trace(f"sf: {self.sample_frequency}, pm: {self.physical_max}, pn: {self.physical_min}, dm: {self.digital_max}, dn: {self.digital_min}")
130
132
  for ch in range(self._n_channels):
131
133
  signal_headers.append({
132
134
  "label": f'channels {self._channels[ch]}',
@@ -169,14 +171,16 @@ class EDFStreamWriter(Thread):
169
171
  # 写入1秒的数据
170
172
  def _write_block(self, block):
171
173
  logger.trace(f"写入数据: {block}")
172
- # 转换数据类型为float64
173
- data_float64 = block.astype(np.float64)
174
+ # 转换数据类型为float64-物理信号uV int32-数字信号
175
+ data_input = block.astype(np.int32)
176
+ logger.trace(f"写入数据-real: {data_input}")
174
177
  # 写入时转置为(样本数, 通道数)格式
175
- self._writer.writeSamples(data_float64)
178
+ self._writer.writeSamples(data_input, digital=True)
176
179
  self._duration += 1
177
180
 
178
181
  if self._duration % 10 == 0: # 每10秒打印一次进度
179
182
  logger.info(f"数据记录中... 文件名:{self.file_path}, 已记录时长: {self._duration}秒")
183
+
180
184
 
181
185
  # 用作数据结构一致化处理,通过调用公共类写入edf文件
182
186
  # 入参包含写入edf的全部前置参数
@@ -200,7 +204,7 @@ class RscEDFHandler(object):
200
204
  self.physical_max = physical_max
201
205
  self.physical_min = physical_min
202
206
  self.digital_max = 8388607 if resolution == 24 else 32767
203
- self.digital_min = -8388607 if resolution == 24 else - 32768
207
+ self.digital_min = -8388608 if resolution == 24 else - 32768
204
208
  self.file_type = EDF_FILE_TYPE["bdf"] if resolution == 24 else EDF_FILE_TYPE["edf"]
205
209
  # 点分辨率
206
210
  self.resolution = resolution
@@ -226,8 +230,8 @@ class RscEDFHandler(object):
226
230
  self._end_time = None
227
231
  self._patient_code = "patient_code"
228
232
  self._patient_name = "patient_name"
229
- self._device_type = "24130032"
230
- self._device_no = "24130032"
233
+ self._device_type = "0000"
234
+ self._device_no = "00000000"
231
235
  self._total_packets = 0
232
236
  self._lost_packets = 0
233
237
  self._storage_path = storage_path
@@ -242,7 +246,7 @@ class RscEDFHandler(object):
242
246
  suffix = "bdf" if self.resolution == 24 else "edf"
243
247
 
244
248
  # 文件名称
245
- 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}"
249
+ file_name = f"{self._file_prefix}_{self._device_no}_{self._start_time.strftime('%y%m%d%H%M%S')}.{suffix}" if self._file_prefix else f"{self._device_no}_{self._start_time.strftime('%y%m%d%H%I%M')}.{suffix}"
246
250
 
247
251
  if self._storage_path:
248
252
  try:
@@ -260,6 +264,8 @@ class RscEDFHandler(object):
260
264
  self._device_type = "C64RS"
261
265
  elif device_type == 0x40:
262
266
  self._device_type = "LJ64S1"
267
+ elif device_type == 0x45:
268
+ self._device_type = "C256RS"
263
269
  elif device_type == 0x51:
264
270
  self._device_type = "C256RS"
265
271
  elif device_type == 0x60:
@@ -312,6 +318,7 @@ class RscEDFHandler(object):
312
318
  self._edf_writer_thread.set_start_time(self._start_time)
313
319
  self._edf_writer_thread.start()
314
320
  logger.info(f"开始写入数据: {self.file_name}")
321
+ self._edf_writer_thread.equipment = f'{self._device_type}_{self._device_no}'
315
322
 
316
323
  self._edf_writer_thread.append(packet.eeg)
317
324
 
@@ -331,5 +338,4 @@ class RscEDFHandler(object):
331
338
  else: onset = 0
332
339
  else:
333
340
  onset = cur_time - self._first_timestamp
334
- self._edf_writer_thread.trigger(onset, desc)
335
-
341
+ self._edf_writer_thread.trigger(onset, desc)
@@ -5,7 +5,7 @@ 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
8
+ from qlsdk.core.entity import RscPacket, ImpedancePacket
9
9
  from qlsdk.core.utils import to_bytes
10
10
  from qlsdk.rsc.interface import IDevice, IParser
11
11
  from qlsdk.rsc.command import StartImpedanceCommand, StopImpedanceCommand, StartStimulationCommand, StopStimulationCommand, SetAcquisitionParamCommand, StartAcquisitionCommand, StopAcquisitionCommand
@@ -136,7 +136,24 @@ class QLBaseDevice(IDevice):
136
136
 
137
137
  # 数据包处理
138
138
  def produce(self, body: bytes, type:Literal['signal', 'impedance']="signal"):
139
- if body is None: return
139
+ if body is None: return
140
+
141
+ if type == "signal":
142
+ self._produce_signal(body)
143
+ elif type == "impedance":
144
+ self._produce_impedance(body)
145
+
146
+ def _produce_impedance(self, body: bytes):
147
+ packet = ImpedancePacket().transfer(body)
148
+ # 分发阻抗数据包给订阅者
149
+ if len(self._impedance_consumer) > 0:
150
+ for topic, q in self._impedance_consumer.items():
151
+ try:
152
+ q.put(packet, timeout=1)
153
+ except Exception as e:
154
+ logger.error(f"impedance data put to queue exception: {str(e)}")
155
+
156
+ def _produce_signal(self, body: bytes):
140
157
 
141
158
  # 处理信号数据
142
159
  data = self._signal_wrapper(body)
@@ -152,8 +169,10 @@ class QLBaseDevice(IDevice):
152
169
  self._write_signal(data)
153
170
 
154
171
  if len(self.signal_consumers) > 0 :
172
+ logger.trace(f"dg eeg: {data.eeg}")
155
173
  # 信号数字值转物理值
156
174
  data.eeg = self.eeg2phy(np.array(data.eeg))
175
+ logger.trace(f"ph eeg: {data.eeg}")
157
176
 
158
177
  # 发送数据包到订阅者
159
178
  for q in list(self.signal_consumers.values()):
@@ -161,7 +180,7 @@ class QLBaseDevice(IDevice):
161
180
  if q.full():
162
181
  q.get()
163
182
 
164
- q.put(data)
183
+ q.put(data)
165
184
 
166
185
  # 信号数据转换
167
186
  def _signal_wrapper(self, body: bytes):
@@ -12,8 +12,9 @@ from qlsdk.rsc.command import *
12
12
  from qlsdk.rsc.device.base import QLBaseDevice
13
13
 
14
14
  class C256RS(QLBaseDevice):
15
-
16
- device_type = 0x51 # C64RS设备类型标识符
15
+ # C256RS设备类型标识符
16
+ device_type_001 = 0x51
17
+ device_type = 0x45
17
18
 
18
19
  def __init__(self, socket):
19
20
  super().__init__(socket)
@@ -188,19 +189,7 @@ class C256RS(QLBaseDevice):
188
189
 
189
190
  # 设置记录文件名称前缀
190
191
  def set_file_prefix(self, prefix):
191
- self._file_prefix = prefix
192
-
193
- # 接收socket消息
194
- def accept(self):
195
- pass
196
- # while True:
197
- # # 缓冲去4M
198
- # data = self.socket.recv(4096*1024)
199
- # if not data:
200
- # logger.warning(f"设备{self.device_name}连接结束")
201
- # break
202
-
203
- # self._parser.append(data)
192
+ self._file_prefix = prefix
204
193
 
205
194
  # socket发送数据
206
195
  def send(self, data):
@@ -355,4 +344,5 @@ class C256RS(QLBaseDevice):
355
344
  return hash((self.device_name, self.device_type, self.device_id))
356
345
 
357
346
 
358
- DeviceFactory.register(C256RS.device_type, C256RS)
347
+ DeviceFactory.register(C256RS.device_type, C256RS)
348
+ DeviceFactory.register(C256RS.device_type_001, C256RS)
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.1
2
2
  Name: qlsdk2
3
- Version: 0.6.0a1
3
+ Version: 0.6.0a3
4
4
  Summary: SDK for quanlan device
5
5
  Home-page: https://github.com/hehuajun/qlsdk
6
6
  Author: hehuajun
@@ -16,15 +16,11 @@ Requires-Dist: bitarray>=1.5.3
16
16
  Provides-Extra: dev
17
17
  Requires-Dist: pytest>=6.0; extra == "dev"
18
18
  Requires-Dist: twine>=3.0; extra == "dev"
19
- Dynamic: author
20
- Dynamic: author-email
21
- Dynamic: classifier
22
- Dynamic: description
23
- Dynamic: description-content-type
24
- Dynamic: home-page
25
- Dynamic: requires-dist
26
- Dynamic: requires-python
27
- Dynamic: summary
19
+
20
+ ## **v0.6.0 (2025-xx-xx)
21
+ #### 新设备
22
+ - C256RS设备
23
+
28
24
 
29
25
  ## **v0.5.1.1** (2025-08-24)
30
26
  #### 🐛 Bug Fixed
@@ -77,4 +77,5 @@ src/qlsdk/x8/__init__.py
77
77
  src/qlsdk/x8m/__init__.py
78
78
  test/test.222.py
79
79
  test/test.py
80
- test/test_ar4m.py
80
+ test/test_ar4m.py
81
+ test/test_bdf.py
@@ -0,0 +1,212 @@
1
+ # 使用示例
2
+ from read_bdf import BDFEDFReader
3
+
4
+
5
+ def main():
6
+ """主函数示例"""
7
+
8
+ # 1. 创建读取器
9
+ filepath = r"E:\project\qlsdk-project\C256RS_450025430001_251223182657.bdf" # 或 "sample.edf"
10
+
11
+ try:
12
+ # 使用上下文管理器自动管理文件
13
+ with BDFEDFReader(filepath) as reader:
14
+
15
+ # 2. 获取文件信息
16
+ file_info = reader.get_file_info()
17
+ print("=== 文件信息 ===")
18
+ for key, value in file_info.items():
19
+ print(f"{key}: {value}")
20
+ print()
21
+
22
+ # 3. 获取通道信息
23
+ channel_info = reader.get_channel_info()
24
+ print("=== 通道信息 (前3个) ===")
25
+ for i in range(min(3, len(channel_info))):
26
+ info = channel_info[i]
27
+ print(f"通道 {i} ({info['label']}):")
28
+ print(f" 采样率: {info['sample_frequency']} Hz")
29
+ print(f" 物理范围: [{info['physical_min']}, {info['physical_max']}] {info['physical_dimension']}")
30
+ print(f" 数字范围: [{info['digital_min']}, {info['digital_max']}]")
31
+ print(f" 增益: {info['gain']:.6f}, 偏移: {info['offset']:.6f}")
32
+ print()
33
+
34
+ # 4. 读取指定时间段的数据
35
+ start_time = 20.0 # 10秒
36
+ end_time = 30.0 # 12秒
37
+
38
+ print(f"=== 读取 {start_time}s 到 {end_time}s 的数据 ===")
39
+
40
+ # 读取所有通道的物理值
41
+ data_physical = reader.read_data(
42
+ start_time=start_time,
43
+ end_time=end_time,
44
+ output_type='digital', # 或 'digital'/'physical'
45
+ return_as_dataframe=True
46
+ )
47
+
48
+ # 5. 显示统计信息
49
+ stats = reader.get_channel_statistics(data_physical)
50
+ print("=== 统计信息 ===")
51
+ for channel_idx, stat in stats.items():
52
+ print(f"通道 {channel_idx} ({stat['label']}):")
53
+ print(f" 样本数: {stat['count']}")
54
+ print(f" 范围: [{stat['min']:.2f}, {stat['max']:.2f}]")
55
+ print(f" 均值: {stat['mean']:.2f} ± {stat['std']:.2f}")
56
+ print()
57
+
58
+ # 6. 获取DataFrame
59
+ if 'dataframe' in data_physical:
60
+ df = data_physical['dataframe']
61
+ print("=== DataFrame 前5行 ===")
62
+ print(df.head())
63
+ print()
64
+ print("=== DataFrame 信息 ===")
65
+ print(df.info())
66
+
67
+ # 7. 导出为CSV
68
+ reader.export_to_csv(data_physical, "output_data.csv")
69
+
70
+ # 8. 读取特定通道
71
+ print("\n=== 读取特定通道 ===")
72
+ channel_indices = [0, 1, 2] # 前3个通道
73
+ data_channels = reader.read_data(
74
+ start_time=15.0,
75
+ end_time=16.0,
76
+ channel_indices=channel_indices,
77
+ output_type='digital' # 数字值
78
+ )
79
+
80
+ for channel_idx, channel_data in data_channels['channels'].items():
81
+ print(f"通道 {channel_idx} 前5个数字值: {channel_data['data'][:5]}")
82
+
83
+ # 9. 通过标签获取通道
84
+ try:
85
+ eeg_channel_idx = reader.get_channel_by_label("EEG")
86
+ print(f"\nEEG通道索引: {eeg_channel_idx}")
87
+ except ValueError as e:
88
+ print(f"未找到EEG通道: {e}")
89
+
90
+ # 10. 读取标注
91
+ annotations = reader.read_annotations()
92
+ if annotations:
93
+ print("\n=== 文件标注 ===")
94
+ for i, annot in enumerate(annotations[:5]): # 显示前5个
95
+ print(f"{i+1}. 时间: {annot['onset']:.2f}s, "
96
+ f"时长: {annot['duration']:.2f}s, "
97
+ f"描述: {annot['description']}")
98
+
99
+ # 11. 绘制图形
100
+ try:
101
+ reader.plot_segment(
102
+ data_dict=data_physical,
103
+ channel_indices=[0, 1, 2],
104
+ figsize=(12, 8),
105
+ save_path="plot.png"
106
+ )
107
+ except Exception as e:
108
+ print(f"绘图失败: {e}")
109
+
110
+ except FileNotFoundError:
111
+ print(f"文件不存在: {filepath}")
112
+ except Exception as e:
113
+ print(f"处理文件时出错: {e}")
114
+
115
+ def batch_process_files(file_list, output_dir="output"):
116
+ """
117
+ 批量处理多个文件
118
+ """
119
+ import os
120
+
121
+ if not os.path.exists(output_dir):
122
+ os.makedirs(output_dir)
123
+
124
+ for filepath in file_list:
125
+ if not os.path.exists(filepath):
126
+ print(f"文件不存在: {filepath}")
127
+ continue
128
+
129
+ print(f"\n处理文件: {filepath}")
130
+
131
+ try:
132
+ reader = BDFEDFReader(filepath)
133
+ reader.open()
134
+
135
+ # 获取文件名
136
+ filename = os.path.basename(filepath)
137
+ base_name = os.path.splitext(filename)[0]
138
+
139
+ # 读取前30秒的数据
140
+ data = reader.read_data(
141
+ start_time=0,
142
+ end_time=30,
143
+ output_type='physical',
144
+ return_as_dataframe=True
145
+ )
146
+
147
+ # 导出CSV
148
+ csv_path = os.path.join(output_dir, f"{base_name}.csv")
149
+ reader.export_to_csv(data, csv_path)
150
+
151
+ # 导出统计信息
152
+ stats = reader.get_channel_statistics(data)
153
+ stats_path = os.path.join(output_dir, f"{base_name}_stats.json")
154
+
155
+ import json
156
+ with open(stats_path, 'w') as f:
157
+ json.dump(stats, f, indent=2, default=str)
158
+
159
+ print(f" 已保存: {csv_path}")
160
+ print(f" 已保存: {stats_path}")
161
+
162
+ reader.close()
163
+
164
+ except Exception as e:
165
+ print(f" 处理失败: {e}")
166
+
167
+ # 命令行接口
168
+ if __name__ == "__main__":
169
+ main()
170
+ # import argparse
171
+
172
+ # parser = argparse.ArgumentParser(description='读取BDF/EDF文件并提取数据')
173
+ # parser.add_argument('filepath', help='BDF/EDF文件路径')
174
+ # parser.add_argument('--start', type=float, default=0, help='开始时间(秒)')
175
+ # parser.add_argument('--end', type=float, default=10, help='结束时间(秒)')
176
+ # parser.add_argument('--channels', type=str, default=1 help='通道索引(逗号分隔)')
177
+ # parser.add_argument('--output-type', choices=['physical', 'digital'], default='physical',
178
+ # help='输出类型: physical(物理值) 或 digital(数字值)')
179
+ # parser.add_argument('--output', help='输出CSV文件路径')
180
+ # parser.add_argument('--plot', action='store_true', help='绘制图表')
181
+
182
+ # args = parser.parse_args()
183
+
184
+ # # 解析通道参数
185
+ # channel_indices = None
186
+ # if args.channels:
187
+ # channel_indices = [int(c.strip()) for c in args.channels.split(',')]
188
+
189
+ # # 处理文件
190
+ # with BDFEDFReader(args.filepath) as reader:
191
+ # data = reader.read_data(
192
+ # start_time=args.start,
193
+ # end_time=args.end,
194
+ # channel_indices=channel_indices,
195
+ # output_type=args.output_type,
196
+ # return_as_dataframe=True
197
+ # )
198
+
199
+ # # 输出到控制台
200
+ # if 'dataframe' in data:
201
+ # print(f"读取了 {len(data['dataframe'])} 个样本")
202
+ # print("\n前10个样本:")
203
+ # print(data['dataframe'].head(10))
204
+
205
+ # # 导出到CSV
206
+ # if args.output:
207
+ # reader.export_to_csv(data, args.output)
208
+ # print(f"\n数据已导出到: {args.output}")
209
+
210
+ # # 绘制图表
211
+ # if args.plot:
212
+ # reader.plot_segment(data, channel_indices=channel_indices)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes