oscura 0.0.1__py3-none-any.whl → 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (465) hide show
  1. oscura/__init__.py +813 -8
  2. oscura/__main__.py +392 -0
  3. oscura/analyzers/__init__.py +37 -0
  4. oscura/analyzers/digital/__init__.py +177 -0
  5. oscura/analyzers/digital/bus.py +691 -0
  6. oscura/analyzers/digital/clock.py +805 -0
  7. oscura/analyzers/digital/correlation.py +720 -0
  8. oscura/analyzers/digital/edges.py +632 -0
  9. oscura/analyzers/digital/extraction.py +413 -0
  10. oscura/analyzers/digital/quality.py +878 -0
  11. oscura/analyzers/digital/signal_quality.py +877 -0
  12. oscura/analyzers/digital/thresholds.py +708 -0
  13. oscura/analyzers/digital/timing.py +1104 -0
  14. oscura/analyzers/eye/__init__.py +46 -0
  15. oscura/analyzers/eye/diagram.py +434 -0
  16. oscura/analyzers/eye/metrics.py +555 -0
  17. oscura/analyzers/jitter/__init__.py +83 -0
  18. oscura/analyzers/jitter/ber.py +333 -0
  19. oscura/analyzers/jitter/decomposition.py +759 -0
  20. oscura/analyzers/jitter/measurements.py +413 -0
  21. oscura/analyzers/jitter/spectrum.py +220 -0
  22. oscura/analyzers/measurements.py +40 -0
  23. oscura/analyzers/packet/__init__.py +171 -0
  24. oscura/analyzers/packet/daq.py +1077 -0
  25. oscura/analyzers/packet/metrics.py +437 -0
  26. oscura/analyzers/packet/parser.py +327 -0
  27. oscura/analyzers/packet/payload.py +2156 -0
  28. oscura/analyzers/packet/payload_analysis.py +1312 -0
  29. oscura/analyzers/packet/payload_extraction.py +236 -0
  30. oscura/analyzers/packet/payload_patterns.py +670 -0
  31. oscura/analyzers/packet/stream.py +359 -0
  32. oscura/analyzers/patterns/__init__.py +266 -0
  33. oscura/analyzers/patterns/clustering.py +1036 -0
  34. oscura/analyzers/patterns/discovery.py +539 -0
  35. oscura/analyzers/patterns/learning.py +797 -0
  36. oscura/analyzers/patterns/matching.py +1091 -0
  37. oscura/analyzers/patterns/periodic.py +650 -0
  38. oscura/analyzers/patterns/sequences.py +767 -0
  39. oscura/analyzers/power/__init__.py +116 -0
  40. oscura/analyzers/power/ac_power.py +391 -0
  41. oscura/analyzers/power/basic.py +383 -0
  42. oscura/analyzers/power/conduction.py +314 -0
  43. oscura/analyzers/power/efficiency.py +297 -0
  44. oscura/analyzers/power/ripple.py +356 -0
  45. oscura/analyzers/power/soa.py +372 -0
  46. oscura/analyzers/power/switching.py +479 -0
  47. oscura/analyzers/protocol/__init__.py +150 -0
  48. oscura/analyzers/protocols/__init__.py +150 -0
  49. oscura/analyzers/protocols/base.py +500 -0
  50. oscura/analyzers/protocols/can.py +620 -0
  51. oscura/analyzers/protocols/can_fd.py +448 -0
  52. oscura/analyzers/protocols/flexray.py +405 -0
  53. oscura/analyzers/protocols/hdlc.py +399 -0
  54. oscura/analyzers/protocols/i2c.py +368 -0
  55. oscura/analyzers/protocols/i2s.py +296 -0
  56. oscura/analyzers/protocols/jtag.py +393 -0
  57. oscura/analyzers/protocols/lin.py +445 -0
  58. oscura/analyzers/protocols/manchester.py +333 -0
  59. oscura/analyzers/protocols/onewire.py +501 -0
  60. oscura/analyzers/protocols/spi.py +334 -0
  61. oscura/analyzers/protocols/swd.py +325 -0
  62. oscura/analyzers/protocols/uart.py +393 -0
  63. oscura/analyzers/protocols/usb.py +495 -0
  64. oscura/analyzers/signal_integrity/__init__.py +63 -0
  65. oscura/analyzers/signal_integrity/embedding.py +294 -0
  66. oscura/analyzers/signal_integrity/equalization.py +370 -0
  67. oscura/analyzers/signal_integrity/sparams.py +484 -0
  68. oscura/analyzers/spectral/__init__.py +53 -0
  69. oscura/analyzers/spectral/chunked.py +273 -0
  70. oscura/analyzers/spectral/chunked_fft.py +571 -0
  71. oscura/analyzers/spectral/chunked_wavelet.py +391 -0
  72. oscura/analyzers/spectral/fft.py +92 -0
  73. oscura/analyzers/statistical/__init__.py +250 -0
  74. oscura/analyzers/statistical/checksum.py +923 -0
  75. oscura/analyzers/statistical/chunked_corr.py +228 -0
  76. oscura/analyzers/statistical/classification.py +778 -0
  77. oscura/analyzers/statistical/entropy.py +1113 -0
  78. oscura/analyzers/statistical/ngrams.py +614 -0
  79. oscura/analyzers/statistics/__init__.py +119 -0
  80. oscura/analyzers/statistics/advanced.py +885 -0
  81. oscura/analyzers/statistics/basic.py +263 -0
  82. oscura/analyzers/statistics/correlation.py +630 -0
  83. oscura/analyzers/statistics/distribution.py +298 -0
  84. oscura/analyzers/statistics/outliers.py +463 -0
  85. oscura/analyzers/statistics/streaming.py +93 -0
  86. oscura/analyzers/statistics/trend.py +520 -0
  87. oscura/analyzers/validation.py +598 -0
  88. oscura/analyzers/waveform/__init__.py +36 -0
  89. oscura/analyzers/waveform/measurements.py +943 -0
  90. oscura/analyzers/waveform/measurements_with_uncertainty.py +371 -0
  91. oscura/analyzers/waveform/spectral.py +1689 -0
  92. oscura/analyzers/waveform/wavelets.py +298 -0
  93. oscura/api/__init__.py +62 -0
  94. oscura/api/dsl.py +538 -0
  95. oscura/api/fluent.py +571 -0
  96. oscura/api/operators.py +498 -0
  97. oscura/api/optimization.py +392 -0
  98. oscura/api/profiling.py +396 -0
  99. oscura/automotive/__init__.py +73 -0
  100. oscura/automotive/can/__init__.py +52 -0
  101. oscura/automotive/can/analysis.py +356 -0
  102. oscura/automotive/can/checksum.py +250 -0
  103. oscura/automotive/can/correlation.py +212 -0
  104. oscura/automotive/can/discovery.py +355 -0
  105. oscura/automotive/can/message_wrapper.py +375 -0
  106. oscura/automotive/can/models.py +385 -0
  107. oscura/automotive/can/patterns.py +381 -0
  108. oscura/automotive/can/session.py +452 -0
  109. oscura/automotive/can/state_machine.py +300 -0
  110. oscura/automotive/can/stimulus_response.py +461 -0
  111. oscura/automotive/dbc/__init__.py +15 -0
  112. oscura/automotive/dbc/generator.py +156 -0
  113. oscura/automotive/dbc/parser.py +146 -0
  114. oscura/automotive/dtc/__init__.py +30 -0
  115. oscura/automotive/dtc/database.py +3036 -0
  116. oscura/automotive/j1939/__init__.py +14 -0
  117. oscura/automotive/j1939/decoder.py +745 -0
  118. oscura/automotive/loaders/__init__.py +35 -0
  119. oscura/automotive/loaders/asc.py +98 -0
  120. oscura/automotive/loaders/blf.py +77 -0
  121. oscura/automotive/loaders/csv_can.py +136 -0
  122. oscura/automotive/loaders/dispatcher.py +136 -0
  123. oscura/automotive/loaders/mdf.py +331 -0
  124. oscura/automotive/loaders/pcap.py +132 -0
  125. oscura/automotive/obd/__init__.py +14 -0
  126. oscura/automotive/obd/decoder.py +707 -0
  127. oscura/automotive/uds/__init__.py +48 -0
  128. oscura/automotive/uds/decoder.py +265 -0
  129. oscura/automotive/uds/models.py +64 -0
  130. oscura/automotive/visualization.py +369 -0
  131. oscura/batch/__init__.py +55 -0
  132. oscura/batch/advanced.py +627 -0
  133. oscura/batch/aggregate.py +300 -0
  134. oscura/batch/analyze.py +139 -0
  135. oscura/batch/logging.py +487 -0
  136. oscura/batch/metrics.py +556 -0
  137. oscura/builders/__init__.py +41 -0
  138. oscura/builders/signal_builder.py +1131 -0
  139. oscura/cli/__init__.py +14 -0
  140. oscura/cli/batch.py +339 -0
  141. oscura/cli/characterize.py +273 -0
  142. oscura/cli/compare.py +775 -0
  143. oscura/cli/decode.py +551 -0
  144. oscura/cli/main.py +247 -0
  145. oscura/cli/shell.py +350 -0
  146. oscura/comparison/__init__.py +66 -0
  147. oscura/comparison/compare.py +397 -0
  148. oscura/comparison/golden.py +487 -0
  149. oscura/comparison/limits.py +391 -0
  150. oscura/comparison/mask.py +434 -0
  151. oscura/comparison/trace_diff.py +30 -0
  152. oscura/comparison/visualization.py +481 -0
  153. oscura/compliance/__init__.py +70 -0
  154. oscura/compliance/advanced.py +756 -0
  155. oscura/compliance/masks.py +363 -0
  156. oscura/compliance/reporting.py +483 -0
  157. oscura/compliance/testing.py +298 -0
  158. oscura/component/__init__.py +38 -0
  159. oscura/component/impedance.py +365 -0
  160. oscura/component/reactive.py +598 -0
  161. oscura/component/transmission_line.py +312 -0
  162. oscura/config/__init__.py +191 -0
  163. oscura/config/defaults.py +254 -0
  164. oscura/config/loader.py +348 -0
  165. oscura/config/memory.py +271 -0
  166. oscura/config/migration.py +458 -0
  167. oscura/config/pipeline.py +1077 -0
  168. oscura/config/preferences.py +530 -0
  169. oscura/config/protocol.py +875 -0
  170. oscura/config/schema.py +713 -0
  171. oscura/config/settings.py +420 -0
  172. oscura/config/thresholds.py +599 -0
  173. oscura/convenience.py +457 -0
  174. oscura/core/__init__.py +299 -0
  175. oscura/core/audit.py +457 -0
  176. oscura/core/backend_selector.py +405 -0
  177. oscura/core/cache.py +590 -0
  178. oscura/core/cancellation.py +439 -0
  179. oscura/core/confidence.py +225 -0
  180. oscura/core/config.py +506 -0
  181. oscura/core/correlation.py +216 -0
  182. oscura/core/cross_domain.py +422 -0
  183. oscura/core/debug.py +301 -0
  184. oscura/core/edge_cases.py +541 -0
  185. oscura/core/exceptions.py +535 -0
  186. oscura/core/gpu_backend.py +523 -0
  187. oscura/core/lazy.py +832 -0
  188. oscura/core/log_query.py +540 -0
  189. oscura/core/logging.py +931 -0
  190. oscura/core/logging_advanced.py +952 -0
  191. oscura/core/memoize.py +171 -0
  192. oscura/core/memory_check.py +274 -0
  193. oscura/core/memory_guard.py +290 -0
  194. oscura/core/memory_limits.py +336 -0
  195. oscura/core/memory_monitor.py +453 -0
  196. oscura/core/memory_progress.py +465 -0
  197. oscura/core/memory_warnings.py +315 -0
  198. oscura/core/numba_backend.py +362 -0
  199. oscura/core/performance.py +352 -0
  200. oscura/core/progress.py +524 -0
  201. oscura/core/provenance.py +358 -0
  202. oscura/core/results.py +331 -0
  203. oscura/core/types.py +504 -0
  204. oscura/core/uncertainty.py +383 -0
  205. oscura/discovery/__init__.py +52 -0
  206. oscura/discovery/anomaly_detector.py +672 -0
  207. oscura/discovery/auto_decoder.py +415 -0
  208. oscura/discovery/comparison.py +497 -0
  209. oscura/discovery/quality_validator.py +528 -0
  210. oscura/discovery/signal_detector.py +769 -0
  211. oscura/dsl/__init__.py +73 -0
  212. oscura/dsl/commands.py +246 -0
  213. oscura/dsl/interpreter.py +455 -0
  214. oscura/dsl/parser.py +689 -0
  215. oscura/dsl/repl.py +172 -0
  216. oscura/exceptions.py +59 -0
  217. oscura/exploratory/__init__.py +111 -0
  218. oscura/exploratory/error_recovery.py +642 -0
  219. oscura/exploratory/fuzzy.py +513 -0
  220. oscura/exploratory/fuzzy_advanced.py +786 -0
  221. oscura/exploratory/legacy.py +831 -0
  222. oscura/exploratory/parse.py +358 -0
  223. oscura/exploratory/recovery.py +275 -0
  224. oscura/exploratory/sync.py +382 -0
  225. oscura/exploratory/unknown.py +707 -0
  226. oscura/export/__init__.py +25 -0
  227. oscura/export/wireshark/README.md +265 -0
  228. oscura/export/wireshark/__init__.py +47 -0
  229. oscura/export/wireshark/generator.py +312 -0
  230. oscura/export/wireshark/lua_builder.py +159 -0
  231. oscura/export/wireshark/templates/dissector.lua.j2 +92 -0
  232. oscura/export/wireshark/type_mapping.py +165 -0
  233. oscura/export/wireshark/validator.py +105 -0
  234. oscura/exporters/__init__.py +94 -0
  235. oscura/exporters/csv.py +303 -0
  236. oscura/exporters/exporters.py +44 -0
  237. oscura/exporters/hdf5.py +219 -0
  238. oscura/exporters/html_export.py +701 -0
  239. oscura/exporters/json_export.py +291 -0
  240. oscura/exporters/markdown_export.py +367 -0
  241. oscura/exporters/matlab_export.py +354 -0
  242. oscura/exporters/npz_export.py +219 -0
  243. oscura/exporters/spice_export.py +210 -0
  244. oscura/extensibility/__init__.py +131 -0
  245. oscura/extensibility/docs.py +752 -0
  246. oscura/extensibility/extensions.py +1125 -0
  247. oscura/extensibility/logging.py +259 -0
  248. oscura/extensibility/measurements.py +485 -0
  249. oscura/extensibility/plugins.py +414 -0
  250. oscura/extensibility/registry.py +346 -0
  251. oscura/extensibility/templates.py +913 -0
  252. oscura/extensibility/validation.py +651 -0
  253. oscura/filtering/__init__.py +89 -0
  254. oscura/filtering/base.py +563 -0
  255. oscura/filtering/convenience.py +564 -0
  256. oscura/filtering/design.py +725 -0
  257. oscura/filtering/filters.py +32 -0
  258. oscura/filtering/introspection.py +605 -0
  259. oscura/guidance/__init__.py +24 -0
  260. oscura/guidance/recommender.py +429 -0
  261. oscura/guidance/wizard.py +518 -0
  262. oscura/inference/__init__.py +251 -0
  263. oscura/inference/active_learning/README.md +153 -0
  264. oscura/inference/active_learning/__init__.py +38 -0
  265. oscura/inference/active_learning/lstar.py +257 -0
  266. oscura/inference/active_learning/observation_table.py +230 -0
  267. oscura/inference/active_learning/oracle.py +78 -0
  268. oscura/inference/active_learning/teachers/__init__.py +15 -0
  269. oscura/inference/active_learning/teachers/simulator.py +192 -0
  270. oscura/inference/adaptive_tuning.py +453 -0
  271. oscura/inference/alignment.py +653 -0
  272. oscura/inference/bayesian.py +943 -0
  273. oscura/inference/binary.py +1016 -0
  274. oscura/inference/crc_reverse.py +711 -0
  275. oscura/inference/logic.py +288 -0
  276. oscura/inference/message_format.py +1305 -0
  277. oscura/inference/protocol.py +417 -0
  278. oscura/inference/protocol_dsl.py +1084 -0
  279. oscura/inference/protocol_library.py +1230 -0
  280. oscura/inference/sequences.py +809 -0
  281. oscura/inference/signal_intelligence.py +1509 -0
  282. oscura/inference/spectral.py +215 -0
  283. oscura/inference/state_machine.py +634 -0
  284. oscura/inference/stream.py +918 -0
  285. oscura/integrations/__init__.py +59 -0
  286. oscura/integrations/llm.py +1827 -0
  287. oscura/jupyter/__init__.py +32 -0
  288. oscura/jupyter/display.py +268 -0
  289. oscura/jupyter/magic.py +334 -0
  290. oscura/loaders/__init__.py +526 -0
  291. oscura/loaders/binary.py +69 -0
  292. oscura/loaders/configurable.py +1255 -0
  293. oscura/loaders/csv.py +26 -0
  294. oscura/loaders/csv_loader.py +473 -0
  295. oscura/loaders/hdf5.py +9 -0
  296. oscura/loaders/hdf5_loader.py +510 -0
  297. oscura/loaders/lazy.py +370 -0
  298. oscura/loaders/mmap_loader.py +583 -0
  299. oscura/loaders/numpy_loader.py +436 -0
  300. oscura/loaders/pcap.py +432 -0
  301. oscura/loaders/preprocessing.py +368 -0
  302. oscura/loaders/rigol.py +287 -0
  303. oscura/loaders/sigrok.py +321 -0
  304. oscura/loaders/tdms.py +367 -0
  305. oscura/loaders/tektronix.py +711 -0
  306. oscura/loaders/validation.py +584 -0
  307. oscura/loaders/vcd.py +464 -0
  308. oscura/loaders/wav.py +233 -0
  309. oscura/math/__init__.py +45 -0
  310. oscura/math/arithmetic.py +824 -0
  311. oscura/math/interpolation.py +413 -0
  312. oscura/onboarding/__init__.py +39 -0
  313. oscura/onboarding/help.py +498 -0
  314. oscura/onboarding/tutorials.py +405 -0
  315. oscura/onboarding/wizard.py +466 -0
  316. oscura/optimization/__init__.py +19 -0
  317. oscura/optimization/parallel.py +440 -0
  318. oscura/optimization/search.py +532 -0
  319. oscura/pipeline/__init__.py +43 -0
  320. oscura/pipeline/base.py +338 -0
  321. oscura/pipeline/composition.py +242 -0
  322. oscura/pipeline/parallel.py +448 -0
  323. oscura/pipeline/pipeline.py +375 -0
  324. oscura/pipeline/reverse_engineering.py +1119 -0
  325. oscura/plugins/__init__.py +122 -0
  326. oscura/plugins/base.py +272 -0
  327. oscura/plugins/cli.py +497 -0
  328. oscura/plugins/discovery.py +411 -0
  329. oscura/plugins/isolation.py +418 -0
  330. oscura/plugins/lifecycle.py +959 -0
  331. oscura/plugins/manager.py +493 -0
  332. oscura/plugins/registry.py +421 -0
  333. oscura/plugins/versioning.py +372 -0
  334. oscura/py.typed +0 -0
  335. oscura/quality/__init__.py +65 -0
  336. oscura/quality/ensemble.py +740 -0
  337. oscura/quality/explainer.py +338 -0
  338. oscura/quality/scoring.py +616 -0
  339. oscura/quality/warnings.py +456 -0
  340. oscura/reporting/__init__.py +248 -0
  341. oscura/reporting/advanced.py +1234 -0
  342. oscura/reporting/analyze.py +448 -0
  343. oscura/reporting/argument_preparer.py +596 -0
  344. oscura/reporting/auto_report.py +507 -0
  345. oscura/reporting/batch.py +615 -0
  346. oscura/reporting/chart_selection.py +223 -0
  347. oscura/reporting/comparison.py +330 -0
  348. oscura/reporting/config.py +615 -0
  349. oscura/reporting/content/__init__.py +39 -0
  350. oscura/reporting/content/executive.py +127 -0
  351. oscura/reporting/content/filtering.py +191 -0
  352. oscura/reporting/content/minimal.py +257 -0
  353. oscura/reporting/content/verbosity.py +162 -0
  354. oscura/reporting/core.py +508 -0
  355. oscura/reporting/core_formats/__init__.py +17 -0
  356. oscura/reporting/core_formats/multi_format.py +210 -0
  357. oscura/reporting/engine.py +836 -0
  358. oscura/reporting/export.py +366 -0
  359. oscura/reporting/formatting/__init__.py +129 -0
  360. oscura/reporting/formatting/emphasis.py +81 -0
  361. oscura/reporting/formatting/numbers.py +403 -0
  362. oscura/reporting/formatting/standards.py +55 -0
  363. oscura/reporting/formatting.py +466 -0
  364. oscura/reporting/html.py +578 -0
  365. oscura/reporting/index.py +590 -0
  366. oscura/reporting/multichannel.py +296 -0
  367. oscura/reporting/output.py +379 -0
  368. oscura/reporting/pdf.py +373 -0
  369. oscura/reporting/plots.py +731 -0
  370. oscura/reporting/pptx_export.py +360 -0
  371. oscura/reporting/renderers/__init__.py +11 -0
  372. oscura/reporting/renderers/pdf.py +94 -0
  373. oscura/reporting/sections.py +471 -0
  374. oscura/reporting/standards.py +680 -0
  375. oscura/reporting/summary_generator.py +368 -0
  376. oscura/reporting/tables.py +397 -0
  377. oscura/reporting/template_system.py +724 -0
  378. oscura/reporting/templates/__init__.py +15 -0
  379. oscura/reporting/templates/definition.py +205 -0
  380. oscura/reporting/templates/index.html +649 -0
  381. oscura/reporting/templates/index.md +173 -0
  382. oscura/schemas/__init__.py +158 -0
  383. oscura/schemas/bus_configuration.json +322 -0
  384. oscura/schemas/device_mapping.json +182 -0
  385. oscura/schemas/packet_format.json +418 -0
  386. oscura/schemas/protocol_definition.json +363 -0
  387. oscura/search/__init__.py +16 -0
  388. oscura/search/anomaly.py +292 -0
  389. oscura/search/context.py +149 -0
  390. oscura/search/pattern.py +160 -0
  391. oscura/session/__init__.py +34 -0
  392. oscura/session/annotations.py +289 -0
  393. oscura/session/history.py +313 -0
  394. oscura/session/session.py +445 -0
  395. oscura/streaming/__init__.py +43 -0
  396. oscura/streaming/chunked.py +611 -0
  397. oscura/streaming/progressive.py +393 -0
  398. oscura/streaming/realtime.py +622 -0
  399. oscura/testing/__init__.py +54 -0
  400. oscura/testing/synthetic.py +808 -0
  401. oscura/triggering/__init__.py +68 -0
  402. oscura/triggering/base.py +229 -0
  403. oscura/triggering/edge.py +353 -0
  404. oscura/triggering/pattern.py +344 -0
  405. oscura/triggering/pulse.py +581 -0
  406. oscura/triggering/window.py +453 -0
  407. oscura/ui/__init__.py +48 -0
  408. oscura/ui/formatters.py +526 -0
  409. oscura/ui/progressive_display.py +340 -0
  410. oscura/utils/__init__.py +99 -0
  411. oscura/utils/autodetect.py +338 -0
  412. oscura/utils/buffer.py +389 -0
  413. oscura/utils/lazy.py +407 -0
  414. oscura/utils/lazy_imports.py +147 -0
  415. oscura/utils/memory.py +836 -0
  416. oscura/utils/memory_advanced.py +1326 -0
  417. oscura/utils/memory_extensions.py +465 -0
  418. oscura/utils/progressive.py +352 -0
  419. oscura/utils/windowing.py +362 -0
  420. oscura/visualization/__init__.py +321 -0
  421. oscura/visualization/accessibility.py +526 -0
  422. oscura/visualization/annotations.py +374 -0
  423. oscura/visualization/axis_scaling.py +305 -0
  424. oscura/visualization/colors.py +453 -0
  425. oscura/visualization/digital.py +337 -0
  426. oscura/visualization/eye.py +420 -0
  427. oscura/visualization/histogram.py +281 -0
  428. oscura/visualization/interactive.py +858 -0
  429. oscura/visualization/jitter.py +702 -0
  430. oscura/visualization/keyboard.py +394 -0
  431. oscura/visualization/layout.py +365 -0
  432. oscura/visualization/optimization.py +1028 -0
  433. oscura/visualization/palettes.py +446 -0
  434. oscura/visualization/plot.py +92 -0
  435. oscura/visualization/power.py +290 -0
  436. oscura/visualization/power_extended.py +626 -0
  437. oscura/visualization/presets.py +467 -0
  438. oscura/visualization/protocols.py +932 -0
  439. oscura/visualization/render.py +207 -0
  440. oscura/visualization/rendering.py +444 -0
  441. oscura/visualization/reverse_engineering.py +791 -0
  442. oscura/visualization/signal_integrity.py +808 -0
  443. oscura/visualization/specialized.py +553 -0
  444. oscura/visualization/spectral.py +811 -0
  445. oscura/visualization/styles.py +381 -0
  446. oscura/visualization/thumbnails.py +311 -0
  447. oscura/visualization/time_axis.py +351 -0
  448. oscura/visualization/waveform.py +367 -0
  449. oscura/workflow/__init__.py +13 -0
  450. oscura/workflow/dag.py +377 -0
  451. oscura/workflows/__init__.py +58 -0
  452. oscura/workflows/compliance.py +280 -0
  453. oscura/workflows/digital.py +272 -0
  454. oscura/workflows/multi_trace.py +502 -0
  455. oscura/workflows/power.py +178 -0
  456. oscura/workflows/protocol.py +492 -0
  457. oscura/workflows/reverse_engineering.py +639 -0
  458. oscura/workflows/signal_integrity.py +227 -0
  459. oscura-0.1.0.dist-info/METADATA +300 -0
  460. oscura-0.1.0.dist-info/RECORD +463 -0
  461. oscura-0.1.0.dist-info/entry_points.txt +2 -0
  462. {oscura-0.0.1.dist-info → oscura-0.1.0.dist-info}/licenses/LICENSE +1 -1
  463. oscura-0.0.1.dist-info/METADATA +0 -63
  464. oscura-0.0.1.dist-info/RECORD +0 -5
  465. {oscura-0.0.1.dist-info → oscura-0.1.0.dist-info}/WHEEL +0 -0
