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,725 @@
1
+ """Filter design functions for TraceKit.
2
+
3
+ Provides high-level filter design API with support for Butterworth,
4
+ Chebyshev, Bessel, and Elliptic filter types. Supports automatic
5
+ order calculation from specifications.
6
+
7
+
8
+ Example:
9
+ >>> from oscura.filtering.design import LowPassFilter, design_filter
10
+ >>> # Simple filter creation
11
+ >>> lpf = LowPassFilter(cutoff=1e6, sample_rate=10e6, order=4)
12
+ >>> # Spec-based design
13
+ >>> filt = design_filter_spec(
14
+ ... passband=1e6, stopband=2e6,
15
+ ... passband_ripple=1.0, stopband_atten=40.0,
16
+ ... sample_rate=10e6
17
+ ... )
18
+ """
19
+
20
+ from __future__ import annotations
21
+
22
+ from typing import Any, Literal
23
+
24
+ import numpy as np
25
+ from scipy import signal
26
+
27
+ from oscura.core.exceptions import AnalysisError
28
+ from oscura.filtering.base import IIRFilter
29
+
30
+ FilterType = Literal["butterworth", "chebyshev1", "chebyshev2", "bessel", "elliptic"]
31
+ BandType = Literal["lowpass", "highpass", "bandpass", "bandstop"]
32
+
33
+
34
+ def design_filter(
35
+ filter_type: FilterType,
36
+ cutoff: float | tuple[float, float],
37
+ sample_rate: float,
38
+ order: int,
39
+ btype: BandType = "lowpass",
40
+ *,
41
+ ripple_db: float = 1.0,
42
+ stopband_atten_db: float = 40.0,
43
+ analog: bool = False,
44
+ output: Literal["sos", "ba"] = "sos",
45
+ ) -> IIRFilter:
46
+ """Design an IIR filter with specified parameters.
47
+
48
+ Args:
49
+ filter_type: Type of filter ("butterworth", "chebyshev1", "chebyshev2",
50
+ "bessel", or "elliptic").
51
+ cutoff: Cutoff frequency in Hz. For bandpass/bandstop, tuple of (low, high).
52
+ sample_rate: Sample rate in Hz.
53
+ order: Filter order.
54
+ btype: Band type ("lowpass", "highpass", "bandpass", "bandstop").
55
+ ripple_db: Passband ripple in dB (for Chebyshev and Elliptic).
56
+ stopband_atten_db: Stopband attenuation in dB (for Chebyshev2 and Elliptic).
57
+ analog: If True, design analog filter (s-domain). Default is digital (z-domain).
58
+ output: Output format - "sos" for second-order sections (recommended),
59
+ "ba" for transfer function polynomials.
60
+
61
+ Returns:
62
+ IIRFilter object with designed coefficients.
63
+
64
+ Raises:
65
+ AnalysisError: If cutoff frequency is invalid or filter design fails.
66
+
67
+ Example:
68
+ >>> lpf = design_filter("butterworth", 1e6, 10e6, order=4)
69
+ >>> filtered = lpf.apply(trace)
70
+
71
+ References:
72
+ scipy.signal.iirfilter, butter, cheby1, cheby2, ellip, bessel
73
+ """
74
+ # Normalize cutoff frequency
75
+ if isinstance(cutoff, tuple):
76
+ Wn = cutoff if analog else (cutoff[0] / (sample_rate / 2), cutoff[1] / (sample_rate / 2))
77
+ else:
78
+ Wn = cutoff if analog else cutoff / (sample_rate / 2) # type: ignore[assignment]
79
+
80
+ # Validate normalized frequency
81
+ if not analog:
82
+ if isinstance(Wn, tuple):
83
+ if not (0 < Wn[0] < 1 and 0 < Wn[1] < 1):
84
+ raise AnalysisError(
85
+ f"Normalized cutoff must be in (0, 1), got {Wn}. "
86
+ f"Cutoff {cutoff} Hz must be less than Nyquist {sample_rate / 2} Hz."
87
+ )
88
+ elif not 0 < Wn < 1: # type: ignore[unreachable]
89
+ raise AnalysisError(
90
+ f"Normalized cutoff must be in (0, 1), got {Wn}. "
91
+ f"Cutoff {cutoff} Hz must be less than Nyquist {sample_rate / 2} Hz."
92
+ )
93
+
94
+ # Design filter
95
+ ftype_map = {
96
+ "butterworth": "butter",
97
+ "chebyshev1": "cheby1",
98
+ "chebyshev2": "cheby2",
99
+ "bessel": "bessel",
100
+ "elliptic": "ellip",
101
+ }
102
+ ftype = ftype_map.get(filter_type)
103
+ if ftype is None:
104
+ raise AnalysisError(f"Unknown filter type: {filter_type}")
105
+
106
+ try:
107
+ if filter_type == "butterworth":
108
+ if output == "sos":
109
+ sos = signal.butter(order, Wn, btype=btype, analog=analog, output="sos")
110
+ return IIRFilter(sample_rate=sample_rate, sos=sos)
111
+ else:
112
+ b, a = signal.butter(order, Wn, btype=btype, analog=analog, output="ba")
113
+ return IIRFilter(sample_rate=sample_rate, ba=(b, a))
114
+
115
+ elif filter_type == "chebyshev1":
116
+ if output == "sos":
117
+ sos = signal.cheby1(order, ripple_db, Wn, btype=btype, analog=analog, output="sos")
118
+ return IIRFilter(sample_rate=sample_rate, sos=sos)
119
+ else:
120
+ b, a = signal.cheby1(order, ripple_db, Wn, btype=btype, analog=analog, output="ba")
121
+ return IIRFilter(sample_rate=sample_rate, ba=(b, a))
122
+
123
+ elif filter_type == "chebyshev2":
124
+ if output == "sos":
125
+ sos = signal.cheby2(
126
+ order,
127
+ stopband_atten_db,
128
+ Wn,
129
+ btype=btype,
130
+ analog=analog,
131
+ output="sos",
132
+ )
133
+ return IIRFilter(sample_rate=sample_rate, sos=sos)
134
+ else:
135
+ b, a = signal.cheby2(
136
+ order,
137
+ stopband_atten_db,
138
+ Wn,
139
+ btype=btype,
140
+ analog=analog,
141
+ output="ba",
142
+ )
143
+ return IIRFilter(sample_rate=sample_rate, ba=(b, a))
144
+
145
+ elif filter_type == "bessel":
146
+ if output == "sos":
147
+ sos = signal.bessel(
148
+ order, Wn, btype=btype, analog=analog, output="sos", norm="phase"
149
+ )
150
+ return IIRFilter(sample_rate=sample_rate, sos=sos)
151
+ else:
152
+ b, a = signal.bessel(
153
+ order, Wn, btype=btype, analog=analog, output="ba", norm="phase"
154
+ )
155
+ return IIRFilter(sample_rate=sample_rate, ba=(b, a))
156
+
157
+ elif filter_type == "elliptic":
158
+ if output == "sos":
159
+ sos = signal.ellip(
160
+ order,
161
+ ripple_db,
162
+ stopband_atten_db,
163
+ Wn,
164
+ btype=btype,
165
+ analog=analog,
166
+ output="sos",
167
+ )
168
+ return IIRFilter(sample_rate=sample_rate, sos=sos)
169
+ else:
170
+ b, a = signal.ellip(
171
+ order,
172
+ ripple_db,
173
+ stopband_atten_db,
174
+ Wn,
175
+ btype=btype,
176
+ analog=analog,
177
+ output="ba",
178
+ )
179
+ return IIRFilter(sample_rate=sample_rate, ba=(b, a))
180
+
181
+ else:
182
+ raise AnalysisError(f"Unsupported filter type: {filter_type}")
183
+
184
+ except Exception as e:
185
+ raise AnalysisError(f"Filter design failed: {e}") from e
186
+
187
+
188
+ def design_filter_spec(
189
+ passband: float | tuple[float, float],
190
+ stopband: float | tuple[float, float],
191
+ sample_rate: float,
192
+ passband_ripple: float = 1.0,
193
+ stopband_atten: float = 40.0,
194
+ *,
195
+ filter_type: FilterType = "elliptic",
196
+ analog: bool = False,
197
+ ) -> IIRFilter:
198
+ """Design filter from passband/stopband specifications.
199
+
200
+ Automatically computes the minimum filter order required to meet
201
+ the specifications.
202
+
203
+ Args:
204
+ passband: Passband edge frequency in Hz. Tuple for bandpass/bandstop.
205
+ stopband: Stopband edge frequency in Hz. Tuple for bandpass/bandstop.
206
+ sample_rate: Sample rate in Hz.
207
+ passband_ripple: Maximum passband ripple in dB.
208
+ stopband_atten: Minimum stopband attenuation in dB.
209
+ filter_type: Filter type to design.
210
+ analog: If True, design analog filter.
211
+
212
+ Returns:
213
+ IIRFilter object with minimum-order design.
214
+
215
+ Raises:
216
+ AnalysisError: If filter order cannot be determined.
217
+
218
+ Example:
219
+ >>> # Design a filter with 1MHz passband, 2MHz stopband, 40dB rejection
220
+ >>> filt = design_filter_spec(
221
+ ... passband=1e6, stopband=2e6,
222
+ ... passband_ripple=1.0, stopband_atten=40.0,
223
+ ... sample_rate=10e6
224
+ ... )
225
+ """
226
+ # Normalize frequencies
227
+ if isinstance(passband, tuple):
228
+ wp = (passband[0] / (sample_rate / 2), passband[1] / (sample_rate / 2))
229
+ ws = (stopband[0] / (sample_rate / 2), stopband[1] / (sample_rate / 2)) # type: ignore[index]
230
+ else:
231
+ wp = passband / (sample_rate / 2) # type: ignore[assignment]
232
+ ws = stopband / (sample_rate / 2) # type: ignore[assignment, operator]
233
+
234
+ # Determine band type
235
+ if isinstance(passband, tuple):
236
+ # Bandpass or bandstop
237
+ if passband[0] < stopband[0]: # type: ignore[index]
238
+ btype: BandType = "bandstop"
239
+ else:
240
+ btype = "bandpass"
241
+ # Lowpass or highpass
242
+ elif passband < stopband: # type: ignore[operator]
243
+ btype = "lowpass"
244
+ else:
245
+ btype = "highpass"
246
+
247
+ # Compute minimum order
248
+ try:
249
+ if filter_type == "butterworth":
250
+ order, Wn = signal.buttord(wp, ws, passband_ripple, stopband_atten, analog=analog)
251
+ elif filter_type == "chebyshev1":
252
+ order, Wn = signal.cheb1ord(wp, ws, passband_ripple, stopband_atten, analog=analog)
253
+ elif filter_type == "chebyshev2":
254
+ order, Wn = signal.cheb2ord(wp, ws, passband_ripple, stopband_atten, analog=analog)
255
+ elif filter_type == "elliptic":
256
+ order, Wn = signal.ellipord(wp, ws, passband_ripple, stopband_atten, analog=analog)
257
+ else:
258
+ # Bessel doesn't have an ord function, estimate based on Butterworth
259
+ order, Wn = signal.buttord(wp, ws, passband_ripple, stopband_atten, analog=analog)
260
+
261
+ except Exception as e:
262
+ raise AnalysisError(f"Could not determine filter order: {e}") from e
263
+
264
+ # Design with computed order
265
+ cutoff = (
266
+ tuple(w * sample_rate / 2 for w in Wn)
267
+ if isinstance(Wn, np.ndarray)
268
+ else Wn * sample_rate / 2
269
+ )
270
+
271
+ return design_filter(
272
+ filter_type=filter_type,
273
+ cutoff=cutoff,
274
+ sample_rate=sample_rate,
275
+ order=int(order),
276
+ btype=btype,
277
+ ripple_db=passband_ripple,
278
+ stopband_atten_db=stopband_atten,
279
+ analog=analog,
280
+ )
281
+
282
+
283
+ # =============================================================================
284
+ # Convenience Filter Classes
285
+ # =============================================================================
286
+
287
+
288
+ class LowPassFilter(IIRFilter):
289
+ """Low-pass Butterworth filter.
290
+
291
+ Convenient class for creating low-pass filters with sensible defaults.
292
+
293
+ Example:
294
+ >>> lpf = LowPassFilter(cutoff=1e6, sample_rate=10e6, order=4)
295
+ >>> filtered = lpf.apply(trace)
296
+ """
297
+
298
+ def __init__(
299
+ self,
300
+ cutoff: float,
301
+ sample_rate: float,
302
+ order: int = 4,
303
+ *,
304
+ filter_type: FilterType = "butterworth",
305
+ ripple_db: float = 1.0,
306
+ stopband_atten_db: float = 40.0,
307
+ ) -> None:
308
+ """Initialize low-pass filter.
309
+
310
+ Args:
311
+ cutoff: Cutoff frequency in Hz (-3dB point for Butterworth).
312
+ sample_rate: Sample rate in Hz.
313
+ order: Filter order.
314
+ filter_type: Type of filter to use.
315
+ ripple_db: Passband ripple for Chebyshev/Elliptic.
316
+ stopband_atten_db: Stopband attenuation for Chebyshev2/Elliptic.
317
+ """
318
+ filt = design_filter(
319
+ filter_type=filter_type,
320
+ cutoff=cutoff,
321
+ sample_rate=sample_rate,
322
+ order=order,
323
+ btype="lowpass",
324
+ ripple_db=ripple_db,
325
+ stopband_atten_db=stopband_atten_db,
326
+ )
327
+ super().__init__(sample_rate=sample_rate, sos=filt.sos)
328
+ self._cutoff = cutoff
329
+ self._filter_type = filter_type
330
+
331
+ @property
332
+ def cutoff(self) -> float:
333
+ """Cutoff frequency in Hz."""
334
+ return self._cutoff
335
+
336
+
337
+ class HighPassFilter(IIRFilter):
338
+ """High-pass Butterworth filter.
339
+
340
+ Example:
341
+ >>> hpf = HighPassFilter(cutoff=1e3, sample_rate=100e3, order=4)
342
+ >>> filtered = hpf.apply(trace)
343
+ """
344
+
345
+ def __init__(
346
+ self,
347
+ cutoff: float,
348
+ sample_rate: float,
349
+ order: int = 4,
350
+ *,
351
+ filter_type: FilterType = "butterworth",
352
+ ripple_db: float = 1.0,
353
+ stopband_atten_db: float = 40.0,
354
+ ) -> None:
355
+ """Initialize high-pass filter."""
356
+ filt = design_filter(
357
+ filter_type=filter_type,
358
+ cutoff=cutoff,
359
+ sample_rate=sample_rate,
360
+ order=order,
361
+ btype="highpass",
362
+ ripple_db=ripple_db,
363
+ stopband_atten_db=stopband_atten_db,
364
+ )
365
+ super().__init__(sample_rate=sample_rate, sos=filt.sos)
366
+ self._cutoff = cutoff
367
+
368
+ @property
369
+ def cutoff(self) -> float:
370
+ """Cutoff frequency in Hz."""
371
+ return self._cutoff
372
+
373
+
374
+ class BandPassFilter(IIRFilter):
375
+ """Band-pass filter.
376
+
377
+ Example:
378
+ >>> bpf = BandPassFilter(low=1e3, high=10e3, sample_rate=100e3, order=4)
379
+ >>> filtered = bpf.apply(trace)
380
+ """
381
+
382
+ def __init__(
383
+ self,
384
+ low: float,
385
+ high: float,
386
+ sample_rate: float,
387
+ order: int = 4,
388
+ *,
389
+ filter_type: FilterType = "butterworth",
390
+ ripple_db: float = 1.0,
391
+ stopband_atten_db: float = 40.0,
392
+ ) -> None:
393
+ """Initialize band-pass filter."""
394
+ filt = design_filter(
395
+ filter_type=filter_type,
396
+ cutoff=(low, high),
397
+ sample_rate=sample_rate,
398
+ order=order,
399
+ btype="bandpass",
400
+ ripple_db=ripple_db,
401
+ stopband_atten_db=stopband_atten_db,
402
+ )
403
+ super().__init__(sample_rate=sample_rate, sos=filt.sos)
404
+ self._low = low
405
+ self._high = high
406
+
407
+ @property
408
+ def passband(self) -> tuple[float, float]:
409
+ """Passband frequencies (low, high) in Hz."""
410
+ return (self._low, self._high)
411
+
412
+
413
+ class BandStopFilter(IIRFilter):
414
+ """Band-stop (notch) filter.
415
+
416
+ Example:
417
+ >>> bsf = BandStopFilter(low=50, high=60, sample_rate=1000, order=4)
418
+ >>> filtered = bsf.apply(trace) # Remove 50-60 Hz interference
419
+ """
420
+
421
+ def __init__(
422
+ self,
423
+ low: float,
424
+ high: float,
425
+ sample_rate: float,
426
+ order: int = 4,
427
+ *,
428
+ filter_type: FilterType = "butterworth",
429
+ ripple_db: float = 1.0,
430
+ stopband_atten_db: float = 40.0,
431
+ ) -> None:
432
+ """Initialize band-stop filter."""
433
+ filt = design_filter(
434
+ filter_type=filter_type,
435
+ cutoff=(low, high),
436
+ sample_rate=sample_rate,
437
+ order=order,
438
+ btype="bandstop",
439
+ ripple_db=ripple_db,
440
+ stopband_atten_db=stopband_atten_db,
441
+ )
442
+ super().__init__(sample_rate=sample_rate, sos=filt.sos)
443
+ self._low = low
444
+ self._high = high
445
+
446
+ @property
447
+ def stopband(self) -> tuple[float, float]:
448
+ """Stopband frequencies (low, high) in Hz."""
449
+ return (self._low, self._high)
450
+
451
+
452
+ # =============================================================================
453
+ # Filter Type Classes
454
+ # =============================================================================
455
+
456
+
457
+ class ButterworthFilter(IIRFilter):
458
+ """Butterworth filter with maximally flat passband.
459
+
460
+ Example:
461
+ >>> filt = ButterworthFilter(cutoff=1e6, sample_rate=10e6, order=4, btype="lowpass")
462
+ """
463
+
464
+ def __init__(
465
+ self,
466
+ cutoff: float | tuple[float, float],
467
+ sample_rate: float,
468
+ order: int = 4,
469
+ btype: BandType = "lowpass",
470
+ ) -> None:
471
+ filt = design_filter("butterworth", cutoff, sample_rate, order, btype)
472
+ super().__init__(sample_rate=sample_rate, sos=filt.sos)
473
+
474
+
475
+ class ChebyshevType1Filter(IIRFilter):
476
+ """Chebyshev Type I filter with passband ripple.
477
+
478
+ Example:
479
+ >>> filt = ChebyshevType1Filter(cutoff=1e6, sample_rate=10e6, order=4, ripple_db=0.5)
480
+ """
481
+
482
+ def __init__(
483
+ self,
484
+ cutoff: float | tuple[float, float],
485
+ sample_rate: float,
486
+ order: int = 4,
487
+ btype: BandType = "lowpass",
488
+ ripple_db: float = 1.0,
489
+ ) -> None:
490
+ filt = design_filter("chebyshev1", cutoff, sample_rate, order, btype, ripple_db=ripple_db)
491
+ super().__init__(sample_rate=sample_rate, sos=filt.sos)
492
+
493
+
494
+ class ChebyshevType2Filter(IIRFilter):
495
+ """Chebyshev Type II filter with stopband ripple.
496
+
497
+ Example:
498
+ >>> filt = ChebyshevType2Filter(cutoff=1e6, sample_rate=10e6, order=4, stopband_atten_db=40)
499
+ """
500
+
501
+ def __init__(
502
+ self,
503
+ cutoff: float | tuple[float, float],
504
+ sample_rate: float,
505
+ order: int = 4,
506
+ btype: BandType = "lowpass",
507
+ stopband_atten_db: float = 40.0,
508
+ ) -> None:
509
+ filt = design_filter(
510
+ "chebyshev2",
511
+ cutoff,
512
+ sample_rate,
513
+ order,
514
+ btype,
515
+ stopband_atten_db=stopband_atten_db,
516
+ )
517
+ super().__init__(sample_rate=sample_rate, sos=filt.sos)
518
+
519
+
520
+ class BesselFilter(IIRFilter):
521
+ """Bessel filter with maximally flat group delay.
522
+
523
+ Best for preserving waveform shape during filtering.
524
+
525
+ Example:
526
+ >>> filt = BesselFilter(cutoff=1e6, sample_rate=10e6, order=4)
527
+ """
528
+
529
+ def __init__(
530
+ self,
531
+ cutoff: float | tuple[float, float],
532
+ sample_rate: float,
533
+ order: int = 4,
534
+ btype: BandType = "lowpass",
535
+ ) -> None:
536
+ filt = design_filter("bessel", cutoff, sample_rate, order, btype)
537
+ super().__init__(sample_rate=sample_rate, sos=filt.sos)
538
+
539
+
540
+ class EllipticFilter(IIRFilter):
541
+ """Elliptic (Cauer) filter with equiripple passband and stopband.
542
+
543
+ Provides the sharpest transition band for a given order.
544
+
545
+ Example:
546
+ >>> filt = EllipticFilter(cutoff=1e6, sample_rate=10e6, order=4,
547
+ ... ripple_db=0.5, stopband_atten_db=60)
548
+ """
549
+
550
+ def __init__(
551
+ self,
552
+ cutoff: float | tuple[float, float],
553
+ sample_rate: float,
554
+ order: int = 4,
555
+ btype: BandType = "lowpass",
556
+ ripple_db: float = 1.0,
557
+ stopband_atten_db: float = 40.0,
558
+ ) -> None:
559
+ filt = design_filter(
560
+ "elliptic",
561
+ cutoff,
562
+ sample_rate,
563
+ order,
564
+ btype,
565
+ ripple_db=ripple_db,
566
+ stopband_atten_db=stopband_atten_db,
567
+ )
568
+ super().__init__(sample_rate=sample_rate, sos=filt.sos)
569
+
570
+
571
+ def suggest_filter_type(
572
+ transition_bandwidth: float,
573
+ passband_ripple_db: float,
574
+ stopband_atten_db: float,
575
+ ) -> FilterType:
576
+ """Suggest best filter type based on requirements.
577
+
578
+ Recommends filter type based on design tradeoffs:
579
+ - Butterworth: Maximally flat passband, moderate rolloff
580
+ - Chebyshev1: Faster rolloff, passband ripple
581
+ - Chebyshev2: Faster rolloff, stopband ripple
582
+ - Elliptic: Sharpest rolloff, both passband and stopband ripple
583
+ - Bessel: Linear phase, slowest rolloff (for waveform preservation)
584
+
585
+ Args:
586
+ transition_bandwidth: Normalized transition bandwidth (stopband - passband) / sample_rate.
587
+ passband_ripple_db: Acceptable passband ripple in dB.
588
+ stopband_atten_db: Required stopband attenuation in dB.
589
+
590
+ Returns:
591
+ Recommended filter type.
592
+
593
+ Example:
594
+ >>> # Sharp transition, can tolerate some ripple
595
+ >>> ftype = suggest_filter_type(
596
+ ... transition_bandwidth=0.1,
597
+ ... passband_ripple_db=0.5,
598
+ ... stopband_atten_db=60.0
599
+ ... )
600
+ >>> print(ftype) # 'elliptic'
601
+
602
+ References:
603
+ API-020: Filter Design Auto-Order
604
+ """
605
+ # For very sharp transitions with ripple tolerance, use elliptic
606
+ if transition_bandwidth < 0.15 and passband_ripple_db >= 0.1:
607
+ return "elliptic"
608
+
609
+ # For moderate sharpness with low passband ripple, use Chebyshev2
610
+ if transition_bandwidth < 0.2 and passband_ripple_db < 0.1:
611
+ return "chebyshev2"
612
+
613
+ # For moderate sharpness with ripple tolerance, use Chebyshev1
614
+ if transition_bandwidth < 0.3 and passband_ripple_db >= 0.1:
615
+ return "chebyshev1"
616
+
617
+ # For phase linearity (waveform preservation), use Bessel
618
+ if stopband_atten_db < 40.0:
619
+ return "bessel"
620
+
621
+ # Default to Butterworth for balanced performance
622
+ return "butterworth"
623
+
624
+
625
+ def auto_design_filter(
626
+ passband: float | tuple[float, float],
627
+ stopband: float | tuple[float, float],
628
+ sample_rate: float,
629
+ *,
630
+ passband_ripple_db: float = 1.0,
631
+ stopband_atten_db: float = 40.0,
632
+ suggest_type: bool = True,
633
+ ) -> tuple[IIRFilter, dict[str, Any]]:
634
+ """Automatically design optimal filter from specifications.
635
+
636
+ Automatically computes filter order and optionally suggests the best
637
+ filter type based on transition band and ripple requirements.
638
+
639
+ Args:
640
+ passband: Passband edge frequency in Hz. Tuple for bandpass/bandstop.
641
+ stopband: Stopband edge frequency in Hz. Tuple for bandpass/bandstop.
642
+ sample_rate: Sample rate in Hz.
643
+ passband_ripple_db: Maximum passband ripple in dB (default: 1.0).
644
+ stopband_atten_db: Minimum stopband attenuation in dB (default: 40.0).
645
+ suggest_type: If True, automatically suggest filter type (default: True).
646
+
647
+ Returns:
648
+ Tuple of (IIRFilter, design_info_dict).
649
+ design_info_dict contains: filter_type, order, cutoff, transition_bandwidth.
650
+
651
+ Example:
652
+ >>> # Automatic filter design with type suggestion
653
+ >>> filt, info = auto_design_filter(
654
+ ... passband=1e6,
655
+ ... stopband=1.5e6,
656
+ ... sample_rate=10e6,
657
+ ... stopband_atten_db=60.0
658
+ ... )
659
+ >>> print(f"Designed {info['filter_type']} filter with order {info['order']}")
660
+ >>> filtered = filt.apply(trace)
661
+
662
+ References:
663
+ API-020: Filter Design Auto-Order
664
+ """
665
+ # Compute transition bandwidth
666
+ if isinstance(passband, tuple):
667
+ # Bandpass/bandstop - use average
668
+ transition_bw = (
669
+ abs(stopband[0] - passband[0]) + abs(stopband[1] - passband[1]) # type: ignore[index]
670
+ ) / 2.0
671
+ else:
672
+ transition_bw = abs(stopband - passband) # type: ignore[operator]
673
+
674
+ normalized_transition = transition_bw / sample_rate
675
+
676
+ # Suggest filter type if requested
677
+ if suggest_type:
678
+ filter_type = suggest_filter_type(
679
+ transition_bandwidth=normalized_transition,
680
+ passband_ripple_db=passband_ripple_db,
681
+ stopband_atten_db=stopband_atten_db,
682
+ )
683
+ else:
684
+ filter_type = "butterworth"
685
+
686
+ # Design filter with auto-order computation
687
+ filt = design_filter_spec(
688
+ passband=passband,
689
+ stopband=stopband,
690
+ sample_rate=sample_rate,
691
+ passband_ripple=passband_ripple_db,
692
+ stopband_atten=stopband_atten_db,
693
+ filter_type=filter_type,
694
+ )
695
+
696
+ # Extract design info
697
+ design_info = {
698
+ "filter_type": filter_type,
699
+ "order": filt.sos.shape[0] * 2 if filt.sos is not None else 0,
700
+ "cutoff": passband,
701
+ "transition_bandwidth": transition_bw,
702
+ "passband_ripple_db": passband_ripple_db,
703
+ "stopband_atten_db": stopband_atten_db,
704
+ }
705
+
706
+ return filt, design_info
707
+
708
+
709
+ __all__ = [
710
+ "BandPassFilter",
711
+ "BandStopFilter",
712
+ "BandType",
713
+ "BesselFilter",
714
+ "ButterworthFilter",
715
+ "ChebyshevType1Filter",
716
+ "ChebyshevType2Filter",
717
+ "EllipticFilter",
718
+ "FilterType",
719
+ "HighPassFilter",
720
+ "LowPassFilter",
721
+ "auto_design_filter",
722
+ "design_filter",
723
+ "design_filter_spec",
724
+ "suggest_filter_type",
725
+ ]