oscura 0.5.0__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.
Files changed (513) hide show
  1. oscura/__init__.py +169 -167
  2. oscura/analyzers/__init__.py +3 -0
  3. oscura/analyzers/classification.py +659 -0
  4. oscura/analyzers/digital/__init__.py +0 -48
  5. oscura/analyzers/digital/edges.py +325 -65
  6. oscura/analyzers/digital/extraction.py +0 -195
  7. oscura/analyzers/digital/quality.py +293 -166
  8. oscura/analyzers/digital/timing.py +260 -115
  9. oscura/analyzers/digital/timing_numba.py +334 -0
  10. oscura/analyzers/entropy.py +605 -0
  11. oscura/analyzers/eye/diagram.py +176 -109
  12. oscura/analyzers/eye/metrics.py +5 -5
  13. oscura/analyzers/jitter/__init__.py +6 -4
  14. oscura/analyzers/jitter/ber.py +52 -52
  15. oscura/analyzers/jitter/classification.py +156 -0
  16. oscura/analyzers/jitter/decomposition.py +163 -113
  17. oscura/analyzers/jitter/spectrum.py +80 -64
  18. oscura/analyzers/ml/__init__.py +39 -0
  19. oscura/analyzers/ml/features.py +600 -0
  20. oscura/analyzers/ml/signal_classifier.py +604 -0
  21. oscura/analyzers/packet/daq.py +246 -158
  22. oscura/analyzers/packet/parser.py +12 -1
  23. oscura/analyzers/packet/payload.py +50 -2110
  24. oscura/analyzers/packet/payload_analysis.py +361 -181
  25. oscura/analyzers/packet/payload_patterns.py +133 -70
  26. oscura/analyzers/packet/stream.py +84 -23
  27. oscura/analyzers/patterns/__init__.py +26 -5
  28. oscura/analyzers/patterns/anomaly_detection.py +908 -0
  29. oscura/analyzers/patterns/clustering.py +169 -108
  30. oscura/analyzers/patterns/clustering_optimized.py +227 -0
  31. oscura/analyzers/patterns/discovery.py +1 -1
  32. oscura/analyzers/patterns/matching.py +581 -197
  33. oscura/analyzers/patterns/pattern_mining.py +778 -0
  34. oscura/analyzers/patterns/periodic.py +121 -38
  35. oscura/analyzers/patterns/sequences.py +175 -78
  36. oscura/analyzers/power/conduction.py +1 -1
  37. oscura/analyzers/power/soa.py +6 -6
  38. oscura/analyzers/power/switching.py +250 -110
  39. oscura/analyzers/protocol/__init__.py +17 -1
  40. oscura/analyzers/protocols/__init__.py +1 -22
  41. oscura/analyzers/protocols/base.py +6 -6
  42. oscura/analyzers/protocols/ble/__init__.py +38 -0
  43. oscura/analyzers/protocols/ble/analyzer.py +809 -0
  44. oscura/analyzers/protocols/ble/uuids.py +288 -0
  45. oscura/analyzers/protocols/can.py +257 -127
  46. oscura/analyzers/protocols/can_fd.py +107 -80
  47. oscura/analyzers/protocols/flexray.py +139 -80
  48. oscura/analyzers/protocols/hdlc.py +93 -58
  49. oscura/analyzers/protocols/i2c.py +247 -106
  50. oscura/analyzers/protocols/i2s.py +138 -86
  51. oscura/analyzers/protocols/industrial/__init__.py +40 -0
  52. oscura/analyzers/protocols/industrial/bacnet/__init__.py +33 -0
  53. oscura/analyzers/protocols/industrial/bacnet/analyzer.py +708 -0
  54. oscura/analyzers/protocols/industrial/bacnet/encoding.py +412 -0
  55. oscura/analyzers/protocols/industrial/bacnet/services.py +622 -0
  56. oscura/analyzers/protocols/industrial/ethercat/__init__.py +30 -0
  57. oscura/analyzers/protocols/industrial/ethercat/analyzer.py +474 -0
  58. oscura/analyzers/protocols/industrial/ethercat/mailbox.py +339 -0
  59. oscura/analyzers/protocols/industrial/ethercat/topology.py +166 -0
  60. oscura/analyzers/protocols/industrial/modbus/__init__.py +31 -0
  61. oscura/analyzers/protocols/industrial/modbus/analyzer.py +525 -0
  62. oscura/analyzers/protocols/industrial/modbus/crc.py +79 -0
  63. oscura/analyzers/protocols/industrial/modbus/functions.py +436 -0
  64. oscura/analyzers/protocols/industrial/opcua/__init__.py +21 -0
  65. oscura/analyzers/protocols/industrial/opcua/analyzer.py +552 -0
  66. oscura/analyzers/protocols/industrial/opcua/datatypes.py +446 -0
  67. oscura/analyzers/protocols/industrial/opcua/services.py +264 -0
  68. oscura/analyzers/protocols/industrial/profinet/__init__.py +23 -0
  69. oscura/analyzers/protocols/industrial/profinet/analyzer.py +441 -0
  70. oscura/analyzers/protocols/industrial/profinet/dcp.py +263 -0
  71. oscura/analyzers/protocols/industrial/profinet/ptcp.py +200 -0
  72. oscura/analyzers/protocols/jtag.py +180 -98
  73. oscura/analyzers/protocols/lin.py +219 -114
  74. oscura/analyzers/protocols/manchester.py +4 -4
  75. oscura/analyzers/protocols/onewire.py +253 -149
  76. oscura/analyzers/protocols/parallel_bus/__init__.py +20 -0
  77. oscura/analyzers/protocols/parallel_bus/centronics.py +92 -0
  78. oscura/analyzers/protocols/parallel_bus/gpib.py +137 -0
  79. oscura/analyzers/protocols/spi.py +192 -95
  80. oscura/analyzers/protocols/swd.py +321 -167
  81. oscura/analyzers/protocols/uart.py +267 -125
  82. oscura/analyzers/protocols/usb.py +235 -131
  83. oscura/analyzers/side_channel/power.py +17 -12
  84. oscura/analyzers/signal/__init__.py +15 -0
  85. oscura/analyzers/signal/timing_analysis.py +1086 -0
  86. oscura/analyzers/signal_integrity/__init__.py +4 -1
  87. oscura/analyzers/signal_integrity/sparams.py +2 -19
  88. oscura/analyzers/spectral/chunked.py +129 -60
  89. oscura/analyzers/spectral/chunked_fft.py +300 -94
  90. oscura/analyzers/spectral/chunked_wavelet.py +100 -80
  91. oscura/analyzers/statistical/checksum.py +376 -217
  92. oscura/analyzers/statistical/classification.py +229 -107
  93. oscura/analyzers/statistical/entropy.py +78 -53
  94. oscura/analyzers/statistics/correlation.py +407 -211
  95. oscura/analyzers/statistics/outliers.py +2 -2
  96. oscura/analyzers/statistics/streaming.py +30 -5
  97. oscura/analyzers/validation.py +216 -101
  98. oscura/analyzers/waveform/measurements.py +9 -0
  99. oscura/analyzers/waveform/measurements_with_uncertainty.py +31 -15
  100. oscura/analyzers/waveform/spectral.py +500 -228
  101. oscura/api/__init__.py +31 -5
  102. oscura/api/dsl/__init__.py +582 -0
  103. oscura/{dsl → api/dsl}/commands.py +43 -76
  104. oscura/{dsl → api/dsl}/interpreter.py +26 -51
  105. oscura/{dsl → api/dsl}/parser.py +107 -77
  106. oscura/{dsl → api/dsl}/repl.py +2 -2
  107. oscura/api/dsl.py +1 -1
  108. oscura/{integrations → api/integrations}/__init__.py +1 -1
  109. oscura/{integrations → api/integrations}/llm.py +201 -102
  110. oscura/api/operators.py +3 -3
  111. oscura/api/optimization.py +144 -30
  112. oscura/api/rest_server.py +921 -0
  113. oscura/api/server/__init__.py +17 -0
  114. oscura/api/server/dashboard.py +850 -0
  115. oscura/api/server/static/README.md +34 -0
  116. oscura/api/server/templates/base.html +181 -0
  117. oscura/api/server/templates/export.html +120 -0
  118. oscura/api/server/templates/home.html +284 -0
  119. oscura/api/server/templates/protocols.html +58 -0
  120. oscura/api/server/templates/reports.html +43 -0
  121. oscura/api/server/templates/session_detail.html +89 -0
  122. oscura/api/server/templates/sessions.html +83 -0
  123. oscura/api/server/templates/waveforms.html +73 -0
  124. oscura/automotive/__init__.py +8 -1
  125. oscura/automotive/can/__init__.py +10 -0
  126. oscura/automotive/can/checksum.py +3 -1
  127. oscura/automotive/can/dbc_generator.py +590 -0
  128. oscura/automotive/can/message_wrapper.py +121 -74
  129. oscura/automotive/can/patterns.py +98 -21
  130. oscura/automotive/can/session.py +292 -56
  131. oscura/automotive/can/state_machine.py +6 -3
  132. oscura/automotive/can/stimulus_response.py +97 -75
  133. oscura/automotive/dbc/__init__.py +10 -2
  134. oscura/automotive/dbc/generator.py +84 -56
  135. oscura/automotive/dbc/parser.py +6 -6
  136. oscura/automotive/dtc/data.json +2763 -0
  137. oscura/automotive/dtc/database.py +2 -2
  138. oscura/automotive/flexray/__init__.py +31 -0
  139. oscura/automotive/flexray/analyzer.py +504 -0
  140. oscura/automotive/flexray/crc.py +185 -0
  141. oscura/automotive/flexray/fibex.py +449 -0
  142. oscura/automotive/j1939/__init__.py +45 -8
  143. oscura/automotive/j1939/analyzer.py +605 -0
  144. oscura/automotive/j1939/spns.py +326 -0
  145. oscura/automotive/j1939/transport.py +306 -0
  146. oscura/automotive/lin/__init__.py +47 -0
  147. oscura/automotive/lin/analyzer.py +612 -0
  148. oscura/automotive/loaders/blf.py +13 -2
  149. oscura/automotive/loaders/csv_can.py +143 -72
  150. oscura/automotive/loaders/dispatcher.py +50 -2
  151. oscura/automotive/loaders/mdf.py +86 -45
  152. oscura/automotive/loaders/pcap.py +111 -61
  153. oscura/automotive/uds/__init__.py +4 -0
  154. oscura/automotive/uds/analyzer.py +725 -0
  155. oscura/automotive/uds/decoder.py +140 -58
  156. oscura/automotive/uds/models.py +7 -1
  157. oscura/automotive/visualization.py +1 -1
  158. oscura/cli/analyze.py +348 -0
  159. oscura/cli/batch.py +142 -122
  160. oscura/cli/benchmark.py +275 -0
  161. oscura/cli/characterize.py +137 -82
  162. oscura/cli/compare.py +224 -131
  163. oscura/cli/completion.py +250 -0
  164. oscura/cli/config_cmd.py +361 -0
  165. oscura/cli/decode.py +164 -87
  166. oscura/cli/export.py +286 -0
  167. oscura/cli/main.py +115 -31
  168. oscura/{onboarding → cli/onboarding}/__init__.py +3 -3
  169. oscura/{onboarding → cli/onboarding}/help.py +80 -58
  170. oscura/{onboarding → cli/onboarding}/tutorials.py +97 -72
  171. oscura/{onboarding → cli/onboarding}/wizard.py +55 -36
  172. oscura/cli/progress.py +147 -0
  173. oscura/cli/shell.py +157 -135
  174. oscura/cli/validate_cmd.py +204 -0
  175. oscura/cli/visualize.py +158 -0
  176. oscura/convenience.py +125 -79
  177. oscura/core/__init__.py +4 -2
  178. oscura/core/backend_selector.py +3 -3
  179. oscura/core/cache.py +126 -15
  180. oscura/core/cancellation.py +1 -1
  181. oscura/{config → core/config}/__init__.py +20 -11
  182. oscura/{config → core/config}/defaults.py +1 -1
  183. oscura/{config → core/config}/loader.py +7 -5
  184. oscura/{config → core/config}/memory.py +5 -5
  185. oscura/{config → core/config}/migration.py +1 -1
  186. oscura/{config → core/config}/pipeline.py +99 -23
  187. oscura/{config → core/config}/preferences.py +1 -1
  188. oscura/{config → core/config}/protocol.py +3 -3
  189. oscura/{config → core/config}/schema.py +426 -272
  190. oscura/{config → core/config}/settings.py +1 -1
  191. oscura/{config → core/config}/thresholds.py +195 -153
  192. oscura/core/correlation.py +5 -6
  193. oscura/core/cross_domain.py +0 -2
  194. oscura/core/debug.py +9 -5
  195. oscura/{extensibility → core/extensibility}/docs.py +158 -70
  196. oscura/{extensibility → core/extensibility}/extensions.py +160 -76
  197. oscura/{extensibility → core/extensibility}/logging.py +1 -1
  198. oscura/{extensibility → core/extensibility}/measurements.py +1 -1
  199. oscura/{extensibility → core/extensibility}/plugins.py +1 -1
  200. oscura/{extensibility → core/extensibility}/templates.py +73 -3
  201. oscura/{extensibility → core/extensibility}/validation.py +1 -1
  202. oscura/core/gpu_backend.py +11 -7
  203. oscura/core/log_query.py +101 -11
  204. oscura/core/logging.py +126 -54
  205. oscura/core/logging_advanced.py +5 -5
  206. oscura/core/memory_limits.py +108 -70
  207. oscura/core/memory_monitor.py +2 -2
  208. oscura/core/memory_progress.py +7 -7
  209. oscura/core/memory_warnings.py +1 -1
  210. oscura/core/numba_backend.py +13 -13
  211. oscura/{plugins → core/plugins}/__init__.py +9 -9
  212. oscura/{plugins → core/plugins}/base.py +7 -7
  213. oscura/{plugins → core/plugins}/cli.py +3 -3
  214. oscura/{plugins → core/plugins}/discovery.py +186 -106
  215. oscura/{plugins → core/plugins}/lifecycle.py +1 -1
  216. oscura/{plugins → core/plugins}/manager.py +7 -7
  217. oscura/{plugins → core/plugins}/registry.py +3 -3
  218. oscura/{plugins → core/plugins}/versioning.py +1 -1
  219. oscura/core/progress.py +16 -1
  220. oscura/core/provenance.py +8 -2
  221. oscura/{schemas → core/schemas}/__init__.py +2 -2
  222. oscura/core/schemas/bus_configuration.json +322 -0
  223. oscura/core/schemas/device_mapping.json +182 -0
  224. oscura/core/schemas/packet_format.json +418 -0
  225. oscura/core/schemas/protocol_definition.json +363 -0
  226. oscura/core/types.py +4 -0
  227. oscura/core/uncertainty.py +3 -3
  228. oscura/correlation/__init__.py +52 -0
  229. oscura/correlation/multi_protocol.py +811 -0
  230. oscura/discovery/auto_decoder.py +117 -35
  231. oscura/discovery/comparison.py +191 -86
  232. oscura/discovery/quality_validator.py +155 -68
  233. oscura/discovery/signal_detector.py +196 -79
  234. oscura/export/__init__.py +18 -20
  235. oscura/export/kaitai_struct.py +513 -0
  236. oscura/export/scapy_layer.py +801 -0
  237. oscura/export/wireshark/README.md +15 -15
  238. oscura/export/wireshark/generator.py +1 -1
  239. oscura/export/wireshark/templates/dissector.lua.j2 +2 -2
  240. oscura/export/wireshark_dissector.py +746 -0
  241. oscura/guidance/wizard.py +207 -111
  242. oscura/hardware/__init__.py +19 -0
  243. oscura/{acquisition → hardware/acquisition}/__init__.py +4 -4
  244. oscura/{acquisition → hardware/acquisition}/file.py +2 -2
  245. oscura/{acquisition → hardware/acquisition}/hardware.py +7 -7
  246. oscura/{acquisition → hardware/acquisition}/saleae.py +15 -12
  247. oscura/{acquisition → hardware/acquisition}/socketcan.py +1 -1
  248. oscura/{acquisition → hardware/acquisition}/streaming.py +2 -2
  249. oscura/{acquisition → hardware/acquisition}/synthetic.py +3 -3
  250. oscura/{acquisition → hardware/acquisition}/visa.py +33 -11
  251. oscura/hardware/firmware/__init__.py +29 -0
  252. oscura/hardware/firmware/pattern_recognition.py +874 -0
  253. oscura/hardware/hal_detector.py +736 -0
  254. oscura/hardware/security/__init__.py +37 -0
  255. oscura/hardware/security/side_channel_detector.py +1126 -0
  256. oscura/inference/__init__.py +4 -0
  257. oscura/inference/active_learning/README.md +7 -7
  258. oscura/inference/active_learning/observation_table.py +4 -1
  259. oscura/inference/alignment.py +216 -123
  260. oscura/inference/bayesian.py +113 -33
  261. oscura/inference/crc_reverse.py +101 -55
  262. oscura/inference/logic.py +6 -2
  263. oscura/inference/message_format.py +342 -183
  264. oscura/inference/protocol.py +95 -44
  265. oscura/inference/protocol_dsl.py +180 -82
  266. oscura/inference/signal_intelligence.py +1439 -706
  267. oscura/inference/spectral.py +99 -57
  268. oscura/inference/state_machine.py +810 -158
  269. oscura/inference/stream.py +270 -110
  270. oscura/iot/__init__.py +34 -0
  271. oscura/iot/coap/__init__.py +32 -0
  272. oscura/iot/coap/analyzer.py +668 -0
  273. oscura/iot/coap/options.py +212 -0
  274. oscura/iot/lorawan/__init__.py +21 -0
  275. oscura/iot/lorawan/crypto.py +206 -0
  276. oscura/iot/lorawan/decoder.py +801 -0
  277. oscura/iot/lorawan/mac_commands.py +341 -0
  278. oscura/iot/mqtt/__init__.py +27 -0
  279. oscura/iot/mqtt/analyzer.py +999 -0
  280. oscura/iot/mqtt/properties.py +315 -0
  281. oscura/iot/zigbee/__init__.py +31 -0
  282. oscura/iot/zigbee/analyzer.py +615 -0
  283. oscura/iot/zigbee/security.py +153 -0
  284. oscura/iot/zigbee/zcl.py +349 -0
  285. oscura/jupyter/display.py +125 -45
  286. oscura/{exploratory → jupyter/exploratory}/__init__.py +8 -8
  287. oscura/{exploratory → jupyter/exploratory}/error_recovery.py +298 -141
  288. oscura/jupyter/exploratory/fuzzy.py +746 -0
  289. oscura/{exploratory → jupyter/exploratory}/fuzzy_advanced.py +258 -100
  290. oscura/{exploratory → jupyter/exploratory}/legacy.py +464 -242
  291. oscura/{exploratory → jupyter/exploratory}/parse.py +167 -145
  292. oscura/{exploratory → jupyter/exploratory}/recovery.py +119 -87
  293. oscura/jupyter/exploratory/sync.py +612 -0
  294. oscura/{exploratory → jupyter/exploratory}/unknown.py +299 -176
  295. oscura/jupyter/magic.py +4 -4
  296. oscura/{ui → jupyter/ui}/__init__.py +2 -2
  297. oscura/{ui → jupyter/ui}/formatters.py +3 -3
  298. oscura/{ui → jupyter/ui}/progressive_display.py +153 -82
  299. oscura/loaders/__init__.py +171 -63
  300. oscura/loaders/binary.py +88 -1
  301. oscura/loaders/chipwhisperer.py +153 -137
  302. oscura/loaders/configurable.py +208 -86
  303. oscura/loaders/csv_loader.py +458 -215
  304. oscura/loaders/hdf5_loader.py +278 -119
  305. oscura/loaders/lazy.py +87 -54
  306. oscura/loaders/mmap_loader.py +1 -1
  307. oscura/loaders/numpy_loader.py +253 -116
  308. oscura/loaders/pcap.py +226 -151
  309. oscura/loaders/rigol.py +110 -49
  310. oscura/loaders/sigrok.py +201 -78
  311. oscura/loaders/tdms.py +81 -58
  312. oscura/loaders/tektronix.py +291 -174
  313. oscura/loaders/touchstone.py +182 -87
  314. oscura/loaders/vcd.py +215 -117
  315. oscura/loaders/wav.py +155 -68
  316. oscura/reporting/__init__.py +9 -7
  317. oscura/reporting/analyze.py +352 -146
  318. oscura/reporting/argument_preparer.py +69 -14
  319. oscura/reporting/auto_report.py +97 -61
  320. oscura/reporting/batch.py +131 -58
  321. oscura/reporting/chart_selection.py +57 -45
  322. oscura/reporting/comparison.py +63 -17
  323. oscura/reporting/content/executive.py +76 -24
  324. oscura/reporting/core_formats/multi_format.py +11 -8
  325. oscura/reporting/engine.py +312 -158
  326. oscura/reporting/enhanced_reports.py +949 -0
  327. oscura/reporting/export.py +86 -43
  328. oscura/reporting/formatting/numbers.py +69 -42
  329. oscura/reporting/html.py +139 -58
  330. oscura/reporting/index.py +137 -65
  331. oscura/reporting/output.py +158 -67
  332. oscura/reporting/pdf.py +67 -102
  333. oscura/reporting/plots.py +191 -112
  334. oscura/reporting/sections.py +88 -47
  335. oscura/reporting/standards.py +104 -61
  336. oscura/reporting/summary_generator.py +75 -55
  337. oscura/reporting/tables.py +138 -54
  338. oscura/reporting/templates/enhanced/protocol_re.html +525 -0
  339. oscura/reporting/templates/index.md +13 -13
  340. oscura/sessions/__init__.py +14 -23
  341. oscura/sessions/base.py +3 -3
  342. oscura/sessions/blackbox.py +106 -10
  343. oscura/sessions/generic.py +2 -2
  344. oscura/sessions/legacy.py +783 -0
  345. oscura/side_channel/__init__.py +63 -0
  346. oscura/side_channel/dpa.py +1025 -0
  347. oscura/utils/__init__.py +15 -1
  348. oscura/utils/autodetect.py +1 -5
  349. oscura/utils/bitwise.py +118 -0
  350. oscura/{builders → utils/builders}/__init__.py +1 -1
  351. oscura/{comparison → utils/comparison}/__init__.py +6 -6
  352. oscura/{comparison → utils/comparison}/compare.py +202 -101
  353. oscura/{comparison → utils/comparison}/golden.py +83 -63
  354. oscura/{comparison → utils/comparison}/limits.py +313 -89
  355. oscura/{comparison → utils/comparison}/mask.py +151 -45
  356. oscura/{comparison → utils/comparison}/trace_diff.py +1 -1
  357. oscura/{comparison → utils/comparison}/visualization.py +147 -89
  358. oscura/{component → utils/component}/__init__.py +3 -3
  359. oscura/{component → utils/component}/impedance.py +122 -58
  360. oscura/{component → utils/component}/reactive.py +165 -168
  361. oscura/{component → utils/component}/transmission_line.py +3 -3
  362. oscura/{filtering → utils/filtering}/__init__.py +6 -6
  363. oscura/{filtering → utils/filtering}/base.py +1 -1
  364. oscura/{filtering → utils/filtering}/convenience.py +2 -2
  365. oscura/{filtering → utils/filtering}/design.py +169 -93
  366. oscura/{filtering → utils/filtering}/filters.py +2 -2
  367. oscura/{filtering → utils/filtering}/introspection.py +2 -2
  368. oscura/utils/geometry.py +31 -0
  369. oscura/utils/imports.py +184 -0
  370. oscura/utils/lazy.py +1 -1
  371. oscura/{math → utils/math}/__init__.py +2 -2
  372. oscura/{math → utils/math}/arithmetic.py +114 -48
  373. oscura/{math → utils/math}/interpolation.py +139 -106
  374. oscura/utils/memory.py +129 -66
  375. oscura/utils/memory_advanced.py +92 -9
  376. oscura/utils/memory_extensions.py +10 -8
  377. oscura/{optimization → utils/optimization}/__init__.py +1 -1
  378. oscura/{optimization → utils/optimization}/search.py +2 -2
  379. oscura/utils/performance/__init__.py +58 -0
  380. oscura/utils/performance/caching.py +889 -0
  381. oscura/utils/performance/lsh_clustering.py +333 -0
  382. oscura/utils/performance/memory_optimizer.py +699 -0
  383. oscura/utils/performance/optimizations.py +675 -0
  384. oscura/utils/performance/parallel.py +654 -0
  385. oscura/utils/performance/profiling.py +661 -0
  386. oscura/{pipeline → utils/pipeline}/base.py +1 -1
  387. oscura/{pipeline → utils/pipeline}/composition.py +11 -3
  388. oscura/{pipeline → utils/pipeline}/parallel.py +3 -2
  389. oscura/{pipeline → utils/pipeline}/pipeline.py +1 -1
  390. oscura/{pipeline → utils/pipeline}/reverse_engineering.py +412 -221
  391. oscura/{search → utils/search}/__init__.py +3 -3
  392. oscura/{search → utils/search}/anomaly.py +188 -58
  393. oscura/utils/search/context.py +294 -0
  394. oscura/{search → utils/search}/pattern.py +138 -10
  395. oscura/utils/serial.py +51 -0
  396. oscura/utils/storage/__init__.py +61 -0
  397. oscura/utils/storage/database.py +1166 -0
  398. oscura/{streaming → utils/streaming}/chunked.py +302 -143
  399. oscura/{streaming → utils/streaming}/progressive.py +1 -1
  400. oscura/{streaming → utils/streaming}/realtime.py +3 -2
  401. oscura/{triggering → utils/triggering}/__init__.py +6 -6
  402. oscura/{triggering → utils/triggering}/base.py +6 -6
  403. oscura/{triggering → utils/triggering}/edge.py +2 -2
  404. oscura/{triggering → utils/triggering}/pattern.py +2 -2
  405. oscura/{triggering → utils/triggering}/pulse.py +115 -74
  406. oscura/{triggering → utils/triggering}/window.py +2 -2
  407. oscura/utils/validation.py +32 -0
  408. oscura/validation/__init__.py +121 -0
  409. oscura/{compliance → validation/compliance}/__init__.py +5 -5
  410. oscura/{compliance → validation/compliance}/advanced.py +5 -5
  411. oscura/{compliance → validation/compliance}/masks.py +1 -1
  412. oscura/{compliance → validation/compliance}/reporting.py +127 -53
  413. oscura/{compliance → validation/compliance}/testing.py +114 -52
  414. oscura/validation/compliance_tests.py +915 -0
  415. oscura/validation/fuzzer.py +990 -0
  416. oscura/validation/grammar_tests.py +596 -0
  417. oscura/validation/grammar_validator.py +904 -0
  418. oscura/validation/hil_testing.py +977 -0
  419. oscura/{quality → validation/quality}/__init__.py +4 -4
  420. oscura/{quality → validation/quality}/ensemble.py +251 -171
  421. oscura/{quality → validation/quality}/explainer.py +3 -3
  422. oscura/{quality → validation/quality}/scoring.py +1 -1
  423. oscura/{quality → validation/quality}/warnings.py +4 -4
  424. oscura/validation/regression_suite.py +808 -0
  425. oscura/validation/replay.py +788 -0
  426. oscura/{testing → validation/testing}/__init__.py +2 -2
  427. oscura/{testing → validation/testing}/synthetic.py +5 -5
  428. oscura/visualization/__init__.py +9 -0
  429. oscura/visualization/accessibility.py +1 -1
  430. oscura/visualization/annotations.py +64 -67
  431. oscura/visualization/colors.py +7 -7
  432. oscura/visualization/digital.py +180 -81
  433. oscura/visualization/eye.py +236 -85
  434. oscura/visualization/interactive.py +320 -143
  435. oscura/visualization/jitter.py +587 -247
  436. oscura/visualization/layout.py +169 -134
  437. oscura/visualization/optimization.py +103 -52
  438. oscura/visualization/palettes.py +1 -1
  439. oscura/visualization/power.py +427 -211
  440. oscura/visualization/power_extended.py +626 -297
  441. oscura/visualization/presets.py +2 -0
  442. oscura/visualization/protocols.py +495 -181
  443. oscura/visualization/render.py +79 -63
  444. oscura/visualization/reverse_engineering.py +171 -124
  445. oscura/visualization/signal_integrity.py +460 -279
  446. oscura/visualization/specialized.py +190 -100
  447. oscura/visualization/spectral.py +670 -255
  448. oscura/visualization/thumbnails.py +166 -137
  449. oscura/visualization/waveform.py +150 -63
  450. oscura/workflows/__init__.py +3 -0
  451. oscura/{batch → workflows/batch}/__init__.py +5 -5
  452. oscura/{batch → workflows/batch}/advanced.py +150 -75
  453. oscura/workflows/batch/aggregate.py +531 -0
  454. oscura/workflows/batch/analyze.py +236 -0
  455. oscura/{batch → workflows/batch}/logging.py +2 -2
  456. oscura/{batch → workflows/batch}/metrics.py +1 -1
  457. oscura/workflows/complete_re.py +1144 -0
  458. oscura/workflows/compliance.py +44 -54
  459. oscura/workflows/digital.py +197 -51
  460. oscura/workflows/legacy/__init__.py +12 -0
  461. oscura/{workflow → workflows/legacy}/dag.py +4 -1
  462. oscura/workflows/multi_trace.py +9 -9
  463. oscura/workflows/power.py +42 -62
  464. oscura/workflows/protocol.py +82 -49
  465. oscura/workflows/reverse_engineering.py +351 -150
  466. oscura/workflows/signal_integrity.py +157 -82
  467. oscura-0.6.0.dist-info/METADATA +643 -0
  468. oscura-0.6.0.dist-info/RECORD +590 -0
  469. oscura/analyzers/digital/ic_database.py +0 -498
  470. oscura/analyzers/digital/timing_paths.py +0 -339
  471. oscura/analyzers/digital/vintage.py +0 -377
  472. oscura/analyzers/digital/vintage_result.py +0 -148
  473. oscura/analyzers/protocols/parallel_bus.py +0 -449
  474. oscura/batch/aggregate.py +0 -300
  475. oscura/batch/analyze.py +0 -139
  476. oscura/dsl/__init__.py +0 -73
  477. oscura/exceptions.py +0 -59
  478. oscura/exploratory/fuzzy.py +0 -513
  479. oscura/exploratory/sync.py +0 -384
  480. oscura/export/wavedrom.py +0 -430
  481. oscura/exporters/__init__.py +0 -94
  482. oscura/exporters/csv.py +0 -303
  483. oscura/exporters/exporters.py +0 -44
  484. oscura/exporters/hdf5.py +0 -217
  485. oscura/exporters/html_export.py +0 -701
  486. oscura/exporters/json_export.py +0 -338
  487. oscura/exporters/markdown_export.py +0 -367
  488. oscura/exporters/matlab_export.py +0 -354
  489. oscura/exporters/npz_export.py +0 -219
  490. oscura/exporters/spice_export.py +0 -210
  491. oscura/exporters/vintage_logic_csv.py +0 -247
  492. oscura/reporting/vintage_logic_report.py +0 -523
  493. oscura/search/context.py +0 -149
  494. oscura/session/__init__.py +0 -34
  495. oscura/session/annotations.py +0 -289
  496. oscura/session/history.py +0 -313
  497. oscura/session/session.py +0 -520
  498. oscura/visualization/digital_advanced.py +0 -718
  499. oscura/visualization/figure_manager.py +0 -156
  500. oscura/workflow/__init__.py +0 -13
  501. oscura-0.5.0.dist-info/METADATA +0 -407
  502. oscura-0.5.0.dist-info/RECORD +0 -486
  503. /oscura/core/{config.py → config/legacy.py} +0 -0
  504. /oscura/{extensibility → core/extensibility}/__init__.py +0 -0
  505. /oscura/{extensibility → core/extensibility}/registry.py +0 -0
  506. /oscura/{plugins → core/plugins}/isolation.py +0 -0
  507. /oscura/{builders → utils/builders}/signal_builder.py +0 -0
  508. /oscura/{optimization → utils/optimization}/parallel.py +0 -0
  509. /oscura/{pipeline → utils/pipeline}/__init__.py +0 -0
  510. /oscura/{streaming → utils/streaming}/__init__.py +0 -0
  511. {oscura-0.5.0.dist-info → oscura-0.6.0.dist-info}/WHEEL +0 -0
  512. {oscura-0.5.0.dist-info → oscura-0.6.0.dist-info}/entry_points.txt +0 -0
  513. {oscura-0.5.0.dist-info → oscura-0.6.0.dist-info}/licenses/LICENSE +0 -0
