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
oscura/core/config.py ADDED
@@ -0,0 +1,506 @@
1
+ """Oscura configuration loading and management.
2
+
3
+ This module provides configuration loading from YAML files with support
4
+ for nested structures, user overrides, and schema validation.
5
+
6
+
7
+ Example:
8
+ >>> from oscura.core.config import load_config
9
+ >>> config = load_config()
10
+ >>> print(config["defaults"]["sample_rate"])
11
+ 1000000.0
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import copy
17
+ from pathlib import Path
18
+ from typing import Any
19
+
20
+ import numpy as np
21
+
22
+ try:
23
+ import yaml
24
+
25
+ YAML_AVAILABLE = True
26
+ except ImportError:
27
+ YAML_AVAILABLE = False
28
+
29
+ from oscura.core.exceptions import ConfigurationError
30
+
31
+ # Default configuration values
32
+ DEFAULT_CONFIG: dict[str, Any] = {
33
+ "version": "1.0",
34
+ "defaults": {
35
+ "sample_rate": 1e6, # 1 MHz default
36
+ "window_function": "hann",
37
+ "fft_size": 1024,
38
+ },
39
+ "loaders": {
40
+ "auto_detect": True,
41
+ "formats": ["wfm", "csv", "npz", "hdf5", "tdms", "vcd", "sr", "wav"],
42
+ "tektronix": {"byte_order": "little"},
43
+ "csv": {"delimiter": ",", "skip_header": 0},
44
+ },
45
+ "measurements": {
46
+ "rise_time": {"ref_levels": [0.1, 0.9]},
47
+ "fall_time": {"ref_levels": [0.9, 0.1]},
48
+ "frequency": {"min_periods": 3},
49
+ },
50
+ "spectral": {
51
+ "default_window": "hann",
52
+ "overlap": 0.5,
53
+ "nfft": None, # Auto-determine from signal length
54
+ },
55
+ "visualization": {
56
+ "default_style": "seaborn",
57
+ "figure_size": [10, 6],
58
+ "dpi": 100,
59
+ },
60
+ "export": {
61
+ "csv": {"precision": 6},
62
+ "hdf5": {"compression": "gzip", "compression_opts": 4},
63
+ },
64
+ }
65
+
66
+
67
+ def _deep_merge(base: dict[str, Any], override: dict[str, Any]) -> dict[str, Any]:
68
+ """Recursively merge two dictionaries.
69
+
70
+ Values from override take precedence. Nested dictionaries are
71
+ merged recursively.
72
+
73
+ Args:
74
+ base: Base dictionary.
75
+ override: Dictionary with values to override.
76
+
77
+ Returns:
78
+ Merged dictionary.
79
+ """
80
+ result = base.copy()
81
+
82
+ for key, value in override.items():
83
+ if key in result and isinstance(result[key], dict) and isinstance(value, dict):
84
+ result[key] = _deep_merge(result[key], value)
85
+ else:
86
+ result[key] = value
87
+
88
+ return result
89
+
90
+
91
+ def load_config(
92
+ config_path: str | Path | None = None,
93
+ *,
94
+ use_defaults: bool = True,
95
+ ) -> dict[str, Any]:
96
+ """Load configuration from YAML file.
97
+
98
+ Loads configuration from the specified path and optionally merges
99
+ with default values. If no path is specified, looks for configuration
100
+ in standard locations.
101
+
102
+ Args:
103
+ config_path: Path to YAML configuration file. If None, searches
104
+ for config in standard locations.
105
+ use_defaults: If True, merge loaded config with defaults.
106
+
107
+ Returns:
108
+ Configuration dictionary.
109
+
110
+ Raises:
111
+ ConfigurationError: If configuration file cannot be loaded or parsed.
112
+
113
+ Example:
114
+ >>> config = load_config()
115
+ >>> print(config["defaults"]["sample_rate"])
116
+ 1000000.0
117
+
118
+ >>> config = load_config("~/.oscura/config.yaml")
119
+ >>> print(config["measurements"]["rise_time"]["ref_levels"])
120
+ [0.1, 0.9]
121
+ """
122
+ config: dict[str, Any] = {}
123
+
124
+ if use_defaults:
125
+ config = copy.deepcopy(DEFAULT_CONFIG)
126
+
127
+ if config_path is None:
128
+ # Search standard locations
129
+ search_paths = [
130
+ Path.cwd() / "oscura.yaml",
131
+ Path.cwd() / ".oscura.yaml",
132
+ Path.home() / ".oscura" / "config.yaml",
133
+ Path.home() / ".config" / "oscura" / "config.yaml",
134
+ ]
135
+
136
+ for path in search_paths:
137
+ if path.exists():
138
+ config_path = path
139
+ break
140
+
141
+ if config_path is not None:
142
+ config_path = Path(config_path).expanduser()
143
+
144
+ if not config_path.exists():
145
+ raise ConfigurationError(
146
+ "Configuration file not found",
147
+ config_key=str(config_path),
148
+ fix_hint=f"Create configuration file at {config_path}",
149
+ )
150
+
151
+ if not YAML_AVAILABLE:
152
+ raise ConfigurationError(
153
+ "YAML support not available",
154
+ details="PyYAML package is required for configuration loading",
155
+ fix_hint="Install PyYAML: pip install pyyaml",
156
+ )
157
+
158
+ try:
159
+ with open(config_path, encoding="utf-8") as f:
160
+ user_config = yaml.safe_load(f)
161
+ except yaml.YAMLError as e:
162
+ raise ConfigurationError(
163
+ "Failed to parse configuration file",
164
+ config_key=str(config_path),
165
+ details=str(e),
166
+ ) from e
167
+ except OSError as e:
168
+ raise ConfigurationError(
169
+ "Failed to read configuration file",
170
+ config_key=str(config_path),
171
+ details=str(e),
172
+ ) from e
173
+
174
+ if user_config is not None:
175
+ config = _deep_merge(config, user_config) if use_defaults else user_config
176
+
177
+ return config
178
+
179
+
180
+ def validate_config(config: dict[str, Any]) -> bool:
181
+ """Validate configuration against schema.
182
+
183
+ Checks that required fields exist and have valid types.
184
+
185
+ Args:
186
+ config: Configuration dictionary to validate.
187
+
188
+ Returns:
189
+ True if configuration is valid.
190
+
191
+ Raises:
192
+ ConfigurationError: If configuration is invalid.
193
+
194
+ Example:
195
+ >>> config = load_config()
196
+ >>> validate_config(config)
197
+ True
198
+ """
199
+ # Required top-level sections
200
+ required_sections = ["defaults", "loaders"]
201
+
202
+ for section in required_sections:
203
+ if section not in config:
204
+ raise ConfigurationError(
205
+ f"Missing required configuration section: {section}",
206
+ config_key=section,
207
+ )
208
+
209
+ # Validate defaults section
210
+ defaults = config.get("defaults", {})
211
+ if "sample_rate" in defaults:
212
+ sample_rate = defaults["sample_rate"]
213
+ if not isinstance(sample_rate, int | float) or sample_rate <= 0:
214
+ raise ConfigurationError(
215
+ "Invalid sample_rate in defaults",
216
+ config_key="defaults.sample_rate",
217
+ expected_type="positive number",
218
+ actual_value=sample_rate,
219
+ )
220
+
221
+ # Validate loaders section
222
+ loaders = config.get("loaders", {})
223
+ if "formats" in loaders:
224
+ formats = loaders["formats"]
225
+ if not isinstance(formats, list):
226
+ raise ConfigurationError(
227
+ "Invalid formats in loaders",
228
+ config_key="loaders.formats",
229
+ expected_type="list of strings",
230
+ actual_value=type(formats).__name__,
231
+ )
232
+
233
+ # Validate measurements section
234
+ measurements = config.get("measurements", {})
235
+ for name, settings in measurements.items():
236
+ if "ref_levels" in settings:
237
+ ref_levels = settings["ref_levels"]
238
+ if not isinstance(ref_levels, list) or len(ref_levels) != 2:
239
+ raise ConfigurationError(
240
+ f"Invalid ref_levels for {name}",
241
+ config_key=f"measurements.{name}.ref_levels",
242
+ expected_type="list of 2 numbers",
243
+ actual_value=ref_levels,
244
+ )
245
+
246
+ return True
247
+
248
+
249
+ def get_config_value(
250
+ config: dict[str, Any],
251
+ key_path: str,
252
+ default: Any = None,
253
+ ) -> Any:
254
+ """Get a configuration value by dot-separated path.
255
+
256
+ Args:
257
+ config: Configuration dictionary.
258
+ key_path: Dot-separated path to the value (e.g., "defaults.sample_rate").
259
+ default: Default value if key not found.
260
+
261
+ Returns:
262
+ Configuration value or default.
263
+
264
+ Example:
265
+ >>> config = load_config()
266
+ >>> get_config_value(config, "defaults.sample_rate", 1e6)
267
+ 1000000.0
268
+ >>> get_config_value(config, "unknown.key", "default")
269
+ 'default'
270
+ """
271
+ keys = key_path.split(".")
272
+ value = config
273
+
274
+ for key in keys:
275
+ if isinstance(value, dict) and key in value:
276
+ value = value[key]
277
+ else:
278
+ return default
279
+
280
+ return value
281
+
282
+
283
+ def save_config(config: dict[str, Any], config_path: str | Path) -> None:
284
+ """Save configuration to YAML file.
285
+
286
+ Args:
287
+ config: Configuration dictionary to save.
288
+ config_path: Path to save configuration to.
289
+
290
+ Raises:
291
+ ConfigurationError: If configuration cannot be saved.
292
+
293
+ Example:
294
+ >>> config = load_config()
295
+ >>> config["defaults"]["sample_rate"] = 2e6
296
+ >>> save_config(config, "~/my_config.yaml")
297
+ """
298
+ if not YAML_AVAILABLE:
299
+ raise ConfigurationError(
300
+ "YAML support not available",
301
+ details="PyYAML package is required for configuration saving",
302
+ fix_hint="Install PyYAML: pip install pyyaml",
303
+ )
304
+
305
+ config_path = Path(config_path).expanduser()
306
+
307
+ # Create parent directory if needed
308
+ config_path.parent.mkdir(parents=True, exist_ok=True)
309
+
310
+ try:
311
+ with open(config_path, "w", encoding="utf-8") as f:
312
+ yaml.dump(config, f, default_flow_style=False, sort_keys=False)
313
+ except OSError as e:
314
+ raise ConfigurationError(
315
+ "Failed to save configuration file",
316
+ config_key=str(config_path),
317
+ details=str(e),
318
+ ) from e
319
+
320
+
321
+ class SmartDefaults:
322
+ """Intelligent defaults configuration.
323
+
324
+ Provides smart default parameters that work for 80% of use cases,
325
+ with automatic parameter selection based on signal characteristics.
326
+
327
+ All auto-selected parameters are logged with rationale for transparency.
328
+ Users can override any parameter.
329
+
330
+ Example:
331
+ >>> from oscura.core.config import SmartDefaults
332
+ >>> defaults = SmartDefaults()
333
+ >>> # Get smart default for FFT size based on signal length
334
+ >>> fft_size = defaults.get_fft_size(signal_length=10000)
335
+ >>> print(fft_size)
336
+ 8192
337
+
338
+ References:
339
+ Best practices from scipy.signal, matplotlib, and numpy
340
+ """
341
+
342
+ def __init__(self, verbose: bool = False):
343
+ """Initialize SmartDefaults.
344
+
345
+ Args:
346
+ verbose: If True, log parameter selection rationale.
347
+ """
348
+ self.verbose = verbose
349
+ self._log_buffer: list[str] = []
350
+
351
+ def _log(self, message: str) -> None:
352
+ """Log a parameter selection message.
353
+
354
+ Args:
355
+ message: Log message.
356
+ """
357
+ self._log_buffer.append(message)
358
+ if self.verbose:
359
+ print(f"[SmartDefaults] {message}")
360
+
361
+ def get_fft_size(
362
+ self,
363
+ signal_length: int,
364
+ *,
365
+ min_size: int = 256,
366
+ max_size: int = 2**16,
367
+ ) -> int:
368
+ """Get smart default FFT size.
369
+
370
+ Args:
371
+ signal_length: Length of signal in samples.
372
+ min_size: Minimum FFT size.
373
+ max_size: Maximum FFT size.
374
+
375
+ Returns:
376
+ Recommended FFT size (power of 2).
377
+ """
378
+ # Next power of 2 at or above signal length
379
+ nfft = 2 ** int(np.ceil(np.log2(signal_length)))
380
+
381
+ # Clamp to reasonable range
382
+ nfft = max(min_size, min(nfft, max_size))
383
+
384
+ self._log(
385
+ f"FFT size: {nfft} (signal_length={signal_length}, "
386
+ f"next_power_of_2={2 ** int(np.ceil(np.log2(signal_length)))})"
387
+ )
388
+
389
+ return nfft # type: ignore[no-any-return]
390
+
391
+ def get_window_function(
392
+ self,
393
+ application: str = "general",
394
+ *,
395
+ dynamic_range_db: float = 60.0,
396
+ ) -> str:
397
+ """Get smart default window function.
398
+
399
+ Args:
400
+ application: Application type ('general', 'narrowband', 'transient').
401
+ dynamic_range_db: Required dynamic range in dB.
402
+
403
+ Returns:
404
+ Window function name.
405
+ """
406
+ if application == "transient":
407
+ window = "boxcar"
408
+ reason = "rectangular window for transient analysis"
409
+ elif dynamic_range_db > 80:
410
+ window = "blackman-harris"
411
+ reason = f"high dynamic range ({dynamic_range_db} dB) requires Blackman-Harris"
412
+ elif dynamic_range_db > 60:
413
+ window = "blackman"
414
+ reason = f"moderate-high dynamic range ({dynamic_range_db} dB) uses Blackman"
415
+ elif application == "narrowband":
416
+ window = "flattop"
417
+ reason = "narrowband analysis uses flat-top for amplitude accuracy"
418
+ else:
419
+ window = "hann"
420
+ reason = "general purpose uses Hann window"
421
+
422
+ self._log(f"Window function: {window} ({reason})")
423
+
424
+ return window
425
+
426
+ def get_overlap(
427
+ self,
428
+ method: str = "welch",
429
+ window: str = "hann",
430
+ ) -> float:
431
+ """Get smart default overlap for windowed methods.
432
+
433
+ Args:
434
+ method: Analysis method ('welch', 'bartlett', 'stft').
435
+ window: Window function name.
436
+
437
+ Returns:
438
+ Overlap fraction (0-1).
439
+ """
440
+ if method == "bartlett":
441
+ overlap = 0.0
442
+ reason = "Bartlett method uses no overlap"
443
+ elif window in ["hann", "hamming", "blackman"]:
444
+ overlap = 0.5
445
+ reason = f"{window} window typically uses 50% overlap"
446
+ elif window == "blackman-harris":
447
+ overlap = 0.75
448
+ reason = "Blackman-Harris uses 75% overlap for smoothness"
449
+ else:
450
+ overlap = 0.5
451
+ reason = "default 50% overlap for general windows"
452
+
453
+ self._log(f"Overlap: {overlap * 100:.0f}% ({reason})")
454
+
455
+ return overlap
456
+
457
+ def get_reference_levels(
458
+ self,
459
+ measurement: str = "rise_time",
460
+ ) -> tuple[float, float]:
461
+ """Get smart default reference levels for timing measurements.
462
+
463
+ Args:
464
+ measurement: Measurement type ('rise_time', 'fall_time', etc.).
465
+
466
+ Returns:
467
+ Tuple of (low_level, high_level) as fractions (0-1).
468
+ """
469
+ if measurement in ["rise_time", "slew_rate"]:
470
+ levels = (0.1, 0.9)
471
+ reason = "10%-90% is IEEE 181-2011 standard for rise time"
472
+ elif measurement == "fall_time":
473
+ levels = (0.9, 0.1)
474
+ reason = "90%-10% for fall time per IEEE 181-2011"
475
+ elif measurement in ["propagation_delay", "setup_time", "hold_time"]:
476
+ levels = (0.5, 0.5)
477
+ reason = "50% threshold for timing measurements"
478
+ else:
479
+ levels = (0.1, 0.9)
480
+ reason = "default 10%-90% for general measurements"
481
+
482
+ self._log(f"Reference levels: {levels[0]:.0%}-{levels[1]:.0%} ({reason})")
483
+
484
+ return levels
485
+
486
+ def get_log_messages(self) -> list[str]:
487
+ """Get all logged parameter selection messages.
488
+
489
+ Returns:
490
+ List of log messages.
491
+ """
492
+ return self._log_buffer.copy()
493
+
494
+ def clear_log(self) -> None:
495
+ """Clear the log buffer."""
496
+ self._log_buffer.clear()
497
+
498
+
499
+ __all__ = [
500
+ "DEFAULT_CONFIG",
501
+ "SmartDefaults",
502
+ "get_config_value",
503
+ "load_config",
504
+ "save_config",
505
+ "validate_config",
506
+ ]