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,487 @@
|
|
|
1
|
+
"""Golden waveform comparison for TraceKit.
|
|
2
|
+
|
|
3
|
+
This module provides golden reference waveform management and comparison
|
|
4
|
+
functions for pass/fail testing against known-good waveforms.
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
Example:
|
|
8
|
+
>>> from oscura.comparison import create_golden, compare_to_golden
|
|
9
|
+
>>> golden = create_golden(reference_trace)
|
|
10
|
+
>>> result = compare_to_golden(measured_trace, golden)
|
|
11
|
+
|
|
12
|
+
References:
|
|
13
|
+
IEEE 181-2011: Standard for Transitional Waveform Definitions
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import json
|
|
19
|
+
from dataclasses import dataclass, field
|
|
20
|
+
from datetime import datetime
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
from typing import TYPE_CHECKING, Any, Literal
|
|
23
|
+
|
|
24
|
+
import numpy as np
|
|
25
|
+
|
|
26
|
+
from oscura.core.exceptions import AnalysisError, LoaderError
|
|
27
|
+
|
|
28
|
+
if TYPE_CHECKING:
|
|
29
|
+
from numpy.typing import NDArray
|
|
30
|
+
|
|
31
|
+
from oscura.core.types import WaveformTrace
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@dataclass
|
|
35
|
+
class GoldenReference:
|
|
36
|
+
"""Golden reference waveform for comparison.
|
|
37
|
+
|
|
38
|
+
Contains a reference waveform with tolerance bounds for pass/fail
|
|
39
|
+
testing of measured waveforms.
|
|
40
|
+
|
|
41
|
+
Attributes:
|
|
42
|
+
data: Reference waveform data.
|
|
43
|
+
sample_rate: Sample rate in Hz.
|
|
44
|
+
upper_bound: Upper tolerance bound.
|
|
45
|
+
lower_bound: Lower tolerance bound.
|
|
46
|
+
tolerance: Tolerance used to create bounds.
|
|
47
|
+
tolerance_type: How tolerance was applied.
|
|
48
|
+
name: Reference name.
|
|
49
|
+
description: Optional description.
|
|
50
|
+
created: Creation timestamp.
|
|
51
|
+
metadata: Additional metadata.
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
data: NDArray[np.float64]
|
|
55
|
+
sample_rate: float
|
|
56
|
+
upper_bound: NDArray[np.float64]
|
|
57
|
+
lower_bound: NDArray[np.float64]
|
|
58
|
+
tolerance: float
|
|
59
|
+
tolerance_type: Literal["absolute", "percentage", "sigma"] = "absolute"
|
|
60
|
+
name: str = "golden"
|
|
61
|
+
description: str = ""
|
|
62
|
+
created: datetime = field(default_factory=datetime.now)
|
|
63
|
+
metadata: dict[str, Any] = field(default_factory=dict)
|
|
64
|
+
|
|
65
|
+
@property
|
|
66
|
+
def num_samples(self) -> int:
|
|
67
|
+
"""Number of samples in the reference."""
|
|
68
|
+
return len(self.data)
|
|
69
|
+
|
|
70
|
+
@property
|
|
71
|
+
def duration(self) -> float:
|
|
72
|
+
"""Duration in seconds."""
|
|
73
|
+
return self.num_samples / self.sample_rate
|
|
74
|
+
|
|
75
|
+
def to_dict(self) -> dict[str, Any]:
|
|
76
|
+
"""Convert to dictionary for serialization."""
|
|
77
|
+
return {
|
|
78
|
+
"data": self.data.tolist(),
|
|
79
|
+
"sample_rate": self.sample_rate,
|
|
80
|
+
"upper_bound": self.upper_bound.tolist(),
|
|
81
|
+
"lower_bound": self.lower_bound.tolist(),
|
|
82
|
+
"tolerance": self.tolerance,
|
|
83
|
+
"tolerance_type": self.tolerance_type,
|
|
84
|
+
"name": self.name,
|
|
85
|
+
"description": self.description,
|
|
86
|
+
"created": self.created.isoformat(),
|
|
87
|
+
"metadata": self.metadata,
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
@classmethod
|
|
91
|
+
def from_dict(cls, data: dict[str, Any]) -> GoldenReference:
|
|
92
|
+
"""Create from dictionary."""
|
|
93
|
+
return cls(
|
|
94
|
+
data=np.array(data["data"], dtype=np.float64),
|
|
95
|
+
sample_rate=data["sample_rate"],
|
|
96
|
+
upper_bound=np.array(data["upper_bound"], dtype=np.float64),
|
|
97
|
+
lower_bound=np.array(data["lower_bound"], dtype=np.float64),
|
|
98
|
+
tolerance=data["tolerance"],
|
|
99
|
+
tolerance_type=data.get("tolerance_type", "absolute"),
|
|
100
|
+
name=data.get("name", "golden"),
|
|
101
|
+
description=data.get("description", ""),
|
|
102
|
+
created=datetime.fromisoformat(data["created"])
|
|
103
|
+
if "created" in data
|
|
104
|
+
else datetime.now(),
|
|
105
|
+
metadata=data.get("metadata", {}),
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
def save(self, path: str | Path) -> None:
|
|
109
|
+
"""Save golden reference to file.
|
|
110
|
+
|
|
111
|
+
Args:
|
|
112
|
+
path: File path (JSON format).
|
|
113
|
+
"""
|
|
114
|
+
path = Path(path)
|
|
115
|
+
with open(path, "w") as f:
|
|
116
|
+
json.dump(self.to_dict(), f, indent=2)
|
|
117
|
+
|
|
118
|
+
@classmethod
|
|
119
|
+
def load(cls, path: str | Path) -> GoldenReference:
|
|
120
|
+
"""Load golden reference from file.
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
path: File path.
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
GoldenReference instance.
|
|
127
|
+
|
|
128
|
+
Raises:
|
|
129
|
+
LoaderError: If golden reference file not found.
|
|
130
|
+
"""
|
|
131
|
+
path = Path(path)
|
|
132
|
+
if not path.exists():
|
|
133
|
+
raise LoaderError(
|
|
134
|
+
f"Golden reference file not found: {path}",
|
|
135
|
+
file_path=str(path),
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
with open(path) as f:
|
|
139
|
+
data = json.load(f)
|
|
140
|
+
|
|
141
|
+
return cls.from_dict(data)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
@dataclass
|
|
145
|
+
class GoldenComparisonResult:
|
|
146
|
+
"""Result of golden waveform comparison.
|
|
147
|
+
|
|
148
|
+
Attributes:
|
|
149
|
+
passed: True if measured waveform is within tolerance.
|
|
150
|
+
num_violations: Number of samples outside tolerance.
|
|
151
|
+
violation_rate: Fraction of samples outside tolerance.
|
|
152
|
+
max_deviation: Maximum deviation from reference.
|
|
153
|
+
rms_deviation: RMS deviation from reference.
|
|
154
|
+
upper_violations: Indices exceeding upper bound.
|
|
155
|
+
lower_violations: Indices below lower bound.
|
|
156
|
+
margin: Minimum margin to tolerance bound.
|
|
157
|
+
margin_percentage: Margin as percentage of tolerance.
|
|
158
|
+
statistics: Additional comparison statistics.
|
|
159
|
+
"""
|
|
160
|
+
|
|
161
|
+
passed: bool
|
|
162
|
+
num_violations: int
|
|
163
|
+
violation_rate: float
|
|
164
|
+
max_deviation: float
|
|
165
|
+
rms_deviation: float
|
|
166
|
+
upper_violations: NDArray[np.int64] | None = None
|
|
167
|
+
lower_violations: NDArray[np.int64] | None = None
|
|
168
|
+
margin: float | None = None
|
|
169
|
+
margin_percentage: float | None = None
|
|
170
|
+
statistics: dict[str, Any] = field(default_factory=dict)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def create_golden(
|
|
174
|
+
trace: WaveformTrace,
|
|
175
|
+
*,
|
|
176
|
+
tolerance: float | None = None,
|
|
177
|
+
tolerance_pct: float | None = None,
|
|
178
|
+
tolerance_sigma: float | None = None,
|
|
179
|
+
name: str = "golden",
|
|
180
|
+
description: str = "",
|
|
181
|
+
) -> GoldenReference:
|
|
182
|
+
"""Create a golden reference from a trace.
|
|
183
|
+
|
|
184
|
+
Creates a golden reference waveform with tolerance bounds for
|
|
185
|
+
subsequent comparison testing.
|
|
186
|
+
|
|
187
|
+
Args:
|
|
188
|
+
trace: Reference waveform trace.
|
|
189
|
+
tolerance: Absolute tolerance value.
|
|
190
|
+
tolerance_pct: Percentage tolerance (0-100).
|
|
191
|
+
tolerance_sigma: Tolerance as multiple of standard deviation.
|
|
192
|
+
name: Name for the reference.
|
|
193
|
+
description: Optional description.
|
|
194
|
+
|
|
195
|
+
Returns:
|
|
196
|
+
GoldenReference for comparison testing.
|
|
197
|
+
|
|
198
|
+
Example:
|
|
199
|
+
>>> golden = create_golden(trace, tolerance_pct=5) # 5% tolerance
|
|
200
|
+
>>> golden = create_golden(trace, tolerance=0.01) # 10mV tolerance
|
|
201
|
+
"""
|
|
202
|
+
data = trace.data.astype(np.float64)
|
|
203
|
+
|
|
204
|
+
# Determine tolerance and type
|
|
205
|
+
if tolerance is not None:
|
|
206
|
+
tol = tolerance
|
|
207
|
+
tol_type: Literal["absolute", "percentage", "sigma"] = "absolute"
|
|
208
|
+
elif tolerance_pct is not None:
|
|
209
|
+
# Calculate absolute tolerance from percentage
|
|
210
|
+
data_range = float(np.ptp(data))
|
|
211
|
+
tol = data_range * tolerance_pct / 100.0
|
|
212
|
+
tol_type = "percentage"
|
|
213
|
+
elif tolerance_sigma is not None:
|
|
214
|
+
# Calculate tolerance from standard deviation
|
|
215
|
+
tol = float(np.std(data)) * tolerance_sigma
|
|
216
|
+
tol_type = "sigma"
|
|
217
|
+
else:
|
|
218
|
+
# Default: 1% of range
|
|
219
|
+
data_range = float(np.ptp(data))
|
|
220
|
+
tol = data_range * 0.01
|
|
221
|
+
tol_type = "percentage"
|
|
222
|
+
|
|
223
|
+
# Create bounds
|
|
224
|
+
upper_bound = data + tol
|
|
225
|
+
lower_bound = data - tol
|
|
226
|
+
|
|
227
|
+
return GoldenReference(
|
|
228
|
+
data=data,
|
|
229
|
+
sample_rate=trace.metadata.sample_rate,
|
|
230
|
+
upper_bound=upper_bound,
|
|
231
|
+
lower_bound=lower_bound,
|
|
232
|
+
tolerance=tol,
|
|
233
|
+
tolerance_type=tol_type,
|
|
234
|
+
name=name,
|
|
235
|
+
description=description,
|
|
236
|
+
metadata={
|
|
237
|
+
"source_file": trace.metadata.source_file,
|
|
238
|
+
"channel_name": trace.metadata.channel_name,
|
|
239
|
+
},
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def tolerance_envelope(
|
|
244
|
+
trace: WaveformTrace,
|
|
245
|
+
*,
|
|
246
|
+
absolute: float | None = None,
|
|
247
|
+
percentage: float | None = None,
|
|
248
|
+
sigma: float | None = None,
|
|
249
|
+
tolerance: float | None = None, # Alias for absolute
|
|
250
|
+
tolerance_pct: float | None = None, # Alias for percentage
|
|
251
|
+
) -> tuple[NDArray[np.float64], NDArray[np.float64]]:
|
|
252
|
+
"""Create tolerance envelope around a trace.
|
|
253
|
+
|
|
254
|
+
Generates upper and lower bounds based on the specified tolerance.
|
|
255
|
+
|
|
256
|
+
Args:
|
|
257
|
+
trace: Reference trace.
|
|
258
|
+
absolute: Absolute tolerance value.
|
|
259
|
+
percentage: Percentage tolerance (0-100).
|
|
260
|
+
sigma: Tolerance as multiple of standard deviation.
|
|
261
|
+
tolerance: Alias for absolute (deprecated, use absolute).
|
|
262
|
+
tolerance_pct: Alias for percentage (deprecated, use percentage).
|
|
263
|
+
|
|
264
|
+
Returns:
|
|
265
|
+
Tuple of (upper_bound, lower_bound) arrays.
|
|
266
|
+
|
|
267
|
+
Raises:
|
|
268
|
+
ValueError: If no tolerance type specified.
|
|
269
|
+
|
|
270
|
+
Example:
|
|
271
|
+
>>> upper, lower = tolerance_envelope(trace, percentage=5)
|
|
272
|
+
>>> upper, lower = tolerance_envelope(trace, tolerance=0.1) # absolute
|
|
273
|
+
"""
|
|
274
|
+
data = trace.data.astype(np.float64)
|
|
275
|
+
|
|
276
|
+
# Handle legacy parameter names
|
|
277
|
+
if tolerance is not None:
|
|
278
|
+
absolute = tolerance
|
|
279
|
+
if tolerance_pct is not None:
|
|
280
|
+
percentage = tolerance_pct
|
|
281
|
+
|
|
282
|
+
if absolute is not None:
|
|
283
|
+
tol = absolute
|
|
284
|
+
elif percentage is not None:
|
|
285
|
+
data_range = float(np.ptp(data))
|
|
286
|
+
tol = data_range * percentage / 100.0
|
|
287
|
+
elif sigma is not None:
|
|
288
|
+
tol = float(np.std(data)) * sigma
|
|
289
|
+
else:
|
|
290
|
+
raise ValueError("Must specify absolute, percentage, or sigma tolerance")
|
|
291
|
+
|
|
292
|
+
return data + tol, data - tol
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def compare_to_golden(
|
|
296
|
+
trace: WaveformTrace,
|
|
297
|
+
golden: GoldenReference,
|
|
298
|
+
*,
|
|
299
|
+
align: bool = True,
|
|
300
|
+
interpolate: bool = True,
|
|
301
|
+
) -> GoldenComparisonResult:
|
|
302
|
+
"""Compare a trace to a golden reference.
|
|
303
|
+
|
|
304
|
+
Tests if the measured trace falls within the tolerance bounds
|
|
305
|
+
of the golden reference.
|
|
306
|
+
|
|
307
|
+
Args:
|
|
308
|
+
trace: Measured trace to compare.
|
|
309
|
+
golden: Golden reference to compare against.
|
|
310
|
+
align: Attempt to align traces by cross-correlation.
|
|
311
|
+
interpolate: Interpolate if sample counts differ.
|
|
312
|
+
|
|
313
|
+
Returns:
|
|
314
|
+
GoldenComparisonResult with pass/fail status.
|
|
315
|
+
|
|
316
|
+
Example:
|
|
317
|
+
>>> result = compare_to_golden(measured, golden)
|
|
318
|
+
>>> if result.passed:
|
|
319
|
+
... print("PASS")
|
|
320
|
+
"""
|
|
321
|
+
measured = trace.data.astype(np.float64)
|
|
322
|
+
reference = golden.data.copy()
|
|
323
|
+
upper = golden.upper_bound.copy()
|
|
324
|
+
lower = golden.lower_bound.copy()
|
|
325
|
+
|
|
326
|
+
# Handle length mismatch
|
|
327
|
+
if len(measured) != len(reference):
|
|
328
|
+
if interpolate:
|
|
329
|
+
# Interpolate measured to match reference length
|
|
330
|
+
x_measured = np.linspace(0, 1, len(measured))
|
|
331
|
+
x_reference = np.linspace(0, 1, len(reference))
|
|
332
|
+
measured = np.interp(x_reference, x_measured, measured)
|
|
333
|
+
else:
|
|
334
|
+
# Truncate to shorter length
|
|
335
|
+
min_len = min(len(measured), len(reference))
|
|
336
|
+
measured = measured[:min_len]
|
|
337
|
+
reference = reference[:min_len]
|
|
338
|
+
upper = upper[:min_len]
|
|
339
|
+
lower = lower[:min_len]
|
|
340
|
+
|
|
341
|
+
# Optionally align by cross-correlation
|
|
342
|
+
if align and len(measured) > 10:
|
|
343
|
+
from scipy import signal as sp_signal
|
|
344
|
+
|
|
345
|
+
corr = sp_signal.correlate(measured, reference, mode="same")
|
|
346
|
+
shift = len(measured) // 2 - np.argmax(corr)
|
|
347
|
+
if abs(shift) < len(measured) // 4: # Only shift if reasonable
|
|
348
|
+
measured = np.roll(measured, -shift)
|
|
349
|
+
|
|
350
|
+
# Find violations
|
|
351
|
+
upper_viol = np.where(measured > upper)[0]
|
|
352
|
+
lower_viol = np.where(measured < lower)[0]
|
|
353
|
+
all_violations = np.union1d(upper_viol, lower_viol)
|
|
354
|
+
|
|
355
|
+
num_violations = len(all_violations)
|
|
356
|
+
violation_rate = num_violations / len(measured) if len(measured) > 0 else 0.0
|
|
357
|
+
|
|
358
|
+
# Compute deviation statistics
|
|
359
|
+
deviation = measured - reference
|
|
360
|
+
max_deviation = float(np.max(np.abs(deviation)))
|
|
361
|
+
rms_deviation = float(np.sqrt(np.mean(deviation**2)))
|
|
362
|
+
|
|
363
|
+
# Compute margin
|
|
364
|
+
upper_margin = float(np.min(upper - measured))
|
|
365
|
+
lower_margin = float(np.min(measured - lower))
|
|
366
|
+
margin = min(upper_margin, lower_margin)
|
|
367
|
+
|
|
368
|
+
# Margin as percentage of tolerance
|
|
369
|
+
margin_pct = (margin / golden.tolerance * 100) if golden.tolerance > 0 else None
|
|
370
|
+
|
|
371
|
+
# Additional statistics
|
|
372
|
+
# Handle constant data (zero std) for correlation calculation
|
|
373
|
+
measured_std = np.std(measured)
|
|
374
|
+
reference_std = np.std(reference)
|
|
375
|
+
if measured_std == 0 or reference_std == 0:
|
|
376
|
+
# For constant data, correlation is undefined (NaN) or 1.0 if both are equal
|
|
377
|
+
if np.allclose(measured, reference):
|
|
378
|
+
correlation = 1.0
|
|
379
|
+
else:
|
|
380
|
+
correlation = float("nan")
|
|
381
|
+
else:
|
|
382
|
+
correlation = float(np.corrcoef(measured, reference)[0, 1])
|
|
383
|
+
|
|
384
|
+
statistics = {
|
|
385
|
+
"mean_deviation": float(np.mean(deviation)),
|
|
386
|
+
"std_deviation": float(np.std(deviation)),
|
|
387
|
+
"max_positive_deviation": float(np.max(deviation)),
|
|
388
|
+
"max_negative_deviation": float(np.min(deviation)),
|
|
389
|
+
"correlation": correlation,
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
return GoldenComparisonResult(
|
|
393
|
+
passed=num_violations == 0,
|
|
394
|
+
num_violations=num_violations,
|
|
395
|
+
violation_rate=violation_rate,
|
|
396
|
+
max_deviation=max_deviation,
|
|
397
|
+
rms_deviation=rms_deviation,
|
|
398
|
+
upper_violations=upper_viol if len(upper_viol) > 0 else None,
|
|
399
|
+
lower_violations=lower_viol if len(lower_viol) > 0 else None,
|
|
400
|
+
margin=margin,
|
|
401
|
+
margin_percentage=margin_pct,
|
|
402
|
+
statistics=statistics,
|
|
403
|
+
)
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
def batch_compare_to_golden(
|
|
407
|
+
traces: list[WaveformTrace],
|
|
408
|
+
golden: GoldenReference,
|
|
409
|
+
*,
|
|
410
|
+
align: bool = True,
|
|
411
|
+
) -> list[GoldenComparisonResult]:
|
|
412
|
+
"""Compare multiple traces to a golden reference.
|
|
413
|
+
|
|
414
|
+
Tests a batch of measured traces against the same golden reference.
|
|
415
|
+
|
|
416
|
+
Args:
|
|
417
|
+
traces: List of traces to compare.
|
|
418
|
+
golden: Golden reference.
|
|
419
|
+
align: Attempt to align traces.
|
|
420
|
+
|
|
421
|
+
Returns:
|
|
422
|
+
List of comparison results.
|
|
423
|
+
|
|
424
|
+
Example:
|
|
425
|
+
>>> results = batch_compare_to_golden(traces, golden)
|
|
426
|
+
>>> pass_rate = sum(r.passed for r in results) / len(results)
|
|
427
|
+
"""
|
|
428
|
+
return [compare_to_golden(trace, golden, align=align) for trace in traces]
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
def golden_from_average(
|
|
432
|
+
traces: list[WaveformTrace],
|
|
433
|
+
*,
|
|
434
|
+
tolerance_sigma: float = 3.0,
|
|
435
|
+
name: str = "averaged_golden",
|
|
436
|
+
) -> GoldenReference:
|
|
437
|
+
"""Create golden reference from averaged traces.
|
|
438
|
+
|
|
439
|
+
Creates a golden reference from the average of multiple traces,
|
|
440
|
+
with tolerance based on the standard deviation.
|
|
441
|
+
|
|
442
|
+
Args:
|
|
443
|
+
traces: List of traces to average.
|
|
444
|
+
tolerance_sigma: Number of standard deviations for tolerance.
|
|
445
|
+
name: Name for the reference.
|
|
446
|
+
|
|
447
|
+
Returns:
|
|
448
|
+
GoldenReference based on averaged data.
|
|
449
|
+
|
|
450
|
+
Raises:
|
|
451
|
+
AnalysisError: If no traces provided for averaging.
|
|
452
|
+
|
|
453
|
+
Example:
|
|
454
|
+
>>> golden = golden_from_average(sample_traces, tolerance_sigma=3)
|
|
455
|
+
"""
|
|
456
|
+
if not traces:
|
|
457
|
+
raise AnalysisError("No traces provided for averaging")
|
|
458
|
+
|
|
459
|
+
# Get common length
|
|
460
|
+
lengths = [len(t.data) for t in traces]
|
|
461
|
+
min_len = min(lengths)
|
|
462
|
+
|
|
463
|
+
# Stack and average
|
|
464
|
+
stacked = np.array([t.data[:min_len] for t in traces], dtype=np.float64)
|
|
465
|
+
avg_data = np.mean(stacked, axis=0)
|
|
466
|
+
std_data = np.std(stacked, axis=0)
|
|
467
|
+
|
|
468
|
+
# Create tolerance from standard deviation
|
|
469
|
+
tolerance = std_data * tolerance_sigma
|
|
470
|
+
|
|
471
|
+
# Use constant tolerance (max of varying tolerance)
|
|
472
|
+
max_tol = float(np.max(tolerance))
|
|
473
|
+
|
|
474
|
+
return GoldenReference(
|
|
475
|
+
data=avg_data,
|
|
476
|
+
sample_rate=traces[0].metadata.sample_rate,
|
|
477
|
+
upper_bound=avg_data + tolerance,
|
|
478
|
+
lower_bound=avg_data - tolerance,
|
|
479
|
+
tolerance=max_tol,
|
|
480
|
+
tolerance_type="sigma",
|
|
481
|
+
name=name,
|
|
482
|
+
description=f"Averaged from {len(traces)} traces, {tolerance_sigma} sigma tolerance",
|
|
483
|
+
metadata={
|
|
484
|
+
"num_traces_averaged": len(traces),
|
|
485
|
+
"tolerance_sigma": tolerance_sigma,
|
|
486
|
+
},
|
|
487
|
+
)
|