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,615 @@
1
+ """Batch report generation for TraceKit.
2
+
3
+ This module provides utilities for generating reports across multiple DUTs
4
+ or files with summary reports and yield analysis.
5
+
6
+
7
+ Example:
8
+ >>> from oscura.reporting.batch import batch_report, generate_batch_report
9
+ >>> # Process multiple files
10
+ >>> results = batch_report(['dut1.wfm', 'dut2.wfm'], template='production', output_dir='reports/')
11
+ >>> # Or generate from pre-computed results
12
+ >>> report = generate_batch_report(batch_results, "batch_summary.pdf")
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import logging
18
+ from pathlib import Path
19
+ from typing import TYPE_CHECKING, Any
20
+
21
+ import numpy as np
22
+
23
+ from oscura.reporting.core import Report, ReportConfig, Section
24
+ from oscura.reporting.tables import format_batch_summary_table
25
+
26
+ if TYPE_CHECKING:
27
+ from collections.abc import Callable
28
+
29
+ from numpy.typing import NDArray
30
+
31
+ logger = logging.getLogger(__name__)
32
+
33
+ __all__ = [
34
+ "BatchReportResult",
35
+ "aggregate_batch_measurements",
36
+ "batch_report",
37
+ "generate_batch_report",
38
+ ]
39
+
40
+
41
+ class BatchReportResult:
42
+ """Result of batch report generation.
43
+
44
+ Attributes:
45
+ summary_report_path: Path to summary report
46
+ individual_report_paths: Paths to individual DUT reports
47
+ total_duts: Total number of DUTs processed
48
+ passed_duts: Number of passing DUTs
49
+ failed_duts: Number of failing DUTs
50
+ errors: List of processing errors
51
+
52
+ References:
53
+ RPT-003: Batch Report Generation
54
+ """
55
+
56
+ def __init__(self) -> None:
57
+ """Initialize batch result."""
58
+ self.summary_report_path: Path | None = None
59
+ self.individual_report_paths: list[Path] = []
60
+ self.total_duts: int = 0
61
+ self.passed_duts: int = 0
62
+ self.failed_duts: int = 0
63
+ self.errors: list[tuple[str, str]] = []
64
+
65
+ @property
66
+ def dut_yield(self) -> float:
67
+ """Calculate DUT yield percentage."""
68
+ if self.total_duts == 0:
69
+ return 0.0
70
+ return (self.passed_duts / self.total_duts) * 100
71
+
72
+
73
+ def batch_report(
74
+ files: list[str | Path],
75
+ template: str = "production",
76
+ output_dir: str | Path = "reports",
77
+ *,
78
+ analyzer: Callable[[Any], dict[str, Any]] | None = None,
79
+ generate_individual: bool = True,
80
+ generate_summary: bool = True,
81
+ output_format: str = "pdf",
82
+ file_pattern: str = "{dut_id}_report.{ext}",
83
+ summary_filename: str = "batch_summary.{ext}",
84
+ dut_id_extractor: Callable[[Path], str] | None = None,
85
+ ) -> BatchReportResult:
86
+ """Generate reports for multiple DUTs/files.
87
+
88
+ This is the primary interface for batch report generation.
89
+
90
+ Args:
91
+ files: List of input file paths
92
+ template: Report template name (default: 'production')
93
+ output_dir: Output directory for reports
94
+ analyzer: Optional analysis function (trace -> results dict)
95
+ generate_individual: Generate individual DUT reports
96
+ generate_summary: Generate summary report across all DUTs
97
+ output_format: Output format ('pdf', 'html')
98
+ file_pattern: Filename pattern for individual reports
99
+ summary_filename: Filename for summary report
100
+ dut_id_extractor: Function to extract DUT ID from file path
101
+
102
+ Returns:
103
+ BatchReportResult with paths and statistics
104
+
105
+ Example:
106
+ >>> result = batch_report(
107
+ ... files=['dut1.wfm', 'dut2.wfm', 'dut3.wfm'],
108
+ ... template='production',
109
+ ... output_dir='./reports'
110
+ ... )
111
+ >>> print(f"Yield: {result.dut_yield:.1f}%")
112
+ >>> print(f"Summary: {result.summary_report_path}")
113
+
114
+ References:
115
+ RPT-003: Batch Report Generation
116
+ """
117
+ import oscura as tk
118
+ from oscura.reporting.template_system import load_template
119
+
120
+ output_path = Path(output_dir)
121
+ output_path.mkdir(parents=True, exist_ok=True)
122
+
123
+ result = BatchReportResult()
124
+ batch_results: list[dict[str, Any]] = []
125
+
126
+ # Load template
127
+ try:
128
+ report_template = load_template(template)
129
+ except ValueError:
130
+ logger.warning(f"Template '{template}' not found, using 'default'")
131
+ report_template = load_template("default")
132
+
133
+ # Default DUT ID extractor
134
+ if dut_id_extractor is None:
135
+
136
+ def dut_id_extractor(p: Path) -> str:
137
+ return Path(p).stem
138
+
139
+ # Process each file
140
+ for file_path in files:
141
+ file_path = Path(file_path)
142
+ dut_id = dut_id_extractor(file_path)
143
+
144
+ try:
145
+ # Load trace
146
+ trace = tk.load(str(file_path))
147
+
148
+ # Run analysis
149
+ if analyzer is not None:
150
+ dut_result = analyzer(trace)
151
+ else:
152
+ # Default analysis
153
+ dut_result = _default_analysis(trace)
154
+
155
+ # Add DUT metadata
156
+ dut_result["dut_id"] = dut_id
157
+ dut_result["source_file"] = str(file_path)
158
+
159
+ batch_results.append(dut_result)
160
+
161
+ # Check pass/fail
162
+ if dut_result.get("pass_count", 0) == dut_result.get("total_count", 0):
163
+ result.passed_duts += 1
164
+ else:
165
+ result.failed_duts += 1
166
+
167
+ result.total_duts += 1
168
+
169
+ # Generate individual report if requested
170
+ if generate_individual:
171
+ ext = output_format.lower()
172
+ individual_filename = file_pattern.format(dut_id=dut_id, ext=ext)
173
+ individual_path = output_path / individual_filename
174
+
175
+ try:
176
+ _generate_individual_report(
177
+ dut_result, individual_path, report_template, output_format
178
+ )
179
+ result.individual_report_paths.append(individual_path)
180
+ except Exception as e:
181
+ logger.error(f"Failed to generate report for {dut_id}: {e}")
182
+ result.errors.append((dut_id, str(e)))
183
+
184
+ except Exception as e:
185
+ logger.error(f"Failed to process {file_path}: {e}")
186
+ result.errors.append((str(file_path), str(e)))
187
+
188
+ # Generate summary report if requested
189
+ if generate_summary and batch_results:
190
+ ext = output_format.lower()
191
+ summary_path = output_path / summary_filename.format(ext=ext)
192
+
193
+ try:
194
+ summary_report = generate_batch_report(batch_results)
195
+ _save_report(summary_report, summary_path, output_format)
196
+ result.summary_report_path = summary_path
197
+ except Exception as e:
198
+ logger.error(f"Failed to generate summary report: {e}")
199
+ result.errors.append(("summary", str(e)))
200
+
201
+ return result
202
+
203
+
204
+ def _default_analysis(trace: Any) -> dict[str, Any]:
205
+ """Default analysis for a trace."""
206
+ import numpy as np
207
+
208
+ from oscura.core.types import WaveformTrace
209
+
210
+ if not isinstance(trace, WaveformTrace):
211
+ return {
212
+ "measurements": {},
213
+ "pass_count": 0,
214
+ "total_count": 0,
215
+ }
216
+
217
+ data = trace.data
218
+
219
+ # Basic measurements
220
+ measurements = {
221
+ "peak_to_peak": {
222
+ "value": float(np.ptp(data)),
223
+ "unit": "V",
224
+ "passed": True,
225
+ },
226
+ "rms": {
227
+ "value": float(np.sqrt(np.mean(data**2))),
228
+ "unit": "V",
229
+ "passed": True,
230
+ },
231
+ "mean": {
232
+ "value": float(np.mean(data)),
233
+ "unit": "V",
234
+ "passed": True,
235
+ },
236
+ "std_dev": {
237
+ "value": float(np.std(data)),
238
+ "unit": "V",
239
+ "passed": True,
240
+ },
241
+ }
242
+
243
+ return {
244
+ "measurements": measurements,
245
+ "pass_count": len(measurements),
246
+ "total_count": len(measurements),
247
+ }
248
+
249
+
250
+ def _generate_individual_report(
251
+ dut_result: dict[str, Any],
252
+ output_path: Path,
253
+ template: Any,
254
+ output_format: str,
255
+ ) -> None:
256
+ """Generate individual DUT report."""
257
+ from oscura.reporting.core import Report, ReportConfig
258
+
259
+ dut_id = dut_result.get("dut_id", "Unknown")
260
+ config = ReportConfig(title=f"Test Report: {dut_id}")
261
+ report = Report(config=config)
262
+
263
+ # Add summary section
264
+ pass_count = dut_result.get("pass_count", 0)
265
+ total_count = dut_result.get("total_count", 0)
266
+ pass_rate = (pass_count / total_count * 100) if total_count > 0 else 0
267
+
268
+ summary = f"DUT: {dut_id}\n"
269
+ summary += f"Source: {dut_result.get('source_file', 'N/A')}\n"
270
+ summary += f"Pass Rate: {pass_count}/{total_count} ({pass_rate:.1f}%)"
271
+
272
+ report.add_section("Summary", summary, level=1)
273
+
274
+ # Add measurements
275
+ if "measurements" in dut_result:
276
+ from oscura.reporting.tables import create_measurement_table
277
+
278
+ table = create_measurement_table(dut_result["measurements"], format="dict")
279
+ report.add_section("Measurements", [table], level=1)
280
+
281
+ _save_report(report, output_path, output_format)
282
+
283
+
284
+ def _save_report(report: Report, output_path: Path, output_format: str) -> None:
285
+ """Save report in specified format."""
286
+ if output_format.lower() == "pdf":
287
+ from oscura.reporting.pdf import save_pdf_report
288
+
289
+ save_pdf_report(report, str(output_path))
290
+ elif output_format.lower() == "html":
291
+ from oscura.reporting.html import save_html_report
292
+
293
+ save_html_report(report, str(output_path))
294
+ else:
295
+ raise ValueError(f"Unsupported output format: {output_format}")
296
+
297
+
298
+ def generate_batch_report(
299
+ batch_results: list[dict[str, Any]],
300
+ *,
301
+ title: str = "Batch Test Summary Report",
302
+ include_individual: bool = True,
303
+ include_yield_analysis: bool = True,
304
+ include_outliers: bool = True,
305
+ **kwargs: Any,
306
+ ) -> Report:
307
+ """Generate batch summary report for multiple DUTs.
308
+
309
+ Args:
310
+ batch_results: List of result dictionaries, one per DUT.
311
+ title: Report title.
312
+ include_individual: Include individual DUT sections.
313
+ include_yield_analysis: Include yield analysis section.
314
+ include_outliers: Include outlier detection section.
315
+ **kwargs: Additional report configuration options.
316
+
317
+ Returns:
318
+ Batch Report object.
319
+
320
+ References:
321
+ REPORT-009, REPORT-018
322
+ """
323
+ config = ReportConfig(title=title, **kwargs)
324
+ report = Report(config=config)
325
+
326
+ # Add batch summary
327
+ summary = _generate_batch_summary(batch_results)
328
+ report.add_section("Batch Summary", summary, level=1)
329
+
330
+ # Add batch summary table
331
+ summary_table = format_batch_summary_table(batch_results, format="dict")
332
+ report.add_section("DUT Summary Table", [summary_table], level=1)
333
+
334
+ # Add yield analysis
335
+ if include_yield_analysis:
336
+ yield_section = _create_yield_analysis_section(batch_results)
337
+ report.sections.append(yield_section)
338
+
339
+ # Add statistical analysis
340
+ stats_section = _create_batch_statistics_section(batch_results)
341
+ report.sections.append(stats_section)
342
+
343
+ # Add outlier detection
344
+ if include_outliers:
345
+ outlier_section = _create_outlier_detection_section(batch_results)
346
+ report.sections.append(outlier_section)
347
+
348
+ # Add individual DUT sections
349
+ if include_individual:
350
+ for i, dut_result in enumerate(batch_results):
351
+ dut_section = _create_dut_section(dut_result, i)
352
+ report.sections.append(dut_section)
353
+
354
+ return report
355
+
356
+
357
+ def _generate_batch_summary(batch_results: list[dict[str, Any]]) -> str:
358
+ """Generate batch summary text."""
359
+ summary_parts = []
360
+
361
+ total_duts = len(batch_results)
362
+ summary_parts.append(f"Tested {total_duts} DUT(s).")
363
+
364
+ # Aggregate statistics
365
+ total_tests = sum(r.get("total_count", 0) for r in batch_results)
366
+ total_passed = sum(r.get("pass_count", 0) for r in batch_results)
367
+
368
+ if total_tests > 0:
369
+ pass_rate = total_passed / total_tests * 100
370
+ summary_parts.append(
371
+ f"\nOverall: {total_passed}/{total_tests} tests passed ({pass_rate:.1f}% pass rate)."
372
+ )
373
+
374
+ # DUT-level yield
375
+ passing_duts = sum(
376
+ 1 for r in batch_results if r.get("pass_count", 0) == r.get("total_count", 0)
377
+ )
378
+
379
+ if total_duts > 0:
380
+ dut_yield = passing_duts / total_duts * 100
381
+ summary_parts.append(
382
+ f"\nDUT Yield: {passing_duts}/{total_duts} DUTs passed all tests "
383
+ f"({dut_yield:.1f}% yield)."
384
+ )
385
+
386
+ # Failed DUTs
387
+ if passing_duts < total_duts:
388
+ failed_duts = []
389
+ for i, r in enumerate(batch_results):
390
+ dut_id = r.get("dut_id", f"DUT-{i + 1}")
391
+ if r.get("pass_count", 0) < r.get("total_count", 0):
392
+ failed_duts.append(dut_id)
393
+
394
+ summary_parts.append(f"\nFailed DUTs: {', '.join(failed_duts)}")
395
+
396
+ return "\n".join(summary_parts)
397
+
398
+
399
+ def _create_yield_analysis_section(batch_results: list[dict[str, Any]]) -> Section:
400
+ """Create yield analysis section."""
401
+ content_parts = []
402
+
403
+ # Overall yield
404
+ total_duts = len(batch_results)
405
+ passing_duts = sum(
406
+ 1 for r in batch_results if r.get("pass_count", 0) == r.get("total_count", 0)
407
+ )
408
+
409
+ overall_yield = (passing_duts / total_duts * 100) if total_duts > 0 else 0
410
+
411
+ content_parts.append(f"**Overall Yield:** {overall_yield:.2f}%")
412
+ content_parts.append(f"**Passing DUTs:** {passing_duts}/{total_duts}")
413
+
414
+ # Per-test yield
415
+ content_parts.append("\n**Per-Test Yield:**")
416
+
417
+ # Collect all test names
418
+ all_tests: set[str] = set()
419
+ for result in batch_results:
420
+ if "measurements" in result:
421
+ all_tests.update(result["measurements"].keys())
422
+
423
+ test_yields: list[tuple[str, float, int, int]] = []
424
+ for test_name in sorted(all_tests):
425
+ total_with_test = 0
426
+ passed_test = 0
427
+
428
+ for result in batch_results:
429
+ if "measurements" in result and test_name in result["measurements"]:
430
+ total_with_test += 1
431
+ if result["measurements"][test_name].get("passed", True):
432
+ passed_test += 1
433
+
434
+ if total_with_test > 0:
435
+ test_yield = passed_test / total_with_test * 100
436
+ test_yields.append((test_name, test_yield, passed_test, total_with_test))
437
+
438
+ # Sort by yield (worst first)
439
+ test_yields.sort(key=lambda x: x[1])
440
+
441
+ for test_name, yield_pct, passed, total in test_yields:
442
+ content_parts.append(f"- {test_name}: {yield_pct:.1f}% ({passed}/{total})")
443
+
444
+ content = "\n".join(content_parts)
445
+
446
+ return Section(
447
+ title="Yield Analysis",
448
+ content=content,
449
+ level=1,
450
+ visible=True,
451
+ )
452
+
453
+
454
+ def _create_batch_statistics_section(batch_results: list[dict[str, Any]]) -> Section:
455
+ """Create batch statistical analysis section."""
456
+ from oscura.reporting.formatting import NumberFormatter
457
+
458
+ formatter = NumberFormatter()
459
+
460
+ # Collect measurements across all DUTs
461
+ param_values: dict[str, list[float]] = {}
462
+
463
+ for result in batch_results:
464
+ if "measurements" in result:
465
+ for param, meas in result["measurements"].items():
466
+ value = meas.get("value")
467
+ if value is not None:
468
+ if param not in param_values:
469
+ param_values[param] = []
470
+ param_values[param].append(value)
471
+
472
+ # Build statistics table
473
+ headers = ["Parameter", "Mean", "Std Dev", "Min", "Max", "Range"]
474
+ rows = []
475
+
476
+ for param in sorted(param_values.keys()):
477
+ values = np.array(param_values[param])
478
+ unit = ""
479
+
480
+ # Get unit from first measurement
481
+ for result in batch_results:
482
+ if "measurements" in result and param in result["measurements"]:
483
+ unit = result["measurements"][param].get("unit", "")
484
+ break
485
+
486
+ rows.append(
487
+ [
488
+ param,
489
+ formatter.format(float(np.mean(values)), unit),
490
+ formatter.format(float(np.std(values)), unit),
491
+ formatter.format(float(np.min(values)), unit),
492
+ formatter.format(float(np.max(values)), unit),
493
+ formatter.format(float(np.max(values) - np.min(values)), unit),
494
+ ]
495
+ )
496
+
497
+ table = {"type": "table", "headers": headers, "data": rows}
498
+
499
+ return Section(
500
+ title="Batch Statistics",
501
+ content=[table],
502
+ level=1,
503
+ visible=True,
504
+ )
505
+
506
+
507
+ def _create_outlier_detection_section(batch_results: list[dict[str, Any]]) -> Section:
508
+ """Create outlier detection section."""
509
+ content_parts: list[str] = []
510
+
511
+ # Collect measurements
512
+ param_values: dict[str, list[tuple[int, float]]] = {}
513
+
514
+ for i, result in enumerate(batch_results):
515
+ if "measurements" in result:
516
+ for param, meas in result["measurements"].items():
517
+ value = meas.get("value")
518
+ if value is not None:
519
+ if param not in param_values:
520
+ param_values[param] = []
521
+ param_values[param].append((i, value))
522
+
523
+ # Detect outliers using 3-sigma rule
524
+ outliers_found = False
525
+
526
+ for param in sorted(param_values.keys()):
527
+ values_with_idx = param_values[param]
528
+ values = np.array([v for _, v in values_with_idx])
529
+
530
+ mean = float(np.mean(values))
531
+ std = float(np.std(values))
532
+
533
+ if std > 0:
534
+ outlier_indices: list[tuple[int, float, float]] = []
535
+ for idx, value in values_with_idx:
536
+ z_score = abs(value - mean) / std
537
+ if z_score > 3: # 3-sigma rule
538
+ outlier_indices.append((idx, value, z_score))
539
+
540
+ if outlier_indices:
541
+ outliers_found = True
542
+ content_parts.append(f"**{param}:**")
543
+ for idx, value, z_score in outlier_indices:
544
+ dut_id = batch_results[idx].get("dut_id", f"DUT-{idx + 1}")
545
+ content_parts.append(f"- {dut_id}: {value:.3g} (z-score: {z_score:.2f})")
546
+
547
+ if not outliers_found:
548
+ content_parts.append("No statistical outliers detected (3-sigma threshold).")
549
+
550
+ content = "\n".join(content_parts)
551
+
552
+ return Section(
553
+ title="Outlier Detection",
554
+ content=content,
555
+ level=1,
556
+ visible=True,
557
+ )
558
+
559
+
560
+ def _create_dut_section(result: dict[str, Any], index: int) -> Section:
561
+ """Create individual DUT section."""
562
+ dut_id = result.get("dut_id", f"DUT-{index + 1}")
563
+
564
+ content_parts: list[Any] = []
565
+
566
+ # DUT summary
567
+ pass_count = result.get("pass_count", 0)
568
+ total_count = result.get("total_count", 0)
569
+
570
+ if total_count > 0:
571
+ pass_rate = pass_count / total_count * 100
572
+ content_parts.append(f"Pass rate: {pass_count}/{total_count} ({pass_rate:.1f}%)")
573
+
574
+ # Measurements table
575
+ if "measurements" in result:
576
+ from oscura.reporting.tables import create_measurement_table
577
+
578
+ table = create_measurement_table(result["measurements"], format="dict")
579
+ content_parts.append(table)
580
+
581
+ return Section(
582
+ title=f"DUT: {dut_id}",
583
+ content=content_parts,
584
+ level=2,
585
+ visible=True,
586
+ collapsible=True,
587
+ )
588
+
589
+
590
+ def aggregate_batch_measurements(
591
+ batch_results: list[dict[str, Any]],
592
+ ) -> dict[str, NDArray[np.float64]]:
593
+ """Aggregate measurements across batch for statistical analysis.
594
+
595
+ Args:
596
+ batch_results: List of DUT results.
597
+
598
+ Returns:
599
+ Dictionary mapping parameter name to array of values.
600
+
601
+ References:
602
+ REPORT-009
603
+ """
604
+ param_values: dict[str, list[float]] = {}
605
+
606
+ for result in batch_results:
607
+ if "measurements" in result:
608
+ for param, meas in result["measurements"].items():
609
+ value = meas.get("value")
610
+ if value is not None:
611
+ if param not in param_values:
612
+ param_values[param] = []
613
+ param_values[param].append(value)
614
+
615
+ return {k: np.array(v) for k, v in param_values.items()}