oscura 0.0.1__py3-none-any.whl → 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (465) hide show
  1. oscura/__init__.py +813 -8
  2. oscura/__main__.py +392 -0
  3. oscura/analyzers/__init__.py +37 -0
  4. oscura/analyzers/digital/__init__.py +177 -0
  5. oscura/analyzers/digital/bus.py +691 -0
  6. oscura/analyzers/digital/clock.py +805 -0
  7. oscura/analyzers/digital/correlation.py +720 -0
  8. oscura/analyzers/digital/edges.py +632 -0
  9. oscura/analyzers/digital/extraction.py +413 -0
  10. oscura/analyzers/digital/quality.py +878 -0
  11. oscura/analyzers/digital/signal_quality.py +877 -0
  12. oscura/analyzers/digital/thresholds.py +708 -0
  13. oscura/analyzers/digital/timing.py +1104 -0
  14. oscura/analyzers/eye/__init__.py +46 -0
  15. oscura/analyzers/eye/diagram.py +434 -0
  16. oscura/analyzers/eye/metrics.py +555 -0
  17. oscura/analyzers/jitter/__init__.py +83 -0
  18. oscura/analyzers/jitter/ber.py +333 -0
  19. oscura/analyzers/jitter/decomposition.py +759 -0
  20. oscura/analyzers/jitter/measurements.py +413 -0
  21. oscura/analyzers/jitter/spectrum.py +220 -0
  22. oscura/analyzers/measurements.py +40 -0
  23. oscura/analyzers/packet/__init__.py +171 -0
  24. oscura/analyzers/packet/daq.py +1077 -0
  25. oscura/analyzers/packet/metrics.py +437 -0
  26. oscura/analyzers/packet/parser.py +327 -0
  27. oscura/analyzers/packet/payload.py +2156 -0
  28. oscura/analyzers/packet/payload_analysis.py +1312 -0
  29. oscura/analyzers/packet/payload_extraction.py +236 -0
  30. oscura/analyzers/packet/payload_patterns.py +670 -0
  31. oscura/analyzers/packet/stream.py +359 -0
  32. oscura/analyzers/patterns/__init__.py +266 -0
  33. oscura/analyzers/patterns/clustering.py +1036 -0
  34. oscura/analyzers/patterns/discovery.py +539 -0
  35. oscura/analyzers/patterns/learning.py +797 -0
  36. oscura/analyzers/patterns/matching.py +1091 -0
  37. oscura/analyzers/patterns/periodic.py +650 -0
  38. oscura/analyzers/patterns/sequences.py +767 -0
  39. oscura/analyzers/power/__init__.py +116 -0
  40. oscura/analyzers/power/ac_power.py +391 -0
  41. oscura/analyzers/power/basic.py +383 -0
  42. oscura/analyzers/power/conduction.py +314 -0
  43. oscura/analyzers/power/efficiency.py +297 -0
  44. oscura/analyzers/power/ripple.py +356 -0
  45. oscura/analyzers/power/soa.py +372 -0
  46. oscura/analyzers/power/switching.py +479 -0
  47. oscura/analyzers/protocol/__init__.py +150 -0
  48. oscura/analyzers/protocols/__init__.py +150 -0
  49. oscura/analyzers/protocols/base.py +500 -0
  50. oscura/analyzers/protocols/can.py +620 -0
  51. oscura/analyzers/protocols/can_fd.py +448 -0
  52. oscura/analyzers/protocols/flexray.py +405 -0
  53. oscura/analyzers/protocols/hdlc.py +399 -0
  54. oscura/analyzers/protocols/i2c.py +368 -0
  55. oscura/analyzers/protocols/i2s.py +296 -0
  56. oscura/analyzers/protocols/jtag.py +393 -0
  57. oscura/analyzers/protocols/lin.py +445 -0
  58. oscura/analyzers/protocols/manchester.py +333 -0
  59. oscura/analyzers/protocols/onewire.py +501 -0
  60. oscura/analyzers/protocols/spi.py +334 -0
  61. oscura/analyzers/protocols/swd.py +325 -0
  62. oscura/analyzers/protocols/uart.py +393 -0
  63. oscura/analyzers/protocols/usb.py +495 -0
  64. oscura/analyzers/signal_integrity/__init__.py +63 -0
  65. oscura/analyzers/signal_integrity/embedding.py +294 -0
  66. oscura/analyzers/signal_integrity/equalization.py +370 -0
  67. oscura/analyzers/signal_integrity/sparams.py +484 -0
  68. oscura/analyzers/spectral/__init__.py +53 -0
  69. oscura/analyzers/spectral/chunked.py +273 -0
  70. oscura/analyzers/spectral/chunked_fft.py +571 -0
  71. oscura/analyzers/spectral/chunked_wavelet.py +391 -0
  72. oscura/analyzers/spectral/fft.py +92 -0
  73. oscura/analyzers/statistical/__init__.py +250 -0
  74. oscura/analyzers/statistical/checksum.py +923 -0
  75. oscura/analyzers/statistical/chunked_corr.py +228 -0
  76. oscura/analyzers/statistical/classification.py +778 -0
  77. oscura/analyzers/statistical/entropy.py +1113 -0
  78. oscura/analyzers/statistical/ngrams.py +614 -0
  79. oscura/analyzers/statistics/__init__.py +119 -0
  80. oscura/analyzers/statistics/advanced.py +885 -0
  81. oscura/analyzers/statistics/basic.py +263 -0
  82. oscura/analyzers/statistics/correlation.py +630 -0
  83. oscura/analyzers/statistics/distribution.py +298 -0
  84. oscura/analyzers/statistics/outliers.py +463 -0
  85. oscura/analyzers/statistics/streaming.py +93 -0
  86. oscura/analyzers/statistics/trend.py +520 -0
  87. oscura/analyzers/validation.py +598 -0
  88. oscura/analyzers/waveform/__init__.py +36 -0
  89. oscura/analyzers/waveform/measurements.py +943 -0
  90. oscura/analyzers/waveform/measurements_with_uncertainty.py +371 -0
  91. oscura/analyzers/waveform/spectral.py +1689 -0
  92. oscura/analyzers/waveform/wavelets.py +298 -0
  93. oscura/api/__init__.py +62 -0
  94. oscura/api/dsl.py +538 -0
  95. oscura/api/fluent.py +571 -0
  96. oscura/api/operators.py +498 -0
  97. oscura/api/optimization.py +392 -0
  98. oscura/api/profiling.py +396 -0
  99. oscura/automotive/__init__.py +73 -0
  100. oscura/automotive/can/__init__.py +52 -0
  101. oscura/automotive/can/analysis.py +356 -0
  102. oscura/automotive/can/checksum.py +250 -0
  103. oscura/automotive/can/correlation.py +212 -0
  104. oscura/automotive/can/discovery.py +355 -0
  105. oscura/automotive/can/message_wrapper.py +375 -0
  106. oscura/automotive/can/models.py +385 -0
  107. oscura/automotive/can/patterns.py +381 -0
  108. oscura/automotive/can/session.py +452 -0
  109. oscura/automotive/can/state_machine.py +300 -0
  110. oscura/automotive/can/stimulus_response.py +461 -0
  111. oscura/automotive/dbc/__init__.py +15 -0
  112. oscura/automotive/dbc/generator.py +156 -0
  113. oscura/automotive/dbc/parser.py +146 -0
  114. oscura/automotive/dtc/__init__.py +30 -0
  115. oscura/automotive/dtc/database.py +3036 -0
  116. oscura/automotive/j1939/__init__.py +14 -0
  117. oscura/automotive/j1939/decoder.py +745 -0
  118. oscura/automotive/loaders/__init__.py +35 -0
  119. oscura/automotive/loaders/asc.py +98 -0
  120. oscura/automotive/loaders/blf.py +77 -0
  121. oscura/automotive/loaders/csv_can.py +136 -0
  122. oscura/automotive/loaders/dispatcher.py +136 -0
  123. oscura/automotive/loaders/mdf.py +331 -0
  124. oscura/automotive/loaders/pcap.py +132 -0
  125. oscura/automotive/obd/__init__.py +14 -0
  126. oscura/automotive/obd/decoder.py +707 -0
  127. oscura/automotive/uds/__init__.py +48 -0
  128. oscura/automotive/uds/decoder.py +265 -0
  129. oscura/automotive/uds/models.py +64 -0
  130. oscura/automotive/visualization.py +369 -0
  131. oscura/batch/__init__.py +55 -0
  132. oscura/batch/advanced.py +627 -0
  133. oscura/batch/aggregate.py +300 -0
  134. oscura/batch/analyze.py +139 -0
  135. oscura/batch/logging.py +487 -0
  136. oscura/batch/metrics.py +556 -0
  137. oscura/builders/__init__.py +41 -0
  138. oscura/builders/signal_builder.py +1131 -0
  139. oscura/cli/__init__.py +14 -0
  140. oscura/cli/batch.py +339 -0
  141. oscura/cli/characterize.py +273 -0
  142. oscura/cli/compare.py +775 -0
  143. oscura/cli/decode.py +551 -0
  144. oscura/cli/main.py +247 -0
  145. oscura/cli/shell.py +350 -0
  146. oscura/comparison/__init__.py +66 -0
  147. oscura/comparison/compare.py +397 -0
  148. oscura/comparison/golden.py +487 -0
  149. oscura/comparison/limits.py +391 -0
  150. oscura/comparison/mask.py +434 -0
  151. oscura/comparison/trace_diff.py +30 -0
  152. oscura/comparison/visualization.py +481 -0
  153. oscura/compliance/__init__.py +70 -0
  154. oscura/compliance/advanced.py +756 -0
  155. oscura/compliance/masks.py +363 -0
  156. oscura/compliance/reporting.py +483 -0
  157. oscura/compliance/testing.py +298 -0
  158. oscura/component/__init__.py +38 -0
  159. oscura/component/impedance.py +365 -0
  160. oscura/component/reactive.py +598 -0
  161. oscura/component/transmission_line.py +312 -0
  162. oscura/config/__init__.py +191 -0
  163. oscura/config/defaults.py +254 -0
  164. oscura/config/loader.py +348 -0
  165. oscura/config/memory.py +271 -0
  166. oscura/config/migration.py +458 -0
  167. oscura/config/pipeline.py +1077 -0
  168. oscura/config/preferences.py +530 -0
  169. oscura/config/protocol.py +875 -0
  170. oscura/config/schema.py +713 -0
  171. oscura/config/settings.py +420 -0
  172. oscura/config/thresholds.py +599 -0
  173. oscura/convenience.py +457 -0
  174. oscura/core/__init__.py +299 -0
  175. oscura/core/audit.py +457 -0
  176. oscura/core/backend_selector.py +405 -0
  177. oscura/core/cache.py +590 -0
  178. oscura/core/cancellation.py +439 -0
  179. oscura/core/confidence.py +225 -0
  180. oscura/core/config.py +506 -0
  181. oscura/core/correlation.py +216 -0
  182. oscura/core/cross_domain.py +422 -0
  183. oscura/core/debug.py +301 -0
  184. oscura/core/edge_cases.py +541 -0
  185. oscura/core/exceptions.py +535 -0
  186. oscura/core/gpu_backend.py +523 -0
  187. oscura/core/lazy.py +832 -0
  188. oscura/core/log_query.py +540 -0
  189. oscura/core/logging.py +931 -0
  190. oscura/core/logging_advanced.py +952 -0
  191. oscura/core/memoize.py +171 -0
  192. oscura/core/memory_check.py +274 -0
  193. oscura/core/memory_guard.py +290 -0
  194. oscura/core/memory_limits.py +336 -0
  195. oscura/core/memory_monitor.py +453 -0
  196. oscura/core/memory_progress.py +465 -0
  197. oscura/core/memory_warnings.py +315 -0
  198. oscura/core/numba_backend.py +362 -0
  199. oscura/core/performance.py +352 -0
  200. oscura/core/progress.py +524 -0
  201. oscura/core/provenance.py +358 -0
  202. oscura/core/results.py +331 -0
  203. oscura/core/types.py +504 -0
  204. oscura/core/uncertainty.py +383 -0
  205. oscura/discovery/__init__.py +52 -0
  206. oscura/discovery/anomaly_detector.py +672 -0
  207. oscura/discovery/auto_decoder.py +415 -0
  208. oscura/discovery/comparison.py +497 -0
  209. oscura/discovery/quality_validator.py +528 -0
  210. oscura/discovery/signal_detector.py +769 -0
  211. oscura/dsl/__init__.py +73 -0
  212. oscura/dsl/commands.py +246 -0
  213. oscura/dsl/interpreter.py +455 -0
  214. oscura/dsl/parser.py +689 -0
  215. oscura/dsl/repl.py +172 -0
  216. oscura/exceptions.py +59 -0
  217. oscura/exploratory/__init__.py +111 -0
  218. oscura/exploratory/error_recovery.py +642 -0
  219. oscura/exploratory/fuzzy.py +513 -0
  220. oscura/exploratory/fuzzy_advanced.py +786 -0
  221. oscura/exploratory/legacy.py +831 -0
  222. oscura/exploratory/parse.py +358 -0
  223. oscura/exploratory/recovery.py +275 -0
  224. oscura/exploratory/sync.py +382 -0
  225. oscura/exploratory/unknown.py +707 -0
  226. oscura/export/__init__.py +25 -0
  227. oscura/export/wireshark/README.md +265 -0
  228. oscura/export/wireshark/__init__.py +47 -0
  229. oscura/export/wireshark/generator.py +312 -0
  230. oscura/export/wireshark/lua_builder.py +159 -0
  231. oscura/export/wireshark/templates/dissector.lua.j2 +92 -0
  232. oscura/export/wireshark/type_mapping.py +165 -0
  233. oscura/export/wireshark/validator.py +105 -0
  234. oscura/exporters/__init__.py +94 -0
  235. oscura/exporters/csv.py +303 -0
  236. oscura/exporters/exporters.py +44 -0
  237. oscura/exporters/hdf5.py +219 -0
  238. oscura/exporters/html_export.py +701 -0
  239. oscura/exporters/json_export.py +291 -0
  240. oscura/exporters/markdown_export.py +367 -0
  241. oscura/exporters/matlab_export.py +354 -0
  242. oscura/exporters/npz_export.py +219 -0
  243. oscura/exporters/spice_export.py +210 -0
  244. oscura/extensibility/__init__.py +131 -0
  245. oscura/extensibility/docs.py +752 -0
  246. oscura/extensibility/extensions.py +1125 -0
  247. oscura/extensibility/logging.py +259 -0
  248. oscura/extensibility/measurements.py +485 -0
  249. oscura/extensibility/plugins.py +414 -0
  250. oscura/extensibility/registry.py +346 -0
  251. oscura/extensibility/templates.py +913 -0
  252. oscura/extensibility/validation.py +651 -0
  253. oscura/filtering/__init__.py +89 -0
  254. oscura/filtering/base.py +563 -0
  255. oscura/filtering/convenience.py +564 -0
  256. oscura/filtering/design.py +725 -0
  257. oscura/filtering/filters.py +32 -0
  258. oscura/filtering/introspection.py +605 -0
  259. oscura/guidance/__init__.py +24 -0
  260. oscura/guidance/recommender.py +429 -0
  261. oscura/guidance/wizard.py +518 -0
  262. oscura/inference/__init__.py +251 -0
  263. oscura/inference/active_learning/README.md +153 -0
  264. oscura/inference/active_learning/__init__.py +38 -0
  265. oscura/inference/active_learning/lstar.py +257 -0
  266. oscura/inference/active_learning/observation_table.py +230 -0
  267. oscura/inference/active_learning/oracle.py +78 -0
  268. oscura/inference/active_learning/teachers/__init__.py +15 -0
  269. oscura/inference/active_learning/teachers/simulator.py +192 -0
  270. oscura/inference/adaptive_tuning.py +453 -0
  271. oscura/inference/alignment.py +653 -0
  272. oscura/inference/bayesian.py +943 -0
  273. oscura/inference/binary.py +1016 -0
  274. oscura/inference/crc_reverse.py +711 -0
  275. oscura/inference/logic.py +288 -0
  276. oscura/inference/message_format.py +1305 -0
  277. oscura/inference/protocol.py +417 -0
  278. oscura/inference/protocol_dsl.py +1084 -0
  279. oscura/inference/protocol_library.py +1230 -0
  280. oscura/inference/sequences.py +809 -0
  281. oscura/inference/signal_intelligence.py +1509 -0
  282. oscura/inference/spectral.py +215 -0
  283. oscura/inference/state_machine.py +634 -0
  284. oscura/inference/stream.py +918 -0
  285. oscura/integrations/__init__.py +59 -0
  286. oscura/integrations/llm.py +1827 -0
  287. oscura/jupyter/__init__.py +32 -0
  288. oscura/jupyter/display.py +268 -0
  289. oscura/jupyter/magic.py +334 -0
  290. oscura/loaders/__init__.py +526 -0
  291. oscura/loaders/binary.py +69 -0
  292. oscura/loaders/configurable.py +1255 -0
  293. oscura/loaders/csv.py +26 -0
  294. oscura/loaders/csv_loader.py +473 -0
  295. oscura/loaders/hdf5.py +9 -0
  296. oscura/loaders/hdf5_loader.py +510 -0
  297. oscura/loaders/lazy.py +370 -0
  298. oscura/loaders/mmap_loader.py +583 -0
  299. oscura/loaders/numpy_loader.py +436 -0
  300. oscura/loaders/pcap.py +432 -0
  301. oscura/loaders/preprocessing.py +368 -0
  302. oscura/loaders/rigol.py +287 -0
  303. oscura/loaders/sigrok.py +321 -0
  304. oscura/loaders/tdms.py +367 -0
  305. oscura/loaders/tektronix.py +711 -0
  306. oscura/loaders/validation.py +584 -0
  307. oscura/loaders/vcd.py +464 -0
  308. oscura/loaders/wav.py +233 -0
  309. oscura/math/__init__.py +45 -0
  310. oscura/math/arithmetic.py +824 -0
  311. oscura/math/interpolation.py +413 -0
  312. oscura/onboarding/__init__.py +39 -0
  313. oscura/onboarding/help.py +498 -0
  314. oscura/onboarding/tutorials.py +405 -0
  315. oscura/onboarding/wizard.py +466 -0
  316. oscura/optimization/__init__.py +19 -0
  317. oscura/optimization/parallel.py +440 -0
  318. oscura/optimization/search.py +532 -0
  319. oscura/pipeline/__init__.py +43 -0
  320. oscura/pipeline/base.py +338 -0
  321. oscura/pipeline/composition.py +242 -0
  322. oscura/pipeline/parallel.py +448 -0
  323. oscura/pipeline/pipeline.py +375 -0
  324. oscura/pipeline/reverse_engineering.py +1119 -0
  325. oscura/plugins/__init__.py +122 -0
  326. oscura/plugins/base.py +272 -0
  327. oscura/plugins/cli.py +497 -0
  328. oscura/plugins/discovery.py +411 -0
  329. oscura/plugins/isolation.py +418 -0
  330. oscura/plugins/lifecycle.py +959 -0
  331. oscura/plugins/manager.py +493 -0
  332. oscura/plugins/registry.py +421 -0
  333. oscura/plugins/versioning.py +372 -0
  334. oscura/py.typed +0 -0
  335. oscura/quality/__init__.py +65 -0
  336. oscura/quality/ensemble.py +740 -0
  337. oscura/quality/explainer.py +338 -0
  338. oscura/quality/scoring.py +616 -0
  339. oscura/quality/warnings.py +456 -0
  340. oscura/reporting/__init__.py +248 -0
  341. oscura/reporting/advanced.py +1234 -0
  342. oscura/reporting/analyze.py +448 -0
  343. oscura/reporting/argument_preparer.py +596 -0
  344. oscura/reporting/auto_report.py +507 -0
  345. oscura/reporting/batch.py +615 -0
  346. oscura/reporting/chart_selection.py +223 -0
  347. oscura/reporting/comparison.py +330 -0
  348. oscura/reporting/config.py +615 -0
  349. oscura/reporting/content/__init__.py +39 -0
  350. oscura/reporting/content/executive.py +127 -0
  351. oscura/reporting/content/filtering.py +191 -0
  352. oscura/reporting/content/minimal.py +257 -0
  353. oscura/reporting/content/verbosity.py +162 -0
  354. oscura/reporting/core.py +508 -0
  355. oscura/reporting/core_formats/__init__.py +17 -0
  356. oscura/reporting/core_formats/multi_format.py +210 -0
  357. oscura/reporting/engine.py +836 -0
  358. oscura/reporting/export.py +366 -0
  359. oscura/reporting/formatting/__init__.py +129 -0
  360. oscura/reporting/formatting/emphasis.py +81 -0
  361. oscura/reporting/formatting/numbers.py +403 -0
  362. oscura/reporting/formatting/standards.py +55 -0
  363. oscura/reporting/formatting.py +466 -0
  364. oscura/reporting/html.py +578 -0
  365. oscura/reporting/index.py +590 -0
  366. oscura/reporting/multichannel.py +296 -0
  367. oscura/reporting/output.py +379 -0
  368. oscura/reporting/pdf.py +373 -0
  369. oscura/reporting/plots.py +731 -0
  370. oscura/reporting/pptx_export.py +360 -0
  371. oscura/reporting/renderers/__init__.py +11 -0
  372. oscura/reporting/renderers/pdf.py +94 -0
  373. oscura/reporting/sections.py +471 -0
  374. oscura/reporting/standards.py +680 -0
  375. oscura/reporting/summary_generator.py +368 -0
  376. oscura/reporting/tables.py +397 -0
  377. oscura/reporting/template_system.py +724 -0
  378. oscura/reporting/templates/__init__.py +15 -0
  379. oscura/reporting/templates/definition.py +205 -0
  380. oscura/reporting/templates/index.html +649 -0
  381. oscura/reporting/templates/index.md +173 -0
  382. oscura/schemas/__init__.py +158 -0
  383. oscura/schemas/bus_configuration.json +322 -0
  384. oscura/schemas/device_mapping.json +182 -0
  385. oscura/schemas/packet_format.json +418 -0
  386. oscura/schemas/protocol_definition.json +363 -0
  387. oscura/search/__init__.py +16 -0
  388. oscura/search/anomaly.py +292 -0
  389. oscura/search/context.py +149 -0
  390. oscura/search/pattern.py +160 -0
  391. oscura/session/__init__.py +34 -0
  392. oscura/session/annotations.py +289 -0
  393. oscura/session/history.py +313 -0
  394. oscura/session/session.py +445 -0
  395. oscura/streaming/__init__.py +43 -0
  396. oscura/streaming/chunked.py +611 -0
  397. oscura/streaming/progressive.py +393 -0
  398. oscura/streaming/realtime.py +622 -0
  399. oscura/testing/__init__.py +54 -0
  400. oscura/testing/synthetic.py +808 -0
  401. oscura/triggering/__init__.py +68 -0
  402. oscura/triggering/base.py +229 -0
  403. oscura/triggering/edge.py +353 -0
  404. oscura/triggering/pattern.py +344 -0
  405. oscura/triggering/pulse.py +581 -0
  406. oscura/triggering/window.py +453 -0
  407. oscura/ui/__init__.py +48 -0
  408. oscura/ui/formatters.py +526 -0
  409. oscura/ui/progressive_display.py +340 -0
  410. oscura/utils/__init__.py +99 -0
  411. oscura/utils/autodetect.py +338 -0
  412. oscura/utils/buffer.py +389 -0
  413. oscura/utils/lazy.py +407 -0
  414. oscura/utils/lazy_imports.py +147 -0
  415. oscura/utils/memory.py +836 -0
  416. oscura/utils/memory_advanced.py +1326 -0
  417. oscura/utils/memory_extensions.py +465 -0
  418. oscura/utils/progressive.py +352 -0
  419. oscura/utils/windowing.py +362 -0
  420. oscura/visualization/__init__.py +321 -0
  421. oscura/visualization/accessibility.py +526 -0
  422. oscura/visualization/annotations.py +374 -0
  423. oscura/visualization/axis_scaling.py +305 -0
  424. oscura/visualization/colors.py +453 -0
  425. oscura/visualization/digital.py +337 -0
  426. oscura/visualization/eye.py +420 -0
  427. oscura/visualization/histogram.py +281 -0
  428. oscura/visualization/interactive.py +858 -0
  429. oscura/visualization/jitter.py +702 -0
  430. oscura/visualization/keyboard.py +394 -0
  431. oscura/visualization/layout.py +365 -0
  432. oscura/visualization/optimization.py +1028 -0
  433. oscura/visualization/palettes.py +446 -0
  434. oscura/visualization/plot.py +92 -0
  435. oscura/visualization/power.py +290 -0
  436. oscura/visualization/power_extended.py +626 -0
  437. oscura/visualization/presets.py +467 -0
  438. oscura/visualization/protocols.py +932 -0
  439. oscura/visualization/render.py +207 -0
  440. oscura/visualization/rendering.py +444 -0
  441. oscura/visualization/reverse_engineering.py +791 -0
  442. oscura/visualization/signal_integrity.py +808 -0
  443. oscura/visualization/specialized.py +553 -0
  444. oscura/visualization/spectral.py +811 -0
  445. oscura/visualization/styles.py +381 -0
  446. oscura/visualization/thumbnails.py +311 -0
  447. oscura/visualization/time_axis.py +351 -0
  448. oscura/visualization/waveform.py +367 -0
  449. oscura/workflow/__init__.py +13 -0
  450. oscura/workflow/dag.py +377 -0
  451. oscura/workflows/__init__.py +58 -0
  452. oscura/workflows/compliance.py +280 -0
  453. oscura/workflows/digital.py +272 -0
  454. oscura/workflows/multi_trace.py +502 -0
  455. oscura/workflows/power.py +178 -0
  456. oscura/workflows/protocol.py +492 -0
  457. oscura/workflows/reverse_engineering.py +639 -0
  458. oscura/workflows/signal_integrity.py +227 -0
  459. oscura-0.1.0.dist-info/METADATA +300 -0
  460. oscura-0.1.0.dist-info/RECORD +463 -0
  461. oscura-0.1.0.dist-info/entry_points.txt +2 -0
  462. {oscura-0.0.1.dist-info → oscura-0.1.0.dist-info}/licenses/LICENSE +1 -1
  463. oscura-0.0.1.dist-info/METADATA +0 -63
  464. oscura-0.0.1.dist-info/RECORD +0 -5
  465. {oscura-0.0.1.dist-info → oscura-0.1.0.dist-info}/WHEEL +0 -0
