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
@@ -78,90 +78,104 @@ def load_npz(
78
78
  CSV, or HDF5.
79
79
  """
80
80
  path = Path(path)
81
+ _validate_npz_file_exists(path)
81
82
 
83
+ npz = _load_npz_archive(path, mmap)
84
+
85
+ try:
86
+ data_array = _extract_data_array(npz, channel, path)
87
+ data = _convert_to_float64(data_array, mmap, path)
88
+ metadata = _build_npz_metadata(npz, sample_rate, channel, path)
89
+ return WaveformTrace(data=data, metadata=metadata)
90
+ finally:
91
+ npz.close()
92
+
93
+
94
+ def _validate_npz_file_exists(path: Path) -> None:
95
+ """Validate that NPZ file exists."""
82
96
  if not path.exists():
83
- raise LoaderError(
84
- "File not found",
97
+ raise LoaderError("File not found", file_path=str(path))
98
+
99
+
100
+ def _load_npz_archive(path: Path, mmap: bool) -> Any:
101
+ """Load NPZ archive with optional memory mapping."""
102
+ try:
103
+ return np.load(path, allow_pickle=True, mmap_mode="r" if mmap else None)
104
+ except Exception as e:
105
+ raise LoaderError("Failed to load NPZ file", file_path=str(path), details=str(e)) from e
106
+
107
+
108
+ def _extract_data_array(npz: Any, channel: str | int | None, path: Path) -> Any:
109
+ """Find and extract data array from NPZ file."""
110
+ data_array = _find_data_array(npz, channel)
111
+
112
+ if data_array is None:
113
+ available = list(npz.keys())
114
+ raise FormatError(
115
+ "No waveform data found in NPZ file",
85
116
  file_path=str(path),
117
+ expected=f"Array named: {', '.join(DATA_ARRAY_NAMES)}",
118
+ got=f"Arrays: {', '.join(available)}",
86
119
  )
87
120
 
121
+ return data_array
122
+
123
+
124
+ def _convert_to_float64(data_array: Any, mmap: bool, path: Path) -> NDArray[np.float64]:
125
+ """Convert data array to float64, preserving memmap if enabled."""
126
+ if mmap and isinstance(data_array, np.memmap):
127
+ return _convert_memmap_to_float64(data_array, path)
128
+
129
+ return _convert_array_to_float64(data_array, path)
130
+
131
+
132
+ def _convert_memmap_to_float64(data_array: np.memmap[Any, Any], path: Path) -> NDArray[np.float64]:
133
+ """Convert memmap array to float64."""
134
+ if data_array.dtype == np.float64:
135
+ return data_array
136
+
88
137
  try:
89
- npz = np.load(path, allow_pickle=True, mmap_mode="r" if mmap else None)
90
- except Exception as e:
91
- raise LoaderError(
92
- "Failed to load NPZ file",
138
+ return data_array.astype(np.float64)
139
+ except (ValueError, TypeError) as e:
140
+ raise FormatError(
141
+ "Data array is not numeric",
93
142
  file_path=str(path),
94
- details=str(e),
143
+ expected="Numeric dtype (int, float)",
144
+ got=f"{data_array.dtype}",
95
145
  ) from e
96
146
 
147
+
148
+ def _convert_array_to_float64(data_array: Any, path: Path) -> NDArray[np.float64]:
149
+ """Convert regular array to float64."""
97
150
  try:
98
- # Find waveform data array
99
- data_array = _find_data_array(npz, channel)
100
-
101
- if data_array is None:
102
- available = list(npz.keys())
103
- raise FormatError(
104
- "No waveform data found in NPZ file",
105
- file_path=str(path),
106
- expected=f"Array named: {', '.join(DATA_ARRAY_NAMES)}",
107
- got=f"Arrays: {', '.join(available)}",
108
- )
151
+ converted: NDArray[np.float64] = np.asarray(data_array.astype(np.float64), dtype=np.float64)
152
+ return converted
153
+ except (ValueError, TypeError) as e:
154
+ raise FormatError(
155
+ "Data array is not numeric",
156
+ file_path=str(path),
157
+ expected="Numeric dtype (int, float)",
158
+ got=f"{data_array.dtype}",
159
+ ) from e
109
160
 
110
- # Convert to float64 (keep mmap if enabled)
111
- if mmap and isinstance(data_array, np.memmap):
112
- # Keep as memmap, just ensure float64 dtype
113
- if data_array.dtype != np.float64:
114
- # For memmap, we need to copy to convert dtype
115
- try:
116
- data = data_array.astype(np.float64)
117
- except (ValueError, TypeError) as e:
118
- raise FormatError(
119
- "Data array is not numeric",
120
- file_path=str(path),
121
- expected="Numeric dtype (int, float)",
122
- got=f"{data_array.dtype}",
123
- ) from e
124
- else:
125
- data = data_array
126
- else:
127
- try:
128
- data = data_array.astype(np.float64)
129
- except (ValueError, TypeError) as e:
130
- raise FormatError(
131
- "Data array is not numeric",
132
- file_path=str(path),
133
- expected="Numeric dtype (int, float)",
134
- got=f"{data_array.dtype}",
135
- ) from e
136
-
137
- # Extract metadata
138
- detected_sample_rate = _find_metadata_value(npz, SAMPLE_RATE_KEYS)
139
- detected_vertical_scale = _find_metadata_value(npz, VERTICAL_SCALE_KEYS)
140
- detected_vertical_offset = _find_metadata_value(npz, VERTICAL_OFFSET_KEYS)
141
-
142
- # Use provided sample_rate if specified
143
- if sample_rate is not None:
144
- detected_sample_rate = sample_rate
145
- elif detected_sample_rate is None:
146
- detected_sample_rate = 1e6 # Default to 1 MSa/s
147
-
148
- # Build metadata
149
- metadata = TraceMetadata(
150
- sample_rate=float(detected_sample_rate),
151
- vertical_scale=float(detected_vertical_scale)
152
- if detected_vertical_scale is not None
153
- else None,
154
- vertical_offset=float(detected_vertical_offset)
155
- if detected_vertical_offset is not None
156
- else None,
157
- source_file=str(path),
158
- channel_name=_get_channel_name(npz, channel),
159
- )
160
161
 
161
- return WaveformTrace(data=data, metadata=metadata)
162
+ def _build_npz_metadata(
163
+ npz: Any, sample_rate: float | None, channel: str | int | None, path: Path
164
+ ) -> TraceMetadata:
165
+ """Build metadata from NPZ file."""
166
+ detected_sample_rate = _find_metadata_value(npz, SAMPLE_RATE_KEYS)
167
+ detected_vertical_scale = _find_metadata_value(npz, VERTICAL_SCALE_KEYS)
168
+ detected_vertical_offset = _find_metadata_value(npz, VERTICAL_OFFSET_KEYS)
162
169
 
163
- finally:
164
- npz.close()
170
+ final_sample_rate = sample_rate if sample_rate is not None else detected_sample_rate or 1e6
171
+
172
+ return TraceMetadata(
173
+ sample_rate=float(final_sample_rate),
174
+ vertical_scale=float(detected_vertical_scale) if detected_vertical_scale else None,
175
+ vertical_offset=float(detected_vertical_offset) if detected_vertical_offset else None,
176
+ source_file=str(path),
177
+ channel_name=_get_channel_name(npz, channel),
178
+ )
165
179
 
166
180
 
167
181
  def _find_data_array(
@@ -181,28 +195,86 @@ def _find_data_array(
181
195
 
182
196
  # If channel specified by name
183
197
  if isinstance(channel, str):
184
- if channel in keys:
185
- return npz[channel]
186
- # Try case-insensitive match
187
- channel_lower = channel.lower()
188
- for key in keys:
189
- if key.lower() == channel_lower:
190
- return npz[key]
191
- return None
198
+ return _find_array_by_name(npz, keys, channel)
192
199
 
193
200
  # If channel specified by index
194
201
  if isinstance(channel, int):
195
- # Find numeric-suffixed arrays (ch1, ch2, etc.)
196
- channel_arrays = [k for k in keys if _is_channel_array(k)]
197
- if channel_arrays and channel < len(channel_arrays):
198
- return npz[sorted(channel_arrays)[channel]]
199
- # Or use nth array
200
- data_arrays = [k for k in keys if _is_data_array(k)]
201
- if data_arrays and channel < len(data_arrays):
202
- return npz[data_arrays[channel]]
203
- return None
202
+ return _find_array_by_index(npz, keys, channel)
204
203
 
205
204
  # Auto-detect: look for common data array names
205
+ return _find_array_auto_detect(npz, keys)
206
+
207
+
208
+ def _find_array_by_name(
209
+ npz: np.lib.npyio.NpzFile,
210
+ keys: list[str],
211
+ channel: str,
212
+ ) -> NDArray[np.float64] | None:
213
+ """Find array by exact or case-insensitive channel name.
214
+
215
+ Args:
216
+ npz: Loaded NPZ file.
217
+ keys: List of available keys.
218
+ channel: Channel name to find.
219
+
220
+ Returns:
221
+ Array if found, None otherwise.
222
+ """
223
+ # Exact match
224
+ if channel in keys:
225
+ return npz[channel]
226
+
227
+ # Case-insensitive match
228
+ channel_lower = channel.lower()
229
+ for key in keys:
230
+ if key.lower() == channel_lower:
231
+ return npz[key]
232
+
233
+ return None
234
+
235
+
236
+ def _find_array_by_index(
237
+ npz: np.lib.npyio.NpzFile,
238
+ keys: list[str],
239
+ channel: int,
240
+ ) -> NDArray[np.float64] | None:
241
+ """Find array by channel index.
242
+
243
+ Args:
244
+ npz: Loaded NPZ file.
245
+ keys: List of available keys.
246
+ channel: Channel index.
247
+
248
+ Returns:
249
+ Array if found, None otherwise.
250
+ """
251
+ # Try numeric-suffixed arrays (ch1, ch2, etc.)
252
+ channel_arrays = [k for k in keys if _is_channel_array(k)]
253
+ if channel_arrays and channel < len(channel_arrays):
254
+ return npz[sorted(channel_arrays)[channel]]
255
+
256
+ # Fall back to nth data array
257
+ data_arrays = [k for k in keys if _is_data_array(k)]
258
+ if data_arrays and channel < len(data_arrays):
259
+ return npz[data_arrays[channel]]
260
+
261
+ return None
262
+
263
+
264
+ def _find_array_auto_detect(
265
+ npz: np.lib.npyio.NpzFile,
266
+ keys: list[str],
267
+ ) -> NDArray[np.float64] | None:
268
+ """Auto-detect waveform data array from common names.
269
+
270
+ Args:
271
+ npz: Loaded NPZ file.
272
+ keys: List of available keys.
273
+
274
+ Returns:
275
+ Array if found, None otherwise.
276
+ """
277
+ # Look for common data array names
206
278
  for name in DATA_ARRAY_NAMES:
207
279
  if name in keys:
208
280
  return npz[name]
@@ -212,13 +284,11 @@ def _find_data_array(
212
284
  if key.lower() == name_lower:
213
285
  return npz[key]
214
286
 
215
- # Fall back to first 1D or 2D array
287
+ # Fall back to first 1D or 2D array (large enough to be data)
216
288
  for key in keys:
217
289
  arr = npz[key]
218
- if isinstance(arr, np.ndarray) and arr.ndim in (1, 2):
219
- # Skip metadata scalars
220
- if arr.size > 10: # Arbitrary threshold
221
- return arr.ravel() if arr.ndim == 2 else arr
290
+ if isinstance(arr, np.ndarray) and arr.ndim in (1, 2) and arr.size > 10:
291
+ return arr.ravel() if arr.ndim == 2 else arr
222
292
 
223
293
  return None
224
294
 
@@ -254,37 +324,104 @@ def _find_metadata_value(
254
324
  """
255
325
  keys = list(npz.keys())
256
326
 
327
+ # Try exact and case-insensitive matches in keys
328
+ value = _find_value_in_keys(npz, keys, key_names)
329
+ if value is not None:
330
+ return value
331
+
332
+ # Check for metadata dict
333
+ return _find_value_in_metadata_dict(npz, keys, key_names)
334
+
335
+
336
+ def _find_value_in_keys(
337
+ npz: np.lib.npyio.NpzFile,
338
+ keys: list[str],
339
+ key_names: list[str],
340
+ ) -> float | None:
341
+ """Find value by exact or case-insensitive match in keys.
342
+
343
+ Args:
344
+ npz: Loaded NPZ file.
345
+ keys: List of available keys.
346
+ key_names: List of possible key names to try.
347
+
348
+ Returns:
349
+ Metadata value or None if not found.
350
+ """
257
351
  for name in key_names:
258
352
  # Exact match
259
- if name in keys:
260
- value = npz[name]
261
- if np.isscalar(value):
262
- return float(value) # type: ignore[arg-type]
263
- elif isinstance(value, np.ndarray) and value.size == 1:
264
- return float(value.item()) # type: ignore[arg-type]
353
+ value = _try_extract_float_from_key(npz, keys, name, name)
354
+ if value is not None:
355
+ return value
265
356
 
266
357
  # Case-insensitive match
267
358
  name_lower = name.lower()
268
359
  for key in keys:
269
360
  if key.lower() == name_lower:
270
- value = npz[key]
271
- if np.isscalar(value):
272
- return float(value) # type: ignore[arg-type]
273
- elif isinstance(value, np.ndarray) and value.size == 1:
274
- return float(value.item()) # type: ignore[arg-type]
361
+ value = _try_extract_float_from_key(npz, keys, key, name_lower)
362
+ if value is not None:
363
+ return value
275
364
 
276
- # Check for metadata dict
277
- if "metadata" in keys:
278
- metadata = npz["metadata"]
279
- if isinstance(metadata, np.ndarray):
280
- try:
281
- meta_dict = metadata.item()
282
- if isinstance(meta_dict, dict):
283
- for name in key_names:
284
- if name in meta_dict:
285
- return float(meta_dict[name])
286
- except (ValueError, TypeError):
287
- pass
365
+ return None
366
+
367
+
368
+ def _try_extract_float_from_key(
369
+ npz: np.lib.npyio.NpzFile,
370
+ keys: list[str],
371
+ key: str,
372
+ _name: str,
373
+ ) -> float | None:
374
+ """Try to extract float value from NPZ key.
375
+
376
+ Args:
377
+ npz: Loaded NPZ file.
378
+ keys: List of available keys.
379
+ key: Key to check.
380
+ _name: Original name (for matching, unused).
381
+
382
+ Returns:
383
+ Float value or None.
384
+ """
385
+ if key not in keys:
386
+ return None
387
+
388
+ value = npz[key]
389
+ if np.isscalar(value):
390
+ return float(value) # type: ignore[arg-type]
391
+ elif isinstance(value, np.ndarray) and value.size == 1:
392
+ return float(value.item())
393
+
394
+ return None
395
+
396
+
397
+ def _find_value_in_metadata_dict(
398
+ npz: np.lib.npyio.NpzFile,
399
+ keys: list[str],
400
+ key_names: list[str],
401
+ ) -> float | None:
402
+ """Find value in metadata dictionary.
403
+
404
+ Args:
405
+ npz: Loaded NPZ file.
406
+ keys: List of available keys.
407
+ key_names: List of possible key names to try.
408
+
409
+ Returns:
410
+ Metadata value or None if not found.
411
+ """
412
+ if "metadata" not in keys:
413
+ return None
414
+
415
+ metadata = npz["metadata"]
416
+ # NPZ files always return np.ndarray for valid keys
417
+ try:
418
+ meta_dict = metadata.item()
419
+ if isinstance(meta_dict, dict):
420
+ for name in key_names:
421
+ if name in meta_dict:
422
+ return float(meta_dict[name])
423
+ except (ValueError, TypeError):
424
+ pass
288
425
 
289
426
  return None
290
427