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,701 @@
1
+ """HTML report export for TraceKit.
2
+
3
+ This module provides interactive HTML report generation with embedded Plotly charts,
4
+ measurement tables, and custom styling/theming.
5
+
6
+
7
+ Example:
8
+ >>> from oscura.exporters.html_export import export_html
9
+ >>> export_html(measurements, "report.html", title="Analysis Report")
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import base64
15
+ from datetime import datetime
16
+ from io import BytesIO
17
+ from pathlib import Path
18
+ from typing import Any
19
+
20
+ # HTML template with modern styling
21
+ HTML_TEMPLATE = """<!DOCTYPE html>
22
+ <html lang="en">
23
+ <head>
24
+ <meta charset="UTF-8">
25
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
26
+ <meta name="generator" content="TraceKit Export">
27
+ <title>{title}</title>
28
+ {plotly_script}
29
+ <style>
30
+ :root {{
31
+ --primary-color: #2c3e50;
32
+ --secondary-color: #3498db;
33
+ --success-color: #27ae60;
34
+ --warning-color: #f39c12;
35
+ --danger-color: #e74c3c;
36
+ --bg-color: #ffffff;
37
+ --text-color: #333333;
38
+ --border-color: #dddddd;
39
+ --table-header-bg: #f2f2f2;
40
+ --table-alt-row-bg: #f9f9f9;
41
+ }}
42
+
43
+ {dark_mode_styles}
44
+
45
+ * {{
46
+ box-sizing: border-box;
47
+ margin: 0;
48
+ padding: 0;
49
+ }}
50
+
51
+ body {{
52
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
53
+ font-size: 14px;
54
+ line-height: 1.6;
55
+ color: var(--text-color);
56
+ background-color: var(--bg-color);
57
+ padding: 20px;
58
+ }}
59
+
60
+ .container {{
61
+ max-width: 1200px;
62
+ margin: 0 auto;
63
+ }}
64
+
65
+ header {{
66
+ margin-bottom: 30px;
67
+ border-bottom: 3px solid var(--primary-color);
68
+ padding-bottom: 20px;
69
+ }}
70
+
71
+ h1 {{
72
+ font-size: 28px;
73
+ color: var(--primary-color);
74
+ margin-bottom: 10px;
75
+ }}
76
+
77
+ h2 {{
78
+ font-size: 20px;
79
+ color: var(--primary-color);
80
+ margin-top: 30px;
81
+ margin-bottom: 15px;
82
+ border-bottom: 1px solid var(--border-color);
83
+ padding-bottom: 8px;
84
+ }}
85
+
86
+ h3 {{
87
+ font-size: 16px;
88
+ color: var(--primary-color);
89
+ margin-top: 20px;
90
+ margin-bottom: 10px;
91
+ }}
92
+
93
+ .metadata {{
94
+ background-color: var(--table-alt-row-bg);
95
+ padding: 15px;
96
+ border-radius: 5px;
97
+ font-size: 13px;
98
+ color: #666;
99
+ }}
100
+
101
+ .metadata span {{
102
+ margin-right: 20px;
103
+ }}
104
+
105
+ table {{
106
+ width: 100%;
107
+ border-collapse: collapse;
108
+ margin: 15px 0;
109
+ }}
110
+
111
+ th, td {{
112
+ padding: 10px 12px;
113
+ text-align: left;
114
+ border: 1px solid var(--border-color);
115
+ }}
116
+
117
+ th {{
118
+ background-color: var(--table-header-bg);
119
+ font-weight: 600;
120
+ }}
121
+
122
+ tr:nth-child(even) {{
123
+ background-color: var(--table-alt-row-bg);
124
+ }}
125
+
126
+ tr:hover {{
127
+ background-color: rgba(52, 152, 219, 0.1);
128
+ }}
129
+
130
+ .pass {{
131
+ color: var(--success-color);
132
+ font-weight: 600;
133
+ }}
134
+
135
+ .pass::before {{
136
+ content: '\\2713 ';
137
+ }}
138
+
139
+ .fail {{
140
+ color: var(--danger-color);
141
+ font-weight: 600;
142
+ }}
143
+
144
+ .fail::before {{
145
+ content: '\\2717 ';
146
+ }}
147
+
148
+ .warning {{
149
+ color: var(--warning-color);
150
+ font-weight: 600;
151
+ }}
152
+
153
+ .summary {{
154
+ background-color: rgba(52, 152, 219, 0.1);
155
+ border-left: 4px solid var(--secondary-color);
156
+ padding: 15px;
157
+ margin: 20px 0;
158
+ }}
159
+
160
+ .plot-container {{
161
+ margin: 20px 0;
162
+ padding: 15px;
163
+ background-color: var(--bg-color);
164
+ border: 1px solid var(--border-color);
165
+ border-radius: 5px;
166
+ }}
167
+
168
+ .plot-container img {{
169
+ max-width: 100%;
170
+ height: auto;
171
+ display: block;
172
+ margin: 0 auto;
173
+ }}
174
+
175
+ .plot-caption {{
176
+ text-align: center;
177
+ font-style: italic;
178
+ margin-top: 10px;
179
+ color: #666;
180
+ }}
181
+
182
+ footer {{
183
+ margin-top: 40px;
184
+ padding-top: 20px;
185
+ border-top: 1px solid var(--border-color);
186
+ text-align: center;
187
+ font-size: 12px;
188
+ color: #888;
189
+ }}
190
+
191
+ @media (max-width: 768px) {{
192
+ body {{
193
+ padding: 10px;
194
+ }}
195
+
196
+ h1 {{
197
+ font-size: 22px;
198
+ }}
199
+
200
+ table {{
201
+ font-size: 12px;
202
+ }}
203
+
204
+ th, td {{
205
+ padding: 6px 8px;
206
+ }}
207
+ }}
208
+
209
+ @media print {{
210
+ body {{
211
+ padding: 0;
212
+ }}
213
+
214
+ .container {{
215
+ max-width: 100%;
216
+ }}
217
+ }}
218
+ </style>
219
+ </head>
220
+ <body{body_class}>
221
+ <div class="container">
222
+ <header>
223
+ <h1>{title}</h1>
224
+ <div class="metadata">
225
+ {metadata_html}
226
+ </div>
227
+ </header>
228
+
229
+ {summary_html}
230
+
231
+ {measurements_html}
232
+
233
+ {plots_html}
234
+
235
+ {conclusions_html}
236
+
237
+ <footer>
238
+ Generated by TraceKit &middot; {timestamp}
239
+ </footer>
240
+ </div>
241
+ </body>
242
+ </html>
243
+ """
244
+ DARK_MODE_CSS = """
245
+ @media (prefers-color-scheme: dark) {
246
+ :root {
247
+ --bg-color: #1e1e1e;
248
+ --text-color: #e0e0e0;
249
+ --border-color: #444444;
250
+ --table-header-bg: #2d2d2d;
251
+ --table-alt-row-bg: #252525;
252
+ }
253
+ }
254
+
255
+ body.dark-mode {
256
+ --bg-color: #1e1e1e;
257
+ --text-color: #e0e0e0;
258
+ --border-color: #444444;
259
+ --table-header-bg: #2d2d2d;
260
+ --table-alt-row-bg: #252525;
261
+ }
262
+ """
263
+
264
+
265
+ def export_html(
266
+ data: dict[str, Any],
267
+ path: str | Path,
268
+ *,
269
+ title: str = "TraceKit Analysis Report",
270
+ author: str | None = None,
271
+ include_plots: bool = True,
272
+ self_contained: bool = True,
273
+ interactive: bool = True,
274
+ dark_mode: bool = False,
275
+ theme: str | None = None,
276
+ ) -> None:
277
+ """Export measurement results to interactive HTML format.
278
+
279
+ Args:
280
+ data: Dictionary containing measurement results, plots, and metadata.
281
+ Expected keys:
282
+ - "measurements": dict of name -> value pairs
283
+ - "plots": list of matplotlib/plotly figures or paths
284
+ - "metadata": optional dict of metadata
285
+ - "summary": optional executive summary text
286
+ - "conclusions": optional conclusions text
287
+ path: Output file path.
288
+ title: Report title.
289
+ author: Author name (optional).
290
+ include_plots: Include plots in report.
291
+ self_contained: Embed all resources inline (True) or save separately.
292
+ interactive: Use Plotly for interactive charts when available.
293
+ dark_mode: Enable dark mode styling.
294
+ theme: Custom theme name (reserved for future use).
295
+
296
+ References:
297
+ EXP-007
298
+ """
299
+ html_content = generate_html_report(
300
+ data,
301
+ title=title,
302
+ author=author,
303
+ include_plots=include_plots,
304
+ self_contained=self_contained,
305
+ interactive=interactive,
306
+ dark_mode=dark_mode,
307
+ theme=theme,
308
+ )
309
+
310
+ Path(path).write_text(html_content, encoding="utf-8")
311
+
312
+
313
+ def generate_html_report(
314
+ data: dict[str, Any],
315
+ *,
316
+ title: str = "TraceKit Analysis Report",
317
+ author: str | None = None,
318
+ include_plots: bool = True,
319
+ self_contained: bool = True,
320
+ interactive: bool = True,
321
+ dark_mode: bool = False,
322
+ theme: str | None = None,
323
+ ) -> str:
324
+ """Generate HTML report as string.
325
+
326
+ Args:
327
+ data: Dictionary containing measurement results, plots, and metadata.
328
+ title: Report title.
329
+ author: Author name (optional).
330
+ include_plots: Include plots in report.
331
+ self_contained: Embed all resources inline.
332
+ interactive: Use Plotly for interactive charts when available.
333
+ dark_mode: Enable dark mode styling.
334
+ theme: Custom theme name (reserved for future use).
335
+
336
+ Returns:
337
+ HTML content as string.
338
+
339
+ References:
340
+ EXP-007
341
+ """
342
+ # Metadata HTML
343
+ metadata_parts = []
344
+ metadata = data.get("metadata", {})
345
+
346
+ if author:
347
+ metadata_parts.append(f"<span><strong>Author:</strong> {author}</span>")
348
+
349
+ timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
350
+ metadata_parts.append(f"<span><strong>Generated:</strong> {timestamp}</span>")
351
+
352
+ if "filename" in metadata:
353
+ metadata_parts.append(
354
+ f"<span><strong>Source:</strong> {_html_escape(metadata['filename'])}</span>"
355
+ )
356
+
357
+ if "sample_rate" in metadata:
358
+ sr = metadata["sample_rate"]
359
+ sr_str = _format_sample_rate(sr)
360
+ metadata_parts.append(f"<span><strong>Sample Rate:</strong> {sr_str}</span>")
361
+
362
+ if "samples" in metadata:
363
+ metadata_parts.append(f"<span><strong>Samples:</strong> {metadata['samples']:,}</span>")
364
+
365
+ metadata_html = "\n ".join(metadata_parts)
366
+
367
+ # Summary HTML
368
+ summary_html = ""
369
+ if "summary" in data:
370
+ summary_html = f"""
371
+ <section id="summary">
372
+ <h2>Executive Summary</h2>
373
+ <div class="summary">
374
+ <p>{_html_escape(data["summary"])}</p>
375
+ </div>
376
+ </section>
377
+ """
378
+ # Measurements HTML
379
+ measurements_html = ""
380
+ if "measurements" in data:
381
+ measurements_html = _generate_measurements_html(data["measurements"])
382
+
383
+ # Plots HTML
384
+ plots_html = ""
385
+ plotly_script = ""
386
+ if include_plots and "plots" in data:
387
+ plots_html, plotly_script = _generate_plots_html(data["plots"], self_contained, interactive)
388
+
389
+ # Conclusions HTML
390
+ conclusions_html = ""
391
+ if "conclusions" in data:
392
+ conclusions_html = f"""
393
+ <section id="conclusions">
394
+ <h2>Conclusions</h2>
395
+ <p>{_html_escape(data["conclusions"])}</p>
396
+ </section>
397
+ """
398
+ # Dark mode styles
399
+ dark_mode_styles = DARK_MODE_CSS if dark_mode else ""
400
+
401
+ # Body class for dark mode
402
+ body_class = ' class="dark-mode"' if dark_mode else ""
403
+
404
+ # Generate final HTML
405
+ html = HTML_TEMPLATE.format(
406
+ title=_html_escape(title),
407
+ plotly_script=plotly_script,
408
+ dark_mode_styles=dark_mode_styles,
409
+ body_class=body_class,
410
+ metadata_html=metadata_html,
411
+ summary_html=summary_html,
412
+ measurements_html=measurements_html,
413
+ plots_html=plots_html,
414
+ conclusions_html=conclusions_html,
415
+ timestamp=timestamp,
416
+ )
417
+
418
+ return html
419
+
420
+
421
+ def _html_escape(text: str) -> str:
422
+ """Escape HTML special characters."""
423
+ return (
424
+ text.replace("&", "&amp;")
425
+ .replace("<", "&lt;")
426
+ .replace(">", "&gt;")
427
+ .replace('"', "&quot;")
428
+ .replace("'", "&#39;")
429
+ )
430
+
431
+
432
+ def _format_sample_rate(sr: float) -> str:
433
+ """Format sample rate with SI prefix."""
434
+ if sr >= 1e9:
435
+ return f"{sr / 1e9:.3f} GS/s"
436
+ elif sr >= 1e6:
437
+ return f"{sr / 1e6:.3f} MS/s"
438
+ elif sr >= 1e3:
439
+ return f"{sr / 1e3:.3f} kS/s"
440
+ else:
441
+ return f"{sr:.3f} S/s"
442
+
443
+
444
+ def _format_value(value: float, unit: str) -> str:
445
+ """Format value with appropriate precision."""
446
+ if value == 0:
447
+ return "0"
448
+
449
+ abs_val = abs(value)
450
+
451
+ # Time units
452
+ if unit in ("s", "sec", "seconds"):
453
+ if abs_val >= 1.0:
454
+ return f"{value:.6g} s"
455
+ elif abs_val >= 1e-3:
456
+ return f"{value * 1e3:.6g} ms"
457
+ elif abs_val >= 1e-6:
458
+ return f"{value * 1e6:.6g} us"
459
+ elif abs_val >= 1e-9:
460
+ return f"{value * 1e9:.6g} ns"
461
+ else:
462
+ return f"{value * 1e12:.6g} ps"
463
+
464
+ # Frequency units
465
+ if unit in ("Hz", "hz"):
466
+ if abs_val >= 1e9:
467
+ return f"{value / 1e9:.6g} GHz"
468
+ elif abs_val >= 1e6:
469
+ return f"{value / 1e6:.6g} MHz"
470
+ elif abs_val >= 1e3:
471
+ return f"{value / 1e3:.6g} kHz"
472
+ else:
473
+ return f"{value:.6g} Hz"
474
+
475
+ # Default formatting
476
+ if unit:
477
+ return f"{value:.6g} {unit}"
478
+ return f"{value:.6g}"
479
+
480
+
481
+ def _generate_measurements_html(measurements: dict[str, Any]) -> str:
482
+ """Generate measurements table HTML."""
483
+ if not measurements:
484
+ return ""
485
+
486
+ rows = []
487
+ for name, value in measurements.items():
488
+ if isinstance(value, dict):
489
+ val = value.get("value", "N/A")
490
+ unit = value.get("unit", "")
491
+ status = value.get("status", "")
492
+
493
+ val_str = _format_value(val, unit) if isinstance(val, float) else str(val)
494
+
495
+ # Status class and formatting
496
+ status_upper = str(status).upper()
497
+ if status_upper == "PASS":
498
+ status_html = '<span class="pass">PASS</span>'
499
+ elif status_upper == "FAIL":
500
+ status_html = '<span class="fail">FAIL</span>'
501
+ elif status_upper == "WARNING":
502
+ status_html = '<span class="warning">WARNING</span>'
503
+ else:
504
+ status_html = _html_escape(str(status))
505
+
506
+ rows.append(
507
+ f"<tr><td>{_html_escape(name)}</td>"
508
+ f"<td>{_html_escape(val_str)}</td>"
509
+ f"<td>{_html_escape(unit)}</td>"
510
+ f"<td>{status_html}</td></tr>"
511
+ )
512
+ else:
513
+ val_str = f"{value:.6g}" if isinstance(value, float) else str(value)
514
+
515
+ rows.append(
516
+ f"<tr><td>{_html_escape(name)}</td>"
517
+ f"<td>{_html_escape(val_str)}</td>"
518
+ f"<td>-</td><td>-</td></tr>"
519
+ )
520
+
521
+ return f"""
522
+ <section id="measurements">
523
+ <h2>Measurement Results</h2>
524
+ <table>
525
+ <thead>
526
+ <tr>
527
+ <th>Parameter</th>
528
+ <th>Value</th>
529
+ <th>Unit</th>
530
+ <th>Status</th>
531
+ </tr>
532
+ </thead>
533
+ <tbody>
534
+ {"".join(rows)}
535
+ </tbody>
536
+ </table>
537
+ </section>
538
+ """
539
+
540
+
541
+ def _generate_plots_html(
542
+ plots: list[Any],
543
+ self_contained: bool,
544
+ interactive: bool,
545
+ ) -> tuple[str, str]:
546
+ """Generate plots HTML and Plotly script if needed.
547
+
548
+ Args:
549
+ plots: List of plot objects (matplotlib figures, plotly figures, or paths).
550
+ self_contained: Embed all resources inline (True) or reference externally.
551
+ interactive: Use Plotly for interactive charts when available.
552
+
553
+ Returns:
554
+ Tuple of (plots_html, plotly_script_tag)
555
+ """
556
+ if not plots:
557
+ return "", ""
558
+
559
+ plot_divs = []
560
+ has_plotly = False
561
+
562
+ for i, plot in enumerate(plots, start=1):
563
+ if isinstance(plot, dict):
564
+ fig = plot.get("figure")
565
+ caption = plot.get("caption", f"Figure {i}")
566
+ else:
567
+ fig = plot
568
+ caption = f"Figure {i}"
569
+
570
+ if fig is None:
571
+ continue
572
+
573
+ # Check if it's a Plotly figure
574
+ plotly_html = _try_render_plotly(fig, interactive)
575
+ if plotly_html:
576
+ has_plotly = True
577
+ plot_divs.append(
578
+ f'<div class="plot-container"><h3>{_html_escape(caption)}</h3>{plotly_html}</div>'
579
+ )
580
+ continue
581
+
582
+ # Try matplotlib figure
583
+ img_html = _try_render_matplotlib(fig, self_contained)
584
+ if img_html:
585
+ plot_divs.append(
586
+ f'<div class="plot-container">'
587
+ f"<h3>{_html_escape(caption)}</h3>"
588
+ f"{img_html}"
589
+ f'<div class="plot-caption">{_html_escape(caption)}</div>'
590
+ f"</div>"
591
+ )
592
+ continue
593
+
594
+ # Image path
595
+ if isinstance(fig, str | Path):
596
+ if self_contained:
597
+ try:
598
+ img_data = Path(fig).read_bytes()
599
+ img_ext = Path(fig).suffix.lower()
600
+ mime_type = {
601
+ ".png": "image/png",
602
+ ".jpg": "image/jpeg",
603
+ ".jpeg": "image/jpeg",
604
+ ".svg": "image/svg+xml",
605
+ }.get(img_ext, "image/png")
606
+
607
+ b64 = base64.b64encode(img_data).decode("utf-8")
608
+ img_html = (
609
+ f'<img src="data:{mime_type};base64,{b64}" alt="{_html_escape(caption)}">'
610
+ )
611
+ except Exception:
612
+ img_html = f"<p><em>Unable to embed image: {fig}</em></p>"
613
+ else:
614
+ img_html = f'<img src="{_html_escape(str(fig))}" alt="{_html_escape(caption)}">'
615
+
616
+ plot_divs.append(
617
+ f'<div class="plot-container">'
618
+ f"<h3>{_html_escape(caption)}</h3>"
619
+ f"{img_html}"
620
+ f'<div class="plot-caption">{_html_escape(caption)}</div>'
621
+ f"</div>"
622
+ )
623
+
624
+ # Plotly CDN script (only included if we have Plotly figures)
625
+ plotly_script = ""
626
+ if has_plotly:
627
+ plotly_script = '<script src="https://cdn.plot.ly/plotly-2.27.0.min.js"></script>'
628
+
629
+ plots_html = (
630
+ f"""
631
+ <section id="plots">
632
+ <h2>Plots and Visualizations</h2>
633
+ {"".join(plot_divs)}
634
+ </section>
635
+ """
636
+ if plot_divs
637
+ else ""
638
+ )
639
+
640
+ return plots_html, plotly_script
641
+
642
+
643
+ def _try_render_plotly(fig: Any, interactive: bool) -> str | None:
644
+ """Try to render a Plotly figure to HTML.
645
+
646
+ Args:
647
+ fig: Figure object to render (may be Plotly figure or other type).
648
+ interactive: Enable interactive Plotly rendering.
649
+
650
+ Returns:
651
+ HTML string if successful, None if not a Plotly figure.
652
+ """
653
+ if not interactive:
654
+ return None
655
+
656
+ try:
657
+ import plotly.graph_objects as go # type: ignore[import-not-found]
658
+
659
+ if isinstance(fig, go.Figure):
660
+ return fig.to_html( # type: ignore[no-any-return]
661
+ full_html=False,
662
+ include_plotlyjs=False,
663
+ config={"displayModeBar": True, "responsive": True},
664
+ )
665
+ except ImportError:
666
+ pass
667
+
668
+ return None
669
+
670
+
671
+ def _try_render_matplotlib(fig: Any, self_contained: bool) -> str | None:
672
+ """Try to render a Matplotlib figure to HTML.
673
+
674
+ Args:
675
+ fig: Figure object to render (may be matplotlib figure or other type).
676
+ self_contained: Embed image as base64 data URI.
677
+
678
+ Returns:
679
+ HTML img tag if successful, None if not a Matplotlib figure.
680
+ """
681
+ try:
682
+ import matplotlib.pyplot as plt
683
+
684
+ if hasattr(fig, "savefig"):
685
+ buf = BytesIO()
686
+ fig.savefig(buf, format="png", dpi=150, bbox_inches="tight")
687
+ buf.seek(0)
688
+ b64 = base64.b64encode(buf.read()).decode("utf-8")
689
+ return f'<img src="data:image/png;base64,{b64}" alt="Figure">'
690
+ except ImportError:
691
+ pass
692
+ except Exception:
693
+ pass
694
+
695
+ return None
696
+
697
+
698
+ __all__ = [
699
+ "export_html",
700
+ "generate_html_report",
701
+ ]