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
@@ -1,718 +0,0 @@
1
- """Advanced digital logic visualizations.
2
-
3
- State-of-the-art visualization tools for digital logic analysis including:
4
- - Logic analyzer-style timeline displays
5
- - Multi-channel bus views with bus decoding
6
- - Timing diagram annotations
7
- - IC timing validation overlays
8
- - Eye diagrams for signal quality
9
-
10
- Example:
11
- >>> from oscura.visualization.digital_advanced import plot_logic_analyzer_view
12
- >>> plot_logic_analyzer_view(channels, title="8-bit Data Bus")
13
- """
14
-
15
- from __future__ import annotations
16
-
17
- from pathlib import Path
18
- from typing import TYPE_CHECKING, Any
19
-
20
- import matplotlib.pyplot as plt
21
- import numpy as np
22
-
23
- if TYPE_CHECKING:
24
- from matplotlib.axes import Axes
25
- from matplotlib.figure import Figure
26
- from numpy.typing import NDArray
27
-
28
- from oscura.core.types import DigitalTrace, WaveformTrace
29
-
30
-
31
- def plot_logic_analyzer_view(
32
- channels: dict[str, WaveformTrace | DigitalTrace],
33
- *,
34
- title: str | None = None,
35
- time_range: tuple[float, float] | None = None,
36
- group_buses: dict[str, list[str]] | None = None,
37
- show_hex: bool = True,
38
- show_cursors: bool = True,
39
- figsize: tuple[float, float] = (14, 8),
40
- ) -> tuple[Figure, Axes]:
41
- """Create logic analyzer-style timeline display.
42
-
43
- Args:
44
- channels: Dictionary mapping channel names to traces.
45
- title: Optional plot title.
46
- time_range: Optional (start, end) time range in seconds.
47
- group_buses: Optional dict mapping bus names to channel lists.
48
- show_hex: Show hexadecimal values for buses.
49
- show_cursors: Show timing cursors.
50
- figsize: Figure size in inches.
51
-
52
- Returns:
53
- Tuple of (figure, axes).
54
-
55
- Example:
56
- >>> channels = {f"D{i}": trace for i, trace in enumerate(data_lines)}
57
- >>> group_buses = {"DATA": [f"D{i}" for i in range(8)]}
58
- >>> fig, ax = plot_logic_analyzer_view(channels, group_buses=group_buses)
59
- """
60
- fig, ax = plt.subplots(figsize=figsize)
61
-
62
- # Determine time range
63
- first_trace = next(iter(channels.values()))
64
- time_base = first_trace.metadata.time_base
65
- total_time = len(first_trace.data) * time_base
66
-
67
- t_start: float
68
- t_end: float
69
- if time_range is None:
70
- t_start, t_end = 0.0, total_time
71
- else:
72
- t_start, t_end = time_range
73
-
74
- # Calculate display window
75
- start_idx = int(t_start / time_base)
76
- end_idx = int(t_end / time_base)
77
-
78
- # Create time array
79
- time_array = np.arange(start_idx, end_idx) * time_base
80
-
81
- # Plot channels from bottom to top
82
- y_offset = 0.0
83
- channel_positions: dict[str, float] = {}
84
-
85
- # Handle bus grouping
86
- if group_buses:
87
- for bus_name, bus_channels in group_buses.items():
88
- # Combine bus channels into values
89
- bus_values = _combine_bus_channels(
90
- {name: channels[name] for name in bus_channels}, start_idx, end_idx
91
- )
92
-
93
- # Plot bus as combined signal with hex labels
94
- _plot_bus_signal(ax, time_array, bus_values, y_offset, bus_name, show_hex=show_hex)
95
- channel_positions[bus_name] = y_offset
96
- y_offset += 1.5 # Extra spacing for buses
97
-
98
- # Remove individual channels from main list
99
- for ch_name in bus_channels:
100
- channels.pop(ch_name, None)
101
- else:
102
- group_buses = {}
103
-
104
- # Plot remaining individual channels
105
- for ch_name, trace in channels.items():
106
- # Extract data in window
107
- data = np.asarray(trace.data[start_idx:end_idx])
108
-
109
- # Convert to digital if needed
110
- if hasattr(data, "dtype") and data.dtype == bool:
111
- digital_data = data.astype(float)
112
- else:
113
- # Threshold analog signal
114
- threshold = (np.max(data) + np.min(data)) / 2
115
- digital_data = (data >= threshold).astype(float)
116
-
117
- # Plot digital waveform
118
- _plot_digital_waveform(ax, time_array, digital_data, y_offset, ch_name)
119
- channel_positions[ch_name] = y_offset
120
- y_offset += 1
121
-
122
- # Style the plot
123
- ax.set_xlim(t_start, t_end)
124
- ax.set_ylim(-0.5, y_offset + 0.5)
125
- ax.set_xlabel("Time (s)", fontsize=12, fontweight="bold")
126
- ax.set_ylabel("Channel", fontsize=12, fontweight="bold")
127
-
128
- if title:
129
- ax.set_title(title, fontsize=14, fontweight="bold")
130
-
131
- # Add grid
132
- ax.grid(True, which="both", alpha=0.3, linestyle="--")
133
- ax.set_yticks([])
134
-
135
- # Format time axis
136
- _format_time_axis(ax, t_start, t_end)
137
-
138
- # Add cursors if requested
139
- if show_cursors:
140
- _add_timing_cursors(ax, t_start, t_end, y_offset)
141
-
142
- plt.tight_layout()
143
- return fig, ax
144
-
145
-
146
- def plot_timing_diagram_with_annotations(
147
- signals: dict[str, WaveformTrace | DigitalTrace],
148
- *,
149
- timing_params: dict[str, tuple[float, float, str]] | None = None,
150
- title: str | None = None,
151
- reference_edges: dict[str, str] | None = None,
152
- figsize: tuple[float, float] = (12, 6),
153
- ) -> tuple[Figure, Axes]:
154
- """Plot timing diagram with measurement annotations.
155
-
156
- Args:
157
- signals: Dictionary mapping signal names to traces.
158
- timing_params: Dict of {name: (start_time, end_time, label)}.
159
- title: Optional plot title.
160
- reference_edges: Dict mapping signal names to edge types.
161
- figsize: Figure size.
162
-
163
- Returns:
164
- Tuple of (figure, axes).
165
-
166
- Example:
167
- >>> signals = {"CLK": clk, "DATA": data}
168
- >>> timing_params = {"t_su": (10e-9, 40e-9, "Setup = 30ns")}
169
- >>> fig, ax = plot_timing_diagram_with_annotations(signals, timing_params=timing_params)
170
- """
171
- fig, ax = plt.subplots(figsize=figsize)
172
-
173
- # Plot signals
174
- y_offset = 0
175
- signal_positions = {}
176
-
177
- for sig_name, trace in signals.items():
178
- # Get time array
179
- time_base = trace.metadata.time_base
180
- time_array = np.arange(len(trace.data)) * time_base
181
- data = np.asarray(trace.data)
182
-
183
- # Convert to digital
184
- if data.dtype == bool:
185
- digital_data = data.astype(float)
186
- else:
187
- threshold = (np.max(data) + np.min(data)) / 2
188
- digital_data = (data >= threshold).astype(float)
189
-
190
- # Offset for stacked view
191
- plot_data = digital_data + y_offset
192
-
193
- # Plot with nice edges
194
- ax.plot(time_array, plot_data, linewidth=2, color="royalblue")
195
- ax.fill_between(time_array, y_offset, plot_data, alpha=0.2, color="royalblue")
196
-
197
- # Add label
198
- ax.text(
199
- -time_array[-1] * 0.02,
200
- y_offset + 0.5,
201
- sig_name,
202
- ha="right",
203
- va="center",
204
- fontweight="bold",
205
- )
206
-
207
- signal_positions[sig_name] = y_offset
208
- y_offset += 2
209
-
210
- # Add timing annotations
211
- if timing_params:
212
- for t_start, t_end, label in timing_params.values():
213
- _add_timing_annotation(ax, t_start, t_end, y_offset - 1, label)
214
-
215
- # Style
216
- ax.set_ylim(-0.5, y_offset + 0.5)
217
- ax.set_xlabel("Time (s)", fontsize=12, fontweight="bold")
218
- if title:
219
- ax.set_title(title, fontsize=14, fontweight="bold")
220
-
221
- ax.grid(True, alpha=0.3, linestyle="--")
222
- ax.set_yticks([])
223
-
224
- plt.tight_layout()
225
- return fig, ax
226
-
227
-
228
- def plot_ic_timing_validation(
229
- signals: dict[str, WaveformTrace | DigitalTrace],
230
- ic_name: str,
231
- measured_timings: dict[str, float],
232
- *,
233
- figsize: tuple[float, float] = (14, 8),
234
- ) -> tuple[Figure, Axes]:
235
- """Plot timing diagram with IC specification overlay.
236
-
237
- Args:
238
- signals: Dictionary of signals.
239
- ic_name: IC part number (e.g., "74LS74").
240
- measured_timings: Measured timing parameters.
241
- figsize: Figure size.
242
-
243
- Returns:
244
- Tuple of (figure, axes).
245
-
246
- Example:
247
- >>> signals = {"CLK": clk, "D": data, "Q": output}
248
- >>> measured = {"t_su": 25e-9, "t_h": 5e-9, "t_pd": 40e-9}
249
- >>> fig, ax = plot_ic_timing_validation(signals, "74LS74", measured)
250
- """
251
- from oscura.analyzers.digital.ic_database import validate_ic_timing
252
-
253
- # Validate timings
254
- validation = validate_ic_timing(ic_name, measured_timings)
255
-
256
- # Plot timing diagram with custom figsize
257
- fig, ax = plot_timing_diagram_with_annotations(
258
- signals, title=f"{ic_name} Timing Validation", figsize=figsize
259
- )
260
-
261
- # Add validation results as text box
262
- results_text = _format_validation_results(validation)
263
- ax.text(
264
- 0.98,
265
- 0.98,
266
- results_text,
267
- transform=ax.transAxes,
268
- fontsize=10,
269
- verticalalignment="top",
270
- horizontalalignment="right",
271
- bbox={"boxstyle": "round", "facecolor": "wheat", "alpha": 0.8},
272
- family="monospace",
273
- )
274
-
275
- return fig, ax
276
-
277
-
278
- def plot_multi_ic_timing_path(
279
- ic_chain: list[tuple[str, dict[str, WaveformTrace | DigitalTrace]]],
280
- *,
281
- title: str = "Multi-IC Timing Path Analysis",
282
- figsize: tuple[float, float] = (16, 10),
283
- ) -> tuple[Figure, Axes]:
284
- """Plot timing analysis for cascaded ICs.
285
-
286
- Args:
287
- ic_chain: List of (ic_name, signals) tuples for each IC in chain.
288
- title: Plot title.
289
- figsize: Figure size.
290
-
291
- Returns:
292
- Tuple of (figure, axes).
293
-
294
- Example:
295
- >>> chain = [
296
- ... ("74LS74", {"CLK": clk1, "Q": q1}),
297
- ... ("74LS00", {"A": q1, "Y": y1}),
298
- ... ("74LS74", {"D": y1, "Q": q2}),
299
- ... ]
300
- >>> fig, ax = plot_multi_ic_timing_path(chain)
301
- """
302
- fig, ax = plt.subplots(figsize=figsize)
303
-
304
- y_offset = 0.0
305
-
306
- for ic_name, signals in ic_chain:
307
- # Plot this IC's signals
308
- for sig_name, trace in signals.items():
309
- time_base = trace.metadata.time_base
310
- time_array = np.arange(len(trace.data)) * time_base
311
- data = np.asarray(trace.data)
312
-
313
- # Convert to digital
314
- if data.dtype == bool:
315
- digital_data = data.astype(float)
316
- else:
317
- threshold = (np.max(data) + np.min(data)) / 2
318
- digital_data = (data >= threshold).astype(float)
319
-
320
- # Plot
321
- plot_data = digital_data + y_offset
322
- ax.plot(time_array, plot_data, linewidth=2, label=f"{ic_name}.{sig_name}")
323
-
324
- y_offset += 1.5
325
-
326
- # Add IC boundary
327
- ax.axhline(y_offset, color="gray", linestyle="--", alpha=0.5)
328
- y_offset += 0.5
329
-
330
- ax.set_xlabel("Time (s)", fontsize=12, fontweight="bold")
331
- ax.set_title(title, fontsize=14, fontweight="bold")
332
- ax.legend(loc="upper right")
333
- ax.grid(True, alpha=0.3)
334
- ax.set_yticks([])
335
-
336
- plt.tight_layout()
337
- return fig, ax
338
-
339
-
340
- def plot_bus_eye_diagram(
341
- bus_traces: list[WaveformTrace],
342
- *,
343
- symbol_period: float,
344
- num_symbols: int = 100,
345
- title: str | None = None,
346
- figsize: tuple[float, float] = (10, 6),
347
- ) -> tuple[Figure, Axes]:
348
- """Plot eye diagram for bus signal quality analysis.
349
-
350
- Args:
351
- bus_traces: List of traces (one per bit).
352
- symbol_period: Symbol period in seconds.
353
- num_symbols: Number of symbols to overlay.
354
- title: Optional plot title.
355
- figsize: Figure size.
356
-
357
- Returns:
358
- Tuple of (figure, axes).
359
- """
360
- fig, axes = plt.subplots(len(bus_traces), 1, figsize=figsize, sharex=True)
361
- if len(bus_traces) == 1:
362
- axes = [axes]
363
-
364
- for idx, trace in enumerate(bus_traces):
365
- ax = axes[idx]
366
-
367
- # Extract eye diagram data
368
- sample_rate = trace.metadata.sample_rate
369
- samples_per_symbol = int(symbol_period * sample_rate)
370
-
371
- data = np.asarray(trace.data)
372
-
373
- # Overlay symbols
374
- for i in range(num_symbols):
375
- start = i * samples_per_symbol
376
- end = start + samples_per_symbol * 2 # Two symbol periods
377
-
378
- if end > len(data):
379
- break
380
-
381
- segment = data[start:end]
382
- time_segment = np.arange(len(segment)) / sample_rate
383
-
384
- ax.plot(time_segment * 1e9, segment, alpha=0.1, color="blue")
385
-
386
- ax.set_ylabel(f"Bit {idx}", fontweight="bold")
387
- ax.grid(True, alpha=0.3)
388
-
389
- axes[-1].set_xlabel("Time (ns)", fontweight="bold")
390
-
391
- if title:
392
- fig.suptitle(title, fontsize=14, fontweight="bold")
393
-
394
- plt.tight_layout()
395
- return fig, axes
396
-
397
-
398
- # =============================================================================
399
- # Helper Functions
400
- # =============================================================================
401
-
402
-
403
- def _plot_digital_waveform(
404
- ax: Axes, time: NDArray[np.float64], data: NDArray[np.float64], y_offset: float, label: str
405
- ) -> None:
406
- """Plot a single digital waveform."""
407
- # Offset data
408
- plot_data = data + y_offset
409
-
410
- # Plot with thick lines
411
- ax.plot(time, plot_data, linewidth=2, color="royalblue", drawstyle="steps-post")
412
-
413
- # Fill area
414
- ax.fill_between(time, y_offset, plot_data, alpha=0.2, color="royalblue", step="post")
415
-
416
- # Add label
417
- ax.text(
418
- time[0] - (time[-1] - time[0]) * 0.02,
419
- y_offset + 0.5,
420
- label,
421
- ha="right",
422
- va="center",
423
- fontweight="bold",
424
- fontsize=10,
425
- )
426
-
427
-
428
- def _plot_bus_signal(
429
- ax: Axes,
430
- time: NDArray[np.float64],
431
- values: NDArray[np.uint32],
432
- y_offset: float,
433
- label: str,
434
- *,
435
- show_hex: bool = True,
436
- ) -> None:
437
- """Plot a bus signal with hex value labels."""
438
- # Plot as multi-level signal
439
- plot_data = values.astype(float) / np.max(values) if np.max(values) > 0 else values
440
- plot_data = plot_data + y_offset
441
-
442
- ax.plot(time, plot_data, linewidth=2, color="green", drawstyle="steps-post")
443
- ax.fill_between(time, y_offset, plot_data, alpha=0.2, color="green", step="post")
444
-
445
- # Add bus label
446
- ax.text(
447
- time[0] - (time[-1] - time[0]) * 0.02,
448
- y_offset + 0.5,
449
- label,
450
- ha="right",
451
- va="center",
452
- fontweight="bold",
453
- fontsize=10,
454
- color="green",
455
- )
456
-
457
- # Add hex values at transitions
458
- if show_hex:
459
- transitions = np.where(np.diff(values) != 0)[0]
460
- for trans_idx in transitions[:10]: # Limit to first 10 transitions
461
- value = values[trans_idx + 1]
462
- ax.text(
463
- time[trans_idx + 1],
464
- y_offset + 0.5,
465
- f"0x{value:02X}",
466
- ha="left",
467
- va="bottom",
468
- fontsize=8,
469
- color="green",
470
- )
471
-
472
-
473
- def _combine_bus_channels(
474
- bus_channels: dict[str, WaveformTrace | DigitalTrace], start_idx: int, end_idx: int
475
- ) -> NDArray[np.uint32]:
476
- """Combine individual bus lines into values."""
477
- # Sort channels by name (assume D0, D1, D2, etc.)
478
- sorted_channels = sorted(bus_channels.items(), key=lambda x: x[0])
479
-
480
- # Initialize result
481
- sorted_channels[0][1]
482
- num_samples = end_idx - start_idx
483
- result = np.zeros(num_samples, dtype=np.uint32)
484
-
485
- # Combine bits
486
- for bit_idx, (_ch_name, trace) in enumerate(sorted_channels):
487
- data = np.asarray(trace.data[start_idx:end_idx])
488
-
489
- # Convert to digital
490
- if data.dtype == bool:
491
- digital_data = data.astype(np.uint32)
492
- else:
493
- threshold = (np.max(data) + np.min(data)) / 2
494
- digital_data = (data >= threshold).astype(np.uint32)
495
-
496
- result |= (digital_data << bit_idx).astype(np.uint32)
497
-
498
- return result
499
-
500
-
501
- def _format_time_axis(ax: Axes, t_start: float, t_end: float) -> None:
502
- """Format time axis with appropriate units."""
503
- duration = t_end - t_start
504
-
505
- # Get current tick locations
506
- ticks = ax.get_xticks()
507
-
508
- if duration < 1e-6: # Nanoseconds
509
- ax.set_xlabel("Time (ns)", fontweight="bold")
510
- ax.set_xticks(ticks) # Set ticks before labels
511
- ax.set_xticklabels([f"{t * 1e9:.1f}" for t in ticks])
512
- elif duration < 1e-3: # Microseconds
513
- ax.set_xlabel("Time (μs)", fontweight="bold")
514
- ax.set_xticks(ticks) # Set ticks before labels
515
- ax.set_xticklabels([f"{t * 1e6:.1f}" for t in ticks])
516
- elif duration < 1: # Milliseconds
517
- ax.set_xlabel("Time (ms)", fontweight="bold")
518
- ax.set_xticks(ticks) # Set ticks before labels
519
- ax.set_xticklabels([f"{t * 1e3:.1f}" for t in ticks])
520
- else: # Seconds
521
- ax.set_xlabel("Time (s)", fontweight="bold")
522
-
523
-
524
- def _add_timing_cursors(ax: Axes, t_start: float, t_end: float, y_max: float) -> None:
525
- """Add timing measurement cursors."""
526
- # Add two cursors at 1/4 and 3/4 of time range
527
- cursor1_time = t_start + (t_end - t_start) * 0.25
528
- cursor2_time = t_start + (t_end - t_start) * 0.75
529
-
530
- ax.axvline(cursor1_time, color="red", linestyle="--", alpha=0.7, linewidth=1.5)
531
- ax.axvline(cursor2_time, color="red", linestyle="--", alpha=0.7, linewidth=1.5)
532
-
533
- # Add delta time label
534
- delta_t = cursor2_time - cursor1_time
535
- mid_y = y_max / 2
536
-
537
- ax.annotate(
538
- "",
539
- xy=(cursor2_time, mid_y),
540
- xytext=(cursor1_time, mid_y),
541
- arrowprops={"arrowstyle": "<->", "color": "red", "lw": 2},
542
- )
543
-
544
- ax.text(
545
- (cursor1_time + cursor2_time) / 2,
546
- mid_y + 0.3,
547
- f"Δt = {delta_t * 1e9:.1f} ns",
548
- ha="center",
549
- fontweight="bold",
550
- bbox={"boxstyle": "round", "facecolor": "yellow", "alpha": 0.8},
551
- )
552
-
553
-
554
- def _add_timing_annotation(
555
- ax: Axes, t_start: float, t_end: float, y_pos: float, label: str
556
- ) -> None:
557
- """Add timing measurement annotation."""
558
- ax.annotate(
559
- "",
560
- xy=(t_end, y_pos),
561
- xytext=(t_start, y_pos),
562
- arrowprops={"arrowstyle": "<->", "color": "red", "lw": 2},
563
- )
564
-
565
- ax.text(
566
- (t_start + t_end) / 2,
567
- y_pos + 0.3,
568
- label,
569
- ha="center",
570
- fontweight="bold",
571
- bbox={"boxstyle": "round", "facecolor": "yellow", "alpha": 0.8},
572
- )
573
-
574
-
575
- def _format_validation_results(validation: dict[str, dict[str, Any]]) -> str:
576
- """Format IC timing validation results."""
577
- lines = ["Timing Validation:\n"]
578
-
579
- for param, result in validation.items():
580
- passes = result.get("passes")
581
- if passes is None:
582
- continue
583
-
584
- measured = result["measured"]
585
- spec = result["spec"]
586
- error = result["error"]
587
-
588
- status = "✓ PASS" if passes else "✗ FAIL"
589
- lines.append(
590
- f"{param}: {status}\n Measured: {measured * 1e9:.1f}ns\n Spec: {spec * 1e9:.1f}ns\n Error: {error * 100:.1f}%\n"
591
- )
592
-
593
- return "".join(lines)
594
-
595
-
596
- def generate_all_vintage_logic_plots(
597
- result: Any,
598
- traces: dict[str, WaveformTrace | DigitalTrace],
599
- *,
600
- output_dir: str | Path | None = None,
601
- save_formats: list[str] | None = None,
602
- ) -> dict[str, tuple[Figure, Axes] | Figure]:
603
- """Generate complete visualization suite for vintage logic analysis.
604
-
605
- Creates all relevant plots based on analysis results. Optionally saves
606
- figures to disk in multiple formats.
607
-
608
- Args:
609
- result: VintageLogicAnalysisResult object.
610
- traces: Dictionary of channel names to traces.
611
- output_dir: If provided, saves all figures to this directory.
612
- save_formats: Formats to save ("png", "svg", "pdf"). Default: ["png"].
613
-
614
- Returns:
615
- Dictionary mapping plot names to Figure/Axes tuples.
616
-
617
- Example:
618
- >>> from oscura.analyzers.digital.vintage import analyze_vintage_logic
619
- >>> result = analyze_vintage_logic(traces)
620
- >>> plots = generate_all_vintage_logic_plots(result, traces, output_dir="./plots")
621
- >>> # plots = {
622
- >>> # "logic_analyzer": (fig, ax),
623
- >>> # "timing_validation": (fig, ax),
624
- >>> # ...
625
- >>> # }
626
- """
627
- from oscura.visualization.figure_manager import FigureManager
628
-
629
- plots: dict[str, tuple[Figure, Axes] | Figure] = {}
630
-
631
- # Initialize figure manager if output directory provided
632
- if output_dir:
633
- output_path = Path(output_dir)
634
- output_path.mkdir(parents=True, exist_ok=True)
635
- fig_manager = FigureManager(output_path)
636
- if save_formats is None:
637
- save_formats = ["png"]
638
- else:
639
- fig_manager = None
640
- save_formats = []
641
-
642
- # 1. Logic analyzer view
643
- try:
644
- fig, ax = plot_logic_analyzer_view(
645
- traces,
646
- title=f"Logic Analyzer View - {result.detected_family}",
647
- show_cursors=True,
648
- )
649
- plots["logic_analyzer"] = (fig, ax)
650
- if fig_manager:
651
- fig_manager.save_figure(fig, "logic_analyzer", formats=save_formats)
652
- except Exception:
653
- pass # Skip if plot fails
654
-
655
- # 2. IC timing validation plots for each identified IC
656
- for idx, ic_result in enumerate(result.identified_ics):
657
- try:
658
- # Create signals dictionary for timing validation
659
- fig, ax = plot_ic_timing_validation(
660
- signals=traces,
661
- ic_name=ic_result.ic_name,
662
- measured_timings=ic_result.timing_params,
663
- )
664
- plot_name = f"timing_validation_{ic_result.ic_name}_{idx}"
665
- plots[plot_name] = (fig, ax)
666
- if fig_manager:
667
- fig_manager.save_figure(fig, plot_name, formats=save_formats)
668
- except Exception:
669
- pass # Skip if plot fails
670
-
671
- # 3. Multi-IC timing path visualization
672
- if result.timing_paths:
673
- for idx, path_result in enumerate(result.timing_paths):
674
- try:
675
- fig, ax = plot_multi_ic_timing_path(path_result)
676
- plot_name = f"timing_path_{idx}"
677
- plots[plot_name] = (fig, ax)
678
- if fig_manager:
679
- fig_manager.save_figure(fig, plot_name, formats=save_formats)
680
- except Exception:
681
- pass # Skip if plot fails
682
-
683
- # 4. Timing diagram with annotations (for first 2-3 channels)
684
- if len(traces) >= 2:
685
- try:
686
- # Select first 2-3 channels
687
- selected_traces = dict(list(traces.items())[: min(3, len(traces))])
688
-
689
- # Create timing annotations from measurements
690
- timing_params: dict[str, tuple[float, float, str]] = {}
691
- for key, value in result.timing_measurements.items():
692
- # Extract channel names from key like "CLK→DATA_t_pd"
693
- if "→" in key:
694
- label = key.split("_")[-1] # Get timing parameter
695
- timing_params[label] = (0.0, float(value), label)
696
-
697
- fig, ax = plot_timing_diagram_with_annotations(
698
- selected_traces,
699
- timing_params=timing_params or None,
700
- title="Timing Diagram with Measurements",
701
- )
702
- plots["timing_diagram"] = (fig, ax)
703
- if fig_manager:
704
- fig_manager.save_figure(fig, "timing_diagram", formats=save_formats)
705
- except Exception:
706
- pass # Skip if plot fails
707
-
708
- return plots
709
-
710
-
711
- __all__ = [
712
- "generate_all_vintage_logic_plots",
713
- "plot_bus_eye_diagram",
714
- "plot_ic_timing_validation",
715
- "plot_logic_analyzer_view",
716
- "plot_multi_ic_timing_path",
717
- "plot_timing_diagram_with_annotations",
718
- ]