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,149 @@
1
+ """Context extraction around points of interest.
2
+
3
+
4
+ This module provides efficient extraction of signal context around
5
+ events, maintaining original time references for debugging workflows.
6
+ """
7
+
8
+ from typing import Any
9
+
10
+ import numpy as np
11
+ from numpy.typing import NDArray
12
+
13
+
14
+ def extract_context(
15
+ trace: NDArray[np.float64],
16
+ index: int | list[int] | NDArray[np.int_],
17
+ *,
18
+ before: int = 100,
19
+ after: int = 100,
20
+ sample_rate: float | None = None,
21
+ include_metadata: bool = True,
22
+ ) -> dict[str, Any] | list[dict[str, Any]]:
23
+ """Extract signal context around a point of interest.
24
+
25
+ : Context extraction with time reference preservation.
26
+ Supports batch extraction for multiple indices and optional protocol data.
27
+
28
+ Args:
29
+ trace: Input signal trace
30
+ index: Sample index or list of indices to extract context around.
31
+ Can be int, list of ints, or numpy array.
32
+ before: Number of samples to include before index (default: 100)
33
+ after: Number of samples to include after index (default: 100)
34
+ sample_rate: Optional sample rate in Hz for time calculations
35
+ include_metadata: Include metadata dict with context info (default: True)
36
+
37
+ Returns:
38
+ If index is scalar: Single context dictionary
39
+ If index is list/array: List of context dictionaries
40
+
41
+ Each context dictionary contains:
42
+ - data: Extracted sub-trace array
43
+ - start_index: Starting index in original trace
44
+ - end_index: Ending index in original trace
45
+ - center_index: Center index (original query index)
46
+ - time_reference: Time offset if sample_rate provided
47
+ - length: Number of samples in context
48
+
49
+ Raises:
50
+ ValueError: If index is out of bounds
51
+ ValueError: If before or after are negative
52
+
53
+ Examples:
54
+ >>> # Extract context around a glitch
55
+ >>> trace = np.random.randn(1000)
56
+ >>> glitch_index = 500
57
+ >>> context = extract_context(
58
+ ... trace,
59
+ ... glitch_index,
60
+ ... before=50,
61
+ ... after=50,
62
+ ... sample_rate=1e6
63
+ ... )
64
+ >>> print(f"Context length: {len(context['data'])}")
65
+ >>> print(f"Time reference: {context['time_reference']*1e6:.2f} µs")
66
+
67
+ >>> # Batch extraction for multiple events
68
+ >>> event_indices = [100, 200, 300]
69
+ >>> contexts = extract_context(
70
+ ... trace,
71
+ ... event_indices,
72
+ ... before=25,
73
+ ... after=25
74
+ ... )
75
+ >>> print(f"Extracted {len(contexts)} contexts")
76
+
77
+ Notes:
78
+ - Handles edge cases at trace boundaries automatically
79
+ - Context may be shorter than before+after at boundaries
80
+ - Time reference is relative to start of extracted context
81
+ - Original trace is not modified
82
+
83
+ References:
84
+ SRCH-003: Context Extraction
85
+ """
86
+ if before < 0 or after < 0:
87
+ raise ValueError("before and after must be non-negative")
88
+
89
+ if trace.size == 0:
90
+ raise ValueError("Trace cannot be empty")
91
+
92
+ # Handle single index vs multiple indices
93
+ if isinstance(index, int | np.integer):
94
+ indices = [int(index)]
95
+ return_single = True
96
+ else:
97
+ indices = [int(i) for i in index]
98
+ return_single = False
99
+
100
+ # Validate indices
101
+ for idx in indices:
102
+ if idx < 0 or idx >= len(trace):
103
+ raise ValueError(f"Index {idx} out of bounds for trace of length {len(trace)}")
104
+
105
+ # Extract contexts
106
+ contexts = []
107
+
108
+ for idx in indices:
109
+ # Calculate window bounds with boundary handling
110
+ start_idx = max(0, idx - before)
111
+ end_idx = min(len(trace), idx + after + 1)
112
+
113
+ # Extract data
114
+ data = trace[start_idx:end_idx].copy()
115
+
116
+ # Build context dictionary
117
+ context: dict[str, Any] = {
118
+ "data": data,
119
+ "start_index": start_idx,
120
+ "end_index": end_idx,
121
+ "center_index": idx,
122
+ "length": len(data),
123
+ }
124
+
125
+ # Add time reference if sample rate provided
126
+ if sample_rate is not None:
127
+ time_offset = start_idx / sample_rate
128
+ context["time_reference"] = time_offset
129
+ context["sample_rate"] = sample_rate
130
+
131
+ # Time array for the context
132
+ dt = 1.0 / sample_rate
133
+ context["time_array"] = np.arange(len(data)) * dt + time_offset
134
+
135
+ if include_metadata:
136
+ context["metadata"] = {
137
+ "samples_before": idx - start_idx,
138
+ "samples_after": end_idx - idx - 1,
139
+ "at_start_boundary": start_idx == 0,
140
+ "at_end_boundary": end_idx == len(trace),
141
+ }
142
+
143
+ contexts.append(context)
144
+
145
+ # Return single context or list
146
+ if return_single:
147
+ return contexts[0]
148
+ else:
149
+ return contexts
@@ -0,0 +1,160 @@
1
+ """Pattern search in digital traces.
2
+
3
+
4
+ This module provides efficient bit pattern matching in digital signals
5
+ with wildcard support via mask parameter.
6
+ """
7
+
8
+ from typing import cast
9
+
10
+ import numpy as np
11
+ from numpy.typing import NDArray
12
+
13
+
14
+ def find_pattern(
15
+ trace: NDArray[np.float64] | NDArray[np.uint8],
16
+ pattern: int | NDArray[np.uint8],
17
+ mask: int | NDArray[np.uint8] | None = None,
18
+ *,
19
+ threshold: float | None = None,
20
+ min_spacing: int = 1,
21
+ ) -> list[tuple[int, NDArray[np.uint8]]]:
22
+ """Find occurrences of bit patterns in digital traces.
23
+
24
+ : Pattern search with wildcard support via mask.
25
+ Works on both raw analog traces (with threshold) and decoded digital data.
26
+
27
+ Args:
28
+ trace: Input trace array. If analog (float), threshold is required.
29
+ If already digital (uint8), threshold is ignored.
30
+ pattern: Bit pattern to search for. Can be:
31
+ - Integer: e.g., 0b10101010 (8-bit pattern)
32
+ - Array: sequence of bytes to match
33
+ mask: Optional mask for wildcard matching. Bits set to 0 in mask
34
+ are "don't care" positions. Can be:
35
+ - Integer: e.g., 0xFF (all bits matter)
36
+ - Array: per-byte masks
37
+ If None, all bits must match (equivalent to all 1s).
38
+ threshold: Threshold for converting analog to digital (required if
39
+ trace is analog). Typically mid-level of logic family.
40
+ min_spacing: Minimum samples between detected patterns to avoid
41
+ overlapping matches (default: 1)
42
+
43
+ Returns:
44
+ List of (index, match) tuples where:
45
+ - index: Starting sample index of the pattern
46
+ - match: The actual matched bit sequence as uint8 array
47
+
48
+ Raises:
49
+ ValueError: If analog trace provided without threshold
50
+ ValueError: If pattern is empty
51
+
52
+ Examples:
53
+ >>> # Find 0xAA pattern in analog trace
54
+ >>> import numpy as np
55
+ >>> trace = np.array([0, 1, 0, 1, 0, 1, 0, 1, 0, 0])
56
+ >>> matches = find_pattern(trace, 0b10101010, threshold=0.5)
57
+ >>> print(f"Found {len(matches)} matches")
58
+
59
+ >>> # Wildcard search: find 0b1010xxxx (x = don't care)
60
+ >>> pattern = 0b10100000
61
+ >>> mask = 0b11110000 # Only upper 4 bits matter
62
+ >>> matches = find_pattern(trace, pattern, mask, threshold=0.5)
63
+
64
+ >>> # Search in already-decoded digital data
65
+ >>> digital = np.array([0xAA, 0x55, 0xAA, 0x00], dtype=np.uint8)
66
+ >>> matches = find_pattern(digital, 0xAA)
67
+
68
+ Notes:
69
+ - For analog traces, values >= threshold are interpreted as '1'
70
+ - Mask bits: 1 = must match, 0 = don't care
71
+ - Overlapping patterns can be filtered with min_spacing > 1
72
+ - Returns empty list if no matches found
73
+
74
+ References:
75
+ SRCH-001: Pattern Search
76
+ """
77
+ if trace.size == 0:
78
+ return []
79
+
80
+ # Convert pattern to array if integer
81
+ if isinstance(pattern, int):
82
+ if pattern < 0:
83
+ raise ValueError("Pattern must be non-negative")
84
+ # Convert to byte array (variable length based on value)
85
+ pattern_bytes = []
86
+ if pattern == 0:
87
+ pattern_bytes = [0]
88
+ else:
89
+ temp = pattern
90
+ while temp > 0:
91
+ pattern_bytes.insert(0, temp & 0xFF)
92
+ temp >>= 8
93
+ pattern_arr = np.array(pattern_bytes, dtype=np.uint8)
94
+ else:
95
+ pattern_arr = np.asarray(pattern, dtype=np.uint8)
96
+
97
+ if pattern_arr.size == 0:
98
+ raise ValueError("Pattern cannot be empty")
99
+
100
+ # Convert mask to array if integer
101
+ if mask is not None:
102
+ if isinstance(mask, int):
103
+ mask_bytes: list[int] = []
104
+ temp = mask
105
+ # Match pattern length
106
+ for _ in range(len(pattern_arr)):
107
+ mask_bytes.insert(0, temp & 0xFF)
108
+ temp >>= 8
109
+ mask_arr = np.array(mask_bytes, dtype=np.uint8)
110
+ else:
111
+ mask_arr = np.asarray(mask, dtype=np.uint8)
112
+
113
+ # Ensure mask and pattern have same length
114
+ if mask_arr.size != pattern_arr.size:
115
+ raise ValueError("Mask and pattern must have same length")
116
+ else:
117
+ # Default: all bits matter
118
+ mask_arr = np.full(pattern_arr.size, 0xFF, dtype=np.uint8)
119
+
120
+ # Convert analog trace to digital if needed
121
+ if trace.dtype != np.uint8:
122
+ if threshold is None:
123
+ raise ValueError(
124
+ "Threshold required for analog trace conversion. "
125
+ "Provide threshold parameter or pre-convert to digital."
126
+ )
127
+ # Simple threshold conversion: >= threshold is 1
128
+ digital = (trace >= threshold).astype(np.uint8)
129
+ # Pack bits into bytes (8 samples per byte)
130
+ # Pad to multiple of 8
131
+ n_pad = (8 - len(digital) % 8) % 8
132
+ if n_pad:
133
+ digital = np.pad(digital, (0, n_pad), constant_values=0)
134
+ # Pack bits
135
+ digital_packed: NDArray[np.uint8] = np.packbits(digital, bitorder="big")
136
+ else:
137
+ digital_packed = cast("NDArray[np.uint8]", trace)
138
+
139
+ if digital_packed.size < pattern_arr.size:
140
+ return []
141
+
142
+ # Sliding window pattern matching with mask
143
+ matches: list[tuple[int, NDArray[np.uint8]]] = []
144
+ i = 0
145
+
146
+ while i <= len(digital_packed) - len(pattern_arr):
147
+ window = digital_packed[i : i + len(pattern_arr)]
148
+
149
+ # Apply mask and compare
150
+ masked_window = window & mask_arr
151
+ masked_pattern = pattern_arr & mask_arr
152
+
153
+ if np.array_equal(masked_window, masked_pattern):
154
+ matches.append((i, window.copy()))
155
+ # Skip ahead by min_spacing to avoid overlapping matches
156
+ i += max(1, min_spacing)
157
+ else:
158
+ i += 1
159
+
160
+ return matches
@@ -0,0 +1,34 @@
1
+ """Session management for TraceKit analysis sessions.
2
+
3
+ This module provides session save/restore, trace annotations, and
4
+ operation history tracking.
5
+
6
+
7
+ Example:
8
+ >>> import oscura as tk
9
+ >>> session = tk.Session()
10
+ >>> session.load_trace('capture.wfm')
11
+ >>> session.annotate(time=1.5e-6, text='Glitch here')
12
+ >>> session.save('debug_session.tks')
13
+ >>>
14
+ >>> # Later...
15
+ >>> session = tk.load_session('debug_session.tks')
16
+ >>> print(session.annotations)
17
+ """
18
+
19
+ from oscura.session.annotations import Annotation, AnnotationLayer, AnnotationType
20
+ from oscura.session.history import HistoryEntry, OperationHistory
21
+ from oscura.session.session import Session, load_session
22
+
23
+ __all__ = [
24
+ # Annotations (SESS-002)
25
+ "Annotation",
26
+ "AnnotationLayer",
27
+ "AnnotationType",
28
+ # History (SESS-003)
29
+ "HistoryEntry",
30
+ "OperationHistory",
31
+ # Session (SESS-001)
32
+ "Session",
33
+ "load_session",
34
+ ]
@@ -0,0 +1,289 @@
1
+ """Trace annotation support.
2
+
3
+ This module provides annotation capabilities for marking points of interest
4
+ in signal traces.
5
+
6
+
7
+ Example:
8
+ >>> layer = AnnotationLayer("Debug Markers")
9
+ >>> layer.add(Annotation(time=1.5e-6, text="Glitch detected"))
10
+ >>> layer.add(Annotation(time_range=(2e-6, 3e-6), text="Data packet"))
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ from dataclasses import dataclass, field
16
+ from datetime import datetime
17
+ from enum import Enum
18
+ from typing import Any
19
+
20
+
21
+ class AnnotationType(Enum):
22
+ """Types of annotations."""
23
+
24
+ POINT = "point" # Single time point
25
+ RANGE = "range" # Time range
26
+ VERTICAL = "vertical" # Vertical line
27
+ HORIZONTAL = "horizontal" # Horizontal line
28
+ REGION = "region" # 2D region (time + amplitude)
29
+ TEXT = "text" # Free-floating text
30
+
31
+
32
+ @dataclass
33
+ class Annotation:
34
+ """Single annotation on a trace.
35
+
36
+ Attributes:
37
+ text: Annotation text/label
38
+ time: Time point (for point annotations)
39
+ time_range: (start, end) time range
40
+ amplitude: Amplitude value (for horizontal lines)
41
+ amplitude_range: (min, max) amplitude range
42
+ annotation_type: Type of annotation
43
+ color: Display color (hex or name)
44
+ style: Line style ('solid', 'dashed', 'dotted')
45
+ visible: Whether annotation is visible
46
+ created_at: Creation timestamp
47
+ metadata: Additional metadata
48
+ """
49
+
50
+ text: str
51
+ time: float | None = None
52
+ time_range: tuple[float, float] | None = None
53
+ amplitude: float | None = None
54
+ amplitude_range: tuple[float, float] | None = None
55
+ annotation_type: AnnotationType = AnnotationType.POINT
56
+ color: str = "#FF6B6B"
57
+ style: str = "solid"
58
+ visible: bool = True
59
+ created_at: datetime = field(default_factory=datetime.now)
60
+ metadata: dict[str, Any] = field(default_factory=dict)
61
+
62
+ def __post_init__(self) -> None:
63
+ """Infer annotation type from provided parameters."""
64
+ if self.annotation_type == AnnotationType.POINT:
65
+ if self.time_range is not None:
66
+ self.annotation_type = AnnotationType.RANGE
67
+ elif self.amplitude is not None and self.time is None:
68
+ self.annotation_type = AnnotationType.HORIZONTAL
69
+ elif self.amplitude_range is not None and self.time_range is not None:
70
+ self.annotation_type = AnnotationType.REGION # type: ignore[unreachable]
71
+
72
+ @property
73
+ def start_time(self) -> float | None:
74
+ """Get start time for range annotations."""
75
+ if self.time_range:
76
+ return self.time_range[0]
77
+ return self.time
78
+
79
+ @property
80
+ def end_time(self) -> float | None:
81
+ """Get end time for range annotations."""
82
+ if self.time_range:
83
+ return self.time_range[1]
84
+ return self.time
85
+
86
+ def to_dict(self) -> dict[str, Any]:
87
+ """Convert to dictionary for serialization."""
88
+ return {
89
+ "text": self.text,
90
+ "time": self.time,
91
+ "time_range": self.time_range,
92
+ "amplitude": self.amplitude,
93
+ "amplitude_range": self.amplitude_range,
94
+ "annotation_type": self.annotation_type.value,
95
+ "color": self.color,
96
+ "style": self.style,
97
+ "visible": self.visible,
98
+ "created_at": self.created_at.isoformat(),
99
+ "metadata": self.metadata,
100
+ }
101
+
102
+ @classmethod
103
+ def from_dict(cls, data: dict[str, Any]) -> Annotation:
104
+ """Create from dictionary."""
105
+ data = data.copy()
106
+ data["annotation_type"] = AnnotationType(data.get("annotation_type", "point"))
107
+ if "created_at" in data and isinstance(data["created_at"], str):
108
+ data["created_at"] = datetime.fromisoformat(data["created_at"])
109
+ return cls(**data)
110
+
111
+
112
+ @dataclass
113
+ class AnnotationLayer:
114
+ """Collection of related annotations.
115
+
116
+ Attributes:
117
+ name: Layer name
118
+ annotations: List of annotations
119
+ visible: Whether layer is visible
120
+ locked: Whether layer is locked (read-only)
121
+ color: Default color for new annotations
122
+ description: Layer description
123
+ """
124
+
125
+ name: str
126
+ annotations: list[Annotation] = field(default_factory=list)
127
+ visible: bool = True
128
+ locked: bool = False
129
+ color: str = "#FF6B6B"
130
+ description: str = ""
131
+
132
+ def add(
133
+ self,
134
+ annotation: Annotation | None = None,
135
+ *,
136
+ text: str = "",
137
+ time: float | None = None,
138
+ time_range: tuple[float, float] | None = None,
139
+ **kwargs: Any,
140
+ ) -> Annotation:
141
+ """Add annotation to layer.
142
+
143
+ Args:
144
+ annotation: Pre-built Annotation object.
145
+ text: Annotation text (if not using pre-built).
146
+ time: Time point.
147
+ time_range: Time range.
148
+ **kwargs: Additional Annotation parameters.
149
+
150
+ Returns:
151
+ Added annotation.
152
+
153
+ Raises:
154
+ ValueError: If layer is locked.
155
+ """
156
+ if self.locked:
157
+ raise ValueError(f"Layer '{self.name}' is locked")
158
+
159
+ if annotation is None:
160
+ annotation = Annotation(
161
+ text=text,
162
+ time=time,
163
+ time_range=time_range,
164
+ color=kwargs.pop("color", self.color),
165
+ **kwargs,
166
+ )
167
+
168
+ self.annotations.append(annotation)
169
+ return annotation
170
+
171
+ def remove(self, annotation: Annotation) -> bool:
172
+ """Remove annotation from layer.
173
+
174
+ Args:
175
+ annotation: Annotation to remove.
176
+
177
+ Returns:
178
+ True if removed, False if not found.
179
+
180
+ Raises:
181
+ ValueError: If layer is locked.
182
+ """
183
+ if self.locked:
184
+ raise ValueError(f"Layer '{self.name}' is locked")
185
+
186
+ try:
187
+ self.annotations.remove(annotation)
188
+ return True
189
+ except ValueError:
190
+ return False
191
+
192
+ def find_at_time(
193
+ self,
194
+ time: float,
195
+ tolerance: float = 0.0,
196
+ ) -> list[Annotation]:
197
+ """Find annotations at or near a specific time.
198
+
199
+ Args:
200
+ time: Time to search.
201
+ tolerance: Time tolerance for matching.
202
+
203
+ Returns:
204
+ List of matching annotations.
205
+ """
206
+ matches = []
207
+ for ann in self.annotations:
208
+ if ann.time is not None:
209
+ if abs(ann.time - time) <= tolerance:
210
+ matches.append(ann)
211
+ elif ann.time_range is not None and (
212
+ ann.time_range[0] - tolerance <= time <= ann.time_range[1] + tolerance
213
+ ):
214
+ matches.append(ann)
215
+ return matches
216
+
217
+ def find_in_range(
218
+ self,
219
+ start_time: float,
220
+ end_time: float,
221
+ ) -> list[Annotation]:
222
+ """Find annotations within a time range.
223
+
224
+ Args:
225
+ start_time: Range start.
226
+ end_time: Range end.
227
+
228
+ Returns:
229
+ List of annotations within range.
230
+ """
231
+ matches = []
232
+ for ann in self.annotations:
233
+ ann_start = ann.start_time
234
+ ann_end = ann.end_time
235
+
236
+ if ann_start is not None and (
237
+ start_time <= ann_start <= end_time
238
+ or (ann_end is not None and ann_start <= end_time and ann_end >= start_time)
239
+ ):
240
+ matches.append(ann)
241
+
242
+ return matches
243
+
244
+ def clear(self) -> int:
245
+ """Remove all annotations.
246
+
247
+ Returns:
248
+ Number of annotations removed.
249
+
250
+ Raises:
251
+ ValueError: If layer is locked.
252
+ """
253
+ if self.locked:
254
+ raise ValueError(f"Layer '{self.name}' is locked")
255
+
256
+ count = len(self.annotations)
257
+ self.annotations.clear()
258
+ return count
259
+
260
+ def to_dict(self) -> dict[str, Any]:
261
+ """Convert to dictionary for serialization."""
262
+ return {
263
+ "name": self.name,
264
+ "annotations": [a.to_dict() for a in self.annotations],
265
+ "visible": self.visible,
266
+ "locked": self.locked,
267
+ "color": self.color,
268
+ "description": self.description,
269
+ }
270
+
271
+ @classmethod
272
+ def from_dict(cls, data: dict[str, Any]) -> AnnotationLayer:
273
+ """Create from dictionary."""
274
+ annotations = [Annotation.from_dict(a) for a in data.get("annotations", [])]
275
+ return cls(
276
+ name=data["name"],
277
+ annotations=annotations,
278
+ visible=data.get("visible", True),
279
+ locked=data.get("locked", False),
280
+ color=data.get("color", "#FF6B6B"),
281
+ description=data.get("description", ""),
282
+ )
283
+
284
+
285
+ __all__ = [
286
+ "Annotation",
287
+ "AnnotationLayer",
288
+ "AnnotationType",
289
+ ]