oscura 0.0.1__py3-none-any.whl → 0.1.1__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.1.dist-info/METADATA +300 -0
  460. oscura-0.1.1.dist-info/RECORD +463 -0
  461. oscura-0.1.1.dist-info/entry_points.txt +2 -0
  462. {oscura-0.0.1.dist-info → oscura-0.1.1.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.1.dist-info}/WHEEL +0 -0
@@ -0,0 +1,632 @@
1
+ """Edge detection with sub-sample precision and timing analysis.
2
+
3
+ This module provides edge detection with interpolation for sub-sample precision,
4
+ timing measurements between edges, and timing constraint validation for digital
5
+ signal analysis.
6
+
7
+
8
+ Example:
9
+ >>> import numpy as np
10
+ >>> from oscura.analyzers.digital.edges import detect_edges, measure_edge_timing
11
+ >>> # Generate test signal
12
+ >>> signal = np.array([0, 0, 0.5, 1.0, 1.0, 1.0, 0.5, 0, 0])
13
+ >>> # Detect edges
14
+ >>> edges = detect_edges(signal, edge_type='both', sample_rate=100e6)
15
+ >>> # Measure timing
16
+ >>> timing = measure_edge_timing(edges, sample_rate=100e6)
17
+ """
18
+
19
+ from __future__ import annotations
20
+
21
+ from dataclasses import dataclass
22
+ from typing import TYPE_CHECKING, Literal
23
+
24
+ import numpy as np
25
+
26
+ from oscura.core.memoize import memoize_analysis
27
+
28
+ if TYPE_CHECKING:
29
+ from numpy.typing import NDArray
30
+
31
+
32
+ @dataclass
33
+ class Edge:
34
+ """A detected edge in the signal.
35
+
36
+ Attributes:
37
+ sample_index: Sample index where edge was detected.
38
+ time: Interpolated edge time in seconds.
39
+ edge_type: Type of edge ('rising' or 'falling').
40
+ amplitude: Transition amplitude in signal units (volts).
41
+ slew_rate: Edge slew rate (signal units per second).
42
+ quality: Edge quality classification.
43
+ """
44
+
45
+ sample_index: int
46
+ time: float # Interpolated time
47
+ edge_type: Literal["rising", "falling"]
48
+ amplitude: float # Transition amplitude
49
+ slew_rate: float # V/s or samples/s
50
+ quality: Literal["clean", "slow", "noisy", "glitch"]
51
+
52
+
53
+ @dataclass
54
+ class EdgeTiming:
55
+ """Timing measurements from edge analysis.
56
+
57
+ Attributes:
58
+ periods: Array of edge-to-edge periods in seconds.
59
+ mean_period: Mean period in seconds.
60
+ std_period: Standard deviation of period in seconds.
61
+ min_period: Minimum period in seconds.
62
+ max_period: Maximum period in seconds.
63
+ duty_cycles: Array of duty cycle ratios (0-1).
64
+ mean_duty_cycle: Mean duty cycle ratio.
65
+ jitter_rms: RMS jitter in seconds.
66
+ jitter_pp: Peak-to-peak jitter in seconds.
67
+ """
68
+
69
+ periods: NDArray[np.float64] # Edge-to-edge periods
70
+ mean_period: float
71
+ std_period: float
72
+ min_period: float
73
+ max_period: float
74
+ duty_cycles: NDArray[np.float64]
75
+ mean_duty_cycle: float
76
+ jitter_rms: float
77
+ jitter_pp: float
78
+
79
+
80
+ @dataclass
81
+ class TimingConstraint:
82
+ """Timing constraint for validation.
83
+
84
+ Attributes:
85
+ name: Descriptive name for the constraint.
86
+ min_time: Minimum allowed time in seconds (None for no minimum).
87
+ max_time: Maximum allowed time in seconds (None for no maximum).
88
+ reference: Which edges to check ('rising', 'falling', or 'both').
89
+ """
90
+
91
+ name: str
92
+ min_time: float | None = None
93
+ max_time: float | None = None
94
+ reference: str | None = None # 'rising', 'falling', 'both'
95
+
96
+
97
+ @dataclass
98
+ class TimingViolation:
99
+ """A timing constraint violation.
100
+
101
+ Attributes:
102
+ constraint: The violated constraint.
103
+ measured_time: The measured time that violated the constraint.
104
+ edge_index: Index of the edge that violated the constraint.
105
+ sample_index: Sample index where violation occurred.
106
+ """
107
+
108
+ constraint: TimingConstraint
109
+ measured_time: float
110
+ edge_index: int
111
+ sample_index: int
112
+
113
+
114
+ @memoize_analysis(maxsize=32)
115
+ def detect_edges(
116
+ trace: NDArray[np.float64],
117
+ edge_type: Literal["rising", "falling", "both"] = "both",
118
+ threshold: float | Literal["auto"] = "auto",
119
+ hysteresis: float = 0.0,
120
+ sample_rate: float = 1.0,
121
+ ) -> list[Edge]:
122
+ """Detect signal edges with configurable threshold.
123
+
124
+ Detects rising and/or falling edges in a digital or analog signal with
125
+ optional hysteresis for noise immunity.
126
+
127
+ Args:
128
+ trace: Input signal trace (analog or digital).
129
+ edge_type: Type of edges to detect ('rising', 'falling', or 'both').
130
+ threshold: Detection threshold. 'auto' computes from signal midpoint.
131
+ hysteresis: Hysteresis amount for noise immunity (signal units).
132
+ sample_rate: Sample rate in Hz for time calculation.
133
+
134
+ Returns:
135
+ List of Edge objects with detected edges.
136
+
137
+ Example:
138
+ >>> signal = np.array([0, 0, 1, 1, 0, 0])
139
+ >>> edges = detect_edges(signal, edge_type='rising')
140
+ >>> len(edges)
141
+ 1
142
+ """
143
+ if len(trace) < 2:
144
+ return []
145
+
146
+ trace = np.asarray(trace)
147
+
148
+ # Compute threshold if auto
149
+ thresh_val: float
150
+ if threshold == "auto":
151
+ thresh_val = float((np.max(trace) + np.min(trace)) / 2.0)
152
+ else:
153
+ thresh_val = threshold
154
+
155
+ # Apply hysteresis if specified
156
+ if hysteresis > 0:
157
+ thresh_high = thresh_val + hysteresis / 2.0
158
+ thresh_low = thresh_val - hysteresis / 2.0
159
+ else:
160
+ thresh_high = thresh_val
161
+ thresh_low = thresh_val
162
+
163
+ edges: list[Edge] = []
164
+ time_base = 1.0 / sample_rate
165
+
166
+ # State machine for hysteresis
167
+ state = trace[0] > thresh_val # Initial state
168
+
169
+ for i in range(1, len(trace)):
170
+ prev_val = trace[i - 1]
171
+ curr_val = trace[i]
172
+
173
+ # Detect transitions with hysteresis
174
+ if not state and curr_val > thresh_high:
175
+ # Rising edge
176
+ if edge_type in ["rising", "both"]:
177
+ # Interpolate edge time
178
+ interp_time = interpolate_edge_time(trace, i - 1, method="linear")
179
+ time = (i - 1 + interp_time) * time_base
180
+
181
+ # Calculate edge properties
182
+ amplitude = curr_val - prev_val
183
+ slew_rate = amplitude * sample_rate
184
+
185
+ # Classify quality (simple heuristic)
186
+ quality = classify_edge_quality(trace, i, sample_rate)
187
+
188
+ edges.append(
189
+ Edge(
190
+ sample_index=i,
191
+ time=time,
192
+ edge_type="rising",
193
+ amplitude=abs(amplitude),
194
+ slew_rate=slew_rate,
195
+ quality=quality,
196
+ )
197
+ )
198
+ state = True
199
+
200
+ elif state and curr_val < thresh_low:
201
+ # Falling edge
202
+ if edge_type in ["falling", "both"]:
203
+ # Interpolate edge time
204
+ interp_time = interpolate_edge_time(trace, i - 1, method="linear")
205
+ time = (i - 1 + interp_time) * time_base
206
+
207
+ # Calculate edge properties
208
+ amplitude = prev_val - curr_val
209
+ slew_rate = -amplitude * sample_rate
210
+
211
+ # Classify quality (simple heuristic)
212
+ quality = classify_edge_quality(trace, i, sample_rate)
213
+
214
+ edges.append(
215
+ Edge(
216
+ sample_index=i,
217
+ time=time,
218
+ edge_type="falling",
219
+ amplitude=abs(amplitude),
220
+ slew_rate=slew_rate,
221
+ quality=quality,
222
+ )
223
+ )
224
+ state = False
225
+
226
+ return edges
227
+
228
+
229
+ def interpolate_edge_time(
230
+ trace: NDArray[np.float64], sample_index: int, method: Literal["linear", "quadratic"] = "linear"
231
+ ) -> float:
232
+ """Interpolate edge time for sub-sample precision.
233
+
234
+ Uses linear or quadratic interpolation to estimate the fractional sample
235
+ position where an edge crosses the threshold.
236
+
237
+ Args:
238
+ trace: Input signal trace.
239
+ sample_index: Sample index just before the edge.
240
+ method: Interpolation method ('linear' or 'quadratic').
241
+
242
+ Returns:
243
+ Fractional sample offset (0.0 to 1.0) from sample_index.
244
+
245
+ Example:
246
+ >>> trace = np.array([0, 0.3, 0.8, 1.0])
247
+ >>> offset = interpolate_edge_time(trace, 1, method='linear')
248
+ """
249
+ if sample_index < 0 or sample_index >= len(trace) - 1:
250
+ return 0.0
251
+
252
+ if method == "linear":
253
+ # Linear interpolation between two points
254
+ v0 = trace[sample_index]
255
+ v1 = trace[sample_index + 1]
256
+
257
+ if abs(v1 - v0) < 1e-10:
258
+ return 0.5 # Avoid division by zero
259
+
260
+ # Find midpoint crossing
261
+ threshold = (v0 + v1) / 2.0
262
+ fraction = (threshold - v0) / (v1 - v0)
263
+
264
+ # Clamp to valid range
265
+ return float(np.clip(fraction, 0.0, 1.0))
266
+
267
+ elif method == "quadratic":
268
+ # Quadratic interpolation using 3 points
269
+ if sample_index < 1 or sample_index >= len(trace) - 1:
270
+ # Fall back to linear
271
+ return interpolate_edge_time(trace, sample_index, method="linear")
272
+
273
+ # Use points before, at, and after edge
274
+ _v_prev = trace[sample_index - 1]
275
+ v0 = trace[sample_index]
276
+ v1 = trace[sample_index + 1]
277
+
278
+ # Fit parabola and find threshold crossing
279
+ # Simplified: use linear for now (full quadratic fit is complex)
280
+ return interpolate_edge_time(trace, sample_index, method="linear")
281
+
282
+
283
+ def measure_edge_timing(edges: list[Edge], sample_rate: float = 1.0) -> EdgeTiming:
284
+ """Measure timing between edges.
285
+
286
+ Computes period, duty cycle, and jitter statistics from a list of detected edges.
287
+
288
+ Args:
289
+ edges: List of Edge objects from detect_edges().
290
+ sample_rate: Sample rate in Hz (for time base).
291
+
292
+ Returns:
293
+ EdgeTiming object with timing measurements.
294
+
295
+ Example:
296
+ >>> edges = detect_edges(signal, edge_type='both', sample_rate=100e6)
297
+ >>> timing = measure_edge_timing(edges, sample_rate=100e6)
298
+ """
299
+ if len(edges) < 2:
300
+ # Not enough edges for timing analysis
301
+ return EdgeTiming(
302
+ periods=np.array([]),
303
+ mean_period=0.0,
304
+ std_period=0.0,
305
+ min_period=0.0,
306
+ max_period=0.0,
307
+ duty_cycles=np.array([]),
308
+ mean_duty_cycle=0.0,
309
+ jitter_rms=0.0,
310
+ jitter_pp=0.0,
311
+ )
312
+
313
+ # Calculate periods (time between consecutive edges)
314
+ edge_times = np.array([e.time for e in edges])
315
+ periods = np.diff(edge_times)
316
+
317
+ # Calculate duty cycles (ratio of high time to period)
318
+ duty_cycles = []
319
+ rising_edges = [e for e in edges if e.edge_type == "rising"]
320
+ falling_edges = [e for e in edges if e.edge_type == "falling"]
321
+
322
+ # Match rising and falling edges to compute duty cycles
323
+ for i in range(min(len(rising_edges), len(falling_edges))):
324
+ rise_time = rising_edges[i].time
325
+ fall_time = falling_edges[i].time
326
+
327
+ # Find next edge of opposite type
328
+ if i + 1 < len(rising_edges):
329
+ next_rise = rising_edges[i + 1].time
330
+ period = next_rise - rise_time
331
+ if period > 0:
332
+ high_time = fall_time - rise_time
333
+ duty_cycle = high_time / period
334
+ duty_cycles.append(np.clip(duty_cycle, 0.0, 1.0))
335
+
336
+ duty_cycles_arr = np.array(duty_cycles) if duty_cycles else np.array([])
337
+
338
+ # Calculate jitter
339
+ if len(periods) > 1:
340
+ mean_period = np.mean(periods)
341
+ jitter_rms = np.std(periods)
342
+ jitter_pp = np.max(periods) - np.min(periods)
343
+ else:
344
+ mean_period = periods[0] if len(periods) > 0 else 0.0
345
+ jitter_rms = 0.0
346
+ jitter_pp = 0.0
347
+
348
+ return EdgeTiming(
349
+ periods=periods,
350
+ mean_period=float(mean_period),
351
+ std_period=float(np.std(periods)) if len(periods) > 0 else 0.0,
352
+ min_period=float(np.min(periods)) if len(periods) > 0 else 0.0,
353
+ max_period=float(np.max(periods)) if len(periods) > 0 else 0.0,
354
+ duty_cycles=duty_cycles_arr,
355
+ mean_duty_cycle=float(np.mean(duty_cycles_arr)) if len(duty_cycles_arr) > 0 else 0.0,
356
+ jitter_rms=float(jitter_rms),
357
+ jitter_pp=float(jitter_pp),
358
+ )
359
+
360
+
361
+ def check_timing_constraints(
362
+ edges: list[Edge], constraints: list[TimingConstraint], sample_rate: float = 1.0
363
+ ) -> list[TimingViolation]:
364
+ """Check edges against timing constraints.
365
+
366
+ Validates edge timing against specified constraints and reports violations.
367
+
368
+ Args:
369
+ edges: List of Edge objects to check.
370
+ constraints: List of TimingConstraint objects defining limits.
371
+ sample_rate: Sample rate in Hz.
372
+
373
+ Returns:
374
+ List of TimingViolation objects for any violations found.
375
+
376
+ Example:
377
+ >>> constraint = TimingConstraint(name="min_period", min_time=10e-9)
378
+ >>> violations = check_timing_constraints(edges, [constraint])
379
+ """
380
+ violations: list[TimingViolation] = []
381
+
382
+ if len(edges) < 2:
383
+ return violations
384
+
385
+ # Calculate periods between edges
386
+ for i in range(len(edges) - 1):
387
+ edge_time = edges[i].time
388
+ next_time = edges[i + 1].time
389
+ period = next_time - edge_time
390
+
391
+ for constraint in constraints:
392
+ # Check if constraint applies to this edge type
393
+ if constraint.reference:
394
+ if constraint.reference == "rising" and edges[i].edge_type != "rising":
395
+ continue
396
+ if constraint.reference == "falling" and edges[i].edge_type != "falling":
397
+ continue
398
+
399
+ # Check timing constraints
400
+ violated = False
401
+
402
+ if constraint.min_time is not None and period < constraint.min_time:
403
+ violated = True
404
+
405
+ if constraint.max_time is not None and period > constraint.max_time:
406
+ violated = True
407
+
408
+ if violated:
409
+ violations.append(
410
+ TimingViolation(
411
+ constraint=constraint,
412
+ measured_time=period,
413
+ edge_index=i,
414
+ sample_index=edges[i].sample_index,
415
+ )
416
+ )
417
+
418
+ return violations
419
+
420
+
421
+ def classify_edge_quality(
422
+ trace: NDArray[np.float64], edge_index: int, sample_rate: float
423
+ ) -> Literal["clean", "slow", "noisy", "glitch"]:
424
+ """Classify edge quality.
425
+
426
+ Analyzes the edge transition to classify its quality based on slew rate,
427
+ noise, and duration.
428
+
429
+ Args:
430
+ trace: Input signal trace.
431
+ edge_index: Sample index of the edge.
432
+ sample_rate: Sample rate in Hz.
433
+
434
+ Returns:
435
+ Quality classification: 'clean', 'slow', 'noisy', or 'glitch'.
436
+
437
+ Example:
438
+ >>> quality = classify_edge_quality(trace, 10, 100e6)
439
+ """
440
+ if edge_index < 1 or edge_index >= len(trace) - 1:
441
+ return "clean"
442
+
443
+ # Get window around edge
444
+ window_size = min(10, edge_index, len(trace) - edge_index - 1)
445
+ window = trace[edge_index - window_size : edge_index + window_size + 1]
446
+
447
+ # Calculate transition amplitude
448
+ v_before = trace[edge_index - 1]
449
+ v_after = trace[edge_index]
450
+ amplitude = abs(v_after - v_before)
451
+
452
+ # Check for glitch (very short duration)
453
+ if window_size < 3:
454
+ return "glitch"
455
+
456
+ # Calculate noise (std dev in window)
457
+ noise = np.std(window)
458
+
459
+ # Calculate slew rate
460
+ _slew_rate = amplitude * sample_rate
461
+
462
+ # Simple heuristic classification
463
+ signal_range = np.max(trace) - np.min(trace)
464
+
465
+ if amplitude < signal_range * 0.1:
466
+ return "glitch"
467
+
468
+ if noise > amplitude * 0.2:
469
+ return "noisy"
470
+
471
+ # Check if transition is slow (takes many samples)
472
+ transition_samples = 0
473
+ _threshold = (v_before + v_after) / 2.0
474
+
475
+ for i in range(max(0, edge_index - window_size), min(len(trace), edge_index + window_size)):
476
+ val = trace[i]
477
+ if v_before < v_after: # Rising
478
+ if v_before <= val <= v_after:
479
+ transition_samples += 1
480
+ else: # Falling
481
+ if v_after <= val <= v_before:
482
+ transition_samples += 1
483
+
484
+ if transition_samples > 5:
485
+ return "slow"
486
+
487
+ return "clean"
488
+
489
+
490
+ class EdgeDetector:
491
+ """Object-oriented wrapper for edge detection functionality.
492
+
493
+ Provides a class-based interface for edge detection operations,
494
+ wrapping the functional API for consistency with test expectations.
495
+
496
+
497
+
498
+ Example:
499
+ >>> detector = EdgeDetector()
500
+ >>> rising, falling = detector.detect_all_edges(signal_data)
501
+ """
502
+
503
+ def __init__(
504
+ self,
505
+ threshold: float | Literal["auto"] = "auto",
506
+ hysteresis: float = 0.0,
507
+ sample_rate: float = 1.0,
508
+ min_pulse_width: int | None = None,
509
+ ):
510
+ """Initialize edge detector.
511
+
512
+ Args:
513
+ threshold: Detection threshold. 'auto' computes from signal midpoint.
514
+ hysteresis: Hysteresis amount for noise immunity (signal units).
515
+ sample_rate: Sample rate in Hz for time calculation.
516
+ min_pulse_width: Minimum pulse width in samples to filter noise.
517
+ """
518
+ self.threshold = threshold
519
+ self.hysteresis = hysteresis
520
+ self.sample_rate = sample_rate
521
+ self.min_pulse_width = min_pulse_width
522
+
523
+ def detect_all_edges(
524
+ self, trace: NDArray[np.float64]
525
+ ) -> tuple[NDArray[np.intp], NDArray[np.intp]]:
526
+ """Detect all rising and falling edges.
527
+
528
+ Args:
529
+ trace: Input signal trace (analog or digital).
530
+
531
+ Returns:
532
+ Tuple of (rising_edge_indices, falling_edge_indices).
533
+
534
+ Example:
535
+ >>> detector = EdgeDetector(sample_rate=100e6)
536
+ >>> rising, falling = detector.detect_all_edges(signal)
537
+ """
538
+ edges = detect_edges(
539
+ trace,
540
+ edge_type="both",
541
+ threshold=self.threshold,
542
+ hysteresis=self.hysteresis,
543
+ sample_rate=self.sample_rate,
544
+ )
545
+
546
+ # Filter by min_pulse_width if specified
547
+ if self.min_pulse_width is not None and len(edges) > 1:
548
+ filtered_edges = []
549
+ for i, edge in enumerate(edges):
550
+ if i == 0:
551
+ filtered_edges.append(edge)
552
+ continue
553
+ # Check distance to previous edge
554
+ dist = edge.sample_index - edges[i - 1].sample_index
555
+ if dist >= self.min_pulse_width:
556
+ filtered_edges.append(edge)
557
+ edges = filtered_edges
558
+
559
+ rising_indices = np.array(
560
+ [e.sample_index for e in edges if e.edge_type == "rising"], dtype=np.int64
561
+ )
562
+ falling_indices = np.array(
563
+ [e.sample_index for e in edges if e.edge_type == "falling"], dtype=np.int64
564
+ )
565
+
566
+ return rising_indices, falling_indices
567
+
568
+ def detect_rising_edges(self, trace: NDArray[np.float64]) -> list[Edge]:
569
+ """Detect only rising edges.
570
+
571
+ Args:
572
+ trace: Input signal trace.
573
+
574
+ Returns:
575
+ List of Edge objects for rising edges.
576
+ """
577
+ return detect_edges(
578
+ trace,
579
+ edge_type="rising",
580
+ threshold=self.threshold,
581
+ hysteresis=self.hysteresis,
582
+ sample_rate=self.sample_rate,
583
+ )
584
+
585
+ def detect_falling_edges(self, trace: NDArray[np.float64]) -> list[Edge]:
586
+ """Detect only falling edges.
587
+
588
+ Args:
589
+ trace: Input signal trace.
590
+
591
+ Returns:
592
+ List of Edge objects for falling edges.
593
+ """
594
+ return detect_edges(
595
+ trace,
596
+ edge_type="falling",
597
+ threshold=self.threshold,
598
+ hysteresis=self.hysteresis,
599
+ sample_rate=self.sample_rate,
600
+ )
601
+
602
+ def measure_timing(self, trace: NDArray[np.float64]) -> EdgeTiming:
603
+ """Detect edges and measure timing.
604
+
605
+ Args:
606
+ trace: Input signal trace.
607
+
608
+ Returns:
609
+ EdgeTiming object with timing measurements.
610
+ """
611
+ edges = detect_edges(
612
+ trace,
613
+ edge_type="both",
614
+ threshold=self.threshold,
615
+ hysteresis=self.hysteresis,
616
+ sample_rate=self.sample_rate,
617
+ )
618
+ return measure_edge_timing(edges, self.sample_rate)
619
+
620
+
621
+ __all__ = [
622
+ "Edge",
623
+ "EdgeDetector",
624
+ "EdgeTiming",
625
+ "TimingConstraint",
626
+ "TimingViolation",
627
+ "check_timing_constraints",
628
+ "classify_edge_quality",
629
+ "detect_edges",
630
+ "interpolate_edge_time",
631
+ "measure_edge_timing",
632
+ ]