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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (465) hide show
  1. oscura/__init__.py +813 -8
  2. oscura/__main__.py +392 -0
  3. oscura/analyzers/__init__.py +37 -0
  4. oscura/analyzers/digital/__init__.py +177 -0
  5. oscura/analyzers/digital/bus.py +691 -0
  6. oscura/analyzers/digital/clock.py +805 -0
  7. oscura/analyzers/digital/correlation.py +720 -0
  8. oscura/analyzers/digital/edges.py +632 -0
  9. oscura/analyzers/digital/extraction.py +413 -0
  10. oscura/analyzers/digital/quality.py +878 -0
  11. oscura/analyzers/digital/signal_quality.py +877 -0
  12. oscura/analyzers/digital/thresholds.py +708 -0
  13. oscura/analyzers/digital/timing.py +1104 -0
  14. oscura/analyzers/eye/__init__.py +46 -0
  15. oscura/analyzers/eye/diagram.py +434 -0
  16. oscura/analyzers/eye/metrics.py +555 -0
  17. oscura/analyzers/jitter/__init__.py +83 -0
  18. oscura/analyzers/jitter/ber.py +333 -0
  19. oscura/analyzers/jitter/decomposition.py +759 -0
  20. oscura/analyzers/jitter/measurements.py +413 -0
  21. oscura/analyzers/jitter/spectrum.py +220 -0
  22. oscura/analyzers/measurements.py +40 -0
  23. oscura/analyzers/packet/__init__.py +171 -0
  24. oscura/analyzers/packet/daq.py +1077 -0
  25. oscura/analyzers/packet/metrics.py +437 -0
  26. oscura/analyzers/packet/parser.py +327 -0
  27. oscura/analyzers/packet/payload.py +2156 -0
  28. oscura/analyzers/packet/payload_analysis.py +1312 -0
  29. oscura/analyzers/packet/payload_extraction.py +236 -0
  30. oscura/analyzers/packet/payload_patterns.py +670 -0
  31. oscura/analyzers/packet/stream.py +359 -0
  32. oscura/analyzers/patterns/__init__.py +266 -0
  33. oscura/analyzers/patterns/clustering.py +1036 -0
  34. oscura/analyzers/patterns/discovery.py +539 -0
  35. oscura/analyzers/patterns/learning.py +797 -0
  36. oscura/analyzers/patterns/matching.py +1091 -0
  37. oscura/analyzers/patterns/periodic.py +650 -0
  38. oscura/analyzers/patterns/sequences.py +767 -0
  39. oscura/analyzers/power/__init__.py +116 -0
  40. oscura/analyzers/power/ac_power.py +391 -0
  41. oscura/analyzers/power/basic.py +383 -0
  42. oscura/analyzers/power/conduction.py +314 -0
  43. oscura/analyzers/power/efficiency.py +297 -0
  44. oscura/analyzers/power/ripple.py +356 -0
  45. oscura/analyzers/power/soa.py +372 -0
  46. oscura/analyzers/power/switching.py +479 -0
  47. oscura/analyzers/protocol/__init__.py +150 -0
  48. oscura/analyzers/protocols/__init__.py +150 -0
  49. oscura/analyzers/protocols/base.py +500 -0
  50. oscura/analyzers/protocols/can.py +620 -0
  51. oscura/analyzers/protocols/can_fd.py +448 -0
  52. oscura/analyzers/protocols/flexray.py +405 -0
  53. oscura/analyzers/protocols/hdlc.py +399 -0
  54. oscura/analyzers/protocols/i2c.py +368 -0
  55. oscura/analyzers/protocols/i2s.py +296 -0
  56. oscura/analyzers/protocols/jtag.py +393 -0
  57. oscura/analyzers/protocols/lin.py +445 -0
  58. oscura/analyzers/protocols/manchester.py +333 -0
  59. oscura/analyzers/protocols/onewire.py +501 -0
  60. oscura/analyzers/protocols/spi.py +334 -0
  61. oscura/analyzers/protocols/swd.py +325 -0
  62. oscura/analyzers/protocols/uart.py +393 -0
  63. oscura/analyzers/protocols/usb.py +495 -0
  64. oscura/analyzers/signal_integrity/__init__.py +63 -0
  65. oscura/analyzers/signal_integrity/embedding.py +294 -0
  66. oscura/analyzers/signal_integrity/equalization.py +370 -0
  67. oscura/analyzers/signal_integrity/sparams.py +484 -0
  68. oscura/analyzers/spectral/__init__.py +53 -0
  69. oscura/analyzers/spectral/chunked.py +273 -0
  70. oscura/analyzers/spectral/chunked_fft.py +571 -0
  71. oscura/analyzers/spectral/chunked_wavelet.py +391 -0
  72. oscura/analyzers/spectral/fft.py +92 -0
  73. oscura/analyzers/statistical/__init__.py +250 -0
  74. oscura/analyzers/statistical/checksum.py +923 -0
  75. oscura/analyzers/statistical/chunked_corr.py +228 -0
  76. oscura/analyzers/statistical/classification.py +778 -0
  77. oscura/analyzers/statistical/entropy.py +1113 -0
  78. oscura/analyzers/statistical/ngrams.py +614 -0
  79. oscura/analyzers/statistics/__init__.py +119 -0
  80. oscura/analyzers/statistics/advanced.py +885 -0
  81. oscura/analyzers/statistics/basic.py +263 -0
  82. oscura/analyzers/statistics/correlation.py +630 -0
  83. oscura/analyzers/statistics/distribution.py +298 -0
  84. oscura/analyzers/statistics/outliers.py +463 -0
  85. oscura/analyzers/statistics/streaming.py +93 -0
  86. oscura/analyzers/statistics/trend.py +520 -0
  87. oscura/analyzers/validation.py +598 -0
  88. oscura/analyzers/waveform/__init__.py +36 -0
  89. oscura/analyzers/waveform/measurements.py +943 -0
  90. oscura/analyzers/waveform/measurements_with_uncertainty.py +371 -0
  91. oscura/analyzers/waveform/spectral.py +1689 -0
  92. oscura/analyzers/waveform/wavelets.py +298 -0
  93. oscura/api/__init__.py +62 -0
  94. oscura/api/dsl.py +538 -0
  95. oscura/api/fluent.py +571 -0
  96. oscura/api/operators.py +498 -0
  97. oscura/api/optimization.py +392 -0
  98. oscura/api/profiling.py +396 -0
  99. oscura/automotive/__init__.py +73 -0
  100. oscura/automotive/can/__init__.py +52 -0
  101. oscura/automotive/can/analysis.py +356 -0
  102. oscura/automotive/can/checksum.py +250 -0
  103. oscura/automotive/can/correlation.py +212 -0
  104. oscura/automotive/can/discovery.py +355 -0
  105. oscura/automotive/can/message_wrapper.py +375 -0
  106. oscura/automotive/can/models.py +385 -0
  107. oscura/automotive/can/patterns.py +381 -0
  108. oscura/automotive/can/session.py +452 -0
  109. oscura/automotive/can/state_machine.py +300 -0
  110. oscura/automotive/can/stimulus_response.py +461 -0
  111. oscura/automotive/dbc/__init__.py +15 -0
  112. oscura/automotive/dbc/generator.py +156 -0
  113. oscura/automotive/dbc/parser.py +146 -0
  114. oscura/automotive/dtc/__init__.py +30 -0
  115. oscura/automotive/dtc/database.py +3036 -0
  116. oscura/automotive/j1939/__init__.py +14 -0
  117. oscura/automotive/j1939/decoder.py +745 -0
  118. oscura/automotive/loaders/__init__.py +35 -0
  119. oscura/automotive/loaders/asc.py +98 -0
  120. oscura/automotive/loaders/blf.py +77 -0
  121. oscura/automotive/loaders/csv_can.py +136 -0
  122. oscura/automotive/loaders/dispatcher.py +136 -0
  123. oscura/automotive/loaders/mdf.py +331 -0
  124. oscura/automotive/loaders/pcap.py +132 -0
  125. oscura/automotive/obd/__init__.py +14 -0
  126. oscura/automotive/obd/decoder.py +707 -0
  127. oscura/automotive/uds/__init__.py +48 -0
  128. oscura/automotive/uds/decoder.py +265 -0
  129. oscura/automotive/uds/models.py +64 -0
  130. oscura/automotive/visualization.py +369 -0
  131. oscura/batch/__init__.py +55 -0
  132. oscura/batch/advanced.py +627 -0
  133. oscura/batch/aggregate.py +300 -0
  134. oscura/batch/analyze.py +139 -0
  135. oscura/batch/logging.py +487 -0
  136. oscura/batch/metrics.py +556 -0
  137. oscura/builders/__init__.py +41 -0
  138. oscura/builders/signal_builder.py +1131 -0
  139. oscura/cli/__init__.py +14 -0
  140. oscura/cli/batch.py +339 -0
  141. oscura/cli/characterize.py +273 -0
  142. oscura/cli/compare.py +775 -0
  143. oscura/cli/decode.py +551 -0
  144. oscura/cli/main.py +247 -0
  145. oscura/cli/shell.py +350 -0
  146. oscura/comparison/__init__.py +66 -0
  147. oscura/comparison/compare.py +397 -0
  148. oscura/comparison/golden.py +487 -0
  149. oscura/comparison/limits.py +391 -0
  150. oscura/comparison/mask.py +434 -0
  151. oscura/comparison/trace_diff.py +30 -0
  152. oscura/comparison/visualization.py +481 -0
  153. oscura/compliance/__init__.py +70 -0
  154. oscura/compliance/advanced.py +756 -0
  155. oscura/compliance/masks.py +363 -0
  156. oscura/compliance/reporting.py +483 -0
  157. oscura/compliance/testing.py +298 -0
  158. oscura/component/__init__.py +38 -0
  159. oscura/component/impedance.py +365 -0
  160. oscura/component/reactive.py +598 -0
  161. oscura/component/transmission_line.py +312 -0
  162. oscura/config/__init__.py +191 -0
  163. oscura/config/defaults.py +254 -0
  164. oscura/config/loader.py +348 -0
  165. oscura/config/memory.py +271 -0
  166. oscura/config/migration.py +458 -0
  167. oscura/config/pipeline.py +1077 -0
  168. oscura/config/preferences.py +530 -0
  169. oscura/config/protocol.py +875 -0
  170. oscura/config/schema.py +713 -0
  171. oscura/config/settings.py +420 -0
  172. oscura/config/thresholds.py +599 -0
  173. oscura/convenience.py +457 -0
  174. oscura/core/__init__.py +299 -0
  175. oscura/core/audit.py +457 -0
  176. oscura/core/backend_selector.py +405 -0
  177. oscura/core/cache.py +590 -0
  178. oscura/core/cancellation.py +439 -0
  179. oscura/core/confidence.py +225 -0
  180. oscura/core/config.py +506 -0
  181. oscura/core/correlation.py +216 -0
  182. oscura/core/cross_domain.py +422 -0
  183. oscura/core/debug.py +301 -0
  184. oscura/core/edge_cases.py +541 -0
  185. oscura/core/exceptions.py +535 -0
  186. oscura/core/gpu_backend.py +523 -0
  187. oscura/core/lazy.py +832 -0
  188. oscura/core/log_query.py +540 -0
  189. oscura/core/logging.py +931 -0
  190. oscura/core/logging_advanced.py +952 -0
  191. oscura/core/memoize.py +171 -0
  192. oscura/core/memory_check.py +274 -0
  193. oscura/core/memory_guard.py +290 -0
  194. oscura/core/memory_limits.py +336 -0
  195. oscura/core/memory_monitor.py +453 -0
  196. oscura/core/memory_progress.py +465 -0
  197. oscura/core/memory_warnings.py +315 -0
  198. oscura/core/numba_backend.py +362 -0
  199. oscura/core/performance.py +352 -0
  200. oscura/core/progress.py +524 -0
  201. oscura/core/provenance.py +358 -0
  202. oscura/core/results.py +331 -0
  203. oscura/core/types.py +504 -0
  204. oscura/core/uncertainty.py +383 -0
  205. oscura/discovery/__init__.py +52 -0
  206. oscura/discovery/anomaly_detector.py +672 -0
  207. oscura/discovery/auto_decoder.py +415 -0
  208. oscura/discovery/comparison.py +497 -0
  209. oscura/discovery/quality_validator.py +528 -0
  210. oscura/discovery/signal_detector.py +769 -0
  211. oscura/dsl/__init__.py +73 -0
  212. oscura/dsl/commands.py +246 -0
  213. oscura/dsl/interpreter.py +455 -0
  214. oscura/dsl/parser.py +689 -0
  215. oscura/dsl/repl.py +172 -0
  216. oscura/exceptions.py +59 -0
  217. oscura/exploratory/__init__.py +111 -0
  218. oscura/exploratory/error_recovery.py +642 -0
  219. oscura/exploratory/fuzzy.py +513 -0
  220. oscura/exploratory/fuzzy_advanced.py +786 -0
  221. oscura/exploratory/legacy.py +831 -0
  222. oscura/exploratory/parse.py +358 -0
  223. oscura/exploratory/recovery.py +275 -0
  224. oscura/exploratory/sync.py +382 -0
  225. oscura/exploratory/unknown.py +707 -0
  226. oscura/export/__init__.py +25 -0
  227. oscura/export/wireshark/README.md +265 -0
  228. oscura/export/wireshark/__init__.py +47 -0
  229. oscura/export/wireshark/generator.py +312 -0
  230. oscura/export/wireshark/lua_builder.py +159 -0
  231. oscura/export/wireshark/templates/dissector.lua.j2 +92 -0
  232. oscura/export/wireshark/type_mapping.py +165 -0
  233. oscura/export/wireshark/validator.py +105 -0
  234. oscura/exporters/__init__.py +94 -0
  235. oscura/exporters/csv.py +303 -0
  236. oscura/exporters/exporters.py +44 -0
  237. oscura/exporters/hdf5.py +219 -0
  238. oscura/exporters/html_export.py +701 -0
  239. oscura/exporters/json_export.py +291 -0
  240. oscura/exporters/markdown_export.py +367 -0
  241. oscura/exporters/matlab_export.py +354 -0
  242. oscura/exporters/npz_export.py +219 -0
  243. oscura/exporters/spice_export.py +210 -0
  244. oscura/extensibility/__init__.py +131 -0
  245. oscura/extensibility/docs.py +752 -0
  246. oscura/extensibility/extensions.py +1125 -0
  247. oscura/extensibility/logging.py +259 -0
  248. oscura/extensibility/measurements.py +485 -0
  249. oscura/extensibility/plugins.py +414 -0
  250. oscura/extensibility/registry.py +346 -0
  251. oscura/extensibility/templates.py +913 -0
  252. oscura/extensibility/validation.py +651 -0
  253. oscura/filtering/__init__.py +89 -0
  254. oscura/filtering/base.py +563 -0
  255. oscura/filtering/convenience.py +564 -0
  256. oscura/filtering/design.py +725 -0
  257. oscura/filtering/filters.py +32 -0
  258. oscura/filtering/introspection.py +605 -0
  259. oscura/guidance/__init__.py +24 -0
  260. oscura/guidance/recommender.py +429 -0
  261. oscura/guidance/wizard.py +518 -0
  262. oscura/inference/__init__.py +251 -0
  263. oscura/inference/active_learning/README.md +153 -0
  264. oscura/inference/active_learning/__init__.py +38 -0
  265. oscura/inference/active_learning/lstar.py +257 -0
  266. oscura/inference/active_learning/observation_table.py +230 -0
  267. oscura/inference/active_learning/oracle.py +78 -0
  268. oscura/inference/active_learning/teachers/__init__.py +15 -0
  269. oscura/inference/active_learning/teachers/simulator.py +192 -0
  270. oscura/inference/adaptive_tuning.py +453 -0
  271. oscura/inference/alignment.py +653 -0
  272. oscura/inference/bayesian.py +943 -0
  273. oscura/inference/binary.py +1016 -0
  274. oscura/inference/crc_reverse.py +711 -0
  275. oscura/inference/logic.py +288 -0
  276. oscura/inference/message_format.py +1305 -0
  277. oscura/inference/protocol.py +417 -0
  278. oscura/inference/protocol_dsl.py +1084 -0
  279. oscura/inference/protocol_library.py +1230 -0
  280. oscura/inference/sequences.py +809 -0
  281. oscura/inference/signal_intelligence.py +1509 -0
  282. oscura/inference/spectral.py +215 -0
  283. oscura/inference/state_machine.py +634 -0
  284. oscura/inference/stream.py +918 -0
  285. oscura/integrations/__init__.py +59 -0
  286. oscura/integrations/llm.py +1827 -0
  287. oscura/jupyter/__init__.py +32 -0
  288. oscura/jupyter/display.py +268 -0
  289. oscura/jupyter/magic.py +334 -0
  290. oscura/loaders/__init__.py +526 -0
  291. oscura/loaders/binary.py +69 -0
  292. oscura/loaders/configurable.py +1255 -0
  293. oscura/loaders/csv.py +26 -0
  294. oscura/loaders/csv_loader.py +473 -0
  295. oscura/loaders/hdf5.py +9 -0
  296. oscura/loaders/hdf5_loader.py +510 -0
  297. oscura/loaders/lazy.py +370 -0
  298. oscura/loaders/mmap_loader.py +583 -0
  299. oscura/loaders/numpy_loader.py +436 -0
  300. oscura/loaders/pcap.py +432 -0
  301. oscura/loaders/preprocessing.py +368 -0
  302. oscura/loaders/rigol.py +287 -0
  303. oscura/loaders/sigrok.py +321 -0
  304. oscura/loaders/tdms.py +367 -0
  305. oscura/loaders/tektronix.py +711 -0
  306. oscura/loaders/validation.py +584 -0
  307. oscura/loaders/vcd.py +464 -0
  308. oscura/loaders/wav.py +233 -0
  309. oscura/math/__init__.py +45 -0
  310. oscura/math/arithmetic.py +824 -0
  311. oscura/math/interpolation.py +413 -0
  312. oscura/onboarding/__init__.py +39 -0
  313. oscura/onboarding/help.py +498 -0
  314. oscura/onboarding/tutorials.py +405 -0
  315. oscura/onboarding/wizard.py +466 -0
  316. oscura/optimization/__init__.py +19 -0
  317. oscura/optimization/parallel.py +440 -0
  318. oscura/optimization/search.py +532 -0
  319. oscura/pipeline/__init__.py +43 -0
  320. oscura/pipeline/base.py +338 -0
  321. oscura/pipeline/composition.py +242 -0
  322. oscura/pipeline/parallel.py +448 -0
  323. oscura/pipeline/pipeline.py +375 -0
  324. oscura/pipeline/reverse_engineering.py +1119 -0
  325. oscura/plugins/__init__.py +122 -0
  326. oscura/plugins/base.py +272 -0
  327. oscura/plugins/cli.py +497 -0
  328. oscura/plugins/discovery.py +411 -0
  329. oscura/plugins/isolation.py +418 -0
  330. oscura/plugins/lifecycle.py +959 -0
  331. oscura/plugins/manager.py +493 -0
  332. oscura/plugins/registry.py +421 -0
  333. oscura/plugins/versioning.py +372 -0
  334. oscura/py.typed +0 -0
  335. oscura/quality/__init__.py +65 -0
  336. oscura/quality/ensemble.py +740 -0
  337. oscura/quality/explainer.py +338 -0
  338. oscura/quality/scoring.py +616 -0
  339. oscura/quality/warnings.py +456 -0
  340. oscura/reporting/__init__.py +248 -0
  341. oscura/reporting/advanced.py +1234 -0
  342. oscura/reporting/analyze.py +448 -0
  343. oscura/reporting/argument_preparer.py +596 -0
  344. oscura/reporting/auto_report.py +507 -0
  345. oscura/reporting/batch.py +615 -0
  346. oscura/reporting/chart_selection.py +223 -0
  347. oscura/reporting/comparison.py +330 -0
  348. oscura/reporting/config.py +615 -0
  349. oscura/reporting/content/__init__.py +39 -0
  350. oscura/reporting/content/executive.py +127 -0
  351. oscura/reporting/content/filtering.py +191 -0
  352. oscura/reporting/content/minimal.py +257 -0
  353. oscura/reporting/content/verbosity.py +162 -0
  354. oscura/reporting/core.py +508 -0
  355. oscura/reporting/core_formats/__init__.py +17 -0
  356. oscura/reporting/core_formats/multi_format.py +210 -0
  357. oscura/reporting/engine.py +836 -0
  358. oscura/reporting/export.py +366 -0
  359. oscura/reporting/formatting/__init__.py +129 -0
  360. oscura/reporting/formatting/emphasis.py +81 -0
  361. oscura/reporting/formatting/numbers.py +403 -0
  362. oscura/reporting/formatting/standards.py +55 -0
  363. oscura/reporting/formatting.py +466 -0
  364. oscura/reporting/html.py +578 -0
  365. oscura/reporting/index.py +590 -0
  366. oscura/reporting/multichannel.py +296 -0
  367. oscura/reporting/output.py +379 -0
  368. oscura/reporting/pdf.py +373 -0
  369. oscura/reporting/plots.py +731 -0
  370. oscura/reporting/pptx_export.py +360 -0
  371. oscura/reporting/renderers/__init__.py +11 -0
  372. oscura/reporting/renderers/pdf.py +94 -0
  373. oscura/reporting/sections.py +471 -0
  374. oscura/reporting/standards.py +680 -0
  375. oscura/reporting/summary_generator.py +368 -0
  376. oscura/reporting/tables.py +397 -0
  377. oscura/reporting/template_system.py +724 -0
  378. oscura/reporting/templates/__init__.py +15 -0
  379. oscura/reporting/templates/definition.py +205 -0
  380. oscura/reporting/templates/index.html +649 -0
  381. oscura/reporting/templates/index.md +173 -0
  382. oscura/schemas/__init__.py +158 -0
  383. oscura/schemas/bus_configuration.json +322 -0
  384. oscura/schemas/device_mapping.json +182 -0
  385. oscura/schemas/packet_format.json +418 -0
  386. oscura/schemas/protocol_definition.json +363 -0
  387. oscura/search/__init__.py +16 -0
  388. oscura/search/anomaly.py +292 -0
  389. oscura/search/context.py +149 -0
  390. oscura/search/pattern.py +160 -0
  391. oscura/session/__init__.py +34 -0
  392. oscura/session/annotations.py +289 -0
  393. oscura/session/history.py +313 -0
  394. oscura/session/session.py +445 -0
  395. oscura/streaming/__init__.py +43 -0
  396. oscura/streaming/chunked.py +611 -0
  397. oscura/streaming/progressive.py +393 -0
  398. oscura/streaming/realtime.py +622 -0
  399. oscura/testing/__init__.py +54 -0
  400. oscura/testing/synthetic.py +808 -0
  401. oscura/triggering/__init__.py +68 -0
  402. oscura/triggering/base.py +229 -0
  403. oscura/triggering/edge.py +353 -0
  404. oscura/triggering/pattern.py +344 -0
  405. oscura/triggering/pulse.py +581 -0
  406. oscura/triggering/window.py +453 -0
  407. oscura/ui/__init__.py +48 -0
  408. oscura/ui/formatters.py +526 -0
  409. oscura/ui/progressive_display.py +340 -0
  410. oscura/utils/__init__.py +99 -0
  411. oscura/utils/autodetect.py +338 -0
  412. oscura/utils/buffer.py +389 -0
  413. oscura/utils/lazy.py +407 -0
  414. oscura/utils/lazy_imports.py +147 -0
  415. oscura/utils/memory.py +836 -0
  416. oscura/utils/memory_advanced.py +1326 -0
  417. oscura/utils/memory_extensions.py +465 -0
  418. oscura/utils/progressive.py +352 -0
  419. oscura/utils/windowing.py +362 -0
  420. oscura/visualization/__init__.py +321 -0
  421. oscura/visualization/accessibility.py +526 -0
  422. oscura/visualization/annotations.py +374 -0
  423. oscura/visualization/axis_scaling.py +305 -0
  424. oscura/visualization/colors.py +453 -0
  425. oscura/visualization/digital.py +337 -0
  426. oscura/visualization/eye.py +420 -0
  427. oscura/visualization/histogram.py +281 -0
  428. oscura/visualization/interactive.py +858 -0
  429. oscura/visualization/jitter.py +702 -0
  430. oscura/visualization/keyboard.py +394 -0
  431. oscura/visualization/layout.py +365 -0
  432. oscura/visualization/optimization.py +1028 -0
  433. oscura/visualization/palettes.py +446 -0
  434. oscura/visualization/plot.py +92 -0
  435. oscura/visualization/power.py +290 -0
  436. oscura/visualization/power_extended.py +626 -0
  437. oscura/visualization/presets.py +467 -0
  438. oscura/visualization/protocols.py +932 -0
  439. oscura/visualization/render.py +207 -0
  440. oscura/visualization/rendering.py +444 -0
  441. oscura/visualization/reverse_engineering.py +791 -0
  442. oscura/visualization/signal_integrity.py +808 -0
  443. oscura/visualization/specialized.py +553 -0
  444. oscura/visualization/spectral.py +811 -0
  445. oscura/visualization/styles.py +381 -0
  446. oscura/visualization/thumbnails.py +311 -0
  447. oscura/visualization/time_axis.py +351 -0
  448. oscura/visualization/waveform.py +367 -0
  449. oscura/workflow/__init__.py +13 -0
  450. oscura/workflow/dag.py +377 -0
  451. oscura/workflows/__init__.py +58 -0
  452. oscura/workflows/compliance.py +280 -0
  453. oscura/workflows/digital.py +272 -0
  454. oscura/workflows/multi_trace.py +502 -0
  455. oscura/workflows/power.py +178 -0
  456. oscura/workflows/protocol.py +492 -0
  457. oscura/workflows/reverse_engineering.py +639 -0
  458. oscura/workflows/signal_integrity.py +227 -0
  459. oscura-0.1.1.dist-info/METADATA +300 -0
  460. oscura-0.1.1.dist-info/RECORD +463 -0
  461. oscura-0.1.1.dist-info/entry_points.txt +2 -0
  462. {oscura-0.0.1.dist-info → oscura-0.1.1.dist-info}/licenses/LICENSE +1 -1
  463. oscura-0.0.1.dist-info/METADATA +0 -63
  464. oscura-0.0.1.dist-info/RECORD +0 -5
  465. {oscura-0.0.1.dist-info → oscura-0.1.1.dist-info}/WHEEL +0 -0
