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,650 @@
1
+ """Periodic pattern detection using multiple algorithms.
2
+
3
+ This module implements robust periodic pattern detection for digital signals
4
+ and binary data using autocorrelation, FFT spectral analysis, and suffix array
5
+ techniques.
6
+
7
+
8
+ Author: TraceKit Development Team
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from dataclasses import dataclass, field
14
+ from typing import TYPE_CHECKING, Literal
15
+
16
+ import numpy as np
17
+
18
+ if TYPE_CHECKING:
19
+ from numpy.typing import NDArray
20
+
21
+
22
+ @dataclass
23
+ class PeriodResult:
24
+ """Result of period detection.
25
+
26
+ Attributes:
27
+ period_samples: Period in number of samples
28
+ period_seconds: Period in seconds (if sample_rate provided)
29
+ frequency_hz: Fundamental frequency in Hz
30
+ confidence: Detection confidence (0-1)
31
+ method: Detection method used
32
+ harmonics: List of detected harmonic frequencies (optional)
33
+ """
34
+
35
+ period_samples: float
36
+ period_seconds: float
37
+ frequency_hz: float
38
+ confidence: float
39
+ method: str
40
+ harmonics: list[float] | None = field(default=None)
41
+
42
+ # Alias for compatibility with tests
43
+ @property
44
+ def period(self) -> float:
45
+ """Alias for period_samples for test compatibility."""
46
+ return self.period_samples
47
+
48
+ def __post_init__(self) -> None:
49
+ """Validate period result values."""
50
+ if self.period_samples <= 0:
51
+ raise ValueError("period_samples must be positive")
52
+ if self.confidence < 0 or self.confidence > 1:
53
+ raise ValueError("confidence must be in range [0, 1]")
54
+
55
+
56
+ def detect_period(
57
+ trace: NDArray[np.float64],
58
+ sample_rate: float = 1.0,
59
+ method: Literal["auto", "autocorr", "fft", "suffix"] = "auto",
60
+ min_period: int = 2,
61
+ max_period: int | None = None,
62
+ ) -> PeriodResult | None:
63
+ """Detect dominant period in signal using best available method.
64
+
65
+ : Periodic Pattern Detection
66
+
67
+ This function automatically selects the most appropriate algorithm based
68
+ on signal characteristics or uses the specified method.
69
+
70
+ Args:
71
+ trace: Input signal array (1D)
72
+ sample_rate: Sampling rate in Hz (default: 1.0)
73
+ method: Detection method ('auto', 'autocorr', 'fft', 'suffix')
74
+ min_period: Minimum period to detect in samples
75
+ max_period: Maximum period to detect in samples (None = len(trace)//2)
76
+
77
+ Returns:
78
+ PeriodResult with detected period information, or None if no period found
79
+
80
+ Raises:
81
+ ValueError: If trace is empty, min_period invalid, or parameters inconsistent
82
+
83
+ Examples:
84
+ >>> signal = np.sin(2 * np.pi * 5 * np.linspace(0, 1, 1000))
85
+ >>> result = detect_period(signal, sample_rate=1000.0)
86
+ >>> print(f"Period: {result.period_seconds:.3f}s, Freq: {result.frequency_hz:.1f}Hz")
87
+ """
88
+ # Input validation
89
+ if trace.size == 0:
90
+ raise ValueError("trace cannot be empty")
91
+ if min_period < 2:
92
+ raise ValueError("min_period must be at least 2")
93
+ if max_period is not None and max_period < min_period:
94
+ raise ValueError("max_period must be >= min_period")
95
+ if sample_rate <= 0:
96
+ raise ValueError("sample_rate must be positive")
97
+
98
+ # Ensure 1D array
99
+ trace = np.asarray(trace).flatten()
100
+
101
+ # Set default max_period
102
+ if max_period is None:
103
+ max_period = len(trace) // 2
104
+ max_period = min(max_period, len(trace) // 2)
105
+
106
+ # Auto-select method based on signal characteristics
107
+ if method == "auto":
108
+ # Use FFT for longer signals (more efficient)
109
+ # Use autocorr for shorter signals or binary data
110
+ if len(trace) > 10000:
111
+ method = "fft"
112
+ elif np.all(np.isin(trace, [0, 1])):
113
+ method = "autocorr" # Better for binary signals
114
+ else:
115
+ method = "fft"
116
+
117
+ # Dispatch to appropriate method
118
+ if method == "autocorr":
119
+ results = detect_periods_autocorr(trace, sample_rate, max_period, min_correlation=0.5)
120
+ return results[0] if results else None
121
+
122
+ elif method == "fft":
123
+ min_freq = sample_rate / max_period if max_period else None
124
+ max_freq = sample_rate / min_period if min_period > 0 else None
125
+ results = detect_periods_fft(trace, sample_rate, min_freq, max_freq, num_peaks=5)
126
+ return results[0] if results else None
127
+
128
+ elif method == "suffix":
129
+ # Suffix array method for exact repeats
130
+ period_samples = _detect_period_suffix(trace, min_period, max_period)
131
+ if period_samples is None:
132
+ return None
133
+ return PeriodResult(
134
+ period_samples=float(period_samples),
135
+ period_seconds=period_samples / sample_rate,
136
+ frequency_hz=sample_rate / period_samples,
137
+ confidence=0.9, # High confidence for exact matches
138
+ method="suffix",
139
+ )
140
+ else:
141
+ raise ValueError(f"Unknown method: {method}")
142
+
143
+
144
+ def detect_periods_fft(
145
+ trace: NDArray[np.float64],
146
+ sample_rate: float = 1.0,
147
+ min_freq: float | None = None,
148
+ max_freq: float | None = None,
149
+ num_peaks: int = 5,
150
+ ) -> list[PeriodResult]:
151
+ """Detect periods using FFT spectral analysis.
152
+
153
+ : Periodic Pattern Detection via FFT
154
+
155
+ Uses power spectral density to identify dominant frequencies and their
156
+ harmonics. More efficient than autocorrelation for long signals.
157
+
158
+ Args:
159
+ trace: Input signal array (1D)
160
+ sample_rate: Sampling rate in Hz
161
+ min_freq: Minimum frequency to consider (Hz)
162
+ max_freq: Maximum frequency to consider (Hz)
163
+ num_peaks: Maximum number of peaks to return
164
+
165
+ Returns:
166
+ List of PeriodResult sorted by confidence (strongest first)
167
+
168
+ Examples:
169
+ >>> signal = np.sin(2*np.pi*10*np.linspace(0, 1, 1000))
170
+ >>> periods = detect_periods_fft(signal, sample_rate=1000.0, num_peaks=3)
171
+ """
172
+ trace = np.asarray(trace).flatten()
173
+
174
+ if trace.size == 0:
175
+ return []
176
+
177
+ # Remove DC component
178
+ trace_centered = trace - np.mean(trace)
179
+
180
+ # Compute FFT
181
+ n = len(trace_centered)
182
+ fft_result = np.fft.rfft(trace_centered)
183
+ power = np.asarray(np.abs(fft_result) ** 2, dtype=np.float64)
184
+ freqs = np.fft.rfftfreq(n, 1.0 / sample_rate)
185
+
186
+ # Apply frequency range filtering
187
+ valid_mask = np.ones(len(freqs), dtype=bool)
188
+ if min_freq is not None:
189
+ valid_mask &= freqs >= min_freq
190
+ if max_freq is not None:
191
+ valid_mask &= freqs <= max_freq
192
+
193
+ # Exclude DC component
194
+ valid_mask[0] = False
195
+
196
+ # Find peaks in power spectrum
197
+ peak_indices = _find_spectral_peaks(power, min_distance=1)
198
+ peak_indices = peak_indices[valid_mask[peak_indices]]
199
+
200
+ if len(peak_indices) == 0:
201
+ return []
202
+
203
+ # Sort by power
204
+ peak_powers = power[peak_indices]
205
+ sorted_indices = np.argsort(peak_powers)[::-1][:num_peaks]
206
+ peak_indices = peak_indices[sorted_indices]
207
+
208
+ # Build results
209
+ results = []
210
+ max_power = np.max(power[peak_indices]) if len(peak_indices) > 0 else 1.0
211
+
212
+ for idx in peak_indices:
213
+ freq = freqs[idx]
214
+ if freq == 0:
215
+ continue
216
+
217
+ period_seconds = 1.0 / freq
218
+ period_samples = sample_rate / freq
219
+
220
+ # Confidence based on relative power
221
+ confidence = float(power[idx] / max_power)
222
+
223
+ # Detect harmonics (simple approach: look for integer multiples)
224
+ harmonics = []
225
+ for mult in range(2, 6):
226
+ harmonic_freq = freq * mult
227
+ if harmonic_freq < freqs[-1]:
228
+ # Find closest frequency bin
229
+ harmonic_idx = np.argmin(np.abs(freqs - harmonic_freq))
230
+ if power[harmonic_idx] > 0.1 * power[idx]:
231
+ harmonics.append(harmonic_freq)
232
+
233
+ results.append(
234
+ PeriodResult(
235
+ period_samples=period_samples,
236
+ period_seconds=period_seconds,
237
+ frequency_hz=freq,
238
+ confidence=min(confidence, 1.0),
239
+ method="fft",
240
+ harmonics=harmonics if harmonics else None,
241
+ )
242
+ )
243
+
244
+ return results
245
+
246
+
247
+ def detect_periods_autocorr(
248
+ trace: NDArray[np.float64],
249
+ sample_rate: float = 1.0,
250
+ max_period: int | None = None,
251
+ min_correlation: float = 0.5,
252
+ ) -> list[PeriodResult]:
253
+ """Detect periods using autocorrelation.
254
+
255
+ : Periodic Pattern Detection via autocorrelation
256
+
257
+ Computes normalized autocorrelation and finds peaks corresponding to
258
+ periodic patterns. More robust to noise than simple pattern matching.
259
+
260
+ Args:
261
+ trace: Input signal array (1D)
262
+ sample_rate: Sampling rate in Hz
263
+ max_period: Maximum period to search (samples)
264
+ min_correlation: Minimum correlation threshold (0-1)
265
+
266
+ Returns:
267
+ List of PeriodResult sorted by confidence
268
+
269
+ Examples:
270
+ >>> signal = np.tile([1, 0, 1, 0], 100)
271
+ >>> periods = detect_periods_autocorr(signal, min_correlation=0.7)
272
+ """
273
+ trace = np.asarray(trace).flatten()
274
+
275
+ if trace.size == 0:
276
+ return []
277
+
278
+ # Set default max_period
279
+ if max_period is None:
280
+ max_period = len(trace) // 2
281
+ max_period = min(max_period, len(trace) // 2)
282
+
283
+ # Compute normalized autocorrelation using FFT (efficient)
284
+ trace_centered = trace - np.mean(trace)
285
+
286
+ # Compute via FFT convolution
287
+ n = len(trace_centered)
288
+ fft_trace = np.fft.fft(trace_centered, n=2 * n)
289
+ autocorr = np.fft.ifft(fft_trace * np.conj(fft_trace)).real[:n]
290
+
291
+ # Normalize
292
+ if autocorr[0] > 0:
293
+ autocorr = autocorr / autocorr[0]
294
+
295
+ # Limit search range
296
+ autocorr = autocorr[: max_period + 1]
297
+
298
+ # Find peaks (skip lag 0)
299
+ peaks = _find_spectral_peaks(autocorr[1:], min_distance=1) + 1
300
+
301
+ # Filter by minimum correlation
302
+ peaks = peaks[autocorr[peaks] >= min_correlation]
303
+
304
+ if len(peaks) == 0:
305
+ return []
306
+
307
+ # Sort by correlation value
308
+ peak_corrs = autocorr[peaks]
309
+ sorted_indices = np.argsort(peak_corrs)[::-1]
310
+ peaks = peaks[sorted_indices]
311
+
312
+ # Build results
313
+ results = []
314
+ for lag in peaks[:5]: # Top 5 peaks
315
+ period_samples = float(lag)
316
+ period_seconds = period_samples / sample_rate
317
+ frequency_hz = sample_rate / period_samples
318
+ confidence = float(autocorr[lag])
319
+
320
+ results.append(
321
+ PeriodResult(
322
+ period_samples=period_samples,
323
+ period_seconds=period_seconds,
324
+ frequency_hz=frequency_hz,
325
+ confidence=confidence,
326
+ method="autocorr",
327
+ )
328
+ )
329
+
330
+ return results
331
+
332
+
333
+ def validate_period(
334
+ trace: NDArray[np.float64], period: float, tolerance: float = 0.01
335
+ ) -> tuple[bool, float]:
336
+ """Validate detected period against signal.
337
+
338
+ : Period validation
339
+
340
+ Verifies that the signal actually repeats at the given period by measuring
341
+ correlation between shifted copies of the signal.
342
+
343
+ Args:
344
+ trace: Input signal array
345
+ period: Period to validate (in samples)
346
+ tolerance: Allowed fractional deviation in period
347
+
348
+ Returns:
349
+ Tuple of (is_valid, actual_confidence)
350
+ - is_valid: True if period is confirmed
351
+ - actual_confidence: Measured correlation strength (0-1)
352
+
353
+ Examples:
354
+ >>> signal = np.tile([1, 2, 3, 4], 50)
355
+ >>> is_valid, conf = validate_period(signal, period=4.0)
356
+ >>> assert is_valid and conf > 0.95
357
+ """
358
+ trace = np.asarray(trace).flatten()
359
+
360
+ if trace.size == 0:
361
+ return False, 0.0
362
+
363
+ if period < 1 or period >= len(trace):
364
+ return False, 0.0
365
+
366
+ # Convert to integer lag for nearest-neighbor validation
367
+ lag = int(round(period))
368
+
369
+ # Check if lag is within tolerance
370
+ if abs(period - lag) > period * tolerance:
371
+ # Use interpolation for sub-sample periods
372
+ lag_low = int(np.floor(period))
373
+ lag_high = int(np.ceil(period))
374
+ alpha = period - lag_low
375
+
376
+ if lag_high >= len(trace):
377
+ lag = lag_low
378
+ else:
379
+ # Weighted average of both lags
380
+ corr_low = _compute_lag_correlation(trace, lag_low)
381
+ corr_high = _compute_lag_correlation(trace, lag_high)
382
+ confidence = (1 - alpha) * corr_low + alpha * corr_high
383
+
384
+ is_valid = confidence >= 0.5
385
+ return is_valid, float(confidence)
386
+
387
+ # Simple case: integer lag
388
+ confidence = _compute_lag_correlation(trace, lag)
389
+ is_valid = confidence >= 0.5
390
+
391
+ return is_valid, float(confidence)
392
+
393
+
394
+ def _compute_lag_correlation(trace: NDArray[np.float64], lag: int) -> float:
395
+ """Compute normalized correlation at specific lag.
396
+
397
+ Args:
398
+ trace: Input signal
399
+ lag: Lag in samples
400
+
401
+ Returns:
402
+ Normalized correlation coefficient (0-1)
403
+ """
404
+ if lag <= 0 or lag >= len(trace):
405
+ return 0.0
406
+
407
+ # Center the signal
408
+ trace_centered = trace - np.mean(trace)
409
+
410
+ # Compute correlation
411
+ n_overlap = len(trace) - lag
412
+ part1 = trace_centered[:n_overlap]
413
+ part2 = trace_centered[lag : lag + n_overlap]
414
+
415
+ # Pearson correlation
416
+ std1 = np.std(part1)
417
+ std2 = np.std(part2)
418
+
419
+ if std1 == 0 or std2 == 0:
420
+ return 0.0
421
+
422
+ correlation = np.mean(part1 * part2) / (std1 * std2)
423
+
424
+ # Clamp to [0, 1] range
425
+ return float(np.clip(correlation, 0, 1))
426
+
427
+
428
+ def _find_spectral_peaks(data: NDArray[np.float64], min_distance: int = 1) -> NDArray[np.intp]:
429
+ """Find peaks in 1D array.
430
+
431
+ Simple peak detection: point is peak if higher than neighbors.
432
+
433
+ Args:
434
+ data: 1D array
435
+ min_distance: Minimum distance between peaks
436
+
437
+ Returns:
438
+ Array of peak indices
439
+ """
440
+ if len(data) < 3:
441
+ return np.array([], dtype=np.intp)
442
+
443
+ # Find local maxima
444
+ peaks_list: list[int] = []
445
+ for i in range(1, len(data) - 1):
446
+ if data[i] > data[i - 1] and data[i] > data[i + 1]:
447
+ peaks_list.append(i)
448
+
449
+ peaks: NDArray[np.intp] = np.array(peaks_list, dtype=np.intp)
450
+
451
+ # Apply minimum distance constraint
452
+ if len(peaks) > 0 and min_distance > 1:
453
+ # Keep highest peaks when too close
454
+ filtered_peaks_list: list[int] = []
455
+ last_peak = -min_distance
456
+
457
+ # Sort by height
458
+ sorted_indices = np.argsort(data[peaks])[::-1]
459
+
460
+ for idx in sorted_indices:
461
+ peak_pos = int(peaks[idx])
462
+ if peak_pos - last_peak >= min_distance:
463
+ filtered_peaks_list.append(peak_pos)
464
+ last_peak = peak_pos
465
+
466
+ peaks = np.array(np.sort(np.array(filtered_peaks_list, dtype=np.intp)), dtype=np.intp)
467
+
468
+ return peaks
469
+
470
+
471
+ def _detect_period_suffix(
472
+ trace: NDArray[np.float64], min_period: int, max_period: int
473
+ ) -> int | None:
474
+ """Detect period using suffix array (for exact repeats).
475
+
476
+ : Suffix array-based period detection
477
+
478
+ This method finds the longest exact repeating substring, which corresponds
479
+ to the period for perfectly periodic signals.
480
+
481
+ Args:
482
+ trace: Input signal (will be converted to bytes)
483
+ min_period: Minimum period length
484
+ max_period: Maximum period length
485
+
486
+ Returns:
487
+ Period in samples, or None if not found
488
+ """
489
+ # Convert to byte sequence for suffix array
490
+ if trace.dtype == np.bool_ or np.all(np.isin(trace, [0, 1])):
491
+ # Binary signal
492
+ trace_bytes = np.packbits(trace.astype(np.uint8))
493
+ else:
494
+ # Use raw bytes
495
+ trace_bytes = trace.astype(np.uint8)
496
+
497
+ n = len(trace_bytes)
498
+
499
+ # Simple period detection: check for repeating patterns
500
+ for period in range(min_period, min(max_period + 1, n // 2)):
501
+ # Check if trace repeats with this period
502
+ num_repeats = n // period
503
+ if num_repeats < 2:
504
+ continue
505
+
506
+ # Compare first period with subsequent periods
507
+ pattern = trace_bytes[:period]
508
+ matches = 0
509
+
510
+ for i in range(1, num_repeats):
511
+ segment = trace_bytes[i * period : (i + 1) * period]
512
+ if len(segment) == period and np.array_equal(pattern, segment):
513
+ matches += 1
514
+
515
+ # If most repetitions match, consider it valid
516
+ if matches >= num_repeats * 0.8 - 1:
517
+ return period
518
+
519
+ return None
520
+
521
+
522
+ class PeriodicPatternDetector:
523
+ """Object-oriented wrapper for periodic pattern detection.
524
+
525
+ Provides a class-based interface for period detection operations,
526
+ wrapping the functional API for consistency with test expectations.
527
+
528
+
529
+
530
+ Example:
531
+ >>> detector = PeriodicPatternDetector()
532
+ >>> result = detector.detect_period(signal)
533
+ >>> print(f"Period: {result.period} samples, confidence: {result.confidence}")
534
+ """
535
+
536
+ def __init__(
537
+ self,
538
+ method: Literal["auto", "autocorr", "fft", "autocorrelation"] = "auto",
539
+ sample_rate: float = 1.0,
540
+ min_period: int = 2,
541
+ max_period: int | None = None,
542
+ ):
543
+ """Initialize periodic pattern detector.
544
+
545
+ Args:
546
+ method: Detection method ('auto', 'autocorr', 'fft', 'autocorrelation').
547
+ sample_rate: Sample rate in Hz.
548
+ min_period: Minimum period in samples.
549
+ max_period: Maximum period in samples.
550
+ """
551
+ # Map 'autocorrelation' to 'autocorr' for test compatibility
552
+ if method == "autocorrelation":
553
+ method = "autocorr"
554
+ self.method = method
555
+ self.sample_rate = sample_rate
556
+ self.min_period = min_period
557
+ self.max_period = max_period
558
+
559
+ def detect_period(self, trace: NDArray[np.float64]) -> PeriodResult:
560
+ """Detect the dominant period in the signal.
561
+
562
+ Args:
563
+ trace: Input signal array (1D, boolean or numeric).
564
+
565
+ Returns:
566
+ PeriodResult with detected period information.
567
+
568
+ Raises:
569
+ ValueError: If trace is empty or too short.
570
+
571
+ Example:
572
+ >>> detector = PeriodicPatternDetector(method="autocorr")
573
+ >>> result = detector.detect_period(np.tile([1, 0], 100))
574
+ >>> result.period == 2
575
+ True
576
+ """
577
+ trace = np.asarray(trace).flatten()
578
+
579
+ # Validate input
580
+ if trace.size == 0:
581
+ raise ValueError("trace cannot be empty")
582
+ if trace.size < 3:
583
+ raise ValueError("trace must have at least 3 elements for period detection")
584
+
585
+ result = detect_period(
586
+ trace,
587
+ sample_rate=self.sample_rate,
588
+ method=self.method,
589
+ min_period=self.min_period,
590
+ max_period=self.max_period,
591
+ )
592
+
593
+ if result is None:
594
+ # Return a low-confidence result if no period found
595
+ return PeriodResult(
596
+ period_samples=1.0,
597
+ period_seconds=1.0 / self.sample_rate,
598
+ frequency_hz=self.sample_rate,
599
+ confidence=0.0,
600
+ method=self.method,
601
+ )
602
+
603
+ return result
604
+
605
+ def detect_multiple_periods(
606
+ self, trace: NDArray[np.float64], num_periods: int = 5
607
+ ) -> list[PeriodResult]:
608
+ """Detect multiple periods in the signal.
609
+
610
+ Args:
611
+ trace: Input signal array.
612
+ num_periods: Maximum number of periods to return.
613
+
614
+ Returns:
615
+ List of PeriodResult sorted by confidence.
616
+ """
617
+ trace = np.asarray(trace).flatten()
618
+
619
+ if self.method in ["fft", "auto"]:
620
+ min_freq = self.sample_rate / self.max_period if self.max_period else None
621
+ max_freq = self.sample_rate / self.min_period if self.min_period > 0 else None
622
+ return detect_periods_fft(trace, self.sample_rate, min_freq, max_freq, num_periods)
623
+ else:
624
+ return detect_periods_autocorr(
625
+ trace, self.sample_rate, self.max_period, min_correlation=0.3
626
+ )[:num_periods]
627
+
628
+ def validate(self, trace: NDArray[np.float64], period: float, tolerance: float = 0.01) -> bool:
629
+ """Validate a detected period.
630
+
631
+ Args:
632
+ trace: Input signal array.
633
+ period: Period to validate.
634
+ tolerance: Tolerance for period matching.
635
+
636
+ Returns:
637
+ True if period is valid.
638
+ """
639
+ is_valid, _ = validate_period(trace, period, tolerance)
640
+ return is_valid
641
+
642
+
643
+ __all__ = [
644
+ "PeriodResult",
645
+ "PeriodicPatternDetector",
646
+ "detect_period",
647
+ "detect_periods_autocorr",
648
+ "detect_periods_fft",
649
+ "validate_period",
650
+ ]