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,611 @@
1
+ """Streaming APIs for chunk-by-chunk processing of large files.
2
+
3
+ This module implements memory-efficient streaming analysis for huge waveform
4
+ files that don't fit in memory. Uses generator-based chunk loading and
5
+ accumulator pattern for rolling statistics.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from pathlib import Path
11
+ from typing import TYPE_CHECKING, Any, cast
12
+
13
+ import numpy as np
14
+ from scipy import signal
15
+
16
+ from ..core.types import WaveformTrace
17
+
18
+ if TYPE_CHECKING:
19
+ from collections.abc import Callable, Generator
20
+
21
+ from numpy.typing import NDArray
22
+
23
+
24
+ def load_trace_chunks(
25
+ file_path: str | Path,
26
+ chunk_size: int | float = 100e6,
27
+ overlap: int = 0,
28
+ loader: Callable[[str | Path], WaveformTrace] | None = None,
29
+ progress_callback: Callable[[int, int], None] | None = None,
30
+ ) -> Generator[WaveformTrace, None, None]:
31
+ """Load large trace files chunk-by-chunk without loading into memory.
32
+
33
+ Yields chunks of the trace for memory-efficient processing. Supports
34
+ overlap between chunks for windowed operations that need continuity.
35
+
36
+ Args:
37
+ file_path: Path to trace file.
38
+ chunk_size: Size of each chunk in samples (if int) or bytes (if float).
39
+ Default 100e6 (100 MB).
40
+ overlap: Number of samples to overlap between chunks. Useful for
41
+ windowed operations like FFT. Default 0.
42
+ loader: Optional custom loader function. If None, uses default loader.
43
+ progress_callback: Optional callback(chunk_num, total_chunks) for
44
+ progress reporting.
45
+
46
+ Yields:
47
+ WaveformTrace chunks.
48
+
49
+ Raises:
50
+ ValueError: If failed to load trace metadata.
51
+
52
+ Example:
53
+ >>> # Stream 10 GB file in 100 MB chunks
54
+ >>> for chunk in tk.load_trace_chunks('huge_trace.bin', chunk_size=100e6):
55
+ ... mean = chunk.data.mean()
56
+ ... std = chunk.data.std()
57
+ ... print(f"Chunk stats: mean={mean:.3f}, std={std:.3f}")
58
+
59
+ Advanced Example:
60
+ >>> # Process with overlap for FFT continuity
61
+ >>> for chunk in tk.load_trace_chunks(
62
+ ... 'large_trace.bin',
63
+ ... chunk_size=50e6,
64
+ ... overlap=8192 # Overlap for continuity
65
+ ... ):
66
+ ... fft_result = tk.fft(chunk, nfft=8192)
67
+ ... # Process FFT result...
68
+
69
+ References:
70
+ API-003: Streaming/Generator API for Large Files
71
+ """
72
+ file_path = Path(file_path)
73
+
74
+ # Import loader here to avoid circular dependency
75
+ from ..loaders import load
76
+
77
+ # Use provided loader or default
78
+ load_func = loader if loader is not None else load
79
+
80
+ # Load full trace metadata to get total size
81
+ # For memory-mapped files, this doesn't load data
82
+ try:
83
+ full_trace = load_func(file_path)
84
+ except Exception as e:
85
+ raise ValueError(f"Failed to load trace metadata: {e}") from e
86
+
87
+ total_samples = len(full_trace.data) # type: ignore[union-attr]
88
+ chunk_samples = int(chunk_size) if chunk_size < 1e6 else int(chunk_size / 8)
89
+
90
+ # Calculate number of chunks
91
+ num_chunks = (total_samples - overlap) // (chunk_samples - overlap)
92
+ if (total_samples - overlap) % (chunk_samples - overlap) != 0:
93
+ num_chunks += 1
94
+
95
+ # Yield chunks
96
+ chunk_num = 0
97
+ start_idx = 0
98
+
99
+ while start_idx < total_samples:
100
+ end_idx = min(start_idx + chunk_samples, total_samples)
101
+
102
+ # Extract chunk
103
+ chunk_data = full_trace.data[start_idx:end_idx] # type: ignore[union-attr]
104
+
105
+ # Create chunk trace with same metadata
106
+ # Cast needed for mypy: slicing a floating array returns a floating array
107
+ chunk_trace = WaveformTrace(
108
+ data=cast("NDArray[np.floating[Any]]", chunk_data),
109
+ metadata=full_trace.metadata,
110
+ )
111
+
112
+ # Call progress callback if provided
113
+ if progress_callback is not None:
114
+ progress_callback(chunk_num, num_chunks)
115
+
116
+ yield chunk_trace
117
+
118
+ # Move to next chunk, accounting for overlap
119
+ start_idx = end_idx - overlap
120
+ chunk_num += 1
121
+
122
+ # Break if we've reached the end
123
+ if end_idx >= total_samples:
124
+ break
125
+
126
+
127
+ class StreamingAnalyzer:
128
+ """Accumulator for streaming analysis of large files.
129
+
130
+ Processes traces chunk-by-chunk, accumulating statistics and measurements
131
+ without loading entire file into memory. Supports streaming PSD estimation
132
+ using Welch's method and other rolling statistics.
133
+
134
+ Example:
135
+ >>> # Create streaming analyzer
136
+ >>> analyzer = tk.StreamingAnalyzer()
137
+ >>> # Process file in chunks
138
+ >>> for chunk in tk.load_trace_chunks('large_trace.bin', chunk_size=50e6):
139
+ ... analyzer.accumulate_psd(chunk, nperseg=4096, window='hann')
140
+ >>> # Get aggregated result
141
+ >>> psd_result = analyzer.get_psd()
142
+
143
+ Advanced Example:
144
+ >>> # Compute multiple statistics in streaming fashion
145
+ >>> analyzer = tk.StreamingAnalyzer()
146
+ >>> for chunk in tk.load_trace_chunks('huge_file.bin'):
147
+ ... analyzer.accumulate_statistics(chunk)
148
+ ... analyzer.accumulate_psd(chunk, nperseg=8192)
149
+ >>> stats = analyzer.get_statistics()
150
+ >>> psd = analyzer.get_psd()
151
+ >>> print(f"Mean: {stats['mean']:.3f}, PSD shape: {psd.shape}")
152
+
153
+ References:
154
+ API-003: Streaming/Generator API for Large Files
155
+ scipy.signal.welch for streaming PSD
156
+ """
157
+
158
+ def __init__(self) -> None:
159
+ """Initialize streaming analyzer."""
160
+ # Statistics accumulators
161
+ self._n_samples = 0
162
+ self._sum = 0.0
163
+ self._sum_sq = 0.0
164
+ self._min = float("inf")
165
+ self._max = float("-inf")
166
+
167
+ # PSD accumulators
168
+ self._psd_sum: NDArray[np.float64] | None = None
169
+ self._psd_freqs: NDArray[np.float64] | None = None
170
+ self._psd_count = 0
171
+ self._sample_rate: float | None = None
172
+
173
+ # Histogram accumulators
174
+ self._hist_counts: NDArray[np.int64] | None = None
175
+ self._hist_edges: NDArray[np.float64] | None = None
176
+
177
+ def accumulate_statistics(self, chunk: WaveformTrace) -> None:
178
+ """Accumulate basic statistics from chunk.
179
+
180
+ Updates running mean, std, min, max using Welford's online algorithm.
181
+
182
+ Args:
183
+ chunk: WaveformTrace chunk to process.
184
+
185
+ Example:
186
+ >>> analyzer.accumulate_statistics(chunk)
187
+ """
188
+ chunk_data = chunk.data
189
+ self._n_samples += len(chunk_data)
190
+ self._sum += float(chunk_data.sum())
191
+ self._sum_sq += float((chunk_data**2).sum())
192
+ self._min = min(self._min, float(chunk_data.min()))
193
+ self._max = max(self._max, float(chunk_data.max()))
194
+
195
+ def accumulate_psd(
196
+ self,
197
+ chunk: WaveformTrace,
198
+ nperseg: int = 4096,
199
+ window: str = "hann",
200
+ **welch_kwargs: Any,
201
+ ) -> None:
202
+ """Accumulate PSD estimate from chunk using Welch's method.
203
+
204
+ Computes PSD for chunk and accumulates with running average.
205
+
206
+ Args:
207
+ chunk: WaveformTrace chunk to process.
208
+ nperseg: Length of each segment for Welch's method.
209
+ window: Window function name (default 'hann').
210
+ **welch_kwargs: Additional arguments for scipy.signal.welch.
211
+
212
+ Example:
213
+ >>> analyzer.accumulate_psd(chunk, nperseg=4096, window='hann')
214
+
215
+ References:
216
+ scipy.signal.welch
217
+ https://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.welch.html
218
+ """
219
+ # Store sample rate from first chunk
220
+ if self._sample_rate is None:
221
+ self._sample_rate = chunk.metadata.sample_rate
222
+
223
+ # Compute PSD for this chunk using Welch's method
224
+ freqs, psd = signal.welch(
225
+ chunk.data,
226
+ fs=chunk.metadata.sample_rate,
227
+ nperseg=nperseg,
228
+ window=window,
229
+ **welch_kwargs,
230
+ )
231
+
232
+ # Initialize or accumulate
233
+ if self._psd_sum is None:
234
+ self._psd_sum = psd
235
+ self._psd_freqs = freqs
236
+ else:
237
+ # Accumulate PSD estimates
238
+ self._psd_sum += psd
239
+
240
+ self._psd_count += 1
241
+
242
+ def accumulate_histogram(
243
+ self,
244
+ chunk: WaveformTrace,
245
+ bins: int | NDArray[np.float64] = 100,
246
+ range: tuple[float, float] | None = None,
247
+ ) -> None:
248
+ """Accumulate histogram from chunk.
249
+
250
+ Args:
251
+ chunk: WaveformTrace chunk to process.
252
+ bins: Number of bins or bin edges.
253
+ range: Range of histogram (min, max).
254
+
255
+ Example:
256
+ >>> analyzer.accumulate_histogram(chunk, bins=100)
257
+ """
258
+ counts, edges = np.histogram(chunk.data, bins=bins, range=range)
259
+
260
+ if self._hist_counts is None:
261
+ self._hist_counts = counts.astype(np.int64)
262
+ self._hist_edges = edges
263
+ else:
264
+ self._hist_counts += counts.astype(np.int64)
265
+
266
+ def get_statistics(self) -> dict[str, float]:
267
+ """Get accumulated statistics.
268
+
269
+ Returns:
270
+ Dictionary with mean, std, min, max, and sample count.
271
+
272
+ Raises:
273
+ ValueError: If no data accumulated yet.
274
+
275
+ Example:
276
+ >>> stats = analyzer.get_statistics()
277
+ >>> print(f"Mean: {stats['mean']:.3f}, Std: {stats['std']:.3f}")
278
+ """
279
+ if self._n_samples == 0:
280
+ raise ValueError("No data accumulated yet")
281
+
282
+ mean = self._sum / self._n_samples
283
+ variance = (self._sum_sq / self._n_samples) - (mean**2)
284
+ std = np.sqrt(max(0, variance)) # Avoid negative due to numerical errors
285
+
286
+ return {
287
+ "mean": mean,
288
+ "std": std,
289
+ "min": self._min,
290
+ "max": self._max,
291
+ "n_samples": self._n_samples,
292
+ }
293
+
294
+ def get_psd(self) -> tuple[NDArray[np.float64], NDArray[np.float64]]:
295
+ """Get accumulated PSD estimate.
296
+
297
+ Returns:
298
+ Tuple of (frequencies, psd) where psd is averaged over all chunks.
299
+
300
+ Raises:
301
+ ValueError: If no PSD data accumulated.
302
+
303
+ Example:
304
+ >>> freqs, psd = analyzer.get_psd()
305
+ >>> print(f"PSD shape: {psd.shape}")
306
+ """
307
+ if self._psd_sum is None or self._psd_freqs is None:
308
+ raise ValueError("No PSD data accumulated yet")
309
+
310
+ # Return averaged PSD
311
+ psd_avg = self._psd_sum / self._psd_count
312
+ return self._psd_freqs, psd_avg
313
+
314
+ def get_histogram(self) -> tuple[NDArray[np.int64], NDArray[np.float64]]:
315
+ """Get accumulated histogram.
316
+
317
+ Returns:
318
+ Tuple of (counts, edges).
319
+
320
+ Raises:
321
+ ValueError: If no histogram data accumulated.
322
+
323
+ Example:
324
+ >>> counts, edges = analyzer.get_histogram()
325
+ """
326
+ if self._hist_counts is None or self._hist_edges is None:
327
+ raise ValueError("No histogram data accumulated yet")
328
+
329
+ return self._hist_counts, self._hist_edges
330
+
331
+ def reset(self) -> None:
332
+ """Reset all accumulators.
333
+
334
+ Example:
335
+ >>> analyzer.reset()
336
+ """
337
+ self._n_samples = 0
338
+ self._sum = 0.0
339
+ self._sum_sq = 0.0
340
+ self._min = float("inf")
341
+ self._max = float("-inf")
342
+ self._psd_sum = None
343
+ self._psd_freqs = None
344
+ self._psd_count = 0
345
+ self._sample_rate = None
346
+ self._hist_counts = None
347
+ self._hist_edges = None
348
+
349
+
350
+ def chunked_spectrogram(
351
+ data: NDArray[np.float64],
352
+ sample_rate: float,
353
+ *,
354
+ chunk_size: int = 10_000_000,
355
+ overlap: int = 0,
356
+ nperseg: int = 256,
357
+ noverlap: int | None = None,
358
+ window: str = "hann",
359
+ ) -> tuple[NDArray[np.float64], NDArray[np.float64], NDArray[np.float64]]:
360
+ """Compute spectrogram for large signals using chunked processing.
361
+
362
+
363
+ Processes large signals in overlapping chunks to compute spectrograms
364
+ without loading entire signal into memory. Stitches STFT results from
365
+ chunks with proper boundary handling.
366
+
367
+ Args:
368
+ data: Input signal array (can be memory-mapped).
369
+ sample_rate: Sample rate in Hz.
370
+ chunk_size: Maximum samples per chunk (default 10M).
371
+ overlap: Overlap samples between chunks for continuity (default 0).
372
+ Should be at least 2*nperseg for proper STFT boundary handling.
373
+ nperseg: Segment length for STFT (default 256).
374
+ noverlap: Overlap between STFT segments within chunk (default nperseg//2).
375
+ window: Window function name (default "hann").
376
+
377
+ Returns:
378
+ (times, frequencies, Sxx_db) - Time axis, frequency axis, and
379
+ spectrogram magnitude in dB as 2D array (frequencies x time).
380
+
381
+ Raises:
382
+ ValueError: If no valid chunks produced.
383
+
384
+ Example:
385
+ >>> # Memory-efficient spectrogram on 1 GB signal
386
+ >>> import numpy as np
387
+ >>> data = np.memmap('huge_trace.dat', dtype='float64', mode='r')
388
+ >>> t, f, Sxx = chunked_spectrogram(data, sample_rate=1e9, chunk_size=10_000_000)
389
+ >>> print(f"Spectrogram shape: {Sxx.shape}")
390
+
391
+ References:
392
+ MEM-004: Chunked Spectrogram requirement
393
+ scipy.signal.spectrogram
394
+ """
395
+ n = len(data)
396
+
397
+ # Handle empty input
398
+ if n == 0:
399
+ return np.array([]), np.array([]), np.array([]).reshape(0, 0)
400
+
401
+ if noverlap is None:
402
+ noverlap = nperseg // 2
403
+
404
+ # Auto-adjust overlap if not specified to ensure continuity
405
+ if overlap == 0:
406
+ overlap = 2 * nperseg
407
+
408
+ # If data fits in one chunk, use scipy directly
409
+ if n <= chunk_size:
410
+ freq, times, Sxx = signal.spectrogram(
411
+ data,
412
+ fs=sample_rate,
413
+ window=window,
414
+ nperseg=nperseg,
415
+ noverlap=noverlap,
416
+ scaling="spectrum",
417
+ )
418
+ # Convert to dB
419
+ Sxx = np.maximum(Sxx, 1e-20)
420
+ Sxx_db = 10 * np.log10(Sxx)
421
+ return times, freq, Sxx_db
422
+
423
+ # Process chunks
424
+ chunks_stft = []
425
+ chunks_times = []
426
+ chunk_start = 0
427
+
428
+ while chunk_start < n:
429
+ # Determine chunk boundaries with overlap
430
+ chunk_end = min(chunk_start + chunk_size, n)
431
+
432
+ # Extract chunk with overlap extension on both sides
433
+ extended_start = max(0, chunk_start - overlap)
434
+ extended_end = min(n, chunk_end + overlap)
435
+
436
+ chunk_data = data[extended_start:extended_end]
437
+
438
+ # Compute spectrogram for chunk
439
+ freq, times_chunk, Sxx_chunk = signal.spectrogram(
440
+ chunk_data,
441
+ fs=sample_rate,
442
+ window=window,
443
+ nperseg=nperseg,
444
+ noverlap=noverlap,
445
+ scaling="spectrum",
446
+ )
447
+
448
+ # Adjust time axis for chunk position
449
+ time_offset = extended_start / sample_rate
450
+ times_chunk_adjusted = times_chunk + time_offset
451
+
452
+ # Trim overlap regions to avoid duplication
453
+ valid_time_start = chunk_start / sample_rate
454
+ valid_time_end = chunk_end / sample_rate
455
+
456
+ valid_mask = (times_chunk_adjusted >= valid_time_start) & (
457
+ times_chunk_adjusted < valid_time_end
458
+ )
459
+
460
+ if np.any(valid_mask):
461
+ Sxx_chunk = Sxx_chunk[:, valid_mask]
462
+ times_chunk_adjusted = times_chunk_adjusted[valid_mask]
463
+
464
+ chunks_stft.append(Sxx_chunk)
465
+ chunks_times.append(times_chunk_adjusted)
466
+
467
+ # Move to next chunk
468
+ chunk_start = chunk_end
469
+
470
+ # Concatenate all chunks
471
+ if len(chunks_stft) == 0:
472
+ raise ValueError("No valid chunks produced")
473
+
474
+ Sxx = np.concatenate(chunks_stft, axis=1)
475
+ times = np.concatenate(chunks_times)
476
+
477
+ # Convert to dB
478
+ Sxx = np.maximum(Sxx, 1e-20)
479
+ Sxx_db = 10 * np.log10(Sxx)
480
+
481
+ return times, freq, Sxx_db
482
+
483
+
484
+ def chunked_fft(
485
+ data: NDArray[np.float64],
486
+ sample_rate: float,
487
+ *,
488
+ chunk_size: int = 10_000_000,
489
+ overlap: float = 50.0,
490
+ window: str = "hann",
491
+ nfft: int | None = None,
492
+ ) -> tuple[NDArray[np.float64], NDArray[np.float64]]:
493
+ """Compute FFT for very long signals using segmented averaging.
494
+
495
+
496
+ Divides signal into overlapping segments, computes FFT for each,
497
+ and averages magnitude spectra. This is memory-bounded by chunk_size
498
+ and provides variance reduction through averaging (similar to Welch's method).
499
+
500
+ Args:
501
+ data: Input signal array (can be memory-mapped).
502
+ sample_rate: Sample rate in Hz.
503
+ chunk_size: Size of each segment in samples (default 10M).
504
+ overlap: Percentage overlap between segments, 0-100 (default 50%).
505
+ window: Window function name (default "hann").
506
+ nfft: FFT length. If None, uses next power of 2 >= chunk_size.
507
+
508
+ Returns:
509
+ (frequencies, magnitude_db) - Frequency axis and averaged magnitude in dB.
510
+
511
+ Example:
512
+ >>> # Memory-efficient FFT on 1 GB signal with 50% overlap
513
+ >>> import numpy as np
514
+ >>> data = np.memmap('huge_trace.dat', dtype='float64', mode='r')
515
+ >>> freq, mag = chunked_fft(data, sample_rate=1e9, chunk_size=1_000_000)
516
+ >>> print(f"Frequency resolution: {freq[1] - freq[0]:.3f} Hz")
517
+
518
+ References:
519
+ MEM-006: Chunked FFT requirement
520
+ Welch's method for spectral estimation
521
+ """
522
+ from ..utils.windowing import get_window
523
+
524
+ n = len(data)
525
+
526
+ # Handle empty input
527
+ if n == 0:
528
+ return np.array([]), np.array([])
529
+
530
+ # If data fits in one chunk, compute single FFT
531
+ if n <= chunk_size:
532
+ if nfft is None:
533
+ nfft = int(2 ** np.ceil(np.log2(n)))
534
+
535
+ # Apply window
536
+ w = get_window(window, n)
537
+ data_windowed = data * w
538
+
539
+ # Compute FFT
540
+ spectrum = np.fft.rfft(data_windowed, n=nfft)
541
+
542
+ # Frequency axis
543
+ freq = np.fft.rfftfreq(nfft, d=1.0 / sample_rate)
544
+
545
+ # Magnitude in dB (normalized by window gain)
546
+ window_gain = np.sum(w) / n
547
+ magnitude = np.abs(spectrum) / (n * window_gain)
548
+ magnitude = np.maximum(magnitude, 1e-20)
549
+ magnitude_db = 20 * np.log10(magnitude)
550
+
551
+ return freq, magnitude_db
552
+
553
+ # Calculate overlap
554
+ overlap_samples = int(chunk_size * overlap / 100.0)
555
+ hop = chunk_size - overlap_samples
556
+
557
+ # Determine number of segments
558
+ num_segments = max(1, (n - overlap_samples) // hop)
559
+
560
+ if nfft is None:
561
+ nfft = int(2 ** np.ceil(np.log2(chunk_size)))
562
+
563
+ # Prepare window
564
+ w = get_window(window, chunk_size)
565
+ window_gain = np.sum(w) / chunk_size
566
+
567
+ # Accumulate magnitude spectra
568
+ freq = np.fft.rfftfreq(nfft, d=1.0 / sample_rate)
569
+ magnitude_sum = np.zeros(len(freq))
570
+
571
+ for i in range(num_segments):
572
+ start = i * hop
573
+ end = min(start + chunk_size, n)
574
+
575
+ # Extract segment
576
+ if end - start < chunk_size:
577
+ # Last segment: pad with zeros
578
+ segment = np.zeros(chunk_size)
579
+ segment[: end - start] = data[start:end]
580
+ else:
581
+ segment = data[start:end]
582
+
583
+ # Detrend (remove mean)
584
+ segment = segment - np.mean(segment)
585
+
586
+ # Window
587
+ segment_windowed = segment * w
588
+
589
+ # FFT
590
+ spectrum = np.fft.rfft(segment_windowed, n=nfft)
591
+
592
+ # Accumulate magnitude
593
+ magnitude = np.abs(spectrum) / (chunk_size * window_gain)
594
+ magnitude_sum += magnitude
595
+
596
+ # Average
597
+ magnitude_avg = magnitude_sum / num_segments
598
+
599
+ # Convert to dB
600
+ magnitude_avg = np.maximum(magnitude_avg, 1e-20)
601
+ magnitude_db = 20 * np.log10(magnitude_avg)
602
+
603
+ return freq, magnitude_db
604
+
605
+
606
+ __all__ = [
607
+ "StreamingAnalyzer",
608
+ "chunked_fft",
609
+ "chunked_spectrogram",
610
+ "load_trace_chunks",
611
+ ]