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,672 @@
1
+ """Automatic anomaly detection and highlighting.
2
+
3
+ This module detects unusual signal features (glitches, dropouts, noise
4
+ spikes, timing violations) to guide user attention.
5
+
6
+
7
+ Example:
8
+ >>> from oscura.discovery import find_anomalies
9
+ >>> anomalies = find_anomalies(trace)
10
+ >>> for anom in anomalies:
11
+ ... print(f"{anom.timestamp_us:.2f}us: {anom.type} - {anom.description}")
12
+
13
+ References:
14
+ IEEE 1057-2017: Digitizing Waveform Recorders
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ from dataclasses import dataclass, field
20
+ from typing import TYPE_CHECKING, Any, Literal
21
+
22
+ import numpy as np
23
+
24
+ from oscura.analyzers.statistics.basic import basic_stats
25
+ from oscura.core.types import DigitalTrace, WaveformTrace
26
+
27
+ if TYPE_CHECKING:
28
+ from numpy.typing import NDArray
29
+
30
+ AnomalyType = Literal[
31
+ "glitch",
32
+ "dropout",
33
+ "noise_spike",
34
+ "timing_violation",
35
+ "ringing",
36
+ "overshoot",
37
+ "undershoot",
38
+ ]
39
+
40
+ Severity = Literal["CRITICAL", "WARNING", "INFO"]
41
+
42
+
43
+ @dataclass
44
+ class Anomaly:
45
+ """Detected signal anomaly.
46
+
47
+ Represents an unusual or interesting signal feature with timing,
48
+ classification, and plain-language explanation.
49
+
50
+ Attributes:
51
+ timestamp_us: Anomaly start time in microseconds.
52
+ type: Type of anomaly detected.
53
+ severity: Impact level (CRITICAL, WARNING, INFO).
54
+ description: Plain-language explanation.
55
+ duration_ns: Duration in nanoseconds.
56
+ confidence: Detection confidence (0.0-1.0).
57
+ metadata: Additional type-specific information.
58
+
59
+ Example:
60
+ >>> anomaly = Anomaly(
61
+ ... timestamp_us=45.23,
62
+ ... type="glitch",
63
+ ... severity="WARNING",
64
+ ... description="Brief 35ns pulse, likely noise spike",
65
+ ... duration_ns=35.0,
66
+ ... confidence=0.92
67
+ ... )
68
+ """
69
+
70
+ timestamp_us: float
71
+ type: AnomalyType
72
+ severity: Severity
73
+ description: str
74
+ duration_ns: float = 0.0
75
+ confidence: float = 1.0
76
+ metadata: dict[str, float] = field(default_factory=dict)
77
+
78
+
79
+ def find_anomalies(
80
+ trace: WaveformTrace | DigitalTrace,
81
+ *,
82
+ severity_filter: list[Severity] | None = None,
83
+ min_confidence: float = 0.7,
84
+ anomaly_types: list[AnomalyType] | None = None,
85
+ ) -> list[Anomaly]:
86
+ """Detect anomalies in signal automatically.
87
+
88
+ Identifies glitches, dropouts, noise spikes, timing violations, ringing,
89
+ and overshoot/undershoot without requiring user configuration.
90
+
91
+ Args:
92
+ trace: Input waveform or digital trace.
93
+ severity_filter: Only return specified severity levels (default: all).
94
+ min_confidence: Minimum confidence threshold (0.0-1.0).
95
+ anomaly_types: Specific anomaly types to detect (default: all).
96
+
97
+ Returns:
98
+ List of detected Anomaly objects, sorted by timestamp.
99
+
100
+ Raises:
101
+ ValueError: If trace is empty or invalid.
102
+
103
+ Example:
104
+ >>> anomalies = find_anomalies(trace, severity_filter=['CRITICAL', 'WARNING'])
105
+ >>> print(f"Found {len(anomalies)} critical/warning anomalies")
106
+ >>> for anom in anomalies[:5]:
107
+ ... print(f" {anom.timestamp_us:.2f}us: {anom.type} - {anom.description}")
108
+
109
+ References:
110
+ DISC-002: Anomaly Highlighting
111
+ """
112
+ # Validate input
113
+ if len(trace) == 0:
114
+ raise ValueError("Cannot detect anomalies in empty trace")
115
+
116
+ # Get signal data
117
+ if isinstance(trace, WaveformTrace):
118
+ data = trace.data
119
+ sample_rate = trace.metadata.sample_rate
120
+ else:
121
+ data = trace.data.astype(np.float64)
122
+ sample_rate = trace.metadata.sample_rate
123
+
124
+ # Compute basic statistics for reference
125
+ stats = basic_stats(data)
126
+ voltage_swing = stats["max"] - stats["min"]
127
+
128
+ # Collect all anomalies
129
+ all_anomalies: list[Anomaly] = []
130
+
131
+ # Define which anomaly types to check
132
+ if anomaly_types is None:
133
+ check_types: list[AnomalyType] = [
134
+ "glitch",
135
+ "dropout",
136
+ "noise_spike",
137
+ "timing_violation",
138
+ "ringing",
139
+ "overshoot",
140
+ "undershoot",
141
+ ]
142
+ else:
143
+ check_types = anomaly_types
144
+
145
+ # Detect each type
146
+ if "glitch" in check_types:
147
+ all_anomalies.extend(_detect_glitches(data, sample_rate, voltage_swing, stats))
148
+
149
+ if "dropout" in check_types:
150
+ all_anomalies.extend(_detect_dropouts(data, sample_rate, voltage_swing, stats))
151
+
152
+ if "noise_spike" in check_types:
153
+ all_anomalies.extend(_detect_noise_spikes(data, sample_rate, voltage_swing, stats))
154
+
155
+ if "timing_violation" in check_types:
156
+ all_anomalies.extend(_detect_timing_violations(data, sample_rate, stats))
157
+
158
+ if "ringing" in check_types:
159
+ all_anomalies.extend(_detect_ringing(data, sample_rate, voltage_swing, stats))
160
+
161
+ if "overshoot" in check_types:
162
+ all_anomalies.extend(_detect_overshoot(data, sample_rate, voltage_swing, stats))
163
+
164
+ if "undershoot" in check_types:
165
+ all_anomalies.extend(_detect_undershoot(data, sample_rate, voltage_swing, stats))
166
+
167
+ # Filter by confidence
168
+ all_anomalies = [a for a in all_anomalies if a.confidence >= min_confidence]
169
+
170
+ # Filter by severity if requested
171
+ if severity_filter is not None:
172
+ all_anomalies = [a for a in all_anomalies if a.severity in severity_filter]
173
+
174
+ # Sort by timestamp
175
+ all_anomalies.sort(key=lambda a: a.timestamp_us)
176
+
177
+ return all_anomalies
178
+
179
+
180
+ def _detect_glitches(
181
+ data: NDArray[np.floating[Any]],
182
+ sample_rate: float,
183
+ voltage_swing: float,
184
+ stats: dict[str, float],
185
+ ) -> list[Anomaly]:
186
+ """Detect brief narrow pulses (glitches).
187
+
188
+ Args:
189
+ data: Signal data array.
190
+ sample_rate: Sample rate in Hz.
191
+ voltage_swing: Peak-to-peak voltage.
192
+ stats: Basic statistics.
193
+
194
+ Returns:
195
+ List of detected glitch anomalies.
196
+ """
197
+ anomalies: list[Anomaly] = []
198
+
199
+ if voltage_swing == 0 or len(data) < 10:
200
+ return anomalies
201
+
202
+ # Threshold for glitch detection
203
+ threshold = stats["mean"]
204
+ glitch_threshold = voltage_swing * 0.3 # 30% of swing
205
+
206
+ # Find samples far from mean
207
+ deviations = np.abs(data - threshold)
208
+ glitch_candidates = np.where(deviations > glitch_threshold)[0]
209
+
210
+ if len(glitch_candidates) == 0:
211
+ return anomalies
212
+
213
+ # Group consecutive samples into glitches
214
+ glitch_groups = []
215
+ current_group = [glitch_candidates[0]]
216
+
217
+ for idx in glitch_candidates[1:]:
218
+ if idx == current_group[-1] + 1:
219
+ current_group.append(idx)
220
+ else:
221
+ glitch_groups.append(current_group)
222
+ current_group = [idx]
223
+
224
+ glitch_groups.append(current_group)
225
+
226
+ # Analyze each glitch
227
+ for group in glitch_groups:
228
+ duration_samples = len(group)
229
+ duration_ns = (duration_samples / sample_rate) * 1e9
230
+
231
+ # Only report glitches < 50ns
232
+ if duration_ns < 50:
233
+ timestamp_us = (group[0] / sample_rate) * 1e6
234
+ magnitude = np.max(np.abs(data[group] - threshold))
235
+
236
+ # Determine severity based on magnitude
237
+ if magnitude > voltage_swing * 0.5:
238
+ severity: Severity = "WARNING"
239
+ else:
240
+ severity = "INFO"
241
+
242
+ description = f"Brief {duration_ns:.0f}ns pulse, likely noise spike"
243
+
244
+ anomalies.append(
245
+ Anomaly(
246
+ timestamp_us=timestamp_us,
247
+ type="glitch",
248
+ severity=severity,
249
+ description=description,
250
+ duration_ns=duration_ns,
251
+ confidence=0.85,
252
+ metadata={"magnitude": magnitude},
253
+ )
254
+ )
255
+
256
+ return anomalies
257
+
258
+
259
+ def _detect_dropouts(
260
+ data: NDArray[np.floating[Any]],
261
+ sample_rate: float,
262
+ voltage_swing: float,
263
+ stats: dict[str, float],
264
+ ) -> list[Anomaly]:
265
+ """Detect missing transitions or prolonged holds.
266
+
267
+ Args:
268
+ data: Signal data array.
269
+ sample_rate: Sample rate in Hz.
270
+ voltage_swing: Peak-to-peak voltage.
271
+ stats: Basic statistics.
272
+
273
+ Returns:
274
+ List of detected dropout anomalies.
275
+ """
276
+ anomalies: list[Anomaly] = []
277
+
278
+ if voltage_swing == 0 or len(data) < 100:
279
+ return anomalies
280
+
281
+ # Estimate expected period from transitions
282
+ threshold = (stats["max"] + stats["min"]) / 2
283
+ digital = data > threshold
284
+ transitions = np.where(np.diff(digital.astype(int)) != 0)[0]
285
+
286
+ if len(transitions) < 5:
287
+ return anomalies
288
+
289
+ # Calculate typical transition interval
290
+ intervals = np.diff(transitions)
291
+ expected_period = np.median(intervals)
292
+
293
+ # Find unusually long intervals (>2x expected)
294
+ for i, interval in enumerate(intervals):
295
+ if interval > expected_period * 2.0:
296
+ timestamp_us = (transitions[i] / sample_rate) * 1e6
297
+ duration_ns = (interval / sample_rate) * 1e9
298
+ multiplier = interval / expected_period
299
+
300
+ description = f"Missing transition, signal held for {multiplier:.1f}x expected duration"
301
+
302
+ # Severity based on how long the dropout is
303
+ if multiplier > 5.0:
304
+ severity: Severity = "CRITICAL"
305
+ elif multiplier > 3.0:
306
+ severity = "WARNING"
307
+ else:
308
+ severity = "INFO"
309
+
310
+ anomalies.append(
311
+ Anomaly(
312
+ timestamp_us=timestamp_us,
313
+ type="dropout",
314
+ severity=severity,
315
+ description=description,
316
+ duration_ns=duration_ns,
317
+ confidence=0.88,
318
+ metadata={"expected_period_ns": (expected_period / sample_rate) * 1e9},
319
+ )
320
+ )
321
+
322
+ return anomalies
323
+
324
+
325
+ def _detect_noise_spikes(
326
+ data: NDArray[np.floating[Any]],
327
+ sample_rate: float,
328
+ voltage_swing: float,
329
+ stats: dict[str, float],
330
+ ) -> list[Anomaly]:
331
+ """Detect noise spikes (>20% of signal swing).
332
+
333
+ Args:
334
+ data: Signal data array.
335
+ sample_rate: Sample rate in Hz.
336
+ voltage_swing: Peak-to-peak voltage.
337
+ stats: Basic statistics.
338
+
339
+ Returns:
340
+ List of detected noise spike anomalies.
341
+ """
342
+ anomalies: list[Anomaly] = []
343
+
344
+ if voltage_swing == 0 or len(data) < 10:
345
+ return anomalies
346
+
347
+ # Use running window to detect local spikes
348
+ window = 10
349
+ spike_threshold = voltage_swing * 0.2
350
+
351
+ for i in range(window, len(data) - window):
352
+ local_mean = np.mean(data[i - window : i + window])
353
+ deviation = abs(data[i] - local_mean)
354
+
355
+ if deviation > spike_threshold:
356
+ timestamp_us = (i / sample_rate) * 1e6
357
+ percent = (deviation / voltage_swing) * 100
358
+
359
+ description = f"Noise spike {percent:.0f}% of signal swing"
360
+
361
+ # Severity based on spike magnitude
362
+ if percent > 50:
363
+ severity: Severity = "WARNING"
364
+ else:
365
+ severity = "INFO"
366
+
367
+ anomalies.append(
368
+ Anomaly(
369
+ timestamp_us=timestamp_us,
370
+ type="noise_spike",
371
+ severity=severity,
372
+ description=description,
373
+ duration_ns=(1 / sample_rate) * 1e9,
374
+ confidence=0.80,
375
+ metadata={"deviation_v": deviation},
376
+ )
377
+ )
378
+
379
+ # Skip ahead to avoid duplicate detections
380
+ i += window
381
+
382
+ # Limit number of noise spikes reported
383
+ return anomalies[:50]
384
+
385
+
386
+ def _detect_timing_violations(
387
+ data: NDArray[np.floating[Any]],
388
+ sample_rate: float,
389
+ stats: dict[str, float],
390
+ ) -> list[Anomaly]:
391
+ """Detect timing violations (±5% of expected timing).
392
+
393
+ Args:
394
+ data: Signal data array.
395
+ sample_rate: Sample rate in Hz.
396
+ stats: Basic statistics.
397
+
398
+ Returns:
399
+ List of detected timing violation anomalies.
400
+ """
401
+ anomalies: list[Anomaly] = []
402
+
403
+ if len(data) < 100:
404
+ return anomalies
405
+
406
+ # Find edges
407
+ threshold = stats["mean"]
408
+ digital = data > threshold
409
+ transitions = np.where(np.diff(digital.astype(int)) != 0)[0]
410
+
411
+ if len(transitions) < 10:
412
+ return anomalies
413
+
414
+ # Analyze timing consistency
415
+ intervals = np.diff(transitions)
416
+ expected_interval = np.median(intervals)
417
+ tolerance = expected_interval * 0.05 # 5% tolerance
418
+
419
+ # Find violations
420
+ for i, interval in enumerate(intervals):
421
+ deviation = abs(interval - expected_interval)
422
+
423
+ if deviation > tolerance:
424
+ timestamp_us = (transitions[i] / sample_rate) * 1e6
425
+ percent_dev = (deviation / expected_interval) * 100
426
+
427
+ description = f"Timing deviation {percent_dev:.1f}% from expected"
428
+
429
+ # Severity based on deviation magnitude
430
+ if percent_dev > 15:
431
+ severity: Severity = "WARNING"
432
+ else:
433
+ severity = "INFO"
434
+
435
+ anomalies.append(
436
+ Anomaly(
437
+ timestamp_us=timestamp_us,
438
+ type="timing_violation",
439
+ severity=severity,
440
+ description=description,
441
+ duration_ns=(interval / sample_rate) * 1e9,
442
+ confidence=0.75,
443
+ metadata={"deviation_percent": percent_dev},
444
+ )
445
+ )
446
+
447
+ # Limit violations reported
448
+ return anomalies[:20]
449
+
450
+
451
+ def _detect_ringing(
452
+ data: NDArray[np.floating[Any]],
453
+ sample_rate: float,
454
+ voltage_swing: float,
455
+ stats: dict[str, float],
456
+ ) -> list[Anomaly]:
457
+ """Detect ringing (≥3 oscillations).
458
+
459
+ Args:
460
+ data: Signal data array.
461
+ sample_rate: Sample rate in Hz.
462
+ voltage_swing: Peak-to-peak voltage.
463
+ stats: Basic statistics.
464
+
465
+ Returns:
466
+ List of detected ringing anomalies.
467
+ """
468
+ anomalies: list[Anomaly] = []
469
+
470
+ if voltage_swing == 0 or len(data) < 50:
471
+ return anomalies
472
+
473
+ # Look for oscillations after transitions
474
+ threshold = stats["mean"]
475
+ digital = data > threshold
476
+ transitions = np.where(np.diff(digital.astype(int)) != 0)[0]
477
+
478
+ for trans_idx in transitions:
479
+ # Check window after transition
480
+ window_size = min(50, len(data) - trans_idx - 1)
481
+ if window_size < 10:
482
+ continue
483
+
484
+ window = data[trans_idx + 1 : trans_idx + 1 + window_size]
485
+
486
+ # Count zero crossings (oscillations)
487
+ window_mean = np.mean(window)
488
+ crossings = np.sum(np.diff(np.sign(window - window_mean)) != 0)
489
+
490
+ # Ringing should have ≥3 oscillations
491
+ if crossings >= 6: # 6 crossings = 3 full oscillations
492
+ timestamp_us = (trans_idx / sample_rate) * 1e6
493
+ duration_ns = (window_size / sample_rate) * 1e9
494
+ num_oscillations = crossings // 2
495
+
496
+ description = f"Ringing with {num_oscillations} oscillations after edge"
497
+
498
+ severity: Severity = "INFO"
499
+
500
+ anomalies.append(
501
+ Anomaly(
502
+ timestamp_us=timestamp_us,
503
+ type="ringing",
504
+ severity=severity,
505
+ description=description,
506
+ duration_ns=duration_ns,
507
+ confidence=0.70,
508
+ metadata={"oscillations": num_oscillations},
509
+ )
510
+ )
511
+
512
+ return anomalies[:10]
513
+
514
+
515
+ def _detect_overshoot(
516
+ data: NDArray[np.floating[Any]],
517
+ sample_rate: float,
518
+ voltage_swing: float,
519
+ stats: dict[str, float],
520
+ ) -> list[Anomaly]:
521
+ """Detect overshoot (>10% beyond high rail).
522
+
523
+ Args:
524
+ data: Signal data array.
525
+ sample_rate: Sample rate in Hz.
526
+ voltage_swing: Peak-to-peak voltage.
527
+ stats: Basic statistics.
528
+
529
+ Returns:
530
+ List of detected overshoot anomalies.
531
+ """
532
+ anomalies: list[Anomaly] = []
533
+
534
+ if voltage_swing == 0:
535
+ return anomalies
536
+
537
+ # Define expected high rail (based on histogram peaks)
538
+ high_rail = stats["max"] * 0.95 # Expected rail at 95th percentile
539
+ overshoot_threshold = high_rail * 1.1 # 10% above rail
540
+
541
+ # Find overshoot samples
542
+ overshoots = np.where(data > overshoot_threshold)[0]
543
+
544
+ if len(overshoots) == 0:
545
+ return anomalies
546
+
547
+ # Group consecutive samples
548
+ groups = []
549
+ current = [overshoots[0]]
550
+
551
+ for idx in overshoots[1:]:
552
+ if idx == current[-1] + 1:
553
+ current.append(idx)
554
+ else:
555
+ groups.append(current)
556
+ current = [idx]
557
+
558
+ groups.append(current)
559
+
560
+ # Report each overshoot event
561
+ for group in groups:
562
+ timestamp_us = (group[0] / sample_rate) * 1e6
563
+ peak_value = np.max(data[group])
564
+ percent_over = ((peak_value - high_rail) / high_rail) * 100
565
+
566
+ description = (
567
+ f"Signal exceeded expected high level by {percent_over:.0f}% (peak: {peak_value:.2f}V)"
568
+ )
569
+
570
+ # Severity based on overshoot magnitude
571
+ if percent_over > 20:
572
+ severity: Severity = "WARNING"
573
+ else:
574
+ severity = "INFO"
575
+
576
+ anomalies.append(
577
+ Anomaly(
578
+ timestamp_us=timestamp_us,
579
+ type="overshoot",
580
+ severity=severity,
581
+ description=description,
582
+ duration_ns=(len(group) / sample_rate) * 1e9,
583
+ confidence=0.82,
584
+ metadata={"peak_voltage": peak_value},
585
+ )
586
+ )
587
+
588
+ return anomalies[:10]
589
+
590
+
591
+ def _detect_undershoot(
592
+ data: NDArray[np.floating[Any]],
593
+ sample_rate: float,
594
+ voltage_swing: float,
595
+ stats: dict[str, float],
596
+ ) -> list[Anomaly]:
597
+ """Detect undershoot (>10% beyond low rail).
598
+
599
+ Args:
600
+ data: Signal data array.
601
+ sample_rate: Sample rate in Hz.
602
+ voltage_swing: Peak-to-peak voltage.
603
+ stats: Basic statistics.
604
+
605
+ Returns:
606
+ List of detected undershoot anomalies.
607
+ """
608
+ anomalies: list[Anomaly] = []
609
+
610
+ if voltage_swing == 0:
611
+ return anomalies
612
+
613
+ # Define expected low rail
614
+ low_rail = stats["min"] * 1.05 # Expected rail at 5th percentile
615
+ undershoot_threshold = low_rail * 0.9 # 10% below rail (more negative)
616
+
617
+ # Find undershoot samples
618
+ undershoots = np.where(data < undershoot_threshold)[0]
619
+
620
+ if len(undershoots) == 0:
621
+ return anomalies
622
+
623
+ # Group consecutive samples
624
+ groups = []
625
+ current = [undershoots[0]]
626
+
627
+ for idx in undershoots[1:]:
628
+ if idx == current[-1] + 1:
629
+ current.append(idx)
630
+ else:
631
+ groups.append(current)
632
+ current = [idx]
633
+
634
+ groups.append(current)
635
+
636
+ # Report each undershoot event
637
+ for group in groups:
638
+ timestamp_us = (group[0] / sample_rate) * 1e6
639
+ min_value = np.min(data[group])
640
+ percent_under = ((low_rail - min_value) / abs(low_rail)) * 100 if low_rail != 0 else 0
641
+
642
+ description = (
643
+ f"Signal fell below expected low level by {percent_under:.0f}% (min: {min_value:.2f}V)"
644
+ )
645
+
646
+ # Severity based on undershoot magnitude
647
+ if percent_under > 20:
648
+ severity: Severity = "WARNING"
649
+ else:
650
+ severity = "INFO"
651
+
652
+ anomalies.append(
653
+ Anomaly(
654
+ timestamp_us=timestamp_us,
655
+ type="undershoot",
656
+ severity=severity,
657
+ description=description,
658
+ duration_ns=(len(group) / sample_rate) * 1e9,
659
+ confidence=0.82,
660
+ metadata={"min_voltage": min_value},
661
+ )
662
+ )
663
+
664
+ return anomalies[:10]
665
+
666
+
667
+ __all__ = [
668
+ "Anomaly",
669
+ "AnomalyType",
670
+ "Severity",
671
+ "find_anomalies",
672
+ ]