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,413 @@
1
+ """Jitter timing measurements.
2
+
3
+ This module provides cycle-to-cycle jitter, period jitter, and
4
+ duty cycle distortion measurements.
5
+
6
+
7
+ Example:
8
+ >>> from oscura.analyzers.jitter.measurements import cycle_to_cycle_jitter
9
+ >>> c2c = cycle_to_cycle_jitter(periods)
10
+ >>> print(f"C2C RMS: {c2c.c2c_rms * 1e12:.2f} ps")
11
+
12
+ References:
13
+ IEEE 2414-2020: Standard for Jitter and Phase Noise
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ from dataclasses import dataclass
19
+ from typing import TYPE_CHECKING
20
+
21
+ import numpy as np
22
+
23
+ from oscura.core.exceptions import InsufficientDataError
24
+ from oscura.core.types import DigitalTrace, WaveformTrace
25
+
26
+ if TYPE_CHECKING:
27
+ from numpy.typing import NDArray
28
+
29
+
30
+ @dataclass
31
+ class CycleJitterResult:
32
+ """Result of cycle-to-cycle or period jitter measurement.
33
+
34
+ Attributes:
35
+ c2c_rms: Cycle-to-cycle jitter RMS in seconds.
36
+ c2c_pp: Cycle-to-cycle jitter peak-to-peak in seconds.
37
+ c2c_values: Array of individual C2C jitter values.
38
+ period_mean: Mean period in seconds.
39
+ period_std: Standard deviation of periods in seconds.
40
+ n_cycles: Number of cycles analyzed.
41
+ histogram: Histogram of C2C values.
42
+ bin_centers: Bin centers for histogram.
43
+ """
44
+
45
+ c2c_rms: float
46
+ c2c_pp: float
47
+ c2c_values: NDArray[np.float64]
48
+ period_mean: float
49
+ period_std: float
50
+ n_cycles: int
51
+ histogram: NDArray[np.float64] | None = None
52
+ bin_centers: NDArray[np.float64] | None = None
53
+
54
+
55
+ @dataclass
56
+ class DutyCycleDistortionResult:
57
+ """Result of duty cycle distortion measurement.
58
+
59
+ Attributes:
60
+ dcd_seconds: DCD in seconds.
61
+ dcd_percent: DCD as percentage of period.
62
+ mean_high_time: Mean high time in seconds.
63
+ mean_low_time: Mean low time in seconds.
64
+ duty_cycle: Actual duty cycle as fraction (0.0 to 1.0).
65
+ period: Mean period in seconds.
66
+ n_cycles: Number of cycles analyzed.
67
+ """
68
+
69
+ dcd_seconds: float
70
+ dcd_percent: float
71
+ mean_high_time: float
72
+ mean_low_time: float
73
+ duty_cycle: float
74
+ period: float
75
+ n_cycles: int
76
+
77
+
78
+ def tie_from_edges(
79
+ edge_timestamps: NDArray[np.float64],
80
+ nominal_period: float | None = None,
81
+ ) -> NDArray[np.float64]:
82
+ """Calculate Time Interval Error from edge timestamps.
83
+
84
+ TIE is the deviation of each edge from its ideal position
85
+ based on the recovered clock period.
86
+
87
+ Args:
88
+ edge_timestamps: Array of edge timestamps in seconds.
89
+ nominal_period: Expected period (computed from data if None).
90
+
91
+ Returns:
92
+ Array of TIE values in seconds.
93
+
94
+ Example:
95
+ >>> tie = tie_from_edges(rising_edges, nominal_period=1e-9)
96
+ >>> print(f"TIE range: {np.ptp(tie) * 1e12:.2f} ps")
97
+ """
98
+ if len(edge_timestamps) < 3:
99
+ return np.array([], dtype=np.float64)
100
+
101
+ # Calculate actual periods
102
+ periods = np.diff(edge_timestamps)
103
+
104
+ # Use mean period if nominal not provided
105
+ if nominal_period is None:
106
+ nominal_period = np.mean(periods)
107
+
108
+ # Calculate ideal edge positions
109
+ n_edges = len(edge_timestamps)
110
+ start_time = edge_timestamps[0]
111
+ ideal_positions = start_time + np.arange(n_edges) * nominal_period
112
+
113
+ # TIE is actual - ideal
114
+ tie: NDArray[np.float64] = edge_timestamps - ideal_positions
115
+
116
+ return tie
117
+
118
+
119
+ def cycle_to_cycle_jitter(
120
+ periods: NDArray[np.float64],
121
+ *,
122
+ include_histogram: bool = True,
123
+ n_bins: int = 50,
124
+ ) -> CycleJitterResult:
125
+ """Measure cycle-to-cycle jitter for clock quality analysis.
126
+
127
+ Cycle-to-cycle jitter measures the variation in period from
128
+ one clock cycle to the next: C2C[n] = |Period[n] - Period[n-1]|
129
+
130
+ Args:
131
+ periods: Array of measured clock periods in seconds.
132
+ include_histogram: Include histogram in result.
133
+ n_bins: Number of histogram bins.
134
+
135
+ Returns:
136
+ CycleJitterResult with C2C jitter statistics.
137
+
138
+ Raises:
139
+ InsufficientDataError: If fewer than 3 periods provided.
140
+
141
+ Example:
142
+ >>> c2c = cycle_to_cycle_jitter(periods)
143
+ >>> print(f"C2C: {c2c.c2c_rms * 1e12:.2f} ps RMS")
144
+
145
+ References:
146
+ IEEE 2414-2020 Section 5.3
147
+ """
148
+ if len(periods) < 3:
149
+ raise InsufficientDataError(
150
+ "Cycle-to-cycle jitter requires at least 3 periods",
151
+ required=3,
152
+ available=len(periods),
153
+ analysis_type="cycle_to_cycle_jitter",
154
+ )
155
+
156
+ # Remove NaN values
157
+ valid_periods = periods[~np.isnan(periods)]
158
+
159
+ if len(valid_periods) < 3:
160
+ raise InsufficientDataError(
161
+ "Cycle-to-cycle jitter requires at least 3 valid periods",
162
+ required=3,
163
+ available=len(valid_periods),
164
+ analysis_type="cycle_to_cycle_jitter",
165
+ )
166
+
167
+ # Calculate cycle-to-cycle differences
168
+ c2c_values = np.abs(np.diff(valid_periods))
169
+
170
+ # Statistics
171
+ c2c_rms = float(np.sqrt(np.mean(c2c_values**2)))
172
+ c2c_pp = float(np.max(c2c_values) - np.min(c2c_values))
173
+ period_mean = float(np.mean(valid_periods))
174
+ period_std = float(np.std(valid_periods))
175
+
176
+ # Optional histogram
177
+ if include_histogram and len(c2c_values) > 10:
178
+ hist, bin_edges = np.histogram(c2c_values, bins=n_bins, density=True)
179
+ bin_centers = (bin_edges[:-1] + bin_edges[1:]) / 2
180
+ else:
181
+ hist = None
182
+ bin_centers = None
183
+
184
+ return CycleJitterResult(
185
+ c2c_rms=c2c_rms,
186
+ c2c_pp=c2c_pp,
187
+ c2c_values=c2c_values,
188
+ period_mean=period_mean,
189
+ period_std=period_std,
190
+ n_cycles=len(valid_periods),
191
+ histogram=hist,
192
+ bin_centers=bin_centers,
193
+ )
194
+
195
+
196
+ def period_jitter(
197
+ periods: NDArray[np.float64],
198
+ nominal_period: float | None = None,
199
+ ) -> CycleJitterResult:
200
+ """Measure period jitter (deviation from nominal period).
201
+
202
+ Period jitter is the deviation of each period from the ideal
203
+ or nominal period. Unlike C2C jitter, it measures absolute deviation.
204
+
205
+ Args:
206
+ periods: Array of measured clock periods in seconds.
207
+ nominal_period: Expected period (uses mean if None).
208
+
209
+ Returns:
210
+ CycleJitterResult with period jitter statistics.
211
+
212
+ Raises:
213
+ InsufficientDataError: If fewer than 2 periods provided.
214
+
215
+ Example:
216
+ >>> pj = period_jitter(periods, nominal_period=1e-9)
217
+ >>> print(f"Period jitter: {pj.c2c_rms * 1e12:.2f} ps RMS")
218
+ """
219
+ if len(periods) < 2:
220
+ raise InsufficientDataError(
221
+ "Period jitter requires at least 2 periods",
222
+ required=2,
223
+ available=len(periods),
224
+ analysis_type="period_jitter",
225
+ )
226
+
227
+ valid_periods = periods[~np.isnan(periods)]
228
+
229
+ if nominal_period is None:
230
+ nominal_period = np.mean(valid_periods)
231
+
232
+ # Calculate deviations from nominal
233
+ deviations = valid_periods - nominal_period
234
+
235
+ return CycleJitterResult(
236
+ c2c_rms=float(np.std(valid_periods)), # RMS of period variation
237
+ c2c_pp=float(np.max(valid_periods) - np.min(valid_periods)),
238
+ c2c_values=np.abs(deviations),
239
+ period_mean=float(np.mean(valid_periods)),
240
+ period_std=float(np.std(valid_periods)),
241
+ n_cycles=len(valid_periods),
242
+ )
243
+
244
+
245
+ def measure_dcd(
246
+ trace: WaveformTrace | DigitalTrace,
247
+ clock_period: float | None = None,
248
+ *,
249
+ threshold: float = 0.5,
250
+ ) -> DutyCycleDistortionResult:
251
+ """Measure duty cycle distortion.
252
+
253
+ DCD measures the asymmetry between high and low times in a clock signal.
254
+ DCD = |mean_high_time - mean_low_time|
255
+
256
+ Args:
257
+ trace: Input waveform or digital trace.
258
+ clock_period: Expected clock period (computed if None).
259
+ threshold: Threshold level as fraction of amplitude (0.0-1.0).
260
+
261
+ Returns:
262
+ DutyCycleDistortionResult with DCD metrics.
263
+
264
+ Raises:
265
+ InsufficientDataError: If not enough edges found.
266
+
267
+ Example:
268
+ >>> dcd = measure_dcd(clock_trace, clock_period=1e-9)
269
+ >>> print(f"DCD: {dcd.dcd_percent:.1f}%")
270
+
271
+ References:
272
+ IEEE 2414-2020 Section 5.4
273
+ """
274
+ # Get edge timestamps
275
+ rising_edges, falling_edges = _find_edges(trace, threshold)
276
+
277
+ if len(rising_edges) < 2 or len(falling_edges) < 2:
278
+ raise InsufficientDataError(
279
+ "DCD measurement requires at least 2 rising and 2 falling edges",
280
+ required=4,
281
+ available=len(rising_edges) + len(falling_edges),
282
+ analysis_type="dcd_measurement",
283
+ )
284
+
285
+ # Measure high times (rising to falling)
286
+ high_times = []
287
+ for r_edge in rising_edges:
288
+ # Find next falling edge
289
+ next_falling = falling_edges[falling_edges > r_edge]
290
+ if len(next_falling) > 0:
291
+ high_times.append(next_falling[0] - r_edge)
292
+
293
+ # Measure low times (falling to rising)
294
+ low_times = []
295
+ for f_edge in falling_edges:
296
+ # Find next rising edge
297
+ next_rising = rising_edges[rising_edges > f_edge]
298
+ if len(next_rising) > 0:
299
+ low_times.append(next_rising[0] - f_edge)
300
+
301
+ if len(high_times) < 1 or len(low_times) < 1:
302
+ raise InsufficientDataError(
303
+ "Could not measure high/low times",
304
+ required=2,
305
+ available=0,
306
+ analysis_type="dcd_measurement",
307
+ )
308
+
309
+ mean_high = float(np.mean(high_times))
310
+ mean_low = float(np.mean(low_times))
311
+
312
+ # Calculate DCD
313
+ dcd_seconds = abs(mean_high - mean_low)
314
+ period = mean_high + mean_low
315
+
316
+ if clock_period is None:
317
+ clock_period = period
318
+
319
+ dcd_percent = (dcd_seconds / clock_period) * 100
320
+ duty_cycle = mean_high / period
321
+
322
+ return DutyCycleDistortionResult(
323
+ dcd_seconds=dcd_seconds,
324
+ dcd_percent=dcd_percent,
325
+ mean_high_time=mean_high,
326
+ mean_low_time=mean_low,
327
+ duty_cycle=duty_cycle,
328
+ period=period,
329
+ n_cycles=min(len(high_times), len(low_times)),
330
+ )
331
+
332
+
333
+ def _find_edges(
334
+ trace: WaveformTrace | DigitalTrace,
335
+ threshold_frac: float,
336
+ ) -> tuple[NDArray[np.float64], NDArray[np.float64]]:
337
+ """Find rising and falling edge timestamps with sub-sample interpolation.
338
+
339
+ Args:
340
+ trace: Input trace.
341
+ threshold_frac: Threshold as fraction of amplitude.
342
+
343
+ Returns:
344
+ Tuple of (rising_edges, falling_edges) arrays in seconds.
345
+ """
346
+ data = trace.data.astype(np.float64) if isinstance(trace, DigitalTrace) else trace.data
347
+
348
+ sample_rate = trace.metadata.sample_rate
349
+ sample_period = 1.0 / sample_rate
350
+
351
+ if len(data) < 3:
352
+ return np.array([]), np.array([])
353
+
354
+ # Find amplitude levels - use more extreme percentiles for better accuracy
355
+ low = np.percentile(data, 5)
356
+ high = np.percentile(data, 95)
357
+ threshold = low + threshold_frac * (high - low)
358
+
359
+ # Find crossings
360
+ above = data >= threshold
361
+ below = data < threshold
362
+
363
+ rising_indices = np.where(below[:-1] & above[1:])[0]
364
+ falling_indices = np.where(above[:-1] & below[1:])[0]
365
+
366
+ # Convert to timestamps with linear interpolation
367
+ # For a crossing between samples i and i+1:
368
+ # time = i * dt + (threshold - v[i]) / (v[i+1] - v[i]) * dt
369
+
370
+ rising_edges = []
371
+ for idx in rising_indices:
372
+ v1, v2 = data[idx], data[idx + 1]
373
+ dv = v2 - v1
374
+ if abs(dv) > 1e-12:
375
+ # Linear interpolation to find exact crossing time
376
+ frac = (threshold - v1) / dv
377
+ # Clamp to [0, 1] to handle numerical errors
378
+ frac = max(0.0, min(1.0, frac))
379
+ t_offset = frac * sample_period
380
+ else:
381
+ # Values are equal, use midpoint
382
+ t_offset = sample_period / 2
383
+ rising_edges.append(idx * sample_period + t_offset)
384
+
385
+ falling_edges = []
386
+ for idx in falling_indices:
387
+ v1, v2 = data[idx], data[idx + 1]
388
+ dv = v2 - v1
389
+ if abs(dv) > 1e-12:
390
+ # Linear interpolation to find exact crossing time
391
+ frac = (threshold - v1) / dv
392
+ # Clamp to [0, 1] to handle numerical errors
393
+ frac = max(0.0, min(1.0, frac))
394
+ t_offset = frac * sample_period
395
+ else:
396
+ # Values are equal, use midpoint
397
+ t_offset = sample_period / 2
398
+ falling_edges.append(idx * sample_period + t_offset)
399
+
400
+ return (
401
+ np.array(rising_edges, dtype=np.float64),
402
+ np.array(falling_edges, dtype=np.float64),
403
+ )
404
+
405
+
406
+ __all__ = [
407
+ "CycleJitterResult",
408
+ "DutyCycleDistortionResult",
409
+ "cycle_to_cycle_jitter",
410
+ "measure_dcd",
411
+ "period_jitter",
412
+ "tie_from_edges",
413
+ ]
@@ -0,0 +1,220 @@
1
+ """Jitter spectrum analysis.
2
+
3
+ This module provides FFT-based analysis of TIE data to identify
4
+ periodic jitter sources and their frequencies.
5
+
6
+
7
+ Example:
8
+ >>> from oscura.analyzers.jitter.spectrum import jitter_spectrum
9
+ >>> result = jitter_spectrum(tie_data, sample_rate=1e9)
10
+ >>> print(f"Dominant frequency: {result.dominant_frequency / 1e3:.1f} kHz")
11
+
12
+ References:
13
+ IEEE 2414-2020: Standard for Jitter and Phase Noise
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ from dataclasses import dataclass
19
+ from typing import TYPE_CHECKING
20
+
21
+ import numpy as np
22
+ from scipy.signal import find_peaks
23
+
24
+ if TYPE_CHECKING:
25
+ from numpy.typing import NDArray
26
+
27
+
28
+ @dataclass
29
+ class JitterSpectrumResult:
30
+ """Result of jitter spectrum analysis.
31
+
32
+ Attributes:
33
+ frequencies: Frequency array in Hz.
34
+ magnitude: Magnitude spectrum in seconds.
35
+ magnitude_db: Magnitude spectrum in dB (relative to 1 ps).
36
+ dominant_frequency: Frequency of largest component in Hz.
37
+ dominant_magnitude: Magnitude at dominant frequency in seconds.
38
+ noise_floor: Estimated noise floor in seconds.
39
+ peaks: List of (frequency, magnitude) tuples for detected peaks.
40
+ """
41
+
42
+ frequencies: NDArray[np.float64]
43
+ magnitude: NDArray[np.float64]
44
+ magnitude_db: NDArray[np.float64]
45
+ dominant_frequency: float | None
46
+ dominant_magnitude: float | None
47
+ noise_floor: float
48
+ peaks: list[tuple[float, float]]
49
+
50
+
51
+ def jitter_spectrum(
52
+ tie_data: NDArray[np.float64],
53
+ sample_rate: float,
54
+ *,
55
+ window: str = "hann",
56
+ detrend: bool = True,
57
+ n_peaks: int = 10,
58
+ ) -> JitterSpectrumResult:
59
+ """Compute FFT of TIE data to identify jitter frequency components.
60
+
61
+ Identifies periodic jitter sources by frequency, useful for
62
+ debugging EMI-induced jitter and power supply noise.
63
+
64
+ Args:
65
+ tie_data: Time Interval Error data in seconds.
66
+ sample_rate: Sample rate of TIE data (edges per second).
67
+ window: Window function ("hann", "hamming", "blackman", "none").
68
+ detrend: Remove linear trend from data before FFT.
69
+ n_peaks: Number of peaks to identify.
70
+
71
+ Returns:
72
+ JitterSpectrumResult with frequency analysis.
73
+
74
+ Example:
75
+ >>> result = jitter_spectrum(tie_data, sample_rate=1e9)
76
+ >>> for freq, mag in result.peaks:
77
+ ... print(f"{freq/1e3:.1f} kHz: {mag*1e12:.2f} ps")
78
+
79
+ References:
80
+ IEEE 2414-2020 Section 6.8
81
+ """
82
+ valid_data = tie_data[~np.isnan(tie_data)]
83
+ n = len(valid_data)
84
+
85
+ if n < 16:
86
+ return JitterSpectrumResult(
87
+ frequencies=np.array([]),
88
+ magnitude=np.array([]),
89
+ magnitude_db=np.array([]),
90
+ dominant_frequency=None,
91
+ dominant_magnitude=None,
92
+ noise_floor=0.0,
93
+ peaks=[],
94
+ )
95
+
96
+ # Detrend if requested
97
+ if detrend:
98
+ # Remove linear trend
99
+ x = np.arange(n)
100
+ slope, intercept = np.polyfit(x, valid_data, 1)
101
+ data_detrended = valid_data - (slope * x + intercept)
102
+ else:
103
+ data_detrended = valid_data - np.mean(valid_data)
104
+
105
+ # Apply window
106
+ if window == "hann":
107
+ win = np.hanning(n)
108
+ elif window == "hamming":
109
+ win = np.hamming(n)
110
+ elif window == "blackman":
111
+ win = np.blackman(n)
112
+ else:
113
+ win = np.ones(n)
114
+
115
+ # Compensate for window power loss
116
+ window_factor = np.sqrt(np.mean(win**2))
117
+ data_windowed = data_detrended * win
118
+
119
+ # Zero-pad to next power of 2
120
+ nfft = int(2 ** np.ceil(np.log2(n)))
121
+
122
+ # Compute FFT
123
+ spectrum = np.fft.rfft(data_windowed, n=nfft)
124
+ frequencies = np.fft.rfftfreq(nfft, d=1.0 / sample_rate)
125
+
126
+ # Calculate magnitude spectrum
127
+ # Scale for proper amplitude: 2/N for single-sided, compensate for window
128
+ magnitude = np.abs(spectrum) * 2 / n / window_factor
129
+
130
+ # Convert to dB (relative to 1 ps = 1e-12 s)
131
+ reference = 1e-12 # 1 ps
132
+ magnitude_db = 20 * np.log10(magnitude / reference + 1e-20)
133
+
134
+ # Estimate noise floor (median of spectrum)
135
+ noise_floor = float(np.median(magnitude))
136
+
137
+ # Find peaks
138
+ peaks = identify_periodic_components(
139
+ frequencies,
140
+ magnitude,
141
+ n_peaks=n_peaks,
142
+ min_height=noise_floor * 3,
143
+ )
144
+
145
+ # Get dominant component
146
+ if len(peaks) > 0:
147
+ dominant_frequency = peaks[0][0]
148
+ dominant_magnitude = peaks[0][1]
149
+ else:
150
+ dominant_frequency = None
151
+ dominant_magnitude = None
152
+
153
+ return JitterSpectrumResult(
154
+ frequencies=frequencies,
155
+ magnitude=magnitude,
156
+ magnitude_db=magnitude_db,
157
+ dominant_frequency=dominant_frequency,
158
+ dominant_magnitude=dominant_magnitude,
159
+ noise_floor=noise_floor,
160
+ peaks=peaks,
161
+ )
162
+
163
+
164
+ def identify_periodic_components(
165
+ frequencies: NDArray[np.float64],
166
+ magnitude: NDArray[np.float64],
167
+ *,
168
+ n_peaks: int = 10,
169
+ min_height: float | None = None,
170
+ min_distance: int = 3,
171
+ ) -> list[tuple[float, float]]:
172
+ """Identify periodic components from jitter spectrum.
173
+
174
+ Finds peaks in the magnitude spectrum that indicate periodic
175
+ jitter sources.
176
+
177
+ Args:
178
+ frequencies: Frequency array in Hz.
179
+ magnitude: Magnitude spectrum.
180
+ n_peaks: Maximum number of peaks to return.
181
+ min_height: Minimum peak height (default: 3x median).
182
+ min_distance: Minimum distance between peaks in bins.
183
+
184
+ Returns:
185
+ List of (frequency, magnitude) tuples, sorted by magnitude.
186
+ """
187
+ if len(magnitude) < 3:
188
+ return []
189
+
190
+ min_height_value: float
191
+ min_height_value = float(np.median(magnitude) * 3) if min_height is None else min_height
192
+
193
+ # Find peaks
194
+ peak_indices, _properties = find_peaks(
195
+ magnitude,
196
+ height=min_height_value,
197
+ distance=min_distance,
198
+ )
199
+
200
+ if len(peak_indices) == 0:
201
+ return []
202
+
203
+ # Get peak heights
204
+ peak_heights = magnitude[peak_indices]
205
+
206
+ # Sort by magnitude (descending)
207
+ sorted_order = np.argsort(peak_heights)[::-1]
208
+ sorted_indices = peak_indices[sorted_order][:n_peaks]
209
+
210
+ # Build result list
211
+ peaks = [(float(frequencies[idx]), float(magnitude[idx])) for idx in sorted_indices]
212
+
213
+ return peaks
214
+
215
+
216
+ __all__ = [
217
+ "JitterSpectrumResult",
218
+ "identify_periodic_components",
219
+ "jitter_spectrum",
220
+ ]
@@ -0,0 +1,40 @@
1
+ """Waveform measurements namespace.
2
+
3
+ This module provides a namespace for measurement functions to support:
4
+ from oscura.analyzers import measurements
5
+ measurements.rise_time(trace)
6
+
7
+ Re-exports waveform measurement functions.
8
+ """
9
+
10
+ from oscura.analyzers.waveform.measurements import (
11
+ amplitude,
12
+ duty_cycle,
13
+ fall_time,
14
+ frequency,
15
+ mean,
16
+ measure,
17
+ overshoot,
18
+ period,
19
+ preshoot,
20
+ pulse_width,
21
+ rise_time,
22
+ rms,
23
+ undershoot,
24
+ )
25
+
26
+ __all__ = [
27
+ "amplitude",
28
+ "duty_cycle",
29
+ "fall_time",
30
+ "frequency",
31
+ "mean",
32
+ "measure",
33
+ "overshoot",
34
+ "period",
35
+ "preshoot",
36
+ "pulse_width",
37
+ "rise_time",
38
+ "rms",
39
+ "undershoot",
40
+ ]