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
@@ -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