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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (465) hide show
  1. oscura/__init__.py +813 -8
  2. oscura/__main__.py +392 -0
  3. oscura/analyzers/__init__.py +37 -0
  4. oscura/analyzers/digital/__init__.py +177 -0
  5. oscura/analyzers/digital/bus.py +691 -0
  6. oscura/analyzers/digital/clock.py +805 -0
  7. oscura/analyzers/digital/correlation.py +720 -0
  8. oscura/analyzers/digital/edges.py +632 -0
  9. oscura/analyzers/digital/extraction.py +413 -0
  10. oscura/analyzers/digital/quality.py +878 -0
  11. oscura/analyzers/digital/signal_quality.py +877 -0
  12. oscura/analyzers/digital/thresholds.py +708 -0
  13. oscura/analyzers/digital/timing.py +1104 -0
  14. oscura/analyzers/eye/__init__.py +46 -0
  15. oscura/analyzers/eye/diagram.py +434 -0
  16. oscura/analyzers/eye/metrics.py +555 -0
  17. oscura/analyzers/jitter/__init__.py +83 -0
  18. oscura/analyzers/jitter/ber.py +333 -0
  19. oscura/analyzers/jitter/decomposition.py +759 -0
  20. oscura/analyzers/jitter/measurements.py +413 -0
  21. oscura/analyzers/jitter/spectrum.py +220 -0
  22. oscura/analyzers/measurements.py +40 -0
  23. oscura/analyzers/packet/__init__.py +171 -0
  24. oscura/analyzers/packet/daq.py +1077 -0
  25. oscura/analyzers/packet/metrics.py +437 -0
  26. oscura/analyzers/packet/parser.py +327 -0
  27. oscura/analyzers/packet/payload.py +2156 -0
  28. oscura/analyzers/packet/payload_analysis.py +1312 -0
  29. oscura/analyzers/packet/payload_extraction.py +236 -0
  30. oscura/analyzers/packet/payload_patterns.py +670 -0
  31. oscura/analyzers/packet/stream.py +359 -0
  32. oscura/analyzers/patterns/__init__.py +266 -0
  33. oscura/analyzers/patterns/clustering.py +1036 -0
  34. oscura/analyzers/patterns/discovery.py +539 -0
  35. oscura/analyzers/patterns/learning.py +797 -0
  36. oscura/analyzers/patterns/matching.py +1091 -0
  37. oscura/analyzers/patterns/periodic.py +650 -0
  38. oscura/analyzers/patterns/sequences.py +767 -0
  39. oscura/analyzers/power/__init__.py +116 -0
  40. oscura/analyzers/power/ac_power.py +391 -0
  41. oscura/analyzers/power/basic.py +383 -0
  42. oscura/analyzers/power/conduction.py +314 -0
  43. oscura/analyzers/power/efficiency.py +297 -0
  44. oscura/analyzers/power/ripple.py +356 -0
  45. oscura/analyzers/power/soa.py +372 -0
  46. oscura/analyzers/power/switching.py +479 -0
  47. oscura/analyzers/protocol/__init__.py +150 -0
  48. oscura/analyzers/protocols/__init__.py +150 -0
  49. oscura/analyzers/protocols/base.py +500 -0
  50. oscura/analyzers/protocols/can.py +620 -0
  51. oscura/analyzers/protocols/can_fd.py +448 -0
  52. oscura/analyzers/protocols/flexray.py +405 -0
  53. oscura/analyzers/protocols/hdlc.py +399 -0
  54. oscura/analyzers/protocols/i2c.py +368 -0
  55. oscura/analyzers/protocols/i2s.py +296 -0
  56. oscura/analyzers/protocols/jtag.py +393 -0
  57. oscura/analyzers/protocols/lin.py +445 -0
  58. oscura/analyzers/protocols/manchester.py +333 -0
  59. oscura/analyzers/protocols/onewire.py +501 -0
  60. oscura/analyzers/protocols/spi.py +334 -0
  61. oscura/analyzers/protocols/swd.py +325 -0
  62. oscura/analyzers/protocols/uart.py +393 -0
  63. oscura/analyzers/protocols/usb.py +495 -0
  64. oscura/analyzers/signal_integrity/__init__.py +63 -0
  65. oscura/analyzers/signal_integrity/embedding.py +294 -0
  66. oscura/analyzers/signal_integrity/equalization.py +370 -0
  67. oscura/analyzers/signal_integrity/sparams.py +484 -0
  68. oscura/analyzers/spectral/__init__.py +53 -0
  69. oscura/analyzers/spectral/chunked.py +273 -0
  70. oscura/analyzers/spectral/chunked_fft.py +571 -0
  71. oscura/analyzers/spectral/chunked_wavelet.py +391 -0
  72. oscura/analyzers/spectral/fft.py +92 -0
  73. oscura/analyzers/statistical/__init__.py +250 -0
  74. oscura/analyzers/statistical/checksum.py +923 -0
  75. oscura/analyzers/statistical/chunked_corr.py +228 -0
  76. oscura/analyzers/statistical/classification.py +778 -0
  77. oscura/analyzers/statistical/entropy.py +1113 -0
  78. oscura/analyzers/statistical/ngrams.py +614 -0
  79. oscura/analyzers/statistics/__init__.py +119 -0
  80. oscura/analyzers/statistics/advanced.py +885 -0
  81. oscura/analyzers/statistics/basic.py +263 -0
  82. oscura/analyzers/statistics/correlation.py +630 -0
  83. oscura/analyzers/statistics/distribution.py +298 -0
  84. oscura/analyzers/statistics/outliers.py +463 -0
  85. oscura/analyzers/statistics/streaming.py +93 -0
  86. oscura/analyzers/statistics/trend.py +520 -0
  87. oscura/analyzers/validation.py +598 -0
  88. oscura/analyzers/waveform/__init__.py +36 -0
  89. oscura/analyzers/waveform/measurements.py +943 -0
  90. oscura/analyzers/waveform/measurements_with_uncertainty.py +371 -0
  91. oscura/analyzers/waveform/spectral.py +1689 -0
  92. oscura/analyzers/waveform/wavelets.py +298 -0
  93. oscura/api/__init__.py +62 -0
  94. oscura/api/dsl.py +538 -0
  95. oscura/api/fluent.py +571 -0
  96. oscura/api/operators.py +498 -0
  97. oscura/api/optimization.py +392 -0
  98. oscura/api/profiling.py +396 -0
  99. oscura/automotive/__init__.py +73 -0
  100. oscura/automotive/can/__init__.py +52 -0
  101. oscura/automotive/can/analysis.py +356 -0
  102. oscura/automotive/can/checksum.py +250 -0
  103. oscura/automotive/can/correlation.py +212 -0
  104. oscura/automotive/can/discovery.py +355 -0
  105. oscura/automotive/can/message_wrapper.py +375 -0
  106. oscura/automotive/can/models.py +385 -0
  107. oscura/automotive/can/patterns.py +381 -0
  108. oscura/automotive/can/session.py +452 -0
  109. oscura/automotive/can/state_machine.py +300 -0
  110. oscura/automotive/can/stimulus_response.py +461 -0
  111. oscura/automotive/dbc/__init__.py +15 -0
  112. oscura/automotive/dbc/generator.py +156 -0
  113. oscura/automotive/dbc/parser.py +146 -0
  114. oscura/automotive/dtc/__init__.py +30 -0
  115. oscura/automotive/dtc/database.py +3036 -0
  116. oscura/automotive/j1939/__init__.py +14 -0
  117. oscura/automotive/j1939/decoder.py +745 -0
  118. oscura/automotive/loaders/__init__.py +35 -0
  119. oscura/automotive/loaders/asc.py +98 -0
  120. oscura/automotive/loaders/blf.py +77 -0
  121. oscura/automotive/loaders/csv_can.py +136 -0
  122. oscura/automotive/loaders/dispatcher.py +136 -0
  123. oscura/automotive/loaders/mdf.py +331 -0
  124. oscura/automotive/loaders/pcap.py +132 -0
  125. oscura/automotive/obd/__init__.py +14 -0
  126. oscura/automotive/obd/decoder.py +707 -0
  127. oscura/automotive/uds/__init__.py +48 -0
  128. oscura/automotive/uds/decoder.py +265 -0
  129. oscura/automotive/uds/models.py +64 -0
  130. oscura/automotive/visualization.py +369 -0
  131. oscura/batch/__init__.py +55 -0
  132. oscura/batch/advanced.py +627 -0
  133. oscura/batch/aggregate.py +300 -0
  134. oscura/batch/analyze.py +139 -0
  135. oscura/batch/logging.py +487 -0
  136. oscura/batch/metrics.py +556 -0
  137. oscura/builders/__init__.py +41 -0
  138. oscura/builders/signal_builder.py +1131 -0
  139. oscura/cli/__init__.py +14 -0
  140. oscura/cli/batch.py +339 -0
  141. oscura/cli/characterize.py +273 -0
  142. oscura/cli/compare.py +775 -0
  143. oscura/cli/decode.py +551 -0
  144. oscura/cli/main.py +247 -0
  145. oscura/cli/shell.py +350 -0
  146. oscura/comparison/__init__.py +66 -0
  147. oscura/comparison/compare.py +397 -0
  148. oscura/comparison/golden.py +487 -0
  149. oscura/comparison/limits.py +391 -0
  150. oscura/comparison/mask.py +434 -0
  151. oscura/comparison/trace_diff.py +30 -0
  152. oscura/comparison/visualization.py +481 -0
  153. oscura/compliance/__init__.py +70 -0
  154. oscura/compliance/advanced.py +756 -0
  155. oscura/compliance/masks.py +363 -0
  156. oscura/compliance/reporting.py +483 -0
  157. oscura/compliance/testing.py +298 -0
  158. oscura/component/__init__.py +38 -0
  159. oscura/component/impedance.py +365 -0
  160. oscura/component/reactive.py +598 -0
  161. oscura/component/transmission_line.py +312 -0
  162. oscura/config/__init__.py +191 -0
  163. oscura/config/defaults.py +254 -0
  164. oscura/config/loader.py +348 -0
  165. oscura/config/memory.py +271 -0
  166. oscura/config/migration.py +458 -0
  167. oscura/config/pipeline.py +1077 -0
  168. oscura/config/preferences.py +530 -0
  169. oscura/config/protocol.py +875 -0
  170. oscura/config/schema.py +713 -0
  171. oscura/config/settings.py +420 -0
  172. oscura/config/thresholds.py +599 -0
  173. oscura/convenience.py +457 -0
  174. oscura/core/__init__.py +299 -0
  175. oscura/core/audit.py +457 -0
  176. oscura/core/backend_selector.py +405 -0
  177. oscura/core/cache.py +590 -0
  178. oscura/core/cancellation.py +439 -0
  179. oscura/core/confidence.py +225 -0
  180. oscura/core/config.py +506 -0
  181. oscura/core/correlation.py +216 -0
  182. oscura/core/cross_domain.py +422 -0
  183. oscura/core/debug.py +301 -0
  184. oscura/core/edge_cases.py +541 -0
  185. oscura/core/exceptions.py +535 -0
  186. oscura/core/gpu_backend.py +523 -0
  187. oscura/core/lazy.py +832 -0
  188. oscura/core/log_query.py +540 -0
  189. oscura/core/logging.py +931 -0
  190. oscura/core/logging_advanced.py +952 -0
  191. oscura/core/memoize.py +171 -0
  192. oscura/core/memory_check.py +274 -0
  193. oscura/core/memory_guard.py +290 -0
  194. oscura/core/memory_limits.py +336 -0
  195. oscura/core/memory_monitor.py +453 -0
  196. oscura/core/memory_progress.py +465 -0
  197. oscura/core/memory_warnings.py +315 -0
  198. oscura/core/numba_backend.py +362 -0
  199. oscura/core/performance.py +352 -0
  200. oscura/core/progress.py +524 -0
  201. oscura/core/provenance.py +358 -0
  202. oscura/core/results.py +331 -0
  203. oscura/core/types.py +504 -0
  204. oscura/core/uncertainty.py +383 -0
  205. oscura/discovery/__init__.py +52 -0
  206. oscura/discovery/anomaly_detector.py +672 -0
  207. oscura/discovery/auto_decoder.py +415 -0
  208. oscura/discovery/comparison.py +497 -0
  209. oscura/discovery/quality_validator.py +528 -0
  210. oscura/discovery/signal_detector.py +769 -0
  211. oscura/dsl/__init__.py +73 -0
  212. oscura/dsl/commands.py +246 -0
  213. oscura/dsl/interpreter.py +455 -0
  214. oscura/dsl/parser.py +689 -0
  215. oscura/dsl/repl.py +172 -0
  216. oscura/exceptions.py +59 -0
  217. oscura/exploratory/__init__.py +111 -0
  218. oscura/exploratory/error_recovery.py +642 -0
  219. oscura/exploratory/fuzzy.py +513 -0
  220. oscura/exploratory/fuzzy_advanced.py +786 -0
  221. oscura/exploratory/legacy.py +831 -0
  222. oscura/exploratory/parse.py +358 -0
  223. oscura/exploratory/recovery.py +275 -0
  224. oscura/exploratory/sync.py +382 -0
  225. oscura/exploratory/unknown.py +707 -0
  226. oscura/export/__init__.py +25 -0
  227. oscura/export/wireshark/README.md +265 -0
  228. oscura/export/wireshark/__init__.py +47 -0
  229. oscura/export/wireshark/generator.py +312 -0
  230. oscura/export/wireshark/lua_builder.py +159 -0
  231. oscura/export/wireshark/templates/dissector.lua.j2 +92 -0
  232. oscura/export/wireshark/type_mapping.py +165 -0
  233. oscura/export/wireshark/validator.py +105 -0
  234. oscura/exporters/__init__.py +94 -0
  235. oscura/exporters/csv.py +303 -0
  236. oscura/exporters/exporters.py +44 -0
  237. oscura/exporters/hdf5.py +219 -0
  238. oscura/exporters/html_export.py +701 -0
  239. oscura/exporters/json_export.py +291 -0
  240. oscura/exporters/markdown_export.py +367 -0
  241. oscura/exporters/matlab_export.py +354 -0
  242. oscura/exporters/npz_export.py +219 -0
  243. oscura/exporters/spice_export.py +210 -0
  244. oscura/extensibility/__init__.py +131 -0
  245. oscura/extensibility/docs.py +752 -0
  246. oscura/extensibility/extensions.py +1125 -0
  247. oscura/extensibility/logging.py +259 -0
  248. oscura/extensibility/measurements.py +485 -0
  249. oscura/extensibility/plugins.py +414 -0
  250. oscura/extensibility/registry.py +346 -0
  251. oscura/extensibility/templates.py +913 -0
  252. oscura/extensibility/validation.py +651 -0
  253. oscura/filtering/__init__.py +89 -0
  254. oscura/filtering/base.py +563 -0
  255. oscura/filtering/convenience.py +564 -0
  256. oscura/filtering/design.py +725 -0
  257. oscura/filtering/filters.py +32 -0
  258. oscura/filtering/introspection.py +605 -0
  259. oscura/guidance/__init__.py +24 -0
  260. oscura/guidance/recommender.py +429 -0
  261. oscura/guidance/wizard.py +518 -0
  262. oscura/inference/__init__.py +251 -0
  263. oscura/inference/active_learning/README.md +153 -0
  264. oscura/inference/active_learning/__init__.py +38 -0
  265. oscura/inference/active_learning/lstar.py +257 -0
  266. oscura/inference/active_learning/observation_table.py +230 -0
  267. oscura/inference/active_learning/oracle.py +78 -0
  268. oscura/inference/active_learning/teachers/__init__.py +15 -0
  269. oscura/inference/active_learning/teachers/simulator.py +192 -0
  270. oscura/inference/adaptive_tuning.py +453 -0
  271. oscura/inference/alignment.py +653 -0
  272. oscura/inference/bayesian.py +943 -0
  273. oscura/inference/binary.py +1016 -0
  274. oscura/inference/crc_reverse.py +711 -0
  275. oscura/inference/logic.py +288 -0
  276. oscura/inference/message_format.py +1305 -0
  277. oscura/inference/protocol.py +417 -0
  278. oscura/inference/protocol_dsl.py +1084 -0
  279. oscura/inference/protocol_library.py +1230 -0
  280. oscura/inference/sequences.py +809 -0
  281. oscura/inference/signal_intelligence.py +1509 -0
  282. oscura/inference/spectral.py +215 -0
  283. oscura/inference/state_machine.py +634 -0
  284. oscura/inference/stream.py +918 -0
  285. oscura/integrations/__init__.py +59 -0
  286. oscura/integrations/llm.py +1827 -0
  287. oscura/jupyter/__init__.py +32 -0
  288. oscura/jupyter/display.py +268 -0
  289. oscura/jupyter/magic.py +334 -0
  290. oscura/loaders/__init__.py +526 -0
  291. oscura/loaders/binary.py +69 -0
  292. oscura/loaders/configurable.py +1255 -0
  293. oscura/loaders/csv.py +26 -0
  294. oscura/loaders/csv_loader.py +473 -0
  295. oscura/loaders/hdf5.py +9 -0
  296. oscura/loaders/hdf5_loader.py +510 -0
  297. oscura/loaders/lazy.py +370 -0
  298. oscura/loaders/mmap_loader.py +583 -0
  299. oscura/loaders/numpy_loader.py +436 -0
  300. oscura/loaders/pcap.py +432 -0
  301. oscura/loaders/preprocessing.py +368 -0
  302. oscura/loaders/rigol.py +287 -0
  303. oscura/loaders/sigrok.py +321 -0
  304. oscura/loaders/tdms.py +367 -0
  305. oscura/loaders/tektronix.py +711 -0
  306. oscura/loaders/validation.py +584 -0
  307. oscura/loaders/vcd.py +464 -0
  308. oscura/loaders/wav.py +233 -0
  309. oscura/math/__init__.py +45 -0
  310. oscura/math/arithmetic.py +824 -0
  311. oscura/math/interpolation.py +413 -0
  312. oscura/onboarding/__init__.py +39 -0
  313. oscura/onboarding/help.py +498 -0
  314. oscura/onboarding/tutorials.py +405 -0
  315. oscura/onboarding/wizard.py +466 -0
  316. oscura/optimization/__init__.py +19 -0
  317. oscura/optimization/parallel.py +440 -0
  318. oscura/optimization/search.py +532 -0
  319. oscura/pipeline/__init__.py +43 -0
  320. oscura/pipeline/base.py +338 -0
  321. oscura/pipeline/composition.py +242 -0
  322. oscura/pipeline/parallel.py +448 -0
  323. oscura/pipeline/pipeline.py +375 -0
  324. oscura/pipeline/reverse_engineering.py +1119 -0
  325. oscura/plugins/__init__.py +122 -0
  326. oscura/plugins/base.py +272 -0
  327. oscura/plugins/cli.py +497 -0
  328. oscura/plugins/discovery.py +411 -0
  329. oscura/plugins/isolation.py +418 -0
  330. oscura/plugins/lifecycle.py +959 -0
  331. oscura/plugins/manager.py +493 -0
  332. oscura/plugins/registry.py +421 -0
  333. oscura/plugins/versioning.py +372 -0
  334. oscura/py.typed +0 -0
  335. oscura/quality/__init__.py +65 -0
  336. oscura/quality/ensemble.py +740 -0
  337. oscura/quality/explainer.py +338 -0
  338. oscura/quality/scoring.py +616 -0
  339. oscura/quality/warnings.py +456 -0
  340. oscura/reporting/__init__.py +248 -0
  341. oscura/reporting/advanced.py +1234 -0
  342. oscura/reporting/analyze.py +448 -0
  343. oscura/reporting/argument_preparer.py +596 -0
  344. oscura/reporting/auto_report.py +507 -0
  345. oscura/reporting/batch.py +615 -0
  346. oscura/reporting/chart_selection.py +223 -0
  347. oscura/reporting/comparison.py +330 -0
  348. oscura/reporting/config.py +615 -0
  349. oscura/reporting/content/__init__.py +39 -0
  350. oscura/reporting/content/executive.py +127 -0
  351. oscura/reporting/content/filtering.py +191 -0
  352. oscura/reporting/content/minimal.py +257 -0
  353. oscura/reporting/content/verbosity.py +162 -0
  354. oscura/reporting/core.py +508 -0
  355. oscura/reporting/core_formats/__init__.py +17 -0
  356. oscura/reporting/core_formats/multi_format.py +210 -0
  357. oscura/reporting/engine.py +836 -0
  358. oscura/reporting/export.py +366 -0
  359. oscura/reporting/formatting/__init__.py +129 -0
  360. oscura/reporting/formatting/emphasis.py +81 -0
  361. oscura/reporting/formatting/numbers.py +403 -0
  362. oscura/reporting/formatting/standards.py +55 -0
  363. oscura/reporting/formatting.py +466 -0
  364. oscura/reporting/html.py +578 -0
  365. oscura/reporting/index.py +590 -0
  366. oscura/reporting/multichannel.py +296 -0
  367. oscura/reporting/output.py +379 -0
  368. oscura/reporting/pdf.py +373 -0
  369. oscura/reporting/plots.py +731 -0
  370. oscura/reporting/pptx_export.py +360 -0
  371. oscura/reporting/renderers/__init__.py +11 -0
  372. oscura/reporting/renderers/pdf.py +94 -0
  373. oscura/reporting/sections.py +471 -0
  374. oscura/reporting/standards.py +680 -0
  375. oscura/reporting/summary_generator.py +368 -0
  376. oscura/reporting/tables.py +397 -0
  377. oscura/reporting/template_system.py +724 -0
  378. oscura/reporting/templates/__init__.py +15 -0
  379. oscura/reporting/templates/definition.py +205 -0
  380. oscura/reporting/templates/index.html +649 -0
  381. oscura/reporting/templates/index.md +173 -0
  382. oscura/schemas/__init__.py +158 -0
  383. oscura/schemas/bus_configuration.json +322 -0
  384. oscura/schemas/device_mapping.json +182 -0
  385. oscura/schemas/packet_format.json +418 -0
  386. oscura/schemas/protocol_definition.json +363 -0
  387. oscura/search/__init__.py +16 -0
  388. oscura/search/anomaly.py +292 -0
  389. oscura/search/context.py +149 -0
  390. oscura/search/pattern.py +160 -0
  391. oscura/session/__init__.py +34 -0
  392. oscura/session/annotations.py +289 -0
  393. oscura/session/history.py +313 -0
  394. oscura/session/session.py +445 -0
  395. oscura/streaming/__init__.py +43 -0
  396. oscura/streaming/chunked.py +611 -0
  397. oscura/streaming/progressive.py +393 -0
  398. oscura/streaming/realtime.py +622 -0
  399. oscura/testing/__init__.py +54 -0
  400. oscura/testing/synthetic.py +808 -0
  401. oscura/triggering/__init__.py +68 -0
  402. oscura/triggering/base.py +229 -0
  403. oscura/triggering/edge.py +353 -0
  404. oscura/triggering/pattern.py +344 -0
  405. oscura/triggering/pulse.py +581 -0
  406. oscura/triggering/window.py +453 -0
  407. oscura/ui/__init__.py +48 -0
  408. oscura/ui/formatters.py +526 -0
  409. oscura/ui/progressive_display.py +340 -0
  410. oscura/utils/__init__.py +99 -0
  411. oscura/utils/autodetect.py +338 -0
  412. oscura/utils/buffer.py +389 -0
  413. oscura/utils/lazy.py +407 -0
  414. oscura/utils/lazy_imports.py +147 -0
  415. oscura/utils/memory.py +836 -0
  416. oscura/utils/memory_advanced.py +1326 -0
  417. oscura/utils/memory_extensions.py +465 -0
  418. oscura/utils/progressive.py +352 -0
  419. oscura/utils/windowing.py +362 -0
  420. oscura/visualization/__init__.py +321 -0
  421. oscura/visualization/accessibility.py +526 -0
  422. oscura/visualization/annotations.py +374 -0
  423. oscura/visualization/axis_scaling.py +305 -0
  424. oscura/visualization/colors.py +453 -0
  425. oscura/visualization/digital.py +337 -0
  426. oscura/visualization/eye.py +420 -0
  427. oscura/visualization/histogram.py +281 -0
  428. oscura/visualization/interactive.py +858 -0
  429. oscura/visualization/jitter.py +702 -0
  430. oscura/visualization/keyboard.py +394 -0
  431. oscura/visualization/layout.py +365 -0
  432. oscura/visualization/optimization.py +1028 -0
  433. oscura/visualization/palettes.py +446 -0
  434. oscura/visualization/plot.py +92 -0
  435. oscura/visualization/power.py +290 -0
  436. oscura/visualization/power_extended.py +626 -0
  437. oscura/visualization/presets.py +467 -0
  438. oscura/visualization/protocols.py +932 -0
  439. oscura/visualization/render.py +207 -0
  440. oscura/visualization/rendering.py +444 -0
  441. oscura/visualization/reverse_engineering.py +791 -0
  442. oscura/visualization/signal_integrity.py +808 -0
  443. oscura/visualization/specialized.py +553 -0
  444. oscura/visualization/spectral.py +811 -0
  445. oscura/visualization/styles.py +381 -0
  446. oscura/visualization/thumbnails.py +311 -0
  447. oscura/visualization/time_axis.py +351 -0
  448. oscura/visualization/waveform.py +367 -0
  449. oscura/workflow/__init__.py +13 -0
  450. oscura/workflow/dag.py +377 -0
  451. oscura/workflows/__init__.py +58 -0
  452. oscura/workflows/compliance.py +280 -0
  453. oscura/workflows/digital.py +272 -0
  454. oscura/workflows/multi_trace.py +502 -0
  455. oscura/workflows/power.py +178 -0
  456. oscura/workflows/protocol.py +492 -0
  457. oscura/workflows/reverse_engineering.py +639 -0
  458. oscura/workflows/signal_integrity.py +227 -0
  459. oscura-0.1.1.dist-info/METADATA +300 -0
  460. oscura-0.1.1.dist-info/RECORD +463 -0
  461. oscura-0.1.1.dist-info/entry_points.txt +2 -0
  462. {oscura-0.0.1.dist-info → oscura-0.1.1.dist-info}/licenses/LICENSE +1 -1
  463. oscura-0.0.1.dist-info/METADATA +0 -63
  464. oscura-0.0.1.dist-info/RECORD +0 -5
  465. {oscura-0.0.1.dist-info → oscura-0.1.1.dist-info}/WHEEL +0 -0
