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,530 @@
1
+ """User preferences management system.
2
+
3
+ This module provides persistent user preferences for Oscura including
4
+ visualization settings, default parameters, export options, and UI
5
+ preferences.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import logging
11
+ import os
12
+ from dataclasses import dataclass, field
13
+ from pathlib import Path
14
+ from typing import Any
15
+
16
+ import yaml
17
+
18
+ from oscura.config.schema import validate_against_schema
19
+ from oscura.core.exceptions import ConfigurationError
20
+
21
+ logger = logging.getLogger(__name__)
22
+
23
+
24
+ @dataclass
25
+ class VisualizationPreferences:
26
+ """Visualization preferences.
27
+
28
+ Attributes:
29
+ style: Matplotlib style name
30
+ figure_size: Default figure size (width, height) in inches
31
+ dpi: Default DPI for figures
32
+ colormap: Default colormap name
33
+ grid: Whether to show grid by default
34
+ dark_mode: Use dark theme
35
+ """
36
+
37
+ style: str = "seaborn-v0_8-whitegrid"
38
+ figure_size: tuple[float, float] = (10, 6)
39
+ dpi: int = 100
40
+ colormap: str = "viridis"
41
+ grid: bool = True
42
+ dark_mode: bool = False
43
+
44
+
45
+ @dataclass
46
+ class DefaultsPreferences:
47
+ """Default analysis parameters.
48
+
49
+ Attributes:
50
+ sample_rate: Default sample rate (Hz)
51
+ window_function: Default FFT window
52
+ fft_size: Default FFT size
53
+ rise_time_thresholds: Default rise time thresholds (low, high)
54
+ logic_family: Default logic family
55
+ """
56
+
57
+ sample_rate: float = 1e9 # 1 GHz
58
+ window_function: str = "hann"
59
+ fft_size: int = 8192
60
+ rise_time_thresholds: tuple[float, float] = (10.0, 90.0)
61
+ logic_family: str = "TTL"
62
+
63
+
64
+ @dataclass
65
+ class ExportPreferences:
66
+ """Export preferences.
67
+
68
+ Attributes:
69
+ default_format: Default export format
70
+ precision: Floating point precision (decimal places)
71
+ include_metadata: Include metadata in exports
72
+ compression: Default compression for HDF5
73
+ """
74
+
75
+ default_format: str = "csv"
76
+ precision: int = 6
77
+ include_metadata: bool = True
78
+ compression: str = "gzip"
79
+
80
+
81
+ @dataclass
82
+ class LoggingPreferences:
83
+ """Logging preferences.
84
+
85
+ Attributes:
86
+ level: Default log level
87
+ file: Log file path (None for no file logging)
88
+ format: Log format string
89
+ show_timestamps: Show timestamps in console
90
+ """
91
+
92
+ level: str = "WARNING"
93
+ file: str | None = None
94
+ format: str = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
95
+ show_timestamps: bool = False
96
+
97
+
98
+ @dataclass
99
+ class EditorPreferences:
100
+ """Editor/REPL preferences.
101
+
102
+ Attributes:
103
+ history_size: Number of commands to keep in history
104
+ auto_save: Auto-save session on exit
105
+ syntax_highlighting: Enable syntax highlighting
106
+ tab_completion: Enable tab completion
107
+ """
108
+
109
+ history_size: int = 1000
110
+ auto_save: bool = True
111
+ syntax_highlighting: bool = True
112
+ tab_completion: bool = True
113
+
114
+
115
+ @dataclass
116
+ class UserPreferences:
117
+ """Complete user preferences.
118
+
119
+ Attributes:
120
+ visualization: Visualization preferences
121
+ defaults: Default analysis parameters
122
+ export: Export preferences
123
+ logging: Logging preferences
124
+ editor: Editor/REPL preferences
125
+ recent_files: List of recent file paths
126
+ custom: Custom user-defined preferences
127
+
128
+ Example:
129
+ >>> prefs = UserPreferences()
130
+ >>> prefs.visualization.dark_mode = True
131
+ >>> prefs.defaults.sample_rate = 2e9
132
+ >>> prefs.save()
133
+ """
134
+
135
+ visualization: VisualizationPreferences = field(default_factory=VisualizationPreferences)
136
+ defaults: DefaultsPreferences = field(default_factory=DefaultsPreferences)
137
+ export: ExportPreferences = field(default_factory=ExportPreferences)
138
+ logging: LoggingPreferences = field(default_factory=LoggingPreferences)
139
+ editor: EditorPreferences = field(default_factory=EditorPreferences)
140
+ recent_files: list[str] = field(default_factory=list)
141
+ custom: dict[str, Any] = field(default_factory=dict)
142
+
143
+ def get(self, key: str, default: Any = None) -> Any:
144
+ """Get preference value by dot-notation key.
145
+
146
+ Args:
147
+ key: Key path (e.g., "visualization.dpi")
148
+ default: Default value if not found
149
+
150
+ Returns:
151
+ Preference value
152
+
153
+ Example:
154
+ >>> prefs.get("visualization.dpi")
155
+ 100
156
+ >>> prefs.get("custom.my_setting", 42)
157
+ 42
158
+ """
159
+ parts = key.split(".")
160
+ obj: Any = self
161
+
162
+ for part in parts:
163
+ if isinstance(obj, dict):
164
+ obj = obj.get(part, default)
165
+ if obj is default:
166
+ return default
167
+ elif hasattr(obj, part):
168
+ obj = getattr(obj, part)
169
+ else:
170
+ return default
171
+
172
+ return obj
173
+
174
+ def set(self, key: str, value: Any) -> None:
175
+ """Set preference value by dot-notation key.
176
+
177
+ Args:
178
+ key: Key path (e.g., "visualization.dpi")
179
+ value: Value to set
180
+
181
+ Raises:
182
+ KeyError: If preference path is invalid or unknown.
183
+
184
+ Example:
185
+ >>> prefs.set("visualization.dpi", 150)
186
+ >>> prefs.set("custom.my_setting", 42)
187
+ """
188
+ parts = key.split(".")
189
+
190
+ if parts[0] == "custom":
191
+ # Custom preferences are a dict
192
+ if len(parts) == 2:
193
+ self.custom[parts[1]] = value
194
+ else:
195
+ # Nested custom preferences
196
+ current = self.custom
197
+ for part in parts[1:-1]:
198
+ if part not in current:
199
+ current[part] = {}
200
+ current = current[part]
201
+ current[parts[-1]] = value
202
+ return
203
+
204
+ # Navigate to parent
205
+ obj: Any = self
206
+ for part in parts[:-1]:
207
+ if hasattr(obj, part):
208
+ obj = getattr(obj, part)
209
+ else:
210
+ raise KeyError(f"Invalid preference path: {key}")
211
+
212
+ # Set value
213
+ final_part = parts[-1]
214
+ if hasattr(obj, final_part):
215
+ setattr(obj, final_part, value)
216
+ else:
217
+ raise KeyError(f"Unknown preference: {key}")
218
+
219
+ def to_dict(self) -> dict[str, Any]:
220
+ """Convert preferences to dictionary.
221
+
222
+ Returns:
223
+ Dictionary representation
224
+ """
225
+ return {
226
+ "visualization": {
227
+ "style": self.visualization.style,
228
+ "figure_size": list(self.visualization.figure_size),
229
+ "dpi": self.visualization.dpi,
230
+ "colormap": self.visualization.colormap,
231
+ "grid": self.visualization.grid,
232
+ "dark_mode": self.visualization.dark_mode,
233
+ },
234
+ "defaults": {
235
+ "sample_rate": self.defaults.sample_rate,
236
+ "window_function": self.defaults.window_function,
237
+ "fft_size": self.defaults.fft_size,
238
+ "rise_time_thresholds": list(self.defaults.rise_time_thresholds),
239
+ "logic_family": self.defaults.logic_family,
240
+ },
241
+ "export": {
242
+ "default_format": self.export.default_format,
243
+ "precision": self.export.precision,
244
+ "include_metadata": self.export.include_metadata,
245
+ "compression": self.export.compression,
246
+ },
247
+ "logging": {
248
+ "level": self.logging.level,
249
+ "file": self.logging.file,
250
+ "format": self.logging.format,
251
+ "show_timestamps": self.logging.show_timestamps,
252
+ },
253
+ "editor": {
254
+ "history_size": self.editor.history_size,
255
+ "auto_save": self.editor.auto_save,
256
+ "syntax_highlighting": self.editor.syntax_highlighting,
257
+ "tab_completion": self.editor.tab_completion,
258
+ },
259
+ "recent_files": self.recent_files,
260
+ "custom": self.custom,
261
+ }
262
+
263
+ @classmethod
264
+ def from_dict(cls, data: dict[str, Any]) -> UserPreferences:
265
+ """Create preferences from dictionary.
266
+
267
+ Args:
268
+ data: Dictionary representation
269
+
270
+ Returns:
271
+ UserPreferences instance
272
+ """
273
+ prefs = cls()
274
+
275
+ # Visualization
276
+ if "visualization" in data:
277
+ v = data["visualization"]
278
+ prefs.visualization = VisualizationPreferences(
279
+ style=v.get("style", prefs.visualization.style),
280
+ figure_size=tuple(v.get("figure_size", prefs.visualization.figure_size)),
281
+ dpi=v.get("dpi", prefs.visualization.dpi),
282
+ colormap=v.get("colormap", prefs.visualization.colormap),
283
+ grid=v.get("grid", prefs.visualization.grid),
284
+ dark_mode=v.get("dark_mode", prefs.visualization.dark_mode),
285
+ )
286
+
287
+ # Defaults
288
+ if "defaults" in data:
289
+ d = data["defaults"]
290
+ prefs.defaults = DefaultsPreferences(
291
+ sample_rate=d.get("sample_rate", prefs.defaults.sample_rate),
292
+ window_function=d.get("window_function", prefs.defaults.window_function),
293
+ fft_size=d.get("fft_size", prefs.defaults.fft_size),
294
+ rise_time_thresholds=tuple(
295
+ d.get("rise_time_thresholds", prefs.defaults.rise_time_thresholds)
296
+ ),
297
+ logic_family=d.get("logic_family", prefs.defaults.logic_family),
298
+ )
299
+
300
+ # Export
301
+ if "export" in data:
302
+ e = data["export"]
303
+ prefs.export = ExportPreferences(
304
+ default_format=e.get("default_format", prefs.export.default_format),
305
+ precision=e.get("precision", prefs.export.precision),
306
+ include_metadata=e.get("include_metadata", prefs.export.include_metadata),
307
+ compression=e.get("compression", prefs.export.compression),
308
+ )
309
+
310
+ # Logging
311
+ if "logging" in data:
312
+ lg = data["logging"]
313
+ prefs.logging = LoggingPreferences(
314
+ level=lg.get("level", prefs.logging.level),
315
+ file=lg.get("file", prefs.logging.file),
316
+ format=lg.get("format", prefs.logging.format),
317
+ show_timestamps=lg.get("show_timestamps", prefs.logging.show_timestamps),
318
+ )
319
+
320
+ # Editor
321
+ if "editor" in data:
322
+ ed = data["editor"]
323
+ prefs.editor = EditorPreferences(
324
+ history_size=ed.get("history_size", prefs.editor.history_size),
325
+ auto_save=ed.get("auto_save", prefs.editor.auto_save),
326
+ syntax_highlighting=ed.get("syntax_highlighting", prefs.editor.syntax_highlighting),
327
+ tab_completion=ed.get("tab_completion", prefs.editor.tab_completion),
328
+ )
329
+
330
+ prefs.recent_files = data.get("recent_files", [])
331
+ prefs.custom = data.get("custom", {})
332
+
333
+ return prefs
334
+
335
+
336
+ class PreferencesManager:
337
+ """Manager for loading and saving user preferences.
338
+
339
+ Handles preferences file location, loading, saving, and migration.
340
+
341
+ Example:
342
+ >>> manager = PreferencesManager()
343
+ >>> prefs = manager.load()
344
+ >>> prefs.visualization.dark_mode = True
345
+ >>> manager.save(prefs)
346
+ """
347
+
348
+ def __init__(self, path: Path | None = None):
349
+ """Initialize preferences manager.
350
+
351
+ Args:
352
+ path: Override preferences file path
353
+ """
354
+ self._path = path or self._get_default_path()
355
+ self._cached: UserPreferences | None = None
356
+
357
+ def _get_default_path(self) -> Path:
358
+ """Get default preferences file path."""
359
+ xdg_config = os.environ.get("XDG_CONFIG_HOME")
360
+ base = Path(xdg_config) if xdg_config else Path.home() / ".config"
361
+
362
+ config_dir = base / "oscura"
363
+ config_dir.mkdir(parents=True, exist_ok=True)
364
+ return config_dir / "preferences.yaml"
365
+
366
+ def load(self, use_cache: bool = True) -> UserPreferences:
367
+ """Load preferences from file.
368
+
369
+ Args:
370
+ use_cache: Use cached preferences if available
371
+
372
+ Returns:
373
+ User preferences
374
+ """
375
+ if use_cache and self._cached is not None:
376
+ return self._cached
377
+
378
+ if not self._path.exists():
379
+ logger.debug(f"No preferences file at {self._path}, using defaults")
380
+ self._cached = UserPreferences()
381
+ return self._cached
382
+
383
+ try:
384
+ with open(self._path, encoding="utf-8") as f:
385
+ data = yaml.safe_load(f)
386
+
387
+ if data is None:
388
+ data = {}
389
+
390
+ # Validate against schema if available
391
+ try:
392
+ validate_against_schema(data, "preferences")
393
+ except Exception as e:
394
+ logger.warning(f"Preferences validation warning: {e}")
395
+
396
+ self._cached = UserPreferences.from_dict(data)
397
+ logger.debug(f"Loaded preferences from {self._path}")
398
+ return self._cached
399
+
400
+ except Exception as e:
401
+ logger.warning(f"Failed to load preferences from {self._path}: {e}")
402
+ self._cached = UserPreferences()
403
+ return self._cached
404
+
405
+ def save(self, prefs: UserPreferences | None = None) -> None:
406
+ """Save preferences to file.
407
+
408
+ Args:
409
+ prefs: Preferences to save (uses cached if None)
410
+
411
+ Raises:
412
+ ConfigurationError: If saving to file fails.
413
+ """
414
+ prefs = prefs or self._cached
415
+ if prefs is None:
416
+ prefs = UserPreferences()
417
+
418
+ try:
419
+ data = prefs.to_dict()
420
+
421
+ # Ensure parent directory exists
422
+ self._path.parent.mkdir(parents=True, exist_ok=True)
423
+
424
+ with open(self._path, "w", encoding="utf-8") as f:
425
+ yaml.dump(data, f, default_flow_style=False)
426
+
427
+ self._cached = prefs
428
+ logger.debug(f"Saved preferences to {self._path}")
429
+
430
+ except Exception as e:
431
+ logger.error(f"Failed to save preferences to {self._path}: {e}")
432
+ raise ConfigurationError(f"Failed to save preferences to {self._path}: {e}") from e
433
+
434
+ def reset(self) -> UserPreferences:
435
+ """Reset preferences to defaults.
436
+
437
+ Returns:
438
+ Default preferences
439
+ """
440
+ self._cached = UserPreferences()
441
+ self.save(self._cached)
442
+ logger.info("Reset preferences to defaults")
443
+ return self._cached
444
+
445
+ def add_recent_file(self, path: str | Path, max_recent: int = 10) -> None:
446
+ """Add file to recent files list.
447
+
448
+ Args:
449
+ path: File path
450
+ max_recent: Maximum number of recent files to keep
451
+ """
452
+ prefs = self.load()
453
+ path_str = str(path)
454
+
455
+ # Remove if already in list
456
+ if path_str in prefs.recent_files:
457
+ prefs.recent_files.remove(path_str)
458
+
459
+ # Add to front
460
+ prefs.recent_files.insert(0, path_str)
461
+
462
+ # Trim to max
463
+ prefs.recent_files = prefs.recent_files[:max_recent]
464
+
465
+ self.save(prefs)
466
+
467
+ def get_recent_files(self, max_count: int = 10) -> list[str]:
468
+ """Get list of recent files.
469
+
470
+ Args:
471
+ max_count: Maximum number to return
472
+
473
+ Returns:
474
+ List of recent file paths
475
+ """
476
+ prefs = self.load()
477
+ return prefs.recent_files[:max_count]
478
+
479
+ @property
480
+ def path(self) -> Path:
481
+ """Get preferences file path."""
482
+ return self._path
483
+
484
+
485
+ # Global preferences manager
486
+ _manager: PreferencesManager | None = None
487
+
488
+
489
+ def get_preferences_manager() -> PreferencesManager:
490
+ """Get global preferences manager.
491
+
492
+ Returns:
493
+ Global PreferencesManager instance
494
+ """
495
+ global _manager
496
+ if _manager is None:
497
+ _manager = PreferencesManager()
498
+ return _manager
499
+
500
+
501
+ def get_preferences() -> UserPreferences:
502
+ """Get current user preferences.
503
+
504
+ Returns:
505
+ User preferences
506
+ """
507
+ return get_preferences_manager().load()
508
+
509
+
510
+ def save_preferences(prefs: UserPreferences | None = None) -> None:
511
+ """Save user preferences.
512
+
513
+ Args:
514
+ prefs: Preferences to save
515
+ """
516
+ get_preferences_manager().save(prefs)
517
+
518
+
519
+ __all__ = [
520
+ "DefaultsPreferences",
521
+ "EditorPreferences",
522
+ "ExportPreferences",
523
+ "LoggingPreferences",
524
+ "PreferencesManager",
525
+ "UserPreferences",
526
+ "VisualizationPreferences",
527
+ "get_preferences",
528
+ "get_preferences_manager",
529
+ "save_preferences",
530
+ ]