oscura 0.5.0__py3-none-any.whl → 0.6.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (513) hide show
  1. oscura/__init__.py +169 -167
  2. oscura/analyzers/__init__.py +3 -0
  3. oscura/analyzers/classification.py +659 -0
  4. oscura/analyzers/digital/__init__.py +0 -48
  5. oscura/analyzers/digital/edges.py +325 -65
  6. oscura/analyzers/digital/extraction.py +0 -195
  7. oscura/analyzers/digital/quality.py +293 -166
  8. oscura/analyzers/digital/timing.py +260 -115
  9. oscura/analyzers/digital/timing_numba.py +334 -0
  10. oscura/analyzers/entropy.py +605 -0
  11. oscura/analyzers/eye/diagram.py +176 -109
  12. oscura/analyzers/eye/metrics.py +5 -5
  13. oscura/analyzers/jitter/__init__.py +6 -4
  14. oscura/analyzers/jitter/ber.py +52 -52
  15. oscura/analyzers/jitter/classification.py +156 -0
  16. oscura/analyzers/jitter/decomposition.py +163 -113
  17. oscura/analyzers/jitter/spectrum.py +80 -64
  18. oscura/analyzers/ml/__init__.py +39 -0
  19. oscura/analyzers/ml/features.py +600 -0
  20. oscura/analyzers/ml/signal_classifier.py +604 -0
  21. oscura/analyzers/packet/daq.py +246 -158
  22. oscura/analyzers/packet/parser.py +12 -1
  23. oscura/analyzers/packet/payload.py +50 -2110
  24. oscura/analyzers/packet/payload_analysis.py +361 -181
  25. oscura/analyzers/packet/payload_patterns.py +133 -70
  26. oscura/analyzers/packet/stream.py +84 -23
  27. oscura/analyzers/patterns/__init__.py +26 -5
  28. oscura/analyzers/patterns/anomaly_detection.py +908 -0
  29. oscura/analyzers/patterns/clustering.py +169 -108
  30. oscura/analyzers/patterns/clustering_optimized.py +227 -0
  31. oscura/analyzers/patterns/discovery.py +1 -1
  32. oscura/analyzers/patterns/matching.py +581 -197
  33. oscura/analyzers/patterns/pattern_mining.py +778 -0
  34. oscura/analyzers/patterns/periodic.py +121 -38
  35. oscura/analyzers/patterns/sequences.py +175 -78
  36. oscura/analyzers/power/conduction.py +1 -1
  37. oscura/analyzers/power/soa.py +6 -6
  38. oscura/analyzers/power/switching.py +250 -110
  39. oscura/analyzers/protocol/__init__.py +17 -1
  40. oscura/analyzers/protocols/__init__.py +1 -22
  41. oscura/analyzers/protocols/base.py +6 -6
  42. oscura/analyzers/protocols/ble/__init__.py +38 -0
  43. oscura/analyzers/protocols/ble/analyzer.py +809 -0
  44. oscura/analyzers/protocols/ble/uuids.py +288 -0
  45. oscura/analyzers/protocols/can.py +257 -127
  46. oscura/analyzers/protocols/can_fd.py +107 -80
  47. oscura/analyzers/protocols/flexray.py +139 -80
  48. oscura/analyzers/protocols/hdlc.py +93 -58
  49. oscura/analyzers/protocols/i2c.py +247 -106
  50. oscura/analyzers/protocols/i2s.py +138 -86
  51. oscura/analyzers/protocols/industrial/__init__.py +40 -0
  52. oscura/analyzers/protocols/industrial/bacnet/__init__.py +33 -0
  53. oscura/analyzers/protocols/industrial/bacnet/analyzer.py +708 -0
  54. oscura/analyzers/protocols/industrial/bacnet/encoding.py +412 -0
  55. oscura/analyzers/protocols/industrial/bacnet/services.py +622 -0
  56. oscura/analyzers/protocols/industrial/ethercat/__init__.py +30 -0
  57. oscura/analyzers/protocols/industrial/ethercat/analyzer.py +474 -0
  58. oscura/analyzers/protocols/industrial/ethercat/mailbox.py +339 -0
  59. oscura/analyzers/protocols/industrial/ethercat/topology.py +166 -0
  60. oscura/analyzers/protocols/industrial/modbus/__init__.py +31 -0
  61. oscura/analyzers/protocols/industrial/modbus/analyzer.py +525 -0
  62. oscura/analyzers/protocols/industrial/modbus/crc.py +79 -0
  63. oscura/analyzers/protocols/industrial/modbus/functions.py +436 -0
  64. oscura/analyzers/protocols/industrial/opcua/__init__.py +21 -0
  65. oscura/analyzers/protocols/industrial/opcua/analyzer.py +552 -0
  66. oscura/analyzers/protocols/industrial/opcua/datatypes.py +446 -0
  67. oscura/analyzers/protocols/industrial/opcua/services.py +264 -0
  68. oscura/analyzers/protocols/industrial/profinet/__init__.py +23 -0
  69. oscura/analyzers/protocols/industrial/profinet/analyzer.py +441 -0
  70. oscura/analyzers/protocols/industrial/profinet/dcp.py +263 -0
  71. oscura/analyzers/protocols/industrial/profinet/ptcp.py +200 -0
  72. oscura/analyzers/protocols/jtag.py +180 -98
  73. oscura/analyzers/protocols/lin.py +219 -114
  74. oscura/analyzers/protocols/manchester.py +4 -4
  75. oscura/analyzers/protocols/onewire.py +253 -149
  76. oscura/analyzers/protocols/parallel_bus/__init__.py +20 -0
  77. oscura/analyzers/protocols/parallel_bus/centronics.py +92 -0
  78. oscura/analyzers/protocols/parallel_bus/gpib.py +137 -0
  79. oscura/analyzers/protocols/spi.py +192 -95
  80. oscura/analyzers/protocols/swd.py +321 -167
  81. oscura/analyzers/protocols/uart.py +267 -125
  82. oscura/analyzers/protocols/usb.py +235 -131
  83. oscura/analyzers/side_channel/power.py +17 -12
  84. oscura/analyzers/signal/__init__.py +15 -0
  85. oscura/analyzers/signal/timing_analysis.py +1086 -0
  86. oscura/analyzers/signal_integrity/__init__.py +4 -1
  87. oscura/analyzers/signal_integrity/sparams.py +2 -19
  88. oscura/analyzers/spectral/chunked.py +129 -60
  89. oscura/analyzers/spectral/chunked_fft.py +300 -94
  90. oscura/analyzers/spectral/chunked_wavelet.py +100 -80
  91. oscura/analyzers/statistical/checksum.py +376 -217
  92. oscura/analyzers/statistical/classification.py +229 -107
  93. oscura/analyzers/statistical/entropy.py +78 -53
  94. oscura/analyzers/statistics/correlation.py +407 -211
  95. oscura/analyzers/statistics/outliers.py +2 -2
  96. oscura/analyzers/statistics/streaming.py +30 -5
  97. oscura/analyzers/validation.py +216 -101
  98. oscura/analyzers/waveform/measurements.py +9 -0
  99. oscura/analyzers/waveform/measurements_with_uncertainty.py +31 -15
  100. oscura/analyzers/waveform/spectral.py +500 -228
  101. oscura/api/__init__.py +31 -5
  102. oscura/api/dsl/__init__.py +582 -0
  103. oscura/{dsl → api/dsl}/commands.py +43 -76
  104. oscura/{dsl → api/dsl}/interpreter.py +26 -51
  105. oscura/{dsl → api/dsl}/parser.py +107 -77
  106. oscura/{dsl → api/dsl}/repl.py +2 -2
  107. oscura/api/dsl.py +1 -1
  108. oscura/{integrations → api/integrations}/__init__.py +1 -1
  109. oscura/{integrations → api/integrations}/llm.py +201 -102
  110. oscura/api/operators.py +3 -3
  111. oscura/api/optimization.py +144 -30
  112. oscura/api/rest_server.py +921 -0
  113. oscura/api/server/__init__.py +17 -0
  114. oscura/api/server/dashboard.py +850 -0
  115. oscura/api/server/static/README.md +34 -0
  116. oscura/api/server/templates/base.html +181 -0
  117. oscura/api/server/templates/export.html +120 -0
  118. oscura/api/server/templates/home.html +284 -0
  119. oscura/api/server/templates/protocols.html +58 -0
  120. oscura/api/server/templates/reports.html +43 -0
  121. oscura/api/server/templates/session_detail.html +89 -0
  122. oscura/api/server/templates/sessions.html +83 -0
  123. oscura/api/server/templates/waveforms.html +73 -0
  124. oscura/automotive/__init__.py +8 -1
  125. oscura/automotive/can/__init__.py +10 -0
  126. oscura/automotive/can/checksum.py +3 -1
  127. oscura/automotive/can/dbc_generator.py +590 -0
  128. oscura/automotive/can/message_wrapper.py +121 -74
  129. oscura/automotive/can/patterns.py +98 -21
  130. oscura/automotive/can/session.py +292 -56
  131. oscura/automotive/can/state_machine.py +6 -3
  132. oscura/automotive/can/stimulus_response.py +97 -75
  133. oscura/automotive/dbc/__init__.py +10 -2
  134. oscura/automotive/dbc/generator.py +84 -56
  135. oscura/automotive/dbc/parser.py +6 -6
  136. oscura/automotive/dtc/data.json +2763 -0
  137. oscura/automotive/dtc/database.py +2 -2
  138. oscura/automotive/flexray/__init__.py +31 -0
  139. oscura/automotive/flexray/analyzer.py +504 -0
  140. oscura/automotive/flexray/crc.py +185 -0
  141. oscura/automotive/flexray/fibex.py +449 -0
  142. oscura/automotive/j1939/__init__.py +45 -8
  143. oscura/automotive/j1939/analyzer.py +605 -0
  144. oscura/automotive/j1939/spns.py +326 -0
  145. oscura/automotive/j1939/transport.py +306 -0
  146. oscura/automotive/lin/__init__.py +47 -0
  147. oscura/automotive/lin/analyzer.py +612 -0
  148. oscura/automotive/loaders/blf.py +13 -2
  149. oscura/automotive/loaders/csv_can.py +143 -72
  150. oscura/automotive/loaders/dispatcher.py +50 -2
  151. oscura/automotive/loaders/mdf.py +86 -45
  152. oscura/automotive/loaders/pcap.py +111 -61
  153. oscura/automotive/uds/__init__.py +4 -0
  154. oscura/automotive/uds/analyzer.py +725 -0
  155. oscura/automotive/uds/decoder.py +140 -58
  156. oscura/automotive/uds/models.py +7 -1
  157. oscura/automotive/visualization.py +1 -1
  158. oscura/cli/analyze.py +348 -0
  159. oscura/cli/batch.py +142 -122
  160. oscura/cli/benchmark.py +275 -0
  161. oscura/cli/characterize.py +137 -82
  162. oscura/cli/compare.py +224 -131
  163. oscura/cli/completion.py +250 -0
  164. oscura/cli/config_cmd.py +361 -0
  165. oscura/cli/decode.py +164 -87
  166. oscura/cli/export.py +286 -0
  167. oscura/cli/main.py +115 -31
  168. oscura/{onboarding → cli/onboarding}/__init__.py +3 -3
  169. oscura/{onboarding → cli/onboarding}/help.py +80 -58
  170. oscura/{onboarding → cli/onboarding}/tutorials.py +97 -72
  171. oscura/{onboarding → cli/onboarding}/wizard.py +55 -36
  172. oscura/cli/progress.py +147 -0
  173. oscura/cli/shell.py +157 -135
  174. oscura/cli/validate_cmd.py +204 -0
  175. oscura/cli/visualize.py +158 -0
  176. oscura/convenience.py +125 -79
  177. oscura/core/__init__.py +4 -2
  178. oscura/core/backend_selector.py +3 -3
  179. oscura/core/cache.py +126 -15
  180. oscura/core/cancellation.py +1 -1
  181. oscura/{config → core/config}/__init__.py +20 -11
  182. oscura/{config → core/config}/defaults.py +1 -1
  183. oscura/{config → core/config}/loader.py +7 -5
  184. oscura/{config → core/config}/memory.py +5 -5
  185. oscura/{config → core/config}/migration.py +1 -1
  186. oscura/{config → core/config}/pipeline.py +99 -23
  187. oscura/{config → core/config}/preferences.py +1 -1
  188. oscura/{config → core/config}/protocol.py +3 -3
  189. oscura/{config → core/config}/schema.py +426 -272
  190. oscura/{config → core/config}/settings.py +1 -1
  191. oscura/{config → core/config}/thresholds.py +195 -153
  192. oscura/core/correlation.py +5 -6
  193. oscura/core/cross_domain.py +0 -2
  194. oscura/core/debug.py +9 -5
  195. oscura/{extensibility → core/extensibility}/docs.py +158 -70
  196. oscura/{extensibility → core/extensibility}/extensions.py +160 -76
  197. oscura/{extensibility → core/extensibility}/logging.py +1 -1
  198. oscura/{extensibility → core/extensibility}/measurements.py +1 -1
  199. oscura/{extensibility → core/extensibility}/plugins.py +1 -1
  200. oscura/{extensibility → core/extensibility}/templates.py +73 -3
  201. oscura/{extensibility → core/extensibility}/validation.py +1 -1
  202. oscura/core/gpu_backend.py +11 -7
  203. oscura/core/log_query.py +101 -11
  204. oscura/core/logging.py +126 -54
  205. oscura/core/logging_advanced.py +5 -5
  206. oscura/core/memory_limits.py +108 -70
  207. oscura/core/memory_monitor.py +2 -2
  208. oscura/core/memory_progress.py +7 -7
  209. oscura/core/memory_warnings.py +1 -1
  210. oscura/core/numba_backend.py +13 -13
  211. oscura/{plugins → core/plugins}/__init__.py +9 -9
  212. oscura/{plugins → core/plugins}/base.py +7 -7
  213. oscura/{plugins → core/plugins}/cli.py +3 -3
  214. oscura/{plugins → core/plugins}/discovery.py +186 -106
  215. oscura/{plugins → core/plugins}/lifecycle.py +1 -1
  216. oscura/{plugins → core/plugins}/manager.py +7 -7
  217. oscura/{plugins → core/plugins}/registry.py +3 -3
  218. oscura/{plugins → core/plugins}/versioning.py +1 -1
  219. oscura/core/progress.py +16 -1
  220. oscura/core/provenance.py +8 -2
  221. oscura/{schemas → core/schemas}/__init__.py +2 -2
  222. oscura/core/schemas/bus_configuration.json +322 -0
  223. oscura/core/schemas/device_mapping.json +182 -0
  224. oscura/core/schemas/packet_format.json +418 -0
  225. oscura/core/schemas/protocol_definition.json +363 -0
  226. oscura/core/types.py +4 -0
  227. oscura/core/uncertainty.py +3 -3
  228. oscura/correlation/__init__.py +52 -0
  229. oscura/correlation/multi_protocol.py +811 -0
  230. oscura/discovery/auto_decoder.py +117 -35
  231. oscura/discovery/comparison.py +191 -86
  232. oscura/discovery/quality_validator.py +155 -68
  233. oscura/discovery/signal_detector.py +196 -79
  234. oscura/export/__init__.py +18 -20
  235. oscura/export/kaitai_struct.py +513 -0
  236. oscura/export/scapy_layer.py +801 -0
  237. oscura/export/wireshark/README.md +15 -15
  238. oscura/export/wireshark/generator.py +1 -1
  239. oscura/export/wireshark/templates/dissector.lua.j2 +2 -2
  240. oscura/export/wireshark_dissector.py +746 -0
  241. oscura/guidance/wizard.py +207 -111
  242. oscura/hardware/__init__.py +19 -0
  243. oscura/{acquisition → hardware/acquisition}/__init__.py +4 -4
  244. oscura/{acquisition → hardware/acquisition}/file.py +2 -2
  245. oscura/{acquisition → hardware/acquisition}/hardware.py +7 -7
  246. oscura/{acquisition → hardware/acquisition}/saleae.py +15 -12
  247. oscura/{acquisition → hardware/acquisition}/socketcan.py +1 -1
  248. oscura/{acquisition → hardware/acquisition}/streaming.py +2 -2
  249. oscura/{acquisition → hardware/acquisition}/synthetic.py +3 -3
  250. oscura/{acquisition → hardware/acquisition}/visa.py +33 -11
  251. oscura/hardware/firmware/__init__.py +29 -0
  252. oscura/hardware/firmware/pattern_recognition.py +874 -0
  253. oscura/hardware/hal_detector.py +736 -0
  254. oscura/hardware/security/__init__.py +37 -0
  255. oscura/hardware/security/side_channel_detector.py +1126 -0
  256. oscura/inference/__init__.py +4 -0
  257. oscura/inference/active_learning/README.md +7 -7
  258. oscura/inference/active_learning/observation_table.py +4 -1
  259. oscura/inference/alignment.py +216 -123
  260. oscura/inference/bayesian.py +113 -33
  261. oscura/inference/crc_reverse.py +101 -55
  262. oscura/inference/logic.py +6 -2
  263. oscura/inference/message_format.py +342 -183
  264. oscura/inference/protocol.py +95 -44
  265. oscura/inference/protocol_dsl.py +180 -82
  266. oscura/inference/signal_intelligence.py +1439 -706
  267. oscura/inference/spectral.py +99 -57
  268. oscura/inference/state_machine.py +810 -158
  269. oscura/inference/stream.py +270 -110
  270. oscura/iot/__init__.py +34 -0
  271. oscura/iot/coap/__init__.py +32 -0
  272. oscura/iot/coap/analyzer.py +668 -0
  273. oscura/iot/coap/options.py +212 -0
  274. oscura/iot/lorawan/__init__.py +21 -0
  275. oscura/iot/lorawan/crypto.py +206 -0
  276. oscura/iot/lorawan/decoder.py +801 -0
  277. oscura/iot/lorawan/mac_commands.py +341 -0
  278. oscura/iot/mqtt/__init__.py +27 -0
  279. oscura/iot/mqtt/analyzer.py +999 -0
  280. oscura/iot/mqtt/properties.py +315 -0
  281. oscura/iot/zigbee/__init__.py +31 -0
  282. oscura/iot/zigbee/analyzer.py +615 -0
  283. oscura/iot/zigbee/security.py +153 -0
  284. oscura/iot/zigbee/zcl.py +349 -0
  285. oscura/jupyter/display.py +125 -45
  286. oscura/{exploratory → jupyter/exploratory}/__init__.py +8 -8
  287. oscura/{exploratory → jupyter/exploratory}/error_recovery.py +298 -141
  288. oscura/jupyter/exploratory/fuzzy.py +746 -0
  289. oscura/{exploratory → jupyter/exploratory}/fuzzy_advanced.py +258 -100
  290. oscura/{exploratory → jupyter/exploratory}/legacy.py +464 -242
  291. oscura/{exploratory → jupyter/exploratory}/parse.py +167 -145
  292. oscura/{exploratory → jupyter/exploratory}/recovery.py +119 -87
  293. oscura/jupyter/exploratory/sync.py +612 -0
  294. oscura/{exploratory → jupyter/exploratory}/unknown.py +299 -176
  295. oscura/jupyter/magic.py +4 -4
  296. oscura/{ui → jupyter/ui}/__init__.py +2 -2
  297. oscura/{ui → jupyter/ui}/formatters.py +3 -3
  298. oscura/{ui → jupyter/ui}/progressive_display.py +153 -82
  299. oscura/loaders/__init__.py +171 -63
  300. oscura/loaders/binary.py +88 -1
  301. oscura/loaders/chipwhisperer.py +153 -137
  302. oscura/loaders/configurable.py +208 -86
  303. oscura/loaders/csv_loader.py +458 -215
  304. oscura/loaders/hdf5_loader.py +278 -119
  305. oscura/loaders/lazy.py +87 -54
  306. oscura/loaders/mmap_loader.py +1 -1
  307. oscura/loaders/numpy_loader.py +253 -116
  308. oscura/loaders/pcap.py +226 -151
  309. oscura/loaders/rigol.py +110 -49
  310. oscura/loaders/sigrok.py +201 -78
  311. oscura/loaders/tdms.py +81 -58
  312. oscura/loaders/tektronix.py +291 -174
  313. oscura/loaders/touchstone.py +182 -87
  314. oscura/loaders/vcd.py +215 -117
  315. oscura/loaders/wav.py +155 -68
  316. oscura/reporting/__init__.py +9 -7
  317. oscura/reporting/analyze.py +352 -146
  318. oscura/reporting/argument_preparer.py +69 -14
  319. oscura/reporting/auto_report.py +97 -61
  320. oscura/reporting/batch.py +131 -58
  321. oscura/reporting/chart_selection.py +57 -45
  322. oscura/reporting/comparison.py +63 -17
  323. oscura/reporting/content/executive.py +76 -24
  324. oscura/reporting/core_formats/multi_format.py +11 -8
  325. oscura/reporting/engine.py +312 -158
  326. oscura/reporting/enhanced_reports.py +949 -0
  327. oscura/reporting/export.py +86 -43
  328. oscura/reporting/formatting/numbers.py +69 -42
  329. oscura/reporting/html.py +139 -58
  330. oscura/reporting/index.py +137 -65
  331. oscura/reporting/output.py +158 -67
  332. oscura/reporting/pdf.py +67 -102
  333. oscura/reporting/plots.py +191 -112
  334. oscura/reporting/sections.py +88 -47
  335. oscura/reporting/standards.py +104 -61
  336. oscura/reporting/summary_generator.py +75 -55
  337. oscura/reporting/tables.py +138 -54
  338. oscura/reporting/templates/enhanced/protocol_re.html +525 -0
  339. oscura/reporting/templates/index.md +13 -13
  340. oscura/sessions/__init__.py +14 -23
  341. oscura/sessions/base.py +3 -3
  342. oscura/sessions/blackbox.py +106 -10
  343. oscura/sessions/generic.py +2 -2
  344. oscura/sessions/legacy.py +783 -0
  345. oscura/side_channel/__init__.py +63 -0
  346. oscura/side_channel/dpa.py +1025 -0
  347. oscura/utils/__init__.py +15 -1
  348. oscura/utils/autodetect.py +1 -5
  349. oscura/utils/bitwise.py +118 -0
  350. oscura/{builders → utils/builders}/__init__.py +1 -1
  351. oscura/{comparison → utils/comparison}/__init__.py +6 -6
  352. oscura/{comparison → utils/comparison}/compare.py +202 -101
  353. oscura/{comparison → utils/comparison}/golden.py +83 -63
  354. oscura/{comparison → utils/comparison}/limits.py +313 -89
  355. oscura/{comparison → utils/comparison}/mask.py +151 -45
  356. oscura/{comparison → utils/comparison}/trace_diff.py +1 -1
  357. oscura/{comparison → utils/comparison}/visualization.py +147 -89
  358. oscura/{component → utils/component}/__init__.py +3 -3
  359. oscura/{component → utils/component}/impedance.py +122 -58
  360. oscura/{component → utils/component}/reactive.py +165 -168
  361. oscura/{component → utils/component}/transmission_line.py +3 -3
  362. oscura/{filtering → utils/filtering}/__init__.py +6 -6
  363. oscura/{filtering → utils/filtering}/base.py +1 -1
  364. oscura/{filtering → utils/filtering}/convenience.py +2 -2
  365. oscura/{filtering → utils/filtering}/design.py +169 -93
  366. oscura/{filtering → utils/filtering}/filters.py +2 -2
  367. oscura/{filtering → utils/filtering}/introspection.py +2 -2
  368. oscura/utils/geometry.py +31 -0
  369. oscura/utils/imports.py +184 -0
  370. oscura/utils/lazy.py +1 -1
  371. oscura/{math → utils/math}/__init__.py +2 -2
  372. oscura/{math → utils/math}/arithmetic.py +114 -48
  373. oscura/{math → utils/math}/interpolation.py +139 -106
  374. oscura/utils/memory.py +129 -66
  375. oscura/utils/memory_advanced.py +92 -9
  376. oscura/utils/memory_extensions.py +10 -8
  377. oscura/{optimization → utils/optimization}/__init__.py +1 -1
  378. oscura/{optimization → utils/optimization}/search.py +2 -2
  379. oscura/utils/performance/__init__.py +58 -0
  380. oscura/utils/performance/caching.py +889 -0
  381. oscura/utils/performance/lsh_clustering.py +333 -0
  382. oscura/utils/performance/memory_optimizer.py +699 -0
  383. oscura/utils/performance/optimizations.py +675 -0
  384. oscura/utils/performance/parallel.py +654 -0
  385. oscura/utils/performance/profiling.py +661 -0
  386. oscura/{pipeline → utils/pipeline}/base.py +1 -1
  387. oscura/{pipeline → utils/pipeline}/composition.py +11 -3
  388. oscura/{pipeline → utils/pipeline}/parallel.py +3 -2
  389. oscura/{pipeline → utils/pipeline}/pipeline.py +1 -1
  390. oscura/{pipeline → utils/pipeline}/reverse_engineering.py +412 -221
  391. oscura/{search → utils/search}/__init__.py +3 -3
  392. oscura/{search → utils/search}/anomaly.py +188 -58
  393. oscura/utils/search/context.py +294 -0
  394. oscura/{search → utils/search}/pattern.py +138 -10
  395. oscura/utils/serial.py +51 -0
  396. oscura/utils/storage/__init__.py +61 -0
  397. oscura/utils/storage/database.py +1166 -0
  398. oscura/{streaming → utils/streaming}/chunked.py +302 -143
  399. oscura/{streaming → utils/streaming}/progressive.py +1 -1
  400. oscura/{streaming → utils/streaming}/realtime.py +3 -2
  401. oscura/{triggering → utils/triggering}/__init__.py +6 -6
  402. oscura/{triggering → utils/triggering}/base.py +6 -6
  403. oscura/{triggering → utils/triggering}/edge.py +2 -2
  404. oscura/{triggering → utils/triggering}/pattern.py +2 -2
  405. oscura/{triggering → utils/triggering}/pulse.py +115 -74
  406. oscura/{triggering → utils/triggering}/window.py +2 -2
  407. oscura/utils/validation.py +32 -0
  408. oscura/validation/__init__.py +121 -0
  409. oscura/{compliance → validation/compliance}/__init__.py +5 -5
  410. oscura/{compliance → validation/compliance}/advanced.py +5 -5
  411. oscura/{compliance → validation/compliance}/masks.py +1 -1
  412. oscura/{compliance → validation/compliance}/reporting.py +127 -53
  413. oscura/{compliance → validation/compliance}/testing.py +114 -52
  414. oscura/validation/compliance_tests.py +915 -0
  415. oscura/validation/fuzzer.py +990 -0
  416. oscura/validation/grammar_tests.py +596 -0
  417. oscura/validation/grammar_validator.py +904 -0
  418. oscura/validation/hil_testing.py +977 -0
  419. oscura/{quality → validation/quality}/__init__.py +4 -4
  420. oscura/{quality → validation/quality}/ensemble.py +251 -171
  421. oscura/{quality → validation/quality}/explainer.py +3 -3
  422. oscura/{quality → validation/quality}/scoring.py +1 -1
  423. oscura/{quality → validation/quality}/warnings.py +4 -4
  424. oscura/validation/regression_suite.py +808 -0
  425. oscura/validation/replay.py +788 -0
  426. oscura/{testing → validation/testing}/__init__.py +2 -2
  427. oscura/{testing → validation/testing}/synthetic.py +5 -5
  428. oscura/visualization/__init__.py +9 -0
  429. oscura/visualization/accessibility.py +1 -1
  430. oscura/visualization/annotations.py +64 -67
  431. oscura/visualization/colors.py +7 -7
  432. oscura/visualization/digital.py +180 -81
  433. oscura/visualization/eye.py +236 -85
  434. oscura/visualization/interactive.py +320 -143
  435. oscura/visualization/jitter.py +587 -247
  436. oscura/visualization/layout.py +169 -134
  437. oscura/visualization/optimization.py +103 -52
  438. oscura/visualization/palettes.py +1 -1
  439. oscura/visualization/power.py +427 -211
  440. oscura/visualization/power_extended.py +626 -297
  441. oscura/visualization/presets.py +2 -0
  442. oscura/visualization/protocols.py +495 -181
  443. oscura/visualization/render.py +79 -63
  444. oscura/visualization/reverse_engineering.py +171 -124
  445. oscura/visualization/signal_integrity.py +460 -279
  446. oscura/visualization/specialized.py +190 -100
  447. oscura/visualization/spectral.py +670 -255
  448. oscura/visualization/thumbnails.py +166 -137
  449. oscura/visualization/waveform.py +150 -63
  450. oscura/workflows/__init__.py +3 -0
  451. oscura/{batch → workflows/batch}/__init__.py +5 -5
  452. oscura/{batch → workflows/batch}/advanced.py +150 -75
  453. oscura/workflows/batch/aggregate.py +531 -0
  454. oscura/workflows/batch/analyze.py +236 -0
  455. oscura/{batch → workflows/batch}/logging.py +2 -2
  456. oscura/{batch → workflows/batch}/metrics.py +1 -1
  457. oscura/workflows/complete_re.py +1144 -0
  458. oscura/workflows/compliance.py +44 -54
  459. oscura/workflows/digital.py +197 -51
  460. oscura/workflows/legacy/__init__.py +12 -0
  461. oscura/{workflow → workflows/legacy}/dag.py +4 -1
  462. oscura/workflows/multi_trace.py +9 -9
  463. oscura/workflows/power.py +42 -62
  464. oscura/workflows/protocol.py +82 -49
  465. oscura/workflows/reverse_engineering.py +351 -150
  466. oscura/workflows/signal_integrity.py +157 -82
  467. oscura-0.6.0.dist-info/METADATA +643 -0
  468. oscura-0.6.0.dist-info/RECORD +590 -0
  469. oscura/analyzers/digital/ic_database.py +0 -498
  470. oscura/analyzers/digital/timing_paths.py +0 -339
  471. oscura/analyzers/digital/vintage.py +0 -377
  472. oscura/analyzers/digital/vintage_result.py +0 -148
  473. oscura/analyzers/protocols/parallel_bus.py +0 -449
  474. oscura/batch/aggregate.py +0 -300
  475. oscura/batch/analyze.py +0 -139
  476. oscura/dsl/__init__.py +0 -73
  477. oscura/exceptions.py +0 -59
  478. oscura/exploratory/fuzzy.py +0 -513
  479. oscura/exploratory/sync.py +0 -384
  480. oscura/export/wavedrom.py +0 -430
  481. oscura/exporters/__init__.py +0 -94
  482. oscura/exporters/csv.py +0 -303
  483. oscura/exporters/exporters.py +0 -44
  484. oscura/exporters/hdf5.py +0 -217
  485. oscura/exporters/html_export.py +0 -701
  486. oscura/exporters/json_export.py +0 -338
  487. oscura/exporters/markdown_export.py +0 -367
  488. oscura/exporters/matlab_export.py +0 -354
  489. oscura/exporters/npz_export.py +0 -219
  490. oscura/exporters/spice_export.py +0 -210
  491. oscura/exporters/vintage_logic_csv.py +0 -247
  492. oscura/reporting/vintage_logic_report.py +0 -523
  493. oscura/search/context.py +0 -149
  494. oscura/session/__init__.py +0 -34
  495. oscura/session/annotations.py +0 -289
  496. oscura/session/history.py +0 -313
  497. oscura/session/session.py +0 -520
  498. oscura/visualization/digital_advanced.py +0 -718
  499. oscura/visualization/figure_manager.py +0 -156
  500. oscura/workflow/__init__.py +0 -13
  501. oscura-0.5.0.dist-info/METADATA +0 -407
  502. oscura-0.5.0.dist-info/RECORD +0 -486
  503. /oscura/core/{config.py → config/legacy.py} +0 -0
  504. /oscura/{extensibility → core/extensibility}/__init__.py +0 -0
  505. /oscura/{extensibility → core/extensibility}/registry.py +0 -0
  506. /oscura/{plugins → core/plugins}/isolation.py +0 -0
  507. /oscura/{builders → utils/builders}/signal_builder.py +0 -0
  508. /oscura/{optimization → utils/optimization}/parallel.py +0 -0
  509. /oscura/{pipeline → utils/pipeline}/__init__.py +0 -0
  510. /oscura/{streaming → utils/streaming}/__init__.py +0 -0
  511. {oscura-0.5.0.dist-info → oscura-0.6.0.dist-info}/WHEEL +0 -0
  512. {oscura-0.5.0.dist-info → oscura-0.6.0.dist-info}/entry_points.txt +0 -0
  513. {oscura-0.5.0.dist-info → oscura-0.6.0.dist-info}/licenses/LICENSE +0 -0
