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,510 @@
1
+ """HDF5 file loader for waveform data.
2
+
3
+ This module provides loading of waveform data from HDF5 (.h5) files
4
+ with automatic dataset discovery and attribute-based metadata extraction.
5
+
6
+
7
+ Example:
8
+ >>> from oscura.loaders.hdf5_loader import load_hdf5
9
+ >>> trace = load_hdf5("data.h5")
10
+ >>> print(f"Sample rate: {trace.metadata.sample_rate} Hz")
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ from pathlib import Path
16
+ from typing import TYPE_CHECKING, Any
17
+
18
+ import numpy as np
19
+
20
+ from oscura.core.exceptions import FormatError, LoaderError
21
+ from oscura.core.types import TraceMetadata, WaveformTrace
22
+
23
+ if TYPE_CHECKING:
24
+ from os import PathLike
25
+
26
+ # Try to import h5py
27
+ try:
28
+ import h5py
29
+
30
+ H5PY_AVAILABLE = True
31
+ except ImportError:
32
+ H5PY_AVAILABLE = False
33
+
34
+
35
+ # Common dataset names for waveform data
36
+ DATASET_NAMES = [
37
+ "data",
38
+ "waveform",
39
+ "signal",
40
+ "samples",
41
+ "voltage",
42
+ "trace",
43
+ "ch1",
44
+ "ch2",
45
+ "channel1",
46
+ "channel2",
47
+ "analog",
48
+ ]
49
+
50
+ # Common attribute names for sample rate
51
+ SAMPLE_RATE_ATTRS = [
52
+ "sample_rate",
53
+ "samplerate",
54
+ "sampling_rate",
55
+ "fs",
56
+ "rate",
57
+ "sample_interval",
58
+ "dt",
59
+ ]
60
+
61
+
62
+ class HDF5MmapTrace:
63
+ """Memory-mapped waveform trace backed by HDF5 dataset.
64
+
65
+ Provides lazy access to HDF5 dataset without loading into memory.
66
+ Useful for huge files that don't fit in RAM.
67
+
68
+ Attributes:
69
+ file_path: Path to the HDF5 file.
70
+ dataset_path: Path to dataset within HDF5 file.
71
+ sample_rate: Sample rate in Hz.
72
+ length: Number of samples in the trace.
73
+ metadata: TraceMetadata object.
74
+
75
+ Example:
76
+ >>> trace = HDF5MmapTrace("huge.h5", "/data", metadata)
77
+ >>> # Access subset without loading entire file
78
+ >>> subset = trace[1000:2000]
79
+ """
80
+
81
+ def __init__(
82
+ self,
83
+ file_path: str | Path,
84
+ dataset_path: str,
85
+ metadata: TraceMetadata,
86
+ ) -> None:
87
+ """Initialize HDF5 memory-mapped trace.
88
+
89
+ Args:
90
+ file_path: Path to HDF5 file.
91
+ dataset_path: Path to dataset within file (e.g., "/data").
92
+ metadata: TraceMetadata with sample rate and other info.
93
+
94
+ Raises:
95
+ LoaderError: If file not found or invalid.
96
+ """
97
+ self._file_path = Path(file_path)
98
+ self._dataset_path = dataset_path
99
+ self._metadata = metadata
100
+ self._h5_file: h5py.File | None = None
101
+ self._dataset: h5py.Dataset | None = None
102
+
103
+ if not self._file_path.exists():
104
+ raise LoaderError(f"File not found: {self._file_path}")
105
+
106
+ @property
107
+ def sample_rate(self) -> float:
108
+ """Sample rate in Hz."""
109
+ return self._metadata.sample_rate
110
+
111
+ @property
112
+ def length(self) -> int:
113
+ """Number of samples."""
114
+ self._ensure_open()
115
+ assert self._dataset is not None
116
+ return len(self._dataset)
117
+
118
+ @property
119
+ def metadata(self) -> TraceMetadata:
120
+ """Trace metadata."""
121
+ return self._metadata
122
+
123
+ def _ensure_open(self) -> None:
124
+ """Ensure HDF5 file is open."""
125
+ if self._h5_file is None or self._dataset is None:
126
+ self._h5_file = h5py.File(self._file_path, "r")
127
+ self._dataset = self._h5_file[self._dataset_path]
128
+
129
+ def __getitem__(self, key: int | slice) -> np.ndarray[Any, Any]:
130
+ """Access data by index or slice.
131
+
132
+ Args:
133
+ key: Index or slice.
134
+
135
+ Returns:
136
+ Numpy array of data.
137
+ """
138
+ self._ensure_open()
139
+ assert self._dataset is not None
140
+ data = self._dataset[key]
141
+ return np.asarray(data, dtype=np.float64)
142
+
143
+ def __len__(self) -> int:
144
+ """Return number of samples."""
145
+ return self.length
146
+
147
+ def close(self) -> None:
148
+ """Close HDF5 file handle."""
149
+ if self._h5_file is not None:
150
+ self._h5_file.close()
151
+ self._h5_file = None
152
+ self._dataset = None
153
+
154
+ def __del__(self) -> None:
155
+ """Cleanup on deletion."""
156
+ self.close()
157
+
158
+ def __enter__(self) -> HDF5MmapTrace:
159
+ """Context manager entry."""
160
+ return self
161
+
162
+ def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
163
+ """Context manager exit."""
164
+ self.close()
165
+
166
+ def __repr__(self) -> str:
167
+ """String representation."""
168
+ return (
169
+ f"HDF5MmapTrace("
170
+ f"file={self._file_path.name}, "
171
+ f"dataset={self._dataset_path}, "
172
+ f"sample_rate={self.sample_rate:.2e} Hz, "
173
+ f"length={self.length:,} samples)"
174
+ )
175
+
176
+
177
+ def load_hdf5(
178
+ path: str | PathLike[str],
179
+ *,
180
+ dataset: str | None = None,
181
+ channel: str | int | None = None,
182
+ sample_rate: float | None = None,
183
+ mmap: bool = False,
184
+ ) -> WaveformTrace | HDF5MmapTrace:
185
+ """Load waveform data from an HDF5 file.
186
+
187
+ Loads waveform data and metadata from HDF5 files. Automatically
188
+ discovers datasets and extracts sample rate from attributes.
189
+
190
+ Args:
191
+ path: Path to the HDF5 file.
192
+ dataset: Specific dataset path to load. If None, auto-detects.
193
+ channel: Alias for dataset (for API consistency with other loaders).
194
+ sample_rate: Override sample rate (if not found in attributes).
195
+ mmap: If True, return memory-mapped trace for large files.
196
+
197
+ Returns:
198
+ WaveformTrace containing the waveform data and metadata.
199
+ If mmap=True, returns HDF5MmapTrace instead.
200
+
201
+ Raises:
202
+ LoaderError: If the file cannot be loaded.
203
+ FormatError: If no valid waveform data is found.
204
+
205
+ Example:
206
+ >>> trace = load_hdf5("data.h5")
207
+ >>> print(f"Sample rate: {trace.metadata.sample_rate} Hz")
208
+
209
+ >>> # Load specific dataset
210
+ >>> trace = load_hdf5("multi.h5", dataset="/measurements/ch1")
211
+
212
+ >>> # Load as memory-mapped for large files
213
+ >>> trace = load_hdf5("huge_data.h5", mmap=True)
214
+ """
215
+ if not H5PY_AVAILABLE:
216
+ raise LoaderError(
217
+ "HDF5 support not available",
218
+ details="h5py package is required for HDF5 loading",
219
+ fix_hint="Install h5py: pip install h5py",
220
+ )
221
+
222
+ path = Path(path)
223
+
224
+ if not path.exists():
225
+ raise LoaderError(
226
+ "File not found",
227
+ file_path=str(path),
228
+ )
229
+
230
+ # Use channel as dataset if dataset not specified
231
+ if dataset is None and channel is not None:
232
+ dataset = str(channel)
233
+
234
+ try:
235
+ with h5py.File(path, "r") as f:
236
+ # Find dataset
237
+ if dataset is not None:
238
+ if dataset in f:
239
+ ds = f[dataset]
240
+ else:
241
+ # Try to find by name
242
+ ds = _find_dataset_by_name(f, dataset)
243
+ if ds is None:
244
+ available = list_datasets(path)
245
+ raise FormatError(
246
+ f"Dataset not found: {dataset}",
247
+ file_path=str(path),
248
+ expected=dataset,
249
+ got=f"Available: {', '.join(available)}",
250
+ )
251
+ else:
252
+ # Auto-detect dataset
253
+ ds = _find_waveform_dataset(f)
254
+ if ds is None:
255
+ available = list_datasets(path)
256
+ raise FormatError(
257
+ "No waveform data found in HDF5 file",
258
+ file_path=str(path),
259
+ expected=f"Dataset named: {', '.join(DATASET_NAMES)}",
260
+ got=f"Datasets: {', '.join(available)}",
261
+ )
262
+
263
+ # Extract data
264
+ if not isinstance(ds, h5py.Dataset):
265
+ raise FormatError(
266
+ "Selected path is not a dataset",
267
+ file_path=str(path),
268
+ got=type(ds).__name__,
269
+ )
270
+
271
+ data = np.asarray(ds, dtype=np.float64)
272
+ if data.ndim > 1:
273
+ data = data.ravel()
274
+
275
+ # Extract metadata from attributes
276
+ detected_sample_rate = sample_rate
277
+ if detected_sample_rate is None:
278
+ detected_sample_rate = _find_sample_rate(f, ds)
279
+
280
+ if detected_sample_rate is None:
281
+ detected_sample_rate = 1e6 # Default
282
+
283
+ # Get other metadata
284
+ vertical_scale = _get_attr(ds, ["vertical_scale", "v_scale", "scale"])
285
+ vertical_offset = _get_attr(ds, ["vertical_offset", "v_offset", "offset"])
286
+ channel_name = _get_attr(ds, ["channel_name", "name", "channel"])
287
+
288
+ if channel_name is None:
289
+ channel_name = ds.name.split("/")[-1] if ds.name else "CH1"
290
+
291
+ metadata = TraceMetadata(
292
+ sample_rate=float(detected_sample_rate),
293
+ vertical_scale=float(vertical_scale) if vertical_scale else None,
294
+ vertical_offset=float(vertical_offset) if vertical_offset else None,
295
+ source_file=str(path),
296
+ channel_name=str(channel_name),
297
+ )
298
+
299
+ # Return memory-mapped trace if requested
300
+ if mmap:
301
+ return HDF5MmapTrace(
302
+ file_path=path,
303
+ dataset_path=ds.name,
304
+ metadata=metadata,
305
+ )
306
+
307
+ return WaveformTrace(data=data, metadata=metadata)
308
+
309
+ except OSError as e:
310
+ raise LoaderError(
311
+ "Failed to read HDF5 file",
312
+ file_path=str(path),
313
+ details=str(e),
314
+ ) from e
315
+ except Exception as e:
316
+ if isinstance(e, LoaderError | FormatError):
317
+ raise
318
+ raise LoaderError(
319
+ "Failed to load HDF5 file",
320
+ file_path=str(path),
321
+ details=str(e),
322
+ ) from e
323
+
324
+
325
+ def _find_waveform_dataset(f: h5py.File) -> h5py.Dataset | None:
326
+ """Find a waveform dataset in the HDF5 file."""
327
+ result: h5py.Dataset | None = None
328
+
329
+ def visitor(name: str, obj: Any) -> None:
330
+ nonlocal result
331
+ if result is not None:
332
+ return
333
+ if isinstance(obj, h5py.Dataset):
334
+ name_lower = name.lower().split("/")[-1]
335
+ # Check for common names
336
+ for ds_name in DATASET_NAMES:
337
+ if ds_name in name_lower:
338
+ result = obj
339
+ return
340
+ # Check if it's a 1D numeric array
341
+ if obj.ndim == 1 and obj.size > 10 and np.issubdtype(obj.dtype, np.number):
342
+ if result is None:
343
+ result = obj
344
+
345
+ f.visititems(visitor)
346
+ return result
347
+
348
+
349
+ def _find_dataset_by_name(f: h5py.File, name: str) -> h5py.Dataset | None:
350
+ """Find a dataset by name (case-insensitive partial match)."""
351
+ name_lower = name.lower()
352
+ result: h5py.Dataset | None = None
353
+
354
+ def visitor(path: str, obj: Any) -> None:
355
+ nonlocal result
356
+ if result is not None:
357
+ return
358
+ if isinstance(obj, h5py.Dataset):
359
+ path_lower = path.lower()
360
+ if name_lower in path_lower:
361
+ result = obj
362
+
363
+ f.visititems(visitor)
364
+ return result
365
+
366
+
367
+ def _find_sample_rate(f: h5py.File, ds: h5py.Dataset) -> float | None:
368
+ """Find sample rate from HDF5 attributes."""
369
+ # Check dataset attributes first
370
+ for attr_name in SAMPLE_RATE_ATTRS:
371
+ if attr_name in ds.attrs:
372
+ value = ds.attrs[attr_name]
373
+ if attr_name in ("sample_interval", "dt") and value > 0:
374
+ return 1.0 / float(value)
375
+ return float(value)
376
+
377
+ # Check parent group attributes
378
+ if ds.parent is not None:
379
+ for attr_name in SAMPLE_RATE_ATTRS:
380
+ if attr_name in ds.parent.attrs:
381
+ value = ds.parent.attrs[attr_name]
382
+ if attr_name in ("sample_interval", "dt") and value > 0:
383
+ return 1.0 / float(value)
384
+ return float(value)
385
+
386
+ # Check root attributes
387
+ for attr_name in SAMPLE_RATE_ATTRS:
388
+ if attr_name in f.attrs:
389
+ value = f.attrs[attr_name]
390
+ if attr_name in ("sample_interval", "dt") and value > 0:
391
+ return 1.0 / float(value)
392
+ return float(value)
393
+
394
+ # Check for metadata group
395
+ if "metadata" in f:
396
+ meta = f["metadata"]
397
+ if isinstance(meta, h5py.Group | h5py.Dataset):
398
+ for attr_name in SAMPLE_RATE_ATTRS:
399
+ if attr_name in meta.attrs:
400
+ value = meta.attrs[attr_name]
401
+ if attr_name in ("sample_interval", "dt") and value > 0:
402
+ return 1.0 / float(value)
403
+ return float(value)
404
+
405
+ return None
406
+
407
+
408
+ def _get_attr(obj: h5py.Dataset | h5py.Group, names: list[str]) -> Any | None:
409
+ """Get attribute value by trying multiple names."""
410
+ for name in names:
411
+ if name in obj.attrs:
412
+ value = obj.attrs[name]
413
+ if isinstance(value, bytes):
414
+ return value.decode("utf-8")
415
+ return value
416
+ return None
417
+
418
+
419
+ def list_datasets(path: str | PathLike[str]) -> list[str]:
420
+ """List all datasets in an HDF5 file.
421
+
422
+ Args:
423
+ path: Path to the HDF5 file.
424
+
425
+ Returns:
426
+ List of dataset paths.
427
+
428
+ Raises:
429
+ LoaderError: If h5py is not available or file not found.
430
+
431
+ Example:
432
+ >>> datasets = list_datasets("data.h5")
433
+ >>> print(datasets)
434
+ ['/measurements/ch1', '/measurements/ch2', '/time']
435
+ """
436
+ if not H5PY_AVAILABLE:
437
+ raise LoaderError(
438
+ "HDF5 support not available",
439
+ details="h5py package is required",
440
+ )
441
+
442
+ path = Path(path)
443
+ if not path.exists():
444
+ raise LoaderError("File not found", file_path=str(path))
445
+
446
+ datasets: list[str] = []
447
+
448
+ def visitor(name: str, obj: Any) -> None:
449
+ if isinstance(obj, h5py.Dataset):
450
+ datasets.append("/" + name)
451
+
452
+ try:
453
+ with h5py.File(path, "r") as f:
454
+ f.visititems(visitor)
455
+ except Exception as e:
456
+ raise LoaderError(
457
+ "Failed to read HDF5 file",
458
+ file_path=str(path),
459
+ details=str(e),
460
+ ) from e
461
+
462
+ return datasets
463
+
464
+
465
+ def get_attributes(
466
+ path: str | PathLike[str],
467
+ dataset: str | None = None,
468
+ ) -> dict[str, Any]:
469
+ """Get attributes from an HDF5 file or dataset.
470
+
471
+ Args:
472
+ path: Path to the HDF5 file.
473
+ dataset: Dataset path. If None, returns root attributes.
474
+
475
+ Returns:
476
+ Dictionary of attributes.
477
+
478
+ Raises:
479
+ LoaderError: If h5py is not available or file not found.
480
+ """
481
+ if not H5PY_AVAILABLE:
482
+ raise LoaderError("HDF5 support not available")
483
+
484
+ path = Path(path)
485
+ if not path.exists():
486
+ raise LoaderError("File not found", file_path=str(path))
487
+
488
+ try:
489
+ with h5py.File(path, "r") as f:
490
+ obj = f[dataset] if dataset is not None else f
491
+
492
+ attrs = {}
493
+ for key, value in obj.attrs.items():
494
+ if isinstance(value, bytes):
495
+ value = value.decode("utf-8")
496
+ elif isinstance(value, np.ndarray):
497
+ value = value.tolist()
498
+ attrs[key] = value
499
+
500
+ return attrs
501
+
502
+ except Exception as e:
503
+ raise LoaderError(
504
+ "Failed to read HDF5 attributes",
505
+ file_path=str(path),
506
+ details=str(e),
507
+ ) from e
508
+
509
+
510
+ __all__ = ["get_attributes", "list_datasets", "load_hdf5"]