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
@@ -88,99 +88,115 @@ class AnalysisEngine:
88
88
  Raises:
89
89
  ValueError: If input type cannot be determined.
90
90
  """
91
- # If path provided, detect from extension
91
+ # Try path-based detection first
92
92
  if input_path is not None:
93
- ext = input_path.suffix.lower()
93
+ path_type = self._detect_from_extension(input_path)
94
+ if path_type is not None:
95
+ return path_type
94
96
 
95
- # Waveform formats
96
- if ext in {".wfm", ".csv", ".npz", ".h5", ".hdf5", ".wav", ".tdms"}:
97
- return InputType.WAVEFORM
98
- # Digital formats
99
- elif ext in {".vcd", ".sr"}:
100
- return InputType.DIGITAL
101
- # Packet formats
102
- elif ext in {".pcap", ".pcapng"}:
103
- return InputType.PCAP
104
- # Binary formats
105
- elif ext in {".bin", ".raw"}:
106
- return InputType.BINARY
107
- # S-parameter/Touchstone formats
108
- elif ext in {".s1p", ".s2p", ".s3p", ".s4p", ".s5p", ".s6p", ".s7p", ".s8p"}:
109
- return InputType.SPARAMS
110
-
111
- # Detect from data object characteristics
97
+ # Fallback to data object characteristics
98
+ data_type = self._detect_from_data_object(data)
99
+ if data_type is not None:
100
+ return data_type
101
+
102
+ raise ValueError("Unable to determine input type from path or data characteristics")
103
+
104
+ def _detect_from_extension(self, input_path: Path) -> InputType | None:
105
+ """Detect input type from file extension."""
106
+ ext = input_path.suffix.lower()
107
+
108
+ # Waveform formats
109
+ if ext in {".wfm", ".csv", ".npz", ".h5", ".hdf5", ".wav", ".tdms"}:
110
+ return InputType.WAVEFORM
111
+
112
+ # Digital formats
113
+ if ext in {".vcd", ".sr"}:
114
+ return InputType.DIGITAL
115
+
116
+ # Packet formats
117
+ if ext in {".pcap", ".pcapng"}:
118
+ return InputType.PCAP
119
+
120
+ # Binary formats
121
+ if ext in {".bin", ".raw"}:
122
+ return InputType.BINARY
123
+
124
+ # S-parameter/Touchstone formats
125
+ if ext in {".s1p", ".s2p", ".s3p", ".s4p", ".s5p", ".s6p", ".s7p", ".s8p"}:
126
+ return InputType.SPARAMS
127
+
128
+ return None
129
+
130
+ def _detect_from_data_object(self, data: Any) -> InputType | None:
131
+ """Detect input type from data object characteristics."""
132
+ # SParameterData
112
133
  if hasattr(data, "s_matrix") and hasattr(data, "frequencies"):
113
- # SParameterData
114
134
  return InputType.SPARAMS
115
- elif hasattr(data, "data") and hasattr(data, "metadata"):
116
- # WaveformTrace or DigitalTrace
135
+
136
+ # WaveformTrace or DigitalTrace
137
+ if hasattr(data, "data") and hasattr(data, "metadata"):
117
138
  if hasattr(data.metadata, "is_digital") and data.metadata.is_digital:
118
139
  return InputType.DIGITAL
119
140
  return InputType.WAVEFORM
120
- elif isinstance(data, bytes | bytearray):
141
+
142
+ # Raw binary data
143
+ if isinstance(data, bytes | bytearray):
121
144
  return InputType.BINARY
122
- elif isinstance(data, list):
123
- # Assume packet list
145
+
146
+ # Packet list
147
+ if isinstance(data, list):
124
148
  return InputType.PACKETS
125
- elif isinstance(data, np.ndarray):
126
- # Assume waveform
149
+
150
+ # NumPy array (assume waveform)
151
+ if isinstance(data, np.ndarray):
127
152
  return InputType.WAVEFORM
128
153
 
129
- raise ValueError("Unable to determine input type from path or data characteristics")
154
+ return None
130
155
 
131
- def run(
132
- self,
133
- input_path: Path | None = None,
134
- data: Any = None,
135
- progress_callback: Callable[[ProgressInfo], None] | None = None,
136
- ) -> dict[str, Any]:
137
- """Run comprehensive analysis on input data.
156
+ def _initialize_engine(self, input_path: Path | None) -> None:
157
+ """Initialize engine state for analysis run.
138
158
 
139
159
  Args:
140
- input_path: Path to input file (or None for in-memory data).
141
- data: Input data object (or None to load from input_path).
142
- progress_callback: Optional callback for progress updates.
143
-
144
- Returns:
145
- Dictionary with keys:
146
- - 'results': Dict mapping AnalysisDomain to analysis results
147
- - 'errors': List of AnalysisError objects
148
- - 'stats': Execution statistics dict
149
-
150
- Raises:
151
- ValueError: If neither input_path nor data provided.
152
- FileNotFoundError: If input_path doesn't exist.
153
-
154
- Example:
155
- >>> def progress(info: ProgressInfo):
156
- ... print(f"{info.phase}: {info.percent:.1f}%")
157
- >>> result = engine.run(input_path=Path("data.wfm"), progress_callback=progress)
160
+ input_path: Input file path (or None for in-memory data).
158
161
  """
