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.
- oscura/__init__.py +813 -8
- oscura/__main__.py +392 -0
- oscura/analyzers/__init__.py +37 -0
- oscura/analyzers/digital/__init__.py +177 -0
- oscura/analyzers/digital/bus.py +691 -0
- oscura/analyzers/digital/clock.py +805 -0
- oscura/analyzers/digital/correlation.py +720 -0
- oscura/analyzers/digital/edges.py +632 -0
- oscura/analyzers/digital/extraction.py +413 -0
- oscura/analyzers/digital/quality.py +878 -0
- oscura/analyzers/digital/signal_quality.py +877 -0
- oscura/analyzers/digital/thresholds.py +708 -0
- oscura/analyzers/digital/timing.py +1104 -0
- oscura/analyzers/eye/__init__.py +46 -0
- oscura/analyzers/eye/diagram.py +434 -0
- oscura/analyzers/eye/metrics.py +555 -0
- oscura/analyzers/jitter/__init__.py +83 -0
- oscura/analyzers/jitter/ber.py +333 -0
- oscura/analyzers/jitter/decomposition.py +759 -0
- oscura/analyzers/jitter/measurements.py +413 -0
- oscura/analyzers/jitter/spectrum.py +220 -0
- oscura/analyzers/measurements.py +40 -0
- oscura/analyzers/packet/__init__.py +171 -0
- oscura/analyzers/packet/daq.py +1077 -0
- oscura/analyzers/packet/metrics.py +437 -0
- oscura/analyzers/packet/parser.py +327 -0
- oscura/analyzers/packet/payload.py +2156 -0
- oscura/analyzers/packet/payload_analysis.py +1312 -0
- oscura/analyzers/packet/payload_extraction.py +236 -0
- oscura/analyzers/packet/payload_patterns.py +670 -0
- oscura/analyzers/packet/stream.py +359 -0
- oscura/analyzers/patterns/__init__.py +266 -0
- oscura/analyzers/patterns/clustering.py +1036 -0
- oscura/analyzers/patterns/discovery.py +539 -0
- oscura/analyzers/patterns/learning.py +797 -0
- oscura/analyzers/patterns/matching.py +1091 -0
- oscura/analyzers/patterns/periodic.py +650 -0
- oscura/analyzers/patterns/sequences.py +767 -0
- oscura/analyzers/power/__init__.py +116 -0
- oscura/analyzers/power/ac_power.py +391 -0
- oscura/analyzers/power/basic.py +383 -0
- oscura/analyzers/power/conduction.py +314 -0
- oscura/analyzers/power/efficiency.py +297 -0
- oscura/analyzers/power/ripple.py +356 -0
- oscura/analyzers/power/soa.py +372 -0
- oscura/analyzers/power/switching.py +479 -0
- oscura/analyzers/protocol/__init__.py +150 -0
- oscura/analyzers/protocols/__init__.py +150 -0
- oscura/analyzers/protocols/base.py +500 -0
- oscura/analyzers/protocols/can.py +620 -0
- oscura/analyzers/protocols/can_fd.py +448 -0
- oscura/analyzers/protocols/flexray.py +405 -0
- oscura/analyzers/protocols/hdlc.py +399 -0
- oscura/analyzers/protocols/i2c.py +368 -0
- oscura/analyzers/protocols/i2s.py +296 -0
- oscura/analyzers/protocols/jtag.py +393 -0
- oscura/analyzers/protocols/lin.py +445 -0
- oscura/analyzers/protocols/manchester.py +333 -0
- oscura/analyzers/protocols/onewire.py +501 -0
- oscura/analyzers/protocols/spi.py +334 -0
- oscura/analyzers/protocols/swd.py +325 -0
- oscura/analyzers/protocols/uart.py +393 -0
- oscura/analyzers/protocols/usb.py +495 -0
- oscura/analyzers/signal_integrity/__init__.py +63 -0
- oscura/analyzers/signal_integrity/embedding.py +294 -0
- oscura/analyzers/signal_integrity/equalization.py +370 -0
- oscura/analyzers/signal_integrity/sparams.py +484 -0
- oscura/analyzers/spectral/__init__.py +53 -0
- oscura/analyzers/spectral/chunked.py +273 -0
- oscura/analyzers/spectral/chunked_fft.py +571 -0
- oscura/analyzers/spectral/chunked_wavelet.py +391 -0
- oscura/analyzers/spectral/fft.py +92 -0
- oscura/analyzers/statistical/__init__.py +250 -0
- oscura/analyzers/statistical/checksum.py +923 -0
- oscura/analyzers/statistical/chunked_corr.py +228 -0
- oscura/analyzers/statistical/classification.py +778 -0
- oscura/analyzers/statistical/entropy.py +1113 -0
- oscura/analyzers/statistical/ngrams.py +614 -0
- oscura/analyzers/statistics/__init__.py +119 -0
- oscura/analyzers/statistics/advanced.py +885 -0
- oscura/analyzers/statistics/basic.py +263 -0
- oscura/analyzers/statistics/correlation.py +630 -0
- oscura/analyzers/statistics/distribution.py +298 -0
- oscura/analyzers/statistics/outliers.py +463 -0
- oscura/analyzers/statistics/streaming.py +93 -0
- oscura/analyzers/statistics/trend.py +520 -0
- oscura/analyzers/validation.py +598 -0
- oscura/analyzers/waveform/__init__.py +36 -0
- oscura/analyzers/waveform/measurements.py +943 -0
- oscura/analyzers/waveform/measurements_with_uncertainty.py +371 -0
- oscura/analyzers/waveform/spectral.py +1689 -0
- oscura/analyzers/waveform/wavelets.py +298 -0
- oscura/api/__init__.py +62 -0
- oscura/api/dsl.py +538 -0
- oscura/api/fluent.py +571 -0
- oscura/api/operators.py +498 -0
- oscura/api/optimization.py +392 -0
- oscura/api/profiling.py +396 -0
- oscura/automotive/__init__.py +73 -0
- oscura/automotive/can/__init__.py +52 -0
- oscura/automotive/can/analysis.py +356 -0
- oscura/automotive/can/checksum.py +250 -0
- oscura/automotive/can/correlation.py +212 -0
- oscura/automotive/can/discovery.py +355 -0
- oscura/automotive/can/message_wrapper.py +375 -0
- oscura/automotive/can/models.py +385 -0
- oscura/automotive/can/patterns.py +381 -0
- oscura/automotive/can/session.py +452 -0
- oscura/automotive/can/state_machine.py +300 -0
- oscura/automotive/can/stimulus_response.py +461 -0
- oscura/automotive/dbc/__init__.py +15 -0
- oscura/automotive/dbc/generator.py +156 -0
- oscura/automotive/dbc/parser.py +146 -0
- oscura/automotive/dtc/__init__.py +30 -0
- oscura/automotive/dtc/database.py +3036 -0
- oscura/automotive/j1939/__init__.py +14 -0
- oscura/automotive/j1939/decoder.py +745 -0
- oscura/automotive/loaders/__init__.py +35 -0
- oscura/automotive/loaders/asc.py +98 -0
- oscura/automotive/loaders/blf.py +77 -0
- oscura/automotive/loaders/csv_can.py +136 -0
- oscura/automotive/loaders/dispatcher.py +136 -0
- oscura/automotive/loaders/mdf.py +331 -0
- oscura/automotive/loaders/pcap.py +132 -0
- oscura/automotive/obd/__init__.py +14 -0
- oscura/automotive/obd/decoder.py +707 -0
- oscura/automotive/uds/__init__.py +48 -0
- oscura/automotive/uds/decoder.py +265 -0
- oscura/automotive/uds/models.py +64 -0
- oscura/automotive/visualization.py +369 -0
- oscura/batch/__init__.py +55 -0
- oscura/batch/advanced.py +627 -0
- oscura/batch/aggregate.py +300 -0
- oscura/batch/analyze.py +139 -0
- oscura/batch/logging.py +487 -0
- oscura/batch/metrics.py +556 -0
- oscura/builders/__init__.py +41 -0
- oscura/builders/signal_builder.py +1131 -0
- oscura/cli/__init__.py +14 -0
- oscura/cli/batch.py +339 -0
- oscura/cli/characterize.py +273 -0
- oscura/cli/compare.py +775 -0
- oscura/cli/decode.py +551 -0
- oscura/cli/main.py +247 -0
- oscura/cli/shell.py +350 -0
- oscura/comparison/__init__.py +66 -0
- oscura/comparison/compare.py +397 -0
- oscura/comparison/golden.py +487 -0
- oscura/comparison/limits.py +391 -0
- oscura/comparison/mask.py +434 -0
- oscura/comparison/trace_diff.py +30 -0
- oscura/comparison/visualization.py +481 -0
- oscura/compliance/__init__.py +70 -0
- oscura/compliance/advanced.py +756 -0
- oscura/compliance/masks.py +363 -0
- oscura/compliance/reporting.py +483 -0
- oscura/compliance/testing.py +298 -0
- oscura/component/__init__.py +38 -0
- oscura/component/impedance.py +365 -0
- oscura/component/reactive.py +598 -0
- oscura/component/transmission_line.py +312 -0
- oscura/config/__init__.py +191 -0
- oscura/config/defaults.py +254 -0
- oscura/config/loader.py +348 -0
- oscura/config/memory.py +271 -0
- oscura/config/migration.py +458 -0
- oscura/config/pipeline.py +1077 -0
- oscura/config/preferences.py +530 -0
- oscura/config/protocol.py +875 -0
- oscura/config/schema.py +713 -0
- oscura/config/settings.py +420 -0
- oscura/config/thresholds.py +599 -0
- oscura/convenience.py +457 -0
- oscura/core/__init__.py +299 -0
- oscura/core/audit.py +457 -0
- oscura/core/backend_selector.py +405 -0
- oscura/core/cache.py +590 -0
- oscura/core/cancellation.py +439 -0
- oscura/core/confidence.py +225 -0
- oscura/core/config.py +506 -0
- oscura/core/correlation.py +216 -0
- oscura/core/cross_domain.py +422 -0
- oscura/core/debug.py +301 -0
- oscura/core/edge_cases.py +541 -0
- oscura/core/exceptions.py +535 -0
- oscura/core/gpu_backend.py +523 -0
- oscura/core/lazy.py +832 -0
- oscura/core/log_query.py +540 -0
- oscura/core/logging.py +931 -0
- oscura/core/logging_advanced.py +952 -0
- oscura/core/memoize.py +171 -0
- oscura/core/memory_check.py +274 -0
- oscura/core/memory_guard.py +290 -0
- oscura/core/memory_limits.py +336 -0
- oscura/core/memory_monitor.py +453 -0
- oscura/core/memory_progress.py +465 -0
- oscura/core/memory_warnings.py +315 -0
- oscura/core/numba_backend.py +362 -0
- oscura/core/performance.py +352 -0
- oscura/core/progress.py +524 -0
- oscura/core/provenance.py +358 -0
- oscura/core/results.py +331 -0
- oscura/core/types.py +504 -0
- oscura/core/uncertainty.py +383 -0
- oscura/discovery/__init__.py +52 -0
- oscura/discovery/anomaly_detector.py +672 -0
- oscura/discovery/auto_decoder.py +415 -0
- oscura/discovery/comparison.py +497 -0
- oscura/discovery/quality_validator.py +528 -0
- oscura/discovery/signal_detector.py +769 -0
- oscura/dsl/__init__.py +73 -0
- oscura/dsl/commands.py +246 -0
- oscura/dsl/interpreter.py +455 -0
- oscura/dsl/parser.py +689 -0
- oscura/dsl/repl.py +172 -0
- oscura/exceptions.py +59 -0
- oscura/exploratory/__init__.py +111 -0
- oscura/exploratory/error_recovery.py +642 -0
- oscura/exploratory/fuzzy.py +513 -0
- oscura/exploratory/fuzzy_advanced.py +786 -0
- oscura/exploratory/legacy.py +831 -0
- oscura/exploratory/parse.py +358 -0
- oscura/exploratory/recovery.py +275 -0
- oscura/exploratory/sync.py +382 -0
- oscura/exploratory/unknown.py +707 -0
- oscura/export/__init__.py +25 -0
- oscura/export/wireshark/README.md +265 -0
- oscura/export/wireshark/__init__.py +47 -0
- oscura/export/wireshark/generator.py +312 -0
- oscura/export/wireshark/lua_builder.py +159 -0
- oscura/export/wireshark/templates/dissector.lua.j2 +92 -0
- oscura/export/wireshark/type_mapping.py +165 -0
- oscura/export/wireshark/validator.py +105 -0
- oscura/exporters/__init__.py +94 -0
- oscura/exporters/csv.py +303 -0
- oscura/exporters/exporters.py +44 -0
- oscura/exporters/hdf5.py +219 -0
- oscura/exporters/html_export.py +701 -0
- oscura/exporters/json_export.py +291 -0
- oscura/exporters/markdown_export.py +367 -0
- oscura/exporters/matlab_export.py +354 -0
- oscura/exporters/npz_export.py +219 -0
- oscura/exporters/spice_export.py +210 -0
- oscura/extensibility/__init__.py +131 -0
- oscura/extensibility/docs.py +752 -0
- oscura/extensibility/extensions.py +1125 -0
- oscura/extensibility/logging.py +259 -0
- oscura/extensibility/measurements.py +485 -0
- oscura/extensibility/plugins.py +414 -0
- oscura/extensibility/registry.py +346 -0
- oscura/extensibility/templates.py +913 -0
- oscura/extensibility/validation.py +651 -0
- oscura/filtering/__init__.py +89 -0
- oscura/filtering/base.py +563 -0
- oscura/filtering/convenience.py +564 -0
- oscura/filtering/design.py +725 -0
- oscura/filtering/filters.py +32 -0
- oscura/filtering/introspection.py +605 -0
- oscura/guidance/__init__.py +24 -0
- oscura/guidance/recommender.py +429 -0
- oscura/guidance/wizard.py +518 -0
- oscura/inference/__init__.py +251 -0
- oscura/inference/active_learning/README.md +153 -0
- oscura/inference/active_learning/__init__.py +38 -0
- oscura/inference/active_learning/lstar.py +257 -0
- oscura/inference/active_learning/observation_table.py +230 -0
- oscura/inference/active_learning/oracle.py +78 -0
- oscura/inference/active_learning/teachers/__init__.py +15 -0
- oscura/inference/active_learning/teachers/simulator.py +192 -0
- oscura/inference/adaptive_tuning.py +453 -0
- oscura/inference/alignment.py +653 -0
- oscura/inference/bayesian.py +943 -0
- oscura/inference/binary.py +1016 -0
- oscura/inference/crc_reverse.py +711 -0
- oscura/inference/logic.py +288 -0
- oscura/inference/message_format.py +1305 -0
- oscura/inference/protocol.py +417 -0
- oscura/inference/protocol_dsl.py +1084 -0
- oscura/inference/protocol_library.py +1230 -0
- oscura/inference/sequences.py +809 -0
- oscura/inference/signal_intelligence.py +1509 -0
- oscura/inference/spectral.py +215 -0
- oscura/inference/state_machine.py +634 -0
- oscura/inference/stream.py +918 -0
- oscura/integrations/__init__.py +59 -0
- oscura/integrations/llm.py +1827 -0
- oscura/jupyter/__init__.py +32 -0
- oscura/jupyter/display.py +268 -0
- oscura/jupyter/magic.py +334 -0
- oscura/loaders/__init__.py +526 -0
- oscura/loaders/binary.py +69 -0
- oscura/loaders/configurable.py +1255 -0
- oscura/loaders/csv.py +26 -0
- oscura/loaders/csv_loader.py +473 -0
- oscura/loaders/hdf5.py +9 -0
- oscura/loaders/hdf5_loader.py +510 -0
- oscura/loaders/lazy.py +370 -0
- oscura/loaders/mmap_loader.py +583 -0
- oscura/loaders/numpy_loader.py +436 -0
- oscura/loaders/pcap.py +432 -0
- oscura/loaders/preprocessing.py +368 -0
- oscura/loaders/rigol.py +287 -0
- oscura/loaders/sigrok.py +321 -0
- oscura/loaders/tdms.py +367 -0
- oscura/loaders/tektronix.py +711 -0
- oscura/loaders/validation.py +584 -0
- oscura/loaders/vcd.py +464 -0
- oscura/loaders/wav.py +233 -0
- oscura/math/__init__.py +45 -0
- oscura/math/arithmetic.py +824 -0
- oscura/math/interpolation.py +413 -0
- oscura/onboarding/__init__.py +39 -0
- oscura/onboarding/help.py +498 -0
- oscura/onboarding/tutorials.py +405 -0
- oscura/onboarding/wizard.py +466 -0
- oscura/optimization/__init__.py +19 -0
- oscura/optimization/parallel.py +440 -0
- oscura/optimization/search.py +532 -0
- oscura/pipeline/__init__.py +43 -0
- oscura/pipeline/base.py +338 -0
- oscura/pipeline/composition.py +242 -0
- oscura/pipeline/parallel.py +448 -0
- oscura/pipeline/pipeline.py +375 -0
- oscura/pipeline/reverse_engineering.py +1119 -0
- oscura/plugins/__init__.py +122 -0
- oscura/plugins/base.py +272 -0
- oscura/plugins/cli.py +497 -0
- oscura/plugins/discovery.py +411 -0
- oscura/plugins/isolation.py +418 -0
- oscura/plugins/lifecycle.py +959 -0
- oscura/plugins/manager.py +493 -0
- oscura/plugins/registry.py +421 -0
- oscura/plugins/versioning.py +372 -0
- oscura/py.typed +0 -0
- oscura/quality/__init__.py +65 -0
- oscura/quality/ensemble.py +740 -0
- oscura/quality/explainer.py +338 -0
- oscura/quality/scoring.py +616 -0
- oscura/quality/warnings.py +456 -0
- oscura/reporting/__init__.py +248 -0
- oscura/reporting/advanced.py +1234 -0
- oscura/reporting/analyze.py +448 -0
- oscura/reporting/argument_preparer.py +596 -0
- oscura/reporting/auto_report.py +507 -0
- oscura/reporting/batch.py +615 -0
- oscura/reporting/chart_selection.py +223 -0
- oscura/reporting/comparison.py +330 -0
- oscura/reporting/config.py +615 -0
- oscura/reporting/content/__init__.py +39 -0
- oscura/reporting/content/executive.py +127 -0
- oscura/reporting/content/filtering.py +191 -0
- oscura/reporting/content/minimal.py +257 -0
- oscura/reporting/content/verbosity.py +162 -0
- oscura/reporting/core.py +508 -0
- oscura/reporting/core_formats/__init__.py +17 -0
- oscura/reporting/core_formats/multi_format.py +210 -0
- oscura/reporting/engine.py +836 -0
- oscura/reporting/export.py +366 -0
- oscura/reporting/formatting/__init__.py +129 -0
- oscura/reporting/formatting/emphasis.py +81 -0
- oscura/reporting/formatting/numbers.py +403 -0
- oscura/reporting/formatting/standards.py +55 -0
- oscura/reporting/formatting.py +466 -0
- oscura/reporting/html.py +578 -0
- oscura/reporting/index.py +590 -0
- oscura/reporting/multichannel.py +296 -0
- oscura/reporting/output.py +379 -0
- oscura/reporting/pdf.py +373 -0
- oscura/reporting/plots.py +731 -0
- oscura/reporting/pptx_export.py +360 -0
- oscura/reporting/renderers/__init__.py +11 -0
- oscura/reporting/renderers/pdf.py +94 -0
- oscura/reporting/sections.py +471 -0
- oscura/reporting/standards.py +680 -0
- oscura/reporting/summary_generator.py +368 -0
- oscura/reporting/tables.py +397 -0
- oscura/reporting/template_system.py +724 -0
- oscura/reporting/templates/__init__.py +15 -0
- oscura/reporting/templates/definition.py +205 -0
- oscura/reporting/templates/index.html +649 -0
- oscura/reporting/templates/index.md +173 -0
- oscura/schemas/__init__.py +158 -0
- oscura/schemas/bus_configuration.json +322 -0
- oscura/schemas/device_mapping.json +182 -0
- oscura/schemas/packet_format.json +418 -0
- oscura/schemas/protocol_definition.json +363 -0
- oscura/search/__init__.py +16 -0
- oscura/search/anomaly.py +292 -0
- oscura/search/context.py +149 -0
- oscura/search/pattern.py +160 -0
- oscura/session/__init__.py +34 -0
- oscura/session/annotations.py +289 -0
- oscura/session/history.py +313 -0
- oscura/session/session.py +445 -0
- oscura/streaming/__init__.py +43 -0
- oscura/streaming/chunked.py +611 -0
- oscura/streaming/progressive.py +393 -0
- oscura/streaming/realtime.py +622 -0
- oscura/testing/__init__.py +54 -0
- oscura/testing/synthetic.py +808 -0
- oscura/triggering/__init__.py +68 -0
- oscura/triggering/base.py +229 -0
- oscura/triggering/edge.py +353 -0
- oscura/triggering/pattern.py +344 -0
- oscura/triggering/pulse.py +581 -0
- oscura/triggering/window.py +453 -0
- oscura/ui/__init__.py +48 -0
- oscura/ui/formatters.py +526 -0
- oscura/ui/progressive_display.py +340 -0
- oscura/utils/__init__.py +99 -0
- oscura/utils/autodetect.py +338 -0
- oscura/utils/buffer.py +389 -0
- oscura/utils/lazy.py +407 -0
- oscura/utils/lazy_imports.py +147 -0
- oscura/utils/memory.py +836 -0
- oscura/utils/memory_advanced.py +1326 -0
- oscura/utils/memory_extensions.py +465 -0
- oscura/utils/progressive.py +352 -0
- oscura/utils/windowing.py +362 -0
- oscura/visualization/__init__.py +321 -0
- oscura/visualization/accessibility.py +526 -0
- oscura/visualization/annotations.py +374 -0
- oscura/visualization/axis_scaling.py +305 -0
- oscura/visualization/colors.py +453 -0
- oscura/visualization/digital.py +337 -0
- oscura/visualization/eye.py +420 -0
- oscura/visualization/histogram.py +281 -0
- oscura/visualization/interactive.py +858 -0
- oscura/visualization/jitter.py +702 -0
- oscura/visualization/keyboard.py +394 -0
- oscura/visualization/layout.py +365 -0
- oscura/visualization/optimization.py +1028 -0
- oscura/visualization/palettes.py +446 -0
- oscura/visualization/plot.py +92 -0
- oscura/visualization/power.py +290 -0
- oscura/visualization/power_extended.py +626 -0
- oscura/visualization/presets.py +467 -0
- oscura/visualization/protocols.py +932 -0
- oscura/visualization/render.py +207 -0
- oscura/visualization/rendering.py +444 -0
- oscura/visualization/reverse_engineering.py +791 -0
- oscura/visualization/signal_integrity.py +808 -0
- oscura/visualization/specialized.py +553 -0
- oscura/visualization/spectral.py +811 -0
- oscura/visualization/styles.py +381 -0
- oscura/visualization/thumbnails.py +311 -0
- oscura/visualization/time_axis.py +351 -0
- oscura/visualization/waveform.py +367 -0
- oscura/workflow/__init__.py +13 -0
- oscura/workflow/dag.py +377 -0
- oscura/workflows/__init__.py +58 -0
- oscura/workflows/compliance.py +280 -0
- oscura/workflows/digital.py +272 -0
- oscura/workflows/multi_trace.py +502 -0
- oscura/workflows/power.py +178 -0
- oscura/workflows/protocol.py +492 -0
- oscura/workflows/reverse_engineering.py +639 -0
- oscura/workflows/signal_integrity.py +227 -0
- oscura-0.1.0.dist-info/METADATA +300 -0
- oscura-0.1.0.dist-info/RECORD +463 -0
- oscura-0.1.0.dist-info/entry_points.txt +2 -0
- {oscura-0.0.1.dist-info → oscura-0.1.0.dist-info}/licenses/LICENSE +1 -1
- oscura-0.0.1.dist-info/METADATA +0 -63
- oscura-0.0.1.dist-info/RECORD +0 -5
- {oscura-0.0.1.dist-info → oscura-0.1.0.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
|
+
# TraceKit version (in production would import from __version__)
|
|
21
|
+
TRACEKIT_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 TraceKit 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 = TRACEKIT_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", TRACEKIT_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 = tk.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 = tk.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 = tk.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
|
+
]
|