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
@@ -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"):