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,564 @@
1
+ """Convenience filtering functions for TraceKit.
2
+
3
+ Provides simple one-call filter functions for common operations like
4
+ moving average, median filter, Savitzky-Golay smoothing, and matched
5
+ filtering.
6
+
7
+ Example:
8
+ >>> from oscura.filtering.convenience import low_pass, moving_average
9
+ >>> filtered = low_pass(trace, cutoff=1e6)
10
+ >>> smoothed = moving_average(trace, window_size=11)
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ from typing import TYPE_CHECKING, Any, Literal
16
+
17
+ import numpy as np
18
+ from scipy import ndimage, signal
19
+
20
+ from oscura.core.exceptions import AnalysisError
21
+ from oscura.core.types import WaveformTrace
22
+ from oscura.filtering.design import (
23
+ BandPassFilter,
24
+ BandStopFilter,
25
+ HighPassFilter,
26
+ LowPassFilter,
27
+ )
28
+
29
+ if TYPE_CHECKING:
30
+ from numpy.typing import NDArray
31
+
32
+
33
+ def low_pass(
34
+ trace: WaveformTrace,
35
+ cutoff: float,
36
+ *,
37
+ order: int = 4,
38
+ filter_type: Literal[
39
+ "butterworth", "chebyshev1", "chebyshev2", "bessel", "elliptic"
40
+ ] = "butterworth",
41
+ ) -> WaveformTrace:
42
+ """Apply low-pass filter to trace.
43
+
44
+ Args:
45
+ trace: Input waveform trace.
46
+ cutoff: Cutoff frequency in Hz.
47
+ order: Filter order (default 4).
48
+ filter_type: Type of filter (default Butterworth).
49
+
50
+ Returns:
51
+ Filtered waveform trace.
52
+
53
+ Example:
54
+ >>> filtered = low_pass(trace, cutoff=1e6)
55
+ """
56
+ filt = LowPassFilter(
57
+ cutoff=cutoff,
58
+ sample_rate=trace.metadata.sample_rate,
59
+ order=order,
60
+ filter_type=filter_type,
61
+ )
62
+ result = filt.apply(trace)
63
+ if isinstance(result, WaveformTrace):
64
+ return result
65
+ return result.trace
66
+
67
+
68
+ def high_pass(
69
+ trace: WaveformTrace,
70
+ cutoff: float,
71
+ *,
72
+ order: int = 4,
73
+ filter_type: Literal[
74
+ "butterworth", "chebyshev1", "chebyshev2", "bessel", "elliptic"
75
+ ] = "butterworth",
76
+ ) -> WaveformTrace:
77
+ """Apply high-pass filter to trace.
78
+
79
+ Args:
80
+ trace: Input waveform trace.
81
+ cutoff: Cutoff frequency in Hz.
82
+ order: Filter order (default 4).
83
+ filter_type: Type of filter (default Butterworth).
84
+
85
+ Returns:
86
+ Filtered waveform trace.
87
+
88
+ Example:
89
+ >>> filtered = high_pass(trace, cutoff=100) # Remove DC and low frequencies
90
+ """
91
+ filt = HighPassFilter(
92
+ cutoff=cutoff,
93
+ sample_rate=trace.metadata.sample_rate,
94
+ order=order,
95
+ filter_type=filter_type,
96
+ )
97
+ result = filt.apply(trace)
98
+ if isinstance(result, WaveformTrace):
99
+ return result
100
+ return result.trace
101
+
102
+
103
+ def band_pass(
104
+ trace: WaveformTrace,
105
+ low: float,
106
+ high: float,
107
+ *,
108
+ order: int = 4,
109
+ filter_type: Literal[
110
+ "butterworth", "chebyshev1", "chebyshev2", "bessel", "elliptic"
111
+ ] = "butterworth",
112
+ ) -> WaveformTrace:
113
+ """Apply band-pass filter to trace.
114
+
115
+ Args:
116
+ trace: Input waveform trace.
117
+ low: Lower cutoff frequency in Hz.
118
+ high: Upper cutoff frequency in Hz.
119
+ order: Filter order (default 4).
120
+ filter_type: Type of filter (default Butterworth).
121
+
122
+ Returns:
123
+ Filtered waveform trace.
124
+
125
+ Example:
126
+ >>> filtered = band_pass(trace, low=1e3, high=10e3)
127
+ """
128
+ filt = BandPassFilter(
129
+ low=low,
130
+ high=high,
131
+ sample_rate=trace.metadata.sample_rate,
132
+ order=order,
133
+ filter_type=filter_type,
134
+ )
135
+ result = filt.apply(trace)
136
+ if isinstance(result, WaveformTrace):
137
+ return result
138
+ return result.trace
139
+
140
+
141
+ def band_stop(
142
+ trace: WaveformTrace,
143
+ low: float,
144
+ high: float,
145
+ *,
146
+ order: int = 4,
147
+ filter_type: Literal[
148
+ "butterworth", "chebyshev1", "chebyshev2", "bessel", "elliptic"
149
+ ] = "butterworth",
150
+ ) -> WaveformTrace:
151
+ """Apply band-stop (notch) filter to trace.
152
+
153
+ Args:
154
+ trace: Input waveform trace.
155
+ low: Lower cutoff frequency in Hz.
156
+ high: Upper cutoff frequency in Hz.
157
+ order: Filter order (default 4).
158
+ filter_type: Type of filter (default Butterworth).
159
+
160
+ Returns:
161
+ Filtered waveform trace.
162
+
163
+ Example:
164
+ >>> filtered = band_stop(trace, low=59, high=61) # Remove 60 Hz
165
+ """
166
+ filt = BandStopFilter(
167
+ low=low,
168
+ high=high,
169
+ sample_rate=trace.metadata.sample_rate,
170
+ order=order,
171
+ filter_type=filter_type,
172
+ )
173
+ result = filt.apply(trace)
174
+ if isinstance(result, WaveformTrace):
175
+ return result
176
+ return result.trace
177
+
178
+
179
+ def notch_filter(
180
+ trace: WaveformTrace,
181
+ freq: float,
182
+ *,
183
+ q_factor: float = 30.0,
184
+ ) -> WaveformTrace:
185
+ """Apply narrow notch filter at specified frequency.
186
+
187
+ Uses a band-stop Butterworth filter with bandwidth determined by Q factor.
188
+ Bandwidth (Hz) = freq / Q
189
+
190
+ Args:
191
+ trace: Input waveform trace.
192
+ freq: Center frequency to notch out in Hz.
193
+ q_factor: Quality factor (higher = narrower notch). Default 30.
194
+
195
+ Returns:
196
+ Filtered waveform trace.
197
+
198
+ Raises:
199
+ AnalysisError: If notch frequency exceeds Nyquist frequency.
200
+
201
+ Example:
202
+ >>> filtered = notch_filter(trace, freq=60, q_factor=30) # Remove 60 Hz line noise
203
+ """
204
+ sample_rate = trace.metadata.sample_rate
205
+
206
+ if freq >= sample_rate / 2:
207
+ raise AnalysisError(
208
+ f"Notch frequency {freq} Hz must be less than Nyquist {sample_rate / 2} Hz"
209
+ )
210
+
211
+ # Calculate bandwidth from Q factor: BW = f0 / Q
212
+ bandwidth = freq / q_factor
213
+
214
+ # Design band-stop filter centered at freq with calculated bandwidth
215
+ # Use 4th order Butterworth for good attenuation
216
+ low = max(freq - bandwidth / 2, 0.1) # Avoid zero frequency
217
+ high = min(freq + bandwidth / 2, sample_rate / 2 - 1) # Stay below Nyquist
218
+
219
+ # Normalize frequencies
220
+ wn = [low / (sample_rate / 2), high / (sample_rate / 2)]
221
+
222
+ # Design bandstop filter
223
+ sos = signal.butter(4, wn, btype="bandstop", output="sos")
224
+
225
+ # Apply zero-phase filter
226
+ filtered_data = signal.sosfiltfilt(sos, trace.data)
227
+
228
+ return WaveformTrace(
229
+ data=filtered_data.astype(np.float64),
230
+ metadata=trace.metadata,
231
+ )
232
+
233
+
234
+ def moving_average(
235
+ trace: WaveformTrace,
236
+ window_size: int,
237
+ *,
238
+ mode: Literal["same", "valid", "full"] = "same",
239
+ ) -> WaveformTrace:
240
+ """Apply moving average filter.
241
+
242
+ Simple FIR filter with uniform weights.
243
+
244
+ Args:
245
+ trace: Input waveform trace.
246
+ window_size: Number of samples in averaging window (must be odd for 'same' mode).
247
+ mode: Convolution mode - "same" preserves length.
248
+
249
+ Returns:
250
+ Filtered waveform trace.
251
+
252
+ Raises:
253
+ AnalysisError: If window_size is not positive or exceeds data length.
254
+
255
+ Example:
256
+ >>> smoothed = moving_average(trace, window_size=11)
257
+ """
258
+ if window_size < 1:
259
+ raise AnalysisError(f"Window size must be positive, got {window_size}")
260
+
261
+ if window_size > len(trace.data):
262
+ raise AnalysisError(f"Window size {window_size} exceeds data length {len(trace.data)}")
263
+
264
+ kernel = np.ones(window_size) / window_size
265
+ filtered_data = np.convolve(trace.data, kernel, mode=mode)
266
+
267
+ return WaveformTrace(
268
+ data=filtered_data.astype(np.float64),
269
+ metadata=trace.metadata,
270
+ )
271
+
272
+
273
+ def median_filter(
274
+ trace: WaveformTrace,
275
+ kernel_size: int,
276
+ ) -> WaveformTrace:
277
+ """Apply median filter for spike/impulse noise removal.
278
+
279
+ Non-linear filter that preserves edges while removing outliers.
280
+
281
+ Args:
282
+ trace: Input waveform trace.
283
+ kernel_size: Size of the median filter kernel (must be odd).
284
+
285
+ Returns:
286
+ Filtered waveform trace.
287
+
288
+ Raises:
289
+ AnalysisError: If kernel_size is not positive or not odd.
290
+
291
+ Example:
292
+ >>> cleaned = median_filter(trace, kernel_size=5) # Remove impulse noise
293
+ """
294
+ if kernel_size < 1:
295
+ raise AnalysisError(f"Kernel size must be positive, got {kernel_size}")
296
+
297
+ if kernel_size % 2 == 0:
298
+ raise AnalysisError(f"Kernel size must be odd, got {kernel_size}")
299
+
300
+ filtered_data = ndimage.median_filter(trace.data, size=kernel_size)
301
+
302
+ return WaveformTrace(
303
+ data=filtered_data.astype(np.float64),
304
+ metadata=trace.metadata,
305
+ )
306
+
307
+
308
+ def savgol_filter(
309
+ trace: WaveformTrace,
310
+ window_length: int,
311
+ polyorder: int,
312
+ *,
313
+ deriv: int = 0,
314
+ ) -> WaveformTrace:
315
+ """Apply Savitzky-Golay smoothing filter.
316
+
317
+ Smooths data while preserving higher moments (peaks, etc.) better
318
+ than simple moving average.
319
+
320
+ Args:
321
+ trace: Input waveform trace.
322
+ window_length: Length of filter window (must be odd and > polyorder).
323
+ polyorder: Order of polynomial used in fitting.
324
+ deriv: Derivative order (0 = smoothing, 1 = first derivative, etc.).
325
+
326
+ Returns:
327
+ Filtered waveform trace.
328
+
329
+ Raises:
330
+ AnalysisError: If window_length is not odd or polyorder is invalid.
331
+
332
+ Example:
333
+ >>> smoothed = savgol_filter(trace, window_length=11, polyorder=3)
334
+ """
335
+ if window_length % 2 == 0:
336
+ raise AnalysisError(f"Window length must be odd, got {window_length}")
337
+
338
+ if polyorder >= window_length:
339
+ raise AnalysisError(
340
+ f"Polynomial order {polyorder} must be less than window length {window_length}"
341
+ )
342
+
343
+ filtered_data = signal.savgol_filter(trace.data, window_length, polyorder, deriv=deriv)
344
+
345
+ return WaveformTrace(
346
+ data=filtered_data.astype(np.float64),
347
+ metadata=trace.metadata,
348
+ )
349
+
350
+
351
+ def matched_filter(
352
+ trace: WaveformTrace,
353
+ template: NDArray[np.floating[Any]],
354
+ *,
355
+ normalize: bool = True,
356
+ ) -> WaveformTrace:
357
+ """Apply matched filter for pulse detection.
358
+
359
+ Correlates the input with a known pulse template to detect
360
+ occurrences of that pulse shape.
361
+
362
+ Args:
363
+ trace: Input waveform trace.
364
+ template: Template pulse to match.
365
+ normalize: If True, normalize template for unit energy.
366
+
367
+ Returns:
368
+ Matched filter output trace. Peaks indicate template matches.
369
+
370
+ Raises:
371
+ AnalysisError: If template is empty or exceeds data length.
372
+
373
+ Example:
374
+ >>> # Detect a specific pulse shape
375
+ >>> pulse_template = np.array([0, 0.5, 1.0, 0.5, 0])
376
+ >>> match_output = matched_filter(trace, pulse_template)
377
+ >>> # Find peaks in match_output for detection
378
+ """
379
+ if len(template) == 0:
380
+ raise AnalysisError("Template cannot be empty")
381
+
382
+ if len(template) > len(trace.data):
383
+ raise AnalysisError(
384
+ f"Template length {len(template)} exceeds data length {len(trace.data)}"
385
+ )
386
+
387
+ # Matched filter is correlation with time-reversed template
388
+ h = template[::-1].copy()
389
+
390
+ if normalize:
391
+ energy = np.sum(h**2)
392
+ if energy > 0:
393
+ h = h / np.sqrt(energy)
394
+
395
+ # Correlate (convolve with time-reversed template)
396
+ output = np.convolve(trace.data, h, mode="same")
397
+
398
+ return WaveformTrace(
399
+ data=output.astype(np.float64),
400
+ metadata=trace.metadata,
401
+ )
402
+
403
+
404
+ def exponential_moving_average(
405
+ trace: WaveformTrace,
406
+ alpha: float,
407
+ ) -> WaveformTrace:
408
+ """Apply exponential moving average (EMA) filter.
409
+
410
+ IIR filter with exponential decay weighting.
411
+
412
+ Args:
413
+ trace: Input waveform trace.
414
+ alpha: Smoothing factor (0 < alpha <= 1). Higher = less smoothing.
415
+
416
+ Returns:
417
+ Filtered waveform trace.
418
+
419
+ Raises:
420
+ AnalysisError: If alpha is not in range (0, 1].
421
+
422
+ Example:
423
+ >>> smoothed = exponential_moving_average(trace, alpha=0.1)
424
+ """
425
+ if not 0 < alpha <= 1:
426
+ raise AnalysisError(f"Alpha must be in (0, 1], got {alpha}")
427
+
428
+ # EMA as IIR filter: y[n] = alpha * x[n] + (1 - alpha) * y[n-1]
429
+ # Transfer function: H(z) = alpha / (1 - (1-alpha) * z^-1)
430
+ b = np.array([alpha])
431
+ a = np.array([1.0, -(1 - alpha)])
432
+
433
+ filtered_data = signal.lfilter(b, a, trace.data)
434
+
435
+ return WaveformTrace(
436
+ data=filtered_data.astype(np.float64),
437
+ metadata=trace.metadata,
438
+ )
439
+
440
+
441
+ def gaussian_filter(
442
+ trace: WaveformTrace,
443
+ sigma: float,
444
+ ) -> WaveformTrace:
445
+ """Apply Gaussian smoothing filter.
446
+
447
+ Smooth with Gaussian kernel of specified standard deviation.
448
+
449
+ Args:
450
+ trace: Input waveform trace.
451
+ sigma: Standard deviation of Gaussian kernel in samples.
452
+
453
+ Returns:
454
+ Filtered waveform trace.
455
+
456
+ Raises:
457
+ AnalysisError: If sigma is not positive.
458
+
459
+ Example:
460
+ >>> smoothed = gaussian_filter(trace, sigma=3.0)
461
+ """
462
+ if sigma <= 0:
463
+ raise AnalysisError(f"Sigma must be positive, got {sigma}")
464
+
465
+ filtered_data = ndimage.gaussian_filter1d(trace.data, sigma)
466
+
467
+ return WaveformTrace(
468
+ data=filtered_data.astype(np.float64),
469
+ metadata=trace.metadata,
470
+ )
471
+
472
+
473
+ def differentiate(
474
+ trace: WaveformTrace,
475
+ *,
476
+ order: int = 1,
477
+ ) -> WaveformTrace:
478
+ """Compute numerical derivative of trace.
479
+
480
+ Uses numpy gradient for smooth differentiation.
481
+
482
+ Args:
483
+ trace: Input waveform trace.
484
+ order: Derivative order (1 = first derivative, 2 = second, etc.).
485
+
486
+ Returns:
487
+ Differentiated waveform trace. Units change (V -> V/s, etc.).
488
+
489
+ Raises:
490
+ AnalysisError: If order is not positive.
491
+
492
+ Example:
493
+ >>> velocity = differentiate(position_trace)
494
+ >>> acceleration = differentiate(position_trace, order=2)
495
+ """
496
+ if order < 1:
497
+ raise AnalysisError(f"Derivative order must be positive, got {order}")
498
+
499
+ sample_period = trace.metadata.time_base
500
+ result = trace.data.copy()
501
+
502
+ for _ in range(order):
503
+ result = np.gradient(result, sample_period)
504
+
505
+ return WaveformTrace(
506
+ data=result.astype(np.float64),
507
+ metadata=trace.metadata,
508
+ )
509
+
510
+
511
+ def integrate(
512
+ trace: WaveformTrace,
513
+ *,
514
+ method: Literal["cumtrapz", "cumsum"] = "cumtrapz",
515
+ initial: float = 0.0,
516
+ ) -> WaveformTrace:
517
+ """Compute numerical integral of trace.
518
+
519
+ Args:
520
+ trace: Input waveform trace.
521
+ method: Integration method - "cumtrapz" (trapezoidal) or "cumsum".
522
+ initial: Initial value at t=0.
523
+
524
+ Returns:
525
+ Integrated waveform trace. Units change (V -> V*s, etc.).
526
+
527
+ Raises:
528
+ AnalysisError: If method is not recognized.
529
+
530
+ Example:
531
+ >>> position = integrate(velocity_trace)
532
+ """
533
+ sample_period = trace.metadata.time_base
534
+
535
+ if method == "cumtrapz":
536
+ from scipy.integrate import cumulative_trapezoid
537
+
538
+ result = cumulative_trapezoid(trace.data, dx=sample_period, initial=initial)
539
+ elif method == "cumsum":
540
+ result = np.cumsum(trace.data) * sample_period + initial
541
+ else:
542
+ raise AnalysisError(f"Unknown integration method: {method}")
543
+
544
+ return WaveformTrace(
545
+ data=result.astype(np.float64),
546
+ metadata=trace.metadata,
547
+ )
548
+
549
+
550
+ __all__ = [
551
+ "band_pass",
552
+ "band_stop",
553
+ "differentiate",
554
+ "exponential_moving_average",
555
+ "gaussian_filter",
556
+ "high_pass",
557
+ "integrate",
558
+ "low_pass",
559
+ "matched_filter",
560
+ "median_filter",
561
+ "moving_average",
562
+ "notch_filter",
563
+ "savgol_filter",
564
+ ]