@@ -0,0 +1,436 @@
1
+ """NumPy NPZ file loader.
2
+
3
+ This module provides loading of waveform data from NumPy .npz archive files.
4
+
5
+
6
+ Example:
7
+ >>> from oscura.loaders.numpy_loader import load_npz
8
+ >>> trace = load_npz("waveform.npz")
9
+ >>> print(f"Sample rate: {trace.metadata.sample_rate} Hz")
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ from pathlib import Path
15
+ from typing import TYPE_CHECKING, Any
16
+
17
+ import numpy as np
18
+ from numpy.typing import NDArray
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
+
27
+ # Common array names for waveform data
28
+ DATA_ARRAY_NAMES = ["data", "waveform", "signal", "samples", "y", "voltage"]
29
+
30
+ # Common metadata keys
31
+ SAMPLE_RATE_KEYS = ["sample_rate", "samplerate", "fs", "sampling_rate", "rate"]
32
+ VERTICAL_SCALE_KEYS = ["vertical_scale", "v_scale", "scale", "volts_per_div"]
33
+ VERTICAL_OFFSET_KEYS = ["vertical_offset", "v_offset", "offset"]
34
+
35
+
36
+ def load_npz(
37
+ path: str | PathLike[str],
38
+ *,
39
+ channel: str | int | None = None,
40
+ sample_rate: float | None = None,
41
+ mmap: bool = False,
42
+ ) -> WaveformTrace:
43
+ """Load waveform data from a NumPy NPZ archive.
44
+
45
+
46
+ Extracts waveform array and metadata from an NPZ file. The function
47
+ looks for common array names like 'data', 'waveform', 'signal', etc.
48
+
49
+ Args:
50
+ path: Path to the .npz file.
51
+ channel: Specific array name or index to load. If None, auto-detects.
52
+ sample_rate: Override sample rate (if not found in file metadata).
53
+ mmap: If True, use memory mapping to avoid loading entire file into RAM.
54
+ Data stays on disk until accessed. Useful for very large files.
55
+
56
+ Returns:
57
+ WaveformTrace containing the waveform data and metadata.
58
+
59
+ Raises:
60
+ LoaderError: If the file cannot be loaded.
61
+ FormatError: If no valid waveform data is found.
62
+
63
+ Example:
64
+ >>> trace = load_npz("waveform.npz")
65
+ >>> print(f"Sample rate: {trace.metadata.sample_rate} Hz")
66
+
67
+ >>> # Load specific channel from multi-channel file
68
+ >>> trace = load_npz("multi.npz", channel="ch1")
69
+
70
+ >>> # Memory-map large file to avoid loading all into RAM
71
+ >>> trace = load_npz("large.npz", mmap=True)
72
+ >>> # Access only what you need: trace.data[1000:2000]
73
+
74
+ Security Warning:
75
+ NPZ files may contain pickled Python objects. Only load NPZ files from
76
+ trusted sources. Loading malicious NPZ files could execute arbitrary
77
+ code. For untrusted data, prefer formats like plain NumPy arrays (.npy),
78
+ CSV, or HDF5.
79
+ """
80
+ path = Path(path)
81
+
82
+ if not path.exists():
83
+ raise LoaderError(
84
+ "File not found",
85
+ file_path=str(path),
86
+ )
87
+
88
+ try:
89
+ npz = np.load(path, allow_pickle=True, mmap_mode="r" if mmap else None)
90
+ except Exception as e:
91
+ raise LoaderError(
92
+ "Failed to load NPZ file",
93
+ file_path=str(path),
94
+ details=str(e),
95
+ ) from e
96
+
97
+ try:
98
+ # Find waveform data array
99
+ data_array = _find_data_array(npz, channel)
100
+
101
+ if data_array is None:
102
+ available = list(npz.keys())
103
+ raise FormatError(
104
+ "No waveform data found in NPZ file",
105
+ file_path=str(path),
106
+ expected=f"Array named: {', '.join(DATA_ARRAY_NAMES)}",
107
+ got=f"Arrays: {', '.join(available)}",
108
+ )
109
+
110
+ # Convert to float64 (keep mmap if enabled)
111
+ if mmap and isinstance(data_array, np.memmap):
112
+ # Keep as memmap, just ensure float64 dtype
113
+ if data_array.dtype != np.float64:
114
+ # For memmap, we need to copy to convert dtype
115
+ try:
116
+ data = data_array.astype(np.float64)
117
+ except (ValueError, TypeError) as e:
118
+ raise FormatError(
119
+ "Data array is not numeric",
120
+ file_path=str(path),
121
+ expected="Numeric dtype (int, float)",
122
+ got=f"{data_array.dtype}",
123
+ ) from e
124
+ else:
125
+ data = data_array
126
+ else:
127
+ try:
128
+ data = data_array.astype(np.float64)
129
+ except (ValueError, TypeError) as e:
130
+ raise FormatError(
131
+ "Data array is not numeric",
132
+ file_path=str(path),
133
+ expected="Numeric dtype (int, float)",
134
+ got=f"{data_array.dtype}",
135
+ ) from e
136
+
137
+ # Extract metadata
138
+ detected_sample_rate = _find_metadata_value(npz, SAMPLE_RATE_KEYS)
139
+ detected_vertical_scale = _find_metadata_value(npz, VERTICAL_SCALE_KEYS)
140
+ detected_vertical_offset = _find_metadata_value(npz, VERTICAL_OFFSET_KEYS)
141
+
142
+ # Use provided sample_rate if specified
143
+ if sample_rate is not None:
144
+ detected_sample_rate = sample_rate
145
+ elif detected_sample_rate is None:
146
+ detected_sample_rate = 1e6 # Default to 1 MSa/s
147
+
148
+ # Build metadata
149
+ metadata = TraceMetadata(
150
+ sample_rate=float(detected_sample_rate),
151
+ vertical_scale=float(detected_vertical_scale)
152
+ if detected_vertical_scale is not None
153
+ else None,
154
+ vertical_offset=float(detected_vertical_offset)
155
+ if detected_vertical_offset is not None
156
+ else None,
157
+ source_file=str(path),
158
+ channel_name=_get_channel_name(npz, channel),
159
+ )
160
+
161
+ return WaveformTrace(data=data, metadata=metadata)
162
+
163
+ finally:
164
+ npz.close()
165
+
166
+
167
+ def _find_data_array(
168
+ npz: np.lib.npyio.NpzFile,
169
+ channel: str | int | None,
170
+ ) -> NDArray[np.float64] | None:
171
+ """Find the waveform data array in the NPZ file.
172
+
173
+ Args:
174
+ npz: Loaded NPZ file.
175
+ channel: Specific channel name or index.
176
+
177
+ Returns:
178
+ Waveform data array or None if not found.
179
+ """
180
+ keys = list(npz.keys())
181
+
182
+ # If channel specified by name
183
+ if isinstance(channel, str):
184
+ if channel in keys:
185
+ return npz[channel]
186
+ # Try case-insensitive match
187
+ channel_lower = channel.lower()
188
+ for key in keys:
189
+ if key.lower() == channel_lower:
190
+ return npz[key]
191
+ return None
192
+
193
+ # If channel specified by index
194
+ if isinstance(channel, int):
195
+ # Find numeric-suffixed arrays (ch1, ch2, etc.)
196
+ channel_arrays = [k for k in keys if _is_channel_array(k)]
197
+ if channel_arrays and channel < len(channel_arrays):
198
+ return npz[sorted(channel_arrays)[channel]]
199
+ # Or use nth array
200
+ data_arrays = [k for k in keys if _is_data_array(k)]
201
+ if data_arrays and channel < len(data_arrays):
202
+ return npz[data_arrays[channel]]
203
+ return None
204
+
205
+ # Auto-detect: look for common data array names
206
+ for name in DATA_ARRAY_NAMES:
207
+ if name in keys:
208
+ return npz[name]
209
+ # Try case-insensitive match
210
+ name_lower = name.lower()
211
+ for key in keys:
212
+ if key.lower() == name_lower:
213
+ return npz[key]
214
+
215
+ # Fall back to first 1D or 2D array
216
+ for key in keys:
217
+ arr = npz[key]
218
+ if isinstance(arr, np.ndarray) and arr.ndim in (1, 2):
219
+ # Skip metadata scalars
220
+ if arr.size > 10: # Arbitrary threshold
221
+ return arr.ravel() if arr.ndim == 2 else arr
222
+
223
+ return None
224
+
225
+
226
+ def _is_channel_array(name: str) -> bool:
227
+ """Check if array name looks like a channel (ch1, channel1, etc.)."""
228
+ name_lower = name.lower()
229
+ return (
230
+ name_lower.startswith("ch")
231
+ or name_lower.startswith("channel")
232
+ or name_lower.startswith("analog")
233
+ )
234
+
235
+
236
+ def _is_data_array(name: str) -> bool:
237
+ """Check if array name looks like waveform data."""
238
+ name_lower = name.lower()
239
+ return any(data_name in name_lower for data_name in DATA_ARRAY_NAMES)
240
+
241
+
242
+ def _find_metadata_value(
243
+ npz: np.lib.npyio.NpzFile,
244
+ key_names: list[str],
245
+ ) -> float | None:
246
+ """Find a metadata value by trying multiple key names.
247
+
248
+ Args:
249
+ npz: Loaded NPZ file.
250
+ key_names: List of possible key names to try.
251
+
252
+ Returns:
253
+ Metadata value or None if not found.
254
+ """
255
+ keys = list(npz.keys())
256
+
257
+ for name in key_names:
258
+ # Exact match
259
+ if name in keys:
260
+ value = npz[name]
261
+ if np.isscalar(value):
262
+ return float(value) # type: ignore[arg-type]
263
+ elif isinstance(value, np.ndarray) and value.size == 1:
264
+ return float(value.item()) # type: ignore[arg-type]
265
+
266
+ # Case-insensitive match
267
+ name_lower = name.lower()
268
+ for key in keys:
269
+ if key.lower() == name_lower:
270
+ value = npz[key]
271
+ if np.isscalar(value):
272
+ return float(value) # type: ignore[arg-type]
273
+ elif isinstance(value, np.ndarray) and value.size == 1:
274
+ return float(value.item()) # type: ignore[arg-type]
275
+
276
+ # Check for metadata dict
277
+ if "metadata" in keys:
278
+ metadata = npz["metadata"]
279
+ if isinstance(metadata, np.ndarray):
280
+ try:
281
+ meta_dict = metadata.item()
282
+ if isinstance(meta_dict, dict):
283
+ for name in key_names:
284
+ if name in meta_dict:
285
+ return float(meta_dict[name])
286
+ except (ValueError, TypeError):
287
+ pass
288
+
289
+ return None
290
+
291
+
292
+ def _get_channel_name(
293
+ npz: np.lib.npyio.NpzFile,
294
+ channel: str | int | None,
295
+ ) -> str:
296
+ """Get a channel name for the loaded data.
297
+
298
+ Args:
299
+ npz: Loaded NPZ file.
300
+ channel: Channel specification.
301
+
302
+ Returns:
303
+ Channel name string.
304
+ """
305
+ if isinstance(channel, str):
306
+ return channel
307
+ elif isinstance(channel, int):
308
+ return f"CH{channel + 1}"
309
+
310
+ # Try to find channel name in metadata
311
+ keys = list(npz.keys())
312
+ if "channel_name" in keys:
313
+ value = npz["channel_name"]
314
+ # NPZ values are always ndarrays
315
+ return str(value.item())
316
+
317
+ return "CH1"
318
+
319
+
320
+ def list_arrays(path: str | PathLike[str]) -> list[str]:
321
+ """List all arrays in an NPZ file.
322
+
323
+ Args:
324
+ path: Path to the NPZ file.
325
+
326
+ Returns:
327
+ List of array names.
328
+
329
+ Raises:
330
+ LoaderError: If file not found or cannot be read.
331
+
332
+ Example:
333
+ >>> arrays = list_arrays("multi.npz")
334
+ >>> print(arrays)
335
+ ['ch1', 'ch2', 'sample_rate']
336
+ """
337
+ path = Path(path)
338
+ if not path.exists():
339
+ raise LoaderError("File not found", file_path=str(path))
340
+
341
+ try:
342
+ with np.load(path, allow_pickle=True) as npz:
343
+ return list(npz.keys())
344
+ except Exception as e:
345
+ raise LoaderError(
346
+ "Failed to read NPZ file",
347
+ file_path=str(path),
348
+ details=str(e),
349
+ ) from e
350
+
351
+
352
+ def load_raw_binary(
353
+ path: str | PathLike[str],
354
+ *,
355
+ dtype: str = "float32",
356
+ sample_rate: float = 1e6,
357
+ mmap: bool = False,
358
+ offset: int = 0,
359
+ count: int = -1,
360
+ ) -> WaveformTrace:
361
+ """Load waveform data from a raw binary file.
362
+
363
+
364
+ Loads raw binary waveform data with optional memory mapping for
365
+ files larger than available RAM.
366
+
367
+ Args:
368
+ path: Path to the raw binary file.
369
+ dtype: Data type of samples (float32, float64, int16, etc.).
370
+ sample_rate: Sample rate in Hz.
371
+ mmap: If True, use memory mapping to avoid loading entire file.
372
+ offset: Number of elements to skip at start of file.
373
+ count: Number of elements to read (-1 = all).
374
+
375
+ Returns:
376
+ WaveformTrace containing the waveform data and metadata.
377
+
378
+ Raises:
379
+ LoaderError: If the file cannot be loaded.
380
+
381
+ Example:
382
+ >>> # Load entire file into memory
383
+ >>> trace = load_raw_binary("signal.bin", dtype="float32", sample_rate=1e9)
384
+
385
+ >>> # Memory-map large file
386
+ >>> trace = load_raw_binary("large.bin", dtype="float32", sample_rate=1e9, mmap=True)
387
+ >>> # Access subset: trace.data[1000:2000]
388
+
389
+ >>> # Load only portion of file
390
+ >>> trace = load_raw_binary("signal.bin", dtype="int16", offset=1000, count=10000)
391
+ """
392
+ path = Path(path)
393
+
394
+ if not path.exists():
395
+ raise LoaderError("File not found", file_path=str(path))
396
+
397
+ try:
398
+ data: NDArray[np.float64] | np.memmap[Any, np.dtype[Any]]
399
+ if mmap:
400
+ # Memory-mapped array (stays on disk)
401
+ data = np.memmap(
402
+ path,
403
+ dtype=dtype,
404
+ mode="r",
405
+ offset=offset * np.dtype(dtype).itemsize,
406
+ shape=(count,) if count > 0 else None,
407
+ )
408
+ # Convert to float64 if needed (may copy)
409
+ if data.dtype != np.float64:
410
+ # For large files, user should slice before converting
411
+ # data = data.astype(np.float64) # This would load entire file!
412
+ # Instead, keep original dtype and convert in WaveformTrace
413
+ pass
414
+ else:
415
+ # Load into memory
416
+ data_raw = np.fromfile(path, dtype=dtype, count=count, offset=offset)
417
+ # Convert to float64
418
+ data = data_raw.astype(np.float64)
419
+
420
+ metadata = TraceMetadata(
421
+ sample_rate=sample_rate,
422
+ source_file=str(path),
423
+ channel_name="RAW",
424
+ )
425
+
426
+ return WaveformTrace(data=data, metadata=metadata)
427
+
428
+ except Exception as e:
429
+ raise LoaderError(
430
+ "Failed to load raw binary file",
431
+ file_path=str(path),
432
+ details=str(e),
433
+ ) from e
434
+
435
+
436
+ __all__ = ["list_arrays", "load_npz", "load_raw_binary"]