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,352 @@
1
+ """Progressive resolution analysis for memory-constrained scenarios.
2
+
3
+ This module provides multi-pass analysis capabilities: preview first,
4
+ then zoom into regions of interest for detailed analysis.
5
+
6
+
7
+ Example:
8
+ >>> from oscura.utils.progressive import create_preview, analyze_roi
9
+ >>> preview = create_preview(trace, downsample_factor=10)
10
+ >>> # User inspects preview, selects ROI
11
+ >>> roi_result = analyze_roi(trace, start_time=0.001, end_time=0.002)
12
+
13
+ References:
14
+ Multi-resolution analysis techniques
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ from dataclasses import dataclass
20
+ from typing import TYPE_CHECKING, Any
21
+
22
+ import numpy as np
23
+
24
+ if TYPE_CHECKING:
25
+ from collections.abc import Callable
26
+
27
+ from numpy.typing import NDArray
28
+
29
+ from oscura.core.types import WaveformTrace
30
+
31
+
32
+ @dataclass
33
+ class PreviewResult:
34
+ """Result of preview analysis.
35
+
36
+
37
+ Attributes:
38
+ downsampled_data: Downsampled waveform data.
39
+ downsample_factor: Downsampling factor applied.
40
+ original_length: Length of original signal.
41
+ preview_length: Length of preview signal.
42
+ sample_rate: Sample rate of preview (original / factor).
43
+ time_vector: Time axis for preview.
44
+ basic_stats: Basic statistics from preview.
45
+ """
46
+
47
+ downsampled_data: NDArray[np.float64]
48
+ downsample_factor: int
49
+ original_length: int
50
+ preview_length: int
51
+ sample_rate: float
52
+ time_vector: NDArray[np.float64]
53
+ basic_stats: dict[str, float]
54
+
55
+
56
+ @dataclass
57
+ class ROISelection:
58
+ """Region of interest selection.
59
+
60
+
61
+ Attributes:
62
+ start_time: Start time in seconds.
63
+ end_time: End time in seconds.
64
+ start_index: Start sample index in original signal.
65
+ end_index: End sample index in original signal.
66
+ duration: Duration in seconds.
67
+ num_samples: Number of samples in ROI.
68
+ """
69
+
70
+ start_time: float
71
+ end_time: float
72
+ start_index: int
73
+ end_index: int
74
+ duration: float
75
+ num_samples: int
76
+
77
+
78
+ def create_preview(
79
+ trace: WaveformTrace,
80
+ *,
81
+ downsample_factor: int | None = None,
82
+ max_samples: int = 10_000,
83
+ apply_antialiasing: bool = True,
84
+ ) -> PreviewResult:
85
+ """Create downsampled preview of waveform for quick inspection.
86
+
87
+
88
+ Args:
89
+ trace: Input waveform trace.
90
+ downsample_factor: Downsampling factor (auto-computed if None).
91
+ max_samples: Target maximum samples in preview.
92
+ apply_antialiasing: Apply anti-aliasing lowpass filter before decimation.
93
+
94
+ Returns:
95
+ PreviewResult with downsampled data and metadata.
96
+
97
+ Example:
98
+ >>> preview = create_preview(large_trace, downsample_factor=10)
99
+ >>> print(f"Preview: {preview.preview_length} samples (factor {preview.downsample_factor}x)")
100
+ >>> # Inspect preview.basic_stats
101
+ """
102
+ from scipy import signal as sp_signal
103
+
104
+ data = trace.data
105
+ original_length = len(data)
106
+ sample_rate = trace.metadata.sample_rate
107
+
108
+ # Auto-compute downsample factor
109
+ if downsample_factor is None:
110
+ downsample_factor = max(1, original_length // max_samples)
111
+ # Round to nearest power of 2 for efficiency
112
+ downsample_factor = 2 ** int(np.ceil(np.log2(downsample_factor)))
113
+ downsample_factor = max(1, downsample_factor)
114
+
115
+ # Apply anti-aliasing filter if requested
116
+ if apply_antialiasing and downsample_factor > 1:
117
+ # Lowpass filter at Nyquist frequency of downsampled rate
118
+ nyquist_freq = (sample_rate / downsample_factor) / 2
119
+ sos = sp_signal.butter(8, nyquist_freq, btype="low", fs=sample_rate, output="sos")
120
+ filtered = sp_signal.sosfilt(sos, data)
121
+ downsampled = filtered[::downsample_factor]
122
+ else:
123
+ # Simple decimation without filtering
124
+ downsampled = data[::downsample_factor]
125
+
126
+ preview_length = len(downsampled)
127
+ preview_sample_rate = sample_rate / downsample_factor
128
+
129
+ # Create time vector
130
+ time_vector = np.arange(preview_length) / preview_sample_rate
131
+
132
+ # Compute basic statistics
133
+ basic_stats = {
134
+ "mean": float(np.mean(downsampled)),
135
+ "std": float(np.std(downsampled)),
136
+ "min": float(np.min(downsampled)),
137
+ "max": float(np.max(downsampled)),
138
+ "rms": float(np.sqrt(np.mean(downsampled**2))),
139
+ "peak_to_peak": float(np.ptp(downsampled)),
140
+ }
141
+
142
+ return PreviewResult(
143
+ downsampled_data=downsampled,
144
+ downsample_factor=downsample_factor,
145
+ original_length=original_length,
146
+ preview_length=preview_length,
147
+ sample_rate=preview_sample_rate,
148
+ time_vector=time_vector,
149
+ basic_stats=basic_stats,
150
+ )
151
+
152
+
153
+ def select_roi(
154
+ trace: WaveformTrace,
155
+ start_time: float,
156
+ end_time: float,
157
+ ) -> ROISelection:
158
+ """Create ROI selection from time range.
159
+
160
+
161
+ Args:
162
+ trace: Input waveform trace.
163
+ start_time: Start time in seconds.
164
+ end_time: End time in seconds.
165
+
166
+ Returns:
167
+ ROISelection with sample indices and metadata.
168
+
169
+ Raises:
170
+ ValueError: If time range is invalid.
171
+
172
+ Example:
173
+ >>> roi = select_roi(trace, start_time=0.001, end_time=0.002)
174
+ >>> print(f"ROI: {roi.num_samples} samples ({roi.duration*1e6:.1f} µs)")
175
+ """
176
+ sample_rate = trace.metadata.sample_rate
177
+ total_length = len(trace.data)
178
+ total_duration = total_length / sample_rate
179
+
180
+ # Validate time range
181
+ if start_time < 0 or end_time > total_duration:
182
+ raise ValueError(
183
+ f"Time range [{start_time}, {end_time}] outside signal duration [0, {total_duration}]"
184
+ )
185
+ if start_time >= end_time:
186
+ raise ValueError(f"start_time ({start_time}) must be < end_time ({end_time})")
187
+
188
+ # Convert to sample indices
189
+ start_index = int(start_time * sample_rate)
190
+ end_index = int(end_time * sample_rate)
191
+
192
+ # Clamp to valid range
193
+ start_index = max(0, min(start_index, total_length - 1))
194
+ end_index = max(start_index + 1, min(end_index, total_length))
195
+
196
+ duration = end_time - start_time
197
+ num_samples = end_index - start_index
198
+
199
+ return ROISelection(
200
+ start_time=start_time,
201
+ end_time=end_time,
202
+ start_index=start_index,
203
+ end_index=end_index,
204
+ duration=duration,
205
+ num_samples=num_samples,
206
+ )
207
+
208
+
209
+ def analyze_roi(
210
+ trace: WaveformTrace,
211
+ roi: ROISelection,
212
+ *,
213
+ analysis_func: Callable[[WaveformTrace], Any],
214
+ **analysis_kwargs: Any,
215
+ ) -> Any:
216
+ """Analyze region of interest with high resolution.
217
+
218
+
219
+ Args:
220
+ trace: Input waveform trace.
221
+ roi: ROI selection.
222
+ analysis_func: Analysis function to apply to ROI.
223
+ **analysis_kwargs: Additional arguments for analysis function.
224
+
225
+ Returns:
226
+ Result of analysis function on ROI.
227
+
228
+ Example:
229
+ >>> from oscura.analyzers.waveform.spectral import fft
230
+ >>> roi = select_roi(trace, 0.001, 0.002)
231
+ >>> freq, mag = analyze_roi(trace, roi, analysis_func=fft, window='hann')
232
+ """
233
+ from oscura.core.types import TraceMetadata, WaveformTrace
234
+
235
+ # Extract ROI data
236
+ roi_data = trace.data[roi.start_index : roi.end_index]
237
+
238
+ # Create new trace for ROI with only standard metadata fields
239
+ roi_trace = WaveformTrace(
240
+ data=roi_data,
241
+ metadata=TraceMetadata(
242
+ sample_rate=trace.metadata.sample_rate,
243
+ vertical_scale=trace.metadata.vertical_scale,
244
+ vertical_offset=trace.metadata.vertical_offset,
245
+ acquisition_time=trace.metadata.acquisition_time,
246
+ trigger_info=trace.metadata.trigger_info,
247
+ source_file=trace.metadata.source_file,
248
+ channel_name=getattr(trace.metadata, "channel_name", None),
249
+ ),
250
+ )
251
+
252
+ # Apply analysis function
253
+ return analysis_func(roi_trace, **analysis_kwargs)
254
+
255
+
256
+ def progressive_analysis(
257
+ trace: WaveformTrace,
258
+ *,
259
+ analysis_func: Callable[[WaveformTrace], Any],
260
+ downsample_factor: int = 10,
261
+ roi_selector: Callable[[PreviewResult], ROISelection] | None = None,
262
+ **analysis_kwargs: Any,
263
+ ) -> tuple[PreviewResult, Any]:
264
+ """Perform progressive multi-pass analysis.
265
+
266
+
267
+ Workflow:
268
+ 1. Create downsampled preview
269
+ 2. User/algorithm selects ROI from preview
270
+ 3. Perform high-resolution analysis on ROI only
271
+
272
+ Args:
273
+ trace: Input waveform trace.
274
+ analysis_func: Analysis function to apply.
275
+ downsample_factor: Downsampling factor for preview.
276
+ roi_selector: Function to select ROI from preview (if None, analyzes full trace).
277
+ **analysis_kwargs: Additional arguments for analysis function.
278
+
279
+ Returns:
280
+ Tuple of (preview_result, analysis_result).
281
+
282
+ Example:
283
+ >>> def select_peak_region(preview):
284
+ ... # Find region with highest amplitude
285
+ ... peak_idx = np.argmax(np.abs(preview.downsampled_data))
286
+ ... start_time = max(0, (peak_idx - 500) / preview.sample_rate)
287
+ ... end_time = min(preview.preview_length / preview.sample_rate,
288
+ ... (peak_idx + 500) / preview.sample_rate)
289
+ ... return select_roi(trace, start_time, end_time)
290
+ >>>
291
+ >>> from oscura.analyzers.waveform.spectral import fft
292
+ >>> preview, result = progressive_analysis(
293
+ ... trace,
294
+ ... analysis_func=fft,
295
+ ... downsample_factor=10,
296
+ ... roi_selector=select_peak_region
297
+ ... )
298
+ """
299
+ # Pass 1: Create preview
300
+ preview = create_preview(trace, downsample_factor=downsample_factor)
301
+
302
+ # Pass 2: Select ROI
303
+ if roi_selector is not None:
304
+ roi = roi_selector(preview)
305
+ # Pass 3: Analyze ROI
306
+ result = analyze_roi(trace, roi, analysis_func=analysis_func, **analysis_kwargs)
307
+ else:
308
+ # No ROI selection, analyze full trace
309
+ result = analysis_func(trace, **analysis_kwargs)
310
+
311
+ return preview, result
312
+
313
+
314
+ def estimate_optimal_preview_factor(
315
+ trace_length: int,
316
+ *,
317
+ target_memory: int = 100_000_000, # 100 MB
318
+ bytes_per_sample: int = 8,
319
+ ) -> int:
320
+ """Estimate optimal downsampling factor for preview.
321
+
322
+ Args:
323
+ trace_length: Number of samples in original trace.
324
+ target_memory: Target memory for preview (bytes).
325
+ bytes_per_sample: Bytes per sample (8 for float64).
326
+
327
+ Returns:
328
+ Recommended downsampling factor.
329
+
330
+ Example:
331
+ >>> factor = estimate_optimal_preview_factor(1_000_000_000) # 1B samples
332
+ >>> print(f"Downsample by {factor}x for preview")
333
+ """
334
+ # Calculate required factor to fit in target memory
335
+ current_memory = trace_length * bytes_per_sample
336
+ factor = max(1, int(np.ceil(current_memory / target_memory)))
337
+
338
+ # Round to power of 2
339
+ factor = 2 ** int(np.ceil(np.log2(factor)))
340
+
341
+ return factor # type: ignore[no-any-return]
342
+
343
+
344
+ __all__ = [
345
+ "PreviewResult",
346
+ "ROISelection",
347
+ "analyze_roi",
348
+ "create_preview",
349
+ "estimate_optimal_preview_factor",
350
+ "progressive_analysis",
351
+ "select_roi",
352
+ ]
@@ -0,0 +1,362 @@
1
+ """Window function support for spectral analysis.
2
+
3
+ This module provides standard window functions for FFT and spectral
4
+ analysis, implementing the requirements for windowed spectral estimation.
5
+
6
+
7
+ Example:
8
+ >>> from oscura.utils.windowing import get_window, WINDOW_FUNCTIONS
9
+ >>> window = get_window("hann", 1024)
10
+ >>> print(f"Available windows: {list(WINDOW_FUNCTIONS.keys())}")
11
+
12
+ References:
13
+ Harris, F. J. (1978). "On the use of windows for harmonic analysis
14
+ with the discrete Fourier transform." Proceedings of the IEEE, 66(1).
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ from collections.abc import Callable
20
+ from typing import TYPE_CHECKING, Any, Literal
21
+
22
+ import numpy as np
23
+
24
+ if TYPE_CHECKING:
25
+ from numpy.typing import NDArray
26
+
27
+ # Type alias for window function (using string annotation for TYPE_CHECKING compatibility)
28
+ WindowFunction = Callable[[int], "NDArray[np.float64]"]
29
+
30
+
31
+ def rectangular(n: int) -> NDArray[np.float64]:
32
+ """Rectangular (boxcar) window.
33
+
34
+ No tapering applied - all samples weighted equally.
35
+
36
+ Args:
37
+ n: Window length in samples.
38
+
39
+ Returns:
40
+ Window coefficients (all ones).
41
+
42
+ Example:
43
+ >>> w = rectangular(64)
44
+ >>> assert np.all(w == 1.0)
45
+ """
46
+ return np.ones(n, dtype=np.float64)
47
+
48
+
49
+ def hann(n: int) -> NDArray[np.float64]:
50
+ """Hann (raised cosine) window.
51
+
52
+ Also known as Hanning window. Provides good frequency resolution
53
+ with moderate sidelobe suppression.
54
+
55
+ Args:
56
+ n: Window length in samples.
57
+
58
+ Returns:
59
+ Window coefficients.
60
+
61
+ Example:
62
+ >>> w = hann(64)
63
+ >>> assert w[0] == w[-1] # Symmetric
64
+
65
+ References:
66
+ IEEE Std 1057-2017 Section 4.4.2
67
+ """
68
+ return np.hanning(n).astype(np.float64)
69
+
70
+
71
+ def hamming(n: int) -> NDArray[np.float64]:
72
+ """Hamming window.
73
+
74
+ Similar to Hann but with reduced first sidelobe at cost of
75
+ slower rolloff.
76
+
77
+ Args:
78
+ n: Window length in samples.
79
+
80
+ Returns:
81
+ Window coefficients.
82
+
83
+ Example:
84
+ >>> w = hamming(64)
85
+ >>> assert w[32] > w[0] # Peak in center
86
+ """
87
+ return np.hamming(n).astype(np.float64)
88
+
89
+
90
+ def blackman(n: int) -> NDArray[np.float64]:
91
+ """Blackman window.
92
+
93
+ Three-term cosine window with excellent sidelobe suppression
94
+ (-58 dB first sidelobe).
95
+
96
+ Args:
97
+ n: Window length in samples.
98
+
99
+ Returns:
100
+ Window coefficients.
101
+
102
+ Example:
103
+ >>> w = blackman(64)
104
+ >>> assert w[32] > w[0]
105
+ """
106
+ return np.blackman(n).astype(np.float64)
107
+
108
+
109
+ def kaiser(n: int, beta: float = 8.6) -> NDArray[np.float64]:
110
+ """Kaiser window with configurable shape parameter.
111
+
112
+ Provides adjustable tradeoff between main lobe width and
113
+ sidelobe attenuation.
114
+
115
+ Args:
116
+ n: Window length in samples.
117
+ beta: Shape parameter (default 8.6 for ~60 dB sidelobe attenuation).
118
+ - beta=0: Rectangular
119
+ - beta=5: ~30 dB sidelobe attenuation
120
+ - beta=8.6: ~60 dB sidelobe attenuation
121
+ - beta=14: ~90 dB sidelobe attenuation
122
+
123
+ Returns:
124
+ Window coefficients.
125
+
126
+ Example:
127
+ >>> w = kaiser(64, beta=10)
128
+ >>> assert 0 < w[0] < w[32]
129
+ """
130
+ return np.kaiser(n, beta).astype(np.float64)
131
+
132
+
133
+ def flattop(n: int) -> NDArray[np.float64]:
134
+ """Flat-top window for accurate amplitude measurements.
135
+
136
+ Provides minimal scalloping loss (<0.01 dB) at cost of
137
+ wider main lobe. Best for amplitude accuracy when frequency
138
+ resolution is not critical.
139
+
140
+ Args:
141
+ n: Window length in samples.
142
+
143
+ Returns:
144
+ Window coefficients.
145
+
146
+ Example:
147
+ >>> w = flattop(64)
148
+ >>> # Flat-top has characteristic near-zero values at edges
149
+
150
+ References:
151
+ D'Antona, G. & Ferrero, A. (2006). "Digital Signal Processing
152
+ for Measurement Systems."
153
+ """
154
+ # Flat-top coefficients per HP/Agilent standard
155
+ a0 = 0.21557895
156
+ a1 = 0.41663158
157
+ a2 = 0.277263158
158
+ a3 = 0.083578947
159
+ a4 = 0.006947368
160
+
161
+ k = np.arange(n, dtype=np.float64)
162
+ w = (
163
+ a0
164
+ - a1 * np.cos(2 * np.pi * k / (n - 1))
165
+ + a2 * np.cos(4 * np.pi * k / (n - 1))
166
+ - a3 * np.cos(6 * np.pi * k / (n - 1))
167
+ + a4 * np.cos(8 * np.pi * k / (n - 1))
168
+ )
169
+ return np.asarray(w, dtype=np.float64)
170
+
171
+
172
+ def bartlett(n: int) -> NDArray[np.float64]:
173
+ """Bartlett (triangular) window.
174
+
175
+ Linear taper from zero at edges to maximum at center.
176
+
177
+ Args:
178
+ n: Window length in samples.
179
+
180
+ Returns:
181
+ Window coefficients.
182
+
183
+ Example:
184
+ >>> w = bartlett(64)
185
+ >>> assert w[32] == 1.0 # Maximum at center
186
+ """
187
+ return np.bartlett(n).astype(np.float64)
188
+
189
+
190
+ def blackman_harris(n: int) -> NDArray[np.float64]:
191
+ """Blackman-Harris window (4-term).
192
+
193
+ Four-term cosine window with excellent sidelobe suppression
194
+ (-92 dB first sidelobe).
195
+
196
+ Args:
197
+ n: Window length in samples.
198
+
199
+ Returns:
200
+ Window coefficients.
201
+
202
+ Example:
203
+ >>> w = blackman_harris(64)
204
+ >>> assert w[32] > w[0]
205
+ """
206
+ a0 = 0.35875
207
+ a1 = 0.48829
208
+ a2 = 0.14128
209
+ a3 = 0.01168
210
+
211
+ k = np.arange(n, dtype=np.float64)
212
+ w = (
213
+ a0
214
+ - a1 * np.cos(2 * np.pi * k / (n - 1))
215
+ + a2 * np.cos(4 * np.pi * k / (n - 1))
216
+ - a3 * np.cos(6 * np.pi * k / (n - 1))
217
+ )
218
+ return np.asarray(w, dtype=np.float64)
219
+
220
+
221
+ # Window function registry
222
+ WINDOW_FUNCTIONS: dict[str, WindowFunction] = {
223
+ "rectangular": rectangular,
224
+ "boxcar": rectangular,
225
+ "rect": rectangular,
226
+ "hann": hann,
227
+ "hanning": hann,
228
+ "hamming": hamming,
229
+ "blackman": blackman,
230
+ "kaiser": lambda n: kaiser(n, beta=8.6),
231
+ "flattop": flattop,
232
+ "flat_top": flattop,
233
+ "bartlett": bartlett,
234
+ "triangular": bartlett,
235
+ "blackman_harris": blackman_harris,
236
+ "blackmanharris": blackman_harris,
237
+ }
238
+
239
+
240
+ # Type for window names
241
+ WindowName = Literal[
242
+ "rectangular",
243
+ "boxcar",
244
+ "rect",
245
+ "hann",
246
+ "hanning",
247
+ "hamming",
248
+ "blackman",
249
+ "kaiser",
250
+ "flattop",
251
+ "flat_top",
252
+ "bartlett",
253
+ "triangular",
254
+ "blackman_harris",
255
+ "blackmanharris",
256
+ ]
257
+
258
+
259
+ def get_window(
260
+ window: str | WindowFunction | NDArray[np.floating[Any]],
261
+ n: int,
262
+ *,
263
+ beta: float | None = None,
264
+ ) -> NDArray[np.float64]:
265
+ """Get window coefficients by name or callable.
266
+
267
+ Args:
268
+ window: Window specification. Can be:
269
+ - A string name from WINDOW_FUNCTIONS
270
+ - A callable that takes length and returns coefficients
271
+ - A pre-computed array of coefficients
272
+ n: Window length in samples.
273
+ beta: Optional beta parameter for Kaiser window.
274
+
275
+ Returns:
276
+ Window coefficients array of length n.
277
+
278
+ Raises:
279
+ ValueError: If window name is unknown.
280
+
281
+ Example:
282
+ >>> w = get_window("hann", 1024)
283
+ >>> w = get_window("kaiser", 1024, beta=10)
284
+ >>> w = get_window(np.hamming, 1024)
285
+ """
286
+ if isinstance(window, np.ndarray):
287
+ if len(window) != n:
288
+ raise ValueError(f"Window array length {len(window)} != requested {n}")
289
+ return window.astype(np.float64)
290
+
291
+ if callable(window) and not isinstance(window, str):
292
+ return np.asarray(window(n), dtype=np.float64)
293
+
294
+ window_name = window.lower()
295
+
296
+ if window_name == "kaiser" and beta is not None:
297
+ return kaiser(n, beta)
298
+
299
+ if window_name not in WINDOW_FUNCTIONS:
300
+ available = ", ".join(sorted(set(WINDOW_FUNCTIONS.keys())))
301
+ raise ValueError(f"Unknown window: {window}. Available: {available}")
302
+
303
+ return WINDOW_FUNCTIONS[window_name](n)
304
+
305
+
306
+ def window_properties(window: str | NDArray[np.floating[Any]], n: int = 1024) -> dict[str, Any]:
307
+ """Compute window properties for analysis.
308
+
309
+ Args:
310
+ window: Window name or coefficients.
311
+ n: Window length for named windows.
312
+
313
+ Returns:
314
+ Dictionary with window properties:
315
+ - coherent_gain: Sum of window / length
316
+ - noise_bandwidth: Normalized equivalent noise bandwidth
317
+ - scalloping_loss: Peak amplitude error in dB
318
+
319
+ Example:
320
+ >>> props = window_properties("hann")
321
+ >>> print(f"ENBW: {props['noise_bandwidth']:.3f}")
322
+ """
323
+ if isinstance(window, str):
324
+ w = get_window(window, n)
325
+ else:
326
+ w = np.asarray(window, dtype=np.float64)
327
+ n = len(w)
328
+
329
+ # Coherent gain (DC gain)
330
+ coherent_gain = np.sum(w) / n
331
+
332
+ # Noise equivalent bandwidth
333
+ # ENBW = N * sum(w^2) / (sum(w))^2
334
+ noise_bandwidth = n * np.sum(w**2) / np.sum(w) ** 2
335
+
336
+ # Scalloping loss (worst-case amplitude error at bin edge)
337
+ # Approximate by evaluating window at half-bin offset
338
+ k = np.arange(n)
339
+ w_shifted = w * np.exp(2j * np.pi * 0.5 * k / n)
340
+ scalloping_loss = 20 * np.log10(np.abs(np.sum(w_shifted)) / np.abs(np.sum(w)))
341
+
342
+ return {
343
+ "coherent_gain": float(coherent_gain),
344
+ "noise_bandwidth": float(noise_bandwidth),
345
+ "scalloping_loss": float(scalloping_loss),
346
+ "length": n,
347
+ }
348
+
349
+
350
+ __all__ = [
351
+ "WINDOW_FUNCTIONS",
352
+ "bartlett",
353
+ "blackman",
354
+ "blackman_harris",
355
+ "flattop",
356
+ "get_window",
357
+ "hamming",
358
+ "hann",
359
+ "kaiser",
360
+ "rectangular",
361
+ "window_properties",
362
+ ]