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,498 @@
1
+ """Pythonic operators and utilities for signal analysis.
2
+
3
+ This module provides Pythonic operators, time-based indexing,
4
+ automatic unit conversion, and convenience utilities.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import re
10
+ from dataclasses import dataclass
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
+ "TimeIndex",
22
+ "UnitConverter",
23
+ "convert_units",
24
+ "make_pipeable",
25
+ ]
26
+
27
+
28
+ # =============================================================================
29
+ # =============================================================================
30
+
31
+
32
+ class TimeIndex:
33
+ """Time-based indexing for trace data.
34
+
35
+ Allows slicing trace data using time values instead of sample indices.
36
+
37
+ Example:
38
+ >>> ti = TimeIndex(data, sample_rate=1e9)
39
+ >>> # Get first 1 millisecond
40
+ >>> segment = ti["0ms":"1ms"]
41
+ >>> # Get from 100us to 200us
42
+ >>> segment = ti["100us":"200us"]
43
+
44
+ References:
45
+ API-016: Time-Based Indexing
46
+ """
47
+
48
+ # Time unit multipliers to seconds
49
+ TIME_UNITS = { # noqa: RUF012
50
+ "s": 1.0,
51
+ "ms": 1e-3,
52
+ "us": 1e-6,
53
+ "ns": 1e-9,
54
+ "ps": 1e-12,
55
+ }
56
+
57
+ def __init__(self, data: NDArray[np.float64], sample_rate: float, start_time: float = 0.0):
58
+ """Initialize time indexer.
59
+
60
+ Args:
61
+ data: Trace data array
62
+ sample_rate: Sample rate in Hz
63
+ start_time: Start time offset in seconds
64
+ """
65
+ self._data = data
66
+ self._sample_rate = sample_rate
67
+ self._start_time = start_time
68
+
69
+ @property
70
+ def duration(self) -> float:
71
+ """Get trace duration in seconds."""
72
+ return len(self._data) / self._sample_rate
73
+
74
+ @property
75
+ def time_axis(self) -> NDArray[np.float64]:
76
+ """Get time axis array."""
77
+ return np.arange(len(self._data)) / self._sample_rate + self._start_time
78
+
79
+ def _parse_time(self, time_str: str) -> float:
80
+ """Parse time string to seconds.
81
+
82
+ Args:
83
+ time_str: Time string (e.g., "100ms", "1.5us")
84
+
85
+ Returns:
86
+ Time in seconds
87
+
88
+ Raises:
89
+ ValueError: If time format is invalid or unit is unknown.
90
+ """
91
+ # Match number with optional unit
92
+ match = re.match(r"([-+]?\d*\.?\d+)\s*([a-zA-Z]*)", time_str.strip())
93
+ if not match:
94
+ raise ValueError(f"Invalid time format: {time_str}")
95
+
96
+ value = float(match.group(1))
97
+ unit = match.group(2).lower() or "s"
98
+
99
+ if unit not in self.TIME_UNITS:
100
+ raise ValueError(
101
+ f"Unknown time unit: {unit}. Valid units: {list(self.TIME_UNITS.keys())}"
102
+ )
103
+
104
+ return value * self.TIME_UNITS[unit]
105
+
106
+ def _time_to_index(self, time_seconds: float) -> int:
107
+ """Convert time to sample index.
108
+
109
+ Args:
110
+ time_seconds: Time in seconds
111
+
112
+ Returns:
113
+ Sample index
114
+ """
115
+ relative_time = time_seconds - self._start_time
116
+ index = int(relative_time * self._sample_rate)
117
+ return max(0, min(index, len(self._data) - 1))
118
+
119
+ def at(self, time: str | float) -> float:
120
+ """Get value at specific time.
121
+
122
+ Args:
123
+ time: Time as string or float (seconds)
124
+
125
+ Returns:
126
+ Value at that time
127
+ """
128
+ if isinstance(time, str):
129
+ time = self._parse_time(time)
130
+ index = self._time_to_index(time)
131
+ return float(self._data[index])
132
+
133
+ def slice(
134
+ self, start: str | float | None = None, end: str | float | None = None
135
+ ) -> NDArray[np.float64]:
136
+ """Slice data by time range.
137
+
138
+ Args:
139
+ start: Start time
140
+ end: End time
141
+
142
+ Returns:
143
+ Sliced data array
144
+ """
145
+ if start is not None:
146
+ if isinstance(start, str):
147
+ start = self._parse_time(start)
148
+ start_idx = self._time_to_index(start)
149
+ else:
150
+ start_idx = 0
151
+
152
+ if end is not None:
153
+ if isinstance(end, str):
154
+ end = self._parse_time(end)
155
+ end_idx = self._time_to_index(end)
156
+ else:
157
+ end_idx = len(self._data)
158
+
159
+ return self._data[start_idx:end_idx]
160
+
161
+ def __getitem__(self, key: slice | str | float) -> NDArray[np.float64] | float: # type: ignore[valid-type]
162
+ """Enable bracket notation for time-based indexing.
163
+
164
+ Args:
165
+ key: Slice with time strings, or single time
166
+
167
+ Returns:
168
+ Sliced data or single value
169
+ """
170
+ if isinstance(key, slice):
171
+ return self.slice(key.start, key.stop)
172
+ else:
173
+ return self.at(key)
174
+
175
+
176
+ # =============================================================================
177
+ # =============================================================================
178
+
179
+
180
+ @dataclass
181
+ class Unit:
182
+ """Unit definition with conversion factor.
183
+
184
+ Attributes:
185
+ name: Unit name
186
+ symbol: Unit symbol
187
+ factor: Conversion factor to base unit
188
+ base_unit: Base unit name
189
+ """
190
+
191
+ name: str
192
+ symbol: str
193
+ factor: float
194
+ base_unit: str
195
+
196
+
197
+ class UnitConverter:
198
+ """Automatic unit conversion for measurements.
199
+
200
+ Supports common electrical and signal analysis units with
201
+ automatic prefix handling (mV, uV, MHz, etc.).
202
+
203
+ Example:
204
+ >>> converter = UnitConverter()
205
+ >>> converter.convert(1000, "mV", "V")
206
+ 1.0
207
+ >>> converter.auto_scale(0.000001, "V")
208
+ (1.0, "uV")
209
+
210
+ References:
211
+ API-018: Automatic Unit Conversion
212
+ """
213
+
214
+ # SI prefixes
215
+ SI_PREFIXES = { # noqa: RUF012
216
+ "P": 1e15, # peta
217
+ "T": 1e12, # tera
218
+ "G": 1e9, # giga
219
+ "M": 1e6, # mega
220
+ "k": 1e3, # kilo
221
+ "": 1.0, # base
222
+ "m": 1e-3, # milli
223
+ "u": 1e-6, # micro
224
+ "n": 1e-9, # nano
225
+ "p": 1e-12, # pico
226
+ "f": 1e-15, # femto
227
+ }
228
+
229
+ # Base units
230
+ BASE_UNITS = { # noqa: RUF012
231
+ "V": "voltage",
232
+ "A": "current",
233
+ "W": "power",
234
+ "Hz": "frequency",
235
+ "s": "time",
236
+ "F": "capacitance",
237
+ "H": "inductance",
238
+ "Ohm": "resistance",
239
+ "dB": "decibel",
240
+ "dBm": "power_dbm",
241
+ "dBV": "voltage_dbv",
242
+ }
243
+
244
+ def __init__(self) -> None:
245
+ """Initialize converter."""
246
+ self._custom_units: dict[str, Unit] = {}
247
+
248
+ def _parse_unit(self, unit_str: str) -> tuple[float, str]:
249
+ """Parse unit string into prefix multiplier and base unit.
250
+
251
+ Args:
252
+ unit_str: Unit string (e.g., "mV", "MHz")
253
+
254
+ Returns:
255
+ Tuple of (multiplier, base_unit)
256
+ """
257
+ # Check for dB-based units first
258
+ for db_unit in ("dBm", "dBV", "dB"):
259
+ if unit_str.endswith(db_unit):
260
+ prefix = unit_str[: -len(db_unit)]
261
+ multiplier = self.SI_PREFIXES.get(prefix, 1.0)
262
+ return multiplier, db_unit
263
+
264
+ # Check for other base units
265
+ for base in sorted(self.BASE_UNITS.keys(), key=len, reverse=True):
266
+ if unit_str.endswith(base):
267
+ prefix = unit_str[: -len(base)]
268
+ multiplier = self.SI_PREFIXES.get(prefix, 1.0)
269
+ return multiplier, base
270
+
271
+ # No recognized base unit
272
+ return 1.0, unit_str
273
+
274
+ def convert(self, value: float, from_unit: str, to_unit: str) -> float:
275
+ """Convert value between units.
276
+
277
+ Args:
278
+ value: Value to convert
279
+ from_unit: Source unit
280
+ to_unit: Target unit
281
+
282
+ Returns:
283
+ Converted value
284
+
285
+ Raises:
286
+ ValueError: If units are incompatible
287
+ """
288
+ from_mult, from_base = self._parse_unit(from_unit)
289
+ to_mult, to_base = self._parse_unit(to_unit)
290
+
291
+ # Check compatibility
292
+ if from_base != to_base:
293
+ # Special handling for dB conversions
294
+ if from_base == "dBm" and to_base == "W":
295
+ return 10 ** ((value * from_mult - 30) / 10) / to_mult
296
+ elif from_base == "W" and to_base == "dBm":
297
+ return (10 * np.log10(value * from_mult) + 30) / to_mult # type: ignore[no-any-return]
298
+ elif from_base == "dBV" and to_base == "V":
299
+ return 10 ** ((value * from_mult) / 20) / to_mult
300
+ elif from_base == "V" and to_base == "dBV":
301
+ return 20 * np.log10(value * from_mult) / to_mult # type: ignore[no-any-return]
302
+ else:
303
+ raise ValueError(f"Cannot convert between {from_base} and {to_base}")
304
+
305
+ # Simple conversion
306
+ return value * from_mult / to_mult
307
+
308
+ def auto_scale(self, value: float, base_unit: str) -> tuple[float, str]:
309
+ """Automatically scale value to appropriate prefix.
310
+
311
+ Args:
312
+ value: Value in base units
313
+ base_unit: Base unit string
314
+
315
+ Returns:
316
+ Tuple of (scaled_value, unit_string)
317
+
318
+ Example:
319
+ >>> converter.auto_scale(0.000001, "V")
320
+ (1.0, "uV")
321
+ """
322
+ abs_value = abs(value) if value != 0 else 1
323
+
324
+ # Find appropriate prefix
325
+ prefixes_ordered = [
326
+ ("P", 1e15),
327
+ ("T", 1e12),
328
+ ("G", 1e9),
329
+ ("M", 1e6),
330
+ ("k", 1e3),
331
+ ("", 1.0),
332
+ ("m", 1e-3),
333
+ ("u", 1e-6),
334
+ ("n", 1e-9),
335
+ ("p", 1e-12),
336
+ ("f", 1e-15),
337
+ ]
338
+
339
+ for prefix, factor in prefixes_ordered:
340
+ scaled = abs_value / factor
341
+ if 1.0 <= scaled < 1000.0 or prefix == "f":
342
+ return value / factor, f"{prefix}{base_unit}"
343
+
344
+ # Default to base unit
345
+ return value, base_unit
346
+
347
+ def format_value(self, value: float, base_unit: str, precision: int = 3) -> str:
348
+ """Format value with automatic scaling.
349
+
350
+ Args:
351
+ value: Value in base units
352
+ base_unit: Base unit string
353
+ precision: Decimal precision
354
+
355
+ Returns:
356
+ Formatted string
357
+ """
358
+ scaled, unit = self.auto_scale(value, base_unit)
359
+ return f"{scaled:.{precision}g} {unit}"
360
+
361
+
362
+ def convert_units(value: float, from_unit: str, to_unit: str) -> float:
363
+ """Convert value between units.
364
+
365
+ Convenience function for unit conversion.
366
+
367
+ Args:
368
+ value: Value to convert
369
+ from_unit: Source unit
370
+ to_unit: Target unit
371
+
372
+ Returns:
373
+ Converted value
374
+
375
+ Example:
376
+ >>> convert_units(1000, "mV", "V")
377
+ 1.0
378
+ >>> convert_units(1, "MHz", "Hz")
379
+ 1000000.0
380
+
381
+ References:
382
+ API-018: Automatic Unit Conversion
383
+ """
384
+ return UnitConverter().convert(value, from_unit, to_unit)
385
+
386
+
387
+ # =============================================================================
388
+ # =============================================================================
389
+
390
+
391
+ class PipeableFunction:
392
+ """Wrapper for making functions pipeable with >> operator.
393
+
394
+ Example:
395
+ >>> @make_pipeable
396
+ >>> def lowpass(data, cutoff):
397
+ ... return filtered_data
398
+ >>> result = data >> lowpass(cutoff=1e6) >> normalize()
399
+
400
+ References:
401
+ API-015: Pythonic Operators
402
+ """
403
+
404
+ def __init__(self, func: Callable, *args: Any, **kwargs: Any): # type: ignore[type-arg]
405
+ """Initialize pipeable function.
406
+
407
+ Args:
408
+ func: Function to wrap
409
+ *args: Positional arguments
410
+ **kwargs: Keyword arguments
411
+ """
412
+ self._func = func
413
+ self._args = args
414
+ self._kwargs = kwargs
415
+
416
+ def __call__(self, data: Any) -> Any:
417
+ """Call function with data as first argument.
418
+
419
+ Args:
420
+ data: Input data
421
+
422
+ Returns:
423
+ Function result
424
+ """
425
+ return self._func(data, *self._args, **self._kwargs)
426
+
427
+ def __rrshift__(self, other: Any) -> Any:
428
+ """Enable data >> func() syntax.
429
+
430
+ Args:
431
+ other: Left operand (data)
432
+
433
+ Returns:
434
+ Function result
435
+ """
436
+ return self(other)
437
+
438
+
439
+ def make_pipeable(func: Callable) -> Callable: # type: ignore[type-arg]
440
+ """Decorator to make function pipeable with >> operator.
441
+
442
+ Args:
443
+ func: Function to wrap
444
+
445
+ Returns:
446
+ Wrapper that returns PipeableFunction
447
+
448
+ Example:
449
+ >>> @make_pipeable
450
+ >>> def scale(data, factor):
451
+ ... return data * factor
452
+ >>> result = data >> scale(factor=2)
453
+
454
+ References:
455
+ API-015: Pythonic Operators
456
+ """
457
+
458
+ def wrapper(*args: Any, **kwargs: Any) -> PipeableFunction:
459
+ return PipeableFunction(func, *args, **kwargs)
460
+
461
+ wrapper.__name__ = func.__name__
462
+ wrapper.__doc__ = func.__doc__
463
+ return wrapper
464
+
465
+
466
+ # Create pipeable versions of common operations
467
+ @make_pipeable
468
+ def scale(data: NDArray[np.float64], factor: float) -> NDArray[np.float64]:
469
+ """Scale data by factor."""
470
+ return data * factor
471
+
472
+
473
+ @make_pipeable
474
+ def offset(data: NDArray[np.float64], value: float) -> NDArray[np.float64]:
475
+ """Add offset to data."""
476
+ return data + value
477
+
478
+
479
+ @make_pipeable
480
+ def clip_values(data: NDArray[np.float64], low: float, high: float) -> NDArray[np.float64]:
481
+ """Clip data to range."""
482
+ return np.clip(data, low, high)
483
+
484
+
485
+ @make_pipeable
486
+ def normalize_data(data: NDArray[np.float64], method: str = "minmax") -> NDArray[np.float64]:
487
+ """Normalize data."""
488
+ if method == "minmax":
489
+ dmin, dmax = data.min(), data.max()
490
+ if dmax - dmin > 0:
491
+ result: NDArray[np.float64] = (data - dmin) / (dmax - dmin)
492
+ return result
493
+ elif method == "zscore":
494
+ std = data.std()
495
+ if std > 0:
496
+ result_z: NDArray[np.float64] = (data - data.mean()) / std
497
+ return result_z
498
+ return data