@@ -0,0 +1,598 @@
1
+ """Signal validation and suitability checking for measurements.
2
+
3
+ This module provides helper functions to determine whether a signal is suitable
4
+ for specific measurements before attempting them. This helps avoid NaN results
5
+ and provides better user feedback.
6
+
7
+ Example:
8
+ >>> from oscura.analyzers.validation import is_suitable_for_frequency, get_valid_measurements
9
+ >>> suitable, reason = is_suitable_for_frequency(trace)
10
+ >>> if suitable:
11
+ ... freq = frequency(trace)
12
+ >>> valid_measurements = get_valid_measurements(trace)
13
+ >>> print(f"Applicable measurements: {', '.join(valid_measurements)}")
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ from typing import TYPE_CHECKING
19
+
20
+ import numpy as np
21
+
22
+ if TYPE_CHECKING:
23
+ from oscura.core.types import WaveformTrace
24
+
25
+
26
+ def is_suitable_for_frequency_measurement(trace: WaveformTrace) -> tuple[bool, str]:
27
+ """Check if trace is suitable for frequency measurement.
28
+
29
+ Args:
30
+ trace: Input waveform trace.
31
+
32
+ Returns:
33
+ Tuple of (is_suitable, reason). If not suitable, reason explains why.
34
+
35
+ Example:
36
+ >>> suitable, reason = is_suitable_for_frequency_measurement(trace)
37
+ >>> if suitable:
38
+ ... freq = frequency(trace)
39
+ ... else:
40
+ ... print(f"Cannot measure frequency: {reason}")
41
+ """
42
+ from oscura.analyzers.waveform.measurements import _find_edges
43
+
44
+ data = trace.data
45
+ n = len(data)
46
+
47
+ # Check minimum samples
48
+ if n < 3:
49
+ return False, f"Insufficient samples ({n} < 3)"
50
+
51
+ # Check for variation (DC signal)
52
+ if np.std(data) < 1e-12:
53
+ return False, "Signal has no variation (DC or constant)"
54
+
55
+ # Check for edges
56
+ rising_edges = _find_edges(trace, "rising")
57
+ if len(rising_edges) < 2:
58
+ return (
59
+ False,
60
+ f"Insufficient edges for periodic measurement (found {len(rising_edges)} rising edges, need at least 2)",
61
+ )
62
+
63
+ # Check period consistency (is it periodic?)
64
+ if len(rising_edges) >= 3:
65
+ edge_times = rising_edges * trace.metadata.time_base
66
+ periods = np.diff(edge_times)
67
+ period_cv = np.std(periods) / np.mean(periods) if np.mean(periods) > 0 else float("inf")
68
+
69
+ if period_cv > 0.2:
70
+ return (
71
+ False,
72
+ f"Signal is not periodic (period variation: {period_cv * 100:.1f}% > 20%)",
73
+ )
74
+
75
+ return True, "Signal is suitable for frequency measurement"
76
+
77
+
78
+ def is_suitable_for_duty_cycle_measurement(trace: WaveformTrace) -> tuple[bool, str]:
79
+ """Check if trace is suitable for duty cycle measurement.
80
+
81
+ Args:
82
+ trace: Input waveform trace.
83
+
84
+ Returns:
85
+ Tuple of (is_suitable, reason).
86
+
87
+ Example:
88
+ >>> suitable, reason = is_suitable_for_duty_cycle_measurement(trace)
89
+ >>> if suitable:
90
+ ... dc = duty_cycle(trace)
91
+ """
92
+ from oscura.analyzers.waveform.measurements import _find_edges
93
+
94
+ # Check if suitable for frequency first (duty cycle needs periodic signal)
95
+ freq_suitable, freq_reason = is_suitable_for_frequency_measurement(trace)
96
+ if not freq_suitable:
97
+ return False, freq_reason
98
+
99
+ # Need both rising and falling edges
100
+ rising = _find_edges(trace, "rising")
101
+ falling = _find_edges(trace, "falling")
102
+
103
+ if len(rising) == 0:
104
+ return False, "No rising edges detected"
105
+
106
+ if len(falling) == 0:
107
+ return False, "No falling edges detected"
108
+
109
+ return True, "Signal is suitable for duty cycle measurement"
110
+
111
+
112
+ def is_suitable_for_rise_time_measurement(trace: WaveformTrace) -> tuple[bool, str]:
113
+ """Check if trace is suitable for rise time measurement.
114
+
115
+ Args:
116
+ trace: Input waveform trace.
117
+
118
+ Returns:
119
+ Tuple of (is_suitable, reason).
120
+
121
+ Example:
122
+ >>> suitable, reason = is_suitable_for_rise_time_measurement(trace)
123
+ >>> if suitable:
124
+ ... rt = rise_time(trace)
125
+ """
126
+ from oscura.analyzers.waveform.measurements import _find_edges, _find_levels
127
+
128
+ data = trace.data
129
+ n = len(data)
130
+
131
+ if n < 3:
132
+ return False, f"Insufficient samples ({n} < 3)"
133
+
134
+ # Check for amplitude
135
+ low, high = _find_levels(data)
136
+ amplitude = high - low
137
+
138
+ if amplitude <= 0:
139
+ return False, "Signal has no amplitude (flat or inverted)"
140
+
141
+ # Check for rising edges
142
+ rising_edges = _find_edges(trace, "rising")
143
+
144
+ if len(rising_edges) == 0:
145
+ return False, "No rising edges detected"
146
+
147
+ # Check sample rate vs transition time
148
+ # Find a rising transition and count samples across it
149
+ sample_rate = trace.metadata.sample_rate
150
+ low_ref = low + 0.1 * amplitude
151
+ high_ref = low + 0.9 * amplitude
152
+
153
+ # Find first rising transition
154
+ crossings = np.where((data[:-1] < low_ref) & (data[1:] >= low_ref))[0]
155
+
156
+ if len(crossings) > 0:
157
+ idx = crossings[0]
158
+ # Count samples from 10% to 90%
159
+ remaining = data[idx:]
160
+ above_high = remaining >= high_ref
161
+
162
+ if np.any(above_high):
163
+ end_offset = np.argmax(above_high)
164
+ samples_in_transition = end_offset
165
+
166
+ if samples_in_transition < 2:
167
+ est_rise_time = samples_in_transition / sample_rate
168
+ recommended_rate = 10 / est_rise_time
169
+ return (
170
+ False,
171
+ f"Insufficient sample rate for transition (< 2 samples). "
172
+ f"Recommend sample rate > {recommended_rate:.3e} Hz",
173
+ )
174
+
175
+ return True, "Signal is suitable for rise time measurement"
176
+
177
+
178
+ def is_suitable_for_fall_time_measurement(trace: WaveformTrace) -> tuple[bool, str]:
179
+ """Check if trace is suitable for fall time measurement.
180
+
181
+ Args:
182
+ trace: Input waveform trace.
183
+
184
+ Returns:
185
+ Tuple of (is_suitable, reason).
186
+ """
187
+ from oscura.analyzers.waveform.measurements import _find_edges, _find_levels
188
+
189
+ data = trace.data
190
+ n = len(data)
191
+
192
+ if n < 3:
193
+ return False, f"Insufficient samples ({n} < 3)"
194
+
195
+ low, high = _find_levels(data)
196
+ amplitude = high - low
197
+
198
+ if amplitude <= 0:
199
+ return False, "Signal has no amplitude (flat or inverted)"
200
+
201
+ # Check for falling edges
202
+ falling_edges = _find_edges(trace, "falling")
203
+
204
+ if len(falling_edges) == 0:
205
+ return False, "No falling edges detected"
206
+
207
+ return True, "Signal is suitable for fall time measurement"
208
+
209
+
210
+ def is_suitable_for_jitter_measurement(trace: WaveformTrace) -> tuple[bool, str]:
211
+ """Check if trace is suitable for jitter measurement.
212
+
213
+ Args:
214
+ trace: Input waveform trace.
215
+
216
+ Returns:
217
+ Tuple of (is_suitable, reason).
218
+
219
+ Example:
220
+ >>> suitable, reason = is_suitable_for_jitter_measurement(trace)
221
+ >>> if suitable:
222
+ ... jitter = rms_jitter(trace)
223
+ """
224
+ from oscura.analyzers.waveform.measurements import _find_edges
225
+
226
+ # Jitter needs periodic signal
227
+ freq_suitable, freq_reason = is_suitable_for_frequency_measurement(trace)
228
+ if not freq_suitable:
229
+ return False, freq_reason
230
+
231
+ # Need at least 3 edges (2 periods minimum)
232
+ edges = _find_edges(trace, "rising")
233
+
234
+ if len(edges) < 3:
235
+ return (
236
+ False,
237
+ f"Insufficient edges for jitter measurement (found {len(edges)}, need at least 3)",
238
+ )
239
+
240
+ return True, "Signal is suitable for jitter measurement"
241
+
242
+
243
+ def get_valid_measurements(trace: WaveformTrace) -> list[str]:
244
+ """Get list of measurements that are suitable for this trace.
245
+
246
+ Analyzes the signal characteristics and returns the names of all
247
+ measurement functions that should return valid (non-NaN) results.
248
+
249
+ Args:
250
+ trace: Input waveform trace.
251
+
252
+ Returns:
253
+ List of measurement function names (without parentheses).
254
+
255
+ Example:
256
+ >>> valid = get_valid_measurements(trace)
257
+ >>> print(f"Applicable measurements: {', '.join(valid)}")
258
+ >>> # Then apply only valid measurements
259
+ >>> for meas_name in valid:
260
+ ... func = getattr(tk, meas_name)
261
+ ... result = func(trace)
262
+ """
263
+ valid = []
264
+
265
+ # These almost always work (just need data)
266
+ if len(trace.data) > 0:
267
+ valid.extend(["mean", "rms"])
268
+
269
+ if len(trace.data) >= 2:
270
+ valid.append("amplitude")
271
+
272
+ # Check edge-based measurements
273
+ suitable, _ = is_suitable_for_rise_time_measurement(trace)
274
+ if suitable:
275
+ valid.append("rise_time")
276
+
277
+ suitable, _ = is_suitable_for_fall_time_measurement(trace)
278
+ if suitable:
279
+ valid.append("fall_time")
280
+
281
+ # Check frequency/period
282
+ suitable, _ = is_suitable_for_frequency_measurement(trace)
283
+ if suitable:
284
+ valid.extend(["frequency", "period"])
285
+
286
+ # Check duty cycle
287
+ suitable, _ = is_suitable_for_duty_cycle_measurement(trace)
288
+ if suitable:
289
+ valid.append("duty_cycle")
290
+
291
+ # Check jitter
292
+ suitable, _ = is_suitable_for_jitter_measurement(trace)
293
+ if suitable:
294
+ valid.extend(["rms_jitter", "peak_to_peak_jitter"])
295
+
296
+ # Pulse width - needs edges but not necessarily periodic
297
+ from oscura.analyzers.waveform.measurements import _find_edges
298
+
299
+ rising = _find_edges(trace, "rising")
300
+ falling = _find_edges(trace, "falling")
301
+
302
+ if len(rising) > 0 and len(falling) > 0:
303
+ valid.append("pulse_width")
304
+
305
+ # Overshoot/undershoot - check amplitude
306
+ from oscura.analyzers.waveform.measurements import _find_levels
307
+
308
+ if len(trace.data) >= 3:
309
+ low, high = _find_levels(trace.data)
310
+ if high - low > 0:
311
+ valid.extend(["overshoot", "undershoot", "preshoot"])
312
+
313
+ # Slew rate - similar to rise/fall time
314
+ if "rise_time" in valid or "fall_time" in valid:
315
+ valid.append("slew_rate")
316
+
317
+ return valid
318
+
319
+
320
+ def analyze_signal_characteristics(trace: WaveformTrace) -> dict[str, bool | int | str | list[str]]:
321
+ """Perform comprehensive signal characteristic analysis.
322
+
323
+ Determines signal type, edge counts, periodicity, and recommends
324
+ applicable measurements.
325
+
326
+ Args:
327
+ trace: Input waveform trace.
328
+
329
+ Returns:
330
+ Dictionary containing:
331
+ - sufficient_samples: bool - at least 16 samples
332
+ - has_amplitude: bool - signal has variation
333
+ - has_variation: bool - standard deviation > 0
334
+ - has_edges: bool - rising or falling edges detected
335
+ - is_periodic: bool - signal appears periodic
336
+ - edge_count: int - total edges (rising + falling)
337
+ - rising_edge_count: int - number of rising edges
338
+ - falling_edge_count: int - number of falling edges
339
+ - signal_type: str - classified type (dc, periodic_digital, etc.)
340
+ - recommended_measurements: list[str] - suggested measurements
341
+
342
+ Example:
343
+ >>> chars = analyze_signal_characteristics(trace)
344
+ >>> if chars['is_periodic']:
345
+ ... print("Signal is periodic")
346
+ ... print(f"Frequency measurement recommended: {'frequency' in chars['recommended_measurements']}")
347
+ """
348
+ from oscura.analyzers.waveform.measurements import _find_edges
349
+
350
+ data = trace.data
351
+ n = len(data)
352
+
353
+ characteristics: dict[str, bool | int | str | list[str]] = {
354
+ "sufficient_samples": n >= 16,
355
+ "has_amplitude": False,
356
+ "has_variation": False,
357
+ "has_edges": False,
358
+ "is_periodic": False,
359
+ "edge_count": 0,
360
+ "rising_edge_count": 0,
361
+ "falling_edge_count": 0,
362
+ "signal_type": "unknown",
363
+ "recommended_measurements": [],
364
+ }
365
+
366
+ # Check variation
367
+ std = np.std(data)
368
+ characteristics["has_variation"] = std > 1e-12
369
+
370
+ # Check amplitude
371
+ amplitude = np.max(data) - np.min(data)
372
+ characteristics["has_amplitude"] = amplitude > 1e-12
373
+
374
+ if not characteristics["has_variation"]:
375
+ characteristics["signal_type"] = "dc"
376
+ characteristics["recommended_measurements"] = ["mean", "rms"]
377
+ return characteristics
378
+
379
+ # Count edges
380
+ rising_edges = _find_edges(trace, "rising")
381
+ falling_edges = _find_edges(trace, "falling")
382
+
383
+ rising_edge_count = len(rising_edges)
384
+ falling_edge_count = len(falling_edges)
385
+ edge_count = rising_edge_count + falling_edge_count
386
+
387
+ characteristics["rising_edge_count"] = rising_edge_count
388
+ characteristics["falling_edge_count"] = falling_edge_count
389
+ characteristics["edge_count"] = edge_count
390
+ characteristics["has_edges"] = edge_count > 0
391
+
392
+ # Check periodicity
393
+ if len(rising_edges) >= 3:
394
+ periods = np.diff(rising_edges)
395
+ period_cv = np.std(periods) / np.mean(periods) if np.mean(periods) > 0 else float("inf")
396
+
397
+ if period_cv < 0.2: # Less than 20% variation
398
+ characteristics["is_periodic"] = True
399
+
400
+ # Classify signal type
401
+ if not characteristics["has_edges"]:
402
+ # No edges - check if analog periodic
403
+ if n >= 16:
404
+ fft_result = np.abs(np.fft.rfft(data - np.mean(data)))
405
+ peak_power = np.max(fft_result[1:]) if len(fft_result) > 1 else 0
406
+ avg_power = np.mean(fft_result[1:]) if len(fft_result) > 1 else 0
407
+
408
+ if peak_power > 10 * avg_power:
409
+ characteristics["signal_type"] = "periodic_analog"
410
+ else:
411
+ characteristics["signal_type"] = "noise"
412
+ else:
413
+ characteristics["signal_type"] = "unknown"
414
+ elif characteristics["is_periodic"]:
415
+ characteristics["signal_type"] = "periodic_digital"
416
+ else:
417
+ characteristics["signal_type"] = "aperiodic_digital"
418
+
419
+ # Recommend measurements
420
+ recommended = get_valid_measurements(trace)
421
+ characteristics["recommended_measurements"] = recommended
422
+
423
+ return characteristics
424
+
425
+
426
+ def get_measurement_requirements(measurement_name: str) -> dict[str, str | int | list[str]]:
427
+ """Get requirements for a specific measurement.
428
+
429
+ Args:
430
+ measurement_name: Name of the measurement function.
431
+
432
+ Returns:
433
+ Dictionary containing:
434
+ - description: str - what the measurement computes
435
+ - min_samples: int - minimum data points needed
436
+ - required_signal_types: list[str] - suitable signal types
437
+ - required_features: list[str] - required signal features
438
+ - common_nan_causes: list[str] - common reasons for NaN
439
+
440
+ Example:
441
+ >>> reqs = get_measurement_requirements('frequency')
442
+ >>> print(f"Minimum samples: {reqs['min_samples']}")
443
+ >>> print(f"Required features: {', '.join(reqs['required_features'])}")
444
+ """
445
+ requirements = {
446
+ "frequency": {
447
+ "description": "Measures the repetition rate of a periodic signal",
448
+ "min_samples": 3,
449
+ "required_signal_types": ["periodic_digital", "periodic_analog"],
450
+ "required_features": ["edges", "periodic"],
451
+ "common_nan_causes": [
452
+ "DC signal (no transitions)",
453
+ "Aperiodic signal (< 2 edges)",
454
+ "Highly variable period (> 20% variation)",
455
+ ],
456
+ },
457
+ "period": {
458
+ "description": "Measures time between consecutive edges",
459
+ "min_samples": 3,
460
+ "required_signal_types": ["periodic_digital", "periodic_analog"],
461
+ "required_features": ["edges", "periodic"],
462
+ "common_nan_causes": [
463
+ "DC signal",
464
+ "Fewer than 2 edges detected",
465
+ "Aperiodic signal",
466
+ ],
467
+ },
468
+ "duty_cycle": {
469
+ "description": "Measures ratio of high time to period",
470
+ "min_samples": 3,
471
+ "required_signal_types": ["periodic_digital"],
472
+ "required_features": ["rising_edges", "falling_edges", "periodic"],
473
+ "common_nan_causes": [
474
+ "Non-periodic signal",
475
+ "Missing rising or falling edges",
476
+ "DC signal",
477
+ ],
478
+ },
479
+ "rise_time": {
480
+ "description": "Measures time for rising edge transition",
481
+ "min_samples": 3,
482
+ "required_signal_types": ["periodic_digital", "aperiodic_digital", "periodic_analog"],
483
+ "required_features": ["rising_edges", "amplitude"],
484
+ "common_nan_causes": [
485
+ "No rising edges",
486
+ "Insufficient sample rate",
487
+ "DC signal",
488
+ ],
489
+ },
490
+ "fall_time": {
491
+ "description": "Measures time for falling edge transition",
492
+ "min_samples": 3,
493
+ "required_signal_types": ["periodic_digital", "aperiodic_digital", "periodic_analog"],
494
+ "required_features": ["falling_edges", "amplitude"],
495
+ "common_nan_causes": [
496
+ "No falling edges",
497
+ "Insufficient sample rate",
498
+ "DC signal",
499
+ ],
500
+ },
501
+ "pulse_width": {
502
+ "description": "Measures duration of high or low pulse",
503
+ "min_samples": 3,
504
+ "required_signal_types": ["periodic_digital", "aperiodic_digital"],
505
+ "required_features": ["rising_edges", "falling_edges"],
506
+ "common_nan_causes": [
507
+ "Missing edge pairs",
508
+ "DC signal",
509
+ "Incomplete pulses",
510
+ ],
511
+ },
512
+ "amplitude": {
513
+ "description": "Measures peak-to-peak voltage",
514
+ "min_samples": 2,
515
+ "required_signal_types": ["all"],
516
+ "required_features": [],
517
+ "common_nan_causes": ["Fewer than 2 samples"],
518
+ },
519
+ "mean": {
520
+ "description": "Calculates DC level (average voltage)",
521
+ "min_samples": 1,
522
+ "required_signal_types": ["all"],
523
+ "required_features": [],
524
+ "common_nan_causes": ["No data"],
525
+ },
526
+ "rms": {
527
+ "description": "Calculates root-mean-square voltage",
528
+ "min_samples": 1,
529
+ "required_signal_types": ["all"],
530
+ "required_features": [],
531
+ "common_nan_causes": ["No data"],
532
+ },
533
+ "overshoot": {
534
+ "description": "Measures overshoot above high level",
535
+ "min_samples": 3,
536
+ "required_signal_types": ["periodic_digital", "aperiodic_digital"],
537
+ "required_features": ["amplitude"],
538
+ "common_nan_causes": ["No amplitude", "DC signal"],
539
+ },
540
+ "undershoot": {
541
+ "description": "Measures undershoot below low level",
542
+ "min_samples": 3,
543
+ "required_signal_types": ["periodic_digital", "aperiodic_digital"],
544
+ "required_features": ["amplitude"],
545
+ "common_nan_causes": ["No amplitude", "DC signal"],
546
+ },
547
+ "slew_rate": {
548
+ "description": "Measures dV/dt during transitions",
549
+ "min_samples": 3,
550
+ "required_signal_types": ["periodic_digital", "aperiodic_digital"],
551
+ "required_features": ["edges", "amplitude"],
552
+ "common_nan_causes": ["No edges", "No amplitude", "DC signal"],
553
+ },
554
+ "rms_jitter": {
555
+ "description": "Measures timing uncertainty (RMS)",
556
+ "min_samples": 3,
557
+ "required_signal_types": ["periodic_digital"],
558
+ "required_features": ["edges", "periodic"],
559
+ "common_nan_causes": [
560
+ "Fewer than 3 edges",
561
+ "Non-periodic signal",
562
+ "DC signal",
563
+ ],
564
+ },
565
+ "peak_to_peak_jitter": {
566
+ "description": "Measures peak-to-peak timing variation",
567
+ "min_samples": 3,
568
+ "required_signal_types": ["periodic_digital"],
569
+ "required_features": ["edges", "periodic"],
570
+ "common_nan_causes": [
571
+ "Fewer than 3 edges",
572
+ "Non-periodic signal",
573
+ "DC signal",
574
+ ],
575
+ },
576
+ }
577
+
578
+ default = {
579
+ "description": "Measurement not documented",
580
+ "min_samples": 1,
581
+ "required_signal_types": ["unknown"],
582
+ "required_features": [],
583
+ "common_nan_causes": ["Check measurement documentation"],
584
+ }
585
+
586
+ return requirements.get(measurement_name, default) # type: ignore[return-value]
587
+
588
+
589
+ __all__ = [
590
+ "analyze_signal_characteristics",
591
+ "get_measurement_requirements",
592
+ "get_valid_measurements",
593
+ "is_suitable_for_duty_cycle_measurement",
594
+ "is_suitable_for_fall_time_measurement",
595
+ "is_suitable_for_frequency_measurement",
596
+ "is_suitable_for_jitter_measurement",
597
+ "is_suitable_for_rise_time_measurement",
598
+ ]
@@ -0,0 +1,36 @@
1
+ """Waveform analysis module.
2
+
3
+ Provides timing and amplitude measurements for analog waveforms.
4
+ """
5
+
6
+ from oscura.analyzers.waveform.measurements import (
7
+ amplitude,
8
+ duty_cycle,
9
+ fall_time,
10
+ frequency,
11
+ mean,
12
+ measure,
13
+ overshoot,
14
+ period,
15
+ preshoot,
16
+ pulse_width,
17
+ rise_time,
18
+ rms,
19
+ undershoot,
20
+ )
21
+
22
+ __all__ = [
23
+ "amplitude",
24
+ "duty_cycle",
25
+ "fall_time",
26
+ "frequency",
27
+ "mean",
28
+ "measure",
29
+ "overshoot",
30
+ "period",
31
+ "preshoot",
32
+ "pulse_width",
33
+ "rise_time",
34
+ "rms",
35
+ "undershoot",
36
+ ]