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,294 @@
1
+ """Channel embedding and de-embedding functions.
2
+
3
+ This module provides time-domain de-embedding to remove fixture
4
+ effects and embedding to simulate channel effects.
5
+
6
+
7
+ Example:
8
+ >>> from oscura.analyzers.signal_integrity.embedding import deembed
9
+ >>> clean_trace = deembed(trace, s_params)
10
+
11
+ References:
12
+ IEEE 370-2020: Standard for Electrical Characterization of PCBs
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import numpy as np
18
+
19
+ from oscura.analyzers.signal_integrity.sparams import (
20
+ SParameterData,
21
+ abcd_to_s,
22
+ s_to_abcd,
23
+ )
24
+ from oscura.core.exceptions import AnalysisError
25
+ from oscura.core.types import WaveformTrace
26
+
27
+
28
+ def deembed(
29
+ trace: WaveformTrace,
30
+ s_params: SParameterData,
31
+ *,
32
+ method: str = "frequency_domain",
33
+ regularization: float = 1e-6,
34
+ ) -> WaveformTrace:
35
+ """Remove fixture effects from waveform using S-parameters.
36
+
37
+ Applies the inverse of the fixture transfer function in the
38
+ frequency domain to recover the signal at the DUT reference plane.
39
+
40
+ Args:
41
+ trace: Input waveform trace.
42
+ s_params: S-parameters of fixture to remove.
43
+ method: De-embedding method ("frequency_domain" or "time_domain").
44
+ regularization: Regularization for matrix inversion.
45
+
46
+ Returns:
47
+ De-embedded waveform trace.
48
+
49
+ Raises:
50
+ ValueError: If method is unknown.
51
+ AnalysisError: If de-embedding fails.
52
+
53
+ Example:
54
+ >>> clean = deembed(measured_trace, fixture_sparams)
55
+ >>> # clean now has fixture effects removed
56
+
57
+ References:
58
+ IEEE 370-2020 Section 7
59
+ """
60
+ if s_params.n_ports != 2:
61
+ raise AnalysisError(
62
+ f"De-embedding requires 2-port S-parameters, got {s_params.n_ports}-port"
63
+ )
64
+
65
+ if method == "frequency_domain":
66
+ return _deembed_frequency_domain(trace, s_params, regularization)
67
+ elif method == "time_domain":
68
+ return _deembed_time_domain(trace, s_params)
69
+ else:
70
+ raise ValueError(f"Unknown method: {method}")
71
+
72
+
73
+ def _deembed_frequency_domain(
74
+ trace: WaveformTrace,
75
+ s_params: SParameterData,
76
+ regularization: float,
77
+ ) -> WaveformTrace:
78
+ """De-embed using frequency domain approach."""
79
+ data = trace.data
80
+ sample_rate = trace.metadata.sample_rate
81
+ n = len(data)
82
+
83
+ # Compute FFT of input signal
84
+ signal_fft = np.fft.rfft(data)
85
+ frequencies = np.fft.rfftfreq(n, d=1.0 / sample_rate)
86
+
87
+ # Interpolate S21 to signal frequencies
88
+ s21 = np.interp(
89
+ frequencies,
90
+ s_params.frequencies,
91
+ s_params.s_matrix[:, 1, 0],
92
+ )
93
+
94
+ # Apply inverse transfer function with regularization
95
+ # H_inv = 1 / S21, but regularized
96
+ magnitude = np.abs(s21)
97
+ magnitude_reg = np.maximum(magnitude, regularization)
98
+ h_inv = np.conj(s21) / (magnitude_reg**2 + regularization)
99
+
100
+ # Apply inverse filter
101
+ deembedded_fft = signal_fft * h_inv
102
+
103
+ # Inverse FFT
104
+ deembedded_data = np.fft.irfft(deembedded_fft, n=n)
105
+
106
+ return WaveformTrace(
107
+ data=deembedded_data.astype(np.float64),
108
+ metadata=trace.metadata,
109
+ )
110
+
111
+
112
+ def _deembed_time_domain(
113
+ trace: WaveformTrace,
114
+ s_params: SParameterData,
115
+ ) -> WaveformTrace:
116
+ """De-embed using time domain impulse response."""
117
+ # Convert S-parameters to impulse response
118
+ s21 = s_params.s_matrix[:, 1, 0]
119
+ frequencies = s_params.frequencies
120
+
121
+ # Create symmetric frequency axis for IFFT
122
+ n_freq = len(frequencies)
123
+ 2 * (n_freq - 1)
124
+
125
+ # Pad with conjugate symmetric extension
126
+ s21_symmetric = np.concatenate([s21, np.conj(s21[-2:0:-1])])
127
+
128
+ # IFFT to get impulse response
129
+ impulse_response = np.fft.ifft(s21_symmetric).real
130
+
131
+ # Create inverse filter (approximate)
132
+ # Use Wiener deconvolution approach
133
+ data = trace.data
134
+ n = len(data)
135
+
136
+ # Pad impulse response
137
+ ir_padded = np.zeros(n)
138
+ ir_len = min(len(impulse_response), n)
139
+ ir_padded[:ir_len] = impulse_response[:ir_len]
140
+
141
+ # FFT-based deconvolution
142
+ data_fft = np.fft.fft(data)
143
+ ir_fft = np.fft.fft(ir_padded)
144
+
145
+ # Wiener filter
146
+ noise_power = 0.01 # Assumed noise level
147
+ ir_power = np.abs(ir_fft) ** 2
148
+ wiener = np.conj(ir_fft) / (ir_power + noise_power)
149
+
150
+ deembedded_fft = data_fft * wiener
151
+ deembedded_data = np.fft.ifft(deembedded_fft).real
152
+
153
+ return WaveformTrace(
154
+ data=deembedded_data.astype(np.float64),
155
+ metadata=trace.metadata,
156
+ )
157
+
158
+
159
+ def embed(
160
+ trace: WaveformTrace,
161
+ s_params: SParameterData,
162
+ ) -> WaveformTrace:
163
+ """Apply channel effects to waveform using S-parameters.
164
+
165
+ Convolves the signal with the channel impulse response
166
+ derived from S21 to simulate channel effects.
167
+
168
+ Args:
169
+ trace: Input (ideal) waveform trace.
170
+ s_params: S-parameters of channel to apply.
171
+
172
+ Returns:
173
+ Waveform with channel effects applied.
174
+
175
+ Raises:
176
+ AnalysisError: If embedding fails.
177
+
178
+ Example:
179
+ >>> degraded = embed(ideal_trace, channel_sparams)
180
+ >>> # degraded now has channel ISI/loss
181
+
182
+ References:
183
+ IEEE 370-2020 Section 7
184
+ """
185
+ if s_params.n_ports != 2:
186
+ raise AnalysisError(f"Embedding requires 2-port S-parameters, got {s_params.n_ports}-port")
187
+
188
+ data = trace.data
189
+ sample_rate = trace.metadata.sample_rate
190
+ n = len(data)
191
+
192
+ # Compute FFT
193
+ signal_fft = np.fft.rfft(data)
194
+ frequencies = np.fft.rfftfreq(n, d=1.0 / sample_rate)
195
+
196
+ # Interpolate S21 to signal frequencies
197
+ s21 = np.interp(
198
+ frequencies,
199
+ s_params.frequencies,
200
+ s_params.s_matrix[:, 1, 0],
201
+ )
202
+
203
+ # Apply transfer function
204
+ embedded_fft = signal_fft * s21
205
+
206
+ # Inverse FFT
207
+ embedded_data = np.fft.irfft(embedded_fft, n=n)
208
+
209
+ return WaveformTrace(
210
+ data=embedded_data.astype(np.float64),
211
+ metadata=trace.metadata,
212
+ )
213
+
214
+
215
+ def cascade_deembed(
216
+ trace: WaveformTrace,
217
+ fixtures: list[SParameterData],
218
+ *,
219
+ regularization: float = 1e-6,
220
+ ) -> WaveformTrace:
221
+ """Remove multiple fixture effects from waveform.
222
+
223
+ Cascades multiple fixtures and removes their combined effect
224
+ using ABCD matrix multiplication.
225
+
226
+ Args:
227
+ trace: Input waveform trace.
228
+ fixtures: List of S-parameter fixtures to remove.
229
+ regularization: Regularization for matrix inversion.
230
+
231
+ Returns:
232
+ De-embedded waveform trace.
233
+
234
+ Example:
235
+ >>> clean = cascade_deembed(trace, [fixture1, fixture2])
236
+
237
+ References:
238
+ IEEE 370-2020 Section 7.3
239
+ """
240
+ if len(fixtures) == 0:
241
+ return trace
242
+
243
+ if len(fixtures) == 1:
244
+ return deembed(trace, fixtures[0], regularization=regularization)
245
+
246
+ # Find common frequency points
247
+ all_freqs = [f.frequencies for f in fixtures]
248
+ min_freq = max(f.min() for f in all_freqs)
249
+ max_freq = min(f.max() for f in all_freqs)
250
+ n_freq = min(len(f) for f in all_freqs)
251
+
252
+ common_freqs = np.linspace(min_freq, max_freq, n_freq)
253
+
254
+ # Convert each fixture to ABCD and cascade
255
+ abcd_cascade = np.zeros((n_freq, 2, 2), dtype=np.complex128)
256
+ abcd_cascade[:, 0, 0] = 1
257
+ abcd_cascade[:, 1, 1] = 1 # Identity matrix
258
+
259
+ for fixture in fixtures:
260
+ abcd = s_to_abcd(fixture)
261
+
262
+ # Interpolate to common frequencies
263
+ abcd_interp = np.zeros((n_freq, 2, 2), dtype=np.complex128)
264
+ for i in range(2):
265
+ for j in range(2):
266
+ abcd_interp[:, i, j] = np.interp(
267
+ common_freqs,
268
+ fixture.frequencies,
269
+ abcd[:, i, j],
270
+ )
271
+
272
+ # Matrix multiply for each frequency
273
+ for f_idx in range(n_freq):
274
+ abcd_cascade[f_idx] = abcd_cascade[f_idx] @ abcd_interp[f_idx]
275
+
276
+ # Convert cascaded ABCD back to S-parameters
277
+ s_cascade = abcd_to_s(abcd_cascade, z0=fixtures[0].z0)
278
+
279
+ # Create combined S-parameter object
280
+ combined = SParameterData(
281
+ frequencies=common_freqs,
282
+ s_matrix=s_cascade,
283
+ n_ports=2,
284
+ z0=fixtures[0].z0,
285
+ )
286
+
287
+ return deembed(trace, combined, regularization=regularization)
288
+
289
+
290
+ __all__ = [
291
+ "cascade_deembed",
292
+ "deembed",
293
+ "embed",
294
+ ]
@@ -0,0 +1,370 @@
1
+ """Equalization algorithms for signal integrity.
2
+
3
+ This module provides FFE, DFE, and CTLE equalization to
4
+ compensate for channel loss and ISI.
5
+
6
+
7
+ Example:
8
+ >>> from oscura.analyzers.signal_integrity.equalization import ffe_equalize
9
+ >>> equalized = ffe_equalize(trace, taps=[-0.1, 1.0, -0.1])
10
+
11
+ References:
12
+ IEEE 802.3: Ethernet PHY Equalization Requirements
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ from dataclasses import dataclass
18
+ from typing import TYPE_CHECKING
19
+
20
+ import numpy as np
21
+ from scipy import optimize
22
+ from scipy import signal as scipy_signal
23
+
24
+ if TYPE_CHECKING:
25
+ from numpy.typing import NDArray
26
+
27
+ from oscura.core.types import WaveformTrace
28
+
29
+
30
+ @dataclass
31
+ class FFEResult:
32
+ """Result of FFE equalization.
33
+
34
+ Attributes:
35
+ equalized_data: Equalized waveform data.
36
+ taps: FFE tap coefficients used.
37
+ n_precursor: Number of precursor taps.
38
+ n_postcursor: Number of postcursor taps.
39
+ mse: Mean squared error (if optimized).
40
+ """
41
+
42
+ equalized_data: NDArray[np.float64]
43
+ taps: NDArray[np.float64]
44
+ n_precursor: int
45
+ n_postcursor: int
46
+ mse: float | None = None
47
+
48
+
49
+ @dataclass
50
+ class DFEResult:
51
+ """Result of DFE equalization.
52
+
53
+ Attributes:
54
+ equalized_data: Equalized waveform data.
55
+ taps: DFE tap coefficients.
56
+ decisions: Decoded bit decisions.
57
+ n_taps: Number of DFE taps.
58
+ error_count: Number of decision errors (if reference known).
59
+ """
60
+
61
+ equalized_data: NDArray[np.float64]
62
+ taps: NDArray[np.float64]
63
+ decisions: NDArray[np.int_] | None
64
+ n_taps: int
65
+ error_count: int | None = None
66
+
67
+
68
+ @dataclass
69
+ class CTLEResult:
70
+ """Result of CTLE equalization.
71
+
72
+ Attributes:
73
+ equalized_data: Equalized waveform data.
74
+ dc_gain: DC gain in dB.
75
+ ac_gain: AC (peaking) gain in dB.
76
+ pole_frequency: Pole frequency in Hz.
77
+ zero_frequency: Zero frequency in Hz.
78
+ boost: High-frequency boost in dB.
79
+ """
80
+
81
+ equalized_data: NDArray[np.float64]
82
+ dc_gain: float
83
+ ac_gain: float
84
+ pole_frequency: float
85
+ zero_frequency: float | None
86
+ boost: float
87
+
88
+
89
+ def ffe_equalize(
90
+ trace: WaveformTrace,
91
+ taps: list[float] | NDArray[np.float64],
92
+ *,
93
+ samples_per_symbol: int | None = None,
94
+ ) -> FFEResult:
95
+ """Apply Feed-Forward Equalization to waveform.
96
+
97
+ FFE uses a linear FIR filter to compensate for channel ISI.
98
+ The main cursor (largest tap) should be 1.0 for unity gain.
99
+
100
+ Args:
101
+ trace: Input waveform trace.
102
+ taps: FFE tap coefficients (main cursor should be 1.0).
103
+ samples_per_symbol: Samples per UI (auto-detected if None).
104
+
105
+ Returns:
106
+ FFEResult with equalized data.
107
+
108
+ Example:
109
+ >>> result = ffe_equalize(trace, taps=[-0.1, 1.0, -0.1])
110
+ >>> # 3-tap equalizer: 1 precursor, 1 main, 1 postcursor
111
+
112
+ References:
113
+ IEEE 802.3 Clause 93
114
+ """
115
+ taps = np.array(taps, dtype=np.float64)
116
+
117
+ # Find main cursor position
118
+ main_idx = int(np.argmax(np.abs(taps)))
119
+ n_precursor = main_idx
120
+ n_postcursor = len(taps) - main_idx - 1
121
+
122
+ # Apply FIR filter
123
+ data = trace.data
124
+ equalized = np.convolve(data, taps, mode="same")
125
+
126
+ return FFEResult(
127
+ equalized_data=equalized,
128
+ taps=taps,
129
+ n_precursor=n_precursor,
130
+ n_postcursor=n_postcursor,
131
+ )
132
+
133
+
134
+ def optimize_ffe(
135
+ trace: WaveformTrace,
136
+ n_taps: int = 5,
137
+ *,
138
+ n_precursor: int = 1,
139
+ samples_per_symbol: int | None = None,
140
+ target: NDArray[np.float64] | None = None,
141
+ ) -> FFEResult:
142
+ """Find optimal FFE tap coefficients.
143
+
144
+ Uses least-squares optimization to find taps that minimize
145
+ ISI and maximize eye opening.
146
+
147
+ Args:
148
+ trace: Input waveform trace.
149
+ n_taps: Total number of FFE taps.
150
+ n_precursor: Number of precursor taps.
151
+ samples_per_symbol: Samples per UI.
152
+ target: Target (ideal) waveform for optimization.
153
+
154
+ Returns:
155
+ FFEResult with optimized taps.
156
+
157
+ Example:
158
+ >>> result = optimize_ffe(trace, n_taps=5, n_precursor=1)
159
+ >>> print(f"Optimal taps: {result.taps}")
160
+ """
161
+ data = trace.data
162
+ len(data)
163
+
164
+ if target is None:
165
+ # Create target from sliced data (simplified)
166
+ # Use a decision slicer approach
167
+ threshold = np.median(data)
168
+ target = np.where(data > threshold, 1.0, -1.0)
169
+
170
+ def objective(taps): # type: ignore[no-untyped-def]
171
+ """Minimize MSE between equalized and target."""
172
+ equalized = np.convolve(data, taps, mode="same")
173
+ mse = np.mean((equalized - target) ** 2)
174
+ return mse
175
+
176
+ # Initial guess: main cursor at 1.0, others small
177
+ n_postcursor = n_taps - n_precursor - 1
178
+ x0 = np.zeros(n_taps)
179
+ x0[n_precursor] = 1.0
180
+
181
+ # Constraints: limit tap magnitude
182
+ bounds = [(-2.0, 2.0)] * n_taps
183
+ bounds[n_precursor] = (0.5, 1.5) # Main cursor near 1.0
184
+
185
+ result = optimize.minimize(
186
+ objective,
187
+ x0,
188
+ method="L-BFGS-B",
189
+ bounds=bounds,
190
+ options={"maxiter": 100},
191
+ )
192
+
193
+ optimal_taps = result.x
194
+
195
+ # Normalize so main cursor is 1.0
196
+ main_val = optimal_taps[n_precursor]
197
+ if abs(main_val) > 1e-6:
198
+ optimal_taps = optimal_taps / main_val
199
+
200
+ equalized = np.convolve(data, optimal_taps, mode="same")
201
+ mse = float(np.mean((equalized - target) ** 2))
202
+
203
+ return FFEResult(
204
+ equalized_data=equalized,
205
+ taps=optimal_taps,
206
+ n_precursor=n_precursor,
207
+ n_postcursor=n_postcursor,
208
+ mse=mse,
209
+ )
210
+
211
+
212
+ def dfe_equalize(
213
+ trace: WaveformTrace,
214
+ taps: list[float] | NDArray[np.float64],
215
+ *,
216
+ threshold: float | None = None,
217
+ samples_per_symbol: int = 1,
218
+ ) -> DFEResult:
219
+ """Apply Decision Feedback Equalization.
220
+
221
+ DFE cancels post-cursor ISI using feedback from previous
222
+ bit decisions. Unlike FFE, DFE does not amplify noise.
223
+
224
+ Args:
225
+ trace: Input waveform trace.
226
+ taps: DFE tap coefficients for post-cursor cancellation.
227
+ threshold: Decision threshold (auto-detected if None).
228
+ samples_per_symbol: Samples per UI (default 1 for symbol-rate).
229
+
230
+ Returns:
231
+ DFEResult with equalized data and decisions.
232
+
233
+ Example:
234
+ >>> result = dfe_equalize(trace, taps=[0.2, 0.1])
235
+ >>> # 2-tap DFE canceling h1 and h2
236
+
237
+ References:
238
+ IEEE 802.3 Clause 93
239
+ """
240
+ taps = np.array(taps, dtype=np.float64)
241
+ n_taps = len(taps)
242
+ data = trace.data
243
+ n = len(data)
244
+
245
+ # Auto-detect threshold
246
+ if threshold is None:
247
+ threshold = float(np.median(data))
248
+
249
+ # Output arrays
250
+ equalized = np.zeros(n, dtype=np.float64)
251
+ decisions = np.zeros(n // samples_per_symbol, dtype=np.int_)
252
+
253
+ # Previous decisions buffer (for feedback)
254
+ prev_decisions = np.zeros(n_taps, dtype=np.float64)
255
+
256
+ # Process symbol-by-symbol
257
+ decision_idx = 0
258
+
259
+ for i in range(0, n, samples_per_symbol):
260
+ # Get input sample
261
+ input_val = data[i]
262
+
263
+ # Subtract DFE feedback
264
+ dfe_correction = np.dot(taps, prev_decisions)
265
+ corrected = input_val - dfe_correction
266
+
267
+ # Make decision
268
+ decision = 1.0 if corrected > threshold else -1.0
269
+
270
+ # Store
271
+ equalized[i : i + samples_per_symbol] = corrected
272
+ if decision_idx < len(decisions):
273
+ decisions[decision_idx] = int((decision + 1) / 2) # 0 or 1
274
+ decision_idx += 1
275
+
276
+ # Shift feedback register
277
+ prev_decisions = np.roll(prev_decisions, 1)
278
+ prev_decisions[0] = decision
279
+
280
+ return DFEResult(
281
+ equalized_data=equalized,
282
+ taps=taps,
283
+ decisions=decisions[:decision_idx],
284
+ n_taps=n_taps,
285
+ )
286
+
287
+
288
+ def ctle_equalize(
289
+ trace: WaveformTrace,
290
+ dc_gain: float = 0.0,
291
+ ac_gain: float = 6.0,
292
+ pole_frequency: float = 5e9,
293
+ *,
294
+ zero_frequency: float | None = None,
295
+ ) -> CTLEResult:
296
+ """Apply Continuous Time Linear Equalization.
297
+
298
+ CTLE provides high-frequency boost to compensate for
299
+ channel loss. It uses an analog-style transfer function.
300
+
301
+ Args:
302
+ trace: Input waveform trace.
303
+ dc_gain: DC gain in dB.
304
+ ac_gain: AC (peaking) gain in dB.
305
+ pole_frequency: Pole frequency in Hz.
306
+ zero_frequency: Zero frequency in Hz (computed if None).
307
+
308
+ Returns:
309
+ CTLEResult with equalized data.
310
+
311
+ Example:
312
+ >>> result = ctle_equalize(trace, ac_gain=6, pole_frequency=5e9)
313
+ >>> # 6 dB of high-frequency boost
314
+
315
+ References:
316
+ IEEE 802.3 Clause 93
317
+ """
318
+ data = trace.data
319
+ sample_rate = trace.metadata.sample_rate
320
+ len(data)
321
+
322
+ # Convert gains from dB to linear
323
+ dc_linear = 10 ** (dc_gain / 20)
324
+ ac_linear = 10 ** (ac_gain / 20)
325
+
326
+ # Calculate zero frequency to achieve desired boost
327
+ if zero_frequency is None:
328
+ # Zero at lower frequency than pole to create peaking
329
+ zero_frequency = pole_frequency / (ac_linear / dc_linear)
330
+
331
+ # Compute boost
332
+ boost = ac_gain - dc_gain
333
+
334
+ # Create CTLE transfer function
335
+ # H(s) = (1 + s/wz) / (1 + s/wp) * gain
336
+ wz = 2 * np.pi * zero_frequency
337
+ wp = 2 * np.pi * pole_frequency
338
+
339
+ # Convert to digital filter using bilinear transform
340
+ b_analog = [1 / wz, 1] # numerator coefficients
341
+ a_analog = [1 / wp, 1] # denominator coefficients
342
+
343
+ # Bilinear transform
344
+ b_digital, a_digital = scipy_signal.bilinear(b_analog, a_analog, fs=sample_rate)
345
+
346
+ # Scale for DC gain
347
+ b_digital = b_digital * dc_linear
348
+
349
+ # Apply filter
350
+ equalized = scipy_signal.lfilter(b_digital, a_digital, data)
351
+
352
+ return CTLEResult(
353
+ equalized_data=equalized.astype(np.float64),
354
+ dc_gain=dc_gain,
355
+ ac_gain=ac_gain,
356
+ pole_frequency=pole_frequency,
357
+ zero_frequency=zero_frequency,
358
+ boost=boost,
359
+ )
360
+
361
+
362
+ __all__ = [
363
+ "CTLEResult",
364
+ "DFEResult",
365
+ "FFEResult",
366
+ "ctle_equalize",
367
+ "dfe_equalize",
368
+ "ffe_equalize",
369
+ "optimize_ffe",
370
+ ]