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,68 @@
1
+ """Signal triggering and event detection module for TraceKit.
2
+
3
+ Provides oscilloscope-style triggering functionality including edge
4
+ triggering, pattern triggering, pulse width triggering, glitch detection,
5
+ runt pulse detection, and window/zone triggering.
6
+
7
+ Example:
8
+ >>> from oscura.triggering import EdgeTrigger, find_triggers
9
+ >>> trigger = EdgeTrigger(level=1.5, edge="rising")
10
+ >>> events = trigger.find_events(trace)
11
+ >>> # Or use convenience function
12
+ >>> events = find_triggers(trace, "edge", level=1.5, edge="rising")
13
+ """
14
+
15
+ from oscura.triggering.base import (
16
+ Trigger,
17
+ TriggerEvent,
18
+ find_triggers,
19
+ )
20
+ from oscura.triggering.edge import (
21
+ EdgeTrigger,
22
+ find_all_edges,
23
+ find_falling_edges,
24
+ find_rising_edges,
25
+ )
26
+ from oscura.triggering.pattern import (
27
+ PatternTrigger,
28
+ find_pattern,
29
+ )
30
+ from oscura.triggering.pulse import (
31
+ PulseWidthTrigger,
32
+ find_glitches,
33
+ find_pulses,
34
+ find_runt_pulses,
35
+ )
36
+ from oscura.triggering.window import (
37
+ WindowTrigger,
38
+ ZoneTrigger,
39
+ check_limits,
40
+ find_window_violations,
41
+ find_zone_events,
42
+ )
43
+
44
+ __all__ = [
45
+ # Edge triggering
46
+ "EdgeTrigger",
47
+ # Pattern triggering
48
+ "PatternTrigger",
49
+ # Pulse triggering
50
+ "PulseWidthTrigger",
51
+ # Base
52
+ "Trigger",
53
+ "TriggerEvent",
54
+ # Window triggering
55
+ "WindowTrigger",
56
+ "ZoneTrigger",
57
+ "check_limits",
58
+ "find_all_edges",
59
+ "find_falling_edges",
60
+ "find_glitches",
61
+ "find_pattern",
62
+ "find_pulses",
63
+ "find_rising_edges",
64
+ "find_runt_pulses",
65
+ "find_triggers",
66
+ "find_window_violations",
67
+ "find_zone_events",
68
+ ]
@@ -0,0 +1,229 @@
1
+ """Base classes and utilities for TraceKit triggering module.
2
+
3
+ Provides abstract base class for triggers and common trigger event
4
+ data structure.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from abc import ABC, abstractmethod
10
+ from dataclasses import dataclass, field
11
+ from enum import Enum
12
+ from typing import TYPE_CHECKING, Any, Literal
13
+
14
+ from oscura.core.exceptions import AnalysisError
15
+
16
+ if TYPE_CHECKING:
17
+ import numpy as np
18
+ from numpy.typing import NDArray
19
+
20
+ from oscura.core.types import DigitalTrace, WaveformTrace
21
+
22
+
23
+ class TriggerType(Enum):
24
+ """Types of trigger events."""
25
+
26
+ RISING_EDGE = "rising_edge"
27
+ FALLING_EDGE = "falling_edge"
28
+ PATTERN_MATCH = "pattern_match"
29
+ PULSE_WIDTH = "pulse_width"
30
+ GLITCH = "glitch"
31
+ RUNT = "runt"
32
+ WINDOW_ENTRY = "window_entry"
33
+ WINDOW_EXIT = "window_exit"
34
+ ZONE_VIOLATION = "zone_violation"
35
+
36
+
37
+ @dataclass
38
+ class TriggerEvent:
39
+ """Represents a detected trigger event.
40
+
41
+ Attributes:
42
+ timestamp: Time of the trigger event in seconds.
43
+ sample_index: Sample index where trigger occurred.
44
+ event_type: Type of trigger event.
45
+ level: Voltage/signal level at trigger point.
46
+ data: Additional event-specific data.
47
+ """
48
+
49
+ timestamp: float
50
+ sample_index: int
51
+ event_type: TriggerType
52
+ level: float | None = None
53
+ duration: float | None = None
54
+ data: dict[str, Any] = field(default_factory=dict)
55
+
56
+ def __repr__(self) -> str:
57
+ return (
58
+ f"TriggerEvent({self.event_type.value} at t={self.timestamp:.6e}s, "
59
+ f"sample={self.sample_index})"
60
+ )
61
+
62
+
63
+ class Trigger(ABC):
64
+ """Abstract base class for all trigger types.
65
+
66
+ Defines the common interface for finding trigger events in traces.
67
+ """
68
+
69
+ @abstractmethod
70
+ def find_events(
71
+ self,
72
+ trace: WaveformTrace | DigitalTrace,
73
+ ) -> list[TriggerEvent]:
74
+ """Find all trigger events in a trace.
75
+
76
+ Args:
77
+ trace: Input trace to search.
78
+
79
+ Returns:
80
+ List of trigger events found.
81
+ """
82
+ ...
83
+
84
+ def find_first(
85
+ self,
86
+ trace: WaveformTrace | DigitalTrace,
87
+ ) -> TriggerEvent | None:
88
+ """Find the first trigger event.
89
+
90
+ Args:
91
+ trace: Input trace to search.
92
+
93
+ Returns:
94
+ First trigger event, or None if no triggers found.
95
+ """
96
+ events = self.find_events(trace)
97
+ return events[0] if events else None
98
+
99
+ def count_events(
100
+ self,
101
+ trace: WaveformTrace | DigitalTrace,
102
+ ) -> int:
103
+ """Count trigger events.
104
+
105
+ Args:
106
+ trace: Input trace to search.
107
+
108
+ Returns:
109
+ Number of trigger events found.
110
+ """
111
+ return len(self.find_events(trace))
112
+
113
+
114
+ def find_triggers(
115
+ trace: WaveformTrace | DigitalTrace,
116
+ trigger_type: Literal["edge", "pattern", "pulse_width", "glitch", "runt", "window"],
117
+ **kwargs: Any,
118
+ ) -> list[TriggerEvent]:
119
+ """Unified function to find trigger events.
120
+
121
+ Args:
122
+ trace: Input trace to search.
123
+ trigger_type: Type of trigger to use.
124
+ **kwargs: Trigger-specific parameters.
125
+
126
+ Returns:
127
+ List of trigger events.
128
+
129
+ Raises:
130
+ AnalysisError: If unknown trigger type.
131
+
132
+ Example:
133
+ >>> events = find_triggers(trace, "edge", level=1.5, edge="rising")
134
+ >>> events = find_triggers(trace, "pulse_width", min_width=1e-6, max_width=2e-6)
135
+ >>> events = find_triggers(trace, "glitch", max_width=50e-9)
136
+ """
137
+ from oscura.triggering.edge import EdgeTrigger
138
+ from oscura.triggering.pulse import PulseWidthTrigger
139
+ from oscura.triggering.window import WindowTrigger
140
+
141
+ if trigger_type == "edge":
142
+ trigger = EdgeTrigger(
143
+ level=kwargs.get("level", 0.0),
144
+ edge=kwargs.get("edge", "rising"),
145
+ hysteresis=kwargs.get("hysteresis", 0.0),
146
+ )
147
+ elif trigger_type == "pattern":
148
+ from oscura.triggering.pattern import PatternTrigger
149
+
150
+ trigger = PatternTrigger( # type: ignore[assignment]
151
+ pattern=kwargs.get("pattern", []),
152
+ levels=kwargs.get("levels"),
153
+ )
154
+ elif trigger_type == "pulse_width":
155
+ trigger = PulseWidthTrigger( # type: ignore[assignment]
156
+ level=kwargs.get("level", 0.0),
157
+ polarity=kwargs.get("polarity", "positive"),
158
+ min_width=kwargs.get("min_width"),
159
+ max_width=kwargs.get("max_width"),
160
+ )
161
+ elif trigger_type == "glitch":
162
+ from oscura.triggering.pulse import GlitchTrigger
163
+
164
+ trigger = GlitchTrigger( # type: ignore[assignment]
165
+ level=kwargs.get("level", 0.0),
166
+ max_width=kwargs.get("max_width", 100e-9),
167
+ polarity=kwargs.get("polarity", "either"),
168
+ )
169
+ elif trigger_type == "runt":
170
+ from oscura.triggering.pulse import RuntTrigger
171
+
172
+ trigger = RuntTrigger( # type: ignore[assignment]
173
+ low_threshold=kwargs.get("low_threshold", 0.0),
174
+ high_threshold=kwargs.get("high_threshold", 1.0),
175
+ polarity=kwargs.get("polarity", "either"),
176
+ )
177
+ elif trigger_type == "window":
178
+ trigger = WindowTrigger( # type: ignore[assignment]
179
+ low_threshold=kwargs.get("low_threshold", 0.0),
180
+ high_threshold=kwargs.get("high_threshold", 1.0),
181
+ trigger_on=kwargs.get("trigger_on", "exit"),
182
+ )
183
+ else:
184
+ raise AnalysisError(f"Unknown trigger type: {trigger_type}")
185
+
186
+ return trigger.find_events(trace)
187
+
188
+
189
+ def interpolate_crossing(
190
+ data: NDArray[np.floating[Any]],
191
+ idx: int,
192
+ threshold: float,
193
+ sample_period: float,
194
+ rising: bool = True,
195
+ ) -> float:
196
+ """Interpolate exact threshold crossing time.
197
+
198
+ Args:
199
+ data: Waveform data array.
200
+ idx: Sample index near crossing.
201
+ threshold: Threshold level.
202
+ sample_period: Time between samples.
203
+ rising: True for rising edge, False for falling.
204
+
205
+ Returns:
206
+ Interpolated crossing time in seconds.
207
+ """
208
+ if idx < 0 or idx >= len(data) - 1:
209
+ return idx * sample_period
210
+
211
+ v1, v2 = data[idx], data[idx + 1]
212
+
213
+ if abs(v2 - v1) < 1e-12:
214
+ return (idx + 0.5) * sample_period
215
+
216
+ # Linear interpolation
217
+ t_offset = (threshold - v1) / (v2 - v1) * sample_period
218
+ t_offset = max(0, min(sample_period, t_offset))
219
+
220
+ return idx * sample_period + t_offset # type: ignore[no-any-return]
221
+
222
+
223
+ __all__ = [
224
+ "Trigger",
225
+ "TriggerEvent",
226
+ "TriggerType",
227
+ "find_triggers",
228
+ "interpolate_crossing",
229
+ ]
@@ -0,0 +1,353 @@
1
+ """Edge triggering for TraceKit.
2
+
3
+ Provides edge detection with configurable thresholds, hysteresis,
4
+ and edge polarity (rising, falling, or both).
5
+
6
+ Example:
7
+ >>> from oscura.triggering.edge import EdgeTrigger, find_rising_edges
8
+ >>> # Object-oriented approach
9
+ >>> trigger = EdgeTrigger(level=1.5, edge="rising", hysteresis=0.1)
10
+ >>> events = trigger.find_events(trace)
11
+ >>> # Functional approach
12
+ >>> timestamps = find_rising_edges(trace, level=1.5)
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ from typing import TYPE_CHECKING, Any, Literal
18
+
19
+ import numpy as np
20
+
21
+ from oscura.core.types import DigitalTrace, WaveformTrace
22
+ from oscura.triggering.base import (
23
+ Trigger,
24
+ TriggerEvent,
25
+ TriggerType,
26
+ interpolate_crossing,
27
+ )
28
+
29
+ if TYPE_CHECKING:
30
+ from numpy.typing import NDArray
31
+
32
+
33
+ class EdgeTrigger(Trigger):
34
+ """Edge trigger with threshold and optional hysteresis.
35
+
36
+ Detects signal crossings of a threshold level with configurable
37
+ hysteresis for noise immunity.
38
+
39
+ Attributes:
40
+ level: Trigger threshold level.
41
+ edge: Edge type - "rising", "falling", or "either".
42
+ hysteresis: Hysteresis band (Schmitt trigger style).
43
+ """
44
+
45
+ def __init__(
46
+ self,
47
+ level: float,
48
+ edge: Literal["rising", "falling", "either"] = "rising",
49
+ hysteresis: float = 0.0,
50
+ ) -> None:
51
+ """Initialize edge trigger.
52
+
53
+ Args:
54
+ level: Trigger threshold level in signal units (e.g., volts).
55
+ edge: Edge polarity to trigger on.
56
+ hysteresis: Hysteresis band width. Trigger requires signal to
57
+ cross level +/- hysteresis/2 before retriggering.
58
+ """
59
+ self.level = level
60
+ self.edge = edge
61
+ self.hysteresis = hysteresis
62
+
63
+ def find_events(
64
+ self,
65
+ trace: WaveformTrace | DigitalTrace,
66
+ ) -> list[TriggerEvent]:
67
+ """Find all edge events in the trace.
68
+
69
+ Args:
70
+ trace: Input waveform or digital trace.
71
+
72
+ Returns:
73
+ List of trigger events for each detected edge.
74
+ """
75
+ if isinstance(trace, DigitalTrace):
76
+ # For digital traces, use edge list if available
77
+ data = trace.data.astype(np.float64)
78
+ else:
79
+ data = trace.data
80
+
81
+ sample_period = trace.metadata.time_base
82
+ events: list[TriggerEvent] = []
83
+
84
+ if self.hysteresis > 0:
85
+ # Schmitt trigger mode
86
+ events = self._find_edges_with_hysteresis(data, sample_period)
87
+ else:
88
+ # Simple threshold crossing
89
+ events = self._find_edges_simple(data, sample_period)
90
+
91
+ return events
92
+
93
+ def _find_edges_simple(
94
+ self,
95
+ data: NDArray[np.floating[Any]],
96
+ sample_period: float,
97
+ ) -> list[TriggerEvent]:
98
+ """Find edges using simple threshold crossing."""
99
+ events: list[TriggerEvent] = []
100
+
101
+ below = data < self.level
102
+ above = data >= self.level
103
+
104
+ if self.edge in ("rising", "either"):
105
+ # Rising: below -> above
106
+ rising_idx = np.where(below[:-1] & above[1:])[0]
107
+ for idx in rising_idx:
108
+ timestamp = interpolate_crossing(data, idx, self.level, sample_period, rising=True)
109
+ events.append(
110
+ TriggerEvent(
111
+ timestamp=timestamp,
112
+ sample_index=int(idx),
113
+ event_type=TriggerType.RISING_EDGE,
114
+ level=float(data[idx + 1]),
115
+ )
116
+ )
117
+
118
+ if self.edge in ("falling", "either"):
119
+ # Falling: above -> below
120
+ falling_idx = np.where(above[:-1] & below[1:])[0]
121
+ for idx in falling_idx:
122
+ timestamp = interpolate_crossing(data, idx, self.level, sample_period, rising=False)
123
+ events.append(
124
+ TriggerEvent(
125
+ timestamp=timestamp,
126
+ sample_index=int(idx),
127
+ event_type=TriggerType.FALLING_EDGE,
128
+ level=float(data[idx + 1]),
129
+ )
130
+ )
131
+
132
+ # Sort by timestamp if we detected both edge types
133
+ if self.edge == "either":
134
+ events.sort(key=lambda e: e.timestamp)
135
+
136
+ return events
137
+
138
+ def _find_edges_with_hysteresis(
139
+ self,
140
+ data: NDArray[np.floating[Any]],
141
+ sample_period: float,
142
+ ) -> list[TriggerEvent]:
143
+ """Find edges using Schmitt trigger with hysteresis."""
144
+ events: list[TriggerEvent] = []
145
+
146
+ high_thresh = self.level + self.hysteresis / 2
147
+ low_thresh = self.level - self.hysteresis / 2
148
+
149
+ # State machine: track if we're currently "high" or "low"
150
+ state = "low" if data[0] < self.level else "high"
151
+
152
+ for i in range(1, len(data)):
153
+ if state == "low" and data[i] >= high_thresh:
154
+ # Rising edge detected
155
+ state = "high"
156
+ if self.edge in ("rising", "either"):
157
+ timestamp = interpolate_crossing(
158
+ data, i - 1, high_thresh, sample_period, rising=True
159
+ )
160
+ events.append(
161
+ TriggerEvent(
162
+ timestamp=timestamp,
163
+ sample_index=i,
164
+ event_type=TriggerType.RISING_EDGE,
165
+ level=float(data[i]),
166
+ )
167
+ )
168
+
169
+ elif state == "high" and data[i] <= low_thresh:
170
+ # Falling edge detected
171
+ state = "low"
172
+ if self.edge in ("falling", "either"):
173
+ timestamp = interpolate_crossing(
174
+ data, i - 1, low_thresh, sample_period, rising=False
175
+ )
176
+ events.append(
177
+ TriggerEvent(
178
+ timestamp=timestamp,
179
+ sample_index=i,
180
+ event_type=TriggerType.FALLING_EDGE,
181
+ level=float(data[i]),
182
+ )
183
+ )
184
+
185
+ return events
186
+
187
+
188
+ def find_rising_edges(
189
+ trace: WaveformTrace,
190
+ level: float | None = None,
191
+ *,
192
+ hysteresis: float = 0.0,
193
+ return_indices: bool = False,
194
+ ) -> NDArray[np.float64] | NDArray[np.int64]:
195
+ """Find all rising edge timestamps or indices.
196
+
197
+ Args:
198
+ trace: Input waveform trace.
199
+ level: Trigger threshold. If None, uses signal midpoint.
200
+ hysteresis: Hysteresis band for noise immunity.
201
+ return_indices: If True, return sample indices instead of timestamps.
202
+
203
+ Returns:
204
+ Array of timestamps (seconds) or sample indices.
205
+
206
+ Example:
207
+ >>> edges = find_rising_edges(trace, level=1.5)
208
+ >>> print(f"Found {len(edges)} rising edges")
209
+ """
210
+ if level is None:
211
+ level = (np.min(trace.data) + np.max(trace.data)) / 2
212
+
213
+ trigger = EdgeTrigger(level=level, edge="rising", hysteresis=hysteresis)
214
+ events = trigger.find_events(trace)
215
+
216
+ if return_indices:
217
+ return np.array([e.sample_index for e in events], dtype=np.int64)
218
+ return np.array([e.timestamp for e in events], dtype=np.float64)
219
+
220
+
221
+ def find_falling_edges(
222
+ trace: WaveformTrace,
223
+ level: float | None = None,
224
+ *,
225
+ hysteresis: float = 0.0,
226
+ return_indices: bool = False,
227
+ ) -> NDArray[np.float64] | NDArray[np.int64]:
228
+ """Find all falling edge timestamps or indices.
229
+
230
+ Args:
231
+ trace: Input waveform trace.
232
+ level: Trigger threshold. If None, uses signal midpoint.
233
+ hysteresis: Hysteresis band for noise immunity.
234
+ return_indices: If True, return sample indices instead of timestamps.
235
+
236
+ Returns:
237
+ Array of timestamps (seconds) or sample indices.
238
+
239
+ Example:
240
+ >>> edges = find_falling_edges(trace, level=1.5)
241
+ """
242
+ if level is None:
243
+ level = (np.min(trace.data) + np.max(trace.data)) / 2
244
+
245
+ trigger = EdgeTrigger(level=level, edge="falling", hysteresis=hysteresis)
246
+ events = trigger.find_events(trace)
247
+
248
+ if return_indices:
249
+ return np.array([e.sample_index for e in events], dtype=np.int64)
250
+ return np.array([e.timestamp for e in events], dtype=np.float64)
251
+
252
+
253
+ def find_all_edges(
254
+ trace: WaveformTrace,
255
+ level: float | None = None,
256
+ *,
257
+ hysteresis: float = 0.0,
258
+ ) -> tuple[NDArray[np.float64], NDArray[np.bool_]]:
259
+ """Find all edges (rising and falling) with polarity.
260
+
261
+ Args:
262
+ trace: Input waveform trace.
263
+ level: Trigger threshold. If None, uses signal midpoint.
264
+ hysteresis: Hysteresis band for noise immunity.
265
+
266
+ Returns:
267
+ Tuple of (timestamps, is_rising) where is_rising is True for
268
+ rising edges and False for falling edges.
269
+
270
+ Example:
271
+ >>> timestamps, is_rising = find_all_edges(trace, level=1.5)
272
+ >>> rising = timestamps[is_rising]
273
+ >>> falling = timestamps[~is_rising]
274
+ """
275
+ if level is None:
276
+ level = (np.min(trace.data) + np.max(trace.data)) / 2
277
+
278
+ trigger = EdgeTrigger(level=level, edge="either", hysteresis=hysteresis)
279
+ events = trigger.find_events(trace)
280
+
281
+ timestamps = np.array([e.timestamp for e in events], dtype=np.float64)
282
+ is_rising = np.array([e.event_type == TriggerType.RISING_EDGE for e in events], dtype=np.bool_)
283
+
284
+ return timestamps, is_rising
285
+
286
+
287
+ def edge_count(
288
+ trace: WaveformTrace,
289
+ level: float | None = None,
290
+ edge: Literal["rising", "falling", "either"] = "either",
291
+ *,
292
+ hysteresis: float = 0.0,
293
+ ) -> int:
294
+ """Count edges in a trace.
295
+
296
+ Args:
297
+ trace: Input waveform trace.
298
+ level: Trigger threshold. If None, uses signal midpoint.
299
+ edge: Edge type to count.
300
+ hysteresis: Hysteresis band for noise immunity.
301
+
302
+ Returns:
303
+ Number of edges found.
304
+
305
+ Example:
306
+ >>> n_rising = edge_count(trace, level=1.5, edge="rising")
307
+ """
308
+ if level is None:
309
+ level = (np.min(trace.data) + np.max(trace.data)) / 2
310
+
311
+ trigger = EdgeTrigger(level=level, edge=edge, hysteresis=hysteresis)
312
+ return trigger.count_events(trace)
313
+
314
+
315
+ def edge_rate(
316
+ trace: WaveformTrace,
317
+ level: float | None = None,
318
+ edge: Literal["rising", "falling", "either"] = "either",
319
+ *,
320
+ hysteresis: float = 0.0,
321
+ ) -> float:
322
+ """Calculate edge rate (edges per second).
323
+
324
+ Args:
325
+ trace: Input waveform trace.
326
+ level: Trigger threshold.
327
+ edge: Edge type to count.
328
+ hysteresis: Hysteresis band for noise immunity.
329
+
330
+ Returns:
331
+ Edge rate in Hz.
332
+
333
+ Example:
334
+ >>> rate = edge_rate(trace, level=1.5, edge="rising")
335
+ >>> print(f"Toggle rate: {rate} Hz")
336
+ """
337
+ count = edge_count(trace, level, edge, hysteresis=hysteresis)
338
+ duration = trace.duration
339
+
340
+ if duration <= 0:
341
+ return 0.0
342
+
343
+ return count / duration
344
+
345
+
346
+ __all__ = [
347
+ "EdgeTrigger",
348
+ "edge_count",
349
+ "edge_rate",
350
+ "find_all_edges",
351
+ "find_falling_edges",
352
+ "find_rising_edges",
353
+ ]