oscura 0.0.1__py3-none-any.whl → 0.1.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 (465) hide show
  1. oscura/__init__.py +813 -8
  2. oscura/__main__.py +392 -0
  3. oscura/analyzers/__init__.py +37 -0
  4. oscura/analyzers/digital/__init__.py +177 -0
  5. oscura/analyzers/digital/bus.py +691 -0
  6. oscura/analyzers/digital/clock.py +805 -0
  7. oscura/analyzers/digital/correlation.py +720 -0
  8. oscura/analyzers/digital/edges.py +632 -0
  9. oscura/analyzers/digital/extraction.py +413 -0
  10. oscura/analyzers/digital/quality.py +878 -0
  11. oscura/analyzers/digital/signal_quality.py +877 -0
  12. oscura/analyzers/digital/thresholds.py +708 -0
  13. oscura/analyzers/digital/timing.py +1104 -0
  14. oscura/analyzers/eye/__init__.py +46 -0
  15. oscura/analyzers/eye/diagram.py +434 -0
  16. oscura/analyzers/eye/metrics.py +555 -0
  17. oscura/analyzers/jitter/__init__.py +83 -0
  18. oscura/analyzers/jitter/ber.py +333 -0
  19. oscura/analyzers/jitter/decomposition.py +759 -0
  20. oscura/analyzers/jitter/measurements.py +413 -0
  21. oscura/analyzers/jitter/spectrum.py +220 -0
  22. oscura/analyzers/measurements.py +40 -0
  23. oscura/analyzers/packet/__init__.py +171 -0
  24. oscura/analyzers/packet/daq.py +1077 -0
  25. oscura/analyzers/packet/metrics.py +437 -0
  26. oscura/analyzers/packet/parser.py +327 -0
  27. oscura/analyzers/packet/payload.py +2156 -0
  28. oscura/analyzers/packet/payload_analysis.py +1312 -0
  29. oscura/analyzers/packet/payload_extraction.py +236 -0
  30. oscura/analyzers/packet/payload_patterns.py +670 -0
  31. oscura/analyzers/packet/stream.py +359 -0
  32. oscura/analyzers/patterns/__init__.py +266 -0
  33. oscura/analyzers/patterns/clustering.py +1036 -0
  34. oscura/analyzers/patterns/discovery.py +539 -0
  35. oscura/analyzers/patterns/learning.py +797 -0
  36. oscura/analyzers/patterns/matching.py +1091 -0
  37. oscura/analyzers/patterns/periodic.py +650 -0
  38. oscura/analyzers/patterns/sequences.py +767 -0
  39. oscura/analyzers/power/__init__.py +116 -0
  40. oscura/analyzers/power/ac_power.py +391 -0
  41. oscura/analyzers/power/basic.py +383 -0
  42. oscura/analyzers/power/conduction.py +314 -0
  43. oscura/analyzers/power/efficiency.py +297 -0
  44. oscura/analyzers/power/ripple.py +356 -0
  45. oscura/analyzers/power/soa.py +372 -0
  46. oscura/analyzers/power/switching.py +479 -0
  47. oscura/analyzers/protocol/__init__.py +150 -0
  48. oscura/analyzers/protocols/__init__.py +150 -0
  49. oscura/analyzers/protocols/base.py +500 -0
  50. oscura/analyzers/protocols/can.py +620 -0
  51. oscura/analyzers/protocols/can_fd.py +448 -0
  52. oscura/analyzers/protocols/flexray.py +405 -0
  53. oscura/analyzers/protocols/hdlc.py +399 -0
  54. oscura/analyzers/protocols/i2c.py +368 -0
  55. oscura/analyzers/protocols/i2s.py +296 -0
  56. oscura/analyzers/protocols/jtag.py +393 -0
  57. oscura/analyzers/protocols/lin.py +445 -0
  58. oscura/analyzers/protocols/manchester.py +333 -0
  59. oscura/analyzers/protocols/onewire.py +501 -0
  60. oscura/analyzers/protocols/spi.py +334 -0
  61. oscura/analyzers/protocols/swd.py +325 -0
  62. oscura/analyzers/protocols/uart.py +393 -0
  63. oscura/analyzers/protocols/usb.py +495 -0
  64. oscura/analyzers/signal_integrity/__init__.py +63 -0
  65. oscura/analyzers/signal_integrity/embedding.py +294 -0
  66. oscura/analyzers/signal_integrity/equalization.py +370 -0
  67. oscura/analyzers/signal_integrity/sparams.py +484 -0
  68. oscura/analyzers/spectral/__init__.py +53 -0
  69. oscura/analyzers/spectral/chunked.py +273 -0
  70. oscura/analyzers/spectral/chunked_fft.py +571 -0
  71. oscura/analyzers/spectral/chunked_wavelet.py +391 -0
  72. oscura/analyzers/spectral/fft.py +92 -0
  73. oscura/analyzers/statistical/__init__.py +250 -0
  74. oscura/analyzers/statistical/checksum.py +923 -0
  75. oscura/analyzers/statistical/chunked_corr.py +228 -0
  76. oscura/analyzers/statistical/classification.py +778 -0
  77. oscura/analyzers/statistical/entropy.py +1113 -0
  78. oscura/analyzers/statistical/ngrams.py +614 -0
  79. oscura/analyzers/statistics/__init__.py +119 -0
  80. oscura/analyzers/statistics/advanced.py +885 -0
  81. oscura/analyzers/statistics/basic.py +263 -0
  82. oscura/analyzers/statistics/correlation.py +630 -0
  83. oscura/analyzers/statistics/distribution.py +298 -0
  84. oscura/analyzers/statistics/outliers.py +463 -0
  85. oscura/analyzers/statistics/streaming.py +93 -0
  86. oscura/analyzers/statistics/trend.py +520 -0
  87. oscura/analyzers/validation.py +598 -0
  88. oscura/analyzers/waveform/__init__.py +36 -0
  89. oscura/analyzers/waveform/measurements.py +943 -0
  90. oscura/analyzers/waveform/measurements_with_uncertainty.py +371 -0
  91. oscura/analyzers/waveform/spectral.py +1689 -0
  92. oscura/analyzers/waveform/wavelets.py +298 -0
  93. oscura/api/__init__.py +62 -0
  94. oscura/api/dsl.py +538 -0
  95. oscura/api/fluent.py +571 -0
  96. oscura/api/operators.py +498 -0
  97. oscura/api/optimization.py +392 -0
  98. oscura/api/profiling.py +396 -0
  99. oscura/automotive/__init__.py +73 -0
  100. oscura/automotive/can/__init__.py +52 -0
  101. oscura/automotive/can/analysis.py +356 -0
  102. oscura/automotive/can/checksum.py +250 -0
  103. oscura/automotive/can/correlation.py +212 -0
  104. oscura/automotive/can/discovery.py +355 -0
  105. oscura/automotive/can/message_wrapper.py +375 -0
  106. oscura/automotive/can/models.py +385 -0
  107. oscura/automotive/can/patterns.py +381 -0
  108. oscura/automotive/can/session.py +452 -0
  109. oscura/automotive/can/state_machine.py +300 -0
  110. oscura/automotive/can/stimulus_response.py +461 -0
  111. oscura/automotive/dbc/__init__.py +15 -0
  112. oscura/automotive/dbc/generator.py +156 -0
  113. oscura/automotive/dbc/parser.py +146 -0
  114. oscura/automotive/dtc/__init__.py +30 -0
  115. oscura/automotive/dtc/database.py +3036 -0
  116. oscura/automotive/j1939/__init__.py +14 -0
  117. oscura/automotive/j1939/decoder.py +745 -0
  118. oscura/automotive/loaders/__init__.py +35 -0
  119. oscura/automotive/loaders/asc.py +98 -0
  120. oscura/automotive/loaders/blf.py +77 -0
  121. oscura/automotive/loaders/csv_can.py +136 -0
  122. oscura/automotive/loaders/dispatcher.py +136 -0
  123. oscura/automotive/loaders/mdf.py +331 -0
  124. oscura/automotive/loaders/pcap.py +132 -0
  125. oscura/automotive/obd/__init__.py +14 -0
  126. oscura/automotive/obd/decoder.py +707 -0
  127. oscura/automotive/uds/__init__.py +48 -0
  128. oscura/automotive/uds/decoder.py +265 -0
  129. oscura/automotive/uds/models.py +64 -0
  130. oscura/automotive/visualization.py +369 -0
  131. oscura/batch/__init__.py +55 -0
  132. oscura/batch/advanced.py +627 -0
  133. oscura/batch/aggregate.py +300 -0
  134. oscura/batch/analyze.py +139 -0
  135. oscura/batch/logging.py +487 -0
  136. oscura/batch/metrics.py +556 -0
  137. oscura/builders/__init__.py +41 -0
  138. oscura/builders/signal_builder.py +1131 -0
  139. oscura/cli/__init__.py +14 -0
  140. oscura/cli/batch.py +339 -0
  141. oscura/cli/characterize.py +273 -0
  142. oscura/cli/compare.py +775 -0
  143. oscura/cli/decode.py +551 -0
  144. oscura/cli/main.py +247 -0
  145. oscura/cli/shell.py +350 -0
  146. oscura/comparison/__init__.py +66 -0
  147. oscura/comparison/compare.py +397 -0
  148. oscura/comparison/golden.py +487 -0
  149. oscura/comparison/limits.py +391 -0
  150. oscura/comparison/mask.py +434 -0
  151. oscura/comparison/trace_diff.py +30 -0
  152. oscura/comparison/visualization.py +481 -0
  153. oscura/compliance/__init__.py +70 -0
  154. oscura/compliance/advanced.py +756 -0
  155. oscura/compliance/masks.py +363 -0
  156. oscura/compliance/reporting.py +483 -0
  157. oscura/compliance/testing.py +298 -0
  158. oscura/component/__init__.py +38 -0
  159. oscura/component/impedance.py +365 -0
  160. oscura/component/reactive.py +598 -0
  161. oscura/component/transmission_line.py +312 -0
  162. oscura/config/__init__.py +191 -0
  163. oscura/config/defaults.py +254 -0
  164. oscura/config/loader.py +348 -0
  165. oscura/config/memory.py +271 -0
  166. oscura/config/migration.py +458 -0
  167. oscura/config/pipeline.py +1077 -0
  168. oscura/config/preferences.py +530 -0
  169. oscura/config/protocol.py +875 -0
  170. oscura/config/schema.py +713 -0
  171. oscura/config/settings.py +420 -0
  172. oscura/config/thresholds.py +599 -0
  173. oscura/convenience.py +457 -0
  174. oscura/core/__init__.py +299 -0
  175. oscura/core/audit.py +457 -0
  176. oscura/core/backend_selector.py +405 -0
  177. oscura/core/cache.py +590 -0
  178. oscura/core/cancellation.py +439 -0
  179. oscura/core/confidence.py +225 -0
  180. oscura/core/config.py +506 -0
  181. oscura/core/correlation.py +216 -0
  182. oscura/core/cross_domain.py +422 -0
  183. oscura/core/debug.py +301 -0
  184. oscura/core/edge_cases.py +541 -0
  185. oscura/core/exceptions.py +535 -0
  186. oscura/core/gpu_backend.py +523 -0
  187. oscura/core/lazy.py +832 -0
  188. oscura/core/log_query.py +540 -0
  189. oscura/core/logging.py +931 -0
  190. oscura/core/logging_advanced.py +952 -0
  191. oscura/core/memoize.py +171 -0
  192. oscura/core/memory_check.py +274 -0
  193. oscura/core/memory_guard.py +290 -0
  194. oscura/core/memory_limits.py +336 -0
  195. oscura/core/memory_monitor.py +453 -0
  196. oscura/core/memory_progress.py +465 -0
  197. oscura/core/memory_warnings.py +315 -0
  198. oscura/core/numba_backend.py +362 -0
  199. oscura/core/performance.py +352 -0
  200. oscura/core/progress.py +524 -0
  201. oscura/core/provenance.py +358 -0
  202. oscura/core/results.py +331 -0
  203. oscura/core/types.py +504 -0
  204. oscura/core/uncertainty.py +383 -0
  205. oscura/discovery/__init__.py +52 -0
  206. oscura/discovery/anomaly_detector.py +672 -0
  207. oscura/discovery/auto_decoder.py +415 -0
  208. oscura/discovery/comparison.py +497 -0
  209. oscura/discovery/quality_validator.py +528 -0
  210. oscura/discovery/signal_detector.py +769 -0
  211. oscura/dsl/__init__.py +73 -0
  212. oscura/dsl/commands.py +246 -0
  213. oscura/dsl/interpreter.py +455 -0
  214. oscura/dsl/parser.py +689 -0
  215. oscura/dsl/repl.py +172 -0
  216. oscura/exceptions.py +59 -0
  217. oscura/exploratory/__init__.py +111 -0
  218. oscura/exploratory/error_recovery.py +642 -0
  219. oscura/exploratory/fuzzy.py +513 -0
  220. oscura/exploratory/fuzzy_advanced.py +786 -0
  221. oscura/exploratory/legacy.py +831 -0
  222. oscura/exploratory/parse.py +358 -0
  223. oscura/exploratory/recovery.py +275 -0
  224. oscura/exploratory/sync.py +382 -0
  225. oscura/exploratory/unknown.py +707 -0
  226. oscura/export/__init__.py +25 -0
  227. oscura/export/wireshark/README.md +265 -0
  228. oscura/export/wireshark/__init__.py +47 -0
  229. oscura/export/wireshark/generator.py +312 -0
  230. oscura/export/wireshark/lua_builder.py +159 -0
  231. oscura/export/wireshark/templates/dissector.lua.j2 +92 -0
  232. oscura/export/wireshark/type_mapping.py +165 -0
  233. oscura/export/wireshark/validator.py +105 -0
  234. oscura/exporters/__init__.py +94 -0
  235. oscura/exporters/csv.py +303 -0
  236. oscura/exporters/exporters.py +44 -0
  237. oscura/exporters/hdf5.py +219 -0
  238. oscura/exporters/html_export.py +701 -0
  239. oscura/exporters/json_export.py +291 -0
  240. oscura/exporters/markdown_export.py +367 -0
  241. oscura/exporters/matlab_export.py +354 -0
  242. oscura/exporters/npz_export.py +219 -0
  243. oscura/exporters/spice_export.py +210 -0
  244. oscura/extensibility/__init__.py +131 -0
  245. oscura/extensibility/docs.py +752 -0
  246. oscura/extensibility/extensions.py +1125 -0
  247. oscura/extensibility/logging.py +259 -0
  248. oscura/extensibility/measurements.py +485 -0
  249. oscura/extensibility/plugins.py +414 -0
  250. oscura/extensibility/registry.py +346 -0
  251. oscura/extensibility/templates.py +913 -0
  252. oscura/extensibility/validation.py +651 -0
  253. oscura/filtering/__init__.py +89 -0
  254. oscura/filtering/base.py +563 -0
  255. oscura/filtering/convenience.py +564 -0
  256. oscura/filtering/design.py +725 -0
  257. oscura/filtering/filters.py +32 -0
  258. oscura/filtering/introspection.py +605 -0
  259. oscura/guidance/__init__.py +24 -0
  260. oscura/guidance/recommender.py +429 -0
  261. oscura/guidance/wizard.py +518 -0
  262. oscura/inference/__init__.py +251 -0
  263. oscura/inference/active_learning/README.md +153 -0
  264. oscura/inference/active_learning/__init__.py +38 -0
  265. oscura/inference/active_learning/lstar.py +257 -0
  266. oscura/inference/active_learning/observation_table.py +230 -0
  267. oscura/inference/active_learning/oracle.py +78 -0
  268. oscura/inference/active_learning/teachers/__init__.py +15 -0
  269. oscura/inference/active_learning/teachers/simulator.py +192 -0
  270. oscura/inference/adaptive_tuning.py +453 -0
  271. oscura/inference/alignment.py +653 -0
  272. oscura/inference/bayesian.py +943 -0
  273. oscura/inference/binary.py +1016 -0
  274. oscura/inference/crc_reverse.py +711 -0
  275. oscura/inference/logic.py +288 -0
  276. oscura/inference/message_format.py +1305 -0
  277. oscura/inference/protocol.py +417 -0
  278. oscura/inference/protocol_dsl.py +1084 -0
  279. oscura/inference/protocol_library.py +1230 -0
  280. oscura/inference/sequences.py +809 -0
  281. oscura/inference/signal_intelligence.py +1509 -0
  282. oscura/inference/spectral.py +215 -0
  283. oscura/inference/state_machine.py +634 -0
  284. oscura/inference/stream.py +918 -0
  285. oscura/integrations/__init__.py +59 -0
  286. oscura/integrations/llm.py +1827 -0
  287. oscura/jupyter/__init__.py +32 -0
  288. oscura/jupyter/display.py +268 -0
  289. oscura/jupyter/magic.py +334 -0
  290. oscura/loaders/__init__.py +526 -0
  291. oscura/loaders/binary.py +69 -0
  292. oscura/loaders/configurable.py +1255 -0
  293. oscura/loaders/csv.py +26 -0
  294. oscura/loaders/csv_loader.py +473 -0
  295. oscura/loaders/hdf5.py +9 -0
  296. oscura/loaders/hdf5_loader.py +510 -0
  297. oscura/loaders/lazy.py +370 -0
  298. oscura/loaders/mmap_loader.py +583 -0
  299. oscura/loaders/numpy_loader.py +436 -0
  300. oscura/loaders/pcap.py +432 -0
  301. oscura/loaders/preprocessing.py +368 -0
  302. oscura/loaders/rigol.py +287 -0
  303. oscura/loaders/sigrok.py +321 -0
  304. oscura/loaders/tdms.py +367 -0
  305. oscura/loaders/tektronix.py +711 -0
  306. oscura/loaders/validation.py +584 -0
  307. oscura/loaders/vcd.py +464 -0
  308. oscura/loaders/wav.py +233 -0
  309. oscura/math/__init__.py +45 -0
  310. oscura/math/arithmetic.py +824 -0
  311. oscura/math/interpolation.py +413 -0
  312. oscura/onboarding/__init__.py +39 -0
  313. oscura/onboarding/help.py +498 -0
  314. oscura/onboarding/tutorials.py +405 -0
  315. oscura/onboarding/wizard.py +466 -0
  316. oscura/optimization/__init__.py +19 -0
  317. oscura/optimization/parallel.py +440 -0
  318. oscura/optimization/search.py +532 -0
  319. oscura/pipeline/__init__.py +43 -0
  320. oscura/pipeline/base.py +338 -0
  321. oscura/pipeline/composition.py +242 -0
  322. oscura/pipeline/parallel.py +448 -0
  323. oscura/pipeline/pipeline.py +375 -0
  324. oscura/pipeline/reverse_engineering.py +1119 -0
  325. oscura/plugins/__init__.py +122 -0
  326. oscura/plugins/base.py +272 -0
  327. oscura/plugins/cli.py +497 -0
  328. oscura/plugins/discovery.py +411 -0
  329. oscura/plugins/isolation.py +418 -0
  330. oscura/plugins/lifecycle.py +959 -0
  331. oscura/plugins/manager.py +493 -0
  332. oscura/plugins/registry.py +421 -0
  333. oscura/plugins/versioning.py +372 -0
  334. oscura/py.typed +0 -0
  335. oscura/quality/__init__.py +65 -0
  336. oscura/quality/ensemble.py +740 -0
  337. oscura/quality/explainer.py +338 -0
  338. oscura/quality/scoring.py +616 -0
  339. oscura/quality/warnings.py +456 -0
  340. oscura/reporting/__init__.py +248 -0
  341. oscura/reporting/advanced.py +1234 -0
  342. oscura/reporting/analyze.py +448 -0
  343. oscura/reporting/argument_preparer.py +596 -0
  344. oscura/reporting/auto_report.py +507 -0
  345. oscura/reporting/batch.py +615 -0
  346. oscura/reporting/chart_selection.py +223 -0
  347. oscura/reporting/comparison.py +330 -0
  348. oscura/reporting/config.py +615 -0
  349. oscura/reporting/content/__init__.py +39 -0
  350. oscura/reporting/content/executive.py +127 -0
  351. oscura/reporting/content/filtering.py +191 -0
  352. oscura/reporting/content/minimal.py +257 -0
  353. oscura/reporting/content/verbosity.py +162 -0
  354. oscura/reporting/core.py +508 -0
  355. oscura/reporting/core_formats/__init__.py +17 -0
  356. oscura/reporting/core_formats/multi_format.py +210 -0
  357. oscura/reporting/engine.py +836 -0
  358. oscura/reporting/export.py +366 -0
  359. oscura/reporting/formatting/__init__.py +129 -0
  360. oscura/reporting/formatting/emphasis.py +81 -0
  361. oscura/reporting/formatting/numbers.py +403 -0
  362. oscura/reporting/formatting/standards.py +55 -0
  363. oscura/reporting/formatting.py +466 -0
  364. oscura/reporting/html.py +578 -0
  365. oscura/reporting/index.py +590 -0
  366. oscura/reporting/multichannel.py +296 -0
  367. oscura/reporting/output.py +379 -0
  368. oscura/reporting/pdf.py +373 -0
  369. oscura/reporting/plots.py +731 -0
  370. oscura/reporting/pptx_export.py +360 -0
  371. oscura/reporting/renderers/__init__.py +11 -0
  372. oscura/reporting/renderers/pdf.py +94 -0
  373. oscura/reporting/sections.py +471 -0
  374. oscura/reporting/standards.py +680 -0
  375. oscura/reporting/summary_generator.py +368 -0
  376. oscura/reporting/tables.py +397 -0
  377. oscura/reporting/template_system.py +724 -0
  378. oscura/reporting/templates/__init__.py +15 -0
  379. oscura/reporting/templates/definition.py +205 -0
  380. oscura/reporting/templates/index.html +649 -0
  381. oscura/reporting/templates/index.md +173 -0
  382. oscura/schemas/__init__.py +158 -0
  383. oscura/schemas/bus_configuration.json +322 -0
  384. oscura/schemas/device_mapping.json +182 -0
  385. oscura/schemas/packet_format.json +418 -0
  386. oscura/schemas/protocol_definition.json +363 -0
  387. oscura/search/__init__.py +16 -0
  388. oscura/search/anomaly.py +292 -0
  389. oscura/search/context.py +149 -0
  390. oscura/search/pattern.py +160 -0
  391. oscura/session/__init__.py +34 -0
  392. oscura/session/annotations.py +289 -0
  393. oscura/session/history.py +313 -0
  394. oscura/session/session.py +445 -0
  395. oscura/streaming/__init__.py +43 -0
  396. oscura/streaming/chunked.py +611 -0
  397. oscura/streaming/progressive.py +393 -0
  398. oscura/streaming/realtime.py +622 -0
  399. oscura/testing/__init__.py +54 -0
  400. oscura/testing/synthetic.py +808 -0
  401. oscura/triggering/__init__.py +68 -0
  402. oscura/triggering/base.py +229 -0
  403. oscura/triggering/edge.py +353 -0
  404. oscura/triggering/pattern.py +344 -0
  405. oscura/triggering/pulse.py +581 -0
  406. oscura/triggering/window.py +453 -0
  407. oscura/ui/__init__.py +48 -0
  408. oscura/ui/formatters.py +526 -0
  409. oscura/ui/progressive_display.py +340 -0
  410. oscura/utils/__init__.py +99 -0
  411. oscura/utils/autodetect.py +338 -0
  412. oscura/utils/buffer.py +389 -0
  413. oscura/utils/lazy.py +407 -0
  414. oscura/utils/lazy_imports.py +147 -0
  415. oscura/utils/memory.py +836 -0
  416. oscura/utils/memory_advanced.py +1326 -0
  417. oscura/utils/memory_extensions.py +465 -0
  418. oscura/utils/progressive.py +352 -0
  419. oscura/utils/windowing.py +362 -0
  420. oscura/visualization/__init__.py +321 -0
  421. oscura/visualization/accessibility.py +526 -0
  422. oscura/visualization/annotations.py +374 -0
  423. oscura/visualization/axis_scaling.py +305 -0
  424. oscura/visualization/colors.py +453 -0
  425. oscura/visualization/digital.py +337 -0
  426. oscura/visualization/eye.py +420 -0
  427. oscura/visualization/histogram.py +281 -0
  428. oscura/visualization/interactive.py +858 -0
  429. oscura/visualization/jitter.py +702 -0
  430. oscura/visualization/keyboard.py +394 -0
  431. oscura/visualization/layout.py +365 -0
  432. oscura/visualization/optimization.py +1028 -0
  433. oscura/visualization/palettes.py +446 -0
  434. oscura/visualization/plot.py +92 -0
  435. oscura/visualization/power.py +290 -0
  436. oscura/visualization/power_extended.py +626 -0
  437. oscura/visualization/presets.py +467 -0
  438. oscura/visualization/protocols.py +932 -0
  439. oscura/visualization/render.py +207 -0
  440. oscura/visualization/rendering.py +444 -0
  441. oscura/visualization/reverse_engineering.py +791 -0
  442. oscura/visualization/signal_integrity.py +808 -0
  443. oscura/visualization/specialized.py +553 -0
  444. oscura/visualization/spectral.py +811 -0
  445. oscura/visualization/styles.py +381 -0
  446. oscura/visualization/thumbnails.py +311 -0
  447. oscura/visualization/time_axis.py +351 -0
  448. oscura/visualization/waveform.py +367 -0
  449. oscura/workflow/__init__.py +13 -0
  450. oscura/workflow/dag.py +377 -0
  451. oscura/workflows/__init__.py +58 -0
  452. oscura/workflows/compliance.py +280 -0
  453. oscura/workflows/digital.py +272 -0
  454. oscura/workflows/multi_trace.py +502 -0
  455. oscura/workflows/power.py +178 -0
  456. oscura/workflows/protocol.py +492 -0
  457. oscura/workflows/reverse_engineering.py +639 -0
  458. oscura/workflows/signal_integrity.py +227 -0
  459. oscura-0.1.0.dist-info/METADATA +300 -0
  460. oscura-0.1.0.dist-info/RECORD +463 -0
  461. oscura-0.1.0.dist-info/entry_points.txt +2 -0
  462. {oscura-0.0.1.dist-info → oscura-0.1.0.dist-info}/licenses/LICENSE +1 -1
  463. oscura-0.0.1.dist-info/METADATA +0 -63
  464. oscura-0.0.1.dist-info/RECORD +0 -5
  465. {oscura-0.0.1.dist-info → oscura-0.1.0.dist-info}/WHEEL +0 -0
