oscura 0.5.1__py3-none-any.whl → 0.7.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 (497) 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/edges.py +325 -65
  5. oscura/analyzers/digital/quality.py +293 -166
  6. oscura/analyzers/digital/timing.py +260 -115
  7. oscura/analyzers/digital/timing_numba.py +334 -0
  8. oscura/analyzers/entropy.py +605 -0
  9. oscura/analyzers/eye/diagram.py +176 -109
  10. oscura/analyzers/eye/metrics.py +5 -5
  11. oscura/analyzers/jitter/__init__.py +6 -4
  12. oscura/analyzers/jitter/ber.py +52 -52
  13. oscura/analyzers/jitter/classification.py +156 -0
  14. oscura/analyzers/jitter/decomposition.py +163 -113
  15. oscura/analyzers/jitter/spectrum.py +80 -64
  16. oscura/analyzers/ml/__init__.py +39 -0
  17. oscura/analyzers/ml/features.py +600 -0
  18. oscura/analyzers/ml/signal_classifier.py +604 -0
  19. oscura/analyzers/packet/daq.py +246 -158
  20. oscura/analyzers/packet/parser.py +12 -1
  21. oscura/analyzers/packet/payload.py +50 -2110
  22. oscura/analyzers/packet/payload_analysis.py +361 -181
  23. oscura/analyzers/packet/payload_patterns.py +133 -70
  24. oscura/analyzers/packet/stream.py +84 -23
  25. oscura/analyzers/patterns/__init__.py +26 -5
  26. oscura/analyzers/patterns/anomaly_detection.py +908 -0
  27. oscura/analyzers/patterns/clustering.py +169 -108
  28. oscura/analyzers/patterns/clustering_optimized.py +227 -0
  29. oscura/analyzers/patterns/discovery.py +1 -1
  30. oscura/analyzers/patterns/matching.py +581 -197
  31. oscura/analyzers/patterns/pattern_mining.py +778 -0
  32. oscura/analyzers/patterns/periodic.py +121 -38
  33. oscura/analyzers/patterns/sequences.py +175 -78
  34. oscura/analyzers/power/conduction.py +1 -1
  35. oscura/analyzers/power/soa.py +6 -6
  36. oscura/analyzers/power/switching.py +250 -110
  37. oscura/analyzers/protocol/__init__.py +17 -1
  38. oscura/analyzers/protocols/base.py +6 -6
  39. oscura/analyzers/protocols/ble/__init__.py +38 -0
  40. oscura/analyzers/protocols/ble/analyzer.py +809 -0
  41. oscura/analyzers/protocols/ble/uuids.py +288 -0
  42. oscura/analyzers/protocols/can.py +257 -127
  43. oscura/analyzers/protocols/can_fd.py +107 -80
  44. oscura/analyzers/protocols/flexray.py +139 -80
  45. oscura/analyzers/protocols/hdlc.py +93 -58
  46. oscura/analyzers/protocols/i2c.py +247 -106
  47. oscura/analyzers/protocols/i2s.py +138 -86
  48. oscura/analyzers/protocols/industrial/__init__.py +40 -0
  49. oscura/analyzers/protocols/industrial/bacnet/__init__.py +33 -0
  50. oscura/analyzers/protocols/industrial/bacnet/analyzer.py +708 -0
  51. oscura/analyzers/protocols/industrial/bacnet/encoding.py +412 -0
  52. oscura/analyzers/protocols/industrial/bacnet/services.py +622 -0
  53. oscura/analyzers/protocols/industrial/ethercat/__init__.py +30 -0
  54. oscura/analyzers/protocols/industrial/ethercat/analyzer.py +474 -0
  55. oscura/analyzers/protocols/industrial/ethercat/mailbox.py +339 -0
  56. oscura/analyzers/protocols/industrial/ethercat/topology.py +166 -0
  57. oscura/analyzers/protocols/industrial/modbus/__init__.py +31 -0
  58. oscura/analyzers/protocols/industrial/modbus/analyzer.py +525 -0
  59. oscura/analyzers/protocols/industrial/modbus/crc.py +79 -0
  60. oscura/analyzers/protocols/industrial/modbus/functions.py +436 -0
  61. oscura/analyzers/protocols/industrial/opcua/__init__.py +21 -0
  62. oscura/analyzers/protocols/industrial/opcua/analyzer.py +552 -0
  63. oscura/analyzers/protocols/industrial/opcua/datatypes.py +446 -0
  64. oscura/analyzers/protocols/industrial/opcua/services.py +264 -0
  65. oscura/analyzers/protocols/industrial/profinet/__init__.py +23 -0
  66. oscura/analyzers/protocols/industrial/profinet/analyzer.py +441 -0
  67. oscura/analyzers/protocols/industrial/profinet/dcp.py +263 -0
  68. oscura/analyzers/protocols/industrial/profinet/ptcp.py +200 -0
  69. oscura/analyzers/protocols/jtag.py +180 -98
  70. oscura/analyzers/protocols/lin.py +219 -114
  71. oscura/analyzers/protocols/manchester.py +4 -4
  72. oscura/analyzers/protocols/onewire.py +253 -149
  73. oscura/analyzers/protocols/parallel_bus/__init__.py +20 -0
  74. oscura/analyzers/protocols/parallel_bus/centronics.py +92 -0
  75. oscura/analyzers/protocols/parallel_bus/gpib.py +137 -0
  76. oscura/analyzers/protocols/spi.py +192 -95
  77. oscura/analyzers/protocols/swd.py +321 -167
  78. oscura/analyzers/protocols/uart.py +267 -125
  79. oscura/analyzers/protocols/usb.py +235 -131
  80. oscura/analyzers/side_channel/power.py +17 -12
  81. oscura/analyzers/signal/__init__.py +15 -0
  82. oscura/analyzers/signal/timing_analysis.py +1086 -0
  83. oscura/analyzers/signal_integrity/__init__.py +4 -1
  84. oscura/analyzers/signal_integrity/sparams.py +2 -19
  85. oscura/analyzers/spectral/chunked.py +129 -60
  86. oscura/analyzers/spectral/chunked_fft.py +300 -94
  87. oscura/analyzers/spectral/chunked_wavelet.py +100 -80
  88. oscura/analyzers/statistical/checksum.py +376 -217
  89. oscura/analyzers/statistical/classification.py +229 -107
  90. oscura/analyzers/statistical/entropy.py +78 -53
  91. oscura/analyzers/statistics/correlation.py +407 -211
  92. oscura/analyzers/statistics/outliers.py +2 -2
  93. oscura/analyzers/statistics/streaming.py +30 -5
  94. oscura/analyzers/validation.py +216 -101
  95. oscura/analyzers/waveform/measurements.py +9 -0
  96. oscura/analyzers/waveform/measurements_with_uncertainty.py +31 -15
  97. oscura/analyzers/waveform/spectral.py +500 -228
  98. oscura/api/__init__.py +31 -5
  99. oscura/api/dsl/__init__.py +582 -0
  100. oscura/{dsl → api/dsl}/commands.py +43 -76
  101. oscura/{dsl → api/dsl}/interpreter.py +26 -51
  102. oscura/{dsl → api/dsl}/parser.py +107 -77
  103. oscura/{dsl → api/dsl}/repl.py +2 -2
  104. oscura/api/dsl.py +1 -1
  105. oscura/{integrations → api/integrations}/__init__.py +1 -1
  106. oscura/{integrations → api/integrations}/llm.py +201 -102
  107. oscura/api/operators.py +3 -3
  108. oscura/api/optimization.py +144 -30
  109. oscura/api/rest_server.py +921 -0
  110. oscura/api/server/__init__.py +17 -0
  111. oscura/api/server/dashboard.py +850 -0
  112. oscura/api/server/static/README.md +34 -0
  113. oscura/api/server/templates/base.html +181 -0
  114. oscura/api/server/templates/export.html +120 -0
  115. oscura/api/server/templates/home.html +284 -0
  116. oscura/api/server/templates/protocols.html +58 -0
  117. oscura/api/server/templates/reports.html +43 -0
  118. oscura/api/server/templates/session_detail.html +89 -0
  119. oscura/api/server/templates/sessions.html +83 -0
  120. oscura/api/server/templates/waveforms.html +73 -0
  121. oscura/automotive/__init__.py +8 -1
  122. oscura/automotive/can/__init__.py +10 -0
  123. oscura/automotive/can/checksum.py +3 -1
  124. oscura/automotive/can/dbc_generator.py +590 -0
  125. oscura/automotive/can/message_wrapper.py +121 -74
  126. oscura/automotive/can/patterns.py +98 -21
  127. oscura/automotive/can/session.py +292 -56
  128. oscura/automotive/can/state_machine.py +6 -3
  129. oscura/automotive/can/stimulus_response.py +97 -75
  130. oscura/automotive/dbc/__init__.py +10 -2
  131. oscura/automotive/dbc/generator.py +84 -56
  132. oscura/automotive/dbc/parser.py +6 -6
  133. oscura/automotive/dtc/data.json +17 -102
  134. oscura/automotive/dtc/database.py +2 -2
  135. oscura/automotive/flexray/__init__.py +31 -0
  136. oscura/automotive/flexray/analyzer.py +504 -0
  137. oscura/automotive/flexray/crc.py +185 -0
  138. oscura/automotive/flexray/fibex.py +449 -0
  139. oscura/automotive/j1939/__init__.py +45 -8
  140. oscura/automotive/j1939/analyzer.py +605 -0
  141. oscura/automotive/j1939/spns.py +326 -0
  142. oscura/automotive/j1939/transport.py +306 -0
  143. oscura/automotive/lin/__init__.py +47 -0
  144. oscura/automotive/lin/analyzer.py +612 -0
  145. oscura/automotive/loaders/blf.py +13 -2
  146. oscura/automotive/loaders/csv_can.py +143 -72
  147. oscura/automotive/loaders/dispatcher.py +50 -2
  148. oscura/automotive/loaders/mdf.py +86 -45
  149. oscura/automotive/loaders/pcap.py +111 -61
  150. oscura/automotive/uds/__init__.py +4 -0
  151. oscura/automotive/uds/analyzer.py +725 -0
  152. oscura/automotive/uds/decoder.py +140 -58
  153. oscura/automotive/uds/models.py +7 -1
  154. oscura/automotive/visualization.py +1 -1
  155. oscura/cli/analyze.py +348 -0
  156. oscura/cli/batch.py +142 -122
  157. oscura/cli/benchmark.py +275 -0
  158. oscura/cli/characterize.py +137 -82
  159. oscura/cli/compare.py +224 -131
  160. oscura/cli/completion.py +250 -0
  161. oscura/cli/config_cmd.py +361 -0
  162. oscura/cli/decode.py +164 -87
  163. oscura/cli/export.py +286 -0
  164. oscura/cli/main.py +115 -31
  165. oscura/{onboarding → cli/onboarding}/__init__.py +3 -3
  166. oscura/{onboarding → cli/onboarding}/help.py +80 -58
  167. oscura/{onboarding → cli/onboarding}/tutorials.py +97 -72
  168. oscura/{onboarding → cli/onboarding}/wizard.py +55 -36
  169. oscura/cli/progress.py +147 -0
  170. oscura/cli/shell.py +157 -135
  171. oscura/cli/validate_cmd.py +204 -0
  172. oscura/cli/visualize.py +158 -0
  173. oscura/convenience.py +125 -79
  174. oscura/core/__init__.py +4 -2
  175. oscura/core/backend_selector.py +3 -3
  176. oscura/core/cache.py +126 -15
  177. oscura/core/cancellation.py +1 -1
  178. oscura/{config → core/config}/__init__.py +20 -11
  179. oscura/{config → core/config}/defaults.py +1 -1
  180. oscura/{config → core/config}/loader.py +7 -5
  181. oscura/{config → core/config}/memory.py +5 -5
  182. oscura/{config → core/config}/migration.py +1 -1
  183. oscura/{config → core/config}/pipeline.py +99 -23
  184. oscura/{config → core/config}/preferences.py +1 -1
  185. oscura/{config → core/config}/protocol.py +3 -3
  186. oscura/{config → core/config}/schema.py +426 -272
  187. oscura/{config → core/config}/settings.py +1 -1
  188. oscura/{config → core/config}/thresholds.py +195 -153
  189. oscura/core/correlation.py +5 -6
  190. oscura/core/cross_domain.py +0 -2
  191. oscura/core/debug.py +9 -5
  192. oscura/{extensibility → core/extensibility}/docs.py +158 -70
  193. oscura/{extensibility → core/extensibility}/extensions.py +160 -76
  194. oscura/{extensibility → core/extensibility}/logging.py +1 -1
  195. oscura/{extensibility → core/extensibility}/measurements.py +1 -1
  196. oscura/{extensibility → core/extensibility}/plugins.py +1 -1
  197. oscura/{extensibility → core/extensibility}/templates.py +73 -3
  198. oscura/{extensibility → core/extensibility}/validation.py +1 -1
  199. oscura/core/gpu_backend.py +11 -7
  200. oscura/core/log_query.py +101 -11
  201. oscura/core/logging.py +126 -54
  202. oscura/core/logging_advanced.py +5 -5
  203. oscura/core/memory_limits.py +108 -70
  204. oscura/core/memory_monitor.py +2 -2
  205. oscura/core/memory_progress.py +7 -7
  206. oscura/core/memory_warnings.py +1 -1
  207. oscura/core/numba_backend.py +13 -13
  208. oscura/{plugins → core/plugins}/__init__.py +9 -9
  209. oscura/{plugins → core/plugins}/base.py +7 -7
  210. oscura/{plugins → core/plugins}/cli.py +3 -3
  211. oscura/{plugins → core/plugins}/discovery.py +186 -106
  212. oscura/{plugins → core/plugins}/lifecycle.py +1 -1
  213. oscura/{plugins → core/plugins}/manager.py +7 -7
  214. oscura/{plugins → core/plugins}/registry.py +3 -3
  215. oscura/{plugins → core/plugins}/versioning.py +1 -1
  216. oscura/core/progress.py +16 -1
  217. oscura/core/provenance.py +8 -2
  218. oscura/{schemas → core/schemas}/__init__.py +2 -2
  219. oscura/{schemas → core/schemas}/device_mapping.json +2 -8
  220. oscura/{schemas → core/schemas}/packet_format.json +4 -24
  221. oscura/{schemas → core/schemas}/protocol_definition.json +2 -12
  222. oscura/core/types.py +4 -0
  223. oscura/core/uncertainty.py +3 -3
  224. oscura/correlation/__init__.py +52 -0
  225. oscura/correlation/multi_protocol.py +811 -0
  226. oscura/discovery/auto_decoder.py +117 -35
  227. oscura/discovery/comparison.py +191 -86
  228. oscura/discovery/quality_validator.py +155 -68
  229. oscura/discovery/signal_detector.py +196 -79
  230. oscura/export/__init__.py +18 -8
  231. oscura/export/kaitai_struct.py +513 -0
  232. oscura/export/scapy_layer.py +801 -0
  233. oscura/export/wireshark/generator.py +1 -1
  234. oscura/export/wireshark/templates/dissector.lua.j2 +2 -2
  235. oscura/export/wireshark_dissector.py +746 -0
  236. oscura/guidance/wizard.py +207 -111
  237. oscura/hardware/__init__.py +19 -0
  238. oscura/{acquisition → hardware/acquisition}/__init__.py +4 -4
  239. oscura/{acquisition → hardware/acquisition}/file.py +2 -2
  240. oscura/{acquisition → hardware/acquisition}/hardware.py +7 -7
  241. oscura/{acquisition → hardware/acquisition}/saleae.py +15 -12
  242. oscura/{acquisition → hardware/acquisition}/socketcan.py +1 -1
  243. oscura/{acquisition → hardware/acquisition}/streaming.py +2 -2
  244. oscura/{acquisition → hardware/acquisition}/synthetic.py +3 -3
  245. oscura/{acquisition → hardware/acquisition}/visa.py +33 -11
  246. oscura/hardware/firmware/__init__.py +29 -0
  247. oscura/hardware/firmware/pattern_recognition.py +874 -0
  248. oscura/hardware/hal_detector.py +736 -0
  249. oscura/hardware/security/__init__.py +37 -0
  250. oscura/hardware/security/side_channel_detector.py +1126 -0
  251. oscura/inference/__init__.py +4 -0
  252. oscura/inference/active_learning/observation_table.py +4 -1
  253. oscura/inference/alignment.py +216 -123
  254. oscura/inference/bayesian.py +113 -33
  255. oscura/inference/crc_reverse.py +101 -55
  256. oscura/inference/logic.py +6 -2
  257. oscura/inference/message_format.py +342 -183
  258. oscura/inference/protocol.py +95 -44
  259. oscura/inference/protocol_dsl.py +180 -82
  260. oscura/inference/signal_intelligence.py +1439 -706
  261. oscura/inference/spectral.py +99 -57
  262. oscura/inference/state_machine.py +810 -158
  263. oscura/inference/stream.py +270 -110
  264. oscura/iot/__init__.py +34 -0
  265. oscura/iot/coap/__init__.py +32 -0
  266. oscura/iot/coap/analyzer.py +668 -0
  267. oscura/iot/coap/options.py +212 -0
  268. oscura/iot/lorawan/__init__.py +21 -0
  269. oscura/iot/lorawan/crypto.py +206 -0
  270. oscura/iot/lorawan/decoder.py +801 -0
  271. oscura/iot/lorawan/mac_commands.py +341 -0
  272. oscura/iot/mqtt/__init__.py +27 -0
  273. oscura/iot/mqtt/analyzer.py +999 -0
  274. oscura/iot/mqtt/properties.py +315 -0
  275. oscura/iot/zigbee/__init__.py +31 -0
  276. oscura/iot/zigbee/analyzer.py +615 -0
  277. oscura/iot/zigbee/security.py +153 -0
  278. oscura/iot/zigbee/zcl.py +349 -0
  279. oscura/jupyter/display.py +125 -45
  280. oscura/{exploratory → jupyter/exploratory}/__init__.py +8 -8
  281. oscura/{exploratory → jupyter/exploratory}/error_recovery.py +298 -141
  282. oscura/jupyter/exploratory/fuzzy.py +746 -0
  283. oscura/{exploratory → jupyter/exploratory}/fuzzy_advanced.py +258 -100
  284. oscura/{exploratory → jupyter/exploratory}/legacy.py +464 -242
  285. oscura/{exploratory → jupyter/exploratory}/parse.py +167 -145
  286. oscura/{exploratory → jupyter/exploratory}/recovery.py +119 -87
  287. oscura/jupyter/exploratory/sync.py +612 -0
  288. oscura/{exploratory → jupyter/exploratory}/unknown.py +299 -176
  289. oscura/jupyter/magic.py +4 -4
  290. oscura/{ui → jupyter/ui}/__init__.py +2 -2
  291. oscura/{ui → jupyter/ui}/formatters.py +3 -3
  292. oscura/{ui → jupyter/ui}/progressive_display.py +153 -82
  293. oscura/loaders/__init__.py +183 -67
  294. oscura/loaders/binary.py +88 -1
  295. oscura/loaders/chipwhisperer.py +153 -137
  296. oscura/loaders/configurable.py +208 -86
  297. oscura/loaders/csv_loader.py +458 -215
  298. oscura/loaders/hdf5_loader.py +278 -119
  299. oscura/loaders/lazy.py +87 -54
  300. oscura/loaders/mmap_loader.py +1 -1
  301. oscura/loaders/numpy_loader.py +253 -116
  302. oscura/loaders/pcap.py +226 -151
  303. oscura/loaders/rigol.py +110 -49
  304. oscura/loaders/sigrok.py +201 -78
  305. oscura/loaders/tdms.py +81 -58
  306. oscura/loaders/tektronix.py +291 -174
  307. oscura/loaders/touchstone.py +182 -87
  308. oscura/loaders/tss.py +456 -0
  309. oscura/loaders/vcd.py +215 -117
  310. oscura/loaders/wav.py +155 -68
  311. oscura/reporting/__init__.py +9 -0
  312. oscura/reporting/analyze.py +352 -146
  313. oscura/reporting/argument_preparer.py +69 -14
  314. oscura/reporting/auto_report.py +97 -61
  315. oscura/reporting/batch.py +131 -58
  316. oscura/reporting/chart_selection.py +57 -45
  317. oscura/reporting/comparison.py +63 -17
  318. oscura/reporting/content/executive.py +76 -24
  319. oscura/reporting/core_formats/multi_format.py +11 -8
  320. oscura/reporting/engine.py +312 -158
  321. oscura/reporting/enhanced_reports.py +949 -0
  322. oscura/reporting/export.py +86 -43
  323. oscura/reporting/formatting/numbers.py +69 -42
  324. oscura/reporting/html.py +139 -58
  325. oscura/reporting/index.py +137 -65
  326. oscura/reporting/output.py +158 -67
  327. oscura/reporting/pdf.py +67 -102
  328. oscura/reporting/plots.py +191 -112
  329. oscura/reporting/sections.py +88 -47
  330. oscura/reporting/standards.py +104 -61
  331. oscura/reporting/summary_generator.py +75 -55
  332. oscura/reporting/tables.py +138 -54
  333. oscura/reporting/templates/enhanced/protocol_re.html +525 -0
  334. oscura/sessions/__init__.py +14 -23
  335. oscura/sessions/base.py +3 -3
  336. oscura/sessions/blackbox.py +106 -10
  337. oscura/sessions/generic.py +2 -2
  338. oscura/sessions/legacy.py +783 -0
  339. oscura/side_channel/__init__.py +63 -0
  340. oscura/side_channel/dpa.py +1025 -0
  341. oscura/utils/__init__.py +15 -1
  342. oscura/utils/bitwise.py +118 -0
  343. oscura/{builders → utils/builders}/__init__.py +1 -1
  344. oscura/{comparison → utils/comparison}/__init__.py +6 -6
  345. oscura/{comparison → utils/comparison}/compare.py +202 -101
  346. oscura/{comparison → utils/comparison}/golden.py +83 -63
  347. oscura/{comparison → utils/comparison}/limits.py +313 -89
  348. oscura/{comparison → utils/comparison}/mask.py +151 -45
  349. oscura/{comparison → utils/comparison}/trace_diff.py +1 -1
  350. oscura/{comparison → utils/comparison}/visualization.py +147 -89
  351. oscura/{component → utils/component}/__init__.py +3 -3
  352. oscura/{component → utils/component}/impedance.py +122 -58
  353. oscura/{component → utils/component}/reactive.py +165 -168
  354. oscura/{component → utils/component}/transmission_line.py +3 -3
  355. oscura/{filtering → utils/filtering}/__init__.py +6 -6
  356. oscura/{filtering → utils/filtering}/base.py +1 -1
  357. oscura/{filtering → utils/filtering}/convenience.py +2 -2
  358. oscura/{filtering → utils/filtering}/design.py +169 -93
  359. oscura/{filtering → utils/filtering}/filters.py +2 -2
  360. oscura/{filtering → utils/filtering}/introspection.py +2 -2
  361. oscura/utils/geometry.py +31 -0
  362. oscura/utils/imports.py +184 -0
  363. oscura/utils/lazy.py +1 -1
  364. oscura/{math → utils/math}/__init__.py +2 -2
  365. oscura/{math → utils/math}/arithmetic.py +114 -48
  366. oscura/{math → utils/math}/interpolation.py +139 -106
  367. oscura/utils/memory.py +129 -66
  368. oscura/utils/memory_advanced.py +92 -9
  369. oscura/utils/memory_extensions.py +10 -8
  370. oscura/{optimization → utils/optimization}/__init__.py +1 -1
  371. oscura/{optimization → utils/optimization}/search.py +2 -2
  372. oscura/utils/performance/__init__.py +58 -0
  373. oscura/utils/performance/caching.py +889 -0
  374. oscura/utils/performance/lsh_clustering.py +333 -0
  375. oscura/utils/performance/memory_optimizer.py +699 -0
  376. oscura/utils/performance/optimizations.py +675 -0
  377. oscura/utils/performance/parallel.py +654 -0
  378. oscura/utils/performance/profiling.py +661 -0
  379. oscura/{pipeline → utils/pipeline}/base.py +1 -1
  380. oscura/{pipeline → utils/pipeline}/composition.py +1 -1
  381. oscura/{pipeline → utils/pipeline}/parallel.py +3 -2
  382. oscura/{pipeline → utils/pipeline}/pipeline.py +1 -1
  383. oscura/{pipeline → utils/pipeline}/reverse_engineering.py +412 -221
  384. oscura/{search → utils/search}/__init__.py +3 -3
  385. oscura/{search → utils/search}/anomaly.py +188 -58
  386. oscura/utils/search/context.py +294 -0
  387. oscura/{search → utils/search}/pattern.py +138 -10
  388. oscura/utils/serial.py +51 -0
  389. oscura/utils/storage/__init__.py +61 -0
  390. oscura/utils/storage/database.py +1166 -0
  391. oscura/{streaming → utils/streaming}/chunked.py +302 -143
  392. oscura/{streaming → utils/streaming}/progressive.py +1 -1
  393. oscura/{streaming → utils/streaming}/realtime.py +3 -2
  394. oscura/{triggering → utils/triggering}/__init__.py +6 -6
  395. oscura/{triggering → utils/triggering}/base.py +6 -6
  396. oscura/{triggering → utils/triggering}/edge.py +2 -2
  397. oscura/{triggering → utils/triggering}/pattern.py +2 -2
  398. oscura/{triggering → utils/triggering}/pulse.py +115 -74
  399. oscura/{triggering → utils/triggering}/window.py +2 -2
  400. oscura/utils/validation.py +32 -0
  401. oscura/validation/__init__.py +121 -0
  402. oscura/{compliance → validation/compliance}/__init__.py +5 -5
  403. oscura/{compliance → validation/compliance}/advanced.py +5 -5
  404. oscura/{compliance → validation/compliance}/masks.py +1 -1
  405. oscura/{compliance → validation/compliance}/reporting.py +127 -53
  406. oscura/{compliance → validation/compliance}/testing.py +114 -52
  407. oscura/validation/compliance_tests.py +915 -0
  408. oscura/validation/fuzzer.py +990 -0
  409. oscura/validation/grammar_tests.py +596 -0
  410. oscura/validation/grammar_validator.py +904 -0
  411. oscura/validation/hil_testing.py +977 -0
  412. oscura/{quality → validation/quality}/__init__.py +4 -4
  413. oscura/{quality → validation/quality}/ensemble.py +251 -171
  414. oscura/{quality → validation/quality}/explainer.py +3 -3
  415. oscura/{quality → validation/quality}/scoring.py +1 -1
  416. oscura/{quality → validation/quality}/warnings.py +4 -4
  417. oscura/validation/regression_suite.py +808 -0
  418. oscura/validation/replay.py +788 -0
  419. oscura/{testing → validation/testing}/__init__.py +2 -2
  420. oscura/{testing → validation/testing}/synthetic.py +5 -5
  421. oscura/visualization/__init__.py +9 -0
  422. oscura/visualization/accessibility.py +1 -1
  423. oscura/visualization/annotations.py +64 -67
  424. oscura/visualization/colors.py +7 -7
  425. oscura/visualization/digital.py +180 -81
  426. oscura/visualization/eye.py +236 -85
  427. oscura/visualization/interactive.py +320 -143
  428. oscura/visualization/jitter.py +587 -247
  429. oscura/visualization/layout.py +169 -134
  430. oscura/visualization/optimization.py +103 -52
  431. oscura/visualization/palettes.py +1 -1
  432. oscura/visualization/power.py +427 -211
  433. oscura/visualization/power_extended.py +626 -297
  434. oscura/visualization/presets.py +2 -0
  435. oscura/visualization/protocols.py +495 -181
  436. oscura/visualization/render.py +79 -63
  437. oscura/visualization/reverse_engineering.py +171 -124
  438. oscura/visualization/signal_integrity.py +460 -279
  439. oscura/visualization/specialized.py +190 -100
  440. oscura/visualization/spectral.py +670 -255
  441. oscura/visualization/thumbnails.py +166 -137
  442. oscura/visualization/waveform.py +150 -63
  443. oscura/workflows/__init__.py +3 -0
  444. oscura/{batch → workflows/batch}/__init__.py +5 -5
  445. oscura/{batch → workflows/batch}/advanced.py +150 -75
  446. oscura/workflows/batch/aggregate.py +531 -0
  447. oscura/workflows/batch/analyze.py +236 -0
  448. oscura/{batch → workflows/batch}/logging.py +2 -2
  449. oscura/{batch → workflows/batch}/metrics.py +1 -1
  450. oscura/workflows/complete_re.py +1144 -0
  451. oscura/workflows/compliance.py +44 -54
  452. oscura/workflows/digital.py +197 -51
  453. oscura/workflows/legacy/__init__.py +12 -0
  454. oscura/{workflow → workflows/legacy}/dag.py +4 -1
  455. oscura/workflows/multi_trace.py +9 -9
  456. oscura/workflows/power.py +42 -62
  457. oscura/workflows/protocol.py +82 -49
  458. oscura/workflows/reverse_engineering.py +351 -150
  459. oscura/workflows/signal_integrity.py +157 -82
  460. oscura-0.7.0.dist-info/METADATA +661 -0
  461. oscura-0.7.0.dist-info/RECORD +591 -0
  462. oscura/batch/aggregate.py +0 -300
  463. oscura/batch/analyze.py +0 -139
  464. oscura/dsl/__init__.py +0 -73
  465. oscura/exceptions.py +0 -59
  466. oscura/exploratory/fuzzy.py +0 -513
  467. oscura/exploratory/sync.py +0 -384
  468. oscura/exporters/__init__.py +0 -94
  469. oscura/exporters/csv.py +0 -303
  470. oscura/exporters/exporters.py +0 -44
  471. oscura/exporters/hdf5.py +0 -217
  472. oscura/exporters/html_export.py +0 -701
  473. oscura/exporters/json_export.py +0 -291
  474. oscura/exporters/markdown_export.py +0 -367
  475. oscura/exporters/matlab_export.py +0 -354
  476. oscura/exporters/npz_export.py +0 -219
  477. oscura/exporters/spice_export.py +0 -210
  478. oscura/search/context.py +0 -149
  479. oscura/session/__init__.py +0 -34
  480. oscura/session/annotations.py +0 -289
  481. oscura/session/history.py +0 -313
  482. oscura/session/session.py +0 -520
  483. oscura/workflow/__init__.py +0 -13
  484. oscura-0.5.1.dist-info/METADATA +0 -583
  485. oscura-0.5.1.dist-info/RECORD +0 -481
  486. /oscura/core/{config.py → config/legacy.py} +0 -0
  487. /oscura/{extensibility → core/extensibility}/__init__.py +0 -0
  488. /oscura/{extensibility → core/extensibility}/registry.py +0 -0
  489. /oscura/{plugins → core/plugins}/isolation.py +0 -0
  490. /oscura/{schemas → core/schemas}/bus_configuration.json +0 -0
  491. /oscura/{builders → utils/builders}/signal_builder.py +0 -0
  492. /oscura/{optimization → utils/optimization}/parallel.py +0 -0
  493. /oscura/{pipeline → utils/pipeline}/__init__.py +0 -0
  494. /oscura/{streaming → utils/streaming}/__init__.py +0 -0
  495. {oscura-0.5.1.dist-info → oscura-0.7.0.dist-info}/WHEEL +0 -0
  496. {oscura-0.5.1.dist-info → oscura-0.7.0.dist-info}/entry_points.txt +0 -0
  497. {oscura-0.5.1.dist-info → oscura-0.7.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
- ]