oscura 0.5.1__py3-none-any.whl → 0.7.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (497) hide show
  1. oscura/__init__.py +169 -167
  2. oscura/analyzers/__init__.py +3 -0
  3. oscura/analyzers/classification.py +659 -0
  4. oscura/analyzers/digital/edges.py +325 -65
  5. oscura/analyzers/digital/quality.py +293 -166
  6. oscura/analyzers/digital/timing.py +260 -115
  7. oscura/analyzers/digital/timing_numba.py +334 -0
  8. oscura/analyzers/entropy.py +605 -0
  9. oscura/analyzers/eye/diagram.py +176 -109
  10. oscura/analyzers/eye/metrics.py +5 -5
  11. oscura/analyzers/jitter/__init__.py +6 -4
  12. oscura/analyzers/jitter/ber.py +52 -52
  13. oscura/analyzers/jitter/classification.py +156 -0
  14. oscura/analyzers/jitter/decomposition.py +163 -113
  15. oscura/analyzers/jitter/spectrum.py +80 -64
  16. oscura/analyzers/ml/__init__.py +39 -0
  17. oscura/analyzers/ml/features.py +600 -0
  18. oscura/analyzers/ml/signal_classifier.py +604 -0
  19. oscura/analyzers/packet/daq.py +246 -158
  20. oscura/analyzers/packet/parser.py +12 -1
  21. oscura/analyzers/packet/payload.py +50 -2110
  22. oscura/analyzers/packet/payload_analysis.py +361 -181
  23. oscura/analyzers/packet/payload_patterns.py +133 -70
  24. oscura/analyzers/packet/stream.py +84 -23
  25. oscura/analyzers/patterns/__init__.py +26 -5
  26. oscura/analyzers/patterns/anomaly_detection.py +908 -0
  27. oscura/analyzers/patterns/clustering.py +169 -108
  28. oscura/analyzers/patterns/clustering_optimized.py +227 -0
  29. oscura/analyzers/patterns/discovery.py +1 -1
  30. oscura/analyzers/patterns/matching.py +581 -197
  31. oscura/analyzers/patterns/pattern_mining.py +778 -0
  32. oscura/analyzers/patterns/periodic.py +121 -38
  33. oscura/analyzers/patterns/sequences.py +175 -78
  34. oscura/analyzers/power/conduction.py +1 -1
  35. oscura/analyzers/power/soa.py +6 -6
  36. oscura/analyzers/power/switching.py +250 -110
  37. oscura/analyzers/protocol/__init__.py +17 -1
  38. oscura/analyzers/protocols/base.py +6 -6
  39. oscura/analyzers/protocols/ble/__init__.py +38 -0
  40. oscura/analyzers/protocols/ble/analyzer.py +809 -0
  41. oscura/analyzers/protocols/ble/uuids.py +288 -0
  42. oscura/analyzers/protocols/can.py +257 -127
  43. oscura/analyzers/protocols/can_fd.py +107 -80
  44. oscura/analyzers/protocols/flexray.py +139 -80
  45. oscura/analyzers/protocols/hdlc.py +93 -58
  46. oscura/analyzers/protocols/i2c.py +247 -106
  47. oscura/analyzers/protocols/i2s.py +138 -86
  48. oscura/analyzers/protocols/industrial/__init__.py +40 -0
  49. oscura/analyzers/protocols/industrial/bacnet/__init__.py +33 -0
  50. oscura/analyzers/protocols/industrial/bacnet/analyzer.py +708 -0
  51. oscura/analyzers/protocols/industrial/bacnet/encoding.py +412 -0
  52. oscura/analyzers/protocols/industrial/bacnet/services.py +622 -0
  53. oscura/analyzers/protocols/industrial/ethercat/__init__.py +30 -0
  54. oscura/analyzers/protocols/industrial/ethercat/analyzer.py +474 -0
  55. oscura/analyzers/protocols/industrial/ethercat/mailbox.py +339 -0
  56. oscura/analyzers/protocols/industrial/ethercat/topology.py +166 -0
  57. oscura/analyzers/protocols/industrial/modbus/__init__.py +31 -0
  58. oscura/analyzers/protocols/industrial/modbus/analyzer.py +525 -0
  59. oscura/analyzers/protocols/industrial/modbus/crc.py +79 -0
  60. oscura/analyzers/protocols/industrial/modbus/functions.py +436 -0
  61. oscura/analyzers/protocols/industrial/opcua/__init__.py +21 -0
  62. oscura/analyzers/protocols/industrial/opcua/analyzer.py +552 -0
  63. oscura/analyzers/protocols/industrial/opcua/datatypes.py +446 -0
  64. oscura/analyzers/protocols/industrial/opcua/services.py +264 -0
  65. oscura/analyzers/protocols/industrial/profinet/__init__.py +23 -0
  66. oscura/analyzers/protocols/industrial/profinet/analyzer.py +441 -0
  67. oscura/analyzers/protocols/industrial/profinet/dcp.py +263 -0
  68. oscura/analyzers/protocols/industrial/profinet/ptcp.py +200 -0
  69. oscura/analyzers/protocols/jtag.py +180 -98
  70. oscura/analyzers/protocols/lin.py +219 -114
  71. oscura/analyzers/protocols/manchester.py +4 -4
  72. oscura/analyzers/protocols/onewire.py +253 -149
  73. oscura/analyzers/protocols/parallel_bus/__init__.py +20 -0
  74. oscura/analyzers/protocols/parallel_bus/centronics.py +92 -0
  75. oscura/analyzers/protocols/parallel_bus/gpib.py +137 -0
  76. oscura/analyzers/protocols/spi.py +192 -95
  77. oscura/analyzers/protocols/swd.py +321 -167
  78. oscura/analyzers/protocols/uart.py +267 -125
  79. oscura/analyzers/protocols/usb.py +235 -131
  80. oscura/analyzers/side_channel/power.py +17 -12
  81. oscura/analyzers/signal/__init__.py +15 -0
  82. oscura/analyzers/signal/timing_analysis.py +1086 -0
  83. oscura/analyzers/signal_integrity/__init__.py +4 -1
  84. oscura/analyzers/signal_integrity/sparams.py +2 -19
  85. oscura/analyzers/spectral/chunked.py +129 -60
  86. oscura/analyzers/spectral/chunked_fft.py +300 -94
  87. oscura/analyzers/spectral/chunked_wavelet.py +100 -80
  88. oscura/analyzers/statistical/checksum.py +376 -217
  89. oscura/analyzers/statistical/classification.py +229 -107
  90. oscura/analyzers/statistical/entropy.py +78 -53
  91. oscura/analyzers/statistics/correlation.py +407 -211
  92. oscura/analyzers/statistics/outliers.py +2 -2
  93. oscura/analyzers/statistics/streaming.py +30 -5
  94. oscura/analyzers/validation.py +216 -101
  95. oscura/analyzers/waveform/measurements.py +9 -0
  96. oscura/analyzers/waveform/measurements_with_uncertainty.py +31 -15
  97. oscura/analyzers/waveform/spectral.py +500 -228
  98. oscura/api/__init__.py +31 -5
  99. oscura/api/dsl/__init__.py +582 -0
  100. oscura/{dsl → api/dsl}/commands.py +43 -76
  101. oscura/{dsl → api/dsl}/interpreter.py +26 -51
  102. oscura/{dsl → api/dsl}/parser.py +107 -77
  103. oscura/{dsl → api/dsl}/repl.py +2 -2
  104. oscura/api/dsl.py +1 -1
  105. oscura/{integrations → api/integrations}/__init__.py +1 -1
  106. oscura/{integrations → api/integrations}/llm.py +201 -102
  107. oscura/api/operators.py +3 -3
  108. oscura/api/optimization.py +144 -30
  109. oscura/api/rest_server.py +921 -0
  110. oscura/api/server/__init__.py +17 -0
  111. oscura/api/server/dashboard.py +850 -0
  112. oscura/api/server/static/README.md +34 -0
  113. oscura/api/server/templates/base.html +181 -0
  114. oscura/api/server/templates/export.html +120 -0
  115. oscura/api/server/templates/home.html +284 -0
  116. oscura/api/server/templates/protocols.html +58 -0
  117. oscura/api/server/templates/reports.html +43 -0
  118. oscura/api/server/templates/session_detail.html +89 -0
  119. oscura/api/server/templates/sessions.html +83 -0
  120. oscura/api/server/templates/waveforms.html +73 -0
  121. oscura/automotive/__init__.py +8 -1
  122. oscura/automotive/can/__init__.py +10 -0
  123. oscura/automotive/can/checksum.py +3 -1
  124. oscura/automotive/can/dbc_generator.py +590 -0
  125. oscura/automotive/can/message_wrapper.py +121 -74
  126. oscura/automotive/can/patterns.py +98 -21
  127. oscura/automotive/can/session.py +292 -56
  128. oscura/automotive/can/state_machine.py +6 -3
  129. oscura/automotive/can/stimulus_response.py +97 -75
  130. oscura/automotive/dbc/__init__.py +10 -2
  131. oscura/automotive/dbc/generator.py +84 -56
  132. oscura/automotive/dbc/parser.py +6 -6
  133. oscura/automotive/dtc/data.json +17 -102
  134. oscura/automotive/dtc/database.py +2 -2
  135. oscura/automotive/flexray/__init__.py +31 -0
  136. oscura/automotive/flexray/analyzer.py +504 -0
  137. oscura/automotive/flexray/crc.py +185 -0
  138. oscura/automotive/flexray/fibex.py +449 -0
  139. oscura/automotive/j1939/__init__.py +45 -8
  140. oscura/automotive/j1939/analyzer.py +605 -0
  141. oscura/automotive/j1939/spns.py +326 -0
  142. oscura/automotive/j1939/transport.py +306 -0
  143. oscura/automotive/lin/__init__.py +47 -0
  144. oscura/automotive/lin/analyzer.py +612 -0
  145. oscura/automotive/loaders/blf.py +13 -2
  146. oscura/automotive/loaders/csv_can.py +143 -72
  147. oscura/automotive/loaders/dispatcher.py +50 -2
  148. oscura/automotive/loaders/mdf.py +86 -45
  149. oscura/automotive/loaders/pcap.py +111 -61
  150. oscura/automotive/uds/__init__.py +4 -0
  151. oscura/automotive/uds/analyzer.py +725 -0
  152. oscura/automotive/uds/decoder.py +140 -58
  153. oscura/automotive/uds/models.py +7 -1
  154. oscura/automotive/visualization.py +1 -1
  155. oscura/cli/analyze.py +348 -0
  156. oscura/cli/batch.py +142 -122
  157. oscura/cli/benchmark.py +275 -0
  158. oscura/cli/characterize.py +137 -82
  159. oscura/cli/compare.py +224 -131
  160. oscura/cli/completion.py +250 -0
  161. oscura/cli/config_cmd.py +361 -0
  162. oscura/cli/decode.py +164 -87
  163. oscura/cli/export.py +286 -0
  164. oscura/cli/main.py +115 -31
  165. oscura/{onboarding → cli/onboarding}/__init__.py +3 -3
  166. oscura/{onboarding → cli/onboarding}/help.py +80 -58
  167. oscura/{onboarding → cli/onboarding}/tutorials.py +97 -72
  168. oscura/{onboarding → cli/onboarding}/wizard.py +55 -36
  169. oscura/cli/progress.py +147 -0
  170. oscura/cli/shell.py +157 -135
  171. oscura/cli/validate_cmd.py +204 -0
  172. oscura/cli/visualize.py +158 -0
  173. oscura/convenience.py +125 -79
  174. oscura/core/__init__.py +4 -2
  175. oscura/core/backend_selector.py +3 -3
  176. oscura/core/cache.py +126 -15
  177. oscura/core/cancellation.py +1 -1
  178. oscura/{config → core/config}/__init__.py +20 -11
  179. oscura/{config → core/config}/defaults.py +1 -1
  180. oscura/{config → core/config}/loader.py +7 -5
  181. oscura/{config → core/config}/memory.py +5 -5
  182. oscura/{config → core/config}/migration.py +1 -1
  183. oscura/{config → core/config}/pipeline.py +99 -23
  184. oscura/{config → core/config}/preferences.py +1 -1
  185. oscura/{config → core/config}/protocol.py +3 -3
  186. oscura/{config → core/config}/schema.py +426 -272
  187. oscura/{config → core/config}/settings.py +1 -1
  188. oscura/{config → core/config}/thresholds.py +195 -153
  189. oscura/core/correlation.py +5 -6
  190. oscura/core/cross_domain.py +0 -2
  191. oscura/core/debug.py +9 -5
  192. oscura/{extensibility → core/extensibility}/docs.py +158 -70
  193. oscura/{extensibility → core/extensibility}/extensions.py +160 -76
  194. oscura/{extensibility → core/extensibility}/logging.py +1 -1
  195. oscura/{extensibility → core/extensibility}/measurements.py +1 -1
  196. oscura/{extensibility → core/extensibility}/plugins.py +1 -1
  197. oscura/{extensibility → core/extensibility}/templates.py +73 -3
  198. oscura/{extensibility → core/extensibility}/validation.py +1 -1
  199. oscura/core/gpu_backend.py +11 -7
  200. oscura/core/log_query.py +101 -11
  201. oscura/core/logging.py +126 -54
  202. oscura/core/logging_advanced.py +5 -5
  203. oscura/core/memory_limits.py +108 -70
  204. oscura/core/memory_monitor.py +2 -2
  205. oscura/core/memory_progress.py +7 -7
  206. oscura/core/memory_warnings.py +1 -1
  207. oscura/core/numba_backend.py +13 -13
  208. oscura/{plugins → core/plugins}/__init__.py +9 -9
  209. oscura/{plugins → core/plugins}/base.py +7 -7
  210. oscura/{plugins → core/plugins}/cli.py +3 -3
  211. oscura/{plugins → core/plugins}/discovery.py +186 -106
  212. oscura/{plugins → core/plugins}/lifecycle.py +1 -1
  213. oscura/{plugins → core/plugins}/manager.py +7 -7
  214. oscura/{plugins → core/plugins}/registry.py +3 -3
  215. oscura/{plugins → core/plugins}/versioning.py +1 -1
  216. oscura/core/progress.py +16 -1
  217. oscura/core/provenance.py +8 -2
  218. oscura/{schemas → core/schemas}/__init__.py +2 -2
  219. oscura/{schemas → core/schemas}/device_mapping.json +2 -8
  220. oscura/{schemas → core/schemas}/packet_format.json +4 -24
  221. oscura/{schemas → core/schemas}/protocol_definition.json +2 -12
  222. oscura/core/types.py +4 -0
  223. oscura/core/uncertainty.py +3 -3
  224. oscura/correlation/__init__.py +52 -0
  225. oscura/correlation/multi_protocol.py +811 -0
  226. oscura/discovery/auto_decoder.py +117 -35
  227. oscura/discovery/comparison.py +191 -86
  228. oscura/discovery/quality_validator.py +155 -68
  229. oscura/discovery/signal_detector.py +196 -79
  230. oscura/export/__init__.py +18 -8
  231. oscura/export/kaitai_struct.py +513 -0
  232. oscura/export/scapy_layer.py +801 -0
  233. oscura/export/wireshark/generator.py +1 -1
  234. oscura/export/wireshark/templates/dissector.lua.j2 +2 -2
  235. oscura/export/wireshark_dissector.py +746 -0
  236. oscura/guidance/wizard.py +207 -111
  237. oscura/hardware/__init__.py +19 -0
  238. oscura/{acquisition → hardware/acquisition}/__init__.py +4 -4
  239. oscura/{acquisition → hardware/acquisition}/file.py +2 -2
  240. oscura/{acquisition → hardware/acquisition}/hardware.py +7 -7
  241. oscura/{acquisition → hardware/acquisition}/saleae.py +15 -12
  242. oscura/{acquisition → hardware/acquisition}/socketcan.py +1 -1
  243. oscura/{acquisition → hardware/acquisition}/streaming.py +2 -2
  244. oscura/{acquisition → hardware/acquisition}/synthetic.py +3 -3
  245. oscura/{acquisition → hardware/acquisition}/visa.py +33 -11
  246. oscura/hardware/firmware/__init__.py +29 -0
  247. oscura/hardware/firmware/pattern_recognition.py +874 -0
  248. oscura/hardware/hal_detector.py +736 -0
  249. oscura/hardware/security/__init__.py +37 -0
  250. oscura/hardware/security/side_channel_detector.py +1126 -0
  251. oscura/inference/__init__.py +4 -0
  252. oscura/inference/active_learning/observation_table.py +4 -1
  253. oscura/inference/alignment.py +216 -123
  254. oscura/inference/bayesian.py +113 -33
  255. oscura/inference/crc_reverse.py +101 -55
  256. oscura/inference/logic.py +6 -2
  257. oscura/inference/message_format.py +342 -183
  258. oscura/inference/protocol.py +95 -44
  259. oscura/inference/protocol_dsl.py +180 -82
  260. oscura/inference/signal_intelligence.py +1439 -706
  261. oscura/inference/spectral.py +99 -57
  262. oscura/inference/state_machine.py +810 -158
  263. oscura/inference/stream.py +270 -110
  264. oscura/iot/__init__.py +34 -0
  265. oscura/iot/coap/__init__.py +32 -0
  266. oscura/iot/coap/analyzer.py +668 -0
  267. oscura/iot/coap/options.py +212 -0
  268. oscura/iot/lorawan/__init__.py +21 -0
  269. oscura/iot/lorawan/crypto.py +206 -0
  270. oscura/iot/lorawan/decoder.py +801 -0
  271. oscura/iot/lorawan/mac_commands.py +341 -0
  272. oscura/iot/mqtt/__init__.py +27 -0
  273. oscura/iot/mqtt/analyzer.py +999 -0
  274. oscura/iot/mqtt/properties.py +315 -0
  275. oscura/iot/zigbee/__init__.py +31 -0
  276. oscura/iot/zigbee/analyzer.py +615 -0
  277. oscura/iot/zigbee/security.py +153 -0
  278. oscura/iot/zigbee/zcl.py +349 -0
  279. oscura/jupyter/display.py +125 -45
  280. oscura/{exploratory → jupyter/exploratory}/__init__.py +8 -8
  281. oscura/{exploratory → jupyter/exploratory}/error_recovery.py +298 -141
  282. oscura/jupyter/exploratory/fuzzy.py +746 -0
  283. oscura/{exploratory → jupyter/exploratory}/fuzzy_advanced.py +258 -100
  284. oscura/{exploratory → jupyter/exploratory}/legacy.py +464 -242
  285. oscura/{exploratory → jupyter/exploratory}/parse.py +167 -145
  286. oscura/{exploratory → jupyter/exploratory}/recovery.py +119 -87
  287. oscura/jupyter/exploratory/sync.py +612 -0
  288. oscura/{exploratory → jupyter/exploratory}/unknown.py +299 -176
  289. oscura/jupyter/magic.py +4 -4
  290. oscura/{ui → jupyter/ui}/__init__.py +2 -2
  291. oscura/{ui → jupyter/ui}/formatters.py +3 -3
  292. oscura/{ui → jupyter/ui}/progressive_display.py +153 -82
  293. oscura/loaders/__init__.py +183 -67
  294. oscura/loaders/binary.py +88 -1
  295. oscura/loaders/chipwhisperer.py +153 -137
  296. oscura/loaders/configurable.py +208 -86
  297. oscura/loaders/csv_loader.py +458 -215
  298. oscura/loaders/hdf5_loader.py +278 -119
  299. oscura/loaders/lazy.py +87 -54
  300. oscura/loaders/mmap_loader.py +1 -1
  301. oscura/loaders/numpy_loader.py +253 -116
  302. oscura/loaders/pcap.py +226 -151
  303. oscura/loaders/rigol.py +110 -49
  304. oscura/loaders/sigrok.py +201 -78
  305. oscura/loaders/tdms.py +81 -58
  306. oscura/loaders/tektronix.py +291 -174
  307. oscura/loaders/touchstone.py +182 -87
  308. oscura/loaders/tss.py +456 -0
  309. oscura/loaders/vcd.py +215 -117
  310. oscura/loaders/wav.py +155 -68
  311. oscura/reporting/__init__.py +9 -0
  312. oscura/reporting/analyze.py +352 -146
  313. oscura/reporting/argument_preparer.py +69 -14
  314. oscura/reporting/auto_report.py +97 -61
  315. oscura/reporting/batch.py +131 -58
  316. oscura/reporting/chart_selection.py +57 -45
  317. oscura/reporting/comparison.py +63 -17
  318. oscura/reporting/content/executive.py +76 -24
  319. oscura/reporting/core_formats/multi_format.py +11 -8
  320. oscura/reporting/engine.py +312 -158
  321. oscura/reporting/enhanced_reports.py +949 -0
  322. oscura/reporting/export.py +86 -43
  323. oscura/reporting/formatting/numbers.py +69 -42
  324. oscura/reporting/html.py +139 -58
  325. oscura/reporting/index.py +137 -65
  326. oscura/reporting/output.py +158 -67
  327. oscura/reporting/pdf.py +67 -102
  328. oscura/reporting/plots.py +191 -112
  329. oscura/reporting/sections.py +88 -47
  330. oscura/reporting/standards.py +104 -61
  331. oscura/reporting/summary_generator.py +75 -55
  332. oscura/reporting/tables.py +138 -54
  333. oscura/reporting/templates/enhanced/protocol_re.html +525 -0
  334. oscura/sessions/__init__.py +14 -23
  335. oscura/sessions/base.py +3 -3
  336. oscura/sessions/blackbox.py +106 -10
  337. oscura/sessions/generic.py +2 -2
  338. oscura/sessions/legacy.py +783 -0
  339. oscura/side_channel/__init__.py +63 -0
  340. oscura/side_channel/dpa.py +1025 -0
  341. oscura/utils/__init__.py +15 -1
  342. oscura/utils/bitwise.py +118 -0
  343. oscura/{builders → utils/builders}/__init__.py +1 -1
  344. oscura/{comparison → utils/comparison}/__init__.py +6 -6
  345. oscura/{comparison → utils/comparison}/compare.py +202 -101
  346. oscura/{comparison → utils/comparison}/golden.py +83 -63
  347. oscura/{comparison → utils/comparison}/limits.py +313 -89
  348. oscura/{comparison → utils/comparison}/mask.py +151 -45
  349. oscura/{comparison → utils/comparison}/trace_diff.py +1 -1
  350. oscura/{comparison → utils/comparison}/visualization.py +147 -89
  351. oscura/{component → utils/component}/__init__.py +3 -3
  352. oscura/{component → utils/component}/impedance.py +122 -58
  353. oscura/{component → utils/component}/reactive.py +165 -168
  354. oscura/{component → utils/component}/transmission_line.py +3 -3
  355. oscura/{filtering → utils/filtering}/__init__.py +6 -6
  356. oscura/{filtering → utils/filtering}/base.py +1 -1
  357. oscura/{filtering → utils/filtering}/convenience.py +2 -2
  358. oscura/{filtering → utils/filtering}/design.py +169 -93
  359. oscura/{filtering → utils/filtering}/filters.py +2 -2
  360. oscura/{filtering → utils/filtering}/introspection.py +2 -2
  361. oscura/utils/geometry.py +31 -0
  362. oscura/utils/imports.py +184 -0
  363. oscura/utils/lazy.py +1 -1
  364. oscura/{math → utils/math}/__init__.py +2 -2
  365. oscura/{math → utils/math}/arithmetic.py +114 -48
  366. oscura/{math → utils/math}/interpolation.py +139 -106
  367. oscura/utils/memory.py +129 -66
  368. oscura/utils/memory_advanced.py +92 -9
  369. oscura/utils/memory_extensions.py +10 -8
  370. oscura/{optimization → utils/optimization}/__init__.py +1 -1
  371. oscura/{optimization → utils/optimization}/search.py +2 -2
  372. oscura/utils/performance/__init__.py +58 -0
  373. oscura/utils/performance/caching.py +889 -0
  374. oscura/utils/performance/lsh_clustering.py +333 -0
  375. oscura/utils/performance/memory_optimizer.py +699 -0
  376. oscura/utils/performance/optimizations.py +675 -0
  377. oscura/utils/performance/parallel.py +654 -0
  378. oscura/utils/performance/profiling.py +661 -0
  379. oscura/{pipeline → utils/pipeline}/base.py +1 -1
  380. oscura/{pipeline → utils/pipeline}/composition.py +1 -1
  381. oscura/{pipeline → utils/pipeline}/parallel.py +3 -2
  382. oscura/{pipeline → utils/pipeline}/pipeline.py +1 -1
  383. oscura/{pipeline → utils/pipeline}/reverse_engineering.py +412 -221
  384. oscura/{search → utils/search}/__init__.py +3 -3
  385. oscura/{search → utils/search}/anomaly.py +188 -58
  386. oscura/utils/search/context.py +294 -0
  387. oscura/{search → utils/search}/pattern.py +138 -10
  388. oscura/utils/serial.py +51 -0
  389. oscura/utils/storage/__init__.py +61 -0
  390. oscura/utils/storage/database.py +1166 -0
  391. oscura/{streaming → utils/streaming}/chunked.py +302 -143
  392. oscura/{streaming → utils/streaming}/progressive.py +1 -1
  393. oscura/{streaming → utils/streaming}/realtime.py +3 -2
  394. oscura/{triggering → utils/triggering}/__init__.py +6 -6
  395. oscura/{triggering → utils/triggering}/base.py +6 -6
  396. oscura/{triggering → utils/triggering}/edge.py +2 -2
  397. oscura/{triggering → utils/triggering}/pattern.py +2 -2
  398. oscura/{triggering → utils/triggering}/pulse.py +115 -74
  399. oscura/{triggering → utils/triggering}/window.py +2 -2
  400. oscura/utils/validation.py +32 -0
  401. oscura/validation/__init__.py +121 -0
  402. oscura/{compliance → validation/compliance}/__init__.py +5 -5
  403. oscura/{compliance → validation/compliance}/advanced.py +5 -5
  404. oscura/{compliance → validation/compliance}/masks.py +1 -1
  405. oscura/{compliance → validation/compliance}/reporting.py +127 -53
  406. oscura/{compliance → validation/compliance}/testing.py +114 -52
  407. oscura/validation/compliance_tests.py +915 -0
  408. oscura/validation/fuzzer.py +990 -0
  409. oscura/validation/grammar_tests.py +596 -0
  410. oscura/validation/grammar_validator.py +904 -0
  411. oscura/validation/hil_testing.py +977 -0
  412. oscura/{quality → validation/quality}/__init__.py +4 -4
  413. oscura/{quality → validation/quality}/ensemble.py +251 -171
  414. oscura/{quality → validation/quality}/explainer.py +3 -3
  415. oscura/{quality → validation/quality}/scoring.py +1 -1
  416. oscura/{quality → validation/quality}/warnings.py +4 -4
  417. oscura/validation/regression_suite.py +808 -0
  418. oscura/validation/replay.py +788 -0
  419. oscura/{testing → validation/testing}/__init__.py +2 -2
  420. oscura/{testing → validation/testing}/synthetic.py +5 -5
  421. oscura/visualization/__init__.py +9 -0
  422. oscura/visualization/accessibility.py +1 -1
  423. oscura/visualization/annotations.py +64 -67
  424. oscura/visualization/colors.py +7 -7
  425. oscura/visualization/digital.py +180 -81
  426. oscura/visualization/eye.py +236 -85
  427. oscura/visualization/interactive.py +320 -143
  428. oscura/visualization/jitter.py +587 -247
  429. oscura/visualization/layout.py +169 -134
  430. oscura/visualization/optimization.py +103 -52
  431. oscura/visualization/palettes.py +1 -1
  432. oscura/visualization/power.py +427 -211
  433. oscura/visualization/power_extended.py +626 -297
  434. oscura/visualization/presets.py +2 -0
  435. oscura/visualization/protocols.py +495 -181
  436. oscura/visualization/render.py +79 -63
  437. oscura/visualization/reverse_engineering.py +171 -124
  438. oscura/visualization/signal_integrity.py +460 -279
  439. oscura/visualization/specialized.py +190 -100
  440. oscura/visualization/spectral.py +670 -255
  441. oscura/visualization/thumbnails.py +166 -137
  442. oscura/visualization/waveform.py +150 -63
  443. oscura/workflows/__init__.py +3 -0
  444. oscura/{batch → workflows/batch}/__init__.py +5 -5
  445. oscura/{batch → workflows/batch}/advanced.py +150 -75
  446. oscura/workflows/batch/aggregate.py +531 -0
  447. oscura/workflows/batch/analyze.py +236 -0
  448. oscura/{batch → workflows/batch}/logging.py +2 -2
  449. oscura/{batch → workflows/batch}/metrics.py +1 -1
  450. oscura/workflows/complete_re.py +1144 -0
  451. oscura/workflows/compliance.py +44 -54
  452. oscura/workflows/digital.py +197 -51
  453. oscura/workflows/legacy/__init__.py +12 -0
  454. oscura/{workflow → workflows/legacy}/dag.py +4 -1
  455. oscura/workflows/multi_trace.py +9 -9
  456. oscura/workflows/power.py +42 -62
  457. oscura/workflows/protocol.py +82 -49
  458. oscura/workflows/reverse_engineering.py +351 -150
  459. oscura/workflows/signal_integrity.py +157 -82
  460. oscura-0.7.0.dist-info/METADATA +661 -0
  461. oscura-0.7.0.dist-info/RECORD +591 -0
  462. oscura/batch/aggregate.py +0 -300
  463. oscura/batch/analyze.py +0 -139
  464. oscura/dsl/__init__.py +0 -73
  465. oscura/exceptions.py +0 -59
  466. oscura/exploratory/fuzzy.py +0 -513
  467. oscura/exploratory/sync.py +0 -384
  468. oscura/exporters/__init__.py +0 -94
  469. oscura/exporters/csv.py +0 -303
  470. oscura/exporters/exporters.py +0 -44
  471. oscura/exporters/hdf5.py +0 -217
  472. oscura/exporters/html_export.py +0 -701
  473. oscura/exporters/json_export.py +0 -291
  474. oscura/exporters/markdown_export.py +0 -367
  475. oscura/exporters/matlab_export.py +0 -354
  476. oscura/exporters/npz_export.py +0 -219
  477. oscura/exporters/spice_export.py +0 -210
  478. oscura/search/context.py +0 -149
  479. oscura/session/__init__.py +0 -34
  480. oscura/session/annotations.py +0 -289
  481. oscura/session/history.py +0 -313
  482. oscura/session/session.py +0 -520
  483. oscura/workflow/__init__.py +0 -13
  484. oscura-0.5.1.dist-info/METADATA +0 -583
  485. oscura-0.5.1.dist-info/RECORD +0 -481
  486. /oscura/core/{config.py → config/legacy.py} +0 -0
  487. /oscura/{extensibility → core/extensibility}/__init__.py +0 -0
  488. /oscura/{extensibility → core/extensibility}/registry.py +0 -0
  489. /oscura/{plugins → core/plugins}/isolation.py +0 -0
  490. /oscura/{schemas → core/schemas}/bus_configuration.json +0 -0
  491. /oscura/{builders → utils/builders}/signal_builder.py +0 -0
  492. /oscura/{optimization → utils/optimization}/parallel.py +0 -0
  493. /oscura/{pipeline → utils/pipeline}/__init__.py +0 -0
  494. /oscura/{streaming → utils/streaming}/__init__.py +0 -0
  495. {oscura-0.5.1.dist-info → oscura-0.7.0.dist-info}/WHEEL +0 -0
  496. {oscura-0.5.1.dist-info → oscura-0.7.0.dist-info}/entry_points.txt +0 -0
  497. {oscura-0.5.1.dist-info → oscura-0.7.0.dist-info}/licenses/LICENSE +0 -0
@@ -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(