oscura/exporters/csv.py DELETED
@@ -1,303 +0,0 @@
1
- """CSV export functionality.
2
-
3
- This module provides trace and measurement export to CSV format.
4
-
5
-
6
- Example:
7
- >>> from oscura.exporters.csv import export_csv
8
- >>> export_csv(trace, "output.csv")
9
- >>> export_csv(measurements, "results.csv")
10
-
11
- References:
12
- RFC 4180 (CSV format)
13
- """
14
-
15
- from __future__ import annotations
16
-
17
- import csv
18
- from pathlib import Path
19
- from typing import TYPE_CHECKING, Any
20
-
21
- import numpy as np
22
-
23
- from oscura.core.types import DigitalTrace, WaveformTrace
24
-
25
- if TYPE_CHECKING:
26
- from numpy.typing import NDArray
27
-
28
-
29
- def export_csv(
30
- data: WaveformTrace | DigitalTrace | dict[str, Any] | NDArray[Any],
31
- path: str | Path,
32
- *,
33
- include_time: bool = True,
34
- time_unit: str = "s",
35
- precision: int = 9,
36
- delimiter: str = ",",
37
- header: bool = True,
38
- ) -> None:
39
- """Export data to CSV format.
40
-
41
- Args:
42
- data: Data to export. Can be:
43
- - WaveformTrace or DigitalTrace (with metadata as comments)
44
- - Dictionary of measurements
45
- - NumPy array
46
- path: Output file path.
47
- include_time: Include time column for traces.
48
- time_unit: Time unit ("s", "ms", "us", "ns").
49
- precision: Decimal precision for floating point values.
50
- delimiter: Column delimiter.
51
- header: Include header row and metadata comments.
52
-
53
- Raises:
54
- TypeError: If data type is not supported.
55
-
56
- Example:
57
- >>> export_csv(trace, "waveform.csv")
58
- >>> export_csv(trace, "data.csv", precision=6, delimiter="\t")
59
- >>> export_csv(measurements, "results.csv")
60
-
61
- Note:
62
- When exporting traces, metadata is included as comment lines
63
- starting with '#' when header=True.
64
-
65
- References:
66
- EXP-001
67
- """
68
- path = Path(path)
69
-
70
- if isinstance(data, WaveformTrace | DigitalTrace):
71
- _export_trace(data, path, include_time, time_unit, precision, delimiter, header)
72
- elif isinstance(data, dict):
73
- _export_dict(data, path, precision, delimiter, header)
74
- elif isinstance(data, np.ndarray):
75
- _export_array(data, path, precision, delimiter, header)
76
- else:
77
- raise TypeError(f"Unsupported data type: {type(data)}")
78
-
79
-
80
- def _export_trace(
81
- trace: WaveformTrace | DigitalTrace,
82
- path: Path,
83
- include_time: bool,
84
- time_unit: str,
85
- precision: int,
86
- delimiter: str,
87
- header: bool,
88
- ) -> None:
89
- """Export trace to CSV.
90
-
91
- Args:
92
- trace: Trace to export.
93
- path: Output file path.
94
- include_time: Include time column.
95
- time_unit: Time unit for column.
96
- precision: Decimal precision.
97
- delimiter: Column delimiter.
98
- header: Include header row.
99
- """
100
- time_multipliers = {"s": 1.0, "ms": 1e3, "us": 1e6, "ns": 1e9}
101
- multiplier = time_multipliers.get(time_unit, 1.0)
102
-
103
- with open(path, "w", newline="") as f:
104
- writer = csv.writer(f, delimiter=delimiter)
105
-
106
- # Write metadata as comments if header is enabled
107
- if header:
108
- # Metadata comments
109
- meta = trace.metadata
110
- f.write("# Oscura CSV Export\n")
111
- f.write(f"# Sample Rate: {meta.sample_rate} Hz\n")
112
- f.write(f"# Time Base: {meta.time_base} s\n")
113
- f.write(f"# Samples: {len(trace.data)}\n")
114
- f.write(f"# Duration: {trace.duration} s\n")
115
-
116
- if meta.vertical_scale is not None:
117
- f.write(f"# Vertical Scale: {meta.vertical_scale} V/div\n")
118
- if meta.vertical_offset is not None:
119
- f.write(f"# Vertical Offset: {meta.vertical_offset} V\n")
120
- if meta.acquisition_time is not None:
121
- f.write(f"# Acquisition Time: {meta.acquisition_time.isoformat()}\n")
122
- if meta.source_file is not None:
123
- f.write(f"# Source File: {meta.source_file}\n")
124
- if meta.channel_name is not None:
125
- f.write(f"# Channel: {meta.channel_name}\n")
126
-
127
- f.write("#\n")
128
-
129
- # Column headers
130
- if include_time:
131
- if isinstance(trace, WaveformTrace):
132
- writer.writerow([f"Time ({time_unit})", "Voltage"])
133
- else:
134
- writer.writerow([f"Time ({time_unit})", "Digital"])
135
- elif isinstance(trace, WaveformTrace):
136
- writer.writerow(["Voltage"])
137
- else:
138
- writer.writerow(["Digital"])
139
-
140
- # Data
141
- n_samples = len(trace.data)
142
- time_base = trace.metadata.time_base
143
-
144
- for i in range(n_samples):
145
- if include_time:
146
- time_val = i * time_base * multiplier
147
- if isinstance(trace, WaveformTrace):
148
- writer.writerow([f"{time_val:.{precision}g}", f"{trace.data[i]:.{precision}g}"])
149
- else:
150
- writer.writerow([f"{time_val:.{precision}g}", int(trace.data[i])])
151
- elif isinstance(trace, WaveformTrace):
152
- writer.writerow([f"{trace.data[i]:.{precision}g}"])
153
- else:
154
- writer.writerow([int(trace.data[i])])
155
-
156
-
157
- def _export_dict(
158
- data: dict[str, Any],
159
- path: Path,
160
- precision: int,
161
- delimiter: str,
162
- header: bool,
163
- ) -> None:
164
- """Export dictionary to CSV.
165
-
166
- Args:
167
- data: Dictionary to export.
168
- path: Output file path.
169
- precision: Decimal precision.
170
- delimiter: Column delimiter.
171
- header: Include header row.
172
- """
173
- with open(path, "w", newline="") as f:
174
- writer = csv.writer(f, delimiter=delimiter)
175
-
176
- if header:
177
- writer.writerow(["Parameter", "Value", "Unit"])
178
-
179
- for key, value in data.items():
180
- if isinstance(value, dict):
181
- # Nested dict with value/unit
182
- val = value.get("value", value)
183
- unit = value.get("unit", "")
184
- if isinstance(val, float):
185
- writer.writerow([key, f"{val:.{precision}g}", unit])
186
- else:
187
- writer.writerow([key, val, unit])
188
- elif isinstance(value, float):
189
- writer.writerow([key, f"{value:.{precision}g}", ""])
190
- else:
191
- writer.writerow([key, value, ""])
192
-
193
-
194
- def _export_array(
195
- data: NDArray[Any],
196
- path: Path,
197
- precision: int,
198
- delimiter: str,
199
- header: bool,
200
- ) -> None:
201
- """Export numpy array to CSV.
202
-
203
- Args:
204
- data: NumPy array to export.
205
- path: Output file path.
206
- precision: Decimal precision.
207
- delimiter: Column delimiter.
208
- header: Include header row.
209
- """
210
- # Handle different array dimensions
211
- if data.ndim == 1:
212
- data = data.reshape(-1, 1)
213
-
214
- with open(path, "w", newline="") as f:
215
- writer = csv.writer(f, delimiter=delimiter)
216
-
217
- if header:
218
- cols = [f"Column_{i}" for i in range(data.shape[1])]
219
- writer.writerow(cols)
220
-
221
- for row in data:
222
- formatted = []
223
- for val in row:
224
- if isinstance(val, float | np.floating):
225
- formatted.append(f"{val:.{precision}g}")
226
- else:
227
- formatted.append(str(val)) # type: ignore[unreachable]
228
- writer.writerow(formatted)
229
-
230
-
231
- def export_multi_trace_csv(
232
- traces: list[WaveformTrace | DigitalTrace],
233
- path: str | Path,
234
- *,
235
- names: list[str] | None = None,
236
- include_time: bool = True,
237
- time_unit: str = "s",
238
- precision: int = 9,
239
- ) -> None:
240
- """Export multiple traces to single CSV file.
241
-
242
- Args:
243
- traces: List of traces to export.
244
- path: Output file path.
245
- names: Column names for each trace.
246
- include_time: Include time column.
247
- time_unit: Time unit.
248
- precision: Decimal precision.
249
-
250
- Example:
251
- >>> export_multi_trace_csv([ch1, ch2, ch3], "channels.csv",
252
- ... names=["CH1", "CH2", "CH3"])
253
- """
254
- if len(traces) == 0:
255
- return
256
-
257
- path = Path(path)
258
-
259
- if names is None:
260
- names = [f"Trace_{i}" for i in range(len(traces))]
261
-
262
- # Use first trace for timing
263
- ref_trace = traces[0]
264
- n_samples = len(ref_trace.data)
265
- time_base = ref_trace.metadata.time_base
266
-
267
- time_multipliers = {"s": 1.0, "ms": 1e3, "us": 1e6, "ns": 1e9}
268
- multiplier = time_multipliers.get(time_unit, 1.0)
269
-
270
- with open(path, "w", newline="") as f:
271
- writer = csv.writer(f)
272
-
273
- # Header
274
- header_row = []
275
- if include_time:
276
- header_row.append(f"Time ({time_unit})")
277
- header_row.extend(names)
278
- writer.writerow(header_row)
279
-
280
- # Data
281
- for i in range(n_samples):
282
- row = []
283
-
284
- if include_time:
285
- time_val = i * time_base * multiplier
286
- row.append(f"{time_val:.{precision}g}")
287
-
288
- for trace in traces:
289
- if i < len(trace.data):
290
- if isinstance(trace, WaveformTrace):
291
- row.append(f"{trace.data[i]:.{precision}g}")
292
- else:
293
- row.append(str(int(trace.data[i])))
294
- else:
295
- row.append("")
296
-
297
- writer.writerow(row)
298
-
299
-
300
- __all__ = [
301
- "export_csv",
302
- "export_multi_trace_csv",
303
- ]
@@ -1,44 +0,0 @@
1
- """Exporters namespace module.
2
-
3
- This module provides a namespace for export functions to support:
4
- from oscura.exporters import exporters
5
- exporters.csv(trace, "output.csv")
6
-
7
- Re-exports main export functions with short names.
8
- """
9
-
10
- from oscura.exporters.csv import (
11
- export_csv as csv,
12
- )
13
- from oscura.exporters.hdf5 import (
14
- export_hdf5 as hdf5,
15
- )
16
- from oscura.exporters.html_export import (
17
- export_html as html,
18
- )
19
- from oscura.exporters.json_export import (
20
- export_json as json,
21
- )
22
- from oscura.exporters.markdown_export import (
23
- export_markdown as markdown,
24
- )
25
- from oscura.exporters.matlab_export import (
26
- export_mat as mat,
27
- )
28
- from oscura.exporters.npz_export import (
29
- export_npz as npz,
30
- )
31
- from oscura.exporters.spice_export import (
32
- export_pwl as pwl,
33
- )
34
-
35
- __all__ = [
36
- "csv",
37
- "hdf5",
38
- "html",
39
- "json",
40
- "markdown",
41
- "mat",
42
- "npz",
43
- "pwl",
44
- ]
oscura/exporters/hdf5.py DELETED
@@ -1,217 +0,0 @@
1
- """HDF5 export functionality.
2
-
3
- This module provides trace export to HDF5 format with metadata attributes.
4
-
5
-
6
- Example:
7
- >>> from oscura.exporters.hdf5 import export_hdf5
8
- >>> export_hdf5(trace, "output.h5")
9
-
10
- References:
11
- HDF5 specification (https://www.hdfgroup.org/)
12
- """
13
-
14
- from datetime import datetime
15
- from pathlib import Path
16
- from typing import Any
17
-
18
- import numpy as np
19
-
20
- try:
21
- import h5py
22
-
23
- HAS_H5PY = True
24
- except ImportError:
25
- HAS_H5PY = False
26
-
27
- from oscura.core.types import DigitalTrace, WaveformTrace
28
-
29
-
30
- def export_hdf5(
31
- data: WaveformTrace | DigitalTrace | dict[str, WaveformTrace | DigitalTrace],
32
- path: str | Path,
33
- *,
34
- compression: str | None = "gzip",
35
- compression_opts: int = 4,
36
- include_metadata: bool = True,
37
- ) -> None:
38
- """Export data to HDF5 format.
39
-
40
- Args:
41
- data: Data to export. Can be:
42
- - Single WaveformTrace or DigitalTrace
43
- - Dictionary mapping names to traces
44
- path: Output file path.
45
- compression: Compression algorithm ("gzip", "lzf", None).
46
- compression_opts: Compression level (1-9 for gzip).
47
- include_metadata: Include trace metadata as attributes.
48
-
49
- Raises:
50
- ImportError: If h5py is not installed.
51
-
52
- Example:
53
- >>> export_hdf5(trace, "waveform.h5")
54
- >>> export_hdf5({"ch1": ch1, "ch2": ch2}, "channels.h5")
55
- """
56
- if not HAS_H5PY:
57
- raise ImportError("h5py is required for HDF5 export. Install with: pip install h5py")
58
-
59
- path = Path(path)
60
-
61
- if isinstance(data, WaveformTrace | DigitalTrace):
62
- data = {"trace": data}
63
-
64
- with h5py.File(path, "w") as f:
65
- # Add file-level metadata
66
- f.attrs["created"] = datetime.now().isoformat()
67
- f.attrs["oscura_version"] = "1.0"
68
- f.attrs["format"] = "oscura_hdf5"
69
-
70
- for name, trace in data.items():
71
- _write_trace_dataset(
72
- f,
73
- name,
74
- trace,
75
- compression,
76
- compression_opts,
77
- include_metadata,
78
- )
79
-
80
-
81
- def _write_trace_dataset(
82
- f: "h5py.File",
83
- name: str,
84
- trace: WaveformTrace | DigitalTrace,
85
- compression: str | None,
86
- compression_opts: int,
87
- include_metadata: bool,
88
- ) -> None:
89
- """Write trace to HDF5 dataset.
90
-
91
- Args:
92
- f: HDF5 file object.
93
- name: Dataset name.
94
- trace: Trace to write.
95
- compression: Compression algorithm.
96
- compression_opts: Compression level.
97
- include_metadata: Include metadata as attributes.
98
- """
99
- # Create dataset
100
- dtype = np.float64 if isinstance(trace, WaveformTrace) else np.bool_
101
-
102
- kwargs = {}
103
- if compression:
104
- kwargs["compression"] = compression
105
- if compression == "gzip":
106
- kwargs["compression_opts"] = compression_opts # type: ignore[assignment]
107
-
108
- ds = f.create_dataset(name, data=trace.data.astype(dtype), **kwargs)
109
-
110
- # Add metadata attributes
111
- if include_metadata:
112
- meta = trace.metadata
113
-
114
- ds.attrs["sample_rate"] = meta.sample_rate
115
- ds.attrs["time_base"] = meta.time_base
116
-
117
- if meta.vertical_scale is not None:
118
- ds.attrs["vertical_scale"] = meta.vertical_scale
119
-
120
- if meta.vertical_offset is not None:
121
- ds.attrs["vertical_offset"] = meta.vertical_offset
122
-
123
- if meta.acquisition_time is not None:
124
- ds.attrs["acquisition_time"] = meta.acquisition_time.isoformat()
125
-
126
- if meta.source_file is not None:
127
- ds.attrs["source_file"] = str(meta.source_file)
128
-
129
- if meta.channel_name is not None:
130
- ds.attrs["channel_name"] = meta.channel_name
131
-
132
- if meta.trigger_info:
133
- for key, value in meta.trigger_info.items():
134
- ds.attrs[f"trigger_{key}"] = value
135
-
136
- # Type indicator
137
- ds.attrs["trace_type"] = "waveform" if isinstance(trace, WaveformTrace) else "digital"
138
-
139
-
140
- def export_measurement_results(
141
- results: dict[str, Any],
142
- path: str | Path,
143
- *,
144
- group_name: str = "measurements",
145
- ) -> None:
146
- """Export measurement results to HDF5.
147
-
148
- Args:
149
- results: Dictionary of measurement results.
150
- path: Output file path.
151
- group_name: HDF5 group name for results.
152
-
153
- Raises:
154
- ImportError: If h5py is not installed.
155
-
156
- Example:
157
- >>> results = measure(trace)
158
- >>> export_measurement_results(results, "measurements.h5")
159
- """
160
- if not HAS_H5PY:
161
- raise ImportError("h5py is required for HDF5 export")
162
-
163
- path = Path(path)
164
-
165
- with h5py.File(path, "a") as f:
166
- grp = f.require_group(group_name)
167
-
168
- for name, value in results.items():
169
- if isinstance(value, dict):
170
- # Nested dict (value/unit pairs)
171
- sub_grp = grp.require_group(name)
172
- for k, v in value.items():
173
- if isinstance(v, np.ndarray):
174
- sub_grp.create_dataset(k, data=v)
175
- else:
176
- sub_grp.attrs[k] = v
177
- elif isinstance(value, np.ndarray):
178
- grp.create_dataset(name, data=value)
179
- else:
180
- grp.attrs[name] = value
181
-
182
-
183
- def append_trace(
184
- path: str | Path,
185
- name: str,
186
- trace: WaveformTrace | DigitalTrace,
187
- *,
188
- compression: str | None = "gzip",
189
- ) -> None:
190
- """Append trace to existing HDF5 file.
191
-
192
- Args:
193
- path: HDF5 file path.
194
- name: Dataset name for new trace.
195
- trace: Trace to append.
196
- compression: Compression algorithm.
197
-
198
- Raises:
199
- ImportError: If h5py is not installed.
200
-
201
- Example:
202
- >>> append_trace("data.h5", "ch3", channel3_trace)
203
- """
204
- if not HAS_H5PY:
205
- raise ImportError("h5py is required for HDF5 export")
206
-
207
- path = Path(path)
208
-
209
- with h5py.File(path, "a") as f:
210
- _write_trace_dataset(f, name, trace, compression, 4, True)
211
-
212
-
213
- __all__ = [
214
- "append_trace",
215
- "export_hdf5",
216
- "export_measurement_results",
217
- ]