@@ -0,0 +1,702 @@
1
+ """Jitter Analysis Visualization Functions.
2
+
3
+ This module provides visualization functions for jitter analysis including
4
+ TIE histograms, bathtub curves, DDJ/DCD plots, and jitter trend analysis.
5
+
6
+ Example:
7
+ >>> from oscura.visualization.jitter import plot_tie_histogram, plot_bathtub_full
8
+ >>> fig = plot_tie_histogram(tie_data)
9
+ >>> fig = plot_bathtub_full(bathtub_result)
10
+
11
+ References:
12
+ - IEEE 802.3: Jitter measurement specifications
13
+ - JEDEC JESD65B: High-Speed Interface Measurements
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ from pathlib import Path
19
+ from typing import TYPE_CHECKING, Any, cast
20
+
21
+ import numpy as np
22
+
23
+ try:
24
+ import matplotlib.pyplot as plt
25
+ from scipy.stats import norm
26
+
27
+ HAS_MATPLOTLIB = True
28
+ HAS_SCIPY = True
29
+ except ImportError:
30
+ HAS_MATPLOTLIB = False
31
+ HAS_SCIPY = False
32
+
33
+ if TYPE_CHECKING:
34
+ from matplotlib.axes import Axes
35
+ from matplotlib.figure import Figure
36
+ from numpy.typing import NDArray
37
+
38
+ __all__ = [
39
+ "plot_bathtub_full",
40
+ "plot_dcd",
41
+ "plot_ddj",
42
+ "plot_jitter_trend",
43
+ "plot_tie_histogram",
44
+ ]
45
+
46
+
47
+ def plot_tie_histogram(
48
+ tie_data: NDArray[np.floating[Any]],
49
+ *,
50
+ ax: Axes | None = None,
51
+ figsize: tuple[float, float] = (10, 6),
52
+ title: str | None = None,
53
+ time_unit: str = "auto",
54
+ bins: int | str = "auto",
55
+ show_gaussian_fit: bool = True,
56
+ show_statistics: bool = True,
57
+ show_rj_dj: bool = True,
58
+ show: bool = True,
59
+ save_path: str | Path | None = None,
60
+ ) -> Figure:
61
+ """Plot Time Interval Error (TIE) histogram with statistical analysis.
62
+
63
+ Creates a histogram of TIE values with optional Gaussian fit overlay
64
+ and RJ/DJ decomposition indicators.
65
+
66
+ Args:
67
+ tie_data: Array of TIE values in seconds.
68
+ ax: Matplotlib axes. If None, creates new figure.
69
+ figsize: Figure size in inches.
70
+ title: Plot title.
71
+ time_unit: Time unit ("s", "ms", "us", "ns", "ps", "auto").
72
+ bins: Number of bins or "auto" for automatic selection.
73
+ show_gaussian_fit: Overlay Gaussian fit for RJ estimation.
74
+ show_statistics: Show statistics box.
75
+ show_rj_dj: Show RJ/DJ separation indicators.
76
+ show: Display plot interactively.
77
+ save_path: Save plot to file.
78
+
79
+ Returns:
80
+ Matplotlib Figure object.
81
+
82
+ Example:
83
+ >>> tie = np.random.randn(10000) * 2e-12 # 2 ps RMS jitter
84
+ >>> fig = plot_tie_histogram(tie, time_unit="ps")
85
+ """
86
+ if not HAS_MATPLOTLIB:
87
+ raise ImportError("matplotlib is required for visualization")
88
+
89
+ # Create figure if needed
90
+ if ax is None:
91
+ fig, ax = plt.subplots(figsize=figsize)
92
+ else:
93
+ fig_temp = ax.get_figure()
94
+ if fig_temp is None:
95
+ raise ValueError("Axes must have an associated figure")
96
+ fig = cast("Figure", fig_temp)
97
+
98
+ # Select time unit
99
+ if time_unit == "auto":
100
+ max_tie = np.max(np.abs(tie_data))
101
+ if max_tie < 1e-12:
102
+ time_unit = "fs"
103
+ time_mult = 1e15
104
+ elif max_tie < 1e-9:
105
+ time_unit = "ps"
106
+ time_mult = 1e12
107
+ elif max_tie < 1e-6:
108
+ time_unit = "ns"
109
+ time_mult = 1e9
110
+ else:
111
+ time_unit = "us"
112
+ time_mult = 1e6
113
+ else:
114
+ time_mult = {
115
+ "s": 1,
116
+ "ms": 1e3,
117
+ "us": 1e6,
118
+ "ns": 1e9,
119
+ "ps": 1e12,
120
+ "fs": 1e15,
121
+ }.get(time_unit, 1e12)
122
+
123
+ tie_scaled = tie_data * time_mult
124
+
125
+ # Calculate statistics
126
+ mean_val = np.mean(tie_scaled)
127
+ std_val = np.std(tie_scaled)
128
+ pp_val = np.ptp(tie_scaled)
129
+ rms_val = np.sqrt(np.mean(tie_scaled**2))
130
+
131
+ # Plot histogram
132
+ counts, bin_edges, patches = ax.hist(
133
+ tie_scaled,
134
+ bins=bins,
135
+ density=True,
136
+ color="#3498DB",
137
+ alpha=0.7,
138
+ edgecolor="black",
139
+ linewidth=0.5,
140
+ )
141
+
142
+ # Gaussian fit overlay
143
+ if show_gaussian_fit and HAS_SCIPY:
144
+ x_fit = np.linspace(bin_edges[0], bin_edges[-1], 200)
145
+ y_fit = norm.pdf(x_fit, mean_val, std_val)
146
+ ax.plot(
147
+ x_fit, y_fit, "r-", linewidth=2, label=f"Gaussian Fit (sigma={std_val:.2f} {time_unit})"
148
+ )
149
+
150
+ # RJ/DJ indicators
151
+ if show_rj_dj:
152
+ # Mark ±3sigma region (RJ contribution)
153
+ ax.axvline(
154
+ mean_val - 3 * std_val, color="#E74C3C", linestyle="--", linewidth=1.5, alpha=0.7
155
+ )
156
+ ax.axvline(
157
+ mean_val + 3 * std_val, color="#E74C3C", linestyle="--", linewidth=1.5, alpha=0.7
158
+ )
159
+
160
+ # Shade RJ region
161
+ ax.axvspan(
162
+ mean_val - 3 * std_val,
163
+ mean_val + 3 * std_val,
164
+ alpha=0.1,
165
+ color="#E74C3C",
166
+ label="±3sigma (99.7% RJ)",
167
+ )
168
+
169
+ # Statistics box
170
+ if show_statistics:
171
+ stats_text = (
172
+ f"Mean: {mean_val:.2f} {time_unit}\n"
173
+ f"RMS: {rms_val:.2f} {time_unit}\n"
174
+ f"Std Dev: {std_val:.2f} {time_unit}\n"
175
+ f"Peak-Peak: {pp_val:.2f} {time_unit}"
176
+ )
177
+ ax.text(
178
+ 0.98,
179
+ 0.98,
180
+ stats_text,
181
+ transform=ax.transAxes,
182
+ fontsize=9,
183
+ verticalalignment="top",
184
+ horizontalalignment="right",
185
+ bbox={"boxstyle": "round", "facecolor": "wheat", "alpha": 0.9},
186
+ fontfamily="monospace",
187
+ )
188
+
189
+ # Labels
190
+ ax.set_xlabel(f"TIE ({time_unit})", fontsize=11)
191
+ ax.set_ylabel("Probability Density", fontsize=11)
192
+ ax.grid(True, alpha=0.3)
193
+ ax.legend(loc="upper left")
194
+
195
+ if title:
196
+ ax.set_title(title, fontsize=12, fontweight="bold")
197
+ else:
198
+ ax.set_title("Time Interval Error Distribution", fontsize=12, fontweight="bold")
199
+
200
+ fig.tight_layout()
201
+
202
+ if save_path is not None:
203
+ fig.savefig(save_path, dpi=300, bbox_inches="tight")
204
+
205
+ if show:
206
+ plt.show()
207
+
208
+ return fig
209
+
210
+
211
+ def plot_bathtub_full(
212
+ positions: NDArray[np.floating[Any]],
213
+ ber_left: NDArray[np.floating[Any]],
214
+ ber_right: NDArray[np.floating[Any]],
215
+ *,
216
+ ber_total: NDArray[np.floating[Any]] | None = None,
217
+ target_ber: float = 1e-12,
218
+ eye_opening: float | None = None,
219
+ ax: Axes | None = None,
220
+ figsize: tuple[float, float] = (10, 6),
221
+ title: str | None = None,
222
+ show_target: bool = True,
223
+ show_eye_opening: bool = True,
224
+ show: bool = True,
225
+ save_path: str | Path | None = None,
226
+ ) -> Figure:
227
+ """Plot full bathtub curve with left/right BER and eye opening.
228
+
229
+ Creates a bathtub curve showing bit error rate vs sampling position
230
+ within the unit interval, with target BER marker and eye opening
231
+ annotation.
232
+
233
+ Args:
234
+ positions: Sample positions in UI (0 to 1).
235
+ ber_left: Left-side BER values.
236
+ ber_right: Right-side BER values.
237
+ ber_total: Total BER values (optional, computed if not provided).
238
+ target_ber: Target BER for eye opening calculation.
239
+ eye_opening: Pre-calculated eye opening in UI (optional).
240
+ ax: Matplotlib axes.
241
+ figsize: Figure size.
242
+ title: Plot title.
243
+ show_target: Show target BER line.
244
+ show_eye_opening: Annotate eye opening.
245
+ show: Display plot.
246
+ save_path: Save path.
247
+
248
+ Returns:
249
+ Matplotlib Figure object.
250
+
251
+ Example:
252
+ >>> pos = np.linspace(0, 1, 100)
253
+ >>> ber_l = 0.5 * erfc((pos - 0) / 0.1 / np.sqrt(2))
254
+ >>> ber_r = 0.5 * erfc((1 - pos) / 0.1 / np.sqrt(2))
255
+ >>> fig = plot_bathtub_full(pos, ber_l, ber_r, target_ber=1e-12)
256
+ """
257
+ if not HAS_MATPLOTLIB:
258
+ raise ImportError("matplotlib is required for visualization")
259
+
260
+ if ax is None:
261
+ fig, ax = plt.subplots(figsize=figsize)
262
+ else:
263
+ fig_temp = ax.get_figure()
264
+ if fig_temp is None:
265
+ raise ValueError("Axes must have an associated figure")
266
+ fig = cast("Figure", fig_temp)
267
+
268
+ # Compute total BER if not provided
269
+ if ber_total is None:
270
+ ber_total = ber_left + ber_right
271
+
272
+ # Clip very small values for log plot
273
+ ber_left_plot = np.clip(ber_left, 1e-18, 1)
274
+ ber_right_plot = np.clip(ber_right, 1e-18, 1)
275
+ ber_total_plot = np.clip(ber_total, 1e-18, 1)
276
+
277
+ # Plot BER curves
278
+ ax.semilogy(positions, ber_left_plot, "b-", linewidth=2, label="BER Left", alpha=0.8)
279
+ ax.semilogy(positions, ber_right_plot, "r-", linewidth=2, label="BER Right", alpha=0.8)
280
+ ax.semilogy(positions, ber_total_plot, "k-", linewidth=2.5, label="BER Total")
281
+
282
+ # Target BER line
283
+ if show_target:
284
+ ax.axhline(
285
+ target_ber,
286
+ color="#27AE60",
287
+ linestyle="--",
288
+ linewidth=2,
289
+ label=f"Target BER = {target_ber:.0e}",
290
+ )
291
+
292
+ # Eye opening annotation
293
+ if show_eye_opening:
294
+ # Find eye opening at target BER
295
+ if eye_opening is None:
296
+ # Find crossover points
297
+ left_cross = np.where(ber_total_plot < target_ber)[0]
298
+ if len(left_cross) > 0:
299
+ left_edge = positions[left_cross[0]]
300
+ right_edge = positions[left_cross[-1]]
301
+ eye_opening = right_edge - left_edge
302
+ else:
303
+ eye_opening = 0
304
+
305
+ if eye_opening > 0:
306
+ # Draw eye opening bracket
307
+ center = 0.5
308
+ left_edge = center - eye_opening / 2
309
+ right_edge = center + eye_opening / 2
310
+
311
+ ax.annotate(
312
+ "",
313
+ xy=(right_edge, target_ber),
314
+ xytext=(left_edge, target_ber),
315
+ arrowprops={"arrowstyle": "<->", "color": "#27AE60", "lw": 2},
316
+ )
317
+ ax.text(
318
+ center,
319
+ target_ber * 0.1,
320
+ f"Eye Opening: {eye_opening:.3f} UI",
321
+ ha="center",
322
+ va="top",
323
+ fontsize=10,
324
+ fontweight="bold",
325
+ color="#27AE60",
326
+ )
327
+
328
+ # Shading for bathtub
329
+ ax.fill_between(positions, 1e-18, ber_total_plot, alpha=0.1, color="gray")
330
+
331
+ # Labels
332
+ ax.set_xlabel("Sample Position (UI)", fontsize=11)
333
+ ax.set_ylabel("Bit Error Rate", fontsize=11)
334
+ ax.set_xlim(0, 1)
335
+ ax.set_ylim(1e-15, 1)
336
+ ax.grid(True, which="both", alpha=0.3)
337
+ ax.legend(loc="upper right")
338
+
339
+ if title:
340
+ ax.set_title(title, fontsize=12, fontweight="bold")
341
+ else:
342
+ ax.set_title("Bathtub Curve", fontsize=12, fontweight="bold")
343
+
344
+ fig.tight_layout()
345
+
346
+ if save_path is not None:
347
+ fig.savefig(save_path, dpi=300, bbox_inches="tight")
348
+
349
+ if show:
350
+ plt.show()
351
+
352
+ return fig
353
+
354
+
355
+ def plot_ddj(
356
+ patterns: list[str],
357
+ jitter_values: NDArray[np.floating[Any]],
358
+ *,
359
+ ax: Axes | None = None,
360
+ figsize: tuple[float, float] = (12, 6),
361
+ title: str | None = None,
362
+ time_unit: str = "ps",
363
+ show: bool = True,
364
+ save_path: str | Path | None = None,
365
+ ) -> Figure:
366
+ """Plot Data-Dependent Jitter (DDJ) by bit pattern.
367
+
368
+ Creates a bar chart showing jitter contribution for each bit pattern,
369
+ useful for identifying pattern-dependent timing variations.
370
+
371
+ Args:
372
+ patterns: List of bit pattern strings (e.g., ["010", "011", "100"]).
373
+ jitter_values: Jitter values for each pattern.
374
+ ax: Matplotlib axes.
375
+ figsize: Figure size.
376
+ title: Plot title.
377
+ time_unit: Time unit for display.
378
+ show: Display plot.
379
+ save_path: Save path.
380
+
381
+ Returns:
382
+ Matplotlib Figure object.
383
+
384
+ Example:
385
+ >>> patterns = ["000", "001", "010", "011", "100", "101", "110", "111"]
386
+ >>> ddj = np.array([0, 2.1, -1.5, 0.5, 0.8, -0.3, 1.2, -0.8]) # ps
387
+ >>> fig = plot_ddj(patterns, ddj, time_unit="ps")
388
+ """
389
+ if not HAS_MATPLOTLIB:
390
+ raise ImportError("matplotlib is required for visualization")
391
+
392
+ if ax is None:
393
+ fig, ax = plt.subplots(figsize=figsize)
394
+ else:
395
+ fig_temp = ax.get_figure()
396
+ if fig_temp is None:
397
+ raise ValueError("Axes must have an associated figure")
398
+ fig = cast("Figure", fig_temp)
399
+
400
+ # Color bars based on sign
401
+ colors = ["#E74C3C" if v < 0 else "#27AE60" for v in jitter_values]
402
+
403
+ # Bar chart
404
+ x_pos = np.arange(len(patterns))
405
+ ax.bar(x_pos, jitter_values, color=colors, edgecolor="black", linewidth=0.5)
406
+
407
+ # Reference line at zero
408
+ ax.axhline(0, color="gray", linestyle="-", linewidth=1)
409
+
410
+ # Labels
411
+ ax.set_xticks(x_pos)
412
+ ax.set_xticklabels(patterns, fontfamily="monospace", fontsize=10)
413
+ ax.set_xlabel("Bit Pattern", fontsize=11)
414
+ ax.set_ylabel(f"DDJ ({time_unit})", fontsize=11)
415
+ ax.grid(True, axis="y", alpha=0.3)
416
+
417
+ # Add DDJ pp annotation
418
+ ddj_pp = np.ptp(jitter_values)
419
+ ax.text(
420
+ 0.98,
421
+ 0.98,
422
+ f"DDJ pk-pk: {ddj_pp:.2f} {time_unit}",
423
+ transform=ax.transAxes,
424
+ fontsize=10,
425
+ ha="right",
426
+ va="top",
427
+ bbox={"boxstyle": "round", "facecolor": "wheat", "alpha": 0.9},
428
+ )
429
+
430
+ if title:
431
+ ax.set_title(title, fontsize=12, fontweight="bold")
432
+ else:
433
+ ax.set_title("Data-Dependent Jitter by Pattern", fontsize=12, fontweight="bold")
434
+
435
+ fig.tight_layout()
436
+
437
+ if save_path is not None:
438
+ fig.savefig(save_path, dpi=300, bbox_inches="tight")
439
+
440
+ if show:
441
+ plt.show()
442
+
443
+ return fig
444
+
445
+
446
+ def plot_dcd(
447
+ high_times: NDArray[np.floating[Any]],
448
+ low_times: NDArray[np.floating[Any]],
449
+ *,
450
+ ax: Axes | None = None,
451
+ figsize: tuple[float, float] = (10, 6),
452
+ title: str | None = None,
453
+ time_unit: str = "auto",
454
+ show: bool = True,
455
+ save_path: str | Path | None = None,
456
+ ) -> Figure:
457
+ """Plot Duty Cycle Distortion (DCD) analysis.
458
+
459
+ Creates overlaid histograms of high and low pulse times to visualize
460
+ duty cycle distortion.
461
+
462
+ Args:
463
+ high_times: Array of high-state durations.
464
+ low_times: Array of low-state durations.
465
+ ax: Matplotlib axes.
466
+ figsize: Figure size.
467
+ title: Plot title.
468
+ time_unit: Time unit.
469
+ show: Display plot.
470
+ save_path: Save path.
471
+
472
+ Returns:
473
+ Matplotlib Figure object.
474
+ """
475
+ if not HAS_MATPLOTLIB:
476
+ raise ImportError("matplotlib is required for visualization")
477
+
478
+ if ax is None:
479
+ fig, ax = plt.subplots(figsize=figsize)
480
+ else:
481
+ fig_temp = ax.get_figure()
482
+ if fig_temp is None:
483
+ raise ValueError("Axes must have an associated figure")
484
+ fig = cast("Figure", fig_temp)
485
+
486
+ # Select time unit
487
+ if time_unit == "auto":
488
+ max_time = max(np.max(high_times), np.max(low_times))
489
+ if max_time < 1e-9:
490
+ time_unit = "ps"
491
+ time_mult = 1e12
492
+ elif max_time < 1e-6:
493
+ time_unit = "ns"
494
+ time_mult = 1e9
495
+ else:
496
+ time_unit = "us"
497
+ time_mult = 1e6
498
+ else:
499
+ time_mult = {"s": 1, "ms": 1e3, "us": 1e6, "ns": 1e9, "ps": 1e12}.get(time_unit, 1e9)
500
+
501
+ high_scaled = high_times * time_mult
502
+ low_scaled = low_times * time_mult
503
+
504
+ # Calculate statistics
505
+ mean_high = np.mean(high_scaled)
506
+ mean_low = np.mean(low_scaled)
507
+ period = mean_high + mean_low
508
+ duty_cycle = mean_high / period * 100
509
+ dcd = (mean_high - mean_low) / 2
510
+
511
+ # Determine common bins
512
+ all_times = np.concatenate([high_scaled, low_scaled])
513
+ bins = np.linspace(np.min(all_times) * 0.95, np.max(all_times) * 1.05, 50)
514
+
515
+ # Plot histograms
516
+ ax.hist(
517
+ high_scaled,
518
+ bins=bins,
519
+ alpha=0.6,
520
+ color="#E74C3C",
521
+ label="High Time",
522
+ edgecolor="black",
523
+ linewidth=0.5,
524
+ )
525
+ ax.hist(
526
+ low_scaled,
527
+ bins=bins,
528
+ alpha=0.6,
529
+ color="#3498DB",
530
+ label="Low Time",
531
+ edgecolor="black",
532
+ linewidth=0.5,
533
+ )
534
+
535
+ # Mean lines
536
+ ax.axvline(mean_high, color="#E74C3C", linestyle="--", linewidth=2, alpha=0.8)
537
+ ax.axvline(mean_low, color="#3498DB", linestyle="--", linewidth=2, alpha=0.8)
538
+
539
+ # Statistics box
540
+ stats_text = (
541
+ f"Mean High: {mean_high:.2f} {time_unit}\n"
542
+ f"Mean Low: {mean_low:.2f} {time_unit}\n"
543
+ f"Duty Cycle: {duty_cycle:.1f}%\n"
544
+ f"DCD: {dcd:.2f} {time_unit}"
545
+ )
546
+ ax.text(
547
+ 0.98,
548
+ 0.98,
549
+ stats_text,
550
+ transform=ax.transAxes,
551
+ fontsize=9,
552
+ va="top",
553
+ ha="right",
554
+ bbox={"boxstyle": "round", "facecolor": "wheat", "alpha": 0.9},
555
+ fontfamily="monospace",
556
+ )
557
+
558
+ ax.set_xlabel(f"Pulse Width ({time_unit})", fontsize=11)
559
+ ax.set_ylabel("Count", fontsize=11)
560
+ ax.grid(True, alpha=0.3)
561
+ ax.legend(loc="upper left")
562
+
563
+ if title:
564
+ ax.set_title(title, fontsize=12, fontweight="bold")
565
+ else:
566
+ ax.set_title("Duty Cycle Distortion Analysis", fontsize=12, fontweight="bold")
567
+
568
+ fig.tight_layout()
569
+
570
+ if save_path is not None:
571
+ fig.savefig(save_path, dpi=300, bbox_inches="tight")
572
+
573
+ if show:
574
+ plt.show()
575
+
576
+ return fig
577
+
578
+
579
+ def plot_jitter_trend(
580
+ time_axis: NDArray[np.floating[Any]],
581
+ jitter_values: NDArray[np.floating[Any]],
582
+ *,
583
+ ax: Axes | None = None,
584
+ figsize: tuple[float, float] = (12, 5),
585
+ title: str | None = None,
586
+ time_unit: str = "auto",
587
+ jitter_unit: str = "auto",
588
+ show_trend: bool = True,
589
+ show_bounds: bool = True,
590
+ show: bool = True,
591
+ save_path: str | Path | None = None,
592
+ ) -> Figure:
593
+ """Plot jitter trend over time.
594
+
595
+ Creates a time series plot of jitter values with optional trend line
596
+ and statistical bounds.
597
+
598
+ Args:
599
+ time_axis: Time values (e.g., cycle number or time in seconds).
600
+ jitter_values: Jitter values at each time point.
601
+ ax: Matplotlib axes.
602
+ figsize: Figure size.
603
+ title: Plot title.
604
+ time_unit: Time axis unit.
605
+ jitter_unit: Jitter axis unit.
606
+ show_trend: Show linear trend line.
607
+ show_bounds: Show ±3σ bounds.
608
+ show: Display plot.
609
+ save_path: Save path.
610
+
611
+ Returns:
612
+ Matplotlib Figure object.
613
+ """
614
+ if not HAS_MATPLOTLIB:
615
+ raise ImportError("matplotlib is required for visualization")
616
+
617
+ if ax is None:
618
+ fig, ax = plt.subplots(figsize=figsize)
619
+ else:
620
+ fig_temp = ax.get_figure()
621
+ if fig_temp is None:
622
+ raise ValueError("Axes must have an associated figure")
623
+ fig = cast("Figure", fig_temp)
624
+
625
+ # Auto-select jitter unit
626
+ if jitter_unit == "auto":
627
+ max_jitter = np.max(np.abs(jitter_values))
628
+ if max_jitter < 1e-9:
629
+ jitter_unit = "ps"
630
+ jitter_mult = 1e12
631
+ elif max_jitter < 1e-6:
632
+ jitter_unit = "ns"
633
+ jitter_mult = 1e9
634
+ else:
635
+ jitter_unit = "us"
636
+ jitter_mult = 1e6
637
+ else:
638
+ jitter_mult = {"s": 1, "ms": 1e3, "us": 1e6, "ns": 1e9, "ps": 1e12}.get(jitter_unit, 1e12)
639
+
640
+ jitter_scaled = jitter_values * jitter_mult
641
+
642
+ # Plot jitter values
643
+ ax.plot(time_axis, jitter_scaled, "b-", linewidth=0.8, alpha=0.7, label="Jitter")
644
+
645
+ mean_val = np.mean(jitter_scaled)
646
+ std_val = np.std(jitter_scaled)
647
+
648
+ # Mean line
649
+ ax.axhline(
650
+ mean_val,
651
+ color="gray",
652
+ linestyle="-",
653
+ linewidth=1,
654
+ label=f"Mean: {mean_val:.2f} {jitter_unit}",
655
+ )
656
+
657
+ # Statistical bounds
658
+ if show_bounds:
659
+ ax.axhline(mean_val + 3 * std_val, color="#E74C3C", linestyle="--", linewidth=1, alpha=0.7)
660
+ ax.axhline(
661
+ mean_val - 3 * std_val,
662
+ color="#E74C3C",
663
+ linestyle="--",
664
+ linewidth=1,
665
+ alpha=0.7,
666
+ label=f"±3sigma: {3 * std_val:.2f} {jitter_unit}",
667
+ )
668
+ ax.fill_between(
669
+ time_axis, mean_val - 3 * std_val, mean_val + 3 * std_val, alpha=0.1, color="#E74C3C"
670
+ )
671
+
672
+ # Trend line
673
+ if show_trend:
674
+ z = np.polyfit(time_axis, jitter_scaled, 1)
675
+ p = np.poly1d(z)
676
+ ax.plot(
677
+ time_axis,
678
+ p(time_axis),
679
+ "g-",
680
+ linewidth=2,
681
+ label=f"Trend: {z[0]:.2e} {jitter_unit}/unit",
682
+ )
683
+
684
+ ax.set_xlabel(f"Time ({time_unit})" if time_unit != "auto" else "Sample Index", fontsize=11)
685
+ ax.set_ylabel(f"Jitter ({jitter_unit})", fontsize=11)
686
+ ax.grid(True, alpha=0.3)
687
+ ax.legend(loc="upper right")
688
+
689
+ if title:
690
+ ax.set_title(title, fontsize=12, fontweight="bold")
691
+ else:
692
+ ax.set_title("Jitter Trend Analysis", fontsize=12, fontweight="bold")
693
+
694
+ fig.tight_layout()
695
+
696
+ if save_path is not None:
697
+ fig.savefig(save_path, dpi=300, bbox_inches="tight")
698
+
699
+ if show:
700
+ plt.show()
701
+
702
+ return fig