oscura 0.0.1__py3-none-any.whl → 0.1.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (465) hide show
  1. oscura/__init__.py +813 -8
  2. oscura/__main__.py +392 -0
  3. oscura/analyzers/__init__.py +37 -0
  4. oscura/analyzers/digital/__init__.py +177 -0
  5. oscura/analyzers/digital/bus.py +691 -0
  6. oscura/analyzers/digital/clock.py +805 -0
  7. oscura/analyzers/digital/correlation.py +720 -0
  8. oscura/analyzers/digital/edges.py +632 -0
  9. oscura/analyzers/digital/extraction.py +413 -0
  10. oscura/analyzers/digital/quality.py +878 -0
  11. oscura/analyzers/digital/signal_quality.py +877 -0
  12. oscura/analyzers/digital/thresholds.py +708 -0
  13. oscura/analyzers/digital/timing.py +1104 -0
  14. oscura/analyzers/eye/__init__.py +46 -0
  15. oscura/analyzers/eye/diagram.py +434 -0
  16. oscura/analyzers/eye/metrics.py +555 -0
  17. oscura/analyzers/jitter/__init__.py +83 -0
  18. oscura/analyzers/jitter/ber.py +333 -0
  19. oscura/analyzers/jitter/decomposition.py +759 -0
  20. oscura/analyzers/jitter/measurements.py +413 -0
  21. oscura/analyzers/jitter/spectrum.py +220 -0
  22. oscura/analyzers/measurements.py +40 -0
  23. oscura/analyzers/packet/__init__.py +171 -0
  24. oscura/analyzers/packet/daq.py +1077 -0
  25. oscura/analyzers/packet/metrics.py +437 -0
  26. oscura/analyzers/packet/parser.py +327 -0
  27. oscura/analyzers/packet/payload.py +2156 -0
  28. oscura/analyzers/packet/payload_analysis.py +1312 -0
  29. oscura/analyzers/packet/payload_extraction.py +236 -0
  30. oscura/analyzers/packet/payload_patterns.py +670 -0
  31. oscura/analyzers/packet/stream.py +359 -0
  32. oscura/analyzers/patterns/__init__.py +266 -0
  33. oscura/analyzers/patterns/clustering.py +1036 -0
  34. oscura/analyzers/patterns/discovery.py +539 -0
  35. oscura/analyzers/patterns/learning.py +797 -0
  36. oscura/analyzers/patterns/matching.py +1091 -0
  37. oscura/analyzers/patterns/periodic.py +650 -0
  38. oscura/analyzers/patterns/sequences.py +767 -0
  39. oscura/analyzers/power/__init__.py +116 -0
  40. oscura/analyzers/power/ac_power.py +391 -0
  41. oscura/analyzers/power/basic.py +383 -0
  42. oscura/analyzers/power/conduction.py +314 -0
  43. oscura/analyzers/power/efficiency.py +297 -0
  44. oscura/analyzers/power/ripple.py +356 -0
  45. oscura/analyzers/power/soa.py +372 -0
  46. oscura/analyzers/power/switching.py +479 -0
  47. oscura/analyzers/protocol/__init__.py +150 -0
  48. oscura/analyzers/protocols/__init__.py +150 -0
  49. oscura/analyzers/protocols/base.py +500 -0
  50. oscura/analyzers/protocols/can.py +620 -0
  51. oscura/analyzers/protocols/can_fd.py +448 -0
  52. oscura/analyzers/protocols/flexray.py +405 -0
  53. oscura/analyzers/protocols/hdlc.py +399 -0
  54. oscura/analyzers/protocols/i2c.py +368 -0
  55. oscura/analyzers/protocols/i2s.py +296 -0
  56. oscura/analyzers/protocols/jtag.py +393 -0
  57. oscura/analyzers/protocols/lin.py +445 -0
  58. oscura/analyzers/protocols/manchester.py +333 -0
  59. oscura/analyzers/protocols/onewire.py +501 -0
  60. oscura/analyzers/protocols/spi.py +334 -0
  61. oscura/analyzers/protocols/swd.py +325 -0
  62. oscura/analyzers/protocols/uart.py +393 -0
  63. oscura/analyzers/protocols/usb.py +495 -0
  64. oscura/analyzers/signal_integrity/__init__.py +63 -0
  65. oscura/analyzers/signal_integrity/embedding.py +294 -0
  66. oscura/analyzers/signal_integrity/equalization.py +370 -0
  67. oscura/analyzers/signal_integrity/sparams.py +484 -0
  68. oscura/analyzers/spectral/__init__.py +53 -0
  69. oscura/analyzers/spectral/chunked.py +273 -0
  70. oscura/analyzers/spectral/chunked_fft.py +571 -0
  71. oscura/analyzers/spectral/chunked_wavelet.py +391 -0
  72. oscura/analyzers/spectral/fft.py +92 -0
  73. oscura/analyzers/statistical/__init__.py +250 -0
  74. oscura/analyzers/statistical/checksum.py +923 -0
  75. oscura/analyzers/statistical/chunked_corr.py +228 -0
  76. oscura/analyzers/statistical/classification.py +778 -0
  77. oscura/analyzers/statistical/entropy.py +1113 -0
  78. oscura/analyzers/statistical/ngrams.py +614 -0
  79. oscura/analyzers/statistics/__init__.py +119 -0
  80. oscura/analyzers/statistics/advanced.py +885 -0
  81. oscura/analyzers/statistics/basic.py +263 -0
  82. oscura/analyzers/statistics/correlation.py +630 -0
  83. oscura/analyzers/statistics/distribution.py +298 -0
  84. oscura/analyzers/statistics/outliers.py +463 -0
  85. oscura/analyzers/statistics/streaming.py +93 -0
  86. oscura/analyzers/statistics/trend.py +520 -0
  87. oscura/analyzers/validation.py +598 -0
  88. oscura/analyzers/waveform/__init__.py +36 -0
  89. oscura/analyzers/waveform/measurements.py +943 -0
  90. oscura/analyzers/waveform/measurements_with_uncertainty.py +371 -0
  91. oscura/analyzers/waveform/spectral.py +1689 -0
  92. oscura/analyzers/waveform/wavelets.py +298 -0
  93. oscura/api/__init__.py +62 -0
  94. oscura/api/dsl.py +538 -0
  95. oscura/api/fluent.py +571 -0
  96. oscura/api/operators.py +498 -0
  97. oscura/api/optimization.py +392 -0
  98. oscura/api/profiling.py +396 -0
  99. oscura/automotive/__init__.py +73 -0
  100. oscura/automotive/can/__init__.py +52 -0
  101. oscura/automotive/can/analysis.py +356 -0
  102. oscura/automotive/can/checksum.py +250 -0
  103. oscura/automotive/can/correlation.py +212 -0
  104. oscura/automotive/can/discovery.py +355 -0
  105. oscura/automotive/can/message_wrapper.py +375 -0
  106. oscura/automotive/can/models.py +385 -0
  107. oscura/automotive/can/patterns.py +381 -0
  108. oscura/automotive/can/session.py +452 -0
  109. oscura/automotive/can/state_machine.py +300 -0
  110. oscura/automotive/can/stimulus_response.py +461 -0
  111. oscura/automotive/dbc/__init__.py +15 -0
  112. oscura/automotive/dbc/generator.py +156 -0
  113. oscura/automotive/dbc/parser.py +146 -0
  114. oscura/automotive/dtc/__init__.py +30 -0
  115. oscura/automotive/dtc/database.py +3036 -0
  116. oscura/automotive/j1939/__init__.py +14 -0
  117. oscura/automotive/j1939/decoder.py +745 -0
  118. oscura/automotive/loaders/__init__.py +35 -0
  119. oscura/automotive/loaders/asc.py +98 -0
  120. oscura/automotive/loaders/blf.py +77 -0
  121. oscura/automotive/loaders/csv_can.py +136 -0
  122. oscura/automotive/loaders/dispatcher.py +136 -0
  123. oscura/automotive/loaders/mdf.py +331 -0
  124. oscura/automotive/loaders/pcap.py +132 -0
  125. oscura/automotive/obd/__init__.py +14 -0
  126. oscura/automotive/obd/decoder.py +707 -0
  127. oscura/automotive/uds/__init__.py +48 -0
  128. oscura/automotive/uds/decoder.py +265 -0
  129. oscura/automotive/uds/models.py +64 -0
  130. oscura/automotive/visualization.py +369 -0
  131. oscura/batch/__init__.py +55 -0
  132. oscura/batch/advanced.py +627 -0
  133. oscura/batch/aggregate.py +300 -0
  134. oscura/batch/analyze.py +139 -0
  135. oscura/batch/logging.py +487 -0
  136. oscura/batch/metrics.py +556 -0
  137. oscura/builders/__init__.py +41 -0
  138. oscura/builders/signal_builder.py +1131 -0
  139. oscura/cli/__init__.py +14 -0
  140. oscura/cli/batch.py +339 -0
  141. oscura/cli/characterize.py +273 -0
  142. oscura/cli/compare.py +775 -0
  143. oscura/cli/decode.py +551 -0
  144. oscura/cli/main.py +247 -0
  145. oscura/cli/shell.py +350 -0
  146. oscura/comparison/__init__.py +66 -0
  147. oscura/comparison/compare.py +397 -0
  148. oscura/comparison/golden.py +487 -0
  149. oscura/comparison/limits.py +391 -0
  150. oscura/comparison/mask.py +434 -0
  151. oscura/comparison/trace_diff.py +30 -0
  152. oscura/comparison/visualization.py +481 -0
  153. oscura/compliance/__init__.py +70 -0
  154. oscura/compliance/advanced.py +756 -0
  155. oscura/compliance/masks.py +363 -0
  156. oscura/compliance/reporting.py +483 -0
  157. oscura/compliance/testing.py +298 -0
  158. oscura/component/__init__.py +38 -0
  159. oscura/component/impedance.py +365 -0
  160. oscura/component/reactive.py +598 -0
  161. oscura/component/transmission_line.py +312 -0
  162. oscura/config/__init__.py +191 -0
  163. oscura/config/defaults.py +254 -0
  164. oscura/config/loader.py +348 -0
  165. oscura/config/memory.py +271 -0
  166. oscura/config/migration.py +458 -0
  167. oscura/config/pipeline.py +1077 -0
  168. oscura/config/preferences.py +530 -0
  169. oscura/config/protocol.py +875 -0
  170. oscura/config/schema.py +713 -0
  171. oscura/config/settings.py +420 -0
  172. oscura/config/thresholds.py +599 -0
  173. oscura/convenience.py +457 -0
  174. oscura/core/__init__.py +299 -0
  175. oscura/core/audit.py +457 -0
  176. oscura/core/backend_selector.py +405 -0
  177. oscura/core/cache.py +590 -0
  178. oscura/core/cancellation.py +439 -0
  179. oscura/core/confidence.py +225 -0
  180. oscura/core/config.py +506 -0
  181. oscura/core/correlation.py +216 -0
  182. oscura/core/cross_domain.py +422 -0
  183. oscura/core/debug.py +301 -0
  184. oscura/core/edge_cases.py +541 -0
  185. oscura/core/exceptions.py +535 -0
  186. oscura/core/gpu_backend.py +523 -0
  187. oscura/core/lazy.py +832 -0
  188. oscura/core/log_query.py +540 -0
  189. oscura/core/logging.py +931 -0
  190. oscura/core/logging_advanced.py +952 -0
  191. oscura/core/memoize.py +171 -0
  192. oscura/core/memory_check.py +274 -0
  193. oscura/core/memory_guard.py +290 -0
  194. oscura/core/memory_limits.py +336 -0
  195. oscura/core/memory_monitor.py +453 -0
  196. oscura/core/memory_progress.py +465 -0
  197. oscura/core/memory_warnings.py +315 -0
  198. oscura/core/numba_backend.py +362 -0
  199. oscura/core/performance.py +352 -0
  200. oscura/core/progress.py +524 -0
  201. oscura/core/provenance.py +358 -0
  202. oscura/core/results.py +331 -0
  203. oscura/core/types.py +504 -0
  204. oscura/core/uncertainty.py +383 -0
  205. oscura/discovery/__init__.py +52 -0
  206. oscura/discovery/anomaly_detector.py +672 -0
  207. oscura/discovery/auto_decoder.py +415 -0
  208. oscura/discovery/comparison.py +497 -0
  209. oscura/discovery/quality_validator.py +528 -0
  210. oscura/discovery/signal_detector.py +769 -0
  211. oscura/dsl/__init__.py +73 -0
  212. oscura/dsl/commands.py +246 -0
  213. oscura/dsl/interpreter.py +455 -0
  214. oscura/dsl/parser.py +689 -0
  215. oscura/dsl/repl.py +172 -0
  216. oscura/exceptions.py +59 -0
  217. oscura/exploratory/__init__.py +111 -0
  218. oscura/exploratory/error_recovery.py +642 -0
  219. oscura/exploratory/fuzzy.py +513 -0
  220. oscura/exploratory/fuzzy_advanced.py +786 -0
  221. oscura/exploratory/legacy.py +831 -0
  222. oscura/exploratory/parse.py +358 -0
  223. oscura/exploratory/recovery.py +275 -0
  224. oscura/exploratory/sync.py +382 -0
  225. oscura/exploratory/unknown.py +707 -0
  226. oscura/export/__init__.py +25 -0
  227. oscura/export/wireshark/README.md +265 -0
  228. oscura/export/wireshark/__init__.py +47 -0
  229. oscura/export/wireshark/generator.py +312 -0
  230. oscura/export/wireshark/lua_builder.py +159 -0
  231. oscura/export/wireshark/templates/dissector.lua.j2 +92 -0
  232. oscura/export/wireshark/type_mapping.py +165 -0
  233. oscura/export/wireshark/validator.py +105 -0
  234. oscura/exporters/__init__.py +94 -0
  235. oscura/exporters/csv.py +303 -0
  236. oscura/exporters/exporters.py +44 -0
  237. oscura/exporters/hdf5.py +219 -0
  238. oscura/exporters/html_export.py +701 -0
  239. oscura/exporters/json_export.py +291 -0
  240. oscura/exporters/markdown_export.py +367 -0
  241. oscura/exporters/matlab_export.py +354 -0
  242. oscura/exporters/npz_export.py +219 -0
  243. oscura/exporters/spice_export.py +210 -0
  244. oscura/extensibility/__init__.py +131 -0
  245. oscura/extensibility/docs.py +752 -0
  246. oscura/extensibility/extensions.py +1125 -0
  247. oscura/extensibility/logging.py +259 -0
  248. oscura/extensibility/measurements.py +485 -0
  249. oscura/extensibility/plugins.py +414 -0
  250. oscura/extensibility/registry.py +346 -0
  251. oscura/extensibility/templates.py +913 -0
  252. oscura/extensibility/validation.py +651 -0
  253. oscura/filtering/__init__.py +89 -0
  254. oscura/filtering/base.py +563 -0
  255. oscura/filtering/convenience.py +564 -0
  256. oscura/filtering/design.py +725 -0
  257. oscura/filtering/filters.py +32 -0
  258. oscura/filtering/introspection.py +605 -0
  259. oscura/guidance/__init__.py +24 -0
  260. oscura/guidance/recommender.py +429 -0
  261. oscura/guidance/wizard.py +518 -0
  262. oscura/inference/__init__.py +251 -0
  263. oscura/inference/active_learning/README.md +153 -0
  264. oscura/inference/active_learning/__init__.py +38 -0
  265. oscura/inference/active_learning/lstar.py +257 -0
  266. oscura/inference/active_learning/observation_table.py +230 -0
  267. oscura/inference/active_learning/oracle.py +78 -0
  268. oscura/inference/active_learning/teachers/__init__.py +15 -0
  269. oscura/inference/active_learning/teachers/simulator.py +192 -0
  270. oscura/inference/adaptive_tuning.py +453 -0
  271. oscura/inference/alignment.py +653 -0
  272. oscura/inference/bayesian.py +943 -0
  273. oscura/inference/binary.py +1016 -0
  274. oscura/inference/crc_reverse.py +711 -0
  275. oscura/inference/logic.py +288 -0
  276. oscura/inference/message_format.py +1305 -0
  277. oscura/inference/protocol.py +417 -0
  278. oscura/inference/protocol_dsl.py +1084 -0
  279. oscura/inference/protocol_library.py +1230 -0
  280. oscura/inference/sequences.py +809 -0
  281. oscura/inference/signal_intelligence.py +1509 -0
  282. oscura/inference/spectral.py +215 -0
  283. oscura/inference/state_machine.py +634 -0
  284. oscura/inference/stream.py +918 -0
  285. oscura/integrations/__init__.py +59 -0
  286. oscura/integrations/llm.py +1827 -0
  287. oscura/jupyter/__init__.py +32 -0
  288. oscura/jupyter/display.py +268 -0
  289. oscura/jupyter/magic.py +334 -0
  290. oscura/loaders/__init__.py +526 -0
  291. oscura/loaders/binary.py +69 -0
  292. oscura/loaders/configurable.py +1255 -0
  293. oscura/loaders/csv.py +26 -0
  294. oscura/loaders/csv_loader.py +473 -0
  295. oscura/loaders/hdf5.py +9 -0
  296. oscura/loaders/hdf5_loader.py +510 -0
  297. oscura/loaders/lazy.py +370 -0
  298. oscura/loaders/mmap_loader.py +583 -0
  299. oscura/loaders/numpy_loader.py +436 -0
  300. oscura/loaders/pcap.py +432 -0
  301. oscura/loaders/preprocessing.py +368 -0
  302. oscura/loaders/rigol.py +287 -0
  303. oscura/loaders/sigrok.py +321 -0
  304. oscura/loaders/tdms.py +367 -0
  305. oscura/loaders/tektronix.py +711 -0
  306. oscura/loaders/validation.py +584 -0
  307. oscura/loaders/vcd.py +464 -0
  308. oscura/loaders/wav.py +233 -0
  309. oscura/math/__init__.py +45 -0
  310. oscura/math/arithmetic.py +824 -0
  311. oscura/math/interpolation.py +413 -0
  312. oscura/onboarding/__init__.py +39 -0
  313. oscura/onboarding/help.py +498 -0
  314. oscura/onboarding/tutorials.py +405 -0
  315. oscura/onboarding/wizard.py +466 -0
  316. oscura/optimization/__init__.py +19 -0
  317. oscura/optimization/parallel.py +440 -0
  318. oscura/optimization/search.py +532 -0
  319. oscura/pipeline/__init__.py +43 -0
  320. oscura/pipeline/base.py +338 -0
  321. oscura/pipeline/composition.py +242 -0
  322. oscura/pipeline/parallel.py +448 -0
  323. oscura/pipeline/pipeline.py +375 -0
  324. oscura/pipeline/reverse_engineering.py +1119 -0
  325. oscura/plugins/__init__.py +122 -0
  326. oscura/plugins/base.py +272 -0
  327. oscura/plugins/cli.py +497 -0
  328. oscura/plugins/discovery.py +411 -0
  329. oscura/plugins/isolation.py +418 -0
  330. oscura/plugins/lifecycle.py +959 -0
  331. oscura/plugins/manager.py +493 -0
  332. oscura/plugins/registry.py +421 -0
  333. oscura/plugins/versioning.py +372 -0
  334. oscura/py.typed +0 -0
  335. oscura/quality/__init__.py +65 -0
  336. oscura/quality/ensemble.py +740 -0
  337. oscura/quality/explainer.py +338 -0
  338. oscura/quality/scoring.py +616 -0
  339. oscura/quality/warnings.py +456 -0
  340. oscura/reporting/__init__.py +248 -0
  341. oscura/reporting/advanced.py +1234 -0
  342. oscura/reporting/analyze.py +448 -0
  343. oscura/reporting/argument_preparer.py +596 -0
  344. oscura/reporting/auto_report.py +507 -0
  345. oscura/reporting/batch.py +615 -0
  346. oscura/reporting/chart_selection.py +223 -0
  347. oscura/reporting/comparison.py +330 -0
  348. oscura/reporting/config.py +615 -0
  349. oscura/reporting/content/__init__.py +39 -0
  350. oscura/reporting/content/executive.py +127 -0
  351. oscura/reporting/content/filtering.py +191 -0
  352. oscura/reporting/content/minimal.py +257 -0
  353. oscura/reporting/content/verbosity.py +162 -0
  354. oscura/reporting/core.py +508 -0
  355. oscura/reporting/core_formats/__init__.py +17 -0
  356. oscura/reporting/core_formats/multi_format.py +210 -0
  357. oscura/reporting/engine.py +836 -0
  358. oscura/reporting/export.py +366 -0
  359. oscura/reporting/formatting/__init__.py +129 -0
  360. oscura/reporting/formatting/emphasis.py +81 -0
  361. oscura/reporting/formatting/numbers.py +403 -0
  362. oscura/reporting/formatting/standards.py +55 -0
  363. oscura/reporting/formatting.py +466 -0
  364. oscura/reporting/html.py +578 -0
  365. oscura/reporting/index.py +590 -0
  366. oscura/reporting/multichannel.py +296 -0
  367. oscura/reporting/output.py +379 -0
  368. oscura/reporting/pdf.py +373 -0
  369. oscura/reporting/plots.py +731 -0
  370. oscura/reporting/pptx_export.py +360 -0
  371. oscura/reporting/renderers/__init__.py +11 -0
  372. oscura/reporting/renderers/pdf.py +94 -0
  373. oscura/reporting/sections.py +471 -0
  374. oscura/reporting/standards.py +680 -0
  375. oscura/reporting/summary_generator.py +368 -0
  376. oscura/reporting/tables.py +397 -0
  377. oscura/reporting/template_system.py +724 -0
  378. oscura/reporting/templates/__init__.py +15 -0
  379. oscura/reporting/templates/definition.py +205 -0
  380. oscura/reporting/templates/index.html +649 -0
  381. oscura/reporting/templates/index.md +173 -0
  382. oscura/schemas/__init__.py +158 -0
  383. oscura/schemas/bus_configuration.json +322 -0
  384. oscura/schemas/device_mapping.json +182 -0
  385. oscura/schemas/packet_format.json +418 -0
  386. oscura/schemas/protocol_definition.json +363 -0
  387. oscura/search/__init__.py +16 -0
  388. oscura/search/anomaly.py +292 -0
  389. oscura/search/context.py +149 -0
  390. oscura/search/pattern.py +160 -0
  391. oscura/session/__init__.py +34 -0
  392. oscura/session/annotations.py +289 -0
  393. oscura/session/history.py +313 -0
  394. oscura/session/session.py +445 -0
  395. oscura/streaming/__init__.py +43 -0
  396. oscura/streaming/chunked.py +611 -0
  397. oscura/streaming/progressive.py +393 -0
  398. oscura/streaming/realtime.py +622 -0
  399. oscura/testing/__init__.py +54 -0
  400. oscura/testing/synthetic.py +808 -0
  401. oscura/triggering/__init__.py +68 -0
  402. oscura/triggering/base.py +229 -0
  403. oscura/triggering/edge.py +353 -0
  404. oscura/triggering/pattern.py +344 -0
  405. oscura/triggering/pulse.py +581 -0
  406. oscura/triggering/window.py +453 -0
  407. oscura/ui/__init__.py +48 -0
  408. oscura/ui/formatters.py +526 -0
  409. oscura/ui/progressive_display.py +340 -0
  410. oscura/utils/__init__.py +99 -0
  411. oscura/utils/autodetect.py +338 -0
  412. oscura/utils/buffer.py +389 -0
  413. oscura/utils/lazy.py +407 -0
  414. oscura/utils/lazy_imports.py +147 -0
  415. oscura/utils/memory.py +836 -0
  416. oscura/utils/memory_advanced.py +1326 -0
  417. oscura/utils/memory_extensions.py +465 -0
  418. oscura/utils/progressive.py +352 -0
  419. oscura/utils/windowing.py +362 -0
  420. oscura/visualization/__init__.py +321 -0
  421. oscura/visualization/accessibility.py +526 -0
  422. oscura/visualization/annotations.py +374 -0
  423. oscura/visualization/axis_scaling.py +305 -0
  424. oscura/visualization/colors.py +453 -0
  425. oscura/visualization/digital.py +337 -0
  426. oscura/visualization/eye.py +420 -0
  427. oscura/visualization/histogram.py +281 -0
  428. oscura/visualization/interactive.py +858 -0
  429. oscura/visualization/jitter.py +702 -0
  430. oscura/visualization/keyboard.py +394 -0
  431. oscura/visualization/layout.py +365 -0
  432. oscura/visualization/optimization.py +1028 -0
  433. oscura/visualization/palettes.py +446 -0
  434. oscura/visualization/plot.py +92 -0
  435. oscura/visualization/power.py +290 -0
  436. oscura/visualization/power_extended.py +626 -0
  437. oscura/visualization/presets.py +467 -0
  438. oscura/visualization/protocols.py +932 -0
  439. oscura/visualization/render.py +207 -0
  440. oscura/visualization/rendering.py +444 -0
  441. oscura/visualization/reverse_engineering.py +791 -0
  442. oscura/visualization/signal_integrity.py +808 -0
  443. oscura/visualization/specialized.py +553 -0
  444. oscura/visualization/spectral.py +811 -0
  445. oscura/visualization/styles.py +381 -0
  446. oscura/visualization/thumbnails.py +311 -0
  447. oscura/visualization/time_axis.py +351 -0
  448. oscura/visualization/waveform.py +367 -0
  449. oscura/workflow/__init__.py +13 -0
  450. oscura/workflow/dag.py +377 -0
  451. oscura/workflows/__init__.py +58 -0
  452. oscura/workflows/compliance.py +280 -0
  453. oscura/workflows/digital.py +272 -0
  454. oscura/workflows/multi_trace.py +502 -0
  455. oscura/workflows/power.py +178 -0
  456. oscura/workflows/protocol.py +492 -0
  457. oscura/workflows/reverse_engineering.py +639 -0
  458. oscura/workflows/signal_integrity.py +227 -0
  459. oscura-0.1.1.dist-info/METADATA +300 -0
  460. oscura-0.1.1.dist-info/RECORD +463 -0
  461. oscura-0.1.1.dist-info/entry_points.txt +2 -0
  462. {oscura-0.0.1.dist-info → oscura-0.1.1.dist-info}/licenses/LICENSE +1 -1
  463. oscura-0.0.1.dist-info/METADATA +0 -63
  464. oscura-0.0.1.dist-info/RECORD +0 -5
  465. {oscura-0.0.1.dist-info → oscura-0.1.1.dist-info}/WHEEL +0 -0
