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
@@ -0,0 +1,583 @@
1
+ """Memory-mapped file loader for huge waveform files.
2
+
3
+ This module provides efficient memory-mapped loading for GB+ files that cannot
4
+ fit in RAM. Unlike eager loading, memory-mapped arrays don't load the entire
5
+ file into memory but access it in chunks on-demand via the OS page cache.
6
+
7
+ Key features:
8
+ - Zero-copy data access via numpy.memmap
9
+ - Chunked iteration for processing huge files
10
+ - Integration with existing TraceKit loader infrastructure
11
+ - Support for common binary formats (raw, NPY, structured)
12
+ - Automatic fallback to regular loading for small files
13
+
14
+ Example:
15
+ >>> from oscura.loaders.mmap_loader import load_mmap
16
+ >>> # Load 10 GB file without loading all data to RAM
17
+ >>> trace = load_mmap("huge_trace.npy", sample_rate=1e9)
18
+ >>> print(f"Length: {len(trace)} samples")
19
+ >>>
20
+ >>> # Process in chunks to avoid OOM
21
+ >>> for chunk in trace.iter_chunks(chunk_size=1_000_000):
22
+ ... result = analyze_chunk(chunk)
23
+
24
+ References:
25
+ Performance optimization for huge files (>1 GB)
26
+ API-017: Lazy Loading for Huge Files
27
+ """
28
+
29
+ from __future__ import annotations
30
+
31
+ from collections.abc import Iterator
32
+ from pathlib import Path
33
+ from typing import TYPE_CHECKING, Any
34
+
35
+ import numpy as np
36
+
37
+ from oscura.core.exceptions import LoaderError
38
+ from oscura.core.types import TraceMetadata
39
+
40
+ if TYPE_CHECKING:
41
+ from os import PathLike
42
+
43
+ from numpy.typing import DTypeLike, NDArray
44
+
45
+
46
+ # File size threshold for automatic mmap suggestion (1 GB)
47
+ MMAP_THRESHOLD = 1024 * 1024 * 1024
48
+
49
+
50
+ class MmapWaveformTrace:
51
+ """Memory-mapped waveform trace for huge files.
52
+
53
+ Provides lazy access to waveform data via memory mapping. Data is not
54
+ loaded into RAM but accessed directly from disk through the OS page cache.
55
+
56
+ This allows working with files larger than available RAM without OOM errors.
57
+
58
+ Attributes:
59
+ file_path: Path to the memory-mapped file.
60
+ sample_rate: Sample rate in Hz.
61
+ length: Number of samples in the trace.
62
+ dtype: NumPy dtype of the samples.
63
+ metadata: Additional trace metadata.
64
+
65
+ Example:
66
+ >>> trace = MmapWaveformTrace(
67
+ ... file_path="huge_trace.bin",
68
+ ... sample_rate=1e9,
69
+ ... length=10_000_000_000,
70
+ ... dtype=np.float32
71
+ ... )
72
+ >>> # Access subset without loading entire file
73
+ >>> subset = trace[1000:2000]
74
+ >>> # Process in chunks
75
+ >>> for chunk in trace.iter_chunks(chunk_size=1_000_000):
76
+ ... process(chunk)
77
+ """
78
+
79
+ def __init__(
80
+ self,
81
+ file_path: str | Path,
82
+ sample_rate: float,
83
+ length: int,
84
+ *,
85
+ dtype: DTypeLike = np.float64,
86
+ offset: int = 0,
87
+ metadata: dict[str, Any] | None = None,
88
+ mode: str = "r",
89
+ ) -> None:
90
+ """Initialize memory-mapped trace.
91
+
92
+ Args:
93
+ file_path: Path to binary data file.
94
+ sample_rate: Sample rate in Hz.
95
+ length: Number of samples.
96
+ dtype: Data type of samples.
97
+ offset: Byte offset to start of data in file.
98
+ metadata: Additional metadata dictionary.
99
+ mode: File access mode ('r' for read-only, 'r+' for read-write).
100
+
101
+ Raises:
102
+ LoaderError: If file not found or invalid parameters.
103
+
104
+ Example:
105
+ >>> trace = MmapWaveformTrace(
106
+ ... file_path="trace.f32",
107
+ ... sample_rate=1e9,
108
+ ... length=1_000_000_000,
109
+ ... dtype=np.float32
110
+ ... )
111
+ """
112
+ self._file_path = Path(file_path)
113
+ self._sample_rate = float(sample_rate)
114
+ self._length = int(length)
115
+ self._dtype = np.dtype(dtype)
116
+ self._offset = int(offset)
117
+ self._metadata = metadata or {}
118
+ self._mode = mode
119
+
120
+ # Memory-mapped array - created on first access
121
+ self._memmap: np.memmap[Any, np.dtype[Any]] | None = None
122
+
123
+ # Validate inputs
124
+ if self._sample_rate <= 0:
125
+ raise LoaderError(f"sample_rate must be positive, got {self._sample_rate}")
126
+ if self._length < 0:
127
+ raise LoaderError(f"length must be non-negative, got {self._length}")
128
+ if self._offset < 0:
129
+ raise LoaderError(f"offset must be non-negative, got {self._offset}")
130
+
131
+ # Verify file exists
132
+ if not self._file_path.exists():
133
+ raise LoaderError(f"File not found: {self._file_path}")
134
+
135
+ # Verify file size
136
+ expected_size = self._offset + self._length * self._dtype.itemsize
137
+ actual_size = self._file_path.stat().st_size
138
+ if actual_size < expected_size:
139
+ raise LoaderError(
140
+ f"File too small for requested data. "
141
+ f"Expected at least {expected_size} bytes, got {actual_size} bytes",
142
+ file_path=str(self._file_path),
143
+ )
144
+
145
+ @property
146
+ def sample_rate(self) -> float:
147
+ """Sample rate in Hz."""
148
+ return self._sample_rate
149
+
150
+ @property
151
+ def length(self) -> int:
152
+ """Number of samples."""
153
+ return self._length
154
+
155
+ @property
156
+ def duration(self) -> float:
157
+ """Duration in seconds."""
158
+ return self._length / self._sample_rate
159
+
160
+ @property
161
+ def metadata(self) -> dict[str, Any]:
162
+ """Metadata dictionary."""
163
+ return self._metadata
164
+
165
+ @property
166
+ def dtype(self) -> np.dtype[Any]:
167
+ """Data type of samples."""
168
+ return self._dtype
169
+
170
+ @property
171
+ def file_path(self) -> Path:
172
+ """Path to memory-mapped file."""
173
+ return self._file_path
174
+
175
+ @property
176
+ def data(self) -> np.memmap[Any, np.dtype[Any]]:
177
+ """Memory-mapped data array.
178
+
179
+ Returns a numpy.memmap object that behaves like a numpy array
180
+ but doesn't load data into memory until accessed.
181
+
182
+ Returns:
183
+ Memory-mapped numpy array.
184
+
185
+ Example:
186
+ >>> trace = load_mmap("huge.npy", sample_rate=1e9)
187
+ >>> data = trace.data # No data loaded yet
188
+ >>> subset = data[1000:2000] # Only this range loaded
189
+ """
190
+ if self._memmap is None:
191
+ self._memmap = np.memmap( # type: ignore[call-overload]
192
+ str(self._file_path),
193
+ dtype=self._dtype,
194
+ mode=self._mode,
195
+ offset=self._offset,
196
+ shape=(self._length,),
197
+ )
198
+ return self._memmap
199
+
200
+ @property
201
+ def time_vector(self) -> NDArray[np.float64]:
202
+ """Time vector in seconds.
203
+
204
+ Note: For huge traces, this can consume significant memory.
205
+ Consider using time values on-demand instead.
206
+
207
+ Returns:
208
+ Array of time values corresponding to samples.
209
+
210
+ Example:
211
+ >>> # For huge traces, avoid materializing full time vector
212
+ >>> # Instead compute on-demand:
213
+ >>> t_start = 0
214
+ >>> t_end = trace.length / trace.sample_rate
215
+ """
216
+ return np.arange(self._length, dtype=np.float64) / self._sample_rate
217
+
218
+ def __getitem__(self, key: int | slice) -> float | NDArray[np.float64]:
219
+ """Slice the memory-mapped trace.
220
+
221
+ Supports both integer indexing and slicing. Only the requested
222
+ portion is loaded from disk.
223
+
224
+ Args:
225
+ key: Index or slice.
226
+
227
+ Returns:
228
+ Single sample (float) or array slice.
229
+
230
+ Raises:
231
+ TypeError: If key is not int or slice.
232
+
233
+ Example:
234
+ >>> sample = trace[1000] # Load single sample
235
+ >>> chunk = trace[1000:2000] # Load 1000 samples
236
+ >>> every_10th = trace[::10] # Load decimated data
237
+ """
238
+ if isinstance(key, (int, slice)):
239
+ return self.data[key]
240
+ else:
241
+ raise TypeError(f"Indices must be int or slice, not {type(key).__name__}")
242
+
243
+ def __len__(self) -> int:
244
+ """Number of samples."""
245
+ return self._length
246
+
247
+ def iter_chunks(
248
+ self, chunk_size: int = 1_000_000, overlap: int = 0
249
+ ) -> Iterator[NDArray[np.float64]]:
250
+ """Iterate over trace in chunks.
251
+
252
+ Yields consecutive chunks of data, optionally with overlap between
253
+ chunks. This is efficient for processing huge files that don't fit
254
+ in memory.
255
+
256
+ Args:
257
+ chunk_size: Number of samples per chunk.
258
+ overlap: Number of samples to overlap between chunks.
259
+
260
+ Yields:
261
+ Numpy arrays of chunk_size (or smaller for last chunk).
262
+
263
+ Raises:
264
+ ValueError: If chunk_size or overlap invalid.
265
+
266
+ Example:
267
+ >>> # Process 10 GB file in 1M sample chunks
268
+ >>> for chunk in trace.iter_chunks(chunk_size=1_000_000):
269
+ ... result = compute_fft(chunk)
270
+ ...
271
+ >>> # With 50% overlap for windowed processing
272
+ >>> for chunk in trace.iter_chunks(chunk_size=2048, overlap=1024):
273
+ ... spectrum = analyze_spectrum(chunk)
274
+ """
275
+ if chunk_size <= 0:
276
+ raise ValueError(f"chunk_size must be positive, got {chunk_size}")
277
+ if overlap < 0:
278
+ raise ValueError(f"overlap must be non-negative, got {overlap}")
279
+ if overlap >= chunk_size:
280
+ raise ValueError(f"overlap ({overlap}) must be less than chunk_size ({chunk_size})")
281
+
282
+ data = self.data
283
+ step = chunk_size - overlap
284
+
285
+ for start in range(0, self._length, step):
286
+ end = min(start + chunk_size, self._length)
287
+ # Convert memmap slice to regular array to avoid keeping file handle open
288
+ yield np.asarray(data[start:end], dtype=np.float64)
289
+
290
+ def to_eager(self) -> Any:
291
+ """Convert to eager WaveformTrace by loading all data.
292
+
293
+ WARNING: This loads the entire file into memory. Only use this
294
+ if you're sure the data fits in RAM.
295
+
296
+ Returns:
297
+ WaveformTrace with data loaded in memory.
298
+
299
+ Example:
300
+ >>> # Only convert to eager if file is small enough
301
+ >>> if trace.length < 10_000_000:
302
+ ... eager_trace = trace.to_eager()
303
+ """
304
+ from oscura.core.types import WaveformTrace
305
+
306
+ # Load all data into memory
307
+ data = np.asarray(self.data, dtype=np.float64)
308
+
309
+ metadata = TraceMetadata(
310
+ sample_rate=self._sample_rate,
311
+ source_file=str(self._file_path),
312
+ **self._metadata, # type: ignore[arg-type]
313
+ )
314
+
315
+ return WaveformTrace(data=data, metadata=metadata)
316
+
317
+ def close(self) -> None:
318
+ """Close memory-mapped file handle.
319
+
320
+ Should be called when done with the trace to free resources.
321
+ The trace cannot be used after closing.
322
+
323
+ Example:
324
+ >>> trace = load_mmap("huge.npy", sample_rate=1e9)
325
+ >>> # ... use trace ...
326
+ >>> trace.close()
327
+ """
328
+ if self._memmap is not None:
329
+ # Delete reference to allow garbage collection
330
+ del self._memmap
331
+ self._memmap = None
332
+
333
+ def __del__(self) -> None:
334
+ """Cleanup memory map on deletion."""
335
+ self.close()
336
+
337
+ def __repr__(self) -> str:
338
+ """String representation."""
339
+ size_mb = (self._length * self._dtype.itemsize) / (1024 * 1024)
340
+ return (
341
+ f"MmapWaveformTrace("
342
+ f"file={self._file_path.name}, "
343
+ f"sample_rate={self._sample_rate:.2e} Hz, "
344
+ f"length={self._length:,} samples, "
345
+ f"size={size_mb:.1f} MB, "
346
+ f"dtype={self._dtype})"
347
+ )
348
+
349
+ def __enter__(self) -> MmapWaveformTrace:
350
+ """Context manager entry."""
351
+ return self
352
+
353
+ def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
354
+ """Context manager exit - close the file."""
355
+ self.close()
356
+
357
+
358
+ def load_mmap(
359
+ file_path: str | PathLike[str],
360
+ sample_rate: float | None = None,
361
+ *,
362
+ dtype: DTypeLike | None = None,
363
+ offset: int = 0,
364
+ length: int | None = None,
365
+ mode: str = "r",
366
+ **metadata: Any,
367
+ ) -> MmapWaveformTrace:
368
+ """Load waveform file with memory mapping.
369
+
370
+ Creates a memory-mapped trace that doesn't load data into RAM.
371
+ Supports .npy files (auto-detects format) and raw binary files.
372
+
373
+ Args:
374
+ file_path: Path to waveform file (.npy or raw binary).
375
+ sample_rate: Sample rate in Hz (required for raw files, optional for .npy).
376
+ dtype: Data type (required for raw files, auto-detected for .npy).
377
+ offset: Byte offset to data start (auto-computed for .npy).
378
+ length: Number of samples (auto-computed if possible).
379
+ mode: File access mode ('r' for read-only, 'r+' for read-write).
380
+ **metadata: Additional metadata to store.
381
+
382
+ Returns:
383
+ MmapWaveformTrace for memory-mapped access.
384
+
385
+ Raises:
386
+ LoaderError: If file not found or parameters invalid.
387
+
388
+ Example:
389
+ >>> # Load NumPy file (auto-detects format)
390
+ >>> trace = load_mmap("huge_trace.npy", sample_rate=1e9)
391
+ >>>
392
+ >>> # Load raw binary file
393
+ >>> trace = load_mmap(
394
+ ... "data.f32",
395
+ ... sample_rate=1e9,
396
+ ... dtype=np.float32,
397
+ ... length=1_000_000_000
398
+ ... )
399
+ >>>
400
+ >>> # Use context manager
401
+ >>> with load_mmap("huge.npy", sample_rate=1e9) as trace:
402
+ ... for chunk in trace.iter_chunks(chunk_size=1_000_000):
403
+ ... process(chunk)
404
+
405
+ References:
406
+ API-017: Lazy Loading for Huge Files
407
+ """
408
+ file_path = Path(file_path)
409
+
410
+ if not file_path.exists():
411
+ raise LoaderError(f"File not found: {file_path}")
412
+
413
+ suffix = file_path.suffix.lower()
414
+
415
+ # Handle .npy files with automatic format detection
416
+ if suffix == ".npy":
417
+ return _load_npy_mmap(file_path, sample_rate, mode, metadata)
418
+
419
+ # Handle .npz files (not directly memory-mappable, but can extract)
420
+ elif suffix == ".npz":
421
+ raise LoaderError(
422
+ "NPZ files cannot be directly memory-mapped. "
423
+ "Extract the array first using np.load() and save as .npy",
424
+ file_path=str(file_path),
425
+ fix_hint="Use: np.save('array.npy', np.load('file.npz')['array'])",
426
+ )
427
+
428
+ # Handle raw binary files
429
+ else:
430
+ if dtype is None:
431
+ raise LoaderError(
432
+ "dtype is required for raw binary files",
433
+ file_path=str(file_path),
434
+ fix_hint="Specify dtype, e.g., dtype=np.float32",
435
+ )
436
+ if sample_rate is None:
437
+ raise LoaderError(
438
+ "sample_rate is required for raw binary files",
439
+ file_path=str(file_path),
440
+ )
441
+
442
+ # Compute length from file size if not provided
443
+ dtype_np = np.dtype(dtype)
444
+ if length is None:
445
+ file_size = file_path.stat().st_size - offset
446
+ length = file_size // dtype_np.itemsize
447
+
448
+ return MmapWaveformTrace(
449
+ file_path=file_path,
450
+ sample_rate=sample_rate,
451
+ length=length,
452
+ dtype=dtype_np,
453
+ offset=offset,
454
+ metadata=metadata,
455
+ mode=mode,
456
+ )
457
+
458
+
459
+ def _load_npy_mmap(
460
+ file_path: Path,
461
+ sample_rate: float | None,
462
+ mode: str,
463
+ metadata: dict[str, Any],
464
+ ) -> MmapWaveformTrace:
465
+ """Load NumPy .npy file with memory mapping.
466
+
467
+ Reads the .npy header to extract dtype, shape, and data offset,
468
+ then creates a memory-mapped array.
469
+
470
+ Args:
471
+ file_path: Path to .npy file.
472
+ sample_rate: Sample rate in Hz (required).
473
+ mode: File access mode.
474
+ metadata: Additional metadata.
475
+
476
+ Returns:
477
+ MmapWaveformTrace for the .npy file.
478
+
479
+ Raises:
480
+ LoaderError: If sample_rate not provided or file invalid.
481
+ """
482
+ if sample_rate is None:
483
+ raise LoaderError(
484
+ "sample_rate is required for .npy files",
485
+ file_path=str(file_path),
486
+ fix_hint="Specify sample_rate, e.g., sample_rate=1e9",
487
+ )
488
+
489
+ try:
490
+ # Read NumPy header without loading data
491
+ with open(file_path, "rb") as f:
492
+ import numpy.lib.format as npf
493
+
494
+ # Read header
495
+ version = npf.read_magic(f) # type: ignore[no-untyped-call]
496
+
497
+ if version == (1, 0):
498
+ shape, fortran_order, dtype = npf.read_array_header_1_0(f) # type: ignore[no-untyped-call]
499
+ elif version == (2, 0):
500
+ shape, fortran_order, dtype = npf.read_array_header_2_0(f) # type: ignore[no-untyped-call]
501
+ else:
502
+ raise LoaderError(
503
+ f"Unsupported NPY version: {version}",
504
+ file_path=str(file_path),
505
+ )
506
+
507
+ # Get data offset
508
+ offset = f.tell()
509
+
510
+ # Validate shape
511
+ if not isinstance(shape, tuple):
512
+ raise LoaderError(
513
+ f"Invalid .npy shape: {shape}",
514
+ file_path=str(file_path),
515
+ )
516
+
517
+ if len(shape) != 1:
518
+ raise LoaderError(
519
+ f"Expected 1D array, got shape {shape}",
520
+ file_path=str(file_path),
521
+ fix_hint="Reshape to 1D or extract specific column",
522
+ )
523
+
524
+ length = shape[0]
525
+
526
+ if fortran_order:
527
+ raise LoaderError(
528
+ "Fortran-ordered arrays not supported for memory mapping",
529
+ file_path=str(file_path),
530
+ fix_hint="Resave array in C order: np.save('file.npy', arr, allow_pickle=False)",
531
+ )
532
+
533
+ return MmapWaveformTrace(
534
+ file_path=file_path,
535
+ sample_rate=sample_rate,
536
+ length=length,
537
+ dtype=dtype,
538
+ offset=offset,
539
+ metadata=metadata,
540
+ mode=mode,
541
+ )
542
+
543
+ except Exception as e:
544
+ if isinstance(e, LoaderError):
545
+ raise
546
+ raise LoaderError(
547
+ f"Failed to load .npy file: {e}",
548
+ file_path=str(file_path),
549
+ ) from e
550
+
551
+
552
+ def should_use_mmap(file_path: str | PathLike[str], threshold: int = MMAP_THRESHOLD) -> bool:
553
+ """Check if file should use memory mapping.
554
+
555
+ Recommends memory mapping for files larger than threshold (default 1 GB).
556
+
557
+ Args:
558
+ file_path: Path to file.
559
+ threshold: Size threshold in bytes (default: 1 GB).
560
+
561
+ Returns:
562
+ True if file size >= threshold, False otherwise.
563
+
564
+ Example:
565
+ >>> if should_use_mmap("huge_trace.npy"):
566
+ ... trace = load_mmap("huge_trace.npy", sample_rate=1e9)
567
+ ... else:
568
+ ... trace = load("huge_trace.npy", sample_rate=1e9)
569
+ """
570
+ file_path = Path(file_path)
571
+ if not file_path.exists():
572
+ return False
573
+
574
+ file_size = file_path.stat().st_size
575
+ return file_size >= threshold
576
+
577
+
578
+ __all__ = [
579
+ "MMAP_THRESHOLD",
580
+ "MmapWaveformTrace",
581
+ "load_mmap",
582
+ "should_use_mmap",
583
+ ]