159
- if input_path is None and data is None:
160
- raise ValueError("Must provide either input_path or data")
161
-
162
162
  self._start_time = time.time()
163
163
  self._input_path = input_path
164
-
165
- # Initialize argument preparer with input path and default sample rate
166
164
  default_sample_rate = self.config.default_sample_rate or 1e6
167
165
  self._arg_preparer = ArgumentPreparer(
168
166
  input_path=input_path, default_sample_rate=default_sample_rate
169
167
  )
170
168
 
171
- # Check available memory and adjust parallelism if needed
169
+ def _check_memory_and_adjust(self) -> None:
170
+ """Check available memory and adjust parallelism if needed."""
172
171
  from oscura.core.memory_guard import check_memory_available
173
172
 
174
- min_required_mb = 500 # Minimum 500MB needed for analysis
173
+ min_required_mb = 500
175
174
  if not check_memory_available(min_required_mb):
176
175
  logger.warning(
177
176
  f"Low memory available (< {min_required_mb} MB). "
178
177
  f"Reducing parallel workers to conserve memory."
179
178
  )
180
- # Temporarily reduce parallelism to conserve memory
181
179
  self.config.parallel_domains = False
182
180
 
183
- # Phase 1: Load data
181
+ def _load_input_data(
182
+ self,
183
+ input_path: Path | None,
184
+ data: Any,
185
+ progress_callback: Callable[[ProgressInfo], None] | None,
186
+ ) -> Any:
187
+ """Load input data from file or validate in-memory data.
188
+
189
+ Args:
190
+ input_path: Path to input file (or None).
191
+ data: In-memory data (or None).
192
+ progress_callback: Progress callback.
193
+
194
+ Returns:
195
+ Loaded or provided data.
196
+
197
+ Raises:
198
+ FileNotFoundError: If file not found.
199
+ """
184
200
  if progress_callback:
185
201
  progress_callback(
186
202
  ProgressInfo(
@@ -197,15 +213,22 @@ class AnalysisEngine:
197
213
  if data is None:
198
214
  if input_path is None or not input_path.exists():
199
215
  raise FileNotFoundError(f"Input file not found: {input_path}")
200
-
201
- # Load using oscura loaders
202
216
  from oscura.loaders import load
203
217
 
204
- data = load(input_path)
218
+ return load(input_path)
219
+ return data
205
220
 
206
- # Phase 2: Detect input type
207
- input_type = self.detect_input_type(input_path, data)
221
+ def _report_detection(
222
+ self,
223
+ input_type: InputType,
224
+ progress_callback: Callable[[ProgressInfo], None] | None,
225
+ ) -> None:
226
+ """Report input type detection progress.
208
227
 
228
+ Args:
229
+ input_type: Detected input type.
230
+ progress_callback: Progress callback.
231
+ """
209
232
  if progress_callback:
210
233
  progress_callback(
211
234
  ProgressInfo(
@@ -219,10 +242,21 @@ class AnalysisEngine:
219
242
  )
220
243
  )
221
244
 
222
- # Phase 3: Determine applicable domains
223
- applicable_domains = get_available_analyses(input_type)
245
+ def _plan_analysis_domains(
246
+ self,
247
+ input_type: InputType,
248
+ progress_callback: Callable[[ProgressInfo], None] | None,
249
+ ) -> list[AnalysisDomain]:
250
+ """Determine enabled analysis domains.
251
+
252
+ Args:
253
+ input_type: Input data type.
254
+ progress_callback: Progress callback.
224
255
 
225
- # Filter by configuration
256
+ Returns:
257
+ List of enabled domains.
258
+ """
259
+ applicable_domains = get_available_analyses(input_type)
226
260
  enabled_domains = [d for d in applicable_domains if self.config.is_domain_enabled(d)]
227
261
 
228
262
  if progress_callback:
@@ -237,80 +271,40 @@ class AnalysisEngine:
237
271
  estimated_remaining_seconds=None,
238
272
  )
239
273
  )