@@ -63,8 +63,51 @@ def configure_dpi_rendering(
63
63
  References:
64
64
  VIS-017: DPI-Aware Rendering
65
65
  """
66
- # Define preset configurations
67
- presets = {
66
+ presets = _get_dpi_presets()
67
+
68
+ # Handle dpi alias and resolve preset
69
+ if dpi is not None and custom_dpi is None:
70
+ custom_dpi = dpi
71
+
72
+ if preset not in presets and custom_dpi is None:
73
+ raise ValueError(f"Invalid preset: {preset}. Must be one of {list(presets.keys())}")
74
+
75
+ # Get configuration and DPI
76
+ target_dpi, preset_config = _resolve_dpi_config(preset, custom_dpi, presets)
77
+
78
+ # Calculate scale factors
79
+ scale = target_dpi / baseline_dpi
80
+ font_scale = scale
81
+ line_scale = scale
82
+ marker_scale = scale
83
+
84
+ # Build style parameters
85
+ style_params = _build_style_params(
86
+ target_dpi, font_scale, line_scale, marker_scale, preset_config
87
+ )
88
+
89
+ _apply_antialias_settings(style_params, preset_config)
90
+
91
+ if preset == "publication":
92
+ _apply_publication_settings(style_params)
93
+
94
+ return {
95
+ "dpi": target_dpi,
96
+ "figsize": figsize,
97
+ "font_scale": font_scale,
98
+ "line_scale": line_scale,
99
+ "marker_scale": marker_scale,
100
+ "antialias": preset_config["antialias"],
101
+ "format": preset_config["format"],
102
+ "style_params": style_params,
103
+ "description": preset_config["description"],
104
+ "preset": preset if custom_dpi is None else "custom",
105
+ }
106
+
107
+
108
+ def _get_dpi_presets() -> dict[str, dict[str, Any]]:
109
+ """Get DPI preset configurations."""
110
+ return {
68
111
  "screen": {
69
112
  "dpi": 96,
70
113
  "font_family": "sans-serif",
@@ -88,44 +131,32 @@ def configure_dpi_rendering(
88
131
  },
89
132
  }
90
133
 
91
- # Handle dpi alias
92
- if dpi is not None and custom_dpi is None:
93
- custom_dpi = dpi
94
-
95
- if preset not in presets and custom_dpi is None:
96
- raise ValueError(f"Invalid preset: {preset}. Must be one of {list(presets.keys())}")
97
134
 
98
- # Get preset configuration
135
+ def _resolve_dpi_config(
136
+ preset: str, custom_dpi: int | None, presets: dict[str, dict[str, Any]]
137
+ ) -> tuple[int, dict[str, Any]]:
138
+ """Resolve target DPI and configuration from preset or custom value."""
99
139
  if custom_dpi is not None:
100
- target_dpi = custom_dpi
101
- preset_config = {
140
+ return custom_dpi, {
102
141
  "font_family": "sans-serif",
103
142
  "antialias": True,
104
- "format": "png" if target_dpi <= 150 else "pdf",
105
- "description": f"Custom ({target_dpi} DPI)",
143
+ "format": "png" if custom_dpi <= 150 else "pdf",
144
+ "description": f"Custom ({custom_dpi} DPI)",
106
145
  }
107
- else:
108
- preset_config = presets[preset]
109
- target_dpi = preset_config["dpi"] # type: ignore[assignment]
110
146
 
111
- # Calculate scale factors based on DPI
112
- # Scale factor = target_dpi / baseline_dpi
113
- scale = target_dpi / baseline_dpi
147
+ preset_config = presets[preset]
148
+ return preset_config["dpi"], preset_config
114
149
 
115
- # Font size scaling: proportional to DPI
116
- # Baseline font sizes at 96 DPI: default 10pt
117
- font_scale = scale
118
-
119
- # Line width scaling: proportional to DPI
120
- # Baseline line width at 96 DPI: 1.0 pt
121
- line_scale = scale
122
150
 
123
- # Marker size scaling: proportional to DPI
124
- # Baseline marker size at 96 DPI: 6.0 pt
125
- marker_scale = scale
126
-
127
- # Build matplotlib rcParams for this preset
128
- style_params = {
151
+ def _build_style_params(
152
+ target_dpi: int,
153
+ font_scale: float,
154
+ line_scale: float,
155
+ marker_scale: float,
156
+ preset_config: dict[str, Any],
157
+ ) -> dict[str, Any]:
158
+ """Build matplotlib rcParams dictionary."""
159
+ return {
129
160
  "figure.dpi": target_dpi,
130
161
  "savefig.dpi": target_dpi,
131
162
  "font.family": preset_config["font_family"],
@@ -146,37 +177,22 @@ def configure_dpi_rendering(
146
177
  "ytick.minor.width": 0.6 * line_scale,
147
178
  }
148
179
 
149
- # Anti-aliasing settings
150
- if preset_config["antialias"]:
151
- style_params["lines.antialiased"] = True
152
- style_params["patch.antialiased"] = True
153
- style_params["text.antialiased"] = True
154
- else:
155
- # Disable for high-DPI print (cleaner output)
156
- style_params["lines.antialiased"] = False
157
- style_params["patch.antialiased"] = False
158
- style_params["text.antialiased"] = False
159
-
160
- # Publication-specific settings
161
- if preset == "publication":
162
- style_params["font.family"] = "serif"
163
- style_params["mathtext.fontset"] = "cm" # Computer Modern for LaTeX
164
- style_params["axes.grid"] = True
165
- style_params["grid.alpha"] = 0.3
166
- style_params["axes.axisbelow"] = True
167
180
 
168
- return {
169
- "dpi": target_dpi,
170
- "figsize": figsize,
171
- "font_scale": font_scale,
172
- "line_scale": line_scale,
173
- "marker_scale": marker_scale,
174
- "antialias": preset_config["antialias"],
175
- "format": preset_config["format"],
176
- "style_params": style_params,
177
- "description": preset_config["description"],
178
- "preset": preset if custom_dpi is None else "custom",
179
- }
181
+ def _apply_antialias_settings(style_params: dict[str, Any], preset_config: dict[str, Any]) -> None:
182
+ """Apply anti-aliasing settings to style params (modifies in-place)."""
183
+ antialias = preset_config["antialias"]
184
+ style_params["lines.antialiased"] = antialias
185
+ style_params["patch.antialiased"] = antialias
186
+ style_params["text.antialiased"] = antialias
187
+
188
+
189
+ def _apply_publication_settings(style_params: dict[str, Any]) -> None:
190
+ """Apply publication-specific settings to style params (modifies in-place)."""
191
+ style_params["font.family"] = "serif"
192
+ style_params["mathtext.fontset"] = "cm" # Computer Modern for LaTeX
193
+ style_params["axes.grid"] = True
194
+ style_params["grid.alpha"] = 0.3
195
+ style_params["axes.axisbelow"] = True
180
196
 
181
197
 
182
198
  def apply_rendering_config(config: dict[str, Any]) -> None:
@@ -197,7 +213,7 @@ def apply_rendering_config(config: dict[str, Any]) -> None:
197
213
 
198
214
  plt.rcParams.update(config["style_params"])
199
215
  except ImportError:
200
- raise ImportError("matplotlib is required for rendering configuration") # noqa: B904
216
+ raise ImportError("matplotlib is required for rendering configuration")
201
217
 
202
218
 
203
219
  __all__ = [
@@ -27,7 +27,7 @@ if TYPE_CHECKING:
27
27
 
28
28
  from oscura.inference.crc_reverse import CRCParameters
29
29
  from oscura.inference.message_format import InferredField, MessageSchema
30
- from oscura.pipeline.reverse_engineering import (
30
+ from oscura.utils.pipeline.reverse_engineering import (
31
31
  MessageTypeInfo,
32
32
  ProtocolCandidate,
33
33
  REAnalysisResult,
@@ -67,7 +67,7 @@ def plot_re_summary(
67
67
  Matplotlib figure object.
68
68
 
69
69
  Example:
70
- >>> from oscura.pipeline.reverse_engineering import REPipeline
70
+ >>> from oscura.utils.pipeline.reverse_engineering import REPipeline
71
71
  >>> pipeline = REPipeline()
72
72
  >>> result = pipeline.analyze(data)
73
73
  >>> fig = plot_re_summary(result)
@@ -107,6 +107,110 @@ def plot_re_summary(
107
107
  return fig
108
108
 
109
109
 
110
+ def _get_field_type_colors() -> dict[str, str]:
111
+ """Get color map for field types."""
112
+ return {
113
+ "constant": "#4CAF50", # Green
114
+ "counter": "#2196F3", # Blue
115
+ "timestamp": "#9C27B0", # Purple
116
+ "length": "#FF9800", # Orange
117
+ "checksum": "#F44336", # Red
118
+ "data": "#607D8B", # Gray
119
+ "unknown": "#9E9E9E", # Light gray
120
+ }
121
+
122
+
123
+ def _draw_field_rectangles(
124
+ ax: Any,
125
+ fields: list[InferredField],
126
+ total_bytes: int,
127
+ bar_height: float,
128
+ y_center: float,
129
+ type_colors: dict[str, str],
130
+ ) -> None:
131
+ """Draw field rectangles with labels."""
132
+ for field in fields:
133
+ color = type_colors.get(field.field_type, "#9E9E9E")
134
+ width = field.size / total_bytes
135
+
136
+ rect = Rectangle(
137
+ (field.offset / total_bytes, y_center - bar_height / 2),
138
+ width,
139
+ bar_height,
140
+ facecolor=color,
141
+ edgecolor="black",
142
+ linewidth=1.5,
143
+ )
144
+ ax.add_patch(rect)
145
+
146
+ x_center = (field.offset + field.size / 2) / total_bytes
147
+ label = f"{field.name}\n({field.field_type})" if field.size > 2 else field.field_type[:3]
148
+
149
+ ax.text(
150
+ x_center,
151
+ y_center,
152
+ label,
153
+ ha="center",
154
+ va="center",
155
+ fontsize=9 if field.size > 2 else 7,
156
+ fontweight="bold",
157
+ color="white",
158
+ )
159
+
160
+
161
+ def _add_field_offsets(
162
+ ax: Any,
163
+ fields: list[InferredField],
164
+ total_bytes: int,
165
+ bar_height: float,
166
+ y_center: float,
167
+ ) -> None:
168
+ """Add byte offset labels."""
169
+ for field in fields:
170
+ ax.text(
171
+ field.offset / total_bytes,
172
+ y_center - bar_height / 2 - 0.08,
173
+ f"{field.offset}",
174
+ ha="center",
175
+ va="top",
176
+ fontsize=8,
177
+ )
178
+
179
+ ax.text(
180
+ 1.0,
181
+ y_center - bar_height / 2 - 0.08,
182
+ f"{total_bytes}",
183
+ ha="center",
184
+ va="top",
185
+ fontsize=8,
186
+ )
187
+
188
+
189
+ def _add_field_legend(ax: Any, fields: list[InferredField], type_colors: dict[str, str]) -> None:
190
+ """Add legend for field types."""
191
+ legend_elements = [
192
+ Rectangle((0, 0), 1, 1, facecolor=color, label=ftype)
193
+ for ftype, color in type_colors.items()
194
+ if any(f.field_type == ftype for f in fields)
195
+ ]
196
+ ax.legend(handles=legend_elements, loc="upper right", fontsize=8)
197
+
198
+
199
+ def _format_layout_axes(ax: Any, total_bytes: int, title: str | None) -> None:
200
+ """Format axes for layout plot."""
201
+ ax.set_xlim(-0.02, 1.02)
202
+ ax.set_ylim(0, 1)
203
+ ax.set_aspect("equal")
204
+ ax.axis("off")
205
+
206
+ if title:
207
+ ax.set_title(title, fontsize=12, fontweight="bold", pad=20)
208
+ else:
209
+ ax.set_title(
210
+ f"Message Field Layout ({total_bytes} bytes)", fontsize=12, fontweight="bold", pad=20
211
+ )
212
+
213
+
110
214
  def plot_message_type_distribution(
111
215
  message_types: list[MessageTypeInfo],
112
216
  *,
@@ -190,93 +294,15 @@ def plot_message_field_layout(
190
294
  ax.set_axis_off()
191
295
  return fig
192
296
 
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
-
297
+ type_colors = _get_field_type_colors()
204
298
  total_bytes = schema.total_size
205
299
  bar_height = 0.6
206
300
  y_center = 0.5
207
301
 
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
- )
302
+ _draw_field_rectangles(ax, schema.fields, total_bytes, bar_height, y_center, type_colors)
303
+ _add_field_offsets(ax, schema.fields, total_bytes, bar_height, y_center)
304
+ _add_field_legend(ax, schema.fields, type_colors)
305
+ _format_layout_axes(ax, schema.total_size, title)
280
306
 
281
307
  return fig
282
308
 
@@ -476,11 +502,20 @@ def plot_crc_parameters(
476
502
  >>> fig = plot_crc_parameters(params)
477
503
  """
478
504
  fig, (ax1, ax2) = plt.subplots(1, 2, figsize=figsize)
505
+ _plot_crc_parameter_table(ax1, params)
506
+ _plot_crc_confidence_gauge(ax2, params)
507
+ _finalize_crc_plot(fig, title)
508
+ return fig
509
+
479
510
 
480
- # Left panel: CRC configuration
481
- ax1.axis("off")
511
+ def _plot_crc_parameter_table(ax: Any, params: CRCParameters) -> None:
512
+ """Plot CRC parameter table.
482
513
 
483
- # Build parameter table
514
+ Args:
515
+ ax: Axes object.
516
+ params: CRC parameters.
517
+ """
518
+ ax.axis("off")
484
519
  param_lines = [
485
520
  f"Width: {params.width} bits",
486
521
  f"Polynomial: 0x{params.polynomial:0{params.width // 4}X}",
@@ -493,26 +528,30 @@ def plot_crc_parameters(
493
528
  if params.algorithm_name:
494
529
  param_lines.insert(0, f"Algorithm: {params.algorithm_name}")
495
530
 
531
+ ax.text(0.5, 0.98, "CRC Parameters", ha="center", fontsize=14, fontweight="bold")
532
+
496
533
  y_start = 0.9
497
534
  y_step = 0.12
498
-
499
- ax1.text(0.5, 0.98, "CRC Parameters", ha="center", fontsize=14, fontweight="bold")
500
-
501
535
  for i, line in enumerate(param_lines):
502
536
  y = y_start - i * y_step
503
- ax1.text(0.1, y, line, fontsize=11, fontfamily="monospace", va="top")
537
+ ax.text(0.1, y, line, fontsize=11, fontfamily="monospace", va="top")
538
+
504
539
 
505
- # Right panel: Confidence gauge
506
- ax2.set_aspect("equal")
540
+ def _plot_crc_confidence_gauge(ax: Any, params: CRCParameters) -> None:
541
+ """Plot confidence gauge for CRC parameters.
507
542
 
508
- # Draw confidence gauge
543
+ Args:
544
+ ax: Axes object.
545
+ params: CRC parameters.
546
+ """
547
+ ax.set_aspect("equal")
548
+
549
+ # Background arc
509
550
  theta = np.linspace(0, np.pi, 100)
510
551
  r = 0.8
511
552
  x = r * np.cos(theta)
512
553
  y = r * np.sin(theta)
513
-
514
- # Background arc (gray)
515
- ax2.plot(x, y, color="#E0E0E0", linewidth=20, solid_capstyle="round")
554
+ ax.plot(x, y, color="#E0E0E0", linewidth=20, solid_capstyle="round")
516
555
 
517
556
  # Confidence arc (colored)
518
557
  conf_angle = np.pi * params.confidence
@@ -520,29 +559,15 @@ def plot_crc_parameters(
520
559
  x_conf = r * np.cos(theta_conf)
521
560
  y_conf = r * np.sin(theta_conf)
522
561
 
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")
562
+ color = _get_confidence_color(params.confidence)
563
+ ax.plot(x_conf, y_conf, color=color, linewidth=20, solid_capstyle="round")
531
564
 
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",
565
+ # Add text annotations
566
+ ax.text(
567
+ 0, 0.2, f"{params.confidence:.0%}", ha="center", va="center", fontsize=24, fontweight="bold"
541
568
  )
542
- ax2.text(0, -0.1, "Confidence", ha="center", va="center", fontsize=12)
543
-
544
- # Add test pass rate
545
- ax2.text(
569
+ ax.text(0, -0.1, "Confidence", ha="center", va="center", fontsize=12)
570
+ ax.text(
546
571
  0,
547
572
  -0.4,
548
573
  f"Test Pass Rate: {params.test_pass_rate:.0%}",
@@ -551,17 +576,39 @@ def plot_crc_parameters(
551
576
  fontsize=10,
552
577
  )
553
578
 
554
- ax2.set_xlim(-1.2, 1.2)
555
- ax2.set_ylim(-0.6, 1.2)
556
- ax2.axis("off")
579
+ ax.set_xlim(-1.2, 1.2)
580
+ ax.set_ylim(-0.6, 1.2)
581
+ ax.axis("off")
582
+
583
+
584
+ def _get_confidence_color(confidence: float) -> str:
585
+ """Get color based on confidence level.
557
586
 
587
+ Args:
588
+ confidence: Confidence value (0-1).
589
+
590
+ Returns:
591
+ Hex color code.
592
+ """
593
+ if confidence >= 0.8:
594
+ return "#4CAF50" # Green
595
+ if confidence >= 0.5:
596
+ return "#FF9800" # Orange
597
+ return "#F44336" # Red
598
+
599
+
600
+ def _finalize_crc_plot(fig: Figure, title: str | None) -> None:
601
+ """Finalize CRC plot with title.
602
+
603
+ Args:
604
+ fig: Figure object.
605
+ title: Optional title.
606
+ """
558
607
  if title:
559
608
  fig.suptitle(title, fontsize=14, fontweight="bold")
560
609
  else:
561
610
  fig.suptitle("CRC Parameter Recovery", fontsize=14, fontweight="bold")
562
-
563
611
  plt.tight_layout()
564
- return fig
565
612
 
566
613
 
567
614
  def plot_pipeline_timing(