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,32 @@
1
+ """Filter convenience functions namespace.
2
+
3
+ This module provides a namespace for filter functions to support:
4
+ from oscura.filtering import filters
5
+ filters.low_pass(trace, cutoff=1000)
6
+
7
+ Re-exports convenience functions from the filtering package.
8
+ """
9
+
10
+ from oscura.filtering.convenience import (
11
+ band_pass,
12
+ band_stop,
13
+ high_pass,
14
+ low_pass,
15
+ matched_filter,
16
+ median_filter,
17
+ moving_average,
18
+ notch_filter,
19
+ savgol_filter,
20
+ )
21
+
22
+ __all__ = [
23
+ "band_pass",
24
+ "band_stop",
25
+ "high_pass",
26
+ "low_pass",
27
+ "matched_filter",
28
+ "median_filter",
29
+ "moving_average",
30
+ "notch_filter",
31
+ "savgol_filter",
32
+ ]
@@ -0,0 +1,605 @@
1
+ """Filter introspection and visualization for Oscura.
2
+
3
+ Provides filter analysis tools including Bode plots, impulse response,
4
+ step response, and pole-zero diagrams.
5
+
6
+
7
+ Example:
8
+ >>> from oscura.filtering import LowPassFilter, plot_bode
9
+ >>> filt = LowPassFilter(cutoff=1e6, sample_rate=10e6, order=4)
10
+ >>> fig = plot_bode(filt)
11
+ >>> plt.show()
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ from typing import TYPE_CHECKING
17
+
18
+ import numpy as np
19
+
20
+ if TYPE_CHECKING:
21
+ from matplotlib.figure import Figure
22
+ from numpy.typing import NDArray
23
+
24
+ from oscura.filtering.base import Filter, IIRFilter
25
+
26
+
27
+ class FilterIntrospection:
28
+ """Mixin class providing filter introspection methods.
29
+
30
+ Provides methods for analyzing filter characteristics including
31
+ frequency response, impulse response, step response, and stability.
32
+ """
33
+
34
+ def __init__(self, filter_obj: Filter) -> None:
35
+ """Initialize with a filter object.
36
+
37
+ Args:
38
+ filter_obj: Filter to introspect.
39
+ """
40
+ self._filter = filter_obj
41
+
42
+ @property
43
+ def filter(self) -> Filter:
44
+ """The wrapped filter object.
45
+
46
+ Returns:
47
+ The filter being introspected.
48
+ """
49
+ return self._filter
50
+
51
+ def magnitude_response(
52
+ self,
53
+ freqs: NDArray[np.float64] | None = None,
54
+ db: bool = True,
55
+ ) -> tuple[NDArray[np.float64], NDArray[np.float64]]:
56
+ """Get magnitude response.
57
+
58
+ Args:
59
+ freqs: Frequencies in Hz. If None, auto-generate.
60
+ db: If True, return magnitude in dB.
61
+
62
+ Returns:
63
+ Tuple of (frequencies, magnitude).
64
+
65
+ Raises:
66
+ ValueError: If freqs is None and filter has no sample_rate.
67
+ """
68
+ if freqs is None:
69
+ if self._filter.sample_rate is None:
70
+ raise ValueError(
71
+ "Either freqs must be provided or filter must have sample_rate set"
72
+ )
73
+ freqs = np.linspace(0, self._filter.sample_rate / 2, 512)
74
+
75
+ h = self._filter.get_transfer_function(freqs)
76
+ mag = np.abs(h)
77
+
78
+ if db:
79
+ mag = 20 * np.log10(np.maximum(mag, 1e-12))
80
+
81
+ return freqs, mag
82
+
83
+ def phase_response(
84
+ self,
85
+ freqs: NDArray[np.float64] | None = None,
86
+ unwrap: bool = True,
87
+ degrees: bool = True,
88
+ ) -> tuple[NDArray[np.float64], NDArray[np.float64]]:
89
+ """Get phase response.
90
+
91
+ Args:
92
+ freqs: Frequencies in Hz. If None, auto-generate.
93
+ unwrap: If True, unwrap phase to remove discontinuities.
94
+ degrees: If True, return phase in degrees.
95
+
96
+ Returns:
97
+ Tuple of (frequencies, phase).
98
+
99
+ Raises:
100
+ ValueError: If freqs is None and filter has no sample_rate.
101
+ """
102
+ if freqs is None:
103
+ if self._filter.sample_rate is None:
104
+ raise ValueError(
105
+ "Either freqs must be provided or filter must have sample_rate set"
106
+ )
107
+ freqs = np.linspace(0, self._filter.sample_rate / 2, 512)
108
+
109
+ h = self._filter.get_transfer_function(freqs)
110
+ phase = np.angle(h)
111
+
112
+ if unwrap:
113
+ phase = np.unwrap(phase)
114
+
115
+ if degrees:
116
+ phase = np.degrees(phase)
117
+
118
+ return freqs, phase
119
+
120
+ def group_delay_hz(
121
+ self,
122
+ freqs: NDArray[np.float64] | None = None,
123
+ ) -> tuple[NDArray[np.float64], NDArray[np.float64]]:
124
+ """Get group delay in seconds.
125
+
126
+ Args:
127
+ freqs: Frequencies in Hz. If None, auto-generate.
128
+
129
+ Returns:
130
+ Tuple of (frequencies in Hz, group delay in seconds).
131
+ """
132
+ w, gd_samples = self._filter.get_group_delay()
133
+
134
+ if self._filter.sample_rate is not None:
135
+ freqs_out = w * self._filter.sample_rate / (2 * np.pi)
136
+ gd_seconds = gd_samples / self._filter.sample_rate
137
+ else:
138
+ freqs_out = w
139
+ gd_seconds = gd_samples
140
+
141
+ return freqs_out, gd_seconds
142
+
143
+ def passband_ripple(
144
+ self,
145
+ passband_edge: float,
146
+ ) -> float:
147
+ """Calculate passband ripple in dB.
148
+
149
+ Args:
150
+ passband_edge: Passband edge frequency in Hz.
151
+
152
+ Returns:
153
+ Peak-to-peak ripple in dB within passband.
154
+
155
+ Raises:
156
+ ValueError: If filter sample_rate is not set.
157
+ """
158
+ if self._filter.sample_rate is None:
159
+ raise ValueError("Sample rate must be set")
160
+
161
+ freqs = np.linspace(0, passband_edge, 256)
162
+ _, mag_db = self.magnitude_response(freqs, db=True)
163
+
164
+ return float(np.max(mag_db) - np.min(mag_db))
165
+
166
+ def stopband_attenuation(
167
+ self,
168
+ stopband_edge: float,
169
+ ) -> float:
170
+ """Calculate minimum stopband attenuation in dB.
171
+
172
+ Args:
173
+ stopband_edge: Stopband edge frequency in Hz.
174
+
175
+ Returns:
176
+ Minimum attenuation in stopband in dB (positive value).
177
+
178
+ Raises:
179
+ ValueError: If filter sample_rate is not set.
180
+ """
181
+ if self._filter.sample_rate is None:
182
+ raise ValueError("Sample rate must be set")
183
+
184
+ freqs = np.linspace(stopband_edge, self._filter.sample_rate / 2, 256)
185
+ _, mag_db = self.magnitude_response(freqs, db=True)
186
+
187
+ return float(-np.max(mag_db))
188
+
189
+ def cutoff_frequency(
190
+ self,
191
+ threshold_db: float = -3.0,
192
+ ) -> float:
193
+ """Find -3dB cutoff frequency.
194
+
195
+ Args:
196
+ threshold_db: Threshold in dB (default -3dB).
197
+
198
+ Returns:
199
+ Cutoff frequency in Hz.
200
+
201
+ Raises:
202
+ ValueError: If filter sample_rate is not set.
203
+ """
204
+ if self._filter.sample_rate is None:
205
+ raise ValueError("Sample rate must be set")
206
+
207
+ freqs = np.linspace(0, self._filter.sample_rate / 2, 1000)
208
+ _, mag_db = self.magnitude_response(freqs, db=True)
209
+
210
+ # Normalize to 0dB at DC
211
+ mag_db = mag_db - mag_db[0]
212
+
213
+ # Find first crossing of threshold
214
+ crossings = np.where(mag_db < threshold_db)[0]
215
+ if len(crossings) == 0:
216
+ return float(freqs[-1])
217
+
218
+ return float(freqs[crossings[0]])
219
+
220
+
221
+ def plot_bode(
222
+ filt: Filter,
223
+ *,
224
+ figsize: tuple[float, float] = (10, 8),
225
+ freq_range: tuple[float, float] | None = None,
226
+ n_points: int = 512,
227
+ title: str | None = None,
228
+ ) -> Figure:
229
+ """Plot Bode diagram (magnitude and phase response).
230
+
231
+ Args:
232
+ filt: Filter to plot.
233
+ figsize: Figure size in inches.
234
+ freq_range: Frequency range (min, max) in Hz. None for auto.
235
+ n_points: Number of frequency points.
236
+ title: Plot title.
237
+
238
+ Returns:
239
+ Matplotlib Figure object.
240
+
241
+ Raises:
242
+ ValueError: If filter sample_rate is not set.
243
+
244
+ Example:
245
+ >>> fig = plot_bode(filt)
246
+ >>> plt.show()
247
+ """
248
+ import matplotlib.pyplot as plt
249
+
250
+ if filt.sample_rate is None:
251
+ raise ValueError("Filter sample rate must be set for plotting")
252
+
253
+ if freq_range is None:
254
+ freq_range = (1, filt.sample_rate / 2)
255
+
256
+ freqs = np.geomspace(freq_range[0], freq_range[1], n_points)
257
+
258
+ introspect = FilterIntrospection(filt)
259
+ _, mag_db = introspect.magnitude_response(freqs, db=True)
260
+ _, phase_deg = introspect.phase_response(freqs, degrees=True)
261
+
262
+ fig, (ax1, ax2) = plt.subplots(2, 1, figsize=figsize, sharex=True)
263
+
264
+ # Magnitude plot
265
+ ax1.semilogx(freqs, mag_db)
266
+ ax1.set_ylabel("Magnitude (dB)")
267
+ ax1.grid(True, which="both", alpha=0.3)
268
+ ax1.axhline(-3, color="r", linestyle="--", alpha=0.5, label="-3 dB")
269
+ ax1.legend()
270
+
271
+ # Phase plot
272
+ ax2.semilogx(freqs, phase_deg)
273
+ ax2.set_xlabel("Frequency (Hz)")
274
+ ax2.set_ylabel("Phase (degrees)")
275
+ ax2.grid(True, which="both", alpha=0.3)
276
+
277
+ if title:
278
+ fig.suptitle(title)
279
+ else:
280
+ fig.suptitle(f"Bode Plot - Order {filt.order} Filter")
281
+
282
+ plt.tight_layout()
283
+ return fig
284
+
285
+
286
+ def plot_impulse(
287
+ filt: Filter,
288
+ *,
289
+ n_samples: int = 256,
290
+ figsize: tuple[float, float] = (10, 4),
291
+ title: str | None = None,
292
+ ) -> Figure:
293
+ """Plot impulse response.
294
+
295
+ Args:
296
+ filt: Filter to plot.
297
+ n_samples: Number of samples in response.
298
+ figsize: Figure size in inches.
299
+ title: Plot title.
300
+
301
+ Returns:
302
+ Matplotlib Figure object.
303
+ """
304
+ import matplotlib.pyplot as plt
305
+
306
+ impulse = filt.get_impulse_response(n_samples)
307
+
308
+ fig, ax = plt.subplots(figsize=figsize)
309
+
310
+ if filt.sample_rate is not None:
311
+ t = np.arange(n_samples) / filt.sample_rate * 1e6 # microseconds
312
+ ax.plot(t, impulse)
313
+ ax.set_xlabel("Time (us)")
314
+ else:
315
+ ax.plot(impulse)
316
+ ax.set_xlabel("Samples")
317
+
318
+ ax.set_ylabel("Amplitude")
319
+ ax.grid(True, alpha=0.3)
320
+ ax.axhline(0, color="k", linewidth=0.5)
321
+
322
+ if title:
323
+ ax.set_title(title)
324
+ else:
325
+ ax.set_title("Impulse Response")
326
+
327
+ plt.tight_layout()
328
+ return fig
329
+
330
+
331
+ def plot_step(
332
+ filt: Filter,
333
+ *,
334
+ n_samples: int = 256,
335
+ figsize: tuple[float, float] = (10, 4),
336
+ title: str | None = None,
337
+ ) -> Figure:
338
+ """Plot step response.
339
+
340
+ Args:
341
+ filt: Filter to plot.
342
+ n_samples: Number of samples in response.
343
+ figsize: Figure size in inches.
344
+ title: Plot title.
345
+
346
+ Returns:
347
+ Matplotlib Figure object.
348
+ """
349
+ import matplotlib.pyplot as plt
350
+
351
+ step = filt.get_step_response(n_samples)
352
+
353
+ fig, ax = plt.subplots(figsize=figsize)
354
+
355
+ if filt.sample_rate is not None:
356
+ t = np.arange(n_samples) / filt.sample_rate * 1e6 # microseconds
357
+ ax.plot(t, step)
358
+ ax.set_xlabel("Time (us)")
359
+ else:
360
+ ax.plot(step)
361
+ ax.set_xlabel("Samples")
362
+
363
+ ax.set_ylabel("Amplitude")
364
+ ax.grid(True, alpha=0.3)
365
+ ax.axhline(1, color="r", linestyle="--", alpha=0.5, label="Final value")
366
+ ax.legend()
367
+
368
+ if title:
369
+ ax.set_title(title)
370
+ else:
371
+ ax.set_title("Step Response")
372
+
373
+ plt.tight_layout()
374
+ return fig
375
+
376
+
377
+ def plot_poles_zeros(
378
+ filt: Filter,
379
+ *,
380
+ figsize: tuple[float, float] = (8, 8),
381
+ title: str | None = None,
382
+ ) -> Figure:
383
+ """Plot pole-zero diagram for IIR filter.
384
+
385
+ Args:
386
+ filt: IIR filter to plot.
387
+ figsize: Figure size in inches.
388
+ title: Plot title.
389
+
390
+ Returns:
391
+ Matplotlib Figure object.
392
+
393
+ Raises:
394
+ ValueError: If filter is not an IIRFilter.
395
+ """
396
+ import matplotlib.pyplot as plt
397
+
398
+ if not isinstance(filt, IIRFilter):
399
+ raise ValueError("Pole-zero plot only available for IIR filters")
400
+
401
+ poles = filt.poles
402
+ zeros = filt.zeros
403
+
404
+ fig, ax = plt.subplots(figsize=figsize)
405
+
406
+ # Draw unit circle
407
+ theta = np.linspace(0, 2 * np.pi, 100)
408
+ ax.plot(np.cos(theta), np.sin(theta), "k--", alpha=0.3, label="Unit circle")
409
+
410
+ # Plot poles and zeros
411
+ ax.scatter(
412
+ np.real(zeros),
413
+ np.imag(zeros),
414
+ marker="o",
415
+ s=100,
416
+ facecolors="none",
417
+ edgecolors="b",
418
+ linewidths=2,
419
+ label="Zeros",
420
+ )
421
+ ax.scatter(
422
+ np.real(poles),
423
+ np.imag(poles),
424
+ marker="x",
425
+ s=100,
426
+ c="r",
427
+ linewidths=2,
428
+ label="Poles",
429
+ )
430
+
431
+ ax.set_xlabel("Real")
432
+ ax.set_ylabel("Imaginary")
433
+ ax.set_aspect("equal")
434
+ ax.grid(True, alpha=0.3)
435
+ ax.legend()
436
+
437
+ # Stability indicator
438
+ is_stable = np.all(np.abs(poles) < 1.0)
439
+ stability_text = "STABLE" if is_stable else "UNSTABLE"
440
+ stability_color = "green" if is_stable else "red"
441
+ ax.text(
442
+ 0.95,
443
+ 0.95,
444
+ stability_text,
445
+ transform=ax.transAxes,
446
+ fontsize=12,
447
+ fontweight="bold",
448
+ color=stability_color,
449
+ ha="right",
450
+ va="top",
451
+ )
452
+
453
+ if title:
454
+ ax.set_title(title)
455
+ else:
456
+ ax.set_title(f"Pole-Zero Plot (Order {filt.order})")
457
+
458
+ plt.tight_layout()
459
+ return fig
460
+
461
+
462
+ def plot_group_delay(
463
+ filt: Filter,
464
+ *,
465
+ figsize: tuple[float, float] = (10, 4),
466
+ freq_range: tuple[float, float] | None = None,
467
+ n_points: int = 512,
468
+ title: str | None = None,
469
+ ) -> Figure:
470
+ """Plot group delay.
471
+
472
+ Args:
473
+ filt: Filter to plot.
474
+ figsize: Figure size in inches.
475
+ freq_range: Frequency range (min, max) in Hz.
476
+ n_points: Number of frequency points.
477
+ title: Plot title.
478
+
479
+ Returns:
480
+ Matplotlib Figure object.
481
+
482
+ Raises:
483
+ ValueError: If filter sample_rate is not set.
484
+ """
485
+ import matplotlib.pyplot as plt
486
+
487
+ if filt.sample_rate is None:
488
+ raise ValueError("Filter sample rate must be set for plotting")
489
+
490
+ introspect = FilterIntrospection(filt)
491
+ freqs, gd = introspect.group_delay_hz()
492
+
493
+ fig, ax = plt.subplots(figsize=figsize)
494
+
495
+ ax.semilogx(freqs, gd * 1e6) # Convert to microseconds
496
+ ax.set_xlabel("Frequency (Hz)")
497
+ ax.set_ylabel("Group Delay (us)")
498
+ ax.grid(True, which="both", alpha=0.3)
499
+
500
+ if title:
501
+ ax.set_title(title)
502
+ else:
503
+ ax.set_title("Group Delay")
504
+
505
+ plt.tight_layout()
506
+ return fig
507
+
508
+
509
+ def compare_filters(
510
+ filters: list[Filter],
511
+ labels: list[str] | None = None,
512
+ *,
513
+ figsize: tuple[float, float] = (12, 10),
514
+ freq_range: tuple[float, float] | None = None,
515
+ n_points: int = 512,
516
+ ) -> Figure:
517
+ """Compare multiple filters on the same plots.
518
+
519
+ Args:
520
+ filters: List of filters to compare.
521
+ labels: Labels for each filter. If None, uses "Filter 1", etc.
522
+ figsize: Figure size in inches.
523
+ freq_range: Frequency range (min, max) in Hz.
524
+ n_points: Number of frequency points.
525
+
526
+ Returns:
527
+ Matplotlib Figure object with comparison plots.
528
+
529
+ Raises:
530
+ ValueError: If number of labels doesn't match number of filters or if filter sample_rate is not set.
531
+ """
532
+ import matplotlib.pyplot as plt
533
+
534
+ if labels is None:
535
+ labels = [f"Filter {i + 1}" for i in range(len(filters))]
536
+
537
+ if len(labels) != len(filters):
538
+ raise ValueError("Number of labels must match number of filters")
539
+
540
+ # Use first filter's sample rate for frequency axis
541
+ sample_rate = filters[0].sample_rate
542
+ if sample_rate is None:
543
+ raise ValueError("Filter sample rate must be set for plotting")
544
+
545
+ if freq_range is None:
546
+ freq_range = (1, sample_rate / 2)
547
+
548
+ freqs = np.geomspace(freq_range[0], freq_range[1], n_points)
549
+
550
+ fig, axes = plt.subplots(2, 2, figsize=figsize)
551
+
552
+ for filt, label in zip(filters, labels, strict=False):
553
+ introspect = FilterIntrospection(filt)
554
+ _, mag_db = introspect.magnitude_response(freqs, db=True)
555
+ _, phase_deg = introspect.phase_response(freqs, degrees=True)
556
+ impulse = filt.get_impulse_response(256)
557
+ step = filt.get_step_response(256)
558
+
559
+ # Magnitude
560
+ axes[0, 0].semilogx(freqs, mag_db, label=label)
561
+ # Phase
562
+ axes[0, 1].semilogx(freqs, phase_deg, label=label)
563
+ # Impulse
564
+ axes[1, 0].plot(impulse, label=label)
565
+ # Step
566
+ axes[1, 1].plot(step, label=label)
567
+
568
+ axes[0, 0].set_ylabel("Magnitude (dB)")
569
+ axes[0, 0].set_title("Magnitude Response")
570
+ axes[0, 0].grid(True, which="both", alpha=0.3)
571
+ axes[0, 0].axhline(-3, color="k", linestyle="--", alpha=0.3)
572
+ axes[0, 0].legend()
573
+
574
+ axes[0, 1].set_ylabel("Phase (degrees)")
575
+ axes[0, 1].set_title("Phase Response")
576
+ axes[0, 1].grid(True, which="both", alpha=0.3)
577
+ axes[0, 1].legend()
578
+
579
+ axes[1, 0].set_xlabel("Samples")
580
+ axes[1, 0].set_ylabel("Amplitude")
581
+ axes[1, 0].set_title("Impulse Response")
582
+ axes[1, 0].grid(True, alpha=0.3)
583
+ axes[1, 0].legend()
584
+
585
+ axes[1, 1].set_xlabel("Samples")
586
+ axes[1, 1].set_ylabel("Amplitude")
587
+ axes[1, 1].set_title("Step Response")
588
+ axes[1, 1].grid(True, alpha=0.3)
589
+ axes[1, 1].axhline(1, color="k", linestyle="--", alpha=0.3)
590
+ axes[1, 1].legend()
591
+
592
+ fig.suptitle("Filter Comparison")
593
+ plt.tight_layout()
594
+ return fig
595
+
596
+
597
+ __all__ = [
598
+ "FilterIntrospection",
599
+ "compare_filters",
600
+ "plot_bode",
601
+ "plot_group_delay",
602
+ "plot_impulse",
603
+ "plot_poles_zeros",
604
+ "plot_step",
605
+ ]
@@ -0,0 +1,24 @@
1
+ """Oscura guidance module.
2
+
3
+ Provides guided analysis workflows and recommendations.
4
+ """
5
+
6
+ from oscura.guidance.recommender import (
7
+ AnalysisHistory,
8
+ Recommendation,
9
+ suggest_next_steps,
10
+ )
11
+ from oscura.guidance.wizard import (
12
+ AnalysisWizard,
13
+ WizardResult,
14
+ WizardStep,
15
+ )
16
+
17
+ __all__ = [
18
+ "AnalysisHistory",
19
+ "AnalysisWizard",
20
+ "Recommendation",
21
+ "WizardResult",
22
+ "WizardStep",
23
+ "suggest_next_steps",
24
+ ]