274
+ return enabled_domains
240
275
 
241
- # Phase 4: Execute analyses
242
- results: dict[AnalysisDomain, dict[str, Any]] = {}
243
- errors: list[AnalysisError] = []
244
-
245
- total_domains = len(enabled_domains)
276
+ def _execute_domains_parallel(
277
+ self,
278
+ enabled_domains: list[AnalysisDomain],
279
+ data: Any,
280
+ progress_callback: Callable[[ProgressInfo], None] | None,
281
+ ) -> tuple[dict[AnalysisDomain, dict[str, Any]], list[AnalysisError]]:
282
+ """Execute analysis domains in parallel.
246
283
 
247
- # Execute domains in parallel if enabled and multiple domains exist
248
- if self.config.parallel_domains and len(enabled_domains) > 1:
249
- import concurrent.futures
284
+ Args:
285
+ enabled_domains: List of domains to analyze.
286
+ data: Input data.
287
+ progress_callback: Progress callback.
250
288
 
251
- # Use ThreadPoolExecutor with bounded workers from config
252
- max_workers = min(self.config.max_parallel_workers, len(enabled_domains))
289
+ Returns:
290
+ Tuple of (results dict, errors list).
291
+ """
292
+ import concurrent.futures
253
293
 
254
- with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor:
255
- # Submit all domain executions
256
- futures = {
257
- executor.submit(self._execute_domain, domain, data): domain
258
- for domain in enabled_domains
259
- }
294
+ results: dict[AnalysisDomain, dict[str, Any]] = {}
295
+ errors: list[AnalysisError] = []
296
+ total_domains = len(enabled_domains)
297
+ max_workers = min(self.config.max_parallel_workers, len(enabled_domains))
260
298
 
261
- # Process results as they complete
262
- for completed, future in enumerate(concurrent.futures.as_completed(futures), 1):
263
- domain = futures[future]
264
- domain_percent = 10.0 + (completed / total_domains) * 80.0
265
-
266
- if progress_callback:
267
- progress_callback(
268
- ProgressInfo(
269
- phase="analyzing",
270
- domain=domain,
271
- function=None,
272
- percent=domain_percent,
273
- message=f"Completed domain: {domain.value}",
274
- elapsed_seconds=time.time() - self._start_time,
275
- estimated_remaining_seconds=None,
276
- )
277
- )
299
+ with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor:
300
+ futures = {
301
+ executor.submit(self._execute_domain, domain, data): domain
302
+ for domain in enabled_domains
303
+ }
278
304
 
279
- try:
280
- # Retrieve result with timeout
281
- timeout_seconds = self.config.timeout_per_analysis or 30.0
282
- domain_results, domain_errors = future.result(timeout=timeout_seconds * 10)
283
- if domain_results:
284
- results[domain] = domain_results
285
- errors.extend(domain_errors)
286
- except concurrent.futures.TimeoutError:
287
- logger.error(f"Domain {domain.value} exceeded timeout")
288
- errors.append(
289
- AnalysisError(
290
- domain=domain,
291
- function=f"{domain.value}.*",
292
- error_type="TimeoutError",
293
- error_message="Domain execution exceeded timeout",
294
- traceback=None,
295
- duration_ms=timeout_seconds * 10 * 1000,
296
- )
297
- )
298
- except Exception as e:
299
- logger.error(f"Domain {domain.value} failed: {e}")
300
- errors.append(
301
- AnalysisError(
302
- domain=domain,
303
- function=f"{domain.value}.*",
304
- error_type=type(e).__name__,
305
- error_message=str(e),
306
- traceback=traceback.format_exc(),
307
- duration_ms=0.0,
308
- )
309
- )
310
- else:
311
- # Sequential fallback (existing code)
312
- for idx, domain in enumerate(enabled_domains):
313
- domain_percent = 10.0 + (idx / total_domains) * 80.0
305
+ for completed, future in enumerate(concurrent.futures.as_completed(futures), 1):
306
+ domain = futures[future]
307
+ domain_percent = 10.0 + (completed / total_domains) * 80.0
314
308
 
