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
@@ -5,7 +5,7 @@ including eye diagram masks and custom polygon masks.
5
5
 
6
6
 
7
7
  Example:
8
- >>> from oscura.comparison import mask_test, eye_mask
8
+ >>> from oscura.utils.comparison import mask_test, eye_mask
9
9
  >>> mask = eye_mask(0.5, 0.4, 0.3)
10
10
  >>> result = mask_test(trace, mask)
11
11
 
@@ -274,20 +274,77 @@ def mask_test(
274
274
  >>> result = mask_test(eye_trace, mask)
275
275
  >>> print(f"Violations: {result.num_violations}")
276
276
  """
277
- # Get Y data
277
+ # Prepare test data
278
+ x_arr, y_arr = _prepare_mask_test_data(trace, x_data, normalize)
279
+
280
+ # Find all violations
281
+ violations, violations_by_region = _find_mask_violations(x_arr, y_arr, mask)
282
+
283
+ # Calculate statistics
284
+ unique_violations = list(set(violations))
285
+ num_violations = len(unique_violations)
286
+ violation_rate = num_violations / len(y_arr) if len(y_arr) > 0 else 0.0
287
+
288
+ # Calculate margin to mask boundary
289
+ margin = _calculate_mask_margin(x_arr, y_arr, mask, num_violations)
290
+
291
+ return MaskTestResult(
292
+ passed=num_violations == 0,
293
+ num_violations=num_violations,
294
+ violation_rate=violation_rate,
295
+ violation_points=unique_violations,
296
+ violations_by_region=violations_by_region,
297
+ margin=margin,
298
+ )
299
+
300
+
301
+ def _prepare_mask_test_data(
302
+ trace: WaveformTrace,
303
+ x_data: NDArray[np.floating[Any]] | None,
304
+ normalize: bool,
305
+ ) -> tuple[NDArray[np.float64], NDArray[np.float64]]:
306
+ """Prepare X and Y data arrays for mask testing.
307
+
308
+ Args:
309
+ trace: Waveform trace.
310
+ x_data: Optional X coordinates (defaults to sample indices).
311
+ normalize: Whether to normalize Y data to [-1, 1].
312
+
313
+ Returns:
314
+ Tuple of (x_array, y_array) as float64.
315
+ """
278
316
  y_data = trace.data.astype(np.float64)
279
317
 
280
- # Get or create X data
318
+ # Generate or use provided X data
281
319
  if x_data is None:
282
- x_data = np.arange(len(y_data), dtype=np.float64)
320
+ x_arr = np.arange(len(y_data), dtype=np.float64)
321
+ else:
322
+ x_arr = x_data
283
323
 
284
- # Normalize if requested
324
+ # Normalize Y data if requested
285
325
  if normalize:
286
326
  y_min, y_max = np.min(y_data), np.max(y_data)
287
327
  if y_max - y_min > 0:
288
328
  y_data = 2 * (y_data - y_min) / (y_max - y_min) - 1
289
329
 
290
- # Test each point against mask regions
330
+ return (x_arr, y_data)
331
+
332
+
333
+ def _find_mask_violations(
334
+ x_data: NDArray[np.float64],
335
+ y_data: NDArray[np.float64],
336
+ mask: Mask,
337
+ ) -> tuple[list[tuple[float, float]], dict[str, int]]:
338
+ """Find all mask violations in waveform data.
339
+
340
+ Args:
341
+ x_data: X coordinates.
342
+ y_data: Y coordinates.
343
+ mask: Mask with regions to test.
344
+
345
+ Returns:
346
+ Tuple of (violation_points, violations_by_region_name).
347
+ """
291
348
  violations: list[tuple[float, float]] = []
292
349
  violations_by_region: dict[str, int] = {}
293
350
 
@@ -296,48 +353,97 @@ def mask_test(
296
353
  violations_by_region[region_name] = 0
297
354
 
298
355
  if region.region_type == "violation":
299
- # Check if points are inside violation region
300
- for i, (x, y) in enumerate(zip(x_data, y_data, strict=False)): # noqa: B007
301
- if region.contains_point(float(x), float(y)):
302
- violations.append((float(x), float(y)))
303
- violations_by_region[region_name] += 1
304
-
356
+ # Points inside violation region are violations
357
+ region_violations = _check_violation_region(x_data, y_data, region)
358
+ violations.extend(region_violations)
359
+ violations_by_region[region_name] = len(region_violations)
305
360
  elif region.region_type == "boundary":
306
- # Check if points are outside boundary region
307
- for i, (x, y) in enumerate(zip(x_data, y_data, strict=False)): # noqa: B007
308
- if not region.contains_point(float(x), float(y)):
309
- violations.append((float(x), float(y)))
310
- violations_by_region[region_name] += 1
361
+ # Points outside boundary region are violations
362
+ region_violations = _check_boundary_region(x_data, y_data, region)
363
+ violations.extend(region_violations)
364
+ violations_by_region[region_name] = len(region_violations)
311
365
 
312
- # Remove duplicates
313
- unique_violations = list(set(violations))
314
- num_violations = len(unique_violations)
315
- violation_rate = num_violations / len(y_data) if len(y_data) > 0 else 0.0
316
-
317
- # Estimate margin (simplified - distance to nearest mask edge)
318
- margin = None
319
- if num_violations == 0 and mask.regions:
320
- # Find minimum distance to any violation region
321
- min_dist = float("inf")
322
- for region in mask.regions:
323
- if region.region_type == "violation":
324
- for x, y in zip(x_data, y_data, strict=False):
325
- for i in range(len(region.vertices)):
326
- x1, y1 = region.vertices[i]
327
- x2, y2 = region.vertices[(i + 1) % len(region.vertices)]
328
- # Distance to line segment
329
- dist = _point_to_segment_distance(x, y, x1, y1, x2, y2)
330
- min_dist = min(min_dist, dist)
331
- margin = min_dist if min_dist != float("inf") else None
366
+ return (violations, violations_by_region)
332
367
 
333
- return MaskTestResult(
334
- passed=num_violations == 0,
335
- num_violations=num_violations,
336
- violation_rate=violation_rate,
337
- violation_points=unique_violations,
338
- violations_by_region=violations_by_region,
339
- margin=margin,
340
- )
368
+
369
+ def _check_violation_region(
370
+ x_data: NDArray[np.float64],
371
+ y_data: NDArray[np.float64],
372
+ region: MaskRegion,
373
+ ) -> list[tuple[float, float]]:
374
+ """Check for points inside violation region.
375
+
376
+ Args:
377
+ x_data: X coordinates.
378
+ y_data: Y coordinates.
379
+ region: Violation region.
380
+
381
+ Returns:
382
+ List of (x, y) points violating this region.
383
+ """
384
+ violations = []
385
+ for x, y in zip(x_data, y_data, strict=False):
386
+ if region.contains_point(float(x), float(y)):
387
+ violations.append((float(x), float(y)))
388
+ return violations
389
+
390
+
391
+ def _check_boundary_region(
392
+ x_data: NDArray[np.float64],
393
+ y_data: NDArray[np.float64],
394
+ region: MaskRegion,
395
+ ) -> list[tuple[float, float]]:
396
+ """Check for points outside boundary region.
397
+
398
+ Args:
399
+ x_data: X coordinates.
400
+ y_data: Y coordinates.
401
+ region: Boundary region.
402
+
403
+ Returns:
404
+ List of (x, y) points violating this region.
405
+ """
406
+ violations = []
407
+ for x, y in zip(x_data, y_data, strict=False):
408
+ if not region.contains_point(float(x), float(y)):
409
+ violations.append((float(x), float(y)))
410
+ return violations
411
+
412
+
413
+ def _calculate_mask_margin(
414
+ x_data: NDArray[np.float64],
415
+ y_data: NDArray[np.float64],
416
+ mask: Mask,
417
+ num_violations: int,
418
+ ) -> float | None:
419
+ """Calculate margin to nearest mask edge.
420
+
421
+ Only calculated when there are no violations.
422
+
423
+ Args:
424
+ x_data: X coordinates.
425
+ y_data: Y coordinates.
426
+ mask: Mask with regions.
427
+ num_violations: Number of violations found.
428
+
429
+ Returns:
430
+ Minimum distance to mask boundary, or None if violations exist or no regions.
431
+ """
432
+ if num_violations > 0 or not mask.regions:
433
+ return None
434
+
435
+ # Find minimum distance to any violation region edge
436
+ min_dist = float("inf")
437
+ for region in mask.regions:
438
+ if region.region_type == "violation":
439
+ for x, y in zip(x_data, y_data, strict=False):
440
+ for i in range(len(region.vertices)):
441
+ x1, y1 = region.vertices[i]
442
+ x2, y2 = region.vertices[(i + 1) % len(region.vertices)]
443
+ dist = _point_to_segment_distance(x, y, x1, y1, x2, y2)
444
+ min_dist = min(min_dist, dist)
445
+
446
+ return min_dist if min_dist != float("inf") else None
341
447
 
342
448
 
343
449
  def _point_to_segment_distance(
@@ -6,7 +6,7 @@ discovery.comparison module to maintain API compatibility.
6
6
 
7
7
 
8
8
  Example:
9
- >>> from oscura.comparison import compare_traces
9
+ >>> from oscura.utils.comparison import compare_traces
10
10
  >>> diff = compare_traces(trace1, trace2)
11
11
  >>> for d in diff.differences:
12
12
  ... print(f"{d.category}: {d.description}")
@@ -5,7 +5,7 @@ overlay plots, difference plots, and comparison heat maps.
5
5
 
6
6
 
7
7
  Example:
8
- >>> from oscura.comparison.visualization import (
8
+ >>> from oscura.utils.comparison.visualization import (
9
9
  ... plot_overlay,
10
10
  ... plot_difference,
11
11
  ... plot_comparison_heatmap
@@ -24,12 +24,14 @@ from typing import TYPE_CHECKING, Any
24
24
  import matplotlib.pyplot as plt
25
25
  import numpy as np
26
26
  from matplotlib.gridspec import GridSpec
27
+ from numpy.typing import NDArray
27
28
 
28
29
  if TYPE_CHECKING:
30
+ from matplotlib.axes import Axes
29
31
  from matplotlib.figure import Figure
30
32
 
31
- from oscura.comparison.compare import ComparisonResult
32
33
  from oscura.core.types import WaveformTrace
34
+ from oscura.utils.comparison.compare import ComparisonResult
33
35
 
34
36
 
35
37
  def plot_overlay(
@@ -61,7 +63,7 @@ def plot_overlay(
61
63
  Matplotlib Figure object
62
64
 
63
65
  Example:
64
- >>> from oscura.comparison.visualization import plot_overlay
66
+ >>> from oscura.utils.comparison.visualization import plot_overlay
65
67
  >>> fig = plot_overlay(measured, reference,
66
68
  ... labels=("Measured", "Reference"))
67
69
  >>> plt.show()
@@ -160,7 +162,7 @@ def plot_difference(
160
162
  Matplotlib Figure object
161
163
 
162
164
  Example:
163
- >>> from oscura.comparison.visualization import plot_difference
165
+ >>> from oscura.utils.comparison.visualization import plot_difference
164
166
  >>> fig = plot_difference(measured, reference, normalize=True)
165
167
  >>> plt.show()
166
168
 
@@ -259,89 +261,111 @@ def plot_comparison_heatmap(
259
261
  Matplotlib Figure object
260
262
 
261
263
  Example:
262
- >>> from oscura.comparison.visualization import plot_comparison_heatmap
264
+ >>> from oscura.utils.comparison.visualization import plot_comparison_heatmap
263
265
  >>> fig = plot_comparison_heatmap(trace1, trace2, window_size=50)
264
266
  >>> plt.show()
265
267
 
266
268
  References:
267
269
  CMP-003: Difference heat map showing where changes occur
268
270
  """
271
+ fig, (ax_heat, ax_trace) = _create_heatmap_axes(figsize)
272
+
273
+ # Align data and compute difference
274
+ data1, data2, diff, min_len = _prepare_diff_data(trace1, trace2)
275
+
276
+ # Create and plot heatmap
277
+ n_windows, window_size = _compute_window_params(min_len, window_size)
278
+ heatmap_data = _build_heatmap(data1, data2, diff, n_windows, window_size, min_len)
279
+ _plot_heatmap(ax_heat, heatmap_data, title, **kwargs)
280
+
281
+ # Plot difference trace
282
+ time, xlabel = _compute_time_axis(trace1, min_len)
283
+ _plot_diff_trace(ax_trace, time, diff, xlabel)
284
+
285
+ plt.tight_layout()
286
+ return fig
287
+
288
+
289
+ def _create_heatmap_axes(figsize: tuple[float, float]) -> tuple[Figure, tuple[Axes, Axes]]:
290
+ """Create figure with heatmap and trace axes."""
269
291
  fig = plt.figure(figsize=figsize)
270
292
  gs = GridSpec(2, 1, height_ratios=[3, 1], hspace=0.3)
271
293
  ax_heat = fig.add_subplot(gs[0])
272
294
  ax_trace = fig.add_subplot(gs[1], sharex=ax_heat)
295
+ return fig, (ax_heat, ax_trace)
273
296
 
274
- # Get data
297
+
298
+ def _prepare_diff_data(
299
+ trace1: WaveformTrace, trace2: WaveformTrace
300
+ ) -> tuple[NDArray[np.float64], NDArray[np.float64], NDArray[np.float64], int]:
301
+ """Prepare aligned data and compute difference."""
275
302
  data1 = trace1.data.astype(np.float64)
276
303
  data2 = trace2.data.astype(np.float64)
277
-
278
- # Align lengths
279
304
  min_len = min(len(data1), len(data2))
280
- data1 = data1[:min_len]
281
- data2 = data2[:min_len]
282
-
283
- # Compute difference
305
+ data1, data2 = data1[:min_len], data2[:min_len]
284
306
  diff = np.abs(data1 - data2)
307
+ return data1, data2, diff, min_len
308
+
285
309
 
286
- # Create windowed heatmap
310
+ def _compute_window_params(min_len: int, window_size: int) -> tuple[int, int]:
311
+ """Compute window parameters for heatmap."""
287
312
  n_windows = min_len // window_size
288
313
  if n_windows == 0:
289
- n_windows = 1
290
- window_size = min_len
291
-
314
+ return 1, min_len
315
+ return n_windows, window_size
316
+
317
+
318
+ def _build_heatmap(
319
+ data1: NDArray[np.float64],
320
+ data2: NDArray[np.float64],
321
+ diff: NDArray[np.float64],
322
+ n_windows: int,
323
+ window_size: int,
324
+ min_len: int,
325
+ ) -> NDArray[np.float64]:
326
+ """Build heatmap data from windowed differences."""
292
327
  heatmap_data = np.zeros((10, n_windows))
293
328
  for i in range(n_windows):
294
329
  start = i * window_size
295
330
  end = min(start + window_size, min_len)
296
- window_diff = diff[start:end]
297
-
298
- # Bin into 10 levels based on amplitude
299
- window_data1 = data1[start:end]
300
- window_data2 = data2[start:end]
301
- y_min = min(np.min(window_data1), np.min(window_data2))
302
- y_max = max(np.max(window_data1), np.max(window_data2))
303
-
331
+ window_data1, window_diff = data1[start:end], diff[start:end]
332
+ y_min, y_max = (
333
+ min(np.min(data1[start:end]), np.min(data2[start:end])),
334
+ max(np.max(data1[start:end]), np.max(data2[start:end])),
335
+ )
304
336
  if y_max - y_min > 0:
305
337
  bins = np.linspace(y_min, y_max, 11)
306
- for sample_idx in range(len(window_diff)):
307
- y_val = window_data1[sample_idx] # window_data1 is already sliced
308
- bin_idx = np.digitize(y_val, bins) - 1
309
- bin_idx = max(0, min(9, bin_idx))
338
+ for sample_idx, y_val in enumerate(window_data1):
339
+ bin_idx = max(0, min(9, np.digitize(y_val, bins) - 1))
310
340
  heatmap_data[bin_idx, i] += window_diff[sample_idx]
341
+ return heatmap_data / window_size
342
+
311
343
 
312
- # Normalize heatmap
313
- heatmap_data = heatmap_data / window_size
314
-
315
- # Plot heatmap
316
- im = ax_heat.imshow(
317
- heatmap_data,
318
- aspect="auto",
319
- cmap="hot",
320
- origin="lower",
321
- interpolation="nearest",
322
- **kwargs,
344
+ def _plot_heatmap(ax: Axes, heatmap_data: NDArray[np.float64], title: str, **kwargs: Any) -> None:
345
+ """Plot heatmap on axes."""
346
+ im = ax.imshow(
347
+ heatmap_data, aspect="auto", cmap="hot", origin="lower", interpolation="nearest", **kwargs
323
348
  )
324
- plt.colorbar(im, ax=ax_heat, label="Average Difference")
349
+ plt.colorbar(im, ax=ax, label="Average Difference")
350
+ ax.set_ylabel("Amplitude Bin")
351
+ ax.set_title(title)
325
352
 
326
- ax_heat.set_ylabel("Amplitude Bin")
327
- ax_heat.set_title(title)
328
353
 
329
- # Plot difference trace below
330
- if hasattr(trace1, "metadata") and trace1.metadata.sample_rate is not None:
331
- sample_rate = trace1.metadata.sample_rate
332
- time = np.arange(min_len) / sample_rate
333
- xlabel = "Time (s)"
334
- else:
335
- time = np.arange(min_len, dtype=np.float64)
336
- xlabel = "Sample"
354
+ def _compute_time_axis(trace: WaveformTrace, min_len: int) -> tuple[NDArray[np.float64], str]:
355
+ """Compute time axis and label."""
356
+ if hasattr(trace, "metadata") and trace.metadata.sample_rate is not None:
357
+ return np.arange(min_len) / trace.metadata.sample_rate, "Time (s)"
358
+ return np.arange(min_len, dtype=np.float64), "Sample"
337
359
 
338
- ax_trace.plot(time, diff, linewidth=0.5)
339
- ax_trace.set_xlabel(xlabel)
340
- ax_trace.set_ylabel("Difference")
341
- ax_trace.grid(True, alpha=0.3)
342
360
 
343
- plt.tight_layout()
344
- return fig
361
+ def _plot_diff_trace(
362
+ ax: Axes, time: NDArray[np.float64], diff: NDArray[np.float64], xlabel: str
363
+ ) -> None:
364
+ """Plot difference trace on axes."""
365
+ ax.plot(time, diff, linewidth=0.5)
366
+ ax.set_xlabel(xlabel)
367
+ ax.set_ylabel("Difference")
368
+ ax.grid(True, alpha=0.3)
345
369
 
346
370
 
347
371
  def plot_comparison_summary(
@@ -363,8 +387,8 @@ def plot_comparison_summary(
363
387
  Matplotlib Figure object
364
388
 
365
389
  Example:
366
- >>> from oscura.comparison import compare_traces
367
- >>> from oscura.comparison.visualization import plot_comparison_summary
390
+ >>> from oscura.utils.comparison import compare_traces
391
+ >>> from oscura.utils.comparison.visualization import plot_comparison_summary
368
392
  >>> result = compare_traces(trace1, trace2)
369
393
  >>> fig = plot_comparison_summary(result)
370
394
  >>> plt.show()
@@ -375,7 +399,28 @@ def plot_comparison_summary(
375
399
  fig = plt.figure(figsize=figsize)
376
400
  gs = GridSpec(3, 2, hspace=0.4, wspace=0.3)
377
401
 
378
- # Statistics table
402
+ # Create statistics table
403
+ _plot_statistics_table(fig, gs, result, title)
404
+
405
+ # Plot difference trace
406
+ if result.difference_trace is not None:
407
+ _plot_difference_trace(fig, gs, result.difference_trace)
408
+ _plot_difference_histogram(fig, gs, result.difference_trace)
409
+
410
+ # Plot violation locations
411
+ _plot_violations(fig, gs, result)
412
+
413
+ plt.tight_layout()
414
+ return fig
415
+
416
+
417
+ def _plot_statistics_table(
418
+ fig: Figure,
419
+ gs: GridSpec,
420
+ result: ComparisonResult,
421
+ title: str,
422
+ ) -> None:
423
+ """Plot statistics table at top of summary."""
379
424
  ax_stats = fig.add_subplot(gs[0, :])
380
425
  ax_stats.axis("off")
381
426
 
@@ -415,34 +460,50 @@ def plot_comparison_summary(
415
460
 
416
461
  ax_stats.set_title(title, fontsize=14, fontweight="bold", pad=20)
417
462
 
418
- # Overlay plot
419
- if result.difference_trace is not None:
420
- # Plot difference trace
421
- ax_overlay = fig.add_subplot(gs[1, :])
422
- n_samples = len(result.difference_trace.data)
423
- time = np.arange(n_samples)
424
- ax_overlay.plot(time, result.difference_trace.data, label="Difference")
425
- ax_overlay.axhline(y=0, color="k", linestyle="--", alpha=0.5)
426
- ax_overlay.set_xlabel("Sample")
427
- ax_overlay.set_ylabel("Difference")
428
- ax_overlay.set_title("Difference Trace")
429
- ax_overlay.legend()
430
- ax_overlay.grid(True, alpha=0.3)
431
-
432
- # Histogram of differences
433
- if result.difference_trace is not None:
434
- ax_hist = fig.add_subplot(gs[2, 0])
435
- diff_data = result.difference_trace.data
436
- ax_hist.hist(diff_data, bins=50, edgecolor="black", alpha=0.7)
437
- ax_hist.axvline(x=0, color="r", linestyle="--", linewidth=2, label="Zero difference")
438
- ax_hist.set_xlabel("Difference")
439
- ax_hist.set_ylabel("Count")
440
- ax_hist.set_title("Difference Distribution")
441
- ax_hist.legend()
442
- ax_hist.grid(True, alpha=0.3)
443
-
444
- # Violation locations
463
+
464
+ def _plot_difference_trace(
465
+ fig: Figure,
466
+ gs: GridSpec,
467
+ difference_trace: Any,
468
+ ) -> None:
469
+ """Plot difference trace in middle row."""
470
+ ax_overlay = fig.add_subplot(gs[1, :])
471
+ n_samples = len(difference_trace.data)
472
+ time = np.arange(n_samples)
473
+ ax_overlay.plot(time, difference_trace.data, label="Difference")
474
+ ax_overlay.axhline(y=0, color="k", linestyle="--", alpha=0.5)
475
+ ax_overlay.set_xlabel("Sample")
476
+ ax_overlay.set_ylabel("Difference")
477
+ ax_overlay.set_title("Difference Trace")
478
+ ax_overlay.legend()
479
+ ax_overlay.grid(True, alpha=0.3)
480
+
481
+
482
+ def _plot_difference_histogram(
483
+ fig: Figure,
484
+ gs: GridSpec,
485
+ difference_trace: Any,
486
+ ) -> None:
487
+ """Plot histogram of differences."""
488
+ ax_hist = fig.add_subplot(gs[2, 0])
489
+ diff_data = difference_trace.data
490
+ ax_hist.hist(diff_data, bins=50, edgecolor="black", alpha=0.7)
491
+ ax_hist.axvline(x=0, color="r", linestyle="--", linewidth=2, label="Zero difference")
492
+ ax_hist.set_xlabel("Difference")
493
+ ax_hist.set_ylabel("Count")
494
+ ax_hist.set_title("Difference Distribution")
495
+ ax_hist.legend()
496
+ ax_hist.grid(True, alpha=0.3)
497
+
498
+
499
+ def _plot_violations(
500
+ fig: Figure,
501
+ gs: GridSpec,
502
+ result: ComparisonResult,
503
+ ) -> None:
504
+ """Plot violation locations."""
445
505
  ax_viol = fig.add_subplot(gs[2, 1])
506
+
446
507
  if result.violations is not None and len(result.violations) > 0:
447
508
  ax_viol.scatter(
448
509
  result.violations,
@@ -469,9 +530,6 @@ def plot_comparison_summary(
469
530
  )
470
531
  ax_viol.axis("off")
471
532
 
472
- plt.tight_layout()
473
- return fig
474
-
475
533
 
476
534
  __all__ = [
477
535
  "plot_comparison_heatmap",
@@ -4,17 +4,17 @@ This module provides TDR-based impedance extraction, capacitance/inductance
4
4
  measurement, parasitic extraction, and transmission line analysis.
5
5
  """
6
6
 
7
- from oscura.component.impedance import (
7
+ from oscura.utils.component.impedance import (
8
8
  discontinuity_analysis,
9
9
  extract_impedance,
10
10
  impedance_profile,
11
11
  )
12
- from oscura.component.reactive import (
12
+ from oscura.utils.component.reactive import (
13
13
  extract_parasitics,
14
14
  measure_capacitance,
15
15
  measure_inductance,
16
16
  )
17
- from oscura.component.transmission_line import (
17
+ from oscura.utils.component.transmission_line import (
18
18
  characteristic_impedance,
19
19
  propagation_delay,
20
20
  transmission_line_analysis,