@@ -0,0 +1,520 @@
1
+ """Trend detection and analysis for signal data.
2
+
3
+ This module provides linear trend detection, drift analysis, and
4
+ detrending functions for identifying systematic changes in signals.
5
+
6
+
7
+ Example:
8
+ >>> from oscura.analyzers.statistics.trend import (
9
+ ... detect_trend, detrend, moving_average
10
+ ... )
11
+ >>> result = detect_trend(trace)
12
+ >>> print(f"Slope: {result['slope']:.2e} V/s")
13
+ >>> detrended = detrend(trace)
14
+
15
+ References:
16
+ Montgomery, D. C. (2012). Introduction to Statistical Quality Control
17
+ NIST Engineering Statistics Handbook
18
+ """
19
+
20
+ from __future__ import annotations
21
+
22
+ from dataclasses import dataclass
23
+ from typing import TYPE_CHECKING, Any, Literal
24
+
25
+ import numpy as np
26
+ from scipy import stats
27
+
28
+ from oscura.core.types import WaveformTrace
29
+
30
+ if TYPE_CHECKING:
31
+ from numpy.typing import NDArray
32
+
33
+
34
+ @dataclass
35
+ class TrendResult:
36
+ """Result of trend analysis.
37
+
38
+ Attributes:
39
+ slope: Trend slope (units per second).
40
+ intercept: Trend intercept (at t=0).
41
+ r_squared: Coefficient of determination.
42
+ p_value: Statistical significance (p < 0.05 is significant).
43
+ std_error: Standard error of slope estimate.
44
+ is_significant: Whether trend is statistically significant.
45
+ trend_line: Fitted trend values at each sample.
46
+ """
47
+
48
+ slope: float
49
+ intercept: float
50
+ r_squared: float
51
+ p_value: float
52
+ std_error: float
53
+ is_significant: bool
54
+ trend_line: NDArray[np.float64]
55
+
56
+
57
+ def detect_trend(
58
+ trace: WaveformTrace | NDArray[np.floating[Any]],
59
+ *,
60
+ significance_level: float = 0.05,
61
+ sample_rate: float | None = None,
62
+ ) -> TrendResult:
63
+ """Detect linear trend in signal data.
64
+
65
+ Fits a linear regression and tests for statistical significance.
66
+ Reports slope, R-squared, and whether drift is significant.
67
+
68
+ Args:
69
+ trace: Input trace or numpy array.
70
+ significance_level: P-value threshold for significance (default 0.05).
71
+ sample_rate: Sample rate in Hz (required for array input).
72
+
73
+ Returns:
74
+ TrendResult with trend analysis.
75
+
76
+ Raises:
77
+ ValueError: If trace is array and sample_rate is not provided.
78
+
79
+ Example:
80
+ >>> result = detect_trend(trace)
81
+ >>> if result.is_significant:
82
+ ... print(f"Significant drift: {result.slope:.2e} V/s")
83
+ ... print(f"R-squared: {result.r_squared:.4f}")
84
+
85
+ References:
86
+ NIST Engineering Statistics Handbook Section 6.6
87
+ """
88
+ if isinstance(trace, WaveformTrace):
89
+ data = trace.data
90
+ fs = trace.metadata.sample_rate
91
+ else:
92
+ data = trace
93
+ if sample_rate is None:
94
+ raise ValueError("sample_rate required when trace is array")
95
+ fs = sample_rate
96
+
97
+ n = len(data)
98
+
99
+ if n < 3:
100
+ return TrendResult(
101
+ slope=np.nan,
102
+ intercept=np.nan,
103
+ r_squared=np.nan,
104
+ p_value=np.nan,
105
+ std_error=np.nan,
106
+ is_significant=False,
107
+ trend_line=np.full(n, np.nan, dtype=np.float64),
108
+ )
109
+
110
+ # Time axis in seconds
111
+ t = np.arange(n) / fs
112
+
113
+ # Linear regression
114
+ result = stats.linregress(t, data)
115
+
116
+ slope = float(result.slope)
117
+ intercept = float(result.intercept)
118
+ r_squared = float(result.rvalue**2)
119
+ p_value = float(result.pvalue)
120
+ std_error = float(result.stderr)
121
+ is_significant = p_value < significance_level
122
+
123
+ # Compute trend line
124
+ trend_line = intercept + slope * t
125
+
126
+ return TrendResult(
127
+ slope=slope,
128
+ intercept=intercept,
129
+ r_squared=r_squared,
130
+ p_value=p_value,
131
+ std_error=std_error,
132
+ is_significant=is_significant,
133
+ trend_line=trend_line.astype(np.float64),
134
+ )
135
+
136
+
137
+ def detrend(
138
+ trace: WaveformTrace | NDArray[np.floating[Any]],
139
+ *,
140
+ method: Literal["linear", "constant", "polynomial"] = "linear",
141
+ order: int = 1,
142
+ return_trend: bool = False,
143
+ sample_rate: float | None = None,
144
+ ) -> NDArray[np.float64] | tuple[NDArray[np.float64], NDArray[np.float64]]:
145
+ """Remove trend from signal data.
146
+
147
+ Subtracts fitted trend to isolate fluctuations around baseline.
148
+
149
+ Args:
150
+ trace: Input trace or numpy array.
151
+ method: Detrending method:
152
+ - "constant": Remove mean (DC offset)
153
+ - "linear": Remove linear trend (default)
154
+ - "polynomial": Remove polynomial trend
155
+ order: Polynomial order (for method="polynomial").
156
+ return_trend: If True, also return the removed trend.
157
+ sample_rate: Sample rate in Hz (required for array input, only for linear).
158
+
159
+ Returns:
160
+ Detrended data array.
161
+ If return_trend=True, returns (detrended, trend).
162
+
163
+ Raises:
164
+ ValueError: If method is not recognized.
165
+
166
+ Example:
167
+ >>> detrended = detrend(trace, method="linear")
168
+ >>> # Or get the trend too
169
+ >>> detrended, trend = detrend(trace, return_trend=True)
170
+ """
171
+ if isinstance(trace, WaveformTrace):
172
+ data = trace.data.astype(np.float64)
173
+ fs = trace.metadata.sample_rate
174
+ else:
175
+ data = np.array(trace, dtype=np.float64)
176
+ fs = sample_rate if sample_rate else 1.0
177
+
178
+ n = len(data)
179
+
180
+ if method == "constant":
181
+ trend = np.full(n, np.mean(data), dtype=np.float64)
182
+
183
+ elif method == "linear":
184
+ result = detect_trend(trace, sample_rate=fs)
185
+ trend = result.trend_line
186
+
187
+ elif method == "polynomial":
188
+ t = np.arange(n)
189
+ coeffs = np.polyfit(t, data, order)
190
+ trend = np.polyval(coeffs, t)
191
+
192
+ else:
193
+ raise ValueError(f"Unknown method: {method}")
194
+
195
+ detrended = data - trend
196
+
197
+ if return_trend:
198
+ return detrended, trend.astype(np.float64)
199
+ return detrended
200
+
201
+
202
+ def moving_average(
203
+ trace: WaveformTrace | NDArray[np.floating[Any]],
204
+ *,
205
+ window_size: int,
206
+ method: Literal["simple", "exponential", "weighted"] = "simple",
207
+ alpha: float = 0.1,
208
+ ) -> NDArray[np.float64]:
209
+ """Compute moving average of signal.
210
+
211
+ Smooths signal by averaging over sliding window.
212
+
213
+ Args:
214
+ trace: Input trace or numpy array.
215
+ window_size: Size of averaging window in samples.
216
+ method: Averaging method:
217
+ - "simple": Simple moving average (default)
218
+ - "exponential": Exponential moving average
219
+ - "weighted": Linearly weighted moving average
220
+ alpha: Smoothing factor for exponential method (0-1).
221
+
222
+ Returns:
223
+ Smoothed signal array (same length as input).
224
+
225
+ Raises:
226
+ ValueError: If method is not recognized.
227
+
228
+ Example:
229
+ >>> smoothed = moving_average(trace, window_size=10)
230
+ >>> # Exponential smoothing
231
+ >>> ema = moving_average(trace, window_size=10, method="exponential", alpha=0.2)
232
+ """
233
+ if isinstance(trace, WaveformTrace):
234
+ data = trace.data.astype(np.float64)
235
+ else:
236
+ data = np.array(trace, dtype=np.float64)
237
+
238
+ n = len(data)
239
+
240
+ window_size = min(window_size, n)
241
+
242
+ if window_size < 1:
243
+ return data.copy()
244
+
245
+ if method == "simple":
246
+ # Simple moving average using convolution
247
+ kernel = np.ones(window_size) / window_size
248
+ # Pad for same output length
249
+ padded = np.pad(data, (window_size - 1, 0), mode="edge")
250
+ result = np.convolve(padded, kernel, mode="valid")
251
+
252
+ elif method == "exponential":
253
+ # Exponential moving average
254
+ result = np.zeros(n, dtype=np.float64)
255
+ result[0] = data[0]
256
+ for i in range(1, n):
257
+ result[i] = alpha * data[i] + (1 - alpha) * result[i - 1]
258
+
259
+ elif method == "weighted":
260
+ # Linearly weighted moving average
261
+ weights = np.arange(1, window_size + 1, dtype=np.float64)
262
+ weights = weights / np.sum(weights)
263
+
264
+ padded = np.pad(data, (window_size - 1, 0), mode="edge")
265
+ result = np.convolve(padded, weights, mode="valid")
266
+
267
+ else:
268
+ raise ValueError(f"Unknown method: {method}")
269
+
270
+ return result.astype(np.float64)
271
+
272
+
273
+ def detect_drift_segments(
274
+ trace: WaveformTrace | NDArray[np.floating[Any]],
275
+ *,
276
+ segment_size: int,
277
+ threshold_slope: float | None = None,
278
+ sample_rate: float | None = None,
279
+ ) -> list[dict]: # type: ignore[type-arg]
280
+ """Detect segments with significant drift.
281
+
282
+ Divides signal into segments and identifies those with
283
+ statistically significant linear trends.
284
+
285
+ Args:
286
+ trace: Input trace or numpy array.
287
+ segment_size: Size of each segment in samples.
288
+ threshold_slope: Minimum slope magnitude to flag (units/second).
289
+ If None, uses statistical significance.
290
+ sample_rate: Sample rate in Hz (required for array input).
291
+
292
+ Returns:
293
+ List of dictionaries describing drift segments:
294
+ - start_sample: Start index of segment
295
+ - end_sample: End index of segment
296
+ - start_time: Start time in seconds
297
+ - end_time: End time in seconds
298
+ - slope: Trend slope
299
+ - r_squared: Coefficient of determination
300
+
301
+ Raises:
302
+ ValueError: If trace is array and sample_rate is not provided.
303
+
304
+ Example:
305
+ >>> segments = detect_drift_segments(trace, segment_size=1000)
306
+ >>> for seg in segments:
307
+ ... print(f"Drift at {seg['start_time']:.3f}s: {seg['slope']:.2e} V/s")
308
+ """
309
+ if isinstance(trace, WaveformTrace):
310
+ data = trace.data
311
+ fs = trace.metadata.sample_rate
312
+ else:
313
+ data = trace
314
+ if sample_rate is None:
315
+ raise ValueError("sample_rate required when trace is array")
316
+ fs = sample_rate
317
+
318
+ n = len(data)
319
+ drift_segments = []
320
+
321
+ for start in range(0, n, segment_size):
322
+ end = min(start + segment_size, n)
323
+
324
+ if end - start < 10: # Need minimum points for regression
325
+ continue
326
+
327
+ segment_data = data[start:end]
328
+ segment_trace = segment_data # Array
329
+
330
+ result = detect_trend(segment_trace, sample_rate=fs)
331
+
332
+ # Check if drift is significant
333
+ is_drift = result.is_significant
334
+ if threshold_slope is not None:
335
+ is_drift = is_drift and abs(result.slope) >= threshold_slope
336
+
337
+ if is_drift:
338
+ drift_segments.append(
339
+ {
340
+ "start_sample": start,
341
+ "end_sample": end,
342
+ "start_time": start / fs,
343
+ "end_time": end / fs,
344
+ "slope": result.slope,
345
+ "r_squared": result.r_squared,
346
+ "p_value": result.p_value,
347
+ }
348
+ )
349
+
350
+ return drift_segments
351
+
352
+
353
+ def change_point_detection(
354
+ trace: WaveformTrace | NDArray[np.floating[Any]],
355
+ *,
356
+ min_segment_size: int = 10,
357
+ penalty: float | None = None,
358
+ ) -> list[int]:
359
+ """Detect change points in signal level or trend.
360
+
361
+ Identifies locations where the signal characteristics change
362
+ significantly, using a simple CUSUM-based approach.
363
+
364
+ Args:
365
+ trace: Input trace or numpy array.
366
+ min_segment_size: Minimum samples between change points.
367
+ penalty: Penalty for adding change points (controls sensitivity).
368
+ If None, auto-selected based on signal variance.
369
+
370
+ Returns:
371
+ List of sample indices where changes occur.
372
+
373
+ Example:
374
+ >>> change_points = change_point_detection(trace)
375
+ >>> for cp in change_points:
376
+ ... print(f"Change at sample {cp}")
377
+ """
378
+ data = trace.data if isinstance(trace, WaveformTrace) else np.array(trace, dtype=np.float64)
379
+
380
+ n = len(data)
381
+
382
+ if n < 2 * min_segment_size:
383
+ return []
384
+
385
+ # Auto-select penalty if not provided
386
+ if penalty is None:
387
+ penalty = np.var(data) * 2
388
+
389
+ # Simple binary segmentation using mean-shift cost
390
+ change_points = []
391
+ segments = [(0, n)]
392
+
393
+ while segments:
394
+ start, end = segments.pop(0)
395
+ segment = data[start:end]
396
+ seg_len = len(segment)
397
+
398
+ if seg_len < 2 * min_segment_size:
399
+ continue
400
+
401
+ # Find best split point
402
+ best_cost_reduction = -np.inf
403
+ best_split = None
404
+
405
+ for split in range(min_segment_size, seg_len - min_segment_size):
406
+ left = segment[:split]
407
+ right = segment[split:]
408
+
409
+ # Cost = sum of squared deviations from segment mean
410
+ cost_whole = np.sum((segment - np.mean(segment)) ** 2)
411
+ cost_left = np.sum((left - np.mean(left)) ** 2)
412
+ cost_right = np.sum((right - np.mean(right)) ** 2)
413
+
414
+ cost_reduction = cost_whole - (cost_left + cost_right) - penalty
415
+
416
+ if cost_reduction > best_cost_reduction:
417
+ best_cost_reduction = cost_reduction
418
+ best_split = split
419
+
420
+ # If significant cost reduction, add change point
421
+ if best_split is not None and best_cost_reduction > 0:
422
+ cp = start + best_split
423
+ change_points.append(cp)
424
+
425
+ # Add new segments to process
426
+ segments.append((start, cp))
427
+ segments.append((cp, end))
428
+
429
+ change_points.sort()
430
+ return change_points
431
+
432
+
433
+ def piecewise_linear_fit(
434
+ trace: WaveformTrace | NDArray[np.floating[Any]],
435
+ *,
436
+ n_segments: int = 3,
437
+ sample_rate: float | None = None,
438
+ ) -> dict: # type: ignore[type-arg]
439
+ """Fit piecewise linear model to signal.
440
+
441
+ Divides signal into segments and fits linear trends to each.
442
+
443
+ Args:
444
+ trace: Input trace or numpy array.
445
+ n_segments: Number of segments to fit.
446
+ sample_rate: Sample rate in Hz (required for array input).
447
+
448
+ Returns:
449
+ Dictionary with fit results:
450
+ - breakpoints: Sample indices of segment boundaries
451
+ - segments: List of (slope, intercept) for each segment
452
+ - fitted: Full fitted signal
453
+ - residuals: Fitting residuals
454
+
455
+ Raises:
456
+ ValueError: If trace is array and sample_rate is not provided.
457
+
458
+ Example:
459
+ >>> result = piecewise_linear_fit(trace, n_segments=4)
460
+ >>> print(f"Breakpoints: {result['breakpoints']}")
461
+ """
462
+ if isinstance(trace, WaveformTrace):
463
+ data = trace.data
464
+ fs = trace.metadata.sample_rate
465
+ else:
466
+ data = np.array(trace, dtype=np.float64)
467
+ if sample_rate is None:
468
+ raise ValueError("sample_rate required when trace is array")
469
+ fs = sample_rate
470
+
471
+ n = len(data)
472
+
473
+ # Calculate segment boundaries
474
+ segment_size = n // n_segments
475
+ breakpoints = [i * segment_size for i in range(1, n_segments)]
476
+ breakpoints = [0, *breakpoints, n]
477
+
478
+ # Fit each segment
479
+ segments = []
480
+ fitted = np.zeros(n, dtype=np.float64)
481
+
482
+ for i in range(len(breakpoints) - 1):
483
+ start = breakpoints[i]
484
+ end = breakpoints[i + 1]
485
+
486
+ segment_data = data[start:end]
487
+ t = np.arange(len(segment_data)) / fs
488
+
489
+ if len(t) >= 2:
490
+ slope, intercept = np.polyfit(t, segment_data, 1)
491
+ fitted[start:end] = intercept + slope * t
492
+ segments.append(
493
+ {
494
+ "slope": float(slope),
495
+ "intercept": float(intercept),
496
+ "start": start,
497
+ "end": end,
498
+ }
499
+ )
500
+
501
+ residuals = data - fitted
502
+
503
+ return {
504
+ "breakpoints": breakpoints,
505
+ "segments": segments,
506
+ "fitted": fitted,
507
+ "residuals": residuals,
508
+ "rmse": float(np.sqrt(np.mean(residuals**2))),
509
+ }
510
+
511
+
512
+ __all__ = [
513
+ "TrendResult",
514
+ "change_point_detection",
515
+ "detect_drift_segments",
516
+ "detect_trend",
517
+ "detrend",
518
+ "moving_average",
519
+ "piecewise_linear_fit",
520
+ ]