oscura 0.0.1__py3-none-any.whl → 0.1.1__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.1.dist-info/METADATA +300 -0
  460. oscura-0.1.1.dist-info/RECORD +463 -0
  461. oscura-0.1.1.dist-info/entry_points.txt +2 -0
  462. {oscura-0.0.1.dist-info → oscura-0.1.1.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.1.dist-info}/WHEEL +0 -0
oscura/api/dsl.py ADDED
@@ -0,0 +1,538 @@
1
+ """Domain-Specific Language (DSL) for signal analysis.
2
+
3
+ This module provides a simple DSL for expressing signal analysis
4
+ operations in a readable, declarative format.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import re
10
+ from dataclasses import dataclass, field
11
+ from typing import TYPE_CHECKING, Any
12
+
13
+ import numpy as np
14
+
15
+ if TYPE_CHECKING:
16
+ from collections.abc import Callable
17
+
18
+ from numpy.typing import NDArray
19
+
20
+ __all__ = [
21
+ "DSLExpression",
22
+ "DSLParser",
23
+ "analyze",
24
+ "parse_expression",
25
+ ]
26
+
27
+
28
+ @dataclass
29
+ class DSLExpression:
30
+ """Parsed DSL expression.
31
+
32
+ Attributes:
33
+ operation: Operation name
34
+ args: Positional arguments
35
+ kwargs: Keyword arguments
36
+ chain: Chained operation (if any)
37
+
38
+ Example:
39
+ >>> expr = DSLExpression(
40
+ ... operation="fft",
41
+ ... kwargs={"nfft": 8192}
42
+ ... )
43
+
44
+ References:
45
+ API-010: Domain-Specific Language (DSL)
46
+ """
47
+
48
+ operation: str
49
+ args: list[Any] = field(default_factory=list)
50
+ kwargs: dict[str, Any] = field(default_factory=dict)
51
+ chain: DSLExpression | None = None
52
+
53
+ def to_dict(self) -> dict[str, Any]:
54
+ """Convert to dictionary."""
55
+ result = {"operation": self.operation, "args": self.args, "kwargs": self.kwargs}
56
+ if self.chain:
57
+ result["chain"] = self.chain.to_dict()
58
+ return result
59
+
60
+
61
+ class DSLParser:
62
+ """Parser for signal analysis DSL.
63
+
64
+ Grammar:
65
+ expression := operation | operation '|' expression
66
+ operation := name | name '(' arguments ')'
67
+ arguments := arg | arg ',' arguments
68
+ arg := value | name '=' value
69
+ value := number | string | list
70
+
71
+ Example:
72
+ >>> parser = DSLParser()
73
+ >>> expr = parser.parse("lowpass(cutoff=1e6) | fft(nfft=8192)")
74
+ >>> print(expr.operation)
75
+ 'lowpass'
76
+
77
+ References:
78
+ API-010: Domain-Specific Language (DSL)
79
+ """
80
+
81
+ OPERATIONS = { # noqa: RUF012
82
+ "load",
83
+ "save",
84
+ "export",
85
+ "lowpass",
86
+ "highpass",
87
+ "bandpass",
88
+ "notch",
89
+ "filter",
90
+ "fft",
91
+ "ifft",
92
+ "psd",
93
+ "spectrogram",
94
+ "cwt",
95
+ "mean",
96
+ "std",
97
+ "min",
98
+ "max",
99
+ "rms",
100
+ "peak_to_peak",
101
+ "rise_time",
102
+ "fall_time",
103
+ "frequency",
104
+ "period",
105
+ "threshold",
106
+ "edges",
107
+ "pulses",
108
+ "decode",
109
+ "uart",
110
+ "spi",
111
+ "i2c",
112
+ "can",
113
+ "plot",
114
+ "show",
115
+ "histogram",
116
+ "resample",
117
+ "decimate",
118
+ "interpolate",
119
+ "normalize",
120
+ "zscore",
121
+ "scale",
122
+ "clip",
123
+ "slice",
124
+ "select",
125
+ }
126
+
127
+ def __init__(self) -> None:
128
+ """Initialize parser."""
129
+ self._pos = 0
130
+ self._text = ""
131
+
132
+ def parse(self, text: str) -> DSLExpression:
133
+ """Parse DSL expression.
134
+
135
+ Args:
136
+ text: DSL expression text
137
+
138
+ Returns:
139
+ Parsed expression
140
+ """
141
+ self._text = text.strip()
142
+ self._pos = 0
143
+ return self._parse_chain()
144
+
145
+ def _parse_chain(self) -> DSLExpression:
146
+ """Parse expression chain."""
147
+ expr = self._parse_operation()
148
+
149
+ self._skip_whitespace()
150
+ if self._pos < len(self._text) and self._text[self._pos] == "|":
151
+ self._pos += 1
152
+ self._skip_whitespace()
153
+ expr.chain = self._parse_chain()
154
+
155
+ return expr
156
+
157
+ def _parse_operation(self) -> DSLExpression:
158
+ """Parse single operation."""
159
+ self._skip_whitespace()
160
+
161
+ # Parse operation name
162
+ name = self._parse_identifier()
163
+ if name not in self.OPERATIONS:
164
+ raise ValueError(f"Unknown operation: {name}")
165
+
166
+ self._skip_whitespace()
167
+
168
+ # Check for arguments
169
+ args: list[Any] = []
170
+ kwargs: dict[str, Any] = {}
171
+
172
+ if self._pos < len(self._text) and self._text[self._pos] == "(":
173
+ self._pos += 1 # Skip '('
174
+ args, kwargs = self._parse_arguments()
175
+
176
+ if self._pos >= len(self._text) or self._text[self._pos] != ")":
177
+ raise ValueError("Expected ')'")
178
+ self._pos += 1 # Skip ')'
179
+
180
+ return DSLExpression(operation=name, args=args, kwargs=kwargs)
181
+
182
+ def _parse_arguments(self) -> tuple[list[Any], dict[str, Any]]:
183
+ """Parse argument list."""
184
+ args = []
185
+ kwargs = {}
186
+
187
+ while True:
188
+ self._skip_whitespace()
189
+
190
+ if self._pos >= len(self._text) or self._text[self._pos] == ")":
191
+ break
192
+
193
+ # Check for keyword argument
194
+ start = self._pos
195
+ name = self._try_parse_identifier()
196
+
197
+ self._skip_whitespace()
198
+ if name and self._pos < len(self._text) and self._text[self._pos] == "=":
199
+ self._pos += 1 # Skip '='
200
+ self._skip_whitespace()
201
+ value = self._parse_value()
202
+ kwargs[name] = value
203
+ else:
204
+ # Positional argument
205
+ self._pos = start
206
+ value = self._parse_value()
207
+ args.append(value)
208
+
209
+ self._skip_whitespace()
210
+ if self._pos < len(self._text) and self._text[self._pos] == ",":
211
+ self._pos += 1
212
+
213
+ return args, kwargs
214
+
215
+ def _parse_value(self) -> Any:
216
+ """Parse a value."""
217
+ self._skip_whitespace()
218
+
219
+ if self._pos >= len(self._text):
220
+ raise ValueError("Unexpected end of expression")
221
+
222
+ char = self._text[self._pos]
223
+
224
+ # String
225
+ if char in "\"'":
226
+ return self._parse_string()
227
+
228
+ # List
229
+ if char == "[":
230
+ return self._parse_list()
231
+
232
+ # Number or identifier
233
+ return self._parse_number_or_identifier()
234
+
235
+ def _parse_string(self) -> str:
236
+ """Parse string literal."""
237
+ quote = self._text[self._pos]
238
+ self._pos += 1
239
+ start = self._pos
240
+
241
+ while self._pos < len(self._text) and self._text[self._pos] != quote:
242
+ if self._text[self._pos] == "\\":
243
+ self._pos += 1 # Skip escape
244
+ self._pos += 1
245
+
246
+ if self._pos >= len(self._text):
247
+ raise ValueError("Unterminated string")
248
+
249
+ value = self._text[start : self._pos]
250
+ self._pos += 1 # Skip closing quote
251
+ return value
252
+
253
+ def _parse_list(self) -> list[Any]:
254
+ """Parse list literal."""
255
+ self._pos += 1 # Skip '['
256
+ items = []
257
+
258
+ while True:
259
+ self._skip_whitespace()
260
+
261
+ if self._pos >= len(self._text):
262
+ raise ValueError("Unterminated list")
263
+
264
+ if self._text[self._pos] == "]":
265
+ self._pos += 1
266
+ break
267
+
268
+ items.append(self._parse_value())
269
+
270
+ self._skip_whitespace()
271
+ if self._pos < len(self._text) and self._text[self._pos] == ",":
272
+ self._pos += 1
273
+
274
+ return items
275
+
276
+ def _parse_number_or_identifier(self) -> Any:
277
+ """Parse number or identifier."""
278
+ # Match number pattern (including scientific notation)
279
+ pattern = r"[-+]?(\d+\.?\d*|\.\d+)([eE][-+]?\d+)?"
280
+ match = re.match(pattern, self._text[self._pos :])
281
+
282
+ if match:
283
+ self._pos += match.end()
284
+ value = match.group()
285
+ if "." in value or "e" in value.lower():
286
+ return float(value)
287
+ return int(value)
288
+
289
+ # Try identifier (True, False, None)
290
+ ident = self._parse_identifier()
291
+ if ident == "True":
292
+ return True
293
+ elif ident == "False":
294
+ return False
295
+ elif ident == "None":
296
+ return None
297
+ return ident
298
+
299
+ def _parse_identifier(self) -> str:
300
+ """Parse identifier."""
301
+ start = self._pos
302
+
303
+ if self._pos < len(self._text) and (
304
+ self._text[self._pos].isalpha() or self._text[self._pos] == "_"
305
+ ):
306
+ self._pos += 1
307
+ while self._pos < len(self._text) and (
308
+ self._text[self._pos].isalnum() or self._text[self._pos] == "_"
309
+ ):
310
+ self._pos += 1
311
+
312
+ if self._pos == start:
313
+ raise ValueError(f"Expected identifier at position {self._pos}")
314
+
315
+ return self._text[start : self._pos]
316
+
317
+ def _try_parse_identifier(self) -> str | None:
318
+ """Try to parse identifier, return None on failure."""
319
+ start = self._pos
320
+ try:
321
+ return self._parse_identifier()
322
+ except ValueError:
323
+ self._pos = start
324
+ return None
325
+
326
+ def _skip_whitespace(self) -> None:
327
+ """Skip whitespace characters."""
328
+ while self._pos < len(self._text) and self._text[self._pos].isspace():
329
+ self._pos += 1
330
+
331
+
332
+ class DSLExecutor:
333
+ """Executes parsed DSL expressions.
334
+
335
+ References:
336
+ API-010: Domain-Specific Language (DSL)
337
+ """
338
+
339
+ def __init__(self) -> None:
340
+ """Initialize executor."""
341
+ self._operations: dict[str, Callable] = {} # type: ignore[type-arg]
342
+ self._register_builtins()
343
+
344
+ def _register_builtins(self) -> None:
345
+ """Register built-in operations."""
346
+ # Filter operations
347
+ self._operations["lowpass"] = self._lowpass
348
+ self._operations["highpass"] = self._highpass
349
+ self._operations["bandpass"] = self._bandpass
350
+
351
+ # Analysis operations
352
+ self._operations["fft"] = self._fft
353
+ self._operations["psd"] = self._psd
354
+
355
+ # Measurement operations
356
+ self._operations["mean"] = lambda data: np.mean(data)
357
+ self._operations["std"] = lambda data: np.std(data)
358
+ self._operations["min"] = lambda data: np.min(data)
359
+ self._operations["max"] = lambda data: np.max(data)
360
+ self._operations["rms"] = lambda data: np.sqrt(np.mean(data**2))
361
+
362
+ # Transform operations
363
+ self._operations["normalize"] = self._normalize
364
+ self._operations["resample"] = self._resample
365
+ self._operations["slice"] = self._slice
366
+
367
+ def execute(self, expr: DSLExpression, data: NDArray[np.float64]) -> Any:
368
+ """Execute DSL expression on data.
369
+
370
+ Args:
371
+ expr: Parsed expression
372
+ data: Input data
373
+
374
+ Returns:
375
+ Result of execution
376
+
377
+ Raises:
378
+ ValueError: If operation is unknown or result cannot be chained.
379
+ """
380
+ # Execute operation
381
+ op = self._operations.get(expr.operation)
382
+ if op is None:
383
+ raise ValueError(f"Unknown operation: {expr.operation}")
384
+
385
+ result = op(data, *expr.args, **expr.kwargs)
386
+
387
+ # Execute chain if present
388
+ if expr.chain:
389
+ if isinstance(result, np.ndarray):
390
+ return self.execute(expr.chain, result)
391
+ else:
392
+ raise ValueError(f"Cannot chain after {expr.operation}: result is not an array")
393
+
394
+ return result
395
+
396
+ def _lowpass(
397
+ self, data: NDArray[np.float64], cutoff: float = 1e6, **kwargs: Any
398
+ ) -> NDArray[np.float64]:
399
+ """Low-pass filter."""
400
+ from scipy import signal
401
+
402
+ b, a = signal.butter(4, cutoff, btype="low", fs=kwargs.get("fs", 2 * cutoff))
403
+ result: NDArray[np.float64] = signal.filtfilt(b, a, data)
404
+ return result
405
+
406
+ def _highpass(
407
+ self, data: NDArray[np.float64], cutoff: float = 1e3, **kwargs: Any
408
+ ) -> NDArray[np.float64]:
409
+ """High-pass filter."""
410
+ from scipy import signal
411
+
412
+ b, a = signal.butter(4, cutoff, btype="high", fs=kwargs.get("fs", 10 * cutoff))
413
+ result: NDArray[np.float64] = signal.filtfilt(b, a, data)
414
+ return result
415
+
416
+ def _bandpass(
417
+ self,
418
+ data: NDArray[np.float64],
419
+ low: float = 1e3,
420
+ high: float = 1e6,
421
+ **kwargs: Any,
422
+ ) -> NDArray[np.float64]:
423
+ """Band-pass filter."""
424
+ from scipy import signal
425
+
426
+ b, a = signal.butter(4, [low, high], btype="band", fs=kwargs.get("fs", 2 * high))
427
+ result: NDArray[np.float64] = signal.filtfilt(b, a, data)
428
+ return result
429
+
430
+ def _fft(
431
+ self,
432
+ data: NDArray[np.float64],
433
+ nfft: int | None = None,
434
+ **kwargs: Any,
435
+ ) -> NDArray[np.complex128]:
436
+ """FFT."""
437
+ return np.fft.fft(data, n=nfft)
438
+
439
+ def _psd(
440
+ self,
441
+ data: NDArray[np.float64],
442
+ nperseg: int = 256,
443
+ **kwargs: Any,
444
+ ) -> NDArray[np.float64]:
445
+ """Power spectral density."""
446
+ from scipy import signal
447
+
448
+ _, psd_result = signal.welch(data, nperseg=nperseg)
449
+ result: NDArray[np.float64] = psd_result
450
+ return result
451
+
452
+ def _normalize(
453
+ self,
454
+ data: NDArray[np.float64],
455
+ method: str = "minmax",
456
+ **kwargs: Any,
457
+ ) -> NDArray[np.float64]:
458
+ """Normalize data."""
459
+ if method == "minmax":
460
+ data_min = np.min(data)
461
+ data_max = np.max(data)
462
+ if data_max - data_min > 0:
463
+ result: NDArray[np.float64] = (data - data_min) / (data_max - data_min)
464
+ return result
465
+ return data
466
+ elif method == "zscore":
467
+ std = np.std(data)
468
+ if std > 0:
469
+ result_z: NDArray[np.float64] = (data - np.mean(data)) / std
470
+ return result_z
471
+ result_mean: NDArray[np.float64] = data - np.mean(data)
472
+ return result_mean
473
+ return data
474
+
475
+ def _resample(
476
+ self,
477
+ data: NDArray[np.float64],
478
+ factor: int = 2,
479
+ **kwargs: Any,
480
+ ) -> NDArray[np.float64]:
481
+ """Resample data."""
482
+ from scipy import signal
483
+
484
+ result: NDArray[np.float64] = signal.resample(data, len(data) // factor)
485
+ return result
486
+
487
+ def _slice(
488
+ self,
489
+ data: NDArray[np.float64],
490
+ start: int = 0,
491
+ end: int | None = None,
492
+ **kwargs: Any,
493
+ ) -> NDArray[np.float64]:
494
+ """Slice data."""
495
+ return data[start:end]
496
+
497
+
498
+ # Global parser and executor
499
+ _parser = DSLParser()
500
+ _executor = DSLExecutor()
501
+
502
+
503
+ def parse_expression(text: str) -> DSLExpression:
504
+ """Parse DSL expression.
505
+
506
+ Args:
507
+ text: DSL expression text
508
+
509
+ Returns:
510
+ Parsed expression
511
+
512
+ Example:
513
+ >>> expr = parse_expression("lowpass(cutoff=1e6) | fft(nfft=8192)")
514
+
515
+ References:
516
+ API-010: Domain-Specific Language (DSL)
517
+ """
518
+ return _parser.parse(text)
519
+
520
+
521
+ def analyze(data: NDArray[np.float64], expression: str) -> Any:
522
+ """Analyze data using DSL expression.
523
+
524
+ Args:
525
+ data: Input data array
526
+ expression: DSL expression string
527
+
528
+ Returns:
529
+ Analysis result
530
+
531
+ Example:
532
+ >>> result = analyze(data, "lowpass(cutoff=1e6) | fft(nfft=8192)")
533
+
534
+ References:
535
+ API-010: Domain-Specific Language (DSL)
536
+ """
537
+ expr = parse_expression(expression)
538
+ return _executor.execute(expr, data)