@@ -0,0 +1,791 @@
1
+ """Reverse Engineering Pipeline Visualization Module.
2
+
3
+ This module provides comprehensive visualization functions for reverse engineering
4
+ pipeline results, including message type distributions, field layouts, confidence
5
+ heatmaps, protocol detection scores, and pipeline performance metrics.
6
+
7
+ Functions:
8
+ plot_re_summary: Multi-panel dashboard of RE pipeline results
9
+ plot_message_type_distribution: Pie/bar chart of discovered message types
10
+ plot_message_field_layout: Visual field layout with byte positions
11
+ plot_field_confidence_heatmap: Heatmap of field inference confidence scores
12
+ plot_protocol_candidates: Bar chart of protocol detection scores
13
+ plot_crc_parameters: Visualization of detected CRC parameters
14
+ plot_pipeline_timing: Performance metrics for each pipeline stage
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ from typing import TYPE_CHECKING, Any
20
+
21
+ import matplotlib.pyplot as plt
22
+ import numpy as np
23
+ from matplotlib.patches import Rectangle
24
+
25
+ if TYPE_CHECKING:
26
+ from matplotlib.figure import Figure
27
+
28
+ from oscura.inference.crc_reverse import CRCParameters
29
+ from oscura.inference.message_format import InferredField, MessageSchema
30
+ from oscura.pipeline.reverse_engineering import (
31
+ MessageTypeInfo,
32
+ ProtocolCandidate,
33
+ REAnalysisResult,
34
+ )
35
+
36
+ __all__ = [
37
+ "plot_crc_parameters",
38
+ "plot_field_confidence_heatmap",
39
+ "plot_message_field_layout",
40
+ "plot_message_type_distribution",
41
+ "plot_pipeline_timing",
42
+ "plot_protocol_candidates",
43
+ "plot_re_summary",
44
+ ]
45
+
46
+
47
+ def plot_re_summary(
48
+ result: REAnalysisResult,
49
+ *,
50
+ figsize: tuple[float, float] = (16, 10),
51
+ title: str | None = None,
52
+ ) -> Figure:
53
+ """Create multi-panel dashboard showing RE pipeline results overview.
54
+
55
+ Displays a comprehensive summary of the reverse engineering analysis including:
56
+ - Message type distribution
57
+ - Protocol candidates
58
+ - Pipeline timing
59
+ - Key statistics
60
+
61
+ Args:
62
+ result: REAnalysisResult from pipeline analysis.
63
+ figsize: Figure size (width, height) in inches.
64
+ title: Optional custom title for the dashboard.
65
+
66
+ Returns:
67
+ Matplotlib figure object.
68
+
69
+ Example:
70
+ >>> from oscura.pipeline.reverse_engineering import REPipeline
71
+ >>> pipeline = REPipeline()
72
+ >>> result = pipeline.analyze(data)
73
+ >>> fig = plot_re_summary(result)
74
+ >>> fig.savefig("re_summary.png", dpi=300)
75
+ """
76
+ fig = plt.figure(figsize=figsize)
77
+
78
+ # Create grid layout
79
+ gs = fig.add_gridspec(2, 3, hspace=0.3, wspace=0.3)
80
+
81
+ # Panel 1: Summary statistics (top-left)
82
+ ax1 = fig.add_subplot(gs[0, 0])
83
+ _plot_summary_stats(ax1, result)
84
+
85
+ # Panel 2: Message type distribution (top-center)
86
+ ax2 = fig.add_subplot(gs[0, 1])
87
+ _plot_message_types_panel(ax2, result.message_types)
88
+
89
+ # Panel 3: Protocol candidates (top-right)
90
+ ax3 = fig.add_subplot(gs[0, 2])
91
+ _plot_protocol_panel(ax3, result.protocol_candidates)
92
+
93
+ # Panel 4: Pipeline timing (bottom-left and center)
94
+ ax4 = fig.add_subplot(gs[1, :2])
95
+ _plot_timing_panel(ax4, result.statistics)
96
+
97
+ # Panel 5: Warnings/info (bottom-right)
98
+ ax5 = fig.add_subplot(gs[1, 2])
99
+ _plot_warnings_panel(ax5, result)
100
+
101
+ # Set main title
102
+ if title:
103
+ fig.suptitle(title, fontsize=14, fontweight="bold")
104
+ else:
105
+ fig.suptitle("Reverse Engineering Analysis Summary", fontsize=14, fontweight="bold")
106
+
107
+ return fig
108
+
109
+
110
+ def plot_message_type_distribution(
111
+ message_types: list[MessageTypeInfo],
112
+ *,
113
+ figsize: tuple[float, float] = (12, 5),
114
+ chart_type: str = "both",
115
+ title: str | None = None,
116
+ ) -> Figure:
117
+ """Plot pie/bar chart of discovered message types.
118
+
119
+ Args:
120
+ message_types: List of MessageTypeInfo from RE analysis.
121
+ figsize: Figure size (width, height) in inches.
122
+ chart_type: Type of chart - "pie", "bar", or "both".
123
+ title: Optional custom title.
124
+
125
+ Returns:
126
+ Matplotlib figure object.
127
+
128
+ Example:
129
+ >>> fig = plot_message_type_distribution(result.message_types)
130
+ """
131
+ if not message_types:
132
+ fig, ax = plt.subplots(figsize=figsize)
133
+ ax.text(0.5, 0.5, "No message types detected", ha="center", va="center", fontsize=14)
134
+ ax.set_axis_off()
135
+ return fig
136
+
137
+ if chart_type == "both":
138
+ fig, (ax1, ax2) = plt.subplots(1, 2, figsize=figsize)
139
+ _plot_type_pie(ax1, message_types)
140
+ _plot_type_bar(ax2, message_types)
141
+ elif chart_type == "pie":
142
+ fig, ax = plt.subplots(figsize=figsize)
143
+ _plot_type_pie(ax, message_types)
144
+ else: # bar
145
+ fig, ax = plt.subplots(figsize=figsize)
146
+ _plot_type_bar(ax, message_types)
147
+
148
+ if title:
149
+ fig.suptitle(title, fontsize=12, fontweight="bold")
150
+ else:
151
+ fig.suptitle("Message Type Distribution", fontsize=12, fontweight="bold")
152
+
153
+ plt.tight_layout()
154
+ return fig
155
+
156
+
157
+ def plot_message_field_layout(
158
+ schema: MessageSchema,
159
+ *,
160
+ figsize: tuple[float, float] = (14, 6),
161
+ title: str | None = None,
162
+ show_values: bool = True,
163
+ ) -> Figure:
164
+ """Create visual field layout diagram showing byte positions.
165
+
166
+ Displays a horizontal layout of message fields with:
167
+ - Field boundaries marked
168
+ - Field types color-coded
169
+ - Byte offsets labeled
170
+ - Optional sample values
171
+
172
+ Args:
173
+ schema: MessageSchema with inferred field structure.
174
+ figsize: Figure size (width, height) in inches.
175
+ title: Optional custom title.
176
+ show_values: Whether to show sample field values.
177
+
178
+ Returns:
179
+ Matplotlib figure object.
180
+
181
+ Example:
182
+ >>> from oscura.inference.message_format import infer_format
183
+ >>> schema = infer_format(messages)
184
+ >>> fig = plot_message_field_layout(schema)
185
+ """
186
+ fig, ax = plt.subplots(figsize=figsize)
187
+
188
+ if not schema.fields:
189
+ ax.text(0.5, 0.5, "No fields detected", ha="center", va="center", fontsize=14)
190
+ ax.set_axis_off()
191
+ return fig
192
+
193
+ # Color map for field types
194
+ type_colors = {
195
+ "constant": "#4CAF50", # Green
196
+ "counter": "#2196F3", # Blue
197
+ "timestamp": "#9C27B0", # Purple
198
+ "length": "#FF9800", # Orange
199
+ "checksum": "#F44336", # Red
200
+ "data": "#607D8B", # Gray
201
+ "unknown": "#9E9E9E", # Light gray
202
+ }
203
+
204
+ total_bytes = schema.total_size
205
+ bar_height = 0.6
206
+ y_center = 0.5
207
+
208
+ # Draw each field as a colored rectangle
209
+ for field in schema.fields:
210
+ color = type_colors.get(field.field_type, "#9E9E9E")
211
+ width = field.size / total_bytes
212
+
213
+ # Draw field rectangle
214
+ rect = Rectangle(
215
+ (field.offset / total_bytes, y_center - bar_height / 2),
216
+ width,
217
+ bar_height,
218
+ facecolor=color,
219
+ edgecolor="black",
220
+ linewidth=1.5,
221
+ )
222
+ ax.add_patch(rect)
223
+
224
+ # Add field label
225
+ x_center = (field.offset + field.size / 2) / total_bytes
226
+ label = f"{field.name}\n({field.field_type})"
227
+ if field.size <= 2:
228
+ label = field.field_type[:3]
229
+
230
+ ax.text(
231
+ x_center,
232
+ y_center,
233
+ label,
234
+ ha="center",
235
+ va="center",
236
+ fontsize=9 if field.size > 2 else 7,
237
+ fontweight="bold",
238
+ color="white",
239
+ )
240
+
241
+ # Add byte offset below
242
+ ax.text(
243
+ field.offset / total_bytes,
244
+ y_center - bar_height / 2 - 0.08,
245
+ f"{field.offset}",
246
+ ha="center",
247
+ va="top",
248
+ fontsize=8,
249
+ )
250
+
251
+ # Add end offset
252
+ ax.text(
253
+ 1.0,
254
+ y_center - bar_height / 2 - 0.08,
255
+ f"{total_bytes}",
256
+ ha="center",
257
+ va="top",
258
+ fontsize=8,
259
+ )
260
+
261
+ # Add legend
262
+ legend_elements = [
263
+ Rectangle((0, 0), 1, 1, facecolor=color, label=ftype)
264
+ for ftype, color in type_colors.items()
265
+ if any(f.field_type == ftype for f in schema.fields)
266
+ ]
267
+ ax.legend(handles=legend_elements, loc="upper right", fontsize=8)
268
+
269
+ ax.set_xlim(-0.02, 1.02)
270
+ ax.set_ylim(0, 1)
271
+ ax.set_aspect("equal")
272
+ ax.axis("off")
273
+
274
+ if title:
275
+ ax.set_title(title, fontsize=12, fontweight="bold", pad=20)
276
+ else:
277
+ ax.set_title(
278
+ f"Message Field Layout ({total_bytes} bytes)", fontsize=12, fontweight="bold", pad=20
279
+ )
280
+
281
+ return fig
282
+
283
+
284
+ def plot_field_confidence_heatmap(
285
+ fields: list[InferredField],
286
+ *,
287
+ figsize: tuple[float, float] = (12, 6),
288
+ title: str | None = None,
289
+ ) -> Figure:
290
+ """Create heatmap showing confidence scores for field inferences.
291
+
292
+ Displays a visual representation of how confident the inference
293
+ algorithm is about each field's type and boundaries.
294
+
295
+ Args:
296
+ fields: List of InferredField objects with confidence scores.
297
+ figsize: Figure size (width, height) in inches.
298
+ title: Optional custom title.
299
+
300
+ Returns:
301
+ Matplotlib figure object.
302
+
303
+ Example:
304
+ >>> fig = plot_field_confidence_heatmap(schema.fields)
305
+ """
306
+ fig, ax = plt.subplots(figsize=figsize)
307
+
308
+ if not fields:
309
+ ax.text(0.5, 0.5, "No fields to display", ha="center", va="center", fontsize=14)
310
+ ax.set_axis_off()
311
+ return fig
312
+
313
+ # Create data matrix
314
+ n_fields = len(fields)
315
+ metrics = ["Confidence", "Entropy", "Variance"]
316
+ data = np.zeros((len(metrics), n_fields))
317
+
318
+ for i, field in enumerate(fields):
319
+ data[0, i] = field.confidence
320
+ # Normalize entropy (0-8 bits) to 0-1
321
+ data[1, i] = min(field.entropy / 8.0, 1.0)
322
+ # Normalize variance using log scale
323
+ data[2, i] = min(np.log1p(field.variance) / 10.0, 1.0)
324
+
325
+ # Create heatmap
326
+ im = ax.imshow(data, cmap="RdYlGn", aspect="auto", vmin=0, vmax=1)
327
+
328
+ # Add colorbar
329
+ cbar = plt.colorbar(im, ax=ax)
330
+ cbar.set_label("Score (normalized)", fontsize=10)
331
+
332
+ # Set labels
333
+ field_names = [f.name for f in fields]
334
+ ax.set_xticks(range(n_fields))
335
+ ax.set_xticklabels(field_names, rotation=45, ha="right", fontsize=9)
336
+ ax.set_yticks(range(len(metrics)))
337
+ ax.set_yticklabels(metrics, fontsize=10)
338
+
339
+ # Add text annotations
340
+ for i in range(len(metrics)):
341
+ for j in range(n_fields):
342
+ value = data[i, j]
343
+ color = "white" if value < 0.5 else "black"
344
+ ax.text(j, i, f"{value:.2f}", ha="center", va="center", color=color, fontsize=8)
345
+
346
+ if title:
347
+ ax.set_title(title, fontsize=12, fontweight="bold", pad=10)
348
+ else:
349
+ ax.set_title("Field Inference Confidence Heatmap", fontsize=12, fontweight="bold", pad=10)
350
+
351
+ plt.tight_layout()
352
+ return fig
353
+
354
+
355
+ def plot_protocol_candidates(
356
+ candidates: list[ProtocolCandidate],
357
+ *,
358
+ figsize: tuple[float, float] = (10, 6),
359
+ title: str | None = None,
360
+ top_n: int = 10,
361
+ ) -> Figure:
362
+ """Create bar chart of protocol detection scores.
363
+
364
+ Shows the confidence scores for each detected protocol candidate,
365
+ with visual indicators for the evidence sources.
366
+
367
+ Args:
368
+ candidates: List of ProtocolCandidate objects.
369
+ figsize: Figure size (width, height) in inches.
370
+ title: Optional custom title.
371
+ top_n: Maximum number of candidates to display.
372
+
373
+ Returns:
374
+ Matplotlib figure object.
375
+
376
+ Example:
377
+ >>> fig = plot_protocol_candidates(result.protocol_candidates)
378
+ """
379
+ fig, ax = plt.subplots(figsize=figsize)
380
+
381
+ if not candidates:
382
+ ax.text(0.5, 0.5, "No protocol candidates detected", ha="center", va="center", fontsize=14)
383
+ ax.set_axis_off()
384
+ return fig
385
+
386
+ # Sort by confidence and take top N
387
+ sorted_candidates = sorted(candidates, key=lambda c: c.confidence, reverse=True)[:top_n]
388
+
389
+ names = [c.name for c in sorted_candidates]
390
+ confidences = [c.confidence for c in sorted_candidates]
391
+
392
+ # Color based on confidence level
393
+ colors = []
394
+ for conf in confidences:
395
+ if conf >= 0.8:
396
+ colors.append("#4CAF50") # Green - high confidence
397
+ elif conf >= 0.5:
398
+ colors.append("#FF9800") # Orange - medium
399
+ else:
400
+ colors.append("#F44336") # Red - low
401
+
402
+ y_pos = np.arange(len(names))
403
+ ax.barh(y_pos, confidences, color=colors, edgecolor="black")
404
+
405
+ # Add evidence indicators
406
+ for i, cand in enumerate(sorted_candidates):
407
+ indicators = []
408
+ if cand.port_hint:
409
+ indicators.append("P") # Port hint
410
+ if cand.header_match:
411
+ indicators.append("H") # Header match
412
+ if cand.matched_patterns:
413
+ indicators.append(f"M{len(cand.matched_patterns)}") # Pattern matches
414
+
415
+ if indicators:
416
+ ax.text(
417
+ confidences[i] + 0.02,
418
+ i,
419
+ " ".join(indicators),
420
+ va="center",
421
+ fontsize=8,
422
+ color="gray",
423
+ )
424
+
425
+ ax.set_yticks(y_pos)
426
+ ax.set_yticklabels(names)
427
+ ax.set_xlim(0, 1.15)
428
+ ax.set_xlabel("Confidence Score")
429
+ ax.axvline(x=0.5, color="gray", linestyle="--", alpha=0.5, label="Threshold")
430
+
431
+ # Add legend for evidence indicators
432
+ ax.text(
433
+ 0.95,
434
+ -0.12,
435
+ "P=Port hint, H=Header match, M#=Pattern matches",
436
+ transform=ax.transAxes,
437
+ fontsize=8,
438
+ ha="right",
439
+ style="italic",
440
+ color="gray",
441
+ )
442
+
443
+ if title:
444
+ ax.set_title(title, fontsize=12, fontweight="bold")
445
+ else:
446
+ ax.set_title("Protocol Detection Candidates", fontsize=12, fontweight="bold")
447
+
448
+ plt.tight_layout()
449
+ return fig
450
+
451
+
452
+ def plot_crc_parameters(
453
+ params: CRCParameters,
454
+ *,
455
+ figsize: tuple[float, float] = (10, 6),
456
+ title: str | None = None,
457
+ ) -> Figure:
458
+ """Create visualization of detected CRC parameters.
459
+
460
+ Shows the recovered CRC parameters in a visually informative format,
461
+ including polynomial representation, configuration flags, and
462
+ confidence metrics.
463
+
464
+ Args:
465
+ params: CRCParameters object with recovered CRC settings.
466
+ figsize: Figure size (width, height) in inches.
467
+ title: Optional custom title.
468
+
469
+ Returns:
470
+ Matplotlib figure object.
471
+
472
+ Example:
473
+ >>> from oscura.inference.crc_reverse import CRCReverser
474
+ >>> reverser = CRCReverser()
475
+ >>> params = reverser.reverse(messages)
476
+ >>> fig = plot_crc_parameters(params)
477
+ """
478
+ fig, (ax1, ax2) = plt.subplots(1, 2, figsize=figsize)
479
+
480
+ # Left panel: CRC configuration
481
+ ax1.axis("off")
482
+
483
+ # Build parameter table
484
+ param_lines = [
485
+ f"Width: {params.width} bits",
486
+ f"Polynomial: 0x{params.polynomial:0{params.width // 4}X}",
487
+ f"Init Value: 0x{params.init:0{params.width // 4}X}",
488
+ f"XOR Out: 0x{params.xor_out:0{params.width // 4}X}",
489
+ f"Reflect In: {'Yes' if params.reflect_in else 'No'}",
490
+ f"Reflect Out: {'Yes' if params.reflect_out else 'No'}",
491
+ ]
492
+
493
+ if params.algorithm_name:
494
+ param_lines.insert(0, f"Algorithm: {params.algorithm_name}")
495
+
496
+ y_start = 0.9
497
+ y_step = 0.12
498
+
499
+ ax1.text(0.5, 0.98, "CRC Parameters", ha="center", fontsize=14, fontweight="bold")
500
+
501
+ for i, line in enumerate(param_lines):
502
+ y = y_start - i * y_step
503
+ ax1.text(0.1, y, line, fontsize=11, fontfamily="monospace", va="top")
504
+
505
+ # Right panel: Confidence gauge
506
+ ax2.set_aspect("equal")
507
+
508
+ # Draw confidence gauge
509
+ theta = np.linspace(0, np.pi, 100)
510
+ r = 0.8
511
+ x = r * np.cos(theta)
512
+ y = r * np.sin(theta)
513
+
514
+ # Background arc (gray)
515
+ ax2.plot(x, y, color="#E0E0E0", linewidth=20, solid_capstyle="round")
516
+
517
+ # Confidence arc (colored)
518
+ conf_angle = np.pi * params.confidence
519
+ theta_conf = np.linspace(0, conf_angle, int(100 * params.confidence) + 1)
520
+ x_conf = r * np.cos(theta_conf)
521
+ y_conf = r * np.sin(theta_conf)
522
+
523
+ if params.confidence >= 0.8:
524
+ color = "#4CAF50" # Green
525
+ elif params.confidence >= 0.5:
526
+ color = "#FF9800" # Orange
527
+ else:
528
+ color = "#F44336" # Red
529
+
530
+ ax2.plot(x_conf, y_conf, color=color, linewidth=20, solid_capstyle="round")
531
+
532
+ # Add confidence text
533
+ ax2.text(
534
+ 0,
535
+ 0.2,
536
+ f"{params.confidence:.0%}",
537
+ ha="center",
538
+ va="center",
539
+ fontsize=24,
540
+ fontweight="bold",
541
+ )
542
+ ax2.text(0, -0.1, "Confidence", ha="center", va="center", fontsize=12)
543
+
544
+ # Add test pass rate
545
+ ax2.text(
546
+ 0,
547
+ -0.4,
548
+ f"Test Pass Rate: {params.test_pass_rate:.0%}",
549
+ ha="center",
550
+ va="center",
551
+ fontsize=10,
552
+ )
553
+
554
+ ax2.set_xlim(-1.2, 1.2)
555
+ ax2.set_ylim(-0.6, 1.2)
556
+ ax2.axis("off")
557
+
558
+ if title:
559
+ fig.suptitle(title, fontsize=14, fontweight="bold")
560
+ else:
561
+ fig.suptitle("CRC Parameter Recovery", fontsize=14, fontweight="bold")
562
+
563
+ plt.tight_layout()
564
+ return fig
565
+
566
+
567
+ def plot_pipeline_timing(
568
+ statistics: dict[str, Any],
569
+ *,
570
+ figsize: tuple[float, float] = (12, 6),
571
+ title: str | None = None,
572
+ ) -> Figure:
573
+ """Create performance metrics visualization for pipeline stages.
574
+
575
+ Shows timing breakdown for each stage of the RE pipeline,
576
+ identifying bottlenecks and processing efficiency.
577
+
578
+ Args:
579
+ statistics: Statistics dictionary from REAnalysisResult.
580
+ figsize: Figure size (width, height) in inches.
581
+ title: Optional custom title.
582
+
583
+ Returns:
584
+ Matplotlib figure object.
585
+
586
+ Example:
587
+ >>> fig = plot_pipeline_timing(result.statistics)
588
+ """
589
+ fig, (ax1, ax2) = plt.subplots(1, 2, figsize=figsize)
590
+
591
+ stage_timing = statistics.get("stage_timing", {})
592
+
593
+ if not stage_timing:
594
+ ax1.text(0.5, 0.5, "No timing data available", ha="center", va="center", fontsize=14)
595
+ ax1.set_axis_off()
596
+ ax2.set_axis_off()
597
+ return fig
598
+
599
+ stages = list(stage_timing.keys())
600
+ times = list(stage_timing.values())
601
+ total_time = sum(times)
602
+
603
+ # Left panel: Bar chart of stage times
604
+ colors = plt.cm.viridis(np.linspace(0.2, 0.8, len(stages)))
605
+ y_pos = np.arange(len(stages))
606
+
607
+ bars = ax1.barh(y_pos, times, color=colors)
608
+ ax1.set_yticks(y_pos)
609
+ ax1.set_yticklabels([s.replace("_", " ").title() for s in stages])
610
+ ax1.set_xlabel("Time (seconds)")
611
+
612
+ # Add time labels on bars
613
+ for i, (_bar, t) in enumerate(zip(bars, times, strict=False)):
614
+ pct = t / total_time * 100 if total_time > 0 else 0
615
+ ax1.text(t + 0.01, i, f"{t:.3f}s ({pct:.1f}%)", va="center", fontsize=9)
616
+
617
+ ax1.set_title("Stage Execution Time", fontsize=11, fontweight="bold")
618
+
619
+ # Right panel: Pie chart of time distribution
620
+ # Filter out very small stages for cleaner pie
621
+ significant = [(s, t) for s, t in zip(stages, times, strict=False) if t / total_time > 0.02]
622
+ if significant:
623
+ pie_stages_tuple, pie_times_tuple = zip(*significant, strict=False)
624
+ other_time = total_time - sum(pie_times_tuple)
625
+ pie_stages: list[str] = list(pie_stages_tuple)
626
+ pie_times: list[float] = list(pie_times_tuple)
627
+ if other_time > 0:
628
+ pie_stages = pie_stages + ["Other"]
629
+ pie_times = pie_times + [other_time]
630
+
631
+ wedges, texts, autotexts = ax2.pie(
632
+ pie_times,
633
+ labels=[s.replace("_", " ").title() for s in pie_stages],
634
+ autopct="%1.1f%%",
635
+ colors=plt.cm.viridis(np.linspace(0.2, 0.8, len(pie_stages))),
636
+ )
637
+ ax2.set_title("Time Distribution", fontsize=11, fontweight="bold")
638
+ else:
639
+ ax2.text(0.5, 0.5, "Insufficient data", ha="center", va="center")
640
+ ax2.set_axis_off()
641
+
642
+ # Add total time annotation
643
+ fig.text(
644
+ 0.5,
645
+ 0.02,
646
+ f"Total Pipeline Time: {total_time:.3f} seconds",
647
+ ha="center",
648
+ fontsize=10,
649
+ style="italic",
650
+ )
651
+
652
+ if title:
653
+ fig.suptitle(title, fontsize=12, fontweight="bold")
654
+ else:
655
+ fig.suptitle("Pipeline Performance Metrics", fontsize=12, fontweight="bold")
656
+
657
+ plt.tight_layout(rect=(0, 0.05, 1, 0.95))
658
+ return fig
659
+
660
+
661
+ # ============================================================================
662
+ # Helper functions for panel plotting
663
+ # ============================================================================
664
+
665
+
666
+ def _plot_summary_stats(ax: Any, result: REAnalysisResult) -> None:
667
+ """Plot summary statistics panel."""
668
+ ax.axis("off")
669
+
670
+ stats = [
671
+ ("Flows Analyzed", str(result.flow_count)),
672
+ ("Messages", str(result.message_count)),
673
+ ("Message Types", str(len(result.message_types))),
674
+ ("Protocol Candidates", str(len(result.protocol_candidates))),
675
+ ("Field Schemas", str(len(result.field_schemas))),
676
+ ("Duration", f"{result.duration_seconds:.2f}s"),
677
+ ("Warnings", str(len(result.warnings))),
678
+ ]
679
+
680
+ ax.text(0.5, 0.95, "Analysis Summary", ha="center", fontsize=11, fontweight="bold")
681
+
682
+ y_pos = 0.8
683
+ for label, value in stats:
684
+ ax.text(0.1, y_pos, f"{label}:", fontsize=9, fontweight="bold")
685
+ ax.text(0.7, y_pos, value, fontsize=9, ha="right")
686
+ y_pos -= 0.11
687
+
688
+
689
+ def _plot_message_types_panel(ax: Any, message_types: list[MessageTypeInfo]) -> None:
690
+ """Plot message types panel."""
691
+ if not message_types:
692
+ ax.text(0.5, 0.5, "No types", ha="center", va="center")
693
+ ax.set_title("Message Types", fontsize=10)
694
+ return
695
+
696
+ names = [mt.name[:15] for mt in message_types[:8]]
697
+ counts = [mt.sample_count for mt in message_types[:8]]
698
+
699
+ colors = plt.cm.Set3(np.linspace(0, 1, len(names)))
700
+ ax.pie(counts, labels=names, colors=colors, autopct="%1.0f%%", textprops={"fontsize": 8})
701
+ ax.set_title("Message Types", fontsize=10)
702
+
703
+
704
+ def _plot_protocol_panel(ax: Any, candidates: list[ProtocolCandidate]) -> None:
705
+ """Plot protocol candidates panel."""
706
+ if not candidates:
707
+ ax.text(0.5, 0.5, "No candidates", ha="center", va="center")
708
+ ax.set_title("Protocol Candidates", fontsize=10)
709
+ return
710
+
711
+ sorted_cand = sorted(candidates, key=lambda c: c.confidence, reverse=True)[:5]
712
+ names = [c.name for c in sorted_cand]
713
+ confs = [c.confidence for c in sorted_cand]
714
+
715
+ y_pos = np.arange(len(names))
716
+ colors = ["#4CAF50" if c >= 0.7 else "#FF9800" if c >= 0.4 else "#F44336" for c in confs]
717
+
718
+ ax.barh(y_pos, confs, color=colors)
719
+ ax.set_yticks(y_pos)
720
+ ax.set_yticklabels(names, fontsize=8)
721
+ ax.set_xlim(0, 1)
722
+ ax.set_title("Protocol Candidates", fontsize=10)
723
+
724
+
725
+ def _plot_timing_panel(ax: Any, statistics: dict[str, Any]) -> None:
726
+ """Plot timing panel."""
727
+ stage_timing = statistics.get("stage_timing", {})
728
+
729
+ if not stage_timing:
730
+ ax.text(0.5, 0.5, "No timing data", ha="center", va="center")
731
+ ax.set_title("Stage Timing", fontsize=10)
732
+ return
733
+
734
+ stages = list(stage_timing.keys())
735
+ times = list(stage_timing.values())
736
+
737
+ colors = plt.cm.viridis(np.linspace(0.2, 0.8, len(stages)))
738
+ bars = ax.bar(range(len(stages)), times, color=colors)
739
+
740
+ ax.set_xticks(range(len(stages)))
741
+ ax.set_xticklabels([s.replace("_", "\n") for s in stages], fontsize=8)
742
+ ax.set_ylabel("Time (s)")
743
+ ax.set_title("Pipeline Stage Timing", fontsize=10)
744
+
745
+ for bar, t in zip(bars, times, strict=False):
746
+ ax.text(
747
+ bar.get_x() + bar.get_width() / 2,
748
+ bar.get_height(),
749
+ f"{t:.2f}s",
750
+ ha="center",
751
+ va="bottom",
752
+ fontsize=7,
753
+ )
754
+
755
+
756
+ def _plot_warnings_panel(ax: Any, result: REAnalysisResult) -> None:
757
+ """Plot warnings/info panel."""
758
+ ax.axis("off")
759
+
760
+ ax.text(0.5, 0.95, "Status & Warnings", ha="center", fontsize=10, fontweight="bold")
761
+
762
+ if result.warnings:
763
+ y_pos = 0.8
764
+ for warning in result.warnings[:5]:
765
+ ax.text(0.05, y_pos, f"! {warning[:40]}...", fontsize=8, color="orange")
766
+ y_pos -= 0.15
767
+ else:
768
+ ax.text(0.5, 0.5, "No warnings", ha="center", va="center", fontsize=10, color="green")
769
+
770
+
771
+ def _plot_type_pie(ax: Any, message_types: list[MessageTypeInfo]) -> None:
772
+ """Plot message type pie chart."""
773
+ names = [mt.name for mt in message_types]
774
+ counts = [mt.sample_count for mt in message_types]
775
+
776
+ colors = plt.cm.Set3(np.linspace(0, 1, len(names)))
777
+ ax.pie(counts, labels=names, colors=colors, autopct="%1.1f%%")
778
+ ax.set_title("Distribution by Sample Count")
779
+
780
+
781
+ def _plot_type_bar(ax: Any, message_types: list[MessageTypeInfo]) -> None:
782
+ """Plot message type bar chart."""
783
+ names = [mt.name for mt in message_types]
784
+ counts = [mt.sample_count for mt in message_types]
785
+
786
+ colors = plt.cm.viridis(np.linspace(0.2, 0.8, len(names)))
787
+ ax.bar(range(len(names)), counts, color=colors)
788
+ ax.set_xticks(range(len(names)))
789
+ ax.set_xticklabels(names, rotation=45, ha="right")
790
+ ax.set_ylabel("Sample Count")
791
+ ax.set_title("Message Count per Type")