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,358 @@
1
+ """Measurement provenance tracking for reproducibility.
2
+
3
+ This module provides provenance tracking to record the complete history
4
+ of how measurements were computed, including algorithms, parameters,
5
+ timestamps, and library versions.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import hashlib
11
+ from dataclasses import dataclass, field
12
+ from datetime import UTC, datetime
13
+ from typing import TYPE_CHECKING, Any
14
+
15
+ import numpy as np
16
+
17
+ if TYPE_CHECKING:
18
+ from numpy.typing import NDArray
19
+
20
+ # Oscura version (in production would import from __version__)
21
+ OSCURA_VERSION = "0.1.0"
22
+
23
+
24
+ @dataclass
25
+ class Provenance:
26
+ """Provenance information for a computation.
27
+
28
+ Tracks the complete chain of operations, parameters, and context
29
+ for reproducibility and debugging.
30
+
31
+ Attributes:
32
+ algorithm: Name of algorithm or method used.
33
+ parameters: Dictionary of parameters passed to the algorithm.
34
+ timestamp: ISO 8601 timestamp of computation.
35
+ library_version: Version of Oscura used.
36
+ input_hash: Optional hash of input data for change detection.
37
+ metadata: Additional context information.
38
+
39
+ Example:
40
+ >>> prov = Provenance(
41
+ ... algorithm='rise_time',
42
+ ... parameters={'ref_levels': (10, 90)},
43
+ ... timestamp='2025-12-21T10:30:00Z',
44
+ ... library_version='0.1.0'
45
+ ... )
46
+
47
+ References:
48
+ API-011: Measurement Provenance Tracking
49
+ """
50
+
51
+ algorithm: str
52
+ parameters: dict[str, Any] = field(default_factory=dict)
53
+ timestamp: str = field(default_factory=lambda: datetime.now(UTC).isoformat())
54
+ library_version: str = OSCURA_VERSION
55
+ input_hash: str | None = None
56
+ metadata: dict[str, Any] = field(default_factory=dict)
57
+
58
+ def to_dict(self) -> dict[str, Any]:
59
+ """Convert provenance to dictionary for serialization.
60
+
61
+ Returns:
62
+ Dictionary representation of provenance.
63
+
64
+ Example:
65
+ >>> prov_dict = prov.to_dict()
66
+ >>> import json
67
+ >>> json.dumps(prov_dict)
68
+ """
69
+ return {
70
+ "algorithm": self.algorithm,
71
+ "parameters": self.parameters,
72
+ "timestamp": self.timestamp,
73
+ "library_version": self.library_version,
74
+ "input_hash": self.input_hash,
75
+ "metadata": self.metadata,
76
+ }
77
+
78
+ @classmethod
79
+ def from_dict(cls, data: dict[str, Any]) -> Provenance:
80
+ """Create Provenance from dictionary.
81
+
82
+ Args:
83
+ data: Dictionary containing provenance fields.
84
+
85
+ Returns:
86
+ Provenance object.
87
+
88
+ Example:
89
+ >>> prov = Provenance.from_dict(prov_dict)
90
+ """
91
+ return cls(
92
+ algorithm=data["algorithm"],
93
+ parameters=data.get("parameters", {}),
94
+ timestamp=data.get("timestamp", ""),
95
+ library_version=data.get("library_version", OSCURA_VERSION),
96
+ input_hash=data.get("input_hash"),
97
+ metadata=data.get("metadata", {}),
98
+ )
99
+
100
+ def __str__(self) -> str:
101
+ """Human-readable provenance summary."""
102
+ lines = [
103
+ f"Algorithm: {self.algorithm}",
104
+ f"Timestamp: {self.timestamp}",
105
+ f"Version: {self.library_version}",
106
+ ]
107
+ if self.parameters:
108
+ params_str = ", ".join(f"{k}={v}" for k, v in self.parameters.items())
109
+ lines.append(f"Parameters: {params_str}")
110
+ if self.input_hash:
111
+ lines.append(f"Input Hash: {self.input_hash[:16]}...")
112
+ return "\n".join(lines)
113
+
114
+
115
+ @dataclass
116
+ class MeasurementResultWithProvenance:
117
+ """Measurement result with full provenance tracking.
118
+
119
+ Extends the basic measurement result with comprehensive provenance
120
+ information for reproducibility and debugging.
121
+
122
+ Attributes:
123
+ value: Measured value.
124
+ units: Units of measurement (e.g., 'V', 'Hz', 's').
125
+ provenance: Provenance information.
126
+ confidence: Optional confidence interval (low, high).
127
+
128
+ Example:
129
+ >>> result = MeasurementResultWithProvenance(
130
+ ... value=3.3,
131
+ ... units='V',
132
+ ... provenance=Provenance(
133
+ ... algorithm='peak_to_peak',
134
+ ... parameters={'window': (0, 1e-3)}
135
+ ... )
136
+ ... )
137
+ >>> print(result)
138
+ 3.3 V (peak_to_peak)
139
+
140
+ References:
141
+ API-011: Measurement Provenance Tracking
142
+ """
143
+
144
+ value: float
145
+ units: str | None = None
146
+ provenance: Provenance | None = None
147
+ confidence: tuple[float, float] | None = None
148
+
149
+ def is_equivalent(
150
+ self,
151
+ other: MeasurementResultWithProvenance,
152
+ *,
153
+ rtol: float = 1e-9,
154
+ atol: float = 0.0,
155
+ check_parameters: bool = True,
156
+ ) -> bool:
157
+ """Check if two results are equivalent.
158
+
159
+ Compares values within tolerance and optionally checks if the
160
+ same algorithm and parameters were used.
161
+
162
+ Args:
163
+ other: Other measurement result to compare.
164
+ rtol: Relative tolerance for value comparison.
165
+ atol: Absolute tolerance for value comparison.
166
+ check_parameters: If True, also verify matching algorithm and parameters.
167
+
168
+ Returns:
169
+ True if results are equivalent.
170
+
171
+ Example:
172
+ >>> result1.is_equivalent(result2, rtol=1e-6)
173
+ True
174
+ """
175
+ # Check value equivalence
176
+ if not np.isclose(self.value, other.value, rtol=rtol, atol=atol):
177
+ return False
178
+
179
+ # Check units match
180
+ if self.units != other.units:
181
+ return False
182
+
183
+ # Optionally check provenance
184
+ if check_parameters and self.provenance and other.provenance:
185
+ if self.provenance.algorithm != other.provenance.algorithm:
186
+ return False
187
+ # Check if critical parameters match
188
+ if self.provenance.parameters != other.provenance.parameters:
189
+ return False
190
+
191
+ return True
192
+
193
+ def to_dict(self) -> dict[str, Any]:
194
+ """Convert to dictionary for serialization.
195
+
196
+ Returns:
197
+ Dictionary representation including provenance.
198
+
199
+ Example:
200
+ >>> result_dict = result.to_dict()
201
+ >>> import json
202
+ >>> json_str = json.dumps(result_dict)
203
+ """
204
+ result: dict[str, Any] = {
205
+ "value": self.value,
206
+ "units": self.units,
207
+ }
208
+ if self.provenance:
209
+ result["provenance"] = self.provenance.to_dict()
210
+ if self.confidence:
211
+ result["confidence"] = self.confidence
212
+ return result
213
+
214
+ @classmethod
215
+ def from_dict(cls, data: dict[str, Any]) -> MeasurementResultWithProvenance:
216
+ """Create result from dictionary.
217
+
218
+ Args:
219
+ data: Dictionary containing result fields.
220
+
221
+ Returns:
222
+ MeasurementResultWithProvenance object.
223
+ """
224
+ provenance = None
225
+ if "provenance" in data:
226
+ provenance = Provenance.from_dict(data["provenance"])
227
+
228
+ confidence = None
229
+ if "confidence" in data:
230
+ confidence = tuple(data["confidence"])
231
+
232
+ return cls(
233
+ value=data["value"],
234
+ units=data.get("units"),
235
+ provenance=provenance,
236
+ confidence=confidence,
237
+ )
238
+
239
+ def __str__(self) -> str:
240
+ """Human-readable string representation."""
241
+ parts = [str(self.value)]
242
+ if self.units:
243
+ parts.append(self.units)
244
+ if self.provenance:
245
+ parts.append(f"({self.provenance.algorithm})")
246
+ return " ".join(parts)
247
+
248
+ def __repr__(self) -> str:
249
+ """Detailed representation."""
250
+ parts = [f"value={self.value}"]
251
+ if self.units:
252
+ parts.append(f"units='{self.units}'")
253
+ if self.provenance:
254
+ parts.append(f"algorithm='{self.provenance.algorithm}'")
255
+ return f"MeasurementResultWithProvenance({', '.join(parts)})"
256
+
257
+ def pretty_print(self) -> str:
258
+ """Pretty-print result with full provenance.
259
+
260
+ Returns:
261
+ Multi-line formatted string with all details.
262
+
263
+ Example:
264
+ >>> print(result.pretty_print())
265
+ Value: 3.3 V
266
+ Algorithm: peak_to_peak
267
+ Timestamp: 2025-12-21T10:30:00Z
268
+ Version: 0.1.0
269
+ Parameters: window=(0, 0.001)
270
+ """
271
+ lines = [f"Value: {self.value}"]
272
+ if self.units:
273
+ lines[-1] += f" {self.units}"
274
+
275
+ if self.confidence:
276
+ lines.append(f"Confidence: ({self.confidence[0]}, {self.confidence[1]})")
277
+
278
+ if self.provenance:
279
+ lines.append(str(self.provenance))
280
+
281
+ return "\n".join(lines)
282
+
283
+
284
+ def compute_input_hash(data: NDArray[np.float64]) -> str:
285
+ """Compute hash of input data for change detection.
286
+
287
+ Uses SHA-256 hash of data array for reproducibility checks.
288
+
289
+ Args:
290
+ data: Input numpy array.
291
+
292
+ Returns:
293
+ Hexadecimal hash string.
294
+
295
+ Example:
296
+ >>> data = np.array([1.0, 2.0, 3.0])
297
+ >>> hash_str = compute_input_hash(data)
298
+
299
+ References:
300
+ API-011: Measurement Provenance Tracking
301
+ """
302
+ # Convert to bytes and hash
303
+ data_bytes = data.tobytes()
304
+ hash_obj = hashlib.sha256(data_bytes)
305
+ return hash_obj.hexdigest()
306
+
307
+
308
+ def create_provenance(
309
+ algorithm: str,
310
+ parameters: dict[str, Any] | None = None,
311
+ *,
312
+ input_data: NDArray[np.float64] | None = None,
313
+ metadata: dict[str, Any] | None = None,
314
+ ) -> Provenance:
315
+ """Create provenance record for a computation.
316
+
317
+ Convenience function to create provenance with automatic timestamp
318
+ and optional input hash.
319
+
320
+ Args:
321
+ algorithm: Name of algorithm or method.
322
+ parameters: Parameters used in computation.
323
+ input_data: Optional input data to hash for change detection.
324
+ metadata: Additional context information.
325
+
326
+ Returns:
327
+ Provenance object.
328
+
329
+ Example:
330
+ >>> import numpy as np
331
+ >>> data = np.array([1.0, 2.0, 3.0])
332
+ >>> prov = create_provenance(
333
+ ... algorithm='mean',
334
+ ... parameters={'axis': 0},
335
+ ... input_data=data
336
+ ... )
337
+
338
+ References:
339
+ API-011: Measurement Provenance Tracking
340
+ """
341
+ input_hash = None
342
+ if input_data is not None:
343
+ input_hash = compute_input_hash(input_data)
344
+
345
+ return Provenance(
346
+ algorithm=algorithm,
347
+ parameters=parameters or {},
348
+ input_hash=input_hash,
349
+ metadata=metadata or {},
350
+ )
351
+
352
+
353
+ __all__ = [
354
+ "MeasurementResultWithProvenance",
355
+ "Provenance",
356
+ "compute_input_hash",
357
+ "create_provenance",
358
+ ]
oscura/core/results.py ADDED
@@ -0,0 +1,331 @@
1
+ """Analysis result classes with intermediate data access.
2
+
3
+ This module provides rich result objects that store intermediate computation
4
+ results (FFT coefficients, filter states, etc.) for multi-step analysis
5
+ without recomputation.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from dataclasses import dataclass, field
11
+ from typing import TYPE_CHECKING, Any
12
+
13
+ import numpy as np
14
+
15
+ if TYPE_CHECKING:
16
+ from numpy.typing import NDArray
17
+
18
+ from .types import WaveformTrace
19
+
20
+
21
+ @dataclass
22
+ class AnalysisResult:
23
+ """Container for analysis results with intermediate data.
24
+
25
+ Stores the final result along with intermediate computation artifacts
26
+ like FFT coefficients, filter states, wavelet coefficients, etc.
27
+ Enables multi-step analysis without recomputation.
28
+
29
+ Attributes:
30
+ value: The final computed value (measurement, trace, etc.).
31
+ intermediates: Dictionary of intermediate computation results.
32
+ metadata: Additional metadata about the computation.
33
+
34
+ Example:
35
+ >>> result = AnalysisResult(
36
+ ... value=42.5,
37
+ ... intermediates={'fft_coeffs': coeffs, 'frequencies': freqs}
38
+ ... )
39
+ >>> fft_data = result.get_intermediate('fft_coeffs')
40
+
41
+ References:
42
+ API-005: Intermediate Result Access
43
+ """
44
+
45
+ value: Any
46
+ intermediates: dict[str, Any] = field(default_factory=dict)
47
+ metadata: dict[str, Any] = field(default_factory=dict)
48
+
49
+ def get_intermediate(self, key: str) -> Any:
50
+ """Get intermediate result by key.
51
+
52
+ Args:
53
+ key: Name of the intermediate result.
54
+
55
+ Returns:
56
+ The intermediate data.
57
+
58
+ Raises:
59
+ KeyError: If key not found in intermediates.
60
+
61
+ Example:
62
+ >>> spectrum = result.get_intermediate('fft_spectrum')
63
+ """
64
+ if key not in self.intermediates:
65
+ available = list(self.intermediates.keys())
66
+ raise KeyError(f"Intermediate '{key}' not found. Available: {available}")
67
+ return self.intermediates[key]
68
+
69
+ def has_intermediate(self, key: str) -> bool:
70
+ """Check if intermediate result exists.
71
+
72
+ Args:
73
+ key: Name of the intermediate result.
74
+
75
+ Returns:
76
+ True if key exists in intermediates.
77
+
78
+ Example:
79
+ >>> if result.has_intermediate('fft_coeffs'):
80
+ ... coeffs = result.get_intermediate('fft_coeffs')
81
+ """
82
+ return key in self.intermediates
83
+
84
+ def list_intermediates(self) -> list[str]:
85
+ """List all available intermediate result keys.
86
+
87
+ Returns:
88
+ List of intermediate result names.
89
+
90
+ Example:
91
+ >>> print(result.list_intermediates())
92
+ ['fft_spectrum', 'fft_frequencies', 'fft_power', 'fft_phase']
93
+ """
94
+ return list(self.intermediates.keys())
95
+
96
+
97
+ @dataclass
98
+ class FFTResult(AnalysisResult):
99
+ """Result object for FFT analysis with intermediate data.
100
+
101
+ Provides convenient access to FFT spectrum, frequencies, power,
102
+ and phase information.
103
+
104
+ Attributes:
105
+ spectrum: Complex FFT coefficients.
106
+ frequencies: Frequency bins in Hz.
107
+ power: Power spectrum (magnitude squared).
108
+ phase: Phase spectrum in radians.
109
+ trace: Original or transformed trace (optional).
110
+
111
+ Example:
112
+ >>> fft_result = osc.fft(trace, nfft=8192)
113
+ >>> spectrum = fft_result.spectrum
114
+ >>> frequencies = fft_result.frequencies
115
+ >>> power = fft_result.power
116
+ >>> phase = fft_result.phase
117
+ >>> peak_freq = frequencies[power.argmax()]
118
+
119
+ References:
120
+ API-005: Intermediate Result Access
121
+ """
122
+
123
+ spectrum: NDArray[np.complex128] = field(default_factory=lambda: np.array([]))
124
+ frequencies: NDArray[np.float64] = field(default_factory=lambda: np.array([]))
125
+ power: NDArray[np.float64] = field(default_factory=lambda: np.array([]))
126
+ phase: NDArray[np.float64] = field(default_factory=lambda: np.array([]))
127
+ trace: WaveformTrace | None = None
128
+
129
+ def __post_init__(self) -> None:
130
+ """Initialize intermediate results dictionary."""
131
+ # Store as intermediates for generic access
132
+ self.intermediates.update(
133
+ {
134
+ "spectrum": self.spectrum,
135
+ "frequencies": self.frequencies,
136
+ "power": self.power,
137
+ "phase": self.phase,
138
+ }
139
+ )
140
+ if self.trace is not None:
141
+ self.intermediates["trace"] = self.trace
142
+
143
+ # Set value to spectrum by default
144
+ if self.value is None:
145
+ self.value = self.spectrum
146
+
147
+ @property
148
+ def peak_frequency(self) -> float:
149
+ """Frequency of maximum power.
150
+
151
+ Returns:
152
+ Frequency in Hz where power spectrum peaks.
153
+
154
+ Example:
155
+ >>> print(f"Peak at {fft_result.peak_frequency:.2e} Hz")
156
+ """
157
+ if len(self.power) == 0:
158
+ return 0.0
159
+ return float(self.frequencies[self.power.argmax()])
160
+
161
+ @property
162
+ def magnitude(self) -> NDArray[np.float64]:
163
+ """Magnitude spectrum (absolute value of FFT).
164
+
165
+ Returns:
166
+ Magnitude of complex spectrum.
167
+
168
+ Example:
169
+ >>> mag = fft_result.magnitude
170
+ """
171
+ return np.abs(self.spectrum)
172
+
173
+
174
+ @dataclass
175
+ class FilterResult(AnalysisResult):
176
+ """Result object for filter operations with intermediate data.
177
+
178
+ Provides access to filtered trace along with filter characteristics
179
+ like transfer function and impulse response.
180
+
181
+ Attributes:
182
+ trace: Filtered WaveformTrace.
183
+ transfer_function: Filter transfer function H(f) (optional).
184
+ impulse_response: Filter impulse response h[n] (optional).
185
+ frequency_response: Tuple of (frequencies, response) (optional).
186
+ filter_coefficients: Filter coefficients (sos or ba format) (optional).
187
+
188
+ Example:
189
+ >>> filter_result = osc.low_pass(trace, cutoff=1e6, return_details=True)
190
+ >>> filtered_trace = filter_result.trace
191
+ >>> transfer_func = filter_result.transfer_function
192
+ >>> impulse_resp = filter_result.impulse_response
193
+
194
+ References:
195
+ API-005: Intermediate Result Access
196
+ API-009: Filter Introspection API
197
+ """
198
+
199
+ trace: WaveformTrace | None = None
200
+ transfer_function: NDArray[np.complex128] | None = None
201
+ impulse_response: NDArray[np.float64] | None = None
202
+ frequency_response: tuple[NDArray[np.float64], NDArray[np.complex128]] | None = None
203
+ filter_coefficients: Any | None = None
204
+
205
+ def __post_init__(self) -> None:
206
+ """Initialize intermediate results dictionary."""
207
+ if self.trace is not None:
208
+ self.intermediates["trace"] = self.trace
209
+ if self.transfer_function is not None:
210
+ self.intermediates["transfer_function"] = self.transfer_function
211
+ if self.impulse_response is not None:
212
+ self.intermediates["impulse_response"] = self.impulse_response
213
+ if self.frequency_response is not None:
214
+ self.intermediates["frequency_response"] = self.frequency_response
215
+ if self.filter_coefficients is not None:
216
+ self.intermediates["filter_coefficients"] = self.filter_coefficients
217
+
218
+ # Set value to trace by default
219
+ if self.value is None:
220
+ self.value = self.trace
221
+
222
+
223
+ @dataclass
224
+ class WaveletResult(AnalysisResult):
225
+ """Result object for wavelet transform with intermediate data.
226
+
227
+ Provides access to wavelet coefficients, scales, and frequencies.
228
+
229
+ Attributes:
230
+ coeffs: Wavelet coefficients.
231
+ scales: Wavelet scales.
232
+ frequencies: Corresponding frequencies in Hz.
233
+ trace: Original trace (optional).
234
+
235
+ Example:
236
+ >>> wavelet_result = osc.wavelet_transform(trace)
237
+ >>> coeffs = wavelet_result.coeffs
238
+ >>> scales = wavelet_result.scales
239
+ >>> frequencies = wavelet_result.frequencies
240
+
241
+ References:
242
+ API-005: Intermediate Result Access
243
+ """
244
+
245
+ coeffs: NDArray[np.complex128] | None = None
246
+ scales: NDArray[np.float64] | None = None
247
+ frequencies: NDArray[np.float64] | None = None
248
+ trace: WaveformTrace | None = None
249
+
250
+ def __post_init__(self) -> None:
251
+ """Initialize intermediate results dictionary."""
252
+ if self.coeffs is not None:
253
+ self.intermediates["coeffs"] = self.coeffs
254
+ if self.scales is not None:
255
+ self.intermediates["scales"] = self.scales
256
+ if self.frequencies is not None:
257
+ self.intermediates["frequencies"] = self.frequencies
258
+ if self.trace is not None:
259
+ self.intermediates["trace"] = self.trace
260
+
261
+ # Set value to coeffs by default
262
+ if self.value is None:
263
+ self.value = self.coeffs
264
+
265
+
266
+ @dataclass
267
+ class MeasurementResult(AnalysisResult):
268
+ """Result object for measurements with metadata.
269
+
270
+ Stores a measurement value along with units, method, and parameters
271
+ used for computation.
272
+
273
+ Attributes:
274
+ value: Measured value.
275
+ units: Units of measurement (e.g., 'V', 'Hz', 's').
276
+ method: Method or algorithm used.
277
+ parameters: Dictionary of parameters used.
278
+ confidence: Confidence interval or uncertainty (optional).
279
+
280
+ Example:
281
+ >>> result = MeasurementResult(
282
+ ... value=3.3,
283
+ ... units='V',
284
+ ... method='peak_to_peak',
285
+ ... parameters={'window': (0, 1e-3)}
286
+ ... )
287
+
288
+ References:
289
+ API-005: Intermediate Result Access
290
+ API-011: Measurement Provenance Tracking
291
+ """
292
+
293
+ units: str | None = None
294
+ method: str | None = None
295
+ parameters: dict[str, Any] = field(default_factory=dict)
296
+ confidence: tuple[float, float] | None = None
297
+
298
+ def __post_init__(self) -> None:
299
+ """Initialize metadata dictionary."""
300
+ self.metadata.update(
301
+ {
302
+ "units": self.units,
303
+ "method": self.method,
304
+ "parameters": self.parameters,
305
+ "confidence": self.confidence,
306
+ }
307
+ )
308
+
309
+ def __str__(self) -> str:
310
+ """String representation of measurement."""
311
+ if self.units:
312
+ return f"{self.value} {self.units}"
313
+ return str(self.value)
314
+
315
+ def __repr__(self) -> str:
316
+ """Detailed representation of measurement."""
317
+ parts = [f"value={self.value}"]
318
+ if self.units:
319
+ parts.append(f"units='{self.units}'")
320
+ if self.method:
321
+ parts.append(f"method='{self.method}'")
322
+ return f"MeasurementResult({', '.join(parts)})"
323
+
324
+
325
+ __all__ = [
326
+ "AnalysisResult",
327
+ "FFTResult",
328
+ "FilterResult",
329
+ "MeasurementResult",
330
+ "WaveletResult",
331
+ ]