315
309
  if progress_callback:
316
310
  progress_callback(
@@ -319,41 +313,134 @@ class AnalysisEngine:
319
313
  domain=domain,
320
314
  function=None,
321
315
  percent=domain_percent,
322
- message=f"Analyzing domain: {domain.value}",
316
+ message=f"Completed domain: {domain.value}",
323
317
  elapsed_seconds=time.time() - self._start_time,
324
318
  estimated_remaining_seconds=None,
325
319
  )
326
320
  )
327
321
 
328
- domain_results, domain_errors = self._execute_domain(domain, data)
329
- if domain_results:
330
- results[domain] = domain_results
331
- errors.extend(domain_errors)
322
+ self._handle_domain_future(future, domain, results, errors)
332
323
 
333
- # Phase 5: Complete
334
- total_duration = time.time() - self._start_time
324
+ return results, errors
335
325
 
336
- if progress_callback:
337
- progress_callback(
338
- ProgressInfo(
339
- phase="complete",
340
- domain=None,
341
- function=None,
342
- percent=100.0,
343
- message="Analysis complete",
344
- elapsed_seconds=total_duration,
345
- estimated_remaining_seconds=0.0,
326
+ def _handle_domain_future(
327
+ self,
328
+ future: Any,
329
+ domain: AnalysisDomain,
330
+ results: dict[AnalysisDomain, dict[str, Any]],
331
+ errors: list[AnalysisError],
332
+ ) -> None:
333
+ """Handle completed domain future.
334
+
335
+ Args:
336
+ future: Completed future.
337
+ domain: Domain being analyzed.
338
+ results: Results accumulator.
339
+ errors: Errors accumulator.
340
+ """
341
+ import concurrent.futures
342
+
343
+ try:
344
+ timeout_seconds = self.config.timeout_per_analysis or 30.0
345
+ domain_results, domain_errors = future.result(timeout=timeout_seconds * 10)
346
+ if domain_results:
347
+ results[domain] = domain_results
348
+ errors.extend(domain_errors)
349
+ except concurrent.futures.TimeoutError:
350
+ logger.error(f"Domain {domain.value} exceeded timeout")
351
+ errors.append(
352
+ AnalysisError(
353
+ domain=domain,
354
+ function=f"{domain.value}.*",
355
+ error_type="TimeoutError",
356
+ error_message="Domain execution exceeded timeout",
357
+ traceback=None,
358
+ duration_ms=timeout_seconds * 10 * 1000,
346
359
  )
347
360
  )
361
+ except Exception as e:
362
+ logger.error(f"Domain {domain.value} failed: {e}")
363
+ errors.append(
364
+ AnalysisError(
365
+ domain=domain,
366
+ function=f"{domain.value}.*",
367
+ error_type=type(e).__name__,
368
+ error_message=str(e),
369
+ traceback=traceback.format_exc(),
370
+ duration_ms=0.0,
371
+ )
372
+ )
373
+
374
+ def _execute_domains_sequential(
375
+ self,
376
+ enabled_domains: list[AnalysisDomain],
377
+ data: Any,
378
+ progress_callback: Callable[[ProgressInfo], None] | None,
379
+ ) -> tuple[dict[AnalysisDomain, dict[str, Any]], list[AnalysisError]]:
380
+ """Execute analysis domains sequentially.
381
+
382
+ Args:
383
+ enabled_domains: List of domains to analyze.
384
+ data: Input data.
385
+ progress_callback: Progress callback.
386
+
387
+ Returns:
388
+ Tuple of (results dict, errors list).
389
+ """
390
+ results: dict[AnalysisDomain, dict[str, Any]] = {}
391
+ errors: list[AnalysisError] = []
392
+ total_domains = len(enabled_domains)
393
+
394
+ for idx, domain in enumerate(enabled_domains):
395
+ domain_percent = 10.0 + (idx / total_domains) * 80.0
396
+
397
+ if progress_callback:
398
+ progress_callback(
399
+ ProgressInfo(
400
+ phase="analyzing",
401
+ domain=domain,
402
+ function=None,
403
+ percent=domain_percent,
404
+ message=f"Analyzing domain: {domain.value}",
405
+ elapsed_seconds=time.time() - self._start_time,
406
+ estimated_remaining_seconds=None,
407
+ )
408
+ )
409
+
410
+ domain_results, domain_errors = self._execute_domain(domain, data)
411
+ if domain_results:
412
+ results[domain] = domain_results
413
+ errors.extend(domain_errors)
414
+
415
+ return results, errors
416
+
417
+ def _calculate_statistics(
418
+ self,
419
+ results: dict[AnalysisDomain, dict[str, Any]],
420
+ errors: list[AnalysisError],
421
+ enabled_domains: list[AnalysisDomain],
422
+ input_type: InputType,
423
+ total_duration: float,
424
+ ) -> dict[str, Any]:
425
+ """Calculate execution statistics.
348
426
 
349
- # Calculate statistics
427
+ Args:
428
+ results: Analysis results.
429
+ errors: Analysis errors.
430
+ enabled_domains: List of enabled domains.
431
+ input_type: Input data type.
432
+ total_duration: Total execution time.
433
+
434
+ Returns:
435
+ Statistics dictionary.
436
+ """
350
437
  total_analyses = sum(len(dr) for dr in results.values())
351
438
  successful_analyses = sum(
352
439
  1 for dr in results.values() for v in dr.values() if not isinstance(v, Exception)
353
440
  )
354
441
  failed_analyses = len(errors)
355
442
 
356
- stats = {
443
+ return {
357
444
  "input_type": input_type.value,
358
445
  "total_domains": len(enabled_domains),
359
446
  "total_analyses": total_analyses,
@@ -365,6 +452,73 @@ class AnalysisEngine:
365
452
  "duration_seconds": total_duration,
366
453
  }
367
454
 
455
+ def run(
456
+ self,
457
+ input_path: Path | None = None,
458
+ data: Any = None,
459
+ progress_callback: Callable[[ProgressInfo], None] | None = None,
460
+ ) -> dict[str, Any]:
461
+ """Run comprehensive analysis on input data.
462
+
463
+ Args:
464
+ input_path: Path to input file (or None for in-memory data).
465
+ data: Input data object (or None to load from input_path).
466
+ progress_callback: Optional callback for progress updates.
467
+
468
+ Returns:
469
+ Dictionary with keys:
470
+ - 'results': Dict mapping AnalysisDomain to analysis results
471
+ - 'errors': List of AnalysisError objects
472
+ - 'stats': Execution statistics dict
473
+
474
+ Raises:
475
+ ValueError: If neither input_path nor data provided.
476
+ FileNotFoundError: If input_path doesn't exist.
477
+
478
+ Example:
479
+ >>> def progress(info: ProgressInfo):
480
+ ... print(f"{info.phase}: {info.percent:.1f}%")
481
+ >>> result = engine.run(input_path=Path("data.wfm"), progress_callback=progress)
482
+ """
483
+ if input_path is None and data is None:
484
+ raise ValueError("Must provide either input_path or data")
485
+
486
+ self._initialize_engine(input_path)
487
+ self._check_memory_and_adjust()
488
+ data = self._load_input_data(input_path, data, progress_callback)
489
+ input_type = self.detect_input_type(input_path, data)
490
+ self._report_detection(input_type, progress_callback)
491
+ enabled_domains = self._plan_analysis_domains(input_type, progress_callback)
492
+
493
+ # Execute analyses (parallel or sequential)
494
+ if self.config.parallel_domains and len(enabled_domains) > 1:
495
+ results, errors = self._execute_domains_parallel(
496
+ enabled_domains, data, progress_callback
497
+ )
498
+ else:
499
+ results, errors = self._execute_domains_sequential(
500
+ enabled_domains, data, progress_callback
501
+ )
502
+
503
+ # Finalize
504
+ total_duration = time.time() - self._start_time
505
+ if progress_callback:
506
+ progress_callback(
507
+ ProgressInfo(
508
+ phase="complete",
509
+ domain=None,
510
+ function=None,
511
+ percent=100.0,
512
+ message="Analysis complete",
513
+ elapsed_seconds=total_duration,
514
+ estimated_remaining_seconds=0.0,
515
+ )
516
+ )
517
+
518
+ stats = self._calculate_statistics(
519
+ results, errors, enabled_domains, input_type, total_duration
520
+ )
521
+
368
522
  return {
369
523
  "results": results,
370
524
  "errors": errors,
@@ -797,7 +951,7 @@ class AnalysisEngine:
797
951
  Result with quality score attached (if applicable).
798
952
  """
799
953
  try:
800
- from oscura.quality import score_analysis_result
954
+ from oscura.validation.quality import score_analysis_result
801
955
 
802
956
  # Extract raw data array for quality assessment
803
957
  if hasattr(data, "data"):