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
@@ -1,354 +0,0 @@
1
- """MATLAB export functionality.
2
-
3
- This module provides trace export to MATLAB .mat format with metadata.
4
-
5
-
6
- Example:
7
- >>> from oscura.exporters.matlab_export import export_mat
8
- >>> export_mat(trace, "waveform.mat")
9
- >>> export_mat({"ch1": ch1, "ch2": ch2}, "channels.mat")
10
-
11
- References:
12
- MATLAB MAT-File Format (https://www.mathworks.com/help/pdf_doc/matlab/matfile_format.pdf)
13
- """
14
-
15
- from __future__ import annotations
16
-
17
- from datetime import datetime
18
- from pathlib import Path
19
- from typing import Any
20
-
21
- import numpy as np
22
-
23
- try:
24
- import scipy.io as sio
25
-
26
- HAS_SCIPY = True
27
- except ImportError:
28
- HAS_SCIPY = False
29
-
30
- try:
31
- import h5py
32
-
33
- HAS_H5PY = True
34
- except ImportError:
35
- HAS_H5PY = False
36
-
37
- from oscura.core.types import DigitalTrace, WaveformTrace
38
-
39
-
40
- def export_mat(
41
- data: WaveformTrace | DigitalTrace | dict[str, Any],
42
- path: str | Path,
43
- *,
44
- version: str = "5",
45
- compression: bool = True,
46
- include_metadata: bool = True,
47
- ) -> None:
48
- """Export data to MATLAB .mat format.
49
-
50
- Args:
51
- data: Data to export. Can be:
52
- - Single WaveformTrace or DigitalTrace
53
- - Dictionary mapping names to traces or data
54
- path: Output file path.
55
- version: MAT-file version ("5", "7.3"). Version 5 is more compatible.
56
- Version 7.3 requires h5py and uses HDF5 backend for large files.
57
- compression: Enable compression.
58
- include_metadata: Include trace metadata in output.
59
-
60
- Raises:
61
- ImportError: If scipy is not installed, or h5py for version 7.3.
62
-
63
- Raises:
64
- TypeError: If data type is not supported.
65
-
66
- Example:
67
- >>> export_mat(trace, "waveform.mat")
68
- >>> export_mat({"ch1": ch1, "ch2": ch2}, "channels.mat")
69
- >>> export_mat(measurements, "results.mat", version="5")
70
-
71
- Note:
72
- Version 5 is the default and most compatible format, readable by
73
- scipy.io.loadmat and MATLAB.
74
-
75
- Version 7.3 uses HDF5 backend and supports:
76
- - Files > 2 GB
77
- - Compression
78
- - But requires h5py and cannot be read by scipy.io.loadmat
79
-
80
- References:
81
- EXP-008
82
- """
83
- if not HAS_SCIPY:
84
- raise ImportError("scipy is required for MATLAB export. Install with: pip install scipy")
85
-
86
- path = Path(path)
87
-
88
- # Prepare data dictionary for MATLAB
89
- mat_dict: dict[str, Any] = {}
90
-
91
- if isinstance(data, WaveformTrace | DigitalTrace):
92
- # Single trace - use standard variable names
93
- _add_trace_to_dict(mat_dict, "trace", data, include_metadata)
94
- elif isinstance(data, dict):
95
- for name, value in data.items():
96
- if isinstance(value, WaveformTrace | DigitalTrace):
97
- _add_trace_to_dict(mat_dict, name, value, include_metadata)
98
- else:
99
- # Convert numpy arrays and other types
100
- mat_dict[_sanitize_varname(name)] = _convert_value(value)
101
- else:
102
- raise TypeError(f"Unsupported data type: {type(data)}")
103
-
104
- # Add export metadata
105
- # Note: MATLAB field names cannot start with underscore
106
- if include_metadata:
107
- mat_dict["oscura_export"] = {
108
- "version": "1.0",
109
- "exported_at": datetime.now().isoformat(),
110
- "format": "oscura_matlab",
111
- }
112
-
113
- # Save to .mat file
114
- if version == "7.3":
115
- # Use HDF5 backend (requires h5py)
116
- if not HAS_H5PY:
117
- raise ImportError(
118
- "h5py is required for MATLAB v7.3 export. "
119
- "Install with: pip install h5py, or use version='5'"
120
- )
121
- _save_hdf5_mat(path, mat_dict, compression)
122
- else:
123
- # Version 5 format (default, most compatible)
124
- sio.savemat(
125
- str(path),
126
- mat_dict,
127
- do_compression=compression,
128
- oned_as="column",
129
- )
130
-
131
-
132
- def _save_hdf5_mat(path: Path, mat_dict: dict[str, Any], compression: bool) -> None:
133
- """Save MATLAB 7.3 format file using h5py (HDF5 backend).
134
-
135
- Args:
136
- path: Output file path.
137
- mat_dict: Dictionary of MATLAB variables.
138
- compression: Enable compression.
139
- """
140
- compression_opts = "gzip" if compression else None
141
-
142
- with h5py.File(path, "w") as f:
143
- # Set MATLAB 7.3 header attributes
144
- f.attrs["MATLAB_class"] = np.bytes_("struct")
145
-
146
- for key, value in mat_dict.items():
147
- _write_hdf5_value(f, key, value, compression_opts)
148
-
149
-
150
- def _write_hdf5_value(
151
- parent: h5py.File | h5py.Group, key: str, value: Any, compression: str | None
152
- ) -> None:
153
- """Write a value to HDF5 file in MATLAB 7.3 compatible format.
154
-
155
- Args:
156
- parent: HDF5 file or group object.
157
- key: Variable name.
158
- value: Value to write.
159
- compression: Compression algorithm.
160
- """
161
- if isinstance(value, np.ndarray):
162
- # Create dataset for arrays
163
- if compression and value.size > 100:
164
- parent.create_dataset(key, data=value, compression=compression)
165
- else:
166
- parent.create_dataset(key, data=value)
167
- # Set MATLAB class attribute
168
- parent[key].attrs["MATLAB_class"] = np.bytes_("double")
169
- elif isinstance(value, dict):
170
- # Create group for structs/dicts
171
- grp = parent.create_group(key)
172
- grp.attrs["MATLAB_class"] = np.bytes_("struct")
173
- for k, v in value.items():
174
- _write_hdf5_value(grp, k, v, compression)
175
- elif isinstance(value, str):
176
- # String as uint16 array (MATLAB format)
177
- dt = h5py.string_dtype(encoding="utf-8")
178
- parent.create_dataset(key, data=value, dtype=dt)
179
- parent[key].attrs["MATLAB_class"] = np.bytes_("char")
180
- elif isinstance(value, int | float):
181
- # Scalar as 1x1 array
182
- parent.create_dataset(key, data=np.array([[value]]))
183
- parent[key].attrs["MATLAB_class"] = np.bytes_("double")
184
- elif isinstance(value, bool):
185
- parent.create_dataset(key, data=np.array([[value]], dtype=np.uint8))
186
- parent[key].attrs["MATLAB_class"] = np.bytes_("logical")
187
- elif isinstance(value, list):
188
- # Convert list to array
189
- arr = np.array(value)
190
- if compression and arr.size > 100:
191
- parent.create_dataset(key, data=arr, compression=compression)
192
- else:
193
- parent.create_dataset(key, data=arr)
194
- parent[key].attrs["MATLAB_class"] = np.bytes_("double")
195
-
196
-
197
- def _add_trace_to_dict(
198
- mat_dict: dict[str, Any],
199
- name: str,
200
- trace: WaveformTrace | DigitalTrace,
201
- include_metadata: bool,
202
- ) -> None:
203
- """Add trace to MATLAB dictionary with metadata.
204
-
205
- Args:
206
- mat_dict: MATLAB variable dictionary.
207
- name: Variable name for trace.
208
- trace: Trace to add.
209
- include_metadata: Include metadata fields.
210
- """
211
- name = _sanitize_varname(name)
212
-
213
- # Add waveform data
214
- if isinstance(trace, WaveformTrace):
215
- mat_dict[f"{name}_data"] = trace.data
216
- mat_dict[f"{name}_time"] = trace.time_vector
217
- else: # DigitalTrace
218
- mat_dict[f"{name}_data"] = trace.data.astype(np.uint8)
219
- mat_dict[f"{name}_time"] = trace.time_vector
220
-
221
- # Add metadata as struct
222
- if include_metadata:
223
- meta = trace.metadata
224
- metadata_struct: dict[str, Any] = {
225
- "sample_rate": meta.sample_rate,
226
- "time_base": meta.time_base,
227
- "num_samples": len(trace.data),
228
- "duration": trace.duration,
229
- }
230
-
231
- if meta.vertical_scale is not None:
232
- metadata_struct["vertical_scale"] = meta.vertical_scale
233
- if meta.vertical_offset is not None:
234
- metadata_struct["vertical_offset"] = meta.vertical_offset
235
- if meta.acquisition_time is not None:
236
- metadata_struct["acquisition_time"] = meta.acquisition_time.isoformat()
237
- if meta.source_file is not None:
238
- metadata_struct["source_file"] = str(meta.source_file)
239
- if meta.channel_name is not None:
240
- metadata_struct["channel_name"] = meta.channel_name
241
- if meta.trigger_info:
242
- metadata_struct["trigger_info"] = meta.trigger_info
243
-
244
- metadata_struct["trace_type"] = (
245
- "waveform" if isinstance(trace, WaveformTrace) else "digital"
246
- )
247
-
248
- mat_dict[f"{name}_metadata"] = metadata_struct
249
-
250
-
251
- def _sanitize_varname(name: str) -> str:
252
- """Sanitize variable name for MATLAB compatibility.
253
-
254
- Args:
255
- name: Variable name to sanitize.
256
-
257
- Returns:
258
- Sanitized variable name compatible with MATLAB.
259
-
260
- Note:
261
- MATLAB variable names must:
262
- - Start with a letter
263
- - Contain only letters, digits, and underscores
264
- - Be <= 63 characters
265
- """
266
- import re
267
-
268
- # Replace invalid characters with underscores
269
- name = re.sub(r"[^a-zA-Z0-9_]", "_", name)
270
-
271
- # Ensure starts with letter
272
- if name and not name[0].isalpha():
273
- name = "var_" + name
274
-
275
- # Truncate to 63 characters
276
- if len(name) > 63:
277
- name = name[:63]
278
-
279
- return name if name else "var"
280
-
281
-
282
- def _convert_value(value: Any) -> Any:
283
- """Convert Python value to MATLAB-compatible format.
284
-
285
- Args:
286
- value: Python value to convert.
287
-
288
- Returns:
289
- MATLAB-compatible representation of the value.
290
- """
291
- if isinstance(value, np.ndarray):
292
- return value
293
- if isinstance(value, list):
294
- return np.array(value)
295
- if isinstance(value, dict):
296
- # Convert nested dict
297
- return {_sanitize_varname(k): _convert_value(v) for k, v in value.items()}
298
- if isinstance(value, str | int | float | bool):
299
- return value
300
- if isinstance(value, complex):
301
- return value
302
- if isinstance(value, datetime):
303
- return value.isoformat()
304
- # For other types, try converting to string
305
- return str(value)
306
-
307
-
308
- def export_multi_trace_mat(
309
- traces: list[WaveformTrace | DigitalTrace],
310
- path: str | Path,
311
- *,
312
- names: list[str] | None = None,
313
- version: str = "5",
314
- include_metadata: bool = True,
315
- ) -> None:
316
- """Export multiple traces to single MATLAB file.
317
-
318
- Args:
319
- traces: List of traces to export.
320
- path: Output file path.
321
- names: Variable names for each trace. If not provided, uses trace_1, trace_2, etc.
322
- version: MAT-file version ("5", "7.3").
323
- include_metadata: Include trace metadata.
324
-
325
- Raises:
326
- ValueError: If number of names does not match number of traces.
327
-
328
- Example:
329
- >>> export_multi_trace_mat(
330
- ... [ch1, ch2, ch3],
331
- ... "channels.mat",
332
- ... names=["ch1", "ch2", "ch3"]
333
- ... )
334
-
335
- References:
336
- EXP-008
337
- """
338
- if names is None:
339
- names = [f"trace_{i + 1}" for i in range(len(traces))]
340
-
341
- if len(names) != len(traces):
342
- raise ValueError("Number of names must match number of traces")
343
-
344
- # Create dictionary mapping names to traces
345
- trace_dict = dict(zip(names, traces, strict=True))
346
-
347
- # Export using main function
348
- export_mat(trace_dict, path, version=version, include_metadata=include_metadata)
349
-
350
-
351
- __all__ = [
352
- "export_mat",
353
- "export_multi_trace_mat",
354
- ]
@@ -1,219 +0,0 @@
1
- """NumPy NPZ export functionality for Oscura.
2
-
3
- This module provides export to NumPy's compressed archive format for
4
- efficient storage and fast loading of trace data.
5
-
6
-
7
- Example:
8
- >>> from oscura.exporters.npz_export import export_npz
9
- >>> export_npz(trace, "waveform.npz")
10
- >>> # Load later with numpy
11
- >>> import numpy as np
12
- >>> data = np.load("waveform.npz")
13
- >>> signal = data['signal']
14
- >>> sample_rate = float(data['sample_rate'])
15
- """
16
-
17
- from __future__ import annotations
18
-
19
- import contextlib
20
- from pathlib import Path
21
- from typing import TYPE_CHECKING, Any
22
-
23
- import numpy as np
24
-
25
- from oscura.core.types import DigitalTrace, WaveformTrace
26
-
27
- if TYPE_CHECKING:
28
- from numpy.typing import NDArray
29
-
30
-
31
- def export_npz(
32
- data: WaveformTrace | DigitalTrace | dict[str, Any] | NDArray[Any],
33
- path: str | Path,
34
- *,
35
- compressed: bool = True,
36
- include_metadata: bool = True,
37
- include_time: bool = False,
38
- ) -> None:
39
- """Export data to NumPy NPZ archive format.
40
-
41
- Creates a NumPy .npz file containing the trace data and optional metadata.
42
- Files can be loaded with `numpy.load()` for fast array access.
43
-
44
- Args:
45
- data: Data to export. Can be:
46
- - WaveformTrace or DigitalTrace
47
- - Dictionary of arrays
48
- - NumPy array
49
- path: Output file path (should end with .npz).
50
- compressed: Use compression (default True). Results in smaller files
51
- but slightly slower save/load.
52
- include_metadata: Include metadata in the archive.
53
- include_time: Include precomputed time array (increases file size).
54
-
55
- Raises:
56
- TypeError: If data type is not supported.
57
-
58
- Example:
59
- >>> export_npz(trace, "waveform.npz")
60
- >>> # Load later
61
- >>> data = np.load("waveform.npz")
62
- >>> signal = data['signal']
63
- >>> sample_rate = float(data['sample_rate'])
64
- >>> time = np.arange(len(signal)) / sample_rate
65
-
66
- References:
67
- EXP-004
68
- """
69
- path = Path(path)
70
-
71
- # Ensure .npz extension
72
- if path.suffix != ".npz":
73
- path = path.with_suffix(".npz")
74
-
75
- if isinstance(data, WaveformTrace | DigitalTrace):
76
- _export_trace(data, path, compressed, include_metadata, include_time)
77
- elif isinstance(data, dict):
78
- _export_dict(data, path, compressed)
79
- elif isinstance(data, np.ndarray):
80
- _export_array(data, path, compressed)
81
- else:
82
- raise TypeError(f"Unsupported data type: {type(data)}")
83
-
84
-
85
- def _export_trace(
86
- trace: WaveformTrace | DigitalTrace,
87
- path: Path,
88
- compressed: bool,
89
- include_metadata: bool,
90
- include_time: bool,
91
- ) -> None:
92
- """Export trace to NPZ.
93
-
94
- Args:
95
- trace: Trace to export.
96
- path: Output file path.
97
- compressed: Use compression.
98
- include_metadata: Include metadata arrays.
99
- include_time: Include time array.
100
- """
101
- arrays: dict[str, Any] = {}
102
-
103
- # Main signal data
104
- arrays["signal"] = trace.data
105
-
106
- # Time array (optional - can be reconstructed from sample_rate)
107
- if include_time:
108
- arrays["time"] = trace.time_vector
109
-
110
- # Metadata as scalars
111
- if include_metadata:
112
- meta = trace.metadata
113
- arrays["sample_rate"] = np.array(meta.sample_rate)
114
- arrays["samples"] = np.array(len(trace.data))
115
-
116
- if hasattr(meta, "channel"):
117
- arrays["channel"] = np.array(str(meta.channel or ""), dtype="U64")
118
-
119
- if hasattr(meta, "source_file") and meta.source_file:
120
- arrays["source_file"] = np.array(str(meta.source_file), dtype="U256")
121
-
122
- if hasattr(meta, "capture_time") and meta.capture_time:
123
- arrays["capture_time"] = np.array(meta.capture_time.isoformat(), dtype="U64")
124
-
125
- if hasattr(meta, "units") and meta.units:
126
- arrays["units"] = np.array(str(meta.units), dtype="U16")
127
-
128
- # Add trace type marker
129
- if isinstance(trace, DigitalTrace):
130
- arrays["trace_type"] = np.array("digital", dtype="U16")
131
- else:
132
- arrays["trace_type"] = np.array("waveform", dtype="U16")
133
-
134
- # Save
135
- if compressed:
136
- np.savez_compressed(path, **arrays)
137
- else:
138
- np.savez(path, **arrays)
139
-
140
-
141
- def _export_dict(
142
- data: dict[str, Any],
143
- path: Path,
144
- compressed: bool,
145
- ) -> None:
146
- """Export dictionary of arrays to NPZ.
147
-
148
- Args:
149
- data: Dictionary to export.
150
- path: Output file path.
151
- compressed: Use compression.
152
- """
153
- # Convert values to arrays
154
- arrays = {}
155
- for key, value in data.items():
156
- if isinstance(value, np.ndarray):
157
- arrays[key] = value
158
- elif isinstance(value, list | tuple | int | float):
159
- arrays[key] = np.array(value)
160
- elif isinstance(value, str):
161
- arrays[key] = np.array(value, dtype="U256")
162
- else:
163
- # Try to convert, skip on failure
164
- with contextlib.suppress(TypeError, ValueError):
165
- arrays[key] = np.array(value)
166
-
167
- if compressed:
168
- np.savez_compressed(path, **arrays)
169
- else:
170
- np.savez(path, **arrays)
171
-
172
-
173
- def _export_array(
174
- data: NDArray[Any],
175
- path: Path,
176
- compressed: bool,
177
- ) -> None:
178
- """Export single array to NPZ.
179
-
180
- Args:
181
- data: Array to export.
182
- path: Output file path.
183
- compressed: Use compression.
184
- """
185
- if compressed:
186
- np.savez_compressed(path, data=data)
187
- else:
188
- np.savez(path, data=data)
189
-
190
-
191
- def load_npz(path: str | Path) -> dict[str, NDArray[Any]]:
192
- """Load NPZ file and return dictionary of arrays.
193
-
194
- Convenience wrapper around numpy.load() that returns a regular dict.
195
-
196
- Args:
197
- path: Path to NPZ file.
198
-
199
- Returns:
200
- Dictionary mapping array names to numpy arrays.
201
-
202
- Example:
203
- >>> data = load_npz("waveform.npz")
204
- >>> signal = data['signal']
205
- >>> sample_rate = float(data['sample_rate'])
206
-
207
- References:
208
- EXP-004
209
- """
210
- path = Path(path)
211
-
212
- with np.load(path) as npz:
213
- return {key: npz[key] for key in npz.files}
214
-
215
-
216
- __all__ = [
217
- "export_npz",
218
- "load_npz",
219
- ]