oscura/core/cache.py ADDED
@@ -0,0 +1,590 @@
1
+ """Persistent LRU cache with disk spillover for TraceKit intermediate results.
2
+
3
+ This module provides memory-bounded caching for expensive operations like FFT,
4
+ spectrograms, and filtered traces with automatic disk spillover when memory
5
+ limits are exceeded.
6
+
7
+
8
+ Example:
9
+ >>> from oscura.core.cache import TraceKitCache, get_cache
10
+ >>> cache = get_cache(max_memory="2GB")
11
+ >>> result = cache.get_or_compute("fft_key", compute_fft, signal, 1024)
12
+ >>> cache.show_stats()
13
+ Cache Statistics: 42 hits, 15 misses (73.7% hit rate)
14
+
15
+ References:
16
+ Python functools.lru_cache
17
+ Python pickle for serialization
18
+ """
19
+
20
+ from __future__ import annotations
21
+
22
+ import contextlib
23
+ import hashlib
24
+ import pickle
25
+ import tempfile
26
+ import threading
27
+ import time
28
+ from collections import OrderedDict
29
+ from dataclasses import dataclass
30
+ from pathlib import Path
31
+ from typing import TYPE_CHECKING, Any, TypeVar
32
+
33
+ import numpy as np
34
+
35
+ if TYPE_CHECKING:
36
+ from collections.abc import Callable
37
+
38
+
39
+ T = TypeVar("T")
40
+
41
+
42
+ @dataclass
43
+ class CacheEntry:
44
+ """Single cache entry with metadata.
45
+
46
+ Attributes:
47
+ key: Cache key (hash of inputs).
48
+ value: Cached value (in memory).
49
+ disk_path: Path to disk file if spilled.
50
+ size_bytes: Size of cached value in bytes.
51
+ created_at: Creation timestamp.
52
+ last_accessed: Last access timestamp.
53
+ access_count: Number of times accessed.
54
+ in_memory: True if value is in memory.
55
+ """
56
+
57
+ key: str
58
+ value: Any | None
59
+ disk_path: Path | None
60
+ size_bytes: int
61
+ created_at: float
62
+ last_accessed: float
63
+ access_count: int
64
+ in_memory: bool
65
+
66
+
67
+ @dataclass
68
+ class CacheStats:
69
+ """Cache statistics.
70
+
71
+
72
+ Attributes:
73
+ hits: Number of cache hits.
74
+ misses: Number of cache misses.
75
+ evictions: Number of entries evicted.
76
+ disk_spills: Number of entries spilled to disk.
77
+ current_memory: Current memory usage (bytes).
78
+ current_entries: Number of entries in cache.
79
+ disk_entries: Number of entries on disk.
80
+ """
81
+
82
+ hits: int
83
+ misses: int
84
+ evictions: int
85
+ disk_spills: int
86
+ current_memory: int
87
+ current_entries: int
88
+ disk_entries: int
89
+
90
+ @property
91
+ def hit_rate(self) -> float:
92
+ """Calculate cache hit rate (0.0-1.0)."""
93
+ total = self.hits + self.misses
94
+ return self.hits / total if total > 0 else 0.0
95
+
96
+ def __str__(self) -> str:
97
+ """Format stats as human-readable string."""
98
+ return (
99
+ f"Cache Statistics:\n"
100
+ f" Hits: {self.hits}\n"
101
+ f" Misses: {self.misses}\n"
102
+ f" Hit Rate: {self.hit_rate * 100:.1f}%\n"
103
+ f" Evictions: {self.evictions}\n"
104
+ f" Disk Spills: {self.disk_spills}\n"
105
+ f" Memory Usage: {self.current_memory / 1e9:.2f} GB\n"
106
+ f" Entries (Memory): {self.current_entries}\n"
107
+ f" Entries (Disk): {self.disk_entries}\n"
108
+ )
109
+
110
+
111
+ class TraceKitCache:
112
+ """LRU cache with disk spillover for intermediate results.
113
+
114
+
115
+ Caches expensive computation results with automatic memory management.
116
+ When memory limit is exceeded, least-recently-used entries are spilled
117
+ to disk. Automatic cleanup on exit.
118
+
119
+ Args:
120
+ max_memory: Maximum memory for cache (bytes or string like "2GB").
121
+ cache_dir: Directory for disk cache (default: /tmp/oscura_cache).
122
+ auto_cleanup: Clean up disk cache on exit (default: True).
123
+
124
+ Example:
125
+ >>> cache = TraceKitCache(max_memory="1GB")
126
+ >>> result = cache.get_or_compute("key", expensive_func, arg1, arg2)
127
+ >>> stats = cache.get_stats()
128
+ >>> cache.clear()
129
+
130
+ Security Note:
131
+ Cache files use pickle serialization. Cache directory should be in
132
+ a secure location with appropriate permissions. Do not share cache
133
+ directories across security boundaries. The cache is intended for
134
+ single-user, local computation only.
135
+
136
+ References:
137
+ MEM-031: Persistent Cache (Disk-Based)
138
+ MEM-029: LRU Cache for Intermediate Results
139
+ """
140
+
141
+ def __init__(
142
+ self,
143
+ max_memory: int | str = "2GB",
144
+ *,
145
+ cache_dir: str | Path | None = None,
146
+ auto_cleanup: bool = True,
147
+ ):
148
+ """Initialize cache.
149
+
150
+ Args:
151
+ max_memory: Maximum memory (bytes or string).
152
+ cache_dir: Directory for disk cache.
153
+ auto_cleanup: Clean up on exit.
154
+ """
155
+ # Parse max_memory
156
+ if isinstance(max_memory, str):
157
+ self.max_memory = self._parse_memory_string(max_memory)
158
+ else:
159
+ self.max_memory = int(max_memory)
160
+
161
+ # Set up cache directory
162
+ if cache_dir is None:
163
+ self.cache_dir = Path(tempfile.gettempdir()) / "oscura_cache"
164
+ else:
165
+ self.cache_dir = Path(cache_dir)
166
+
167
+ self.cache_dir.mkdir(parents=True, exist_ok=True)
168
+ self.auto_cleanup = auto_cleanup
169
+
170
+ # Cache storage (LRU via OrderedDict)
171
+ self._cache: OrderedDict[str, CacheEntry] = OrderedDict()
172
+
173
+ # Thread lock for thread-safe operations (MEM-031)
174
+ self._lock = threading.RLock()
175
+
176
+ # Statistics
177
+ self._hits = 0
178
+ self._misses = 0
179
+ self._evictions = 0
180
+ self._disk_spills = 0
181
+ self._current_memory = 0
182
+
183
+ def __enter__(self) -> TraceKitCache:
184
+ """Enter context."""
185
+ return self
186
+
187
+ def __exit__(self, exc_type, exc_val, exc_tb) -> None: # type: ignore[no-untyped-def]
188
+ """Exit context and clean up if enabled."""
189
+ # Note: exc_val and exc_tb intentionally unused but required for Python 3.11+ compatibility
190
+ if self.auto_cleanup:
191
+ self.clear()
192
+
193
+ def get(self, key: str) -> Any | None:
194
+ """Get value from cache.
195
+
196
+
197
+ Args:
198
+ key: Cache key.
199
+
200
+ Returns:
201
+ Cached value or None if not found.
202
+
203
+ Example:
204
+ >>> value = cache.get("my_key")
205
+ >>> if value is None:
206
+ ... value = compute_value()
207
+ ... cache.put("my_key", value)
208
+
209
+ References:
210
+ MEM-031: Persistent Cache (Disk-Based)
211
+ """
212
+ with self._lock:
213
+ if key not in self._cache:
214
+ self._misses += 1
215
+ return None
216
+
217
+ # Cache hit
218
+ self._hits += 1
219
+ entry = self._cache[key]
220
+
221
+ # Update access metadata
222
+ entry.last_accessed = time.time()
223
+ entry.access_count += 1
224
+
225
+ # Move to end (most recently used)
226
+ self._cache.move_to_end(key)
227
+
228
+ # Load from disk if needed
229
+ if not entry.in_memory:
230
+ entry.value = self._load_from_disk(entry.disk_path) # type: ignore[arg-type]
231
+ entry.in_memory = True
232
+ self._current_memory += entry.size_bytes
233
+
234
+ # Check if we need to spill to make room
235
+ self._ensure_memory_limit()
236
+
237
+ return entry.value
238
+
239
+ def put(self, key: str, value: Any) -> None:
240
+ """Put value in cache.
241
+
242
+
243
+ Args:
244
+ key: Cache key.
245
+ value: Value to cache.
246
+
247
+ Example:
248
+ >>> cache.put("result_key", computed_result)
249
+
250
+ References:
251
+ MEM-031: Persistent Cache (Disk-Based)
252
+ """
253
+ # Calculate size outside lock (potentially expensive)
254
+ size_bytes = self._estimate_size(value)
255
+
256
+ with self._lock:
257
+ # Remove old entry if exists
258
+ if key in self._cache:
259
+ old_entry = self._cache[key]
260
+ self._current_memory -= old_entry.size_bytes
261
+ if old_entry.disk_path and old_entry.disk_path.exists():
262
+ old_entry.disk_path.unlink()
263
+ del self._cache[key]
264
+
265
+ # Create new entry
266
+ entry = CacheEntry(
267
+ key=key,
268
+ value=value,
269
+ disk_path=None,
270
+ size_bytes=size_bytes,
271
+ created_at=time.time(),
272
+ last_accessed=time.time(),
273
+ access_count=0,
274
+ in_memory=True,
275
+ )
276
+
277
+ # Add to cache
278
+ self._cache[key] = entry
279
+ self._current_memory += size_bytes
280
+
281
+ # Ensure memory limit
282
+ self._ensure_memory_limit()
283
+
284
+ def get_or_compute(
285
+ self,
286
+ key: str,
287
+ compute_fn: Callable[..., T],
288
+ *args: Any,
289
+ **kwargs: Any,
290
+ ) -> T:
291
+ """Get cached value or compute and cache it.
292
+
293
+
294
+ Args:
295
+ key: Cache key.
296
+ compute_fn: Function to compute value if not cached.
297
+ args: Arguments to compute_fn.
298
+ kwargs: Keyword arguments to compute_fn.
299
+
300
+ Returns:
301
+ Cached or computed value.
302
+
303
+ Example:
304
+ >>> result = cache.get_or_compute("fft_key", np.fft.fft, signal)
305
+
306
+ References:
307
+ MEM-029: LRU Cache for Intermediate Results
308
+ """
309
+ value = self.get(key)
310
+ if value is not None:
311
+ return value # type: ignore[no-any-return]
312
+
313
+ # Compute value
314
+ value = compute_fn(*args, **kwargs)
315
+
316
+ # Cache it
317
+ self.put(key, value)
318
+
319
+ return value
320
+
321
+ def clear(self) -> None:
322
+ """Clear all cache entries and disk files.
323
+
324
+
325
+ Example:
326
+ >>> cache.clear()
327
+
328
+ References:
329
+ MEM-031: Persistent Cache (Disk-Based)
330
+ """
331
+ with self._lock:
332
+ # Remove disk files
333
+ for entry in self._cache.values():
334
+ if entry.disk_path and entry.disk_path.exists():
335
+ with contextlib.suppress(OSError):
336
+ entry.disk_path.unlink()
337
+
338
+ # Clear cache
339
+ self._cache.clear()
340
+ self._current_memory = 0
341
+
342
+ # Try to remove cache directory if empty (outside lock, not critical)
343
+ try:
344
+ if self.cache_dir.exists() and not any(self.cache_dir.iterdir()):
345
+ self.cache_dir.rmdir()
346
+ except OSError:
347
+ pass
348
+
349
+ def get_stats(self) -> CacheStats:
350
+ """Get cache statistics.
351
+
352
+
353
+ Returns:
354
+ CacheStats object.
355
+
356
+ Example:
357
+ >>> stats = cache.get_stats()
358
+ >>> print(f"Hit rate: {stats.hit_rate * 100:.1f}%")
359
+
360
+ References:
361
+ MEM-031: Persistent Cache (Disk-Based)
362
+ """
363
+ with self._lock:
364
+ disk_entries = sum(1 for e in self._cache.values() if not e.in_memory)
365
+ return CacheStats(
366
+ hits=self._hits,
367
+ misses=self._misses,
368
+ evictions=self._evictions,
369
+ disk_spills=self._disk_spills,
370
+ current_memory=self._current_memory,
371
+ current_entries=len(self._cache),
372
+ disk_entries=disk_entries,
373
+ )
374
+
375
+ def show_stats(self) -> None:
376
+ """Print cache statistics.
377
+
378
+
379
+ Example:
380
+ >>> cache.show_stats()
381
+ Cache Statistics: 42 hits, 15 misses (73.7% hit rate)
382
+
383
+ References:
384
+ MEM-031: Persistent Cache (Disk-Based)
385
+ """
386
+ stats = self.get_stats()
387
+ print(stats)
388
+
389
+ def compute_key(self, *args: Any, **kwargs: Any) -> str:
390
+ """Compute cache key from arguments.
391
+
392
+ Creates a hash key from arbitrary arguments for cache lookups.
393
+
394
+ Args:
395
+ args: Positional arguments.
396
+ kwargs: Keyword arguments.
397
+
398
+ Returns:
399
+ Hash key string.
400
+
401
+ Example:
402
+ >>> key = cache.compute_key("operation", param1=10, param2="value")
403
+
404
+ References:
405
+ MEM-029: LRU Cache for Intermediate Results
406
+ """
407
+ # Create hashable representation
408
+ hash_obj = hashlib.sha256()
409
+
410
+ # Hash positional args
411
+ for arg in args:
412
+ hash_obj.update(self._make_hashable(arg))
413
+
414
+ # Hash keyword args (sorted for consistency)
415
+ for k in sorted(kwargs.keys()):
416
+ hash_obj.update(k.encode())
417
+ hash_obj.update(self._make_hashable(kwargs[k]))
418
+
419
+ return hash_obj.hexdigest()
420
+
421
+ def _ensure_memory_limit(self) -> None:
422
+ """Ensure cache memory usage is within limit."""
423
+ while self._current_memory > self.max_memory and self._cache:
424
+ # Evict least recently used entry
425
+ key, entry = self._cache.popitem(last=False)
426
+
427
+ if entry.in_memory:
428
+ # Spill to disk if not already there
429
+ if entry.disk_path is None:
430
+ entry.disk_path = self._spill_to_disk(key, entry.value)
431
+ entry.in_memory = False
432
+ entry.value = None
433
+ self._disk_spills += 1
434
+
435
+ # Put back in cache (on disk)
436
+ self._cache[key] = entry
437
+ self._cache.move_to_end(key, last=False)
438
+
439
+ self._current_memory -= entry.size_bytes
440
+
441
+ self._evictions += 1
442
+
443
+ def _spill_to_disk(self, key: str, value: Any) -> Path:
444
+ """Write value to disk.
445
+
446
+
447
+ Args:
448
+ key: Cache key.
449
+ value: Value to write.
450
+
451
+ Returns:
452
+ Path to disk file.
453
+ """
454
+ disk_path = self.cache_dir / f"{key}.pkl"
455
+ with open(disk_path, "wb") as f:
456
+ pickle.dump(value, f, protocol=pickle.HIGHEST_PROTOCOL)
457
+ return disk_path
458
+
459
+ def _load_from_disk(self, disk_path: Path) -> Any:
460
+ """Load value from disk.
461
+
462
+
463
+ Args:
464
+ disk_path: Path to disk file.
465
+
466
+ Returns:
467
+ Loaded value.
468
+ """
469
+ with open(disk_path, "rb") as f:
470
+ return pickle.load(f)
471
+
472
+ def _estimate_size(self, value: Any) -> int:
473
+ """Estimate size of value in bytes."""
474
+ if isinstance(value, np.ndarray):
475
+ return value.nbytes # type: ignore[no-any-return]
476
+ elif isinstance(value, list | tuple):
477
+ return sum(self._estimate_size(item) for item in value)
478
+ elif isinstance(value, dict):
479
+ return sum(self._estimate_size(k) + self._estimate_size(v) for k, v in value.items())
480
+ else:
481
+ # Fallback: use pickle size
482
+ try:
483
+ return len(pickle.dumps(value, protocol=pickle.HIGHEST_PROTOCOL))
484
+ except (TypeError, pickle.PicklingError):
485
+ return 1024 # Default estimate
486
+
487
+ def _make_hashable(self, obj: Any) -> bytes:
488
+ """Convert object to hashable bytes."""
489
+ if isinstance(obj, np.ndarray):
490
+ # Use array bytes for hashing
491
+ return obj.tobytes() # type: ignore[no-any-return]
492
+ elif isinstance(obj, str | bytes):
493
+ return obj.encode() if isinstance(obj, str) else obj
494
+ elif isinstance(obj, int | float | bool):
495
+ return str(obj).encode()
496
+ elif isinstance(obj, list | tuple):
497
+ return b"".join(self._make_hashable(item) for item in obj)
498
+ else:
499
+ # Fallback: use string representation
500
+ return str(obj).encode()
501
+
502
+ def _parse_memory_string(self, memory_str: str) -> int:
503
+ """Parse memory string like '2GB' to bytes."""
504
+ memory_str = memory_str.strip().upper()
505
+
506
+ if memory_str.endswith("GB"):
507
+ return int(float(memory_str[:-2]) * 1e9)
508
+ elif memory_str.endswith("MB"):
509
+ return int(float(memory_str[:-2]) * 1e6)
510
+ elif memory_str.endswith("KB"):
511
+ return int(float(memory_str[:-2]) * 1e3)
512
+ else:
513
+ return int(memory_str)
514
+
515
+
516
+ # Global cache instance
517
+ _global_cache: TraceKitCache | None = None
518
+
519
+
520
+ def get_cache(
521
+ max_memory: int | str = "2GB",
522
+ *,
523
+ cache_dir: str | Path | None = None,
524
+ ) -> TraceKitCache:
525
+ """Get or create global cache instance.
526
+
527
+
528
+ Args:
529
+ max_memory: Maximum memory for cache.
530
+ cache_dir: Cache directory.
531
+
532
+ Returns:
533
+ Global TraceKitCache instance.
534
+
535
+ Example:
536
+ >>> cache = get_cache(max_memory="1GB")
537
+ >>> result = cache.get_or_compute("key", compute_fn, args)
538
+
539
+ References:
540
+ MEM-031: Persistent Cache (Disk-Based)
541
+ """
542
+ global _global_cache
543
+
544
+ if _global_cache is None:
545
+ _global_cache = TraceKitCache(max_memory, cache_dir=cache_dir)
546
+
547
+ return _global_cache
548
+
549
+
550
+ def clear_cache() -> None:
551
+ """Clear global cache.
552
+
553
+ Example:
554
+ >>> clear_cache()
555
+
556
+ References:
557
+ MEM-031: Persistent Cache (Disk-Based)
558
+ """
559
+ global _global_cache
560
+
561
+ if _global_cache is not None:
562
+ _global_cache.clear()
563
+ _global_cache = None
564
+
565
+
566
+ def show_cache_stats() -> None:
567
+ """Show global cache statistics.
568
+
569
+
570
+ Example:
571
+ >>> show_cache_stats()
572
+ Cache Statistics: 42 hits, 15 misses (73.7% hit rate)
573
+
574
+ References:
575
+ MEM-031: Persistent Cache (Disk-Based)
576
+ """
577
+ if _global_cache is not None:
578
+ _global_cache.show_stats()
579
+ else:
580
+ print("Cache not initialized")
581
+
582
+
583
+ __all__ = [
584
+ "CacheEntry",
585
+ "CacheStats",
586
+ "TraceKitCache",
587
+ "clear_cache",
588
+ "get_cache",
589
+ "show_cache_stats",
590
+ ]