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,89 @@
1
+ """Signal filtering module for TraceKit.
2
+
3
+ Provides digital filter design, application, and introspection capabilities
4
+ including IIR and FIR filters, various filter types (Butterworth, Chebyshev,
5
+ Bessel, Elliptic), and convenience filters (moving average, median).
6
+
7
+
8
+ Example:
9
+ >>> from oscura.filtering import LowPassFilter, design_filter
10
+ >>> lpf = LowPassFilter(cutoff=1e6, sample_rate=10e6, order=4)
11
+ >>> filtered_trace = lpf.apply(trace)
12
+ >>> w, h = lpf.get_frequency_response()
13
+ """
14
+
15
+ # Import filters module as namespace for DSL compatibility
16
+ from oscura.filtering import filters
17
+ from oscura.filtering.base import (
18
+ Filter,
19
+ FIRFilter,
20
+ IIRFilter,
21
+ )
22
+ from oscura.filtering.convenience import (
23
+ band_pass,
24
+ band_stop,
25
+ high_pass,
26
+ low_pass,
27
+ matched_filter,
28
+ median_filter,
29
+ moving_average,
30
+ notch_filter,
31
+ savgol_filter,
32
+ )
33
+ from oscura.filtering.design import (
34
+ BandPassFilter,
35
+ BandStopFilter,
36
+ BesselFilter,
37
+ ButterworthFilter,
38
+ ChebyshevType1Filter,
39
+ ChebyshevType2Filter,
40
+ EllipticFilter,
41
+ HighPassFilter,
42
+ LowPassFilter,
43
+ design_filter,
44
+ design_filter_spec,
45
+ )
46
+ from oscura.filtering.introspection import (
47
+ FilterIntrospection,
48
+ plot_bode,
49
+ plot_impulse,
50
+ plot_poles_zeros,
51
+ plot_step,
52
+ )
53
+
54
+ __all__ = [
55
+ "BandPassFilter",
56
+ "BandStopFilter",
57
+ "BesselFilter",
58
+ "ButterworthFilter",
59
+ "ChebyshevType1Filter",
60
+ "ChebyshevType2Filter",
61
+ "EllipticFilter",
62
+ "FIRFilter",
63
+ # Base classes
64
+ "Filter",
65
+ # Introspection
66
+ "FilterIntrospection",
67
+ "HighPassFilter",
68
+ "IIRFilter",
69
+ # Filter types
70
+ "LowPassFilter",
71
+ "band_pass",
72
+ "band_stop",
73
+ # Design functions
74
+ "design_filter",
75
+ "design_filter_spec",
76
+ "filters",
77
+ "high_pass",
78
+ "low_pass",
79
+ "matched_filter",
80
+ "median_filter",
81
+ # Convenience functions
82
+ "moving_average",
83
+ "notch_filter",
84
+ "plot_bode",
85
+ "plot_impulse",
86
+ "plot_poles_zeros",
87
+ "plot_step",
88
+ "savgol_filter",
89
+ ]
@@ -0,0 +1,563 @@
1
+ """Base filter classes for TraceKit filtering module.
2
+
3
+ Provides abstract base classes for IIR and FIR filter implementations
4
+ with common interface for filter application and introspection.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from abc import ABC, abstractmethod
10
+ from dataclasses import dataclass
11
+ from typing import TYPE_CHECKING, Literal
12
+
13
+ import numpy as np
14
+ from scipy import signal
15
+
16
+ from oscura.core.exceptions import AnalysisError
17
+ from oscura.core.types import WaveformTrace
18
+
19
+ if TYPE_CHECKING:
20
+ from numpy.typing import NDArray
21
+
22
+
23
+ @dataclass
24
+ class FilterResult:
25
+ """Result of filter application with optional introspection data.
26
+
27
+ Attributes:
28
+ trace: Filtered waveform trace.
29
+ transfer_function: Optional frequency response H(f).
30
+ impulse_response: Optional impulse response h[n].
31
+ group_delay: Optional group delay in samples.
32
+ """
33
+
34
+ trace: WaveformTrace
35
+ transfer_function: NDArray[np.complex128] | None = None
36
+ impulse_response: NDArray[np.float64] | None = None
37
+ group_delay: NDArray[np.float64] | None = None
38
+
39
+
40
+ class Filter(ABC):
41
+ """Abstract base class for all filters.
42
+
43
+ Defines the common interface for filter application and introspection.
44
+ All filter implementations must inherit from this class.
45
+
46
+ Attributes:
47
+ sample_rate: Sample rate in Hz for digital filter design.
48
+ is_stable: Whether the filter is stable (for IIR filters).
49
+ """
50
+
51
+ def __init__(self, sample_rate: float | None = None) -> None:
52
+ """Initialize filter.
53
+
54
+ Args:
55
+ sample_rate: Sample rate in Hz. If None, must be provided at apply time.
56
+ """
57
+ self._sample_rate = sample_rate
58
+ self._is_designed = False
59
+
60
+ @property
61
+ def sample_rate(self) -> float | None:
62
+ """Sample rate in Hz."""
63
+ return self._sample_rate
64
+
65
+ @sample_rate.setter
66
+ def sample_rate(self, value: float) -> None:
67
+ """Set sample rate and mark filter for redesign."""
68
+ if value != self._sample_rate:
69
+ self._sample_rate = value
70
+ self._is_designed = False
71
+
72
+ @property
73
+ @abstractmethod
74
+ def is_stable(self) -> bool:
75
+ """Check if filter is stable."""
76
+ ...
77
+
78
+ @property
79
+ @abstractmethod
80
+ def order(self) -> int:
81
+ """Filter order."""
82
+ ...
83
+
84
+ @abstractmethod
85
+ def apply(
86
+ self,
87
+ trace: WaveformTrace,
88
+ *,
89
+ return_details: bool = False,
90
+ ) -> WaveformTrace | FilterResult:
91
+ """Apply filter to a waveform trace.
92
+
93
+ Args:
94
+ trace: Input waveform trace.
95
+ return_details: If True, return FilterResult with introspection data.
96
+
97
+ Returns:
98
+ Filtered trace, or FilterResult if return_details=True.
99
+ """
100
+ ...
101
+
102
+ @abstractmethod
103
+ def get_frequency_response(
104
+ self,
105
+ worN: int | NDArray[np.float64] | None = None,
106
+ ) -> tuple[NDArray[np.float64], NDArray[np.complex128]]:
107
+ """Get frequency response of the filter.
108
+
109
+ Args:
110
+ worN: Frequencies at which to evaluate. If int, that many frequencies
111
+ from 0 to pi (Nyquist). If array, specific frequencies in rad/s.
112
+ If None, uses 512 points.
113
+
114
+ Returns:
115
+ Tuple of (frequencies, complex response H(f)).
116
+ """
117
+ ...
118
+
119
+ @abstractmethod
120
+ def get_impulse_response(
121
+ self,
122
+ n_samples: int = 256,
123
+ ) -> NDArray[np.float64]:
124
+ """Get impulse response of the filter.
125
+
126
+ Args:
127
+ n_samples: Number of samples in impulse response.
128
+
129
+ Returns:
130
+ Impulse response h[n].
131
+ """
132
+ ...
133
+
134
+ @abstractmethod
135
+ def get_step_response(
136
+ self,
137
+ n_samples: int = 256,
138
+ ) -> NDArray[np.float64]:
139
+ """Get step response of the filter.
140
+
141
+ Args:
142
+ n_samples: Number of samples in step response.
143
+
144
+ Returns:
145
+ Step response s[n].
146
+ """
147
+ ...
148
+
149
+ def get_transfer_function(
150
+ self,
151
+ freqs: NDArray[np.float64] | None = None,
152
+ ) -> NDArray[np.complex128]:
153
+ """Get transfer function H(f) at specified frequencies.
154
+
155
+ Args:
156
+ freqs: Frequencies in Hz. If None, uses 512 points from 0 to Nyquist.
157
+
158
+ Returns:
159
+ Complex transfer function values.
160
+
161
+ Raises:
162
+ AnalysisError: If sample rate is not set.
163
+ """
164
+ if self._sample_rate is None:
165
+ raise AnalysisError("Sample rate must be set to compute transfer function")
166
+
167
+ if freqs is None:
168
+ freqs = np.linspace(0, self._sample_rate / 2, 512)
169
+
170
+ # Convert Hz to normalized frequency
171
+ w = 2 * np.pi * freqs / self._sample_rate
172
+ _, h = self.get_frequency_response(w)
173
+ return h
174
+
175
+ def get_group_delay(
176
+ self,
177
+ worN: int | NDArray[np.float64] | None = None,
178
+ ) -> tuple[NDArray[np.float64], NDArray[np.float64]]:
179
+ """Get group delay of the filter.
180
+
181
+ Args:
182
+ worN: Frequencies at which to evaluate. If int, that many frequencies.
183
+ If None, uses 512 points.
184
+
185
+ Returns:
186
+ Tuple of (frequencies, group delay in samples).
187
+ """
188
+ if worN is None:
189
+ worN = 512
190
+ # Default implementation using phase derivative
191
+ w, h = self.get_frequency_response(worN)
192
+ phase = np.unwrap(np.angle(h))
193
+ dw = np.diff(w)
194
+ dphi = np.diff(phase)
195
+ # Avoid division by zero
196
+ gd = np.zeros_like(w)
197
+ gd[:-1] = -dphi / dw
198
+ gd[-1] = gd[-2] if len(gd) > 1 else 0
199
+ return w, gd
200
+
201
+
202
+ class IIRFilter(Filter):
203
+ """Infinite Impulse Response filter base class.
204
+
205
+ Stores filter coefficients in Second-Order Sections (SOS) format
206
+ for numerical stability, with optional B/A polynomial format.
207
+
208
+ Attributes:
209
+ sos: Second-order sections coefficients (preferred format).
210
+ ba: Numerator/denominator polynomial coefficients (optional).
211
+ """
212
+
213
+ def __init__(
214
+ self,
215
+ sample_rate: float | None = None,
216
+ sos: NDArray[np.float64] | None = None,
217
+ ba: tuple[NDArray[np.float64], NDArray[np.float64]] | None = None,
218
+ ) -> None:
219
+ """Initialize IIR filter.
220
+
221
+ Args:
222
+ sample_rate: Sample rate in Hz.
223
+ sos: Second-order sections array (n_sections, 6).
224
+ ba: Tuple of (b, a) polynomial coefficients.
225
+ """
226
+ super().__init__(sample_rate)
227
+ self._sos = sos
228
+ self._ba = ba
229
+ self._is_designed = sos is not None or ba is not None
230
+
231
+ @property
232
+ def sos(self) -> NDArray[np.float64] | None:
233
+ """Second-order sections coefficients."""
234
+ return self._sos
235
+
236
+ @property
237
+ def ba(self) -> tuple[NDArray[np.float64], NDArray[np.float64]] | None:
238
+ """B/A polynomial coefficients."""
239
+ if self._ba is not None:
240
+ return self._ba
241
+ if self._sos is not None:
242
+ # Convert SOS to BA
243
+ b, a = signal.sos2tf(self._sos)
244
+ return (b, a)
245
+ return None
246
+
247
+ @property
248
+ def is_stable(self) -> bool:
249
+ """Check if filter is stable (all poles inside unit circle)."""
250
+ if self._sos is None and self._ba is None:
251
+ return True # Not designed yet
252
+
253
+ ba = self.ba
254
+ if ba is None:
255
+ return True
256
+
257
+ _, a = ba
258
+ poles = np.roots(a)
259
+ return bool(np.all(np.abs(poles) < 1.0))
260
+
261
+ @property
262
+ def order(self) -> int:
263
+ """Filter order."""
264
+ if self._sos is not None:
265
+ return 2 * len(self._sos)
266
+ if self._ba is not None:
267
+ return len(self._ba[1]) - 1
268
+ return 0
269
+
270
+ @property
271
+ def poles(self) -> NDArray[np.complex128]:
272
+ """Filter poles in z-domain."""
273
+ ba = self.ba
274
+ if ba is None:
275
+ return np.array([], dtype=np.complex128)
276
+ _, a = ba
277
+ return np.roots(a).astype(np.complex128)
278
+
279
+ @property
280
+ def zeros(self) -> NDArray[np.complex128]:
281
+ """Filter zeros in z-domain."""
282
+ ba = self.ba
283
+ if ba is None:
284
+ return np.array([], dtype=np.complex128)
285
+ b, _ = ba
286
+ return np.roots(b).astype(np.complex128)
287
+
288
+ def apply(
289
+ self,
290
+ trace: WaveformTrace,
291
+ *,
292
+ return_details: bool = False,
293
+ filtfilt: bool = True,
294
+ ) -> WaveformTrace | FilterResult:
295
+ """Apply IIR filter to waveform.
296
+
297
+ Args:
298
+ trace: Input waveform trace.
299
+ return_details: If True, return FilterResult with introspection data.
300
+ filtfilt: If True, use zero-phase filtering (forward-backward).
301
+ If False, use causal filtering.
302
+
303
+ Returns:
304
+ Filtered trace, or FilterResult if return_details=True.
305
+
306
+ Raises:
307
+ AnalysisError: If filter not designed or is unstable.
308
+ """
309
+ if self._sos is None and self._ba is None:
310
+ raise AnalysisError("Filter not designed - no coefficients available")
311
+
312
+ if not self.is_stable:
313
+ raise AnalysisError("Cannot apply unstable filter")
314
+
315
+ # Apply filter
316
+ if self._sos is not None:
317
+ if filtfilt:
318
+ filtered_data = signal.sosfiltfilt(self._sos, trace.data)
319
+ else:
320
+ filtered_data = signal.sosfilt(self._sos, trace.data)
321
+ else:
322
+ b, a = self._ba # type: ignore[misc]
323
+ if filtfilt:
324
+ filtered_data = signal.filtfilt(b, a, trace.data)
325
+ else:
326
+ filtered_data = signal.lfilter(b, a, trace.data)
327
+
328
+ filtered_trace = WaveformTrace(
329
+ data=filtered_data.astype(np.float64),
330
+ metadata=trace.metadata,
331
+ )
332
+
333
+ if return_details:
334
+ _w, h = self.get_frequency_response()
335
+ impulse = self.get_impulse_response()
336
+ _, gd = self.get_group_delay()
337
+ return FilterResult(
338
+ trace=filtered_trace,
339
+ transfer_function=h,
340
+ impulse_response=impulse,
341
+ group_delay=gd,
342
+ )
343
+
344
+ return filtered_trace
345
+
346
+ def get_frequency_response(
347
+ self,
348
+ worN: int | NDArray[np.float64] | None = None,
349
+ ) -> tuple[NDArray[np.float64], NDArray[np.complex128]]:
350
+ """Get frequency response."""
351
+ if worN is None:
352
+ worN = 512
353
+
354
+ if self._sos is not None:
355
+ w, h = signal.sosfreqz(self._sos, worN=worN)
356
+ elif self._ba is not None:
357
+ w, h = signal.freqz(self._ba[0], self._ba[1], worN=worN)
358
+ else:
359
+ raise AnalysisError("Filter not designed")
360
+
361
+ return w.astype(np.float64), h.astype(np.complex128)
362
+
363
+ def get_impulse_response(
364
+ self,
365
+ n_samples: int = 256,
366
+ ) -> NDArray[np.float64]:
367
+ """Get impulse response."""
368
+ impulse = np.zeros(n_samples)
369
+ impulse[0] = 1.0
370
+
371
+ response: NDArray[np.float64]
372
+ if self._sos is not None:
373
+ response = signal.sosfilt(self._sos, impulse).astype(np.float64)
374
+ elif self._ba is not None:
375
+ response = signal.lfilter(self._ba[0], self._ba[1], impulse).astype(np.float64)
376
+ else:
377
+ raise AnalysisError("Filter not designed")
378
+
379
+ return response
380
+
381
+ def get_step_response(
382
+ self,
383
+ n_samples: int = 256,
384
+ ) -> NDArray[np.float64]:
385
+ """Get step response."""
386
+ step = np.ones(n_samples)
387
+
388
+ response: NDArray[np.float64]
389
+ if self._sos is not None:
390
+ response = signal.sosfilt(self._sos, step).astype(np.float64)
391
+ elif self._ba is not None:
392
+ response = signal.lfilter(self._ba[0], self._ba[1], step).astype(np.float64)
393
+ else:
394
+ raise AnalysisError("Filter not designed")
395
+
396
+ return response
397
+
398
+
399
+ class FIRFilter(Filter):
400
+ """Finite Impulse Response filter base class.
401
+
402
+ Stores filter coefficients as a single array of tap weights.
403
+ FIR filters are always stable and can achieve linear phase.
404
+
405
+ Attributes:
406
+ coeffs: Filter tap coefficients.
407
+ """
408
+
409
+ def __init__(
410
+ self,
411
+ sample_rate: float | None = None,
412
+ coeffs: NDArray[np.float64] | None = None,
413
+ ) -> None:
414
+ """Initialize FIR filter.
415
+
416
+ Args:
417
+ sample_rate: Sample rate in Hz.
418
+ coeffs: Filter coefficients (tap weights).
419
+ """
420
+ super().__init__(sample_rate)
421
+ self._coeffs = coeffs
422
+ self._is_designed = coeffs is not None
423
+
424
+ @property
425
+ def coeffs(self) -> NDArray[np.float64] | None:
426
+ """Filter coefficients."""
427
+ return self._coeffs
428
+
429
+ @coeffs.setter
430
+ def coeffs(self, value: NDArray[np.float64]) -> None:
431
+ """Set filter coefficients."""
432
+ self._coeffs = value
433
+ self._is_designed = True
434
+
435
+ @property
436
+ def is_stable(self) -> bool:
437
+ """FIR filters are always stable."""
438
+ return True
439
+
440
+ @property
441
+ def order(self) -> int:
442
+ """Filter order (number of taps - 1)."""
443
+ if self._coeffs is not None:
444
+ return len(self._coeffs) - 1
445
+ return 0
446
+
447
+ @property
448
+ def is_linear_phase(self) -> bool:
449
+ """Check if filter has linear phase (symmetric or antisymmetric coefficients)."""
450
+ if self._coeffs is None:
451
+ return False
452
+ len(self._coeffs)
453
+ # Check symmetry
454
+ symmetric = np.allclose(self._coeffs, self._coeffs[::-1])
455
+ antisymmetric = np.allclose(self._coeffs, -self._coeffs[::-1])
456
+ return symmetric or antisymmetric # type: ignore[no-any-return]
457
+
458
+ def apply(
459
+ self,
460
+ trace: WaveformTrace,
461
+ *,
462
+ return_details: bool = False,
463
+ mode: Literal["full", "same", "valid"] = "same",
464
+ ) -> WaveformTrace | FilterResult:
465
+ """Apply FIR filter to waveform.
466
+
467
+ Args:
468
+ trace: Input waveform trace.
469
+ return_details: If True, return FilterResult with introspection data.
470
+ mode: Convolution mode - "same" preserves length.
471
+
472
+ Returns:
473
+ Filtered trace, or FilterResult if return_details=True.
474
+
475
+ Raises:
476
+ AnalysisError: If filter not designed.
477
+ """
478
+ if self._coeffs is None:
479
+ raise AnalysisError("Filter not designed - no coefficients available")
480
+
481
+ # Apply filter using convolution
482
+ filtered_data = np.convolve(trace.data, self._coeffs, mode=mode)
483
+
484
+ filtered_trace = WaveformTrace(
485
+ data=filtered_data.astype(np.float64),
486
+ metadata=trace.metadata,
487
+ )
488
+
489
+ if return_details:
490
+ _w, h = self.get_frequency_response()
491
+ impulse = self.get_impulse_response()
492
+ _, gd = self.get_group_delay()
493
+ return FilterResult(
494
+ trace=filtered_trace,
495
+ transfer_function=h,
496
+ impulse_response=impulse,
497
+ group_delay=gd,
498
+ )
499
+
500
+ return filtered_trace
501
+
502
+ def get_frequency_response(
503
+ self,
504
+ worN: int | NDArray[np.float64] | None = None,
505
+ ) -> tuple[NDArray[np.float64], NDArray[np.complex128]]:
506
+ """Get frequency response."""
507
+ if self._coeffs is None:
508
+ raise AnalysisError("Filter not designed")
509
+
510
+ if worN is None:
511
+ worN = 512
512
+
513
+ w, h = signal.freqz(self._coeffs, 1, worN=worN)
514
+ return w.astype(np.float64), h.astype(np.complex128)
515
+
516
+ def get_impulse_response(
517
+ self,
518
+ n_samples: int = 256,
519
+ ) -> NDArray[np.float64]:
520
+ """Get impulse response (just the coefficients, zero-padded)."""
521
+ if self._coeffs is None:
522
+ raise AnalysisError("Filter not designed")
523
+
524
+ if len(self._coeffs) >= n_samples:
525
+ return self._coeffs[:n_samples].astype(np.float64)
526
+
527
+ response = np.zeros(n_samples)
528
+ response[: len(self._coeffs)] = self._coeffs
529
+ return response.astype(np.float64)
530
+
531
+ def get_step_response(
532
+ self,
533
+ n_samples: int = 256,
534
+ ) -> NDArray[np.float64]:
535
+ """Get step response."""
536
+ if self._coeffs is None:
537
+ raise AnalysisError("Filter not designed")
538
+
539
+ step = np.ones(n_samples)
540
+ response = np.convolve(step, self._coeffs, mode="full")[:n_samples]
541
+ return response.astype(np.float64)
542
+
543
+ def get_group_delay(
544
+ self,
545
+ worN: int | NDArray[np.float64] | None = None,
546
+ ) -> tuple[NDArray[np.float64], NDArray[np.float64]]:
547
+ """Get group delay."""
548
+ if self._coeffs is None:
549
+ raise AnalysisError("Filter not designed")
550
+
551
+ if worN is None:
552
+ worN = 512
553
+
554
+ w, gd = signal.group_delay((self._coeffs, 1), w=worN)
555
+ return w.astype(np.float64), gd.astype(np.float64)
556
+
557
+
558
+ __all__ = [
559
+ "FIRFilter",
560
+ "Filter",
561
+ "FilterResult",
562
+ "IIRFilter",
563
+ ]