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,622 @@
1
+ """Real-time streaming APIs for live data acquisition and processing.
2
+
3
+ This module provides interfaces for real-time data capture, buffering, and
4
+ on-the-fly analysis of streaming waveforms. Supports pluggable input sources,
5
+ configurable sample buffers, and streaming statistics.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import threading
11
+ import time
12
+ from collections import deque
13
+ from collections.abc import Callable
14
+ from dataclasses import dataclass
15
+ from typing import TYPE_CHECKING, Any
16
+
17
+ import numpy as np
18
+
19
+ from ..core.types import TraceMetadata, WaveformTrace
20
+
21
+ if TYPE_CHECKING:
22
+ from collections.abc import Generator
23
+
24
+ from numpy.typing import NDArray
25
+
26
+
27
+ @dataclass
28
+ class RealtimeConfig:
29
+ """Configuration for real-time streaming."""
30
+
31
+ sample_rate: float
32
+ """Sample rate in Hz."""
33
+ buffer_size: int = 10000
34
+ """Size of the circular buffer in samples."""
35
+ chunk_size: int = 1000
36
+ """Number of samples to yield per chunk."""
37
+ timeout: float = 10.0
38
+ """Timeout in seconds for buffer operations."""
39
+ window_size: int | None = None
40
+ """Window size for rolling statistics. If None, uses buffer_size."""
41
+ enable_validation: bool = True
42
+ """Enable input validation."""
43
+
44
+ def validate(self) -> None:
45
+ """Validate configuration parameters.
46
+
47
+ Raises:
48
+ ValueError: If configuration is invalid.
49
+ """
50
+ if self.sample_rate <= 0:
51
+ raise ValueError("sample_rate must be positive")
52
+
53
+ if self.buffer_size <= 0:
54
+ raise ValueError("buffer_size must be positive")
55
+
56
+ if self.chunk_size <= 0:
57
+ raise ValueError("chunk_size must be positive")
58
+
59
+ if self.chunk_size > self.buffer_size:
60
+ raise ValueError("chunk_size cannot exceed buffer_size")
61
+
62
+ if self.timeout <= 0:
63
+ raise ValueError("timeout must be positive")
64
+
65
+ if self.window_size is not None and self.window_size <= 0:
66
+ raise ValueError("window_size must be positive")
67
+
68
+
69
+ class RealtimeBuffer:
70
+ """Thread-safe circular buffer for real-time streaming.
71
+
72
+ Maintains a fixed-size buffer of the most recent samples with
73
+ thread-safe read/write operations and overflow handling.
74
+
75
+ Example:
76
+ >>> buffer = RealtimeBuffer(config)
77
+ >>> buffer.write(samples)
78
+ >>> chunk = buffer.read(chunk_size)
79
+ """
80
+
81
+ def __init__(self, config: RealtimeConfig) -> None:
82
+ """Initialize real-time buffer.
83
+
84
+ Args:
85
+ config: Realtime configuration.
86
+ """
87
+ config.validate()
88
+ self.config = config
89
+
90
+ self._buffer: deque[float] = deque(maxlen=config.buffer_size)
91
+ self._lock = threading.RLock()
92
+ self._not_empty = threading.Condition(self._lock)
93
+ self._total_samples = 0
94
+ self._overflow_count = 0
95
+
96
+ def write(self, data: NDArray[np.floating[Any]]) -> int:
97
+ """Write samples to buffer.
98
+
99
+ Args:
100
+ data: Array of samples to write.
101
+
102
+ Returns:
103
+ Number of samples written.
104
+
105
+ Raises:
106
+ TypeError: If data is not numeric array.
107
+ """
108
+ if not isinstance(data, np.ndarray):
109
+ raise TypeError("data must be numpy array")
110
+
111
+ if data.dtype.kind not in "fc": # float or complex
112
+ raise TypeError("data must be float or complex array")
113
+
114
+ with self._not_empty:
115
+ initial_len = len(self._buffer)
116
+
117
+ # Write samples to buffer
118
+ for sample in data.flat:
119
+ self._buffer.append(float(sample))
120
+
121
+ # Track overflow
122
+ if len(self._buffer) == self.config.buffer_size:
123
+ written = self.config.buffer_size - initial_len
124
+ if written < len(data):
125
+ self._overflow_count += len(data) - written
126
+ else:
127
+ written = len(data)
128
+
129
+ self._total_samples += len(data)
130
+ self._not_empty.notify_all()
131
+
132
+ return written
133
+
134
+ def read(self, n_samples: int, timeout: float | None = None) -> NDArray[np.float64]:
135
+ """Read samples from buffer (blocking).
136
+
137
+ Args:
138
+ n_samples: Number of samples to read.
139
+ timeout: Timeout in seconds (None = use config timeout).
140
+
141
+ Returns:
142
+ Array of samples, may be shorter if timeout occurs.
143
+
144
+ Raises:
145
+ ValueError: If n_samples is invalid.
146
+ TimeoutError: If timeout occurs without sufficient data.
147
+ """
148
+ if n_samples <= 0:
149
+ raise ValueError("n_samples must be positive")
150
+
151
+ if timeout is None:
152
+ timeout = self.config.timeout
153
+
154
+ with self._not_empty:
155
+ # Wait for data with timeout
156
+ if not self._wait_for_data(n_samples, timeout):
157
+ if len(self._buffer) == 0:
158
+ raise TimeoutError("No data available")
159
+
160
+ # Read available samples
161
+ n_read = min(n_samples, len(self._buffer))
162
+ data = np.array(list(self._buffer)[:n_read], dtype=np.float64)
163
+ return data
164
+
165
+ def _wait_for_data(self, n_samples: int, timeout: float) -> bool:
166
+ """Wait for minimum data in buffer.
167
+
168
+ Args:
169
+ n_samples: Minimum samples to wait for.
170
+ timeout: Timeout in seconds.
171
+
172
+ Returns:
173
+ True if sufficient data available, False on timeout.
174
+ """
175
+ deadline = time.time() + timeout
176
+ while len(self._buffer) < n_samples:
177
+ remaining = deadline - time.time()
178
+ if remaining <= 0:
179
+ return False
180
+ if not self._not_empty.wait(timeout=min(remaining, 0.1)):
181
+ continue
182
+ return True
183
+
184
+ def get_available(self) -> int:
185
+ """Get number of available samples in buffer.
186
+
187
+ Returns:
188
+ Number of samples currently in buffer.
189
+ """
190
+ with self._lock:
191
+ return len(self._buffer)
192
+
193
+ def get_stats(self) -> dict[str, int]:
194
+ """Get buffer statistics.
195
+
196
+ Returns:
197
+ Dictionary with buffer stats (total_samples, overflow_count, available).
198
+ """
199
+ with self._lock:
200
+ return {
201
+ "total_samples": self._total_samples,
202
+ "overflow_count": self._overflow_count,
203
+ "available": len(self._buffer),
204
+ }
205
+
206
+ def clear(self) -> None:
207
+ """Clear buffer contents.
208
+
209
+ Example:
210
+ >>> buffer.clear()
211
+ """
212
+ with self._lock:
213
+ self._buffer.clear()
214
+ self._total_samples = 0
215
+ self._overflow_count = 0
216
+
217
+ def close(self) -> None:
218
+ """Close buffer and release resources.
219
+
220
+ Example:
221
+ >>> buffer.close()
222
+ """
223
+ self.clear()
224
+
225
+
226
+ class RealtimeSource:
227
+ """Base class for real-time data sources.
228
+
229
+ Subclass to implement custom data sources that feed the real-time
230
+ buffer. Must implement the acquire method.
231
+
232
+ Example:
233
+ >>> class CustomSource(RealtimeSource):
234
+ ... def acquire(self) -> np.ndarray:
235
+ ... # Get data from hardware
236
+ ... return np.array([...])
237
+ """
238
+
239
+ def acquire(self) -> NDArray[np.floating[Any]]:
240
+ """Acquire samples from source.
241
+
242
+ Raises:
243
+ NotImplementedError: Subclasses must implement.
244
+ """
245
+ raise NotImplementedError("Subclasses must implement acquire()")
246
+
247
+ def start(self) -> None:
248
+ """Start acquisition (optional).
249
+
250
+ Default implementation does nothing.
251
+ """
252
+
253
+ def stop(self) -> None:
254
+ """Stop acquisition (optional).
255
+
256
+ Default implementation does nothing.
257
+ """
258
+
259
+
260
+ class SimulatedSource(RealtimeSource):
261
+ """Simulated data source for testing and examples.
262
+
263
+ Generates synthetic waveforms (sine, square, noise) for real-time
264
+ streaming without requiring hardware.
265
+
266
+ Args:
267
+ signal_type: Type of signal ("sine", "square", "noise", "mixed").
268
+ frequency: Signal frequency in Hz (for periodic signals).
269
+ amplitude: Signal amplitude.
270
+ sample_rate: Sample rate in Hz.
271
+ chunk_size: Number of samples per acquire() call.
272
+ noise_level: Noise amplitude (0-1 relative to signal).
273
+
274
+ Example:
275
+ >>> source = SimulatedSource("sine", frequency=1000, sample_rate=48000)
276
+ >>> data = source.acquire() # Get one chunk
277
+ """
278
+
279
+ def __init__(
280
+ self,
281
+ signal_type: str = "sine",
282
+ *,
283
+ frequency: float = 1000.0,
284
+ amplitude: float = 1.0,
285
+ sample_rate: float = 48000.0,
286
+ chunk_size: int = 1024,
287
+ noise_level: float = 0.0,
288
+ ) -> None:
289
+ """Initialize simulated source."""
290
+ self.signal_type = signal_type
291
+ self.frequency = frequency
292
+ self.amplitude = amplitude
293
+ self.sample_rate = sample_rate
294
+ self.chunk_size = chunk_size
295
+ self.noise_level = noise_level
296
+ self._phase = 0.0
297
+ self._running = False
298
+
299
+ def acquire(self) -> NDArray[np.floating[Any]]:
300
+ """Acquire one chunk of simulated data.
301
+
302
+ Returns:
303
+ Array of simulated samples.
304
+
305
+ Raises:
306
+ ValueError: If signal_type is not recognized.
307
+ """
308
+ t = np.arange(self.chunk_size) / self.sample_rate
309
+ t += self._phase / (2 * np.pi * self.frequency)
310
+
311
+ # Generate base signal
312
+ if self.signal_type == "sine":
313
+ signal = self.amplitude * np.sin(2 * np.pi * self.frequency * t)
314
+ elif self.signal_type == "square":
315
+ signal = self.amplitude * np.sign(np.sin(2 * np.pi * self.frequency * t))
316
+ elif self.signal_type == "noise":
317
+ signal = self.amplitude * np.random.randn(self.chunk_size)
318
+ elif self.signal_type == "mixed":
319
+ # Mix sine at frequency and sine at 3*frequency
320
+ signal = self.amplitude * (
321
+ np.sin(2 * np.pi * self.frequency * t)
322
+ + 0.3 * np.sin(2 * np.pi * 3 * self.frequency * t)
323
+ )
324
+ else:
325
+ raise ValueError(f"Unknown signal type: {self.signal_type}")
326
+
327
+ # Add noise if requested
328
+ if self.noise_level > 0:
329
+ noise = self.noise_level * self.amplitude * np.random.randn(self.chunk_size)
330
+ signal = signal + noise
331
+
332
+ # Update phase for continuity
333
+ self._phase = (
334
+ self._phase + 2 * np.pi * self.frequency * self.chunk_size / self.sample_rate
335
+ ) % (2 * np.pi)
336
+
337
+ # Cast to the expected return type
338
+ return signal.astype(np.float64) # type: ignore[return-value,no-any-return]
339
+
340
+ def start(self) -> None:
341
+ """Start acquisition."""
342
+ self._running = True
343
+ self._phase = 0.0
344
+
345
+ def stop(self) -> None:
346
+ """Stop acquisition."""
347
+ self._running = False
348
+
349
+
350
+ class RealtimeAnalyzer:
351
+ """Analyzer for real-time streaming data.
352
+
353
+ Maintains rolling statistics and runs configurable analysis on
354
+ incoming data chunks.
355
+
356
+ Example:
357
+ >>> config = RealtimeConfig(sample_rate=1e6)
358
+ >>> analyzer = RealtimeAnalyzer(config)
359
+ >>> analyzer.accumulate(chunk)
360
+ >>> stats = analyzer.get_statistics()
361
+ """
362
+
363
+ def __init__(self, config: RealtimeConfig) -> None:
364
+ """Initialize real-time analyzer.
365
+
366
+ Args:
367
+ config: Realtime configuration.
368
+ """
369
+ config.validate()
370
+ self.config = config
371
+
372
+ self._window_size = config.window_size or config.buffer_size
373
+ self._samples: deque[float] = deque(maxlen=self._window_size)
374
+ self._sum = 0.0
375
+ self._sum_sq = 0.0
376
+ self._min = float("inf")
377
+ self._max = float("-inf")
378
+ self._update_count = 0
379
+
380
+ def accumulate(self, data: NDArray[np.floating[Any]]) -> None:
381
+ """Accumulate statistics from data chunk.
382
+
383
+ Args:
384
+ data: Array of samples to process.
385
+
386
+ Raises:
387
+ TypeError: If data is not numeric array.
388
+ """
389
+ if not isinstance(data, np.ndarray):
390
+ raise TypeError("data must be numpy array")
391
+
392
+ if data.dtype.kind not in "fc":
393
+ raise TypeError("data must be float or complex array")
394
+
395
+ for sample in data.flat:
396
+ sample_float = float(sample)
397
+
398
+ # Remove oldest sample from stats if window full
399
+ if len(self._samples) == self._window_size:
400
+ old_sample = self._samples[0]
401
+ self._sum -= old_sample
402
+ self._sum_sq -= old_sample**2
403
+
404
+ # Add new sample
405
+ self._samples.append(sample_float)
406
+ self._sum += sample_float
407
+ self._sum_sq += sample_float**2
408
+ self._min = min(self._min, sample_float)
409
+ self._max = max(self._max, sample_float)
410
+ self._update_count += 1
411
+
412
+ def get_statistics(self) -> dict[str, float]:
413
+ """Get current rolling statistics.
414
+
415
+ Returns:
416
+ Dictionary with mean, std, min, max, peak_to_peak.
417
+
418
+ Raises:
419
+ ValueError: If no data accumulated.
420
+ """
421
+ if len(self._samples) == 0:
422
+ raise ValueError("No data accumulated yet")
423
+
424
+ n = len(self._samples)
425
+ mean = self._sum / n
426
+ variance = (self._sum_sq / n) - (mean**2)
427
+ std = np.sqrt(max(0, variance))
428
+
429
+ return {
430
+ "mean": mean,
431
+ "std": std,
432
+ "min": self._min,
433
+ "max": self._max,
434
+ "peak_to_peak": self._max - self._min,
435
+ "n_samples": n,
436
+ }
437
+
438
+ def reset(self) -> None:
439
+ """Reset accumulated statistics.
440
+
441
+ Example:
442
+ >>> analyzer.reset()
443
+ """
444
+ self._samples.clear()
445
+ self._sum = 0.0
446
+ self._sum_sq = 0.0
447
+ self._min = float("inf")
448
+ self._max = float("-inf")
449
+
450
+
451
+ class RealtimeStream:
452
+ """High-level API for real-time data streaming and analysis.
453
+
454
+ Manages a data source, circular buffer, and analyzer for streaming
455
+ waveform processing.
456
+
457
+ Example:
458
+ >>> config = RealtimeConfig(sample_rate=1e6)
459
+ >>> source = CustomSource()
460
+ >>> stream = RealtimeStream(config, source)
461
+ >>> stream.start()
462
+ >>> for chunk in stream.iter_chunks(chunk_size=1000):
463
+ ... print(chunk.data.mean())
464
+ >>> stream.stop()
465
+ """
466
+
467
+ def __init__(
468
+ self,
469
+ config: RealtimeConfig,
470
+ source: RealtimeSource,
471
+ on_chunk: Callable[[WaveformTrace], None] | None = None,
472
+ ) -> None:
473
+ """Initialize real-time stream.
474
+
475
+ Args:
476
+ config: Realtime configuration.
477
+ source: Data source for acquisition.
478
+ on_chunk: Optional callback for each chunk acquired.
479
+ """
480
+ config.validate()
481
+ self.config = config
482
+ self.source = source
483
+ self._on_chunk = on_chunk
484
+
485
+ self._buffer = RealtimeBuffer(config)
486
+ self._analyzer = RealtimeAnalyzer(config)
487
+ self._is_running = False
488
+ self._acquire_thread: threading.Thread | None = None
489
+ self._chunk_count = 0
490
+
491
+ def start(self) -> None:
492
+ """Start acquisition thread.
493
+
494
+ Example:
495
+ >>> stream.start()
496
+ """
497
+ if self._is_running:
498
+ return
499
+
500
+ self._is_running = True
501
+ self.source.start()
502
+
503
+ self._acquire_thread = threading.Thread(target=self._acquire_loop, daemon=True)
504
+ self._acquire_thread.start()
505
+
506
+ def stop(self) -> None:
507
+ """Stop acquisition thread.
508
+
509
+ Example:
510
+ >>> stream.stop()
511
+ """
512
+ if not self._is_running:
513
+ return
514
+
515
+ self._is_running = False
516
+ self.source.stop()
517
+
518
+ if self._acquire_thread is not None:
519
+ self._acquire_thread.join(timeout=5.0)
520
+
521
+ def iter_chunks(self) -> Generator[WaveformTrace, None, None]:
522
+ """Iterate over data chunks as they arrive.
523
+
524
+ Yields chunks of configured size as data becomes available.
525
+
526
+ Yields:
527
+ WaveformTrace chunks.
528
+
529
+ Raises:
530
+ RuntimeError: If stream not started.
531
+
532
+ Example:
533
+ >>> for chunk in stream.iter_chunks():
534
+ ... print(f"Chunk {chunk.metadata.start_index} has {len(chunk.data)} samples")
535
+ """
536
+ if not self._is_running:
537
+ raise RuntimeError("Stream not started")
538
+
539
+ sample_index = 0
540
+
541
+ while self._is_running:
542
+ try:
543
+ data = self._buffer.read(self.config.chunk_size)
544
+
545
+ if len(data) > 0:
546
+ # Accumulate statistics
547
+ self._analyzer.accumulate(data)
548
+
549
+ # Create trace chunk
550
+ metadata = TraceMetadata(
551
+ sample_rate=self.config.sample_rate,
552
+ )
553
+
554
+ chunk = WaveformTrace(data=data, metadata=metadata)
555
+ sample_index += len(data)
556
+ self._chunk_count += 1
557
+
558
+ # Call callback if provided
559
+ if self._on_chunk is not None:
560
+ self._on_chunk(chunk)
561
+
562
+ yield chunk
563
+
564
+ except TimeoutError:
565
+ # Check if stopped during timeout
566
+ # Note: _is_running may change asynchronously in another thread
567
+ continue
568
+
569
+ def get_statistics(self) -> dict[str, float]:
570
+ """Get current statistics.
571
+
572
+ Returns:
573
+ Dictionary with stream statistics.
574
+ """
575
+ try:
576
+ return self._analyzer.get_statistics()
577
+ except ValueError:
578
+ return {
579
+ "mean": 0.0,
580
+ "std": 0.0,
581
+ "min": 0.0,
582
+ "max": 0.0,
583
+ "peak_to_peak": 0.0,
584
+ "n_samples": 0,
585
+ }
586
+
587
+ def get_buffer_stats(self) -> dict[str, int]:
588
+ """Get buffer statistics.
589
+
590
+ Returns:
591
+ Dictionary with buffer stats.
592
+ """
593
+ return self._buffer.get_stats()
594
+
595
+ def get_chunk_count(self) -> int:
596
+ """Get total number of chunks acquired.
597
+
598
+ Returns:
599
+ Number of chunks yielded so far.
600
+ """
601
+ return self._chunk_count
602
+
603
+ def _acquire_loop(self) -> None:
604
+ """Background thread that acquires data from source."""
605
+ while self._is_running:
606
+ try:
607
+ data = self.source.acquire()
608
+ if data is not None and len(data) > 0:
609
+ self._buffer.write(data)
610
+ except Exception:
611
+ if self._is_running:
612
+ time.sleep(0.001)
613
+
614
+
615
+ __all__ = [
616
+ "RealtimeAnalyzer",
617
+ "RealtimeBuffer",
618
+ "RealtimeConfig",
619
+ "RealtimeSource",
620
+ "RealtimeStream",
621
+ "SimulatedSource",
622
+ ]
@@ -0,0 +1,54 @@
1
+ """Testing utilities for TraceKit.
2
+
3
+ This module provides synthetic test data generation with known ground truth
4
+ for validation and testing purposes.
5
+
6
+ Signal Generators
7
+ -----------------
8
+ These functions return WaveformTrace objects ready for use:
9
+
10
+ - generate_sine_wave: Pure sine wave
11
+ - generate_square_wave: Square wave with configurable duty cycle
12
+ - generate_dc: DC (constant) signal
13
+ - generate_multi_tone: Sum of multiple sine waves
14
+ - generate_pulse: Single pulse with configurable rise/fall times
15
+
16
+ Example:
17
+ >>> from oscura.testing import generate_sine_wave, generate_square_wave
18
+ >>> sine = generate_sine_wave(frequency=1e6, amplitude=1.0)
19
+ >>> square = generate_square_wave(frequency=500e3, duty_cycle=0.3)
20
+ """
21
+
22
+ from oscura.testing.synthetic import (
23
+ GroundTruth,
24
+ SyntheticDataGenerator,
25
+ SyntheticMessageConfig,
26
+ SyntheticPacketConfig,
27
+ SyntheticSignalConfig,
28
+ generate_dc,
29
+ generate_digital_signal,
30
+ generate_multi_tone,
31
+ generate_packets,
32
+ generate_protocol_messages,
33
+ generate_pulse,
34
+ generate_sine_wave,
35
+ generate_square_wave,
36
+ generate_test_dataset,
37
+ )
38
+
39
+ __all__ = [
40
+ "GroundTruth",
41
+ "SyntheticDataGenerator",
42
+ "SyntheticMessageConfig",
43
+ "SyntheticPacketConfig",
44
+ "SyntheticSignalConfig",
45
+ "generate_dc",
46
+ "generate_digital_signal",
47
+ "generate_multi_tone",
48
+ "generate_packets",
49
+ "generate_protocol_messages",
50
+ "generate_pulse",
51
+ "generate_sine_wave",
52
+ "generate_square_wave",
53
+ "generate_test_dataset",
54
+ ]