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,571 @@
1
+ """Chunked FFT computation for very long signals.
2
+
3
+ This module implements FFT computation for signals larger than memory
4
+ using overlapping segments with result aggregation.
5
+
6
+
7
+ Example:
8
+ >>> from oscura.analyzers.spectral.chunked_fft import fft_chunked
9
+ >>> freqs, spectrum = fft_chunked('huge_signal.bin', segment_size=1e6, overlap_pct=50)
10
+ >>> print(f"FFT shape: {spectrum.shape}")
11
+
12
+ References:
13
+ scipy.fft for FFT computation
14
+ Welch's method for spectral averaging
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ from pathlib import Path
20
+ from typing import TYPE_CHECKING, Any
21
+
22
+ import numpy as np
23
+ from scipy import fft, signal
24
+
25
+ from oscura.core.memoize import memoize_analysis
26
+
27
+ if TYPE_CHECKING:
28
+ from collections.abc import Callable, Iterator
29
+
30
+ from numpy.typing import NDArray
31
+
32
+
33
+ @memoize_analysis(maxsize=16)
34
+ def fft_chunked(
35
+ file_path: str | Path,
36
+ segment_size: int | float,
37
+ overlap_pct: float = 50.0,
38
+ *,
39
+ window: str | NDArray[np.float64] = "hann",
40
+ nfft: int | None = None,
41
+ detrend: str | bool = False,
42
+ scaling: str = "density",
43
+ average_method: str = "mean",
44
+ sample_rate: float = 1.0,
45
+ dtype: str = "float32",
46
+ preserve_phase: bool = False,
47
+ ) -> tuple[NDArray[np.float64], NDArray[np.float64] | NDArray[np.complex128]]:
48
+ """Compute FFT for very long signals using overlapping segments.
49
+
50
+
51
+ Processes signal in segments with overlap, computes FFT per segment,
52
+ and aggregates using specified method. Handles window edge effects.
53
+
54
+ Args:
55
+ file_path: Path to signal file (binary format).
56
+ segment_size: Segment size in samples.
57
+ overlap_pct: Overlap percentage between segments (0-100).
58
+ window: Window function name or array.
59
+ nfft: FFT length (default: segment_size, zero-padded if larger).
60
+ detrend: Detrend type ('constant', 'linear', False).
61
+ scaling: Scaling mode ('density' or 'spectrum').
62
+ average_method: Aggregation method ('mean', 'median', 'max').
63
+ sample_rate: Sample rate in Hz (for frequency axis).
64
+ dtype: Data type of input file ('float32' or 'float64').
65
+ preserve_phase: If True, preserve phase information (complex output).
66
+
67
+ Returns:
68
+ Tuple of (frequencies, spectrum) where:
69
+ - frequencies: Frequency bins in Hz.
70
+ - spectrum: Averaged FFT magnitude (or complex if preserve_phase=True).
71
+
72
+ Raises:
73
+ ValueError: If overlap_pct not in [0, 100] or file cannot be read.
74
+
75
+ Example:
76
+ >>> # Process 10 GB file with 1M sample segments, 50% overlap
77
+ >>> freqs, spectrum = fft_chunked(
78
+ ... 'huge_signal.bin',
79
+ ... segment_size=1e6,
80
+ ... overlap_pct=50,
81
+ ... window='hann',
82
+ ... sample_rate=1e9,
83
+ ... dtype='float32'
84
+ ... )
85
+ >>> print(f"Spectrum shape: {spectrum.shape}")
86
+
87
+ References:
88
+ MEM-006: Chunked FFT for Very Long Signals
89
+ """
90
+ if not 0 <= overlap_pct < 100:
91
+ raise ValueError(
92
+ f"overlap_pct must be in [0, 100), got {overlap_pct}. Note: 100% overlap would create an infinite loop."
93
+ )
94
+
95
+ segment_size = int(segment_size)
96
+ if nfft is None:
97
+ nfft = segment_size
98
+
99
+ # Calculate overlap in samples
100
+ noverlap = int(segment_size * overlap_pct / 100)
101
+
102
+ # Determine dtype
103
+ np_dtype = np.float32 if dtype == "float32" else np.float64
104
+ bytes_per_sample = 4 if dtype == "float32" else 8
105
+
106
+ # Open file and get total size
107
+ file_path = Path(file_path)
108
+ file_size_bytes = file_path.stat().st_size
109
+ total_samples = file_size_bytes // bytes_per_sample
110
+
111
+ # Generate window
112
+ if isinstance(window, str):
113
+ window_arr = signal.get_window(window, segment_size)
114
+ else:
115
+ window_arr = np.asarray(window)
116
+
117
+ # Initialize accumulators
118
+ fft_accum: list[NDArray[np.float64] | NDArray[np.complex128]] = []
119
+
120
+ # Process segments
121
+ for segment in _generate_segments(file_path, total_samples, segment_size, noverlap, np_dtype):
122
+ # Apply detrending
123
+ if detrend:
124
+ segment = signal.detrend(segment, type=detrend)
125
+
126
+ # Apply window
127
+ windowed = segment * window_arr[: len(segment)]
128
+
129
+ # Zero-pad if needed
130
+ if len(windowed) < nfft:
131
+ windowed = np.pad(windowed, (0, nfft - len(windowed)), mode="constant")
132
+
133
+ # Compute FFT
134
+ fft_result = fft.rfft(windowed, n=nfft)
135
+
136
+ # Store result (magnitude or complex)
137
+ if preserve_phase:
138
+ fft_accum.append(fft_result)
139
+ else:
140
+ fft_accum.append(np.abs(fft_result))
141
+
142
+ # Aggregate results
143
+ if len(fft_accum) == 0:
144
+ raise ValueError(f"No segments processed from {file_path}")
145
+
146
+ if average_method == "mean":
147
+ spectrum = np.mean(fft_accum, axis=0)
148
+ elif average_method == "median":
149
+ spectrum = np.median(fft_accum, axis=0)
150
+ elif average_method == "max":
151
+ spectrum = np.max(fft_accum, axis=0)
152
+ else:
153
+ raise ValueError(
154
+ f"Unknown average_method: {average_method}. Use 'mean', 'median', or 'max'."
155
+ )
156
+
157
+ # Apply scaling
158
+ if scaling == "density" and not preserve_phase:
159
+ # Convert to PSD-like scaling
160
+ spectrum = spectrum**2 / (sample_rate * np.sum(window_arr**2))
161
+ elif scaling == "spectrum" and not preserve_phase:
162
+ # RMS scaling
163
+ spectrum = spectrum / len(window_arr)
164
+
165
+ # Frequency axis
166
+ frequencies = fft.rfftfreq(nfft, d=1 / sample_rate)
167
+
168
+ return frequencies, spectrum
169
+
170
+
171
+ def _generate_segments(
172
+ file_path: Path,
173
+ total_samples: int,
174
+ segment_size: int,
175
+ noverlap: int,
176
+ dtype: type,
177
+ ) -> Iterator[NDArray[np.float64]]:
178
+ """Generate overlapping segments from file.
179
+
180
+ Args:
181
+ file_path: Path to binary file.
182
+ total_samples: Total number of samples in file.
183
+ segment_size: Samples per segment.
184
+ noverlap: Overlap samples between segments.
185
+ dtype: NumPy dtype for data.
186
+
187
+ Yields:
188
+ Segment arrays.
189
+ """
190
+ hop = segment_size - noverlap
191
+ offset = 0
192
+
193
+ with open(file_path, "rb") as f:
194
+ while offset < total_samples:
195
+ # Read segment
196
+ f.seek(offset * dtype().itemsize)
197
+ segment_data: NDArray[np.float64] = np.fromfile(f, dtype=dtype, count=segment_size)
198
+
199
+ if len(segment_data) == 0:
200
+ break
201
+
202
+ yield segment_data
203
+
204
+ offset += hop
205
+
206
+
207
+ def welch_psd_chunked(
208
+ file_path: str | Path,
209
+ segment_size: int | float = 256,
210
+ overlap_pct: float = 50.0,
211
+ *,
212
+ window: str | NDArray[np.float64] = "hann",
213
+ nfft: int | None = None,
214
+ detrend: str | bool = "constant",
215
+ scaling: str = "density",
216
+ sample_rate: float = 1.0,
217
+ dtype: str = "float32",
218
+ ) -> tuple[NDArray[np.float64], NDArray[np.float64]]:
219
+ """Compute Welch PSD estimate for very long signals.
220
+
221
+ Similar to fft_chunked but specifically implements Welch's method
222
+ for power spectral density estimation.
223
+
224
+
225
+ Args:
226
+ file_path: Path to signal file.
227
+ segment_size: Segment size for Welch's method.
228
+ overlap_pct: Overlap percentage (typically 50%).
229
+ window: Window function.
230
+ nfft: FFT length.
231
+ detrend: Detrend type.
232
+ scaling: Scaling mode ('density' or 'spectrum').
233
+ sample_rate: Sample rate in Hz.
234
+ dtype: Data type of input file.
235
+
236
+ Returns:
237
+ Tuple of (frequencies, psd).
238
+
239
+ Example:
240
+ >>> freqs, psd = welch_psd_chunked('signal.bin', segment_size=1024, sample_rate=1e6)
241
+ >>> print(f"PSD shape: {psd.shape}")
242
+
243
+ References:
244
+ MEM-005: Chunked Welch PSD
245
+ Welch, P.D. (1967). "The use of fast Fourier transform for the
246
+ estimation of power spectra"
247
+ """
248
+ freqs, spectrum = fft_chunked(
249
+ file_path,
250
+ segment_size=segment_size,
251
+ overlap_pct=overlap_pct,
252
+ window=window,
253
+ nfft=nfft,
254
+ detrend=detrend,
255
+ scaling=scaling,
256
+ average_method="mean",
257
+ sample_rate=sample_rate,
258
+ dtype=dtype,
259
+ preserve_phase=False,
260
+ )
261
+ # preserve_phase=False guarantees float64 output, not complex128
262
+ return freqs, spectrum # type: ignore[return-value]
263
+
264
+
265
+ def fft_chunked_parallel(
266
+ file_path: str | Path,
267
+ segment_size: int | float,
268
+ overlap_pct: float = 50.0,
269
+ *,
270
+ n_workers: int = 4,
271
+ **kwargs: Any,
272
+ ) -> tuple[NDArray[np.float64], NDArray[np.float64]]:
273
+ """Compute chunked FFT with parallel processing.
274
+
275
+ Similar to fft_chunked but uses multiple workers for parallel
276
+ segment processing. Useful for very large files on multi-core systems.
277
+
278
+ Args:
279
+ file_path: Path to signal file.
280
+ segment_size: Segment size in samples.
281
+ overlap_pct: Overlap percentage.
282
+ n_workers: Number of parallel workers.
283
+ **kwargs: Additional arguments passed to fft_chunked.
284
+
285
+ Returns:
286
+ Tuple of (frequencies, spectrum).
287
+
288
+ Note:
289
+ FUTURE ENHANCEMENT: Parallel processing with multiprocessing/joblib.
290
+ Currently uses serial processing (n_workers parameter is reserved
291
+ for future implementation). The serial fallback provides correct
292
+ results; parallelization is an optimization opportunity.
293
+
294
+ Example:
295
+ >>> freqs, spectrum = fft_chunked_parallel(
296
+ ... 'signal.bin',
297
+ ... segment_size=1e6,
298
+ ... overlap_pct=50,
299
+ ... n_workers=8
300
+ ... )
301
+ """
302
+ # Future: Implement parallel processing with multiprocessing or joblib
303
+ # For now, fall back to serial processing
304
+ freqs, spectrum = fft_chunked(file_path, segment_size, overlap_pct, **kwargs)
305
+ # kwargs may contain preserve_phase, handle both float64 and complex128
306
+ return freqs, spectrum # type: ignore[return-value]
307
+
308
+
309
+ def streaming_fft(
310
+ file_path: str | Path,
311
+ segment_size: int | float,
312
+ overlap_pct: float = 50.0,
313
+ *,
314
+ window: str | NDArray[np.float64] = "hann",
315
+ nfft: int | None = None,
316
+ detrend: str | bool = False,
317
+ sample_rate: float = 1.0,
318
+ dtype: str = "float32",
319
+ progress_callback: Callable[[int, int], None] | None = None,
320
+ ) -> Iterator[tuple[NDArray[np.float64], NDArray[np.float64]]]:
321
+ """Stream FFT computation yielding frequency bins as computed.
322
+
323
+ Implements streaming/generator API for memory-efficient FFT computation
324
+ on very large files. Yields frequency bins as they are computed, allowing
325
+ downstream processing before all segments are complete.
326
+
327
+
328
+ Args:
329
+ file_path: Path to signal file (binary format).
330
+ segment_size: Segment size in samples.
331
+ overlap_pct: Overlap percentage between segments (0-100).
332
+ window: Window function name or array.
333
+ nfft: FFT length (default: segment_size).
334
+ detrend: Detrend type ('constant', 'linear', False).
335
+ sample_rate: Sample rate in Hz (for frequency axis).
336
+ dtype: Data type of input file ('float32' or 'float64').
337
+ progress_callback: Optional callback(current, total) to report progress.
338
+
339
+ Yields:
340
+ Tuple of (frequencies, fft_magnitude) for each segment.
341
+
342
+ Raises:
343
+ ValueError: If overlap_pct not in valid range.
344
+
345
+ Example:
346
+ >>> # Stream FFT results as computed
347
+ >>> def on_progress(current, total):
348
+ ... print(f"Progress: {current}/{total} ({current/total*100:.1f}%)")
349
+ >>>
350
+ >>> for frequencies, magnitude in streaming_fft(
351
+ ... 'huge_signal.bin',
352
+ ... segment_size=1e6,
353
+ ... overlap_pct=50,
354
+ ... progress_callback=on_progress
355
+ ... ):
356
+ ... # Process each segment immediately
357
+ ... peak_freq = frequencies[magnitude.argmax()]
358
+ ... print(f"Peak frequency: {peak_freq:.2e} Hz")
359
+
360
+ References:
361
+ API-003: Streaming/Generator API for Large Files
362
+ """
363
+ if not 0 <= overlap_pct < 100:
364
+ raise ValueError(
365
+ f"overlap_pct must be in [0, 100), got {overlap_pct}. Note: 100% overlap would create an infinite loop."
366
+ )
367
+
368
+ segment_size = int(segment_size)
369
+ if nfft is None:
370
+ nfft = segment_size
371
+
372
+ # Calculate overlap in samples
373
+ noverlap = int(segment_size * overlap_pct / 100)
374
+
375
+ # Determine dtype
376
+ np_dtype = np.float32 if dtype == "float32" else np.float64
377
+ bytes_per_sample = 4 if dtype == "float32" else 8
378
+
379
+ # Open file and get total size
380
+ file_path = Path(file_path)
381
+ file_size_bytes = file_path.stat().st_size
382
+ total_samples = file_size_bytes // bytes_per_sample
383
+
384
+ # Calculate total segments for progress reporting
385
+ hop = segment_size - noverlap
386
+ total_segments = max(1, (total_samples - segment_size) // hop + 1)
387
+
388
+ # Generate window
389
+ if isinstance(window, str):
390
+ window_arr = signal.get_window(window, segment_size)
391
+ else:
392
+ window_arr = np.asarray(window)
393
+
394
+ # Frequency axis (computed once)
395
+ frequencies = fft.rfftfreq(nfft, d=1 / sample_rate)
396
+
397
+ # Process and yield segments
398
+ segment_count = 0
399
+ for segment in _generate_segments(file_path, total_samples, segment_size, noverlap, np_dtype):
400
+ # Apply detrending
401
+ if detrend:
402
+ segment = signal.detrend(segment, type=detrend)
403
+
404
+ # Apply window
405
+ windowed = segment * window_arr[: len(segment)]
406
+
407
+ # Zero-pad if needed
408
+ if len(windowed) < nfft:
409
+ windowed = np.pad(windowed, (0, nfft - len(windowed)), mode="constant")
410
+
411
+ # Compute FFT
412
+ fft_result = fft.rfft(windowed, n=nfft)
413
+ magnitude = np.abs(fft_result)
414
+
415
+ # Yield result immediately
416
+ yield frequencies, magnitude
417
+
418
+ # Update progress
419
+ segment_count += 1 # noqa: SIM113
420
+ if progress_callback is not None:
421
+ progress_callback(segment_count, total_segments)
422
+
423
+
424
+ class StreamingAnalyzer:
425
+ """Accumulator for streaming analysis across chunks.
426
+
427
+ Enables processing of huge files chunk-by-chunk with accumulation
428
+ of statistics, PSD estimates, and other aggregated measurements.
429
+
430
+
431
+ Attributes:
432
+ chunk_count: Number of chunks processed.
433
+ accumulated_psd: Accumulated PSD estimate (if accumulate_psd called).
434
+ accumulated_stats: Dictionary of accumulated statistics.
435
+
436
+ Example:
437
+ >>> analyzer = StreamingAnalyzer()
438
+ >>> for chunk in load_trace_chunks('large.bin', chunk_size=50e6):
439
+ ... analyzer.accumulate_psd(chunk, nperseg=4096)
440
+ ... analyzer.accumulate_stats(chunk)
441
+ >>> psd = analyzer.get_psd()
442
+ >>> stats = analyzer.get_stats()
443
+
444
+ References:
445
+ API-003: Streaming/Generator API for Large Files
446
+ """
447
+
448
+ def __init__(self) -> None:
449
+ """Initialize streaming analyzer."""
450
+ self.chunk_count: int = 0
451
+ self._psd_accumulator: list[NDArray[Any]] = []
452
+ self._psd_frequencies: NDArray[Any] | None = None
453
+ self._psd_config: dict[str, Any] = {}
454
+ self._stats_accumulator: dict[str, list[float]] = {
455
+ "mean": [],
456
+ "std": [],
457
+ "min": [],
458
+ "max": [],
459
+ }
460
+
461
+ def accumulate_psd(
462
+ self,
463
+ chunk: NDArray[Any],
464
+ nperseg: int = 256,
465
+ window: str = "hann",
466
+ sample_rate: float = 1.0,
467
+ ) -> None:
468
+ """Accumulate PSD estimate from chunk using Welch's method.
469
+
470
+ Args:
471
+ chunk: Data chunk to process.
472
+ nperseg: Length of each segment for Welch's method.
473
+ window: Window function name.
474
+ sample_rate: Sample rate in Hz.
475
+
476
+ Example:
477
+ >>> analyzer.accumulate_psd(chunk, nperseg=4096, window='hann')
478
+ """
479
+ # Compute Welch PSD for this chunk
480
+ freqs, psd = signal.welch(chunk, fs=sample_rate, nperseg=nperseg, window=window)
481
+
482
+ # Store frequencies on first call
483
+ if self._psd_frequencies is None:
484
+ self._psd_frequencies = freqs
485
+ self._psd_config = {
486
+ "nperseg": nperseg,
487
+ "window": window,
488
+ "sample_rate": sample_rate,
489
+ }
490
+
491
+ # Accumulate PSD
492
+ self._psd_accumulator.append(psd)
493
+ self.chunk_count += 1
494
+
495
+ def accumulate_stats(self, chunk: NDArray[np.float64]) -> None:
496
+ """Accumulate basic statistics from chunk.
497
+
498
+ Args:
499
+ chunk: Data chunk to process.
500
+
501
+ Example:
502
+ >>> analyzer.accumulate_stats(chunk)
503
+ """
504
+ self._stats_accumulator["mean"].append(float(np.mean(chunk)))
505
+ self._stats_accumulator["std"].append(float(np.std(chunk)))
506
+ self._stats_accumulator["min"].append(float(np.min(chunk)))
507
+ self._stats_accumulator["max"].append(float(np.max(chunk)))
508
+
509
+ def get_psd(self) -> tuple[NDArray[np.float64], NDArray[np.float64]]:
510
+ """Get aggregated PSD estimate.
511
+
512
+ Returns:
513
+ Tuple of (frequencies, psd) with averaged PSD across chunks.
514
+
515
+ Raises:
516
+ ValueError: If no PSD data accumulated.
517
+
518
+ Example:
519
+ >>> freqs, psd = analyzer.get_psd()
520
+ """
521
+ if not self._psd_accumulator:
522
+ raise ValueError("No PSD data accumulated. Call accumulate_psd() first.")
523
+
524
+ if self._psd_frequencies is None:
525
+ raise ValueError("PSD frequencies not initialized. Call accumulate_psd() first.")
526
+
527
+ # Average PSDs across all chunks
528
+ avg_psd = np.mean(self._psd_accumulator, axis=0)
529
+ return self._psd_frequencies, avg_psd
530
+
531
+ def get_stats(self) -> dict[str, float]:
532
+ """Get aggregated statistics.
533
+
534
+ Returns:
535
+ Dictionary with overall mean, std, min, max.
536
+
537
+ Example:
538
+ >>> stats = analyzer.get_stats()
539
+ >>> print(f"Overall mean: {stats['mean']:.3f}")
540
+ """
541
+ if not self._stats_accumulator["mean"]:
542
+ return {"mean": 0.0, "std": 0.0, "min": 0.0, "max": 0.0}
543
+
544
+ return {
545
+ "mean": float(np.mean(self._stats_accumulator["mean"])),
546
+ "std": float(np.mean(self._stats_accumulator["std"])),
547
+ "min": float(np.min(self._stats_accumulator["min"])),
548
+ "max": float(np.max(self._stats_accumulator["max"])),
549
+ }
550
+
551
+ def reset(self) -> None:
552
+ """Reset all accumulated data.
553
+
554
+ Example:
555
+ >>> analyzer.reset()
556
+ """
557
+ self.chunk_count = 0
558
+ self._psd_accumulator.clear()
559
+ self._psd_frequencies = None
560
+ self._psd_config.clear()
561
+ for key in self._stats_accumulator:
562
+ self._stats_accumulator[key].clear()
563
+
564
+
565
+ __all__ = [
566
+ "StreamingAnalyzer",
567
+ "fft_chunked",
568
+ "fft_chunked_parallel",
569
+ "streaming_fft",
570
+ "welch_psd_chunked",
571
+ ]