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.
- 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.1.dist-info/METADATA +300 -0
- oscura-0.1.1.dist-info/RECORD +463 -0
- oscura-0.1.1.dist-info/entry_points.txt +2 -0
- {oscura-0.0.1.dist-info → oscura-0.1.1.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.1.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,497 @@
|
|
|
1
|
+
"""Intelligent trace comparison for auto-discovery.
|
|
2
|
+
|
|
3
|
+
This module provides automatic trace comparison with alignment, difference
|
|
4
|
+
detection, and plain-language explanations.
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
Example:
|
|
8
|
+
>>> from oscura.discovery import compare_traces
|
|
9
|
+
>>> diff = compare_traces(trace1, trace2)
|
|
10
|
+
>>> for d in diff.differences:
|
|
11
|
+
... print(f"{d.category}: {d.description}")
|
|
12
|
+
|
|
13
|
+
References:
|
|
14
|
+
Oscura Auto-Discovery Specification
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
from dataclasses import dataclass, field
|
|
20
|
+
from typing import TYPE_CHECKING, Literal
|
|
21
|
+
|
|
22
|
+
import numpy as np
|
|
23
|
+
from scipy import signal as sp_signal
|
|
24
|
+
|
|
25
|
+
if TYPE_CHECKING:
|
|
26
|
+
from numpy.typing import NDArray
|
|
27
|
+
|
|
28
|
+
from oscura.core.types import WaveformTrace
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclass
|
|
32
|
+
class Difference:
|
|
33
|
+
"""Individual difference between traces.
|
|
34
|
+
|
|
35
|
+
Attributes:
|
|
36
|
+
category: Difference category (timing, amplitude, pattern, transitions).
|
|
37
|
+
timestamp_us: Timestamp in microseconds.
|
|
38
|
+
description: Plain language explanation.
|
|
39
|
+
severity: Severity level (CRITICAL, WARNING, INFO).
|
|
40
|
+
impact_score: Impact score (0.0-1.0, higher = more severe).
|
|
41
|
+
expected_value: Expected value from reference.
|
|
42
|
+
actual_value: Actual value from measured trace.
|
|
43
|
+
delta_value: Absolute difference.
|
|
44
|
+
delta_percent: Percentage difference.
|
|
45
|
+
confidence: Confidence in this difference detection.
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
category: str
|
|
49
|
+
timestamp_us: float
|
|
50
|
+
description: str
|
|
51
|
+
severity: str
|
|
52
|
+
impact_score: float
|
|
53
|
+
expected_value: float | None = None
|
|
54
|
+
actual_value: float | None = None
|
|
55
|
+
delta_value: float | None = None
|
|
56
|
+
delta_percent: float | None = None
|
|
57
|
+
confidence: float = 1.0
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@dataclass
|
|
61
|
+
class TraceDiff:
|
|
62
|
+
"""Result of intelligent trace comparison.
|
|
63
|
+
|
|
64
|
+
Attributes:
|
|
65
|
+
summary: High-level summary of comparison.
|
|
66
|
+
alignment_method: Method used to align traces.
|
|
67
|
+
similarity_score: Overall similarity (0.0-1.0).
|
|
68
|
+
differences: List of detected differences, sorted by impact.
|
|
69
|
+
visual_path: Path to generated visual comparison (if created).
|
|
70
|
+
stats: Statistical comparison metrics.
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
summary: str
|
|
74
|
+
alignment_method: str
|
|
75
|
+
similarity_score: float
|
|
76
|
+
differences: list[Difference] = field(default_factory=list)
|
|
77
|
+
visual_path: str | None = None
|
|
78
|
+
stats: dict | None = None # type: ignore[type-arg]
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _align_time_based(
|
|
82
|
+
trace1: WaveformTrace,
|
|
83
|
+
trace2: WaveformTrace,
|
|
84
|
+
) -> tuple[NDArray[np.float64], NDArray[np.float64], int]:
|
|
85
|
+
"""Align traces based on time (sync to t=0).
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
trace1: First trace.
|
|
89
|
+
trace2: Second trace.
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
Tuple of (data1, data2, offset_samples).
|
|
93
|
+
"""
|
|
94
|
+
# Simply align to start (t=0)
|
|
95
|
+
min_len = min(len(trace1.data), len(trace2.data))
|
|
96
|
+
data1 = trace1.data[:min_len].astype(np.float64)
|
|
97
|
+
data2 = trace2.data[:min_len].astype(np.float64)
|
|
98
|
+
|
|
99
|
+
return data1, data2, 0
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _align_trigger_based(
|
|
103
|
+
trace1: WaveformTrace,
|
|
104
|
+
trace2: WaveformTrace,
|
|
105
|
+
threshold_pct: float = 50.0,
|
|
106
|
+
) -> tuple[NDArray[np.float64], NDArray[np.float64], int]:
|
|
107
|
+
"""Align traces based on trigger point (first edge).
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
trace1: First trace.
|
|
111
|
+
trace2: Second trace.
|
|
112
|
+
threshold_pct: Threshold percentage for edge detection.
|
|
113
|
+
|
|
114
|
+
Returns:
|
|
115
|
+
Tuple of (data1, data2, offset_samples).
|
|
116
|
+
"""
|
|
117
|
+
data1 = trace1.data.astype(np.float64)
|
|
118
|
+
data2 = trace2.data.astype(np.float64)
|
|
119
|
+
|
|
120
|
+
# Find first significant edge in each trace
|
|
121
|
+
range1 = np.ptp(data1)
|
|
122
|
+
range2 = np.ptp(data2)
|
|
123
|
+
|
|
124
|
+
threshold1 = np.min(data1) + range1 * threshold_pct / 100.0
|
|
125
|
+
threshold2 = np.min(data2) + range2 * threshold_pct / 100.0
|
|
126
|
+
|
|
127
|
+
# Find first crossing
|
|
128
|
+
idx1 = np.where(data1 > threshold1)[0]
|
|
129
|
+
idx2 = np.where(data2 > threshold2)[0]
|
|
130
|
+
|
|
131
|
+
offset1 = idx1[0] if len(idx1) > 0 else 0
|
|
132
|
+
offset2 = idx2[0] if len(idx2) > 0 else 0
|
|
133
|
+
|
|
134
|
+
# Align to earliest trigger
|
|
135
|
+
if offset1 <= offset2:
|
|
136
|
+
offset_samples = offset2 - offset1
|
|
137
|
+
data1_aligned = data1[offset1:]
|
|
138
|
+
data2_aligned = data2[offset2:]
|
|
139
|
+
else:
|
|
140
|
+
offset_samples = offset1 - offset2
|
|
141
|
+
data1_aligned = data1[offset1:]
|
|
142
|
+
data2_aligned = data2[offset2:]
|
|
143
|
+
|
|
144
|
+
# Truncate to same length
|
|
145
|
+
min_len = min(len(data1_aligned), len(data2_aligned))
|
|
146
|
+
return data1_aligned[:min_len], data2_aligned[:min_len], offset_samples
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def _align_pattern_based(
|
|
150
|
+
trace1: WaveformTrace,
|
|
151
|
+
trace2: WaveformTrace,
|
|
152
|
+
) -> tuple[NDArray[np.float64], NDArray[np.float64], int]:
|
|
153
|
+
"""Align traces using cross-correlation.
|
|
154
|
+
|
|
155
|
+
Args:
|
|
156
|
+
trace1: First trace.
|
|
157
|
+
trace2: Second trace.
|
|
158
|
+
|
|
159
|
+
Returns:
|
|
160
|
+
Tuple of (data1, data2, offset_samples).
|
|
161
|
+
"""
|
|
162
|
+
data1 = trace1.data.astype(np.float64)
|
|
163
|
+
data2 = trace2.data.astype(np.float64)
|
|
164
|
+
|
|
165
|
+
# Normalize for correlation
|
|
166
|
+
data1_norm = (data1 - np.mean(data1)) / (np.std(data1) + 1e-10)
|
|
167
|
+
data2_norm = (data2 - np.mean(data2)) / (np.std(data2) + 1e-10)
|
|
168
|
+
|
|
169
|
+
# Cross-correlation
|
|
170
|
+
correlation = sp_signal.correlate(data1_norm, data2_norm, mode="full")
|
|
171
|
+
|
|
172
|
+
# Find peak
|
|
173
|
+
peak_idx = np.argmax(np.abs(correlation))
|
|
174
|
+
offset_samples = peak_idx - (len(data2) - 1)
|
|
175
|
+
|
|
176
|
+
# Align based on offset
|
|
177
|
+
if offset_samples >= 0:
|
|
178
|
+
data1_aligned = data1[offset_samples:]
|
|
179
|
+
data2_aligned = data2
|
|
180
|
+
else:
|
|
181
|
+
data1_aligned = data1
|
|
182
|
+
data2_aligned = data2[-offset_samples:]
|
|
183
|
+
|
|
184
|
+
# Truncate to same length
|
|
185
|
+
min_len = min(len(data1_aligned), len(data2_aligned))
|
|
186
|
+
return data1_aligned[:min_len], data2_aligned[:min_len], int(offset_samples)
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def _detect_timing_differences(
|
|
190
|
+
data1: NDArray[np.float64],
|
|
191
|
+
data2: NDArray[np.float64],
|
|
192
|
+
sample_rate: float,
|
|
193
|
+
) -> list[Difference]:
|
|
194
|
+
"""Detect timing differences between aligned traces.
|
|
195
|
+
|
|
196
|
+
Args:
|
|
197
|
+
data1: First trace data.
|
|
198
|
+
data2: Second trace data.
|
|
199
|
+
sample_rate: Sample rate in Hz.
|
|
200
|
+
|
|
201
|
+
Returns:
|
|
202
|
+
List of timing differences.
|
|
203
|
+
"""
|
|
204
|
+
differences = []
|
|
205
|
+
|
|
206
|
+
# Look for timing shifts in edges
|
|
207
|
+
# Compute derivatives to find edges
|
|
208
|
+
diff1 = np.diff(data1)
|
|
209
|
+
diff2 = np.diff(data2)
|
|
210
|
+
|
|
211
|
+
# Find significant edges (> 10% of range per sample)
|
|
212
|
+
range1 = np.ptp(data1)
|
|
213
|
+
range2 = np.ptp(data2)
|
|
214
|
+
|
|
215
|
+
edge_threshold1 = range1 * 0.1
|
|
216
|
+
edge_threshold2 = range2 * 0.1
|
|
217
|
+
|
|
218
|
+
edges1 = np.where(np.abs(diff1) > edge_threshold1)[0]
|
|
219
|
+
edges2 = np.where(np.abs(diff2) > edge_threshold2)[0]
|
|
220
|
+
|
|
221
|
+
# Compare edge counts
|
|
222
|
+
if abs(len(edges1) - len(edges2)) > 2:
|
|
223
|
+
delta_edges = abs(len(edges1) - len(edges2))
|
|
224
|
+
timestamp_us = 0.0
|
|
225
|
+
|
|
226
|
+
differences.append(
|
|
227
|
+
Difference(
|
|
228
|
+
category="timing",
|
|
229
|
+
timestamp_us=timestamp_us,
|
|
230
|
+
description=f"Trace 1 has {len(edges1)} transitions while Trace 2 has {len(edges2)} transitions (difference: {delta_edges})",
|
|
231
|
+
severity="WARNING" if delta_edges > 5 else "INFO",
|
|
232
|
+
impact_score=min(1.0, delta_edges / 10.0),
|
|
233
|
+
confidence=0.90,
|
|
234
|
+
)
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
return differences
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def _detect_amplitude_differences(
|
|
241
|
+
data1: NDArray[np.float64],
|
|
242
|
+
data2: NDArray[np.float64],
|
|
243
|
+
sample_rate: float,
|
|
244
|
+
) -> list[Difference]:
|
|
245
|
+
"""Detect amplitude differences between aligned traces.
|
|
246
|
+
|
|
247
|
+
Args:
|
|
248
|
+
data1: First trace data.
|
|
249
|
+
data2: Second trace data.
|
|
250
|
+
sample_rate: Sample rate in Hz.
|
|
251
|
+
|
|
252
|
+
Returns:
|
|
253
|
+
List of amplitude differences.
|
|
254
|
+
"""
|
|
255
|
+
differences = [] # type: ignore[var-annotated]
|
|
256
|
+
|
|
257
|
+
# Compute amplitude difference
|
|
258
|
+
amp_diff = np.abs(data1 - data2)
|
|
259
|
+
ref_range = np.ptp(data2)
|
|
260
|
+
|
|
261
|
+
if ref_range == 0:
|
|
262
|
+
return differences
|
|
263
|
+
|
|
264
|
+
# Find points with significant amplitude difference
|
|
265
|
+
threshold = ref_range * 0.05 # 5% of swing
|
|
266
|
+
|
|
267
|
+
significant_diffs = np.where(amp_diff > threshold)[0]
|
|
268
|
+
|
|
269
|
+
if len(significant_diffs) > len(data1) * 0.1: # More than 10% of samples
|
|
270
|
+
max_diff_idx = np.argmax(amp_diff)
|
|
271
|
+
max_diff = amp_diff[max_diff_idx]
|
|
272
|
+
timestamp_us = max_diff_idx / sample_rate * 1e6
|
|
273
|
+
|
|
274
|
+
delta_percent = (max_diff / ref_range) * 100.0
|
|
275
|
+
|
|
276
|
+
severity = "CRITICAL" if delta_percent > 20 else "WARNING" if delta_percent > 5 else "INFO"
|
|
277
|
+
|
|
278
|
+
differences.append(
|
|
279
|
+
Difference(
|
|
280
|
+
category="amplitude",
|
|
281
|
+
timestamp_us=float(timestamp_us),
|
|
282
|
+
description=f"Voltage differs by {max_diff:.3f}V ({delta_percent:.1f}% of signal swing)",
|
|
283
|
+
severity=severity,
|
|
284
|
+
impact_score=min(1.0, delta_percent / 20.0),
|
|
285
|
+
expected_value=float(data2[max_diff_idx]),
|
|
286
|
+
actual_value=float(data1[max_diff_idx]),
|
|
287
|
+
delta_value=float(max_diff),
|
|
288
|
+
delta_percent=delta_percent,
|
|
289
|
+
confidence=0.95,
|
|
290
|
+
)
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
return differences
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
def _detect_pattern_differences(
|
|
297
|
+
data1: NDArray[np.float64],
|
|
298
|
+
data2: NDArray[np.float64],
|
|
299
|
+
sample_rate: float,
|
|
300
|
+
) -> list[Difference]:
|
|
301
|
+
"""Detect pattern differences between aligned traces.
|
|
302
|
+
|
|
303
|
+
Args:
|
|
304
|
+
data1: First trace data.
|
|
305
|
+
data2: Second trace data.
|
|
306
|
+
sample_rate: Sample rate in Hz.
|
|
307
|
+
|
|
308
|
+
Returns:
|
|
309
|
+
List of pattern differences.
|
|
310
|
+
"""
|
|
311
|
+
differences = [] # type: ignore[var-annotated]
|
|
312
|
+
|
|
313
|
+
# Compute correlation
|
|
314
|
+
if len(data1) < 2:
|
|
315
|
+
return differences
|
|
316
|
+
|
|
317
|
+
data1_norm = (data1 - np.mean(data1)) / (np.std(data1) + 1e-10)
|
|
318
|
+
data2_norm = (data2 - np.mean(data2)) / (np.std(data2) + 1e-10)
|
|
319
|
+
|
|
320
|
+
correlation = np.corrcoef(data1_norm, data2_norm)[0, 1]
|
|
321
|
+
|
|
322
|
+
if correlation < 0.95:
|
|
323
|
+
severity = "CRITICAL" if correlation < 0.8 else "WARNING" if correlation < 0.95 else "INFO"
|
|
324
|
+
|
|
325
|
+
differences.append(
|
|
326
|
+
Difference(
|
|
327
|
+
category="pattern",
|
|
328
|
+
timestamp_us=0.0,
|
|
329
|
+
description=f"Signal patterns differ (correlation: {correlation:.2f}, expected: >0.95)",
|
|
330
|
+
severity=severity,
|
|
331
|
+
impact_score=1.0 - correlation,
|
|
332
|
+
confidence=0.88,
|
|
333
|
+
)
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
return differences
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
def compare_traces(
|
|
340
|
+
trace1: WaveformTrace,
|
|
341
|
+
trace2: WaveformTrace,
|
|
342
|
+
*,
|
|
343
|
+
alignment: Literal["time", "trigger", "pattern", "auto"] = "auto",
|
|
344
|
+
difference_types: list[str] | None = None,
|
|
345
|
+
severity_threshold: str | None = None,
|
|
346
|
+
) -> TraceDiff:
|
|
347
|
+
"""Compare traces with intelligent alignment and difference detection.
|
|
348
|
+
|
|
349
|
+
Automatically aligns traces and identifies timing, amplitude, pattern,
|
|
350
|
+
and transition differences with plain-language explanations.
|
|
351
|
+
|
|
352
|
+
Args:
|
|
353
|
+
trace1: First trace (typically measured/actual).
|
|
354
|
+
trace2: Second trace (typically reference/expected).
|
|
355
|
+
alignment: Alignment method:
|
|
356
|
+
- "time": Sync to t=0
|
|
357
|
+
- "trigger": Sync to first edge (≥50% swing)
|
|
358
|
+
- "pattern": Cross-correlation alignment
|
|
359
|
+
- "auto": Try all methods, use best
|
|
360
|
+
difference_types: Types to detect (default: all).
|
|
361
|
+
severity_threshold: Only return differences at or above this level.
|
|
362
|
+
|
|
363
|
+
Returns:
|
|
364
|
+
TraceDiff with alignment method, differences, and summary.
|
|
365
|
+
|
|
366
|
+
Example:
|
|
367
|
+
>>> diff = compare_traces(measured, golden)
|
|
368
|
+
>>> for d in diff.differences[:5]:
|
|
369
|
+
... print(f"{d.severity}: {d.description}")
|
|
370
|
+
|
|
371
|
+
References:
|
|
372
|
+
DISC-004: Intelligent Trace Comparison
|
|
373
|
+
"""
|
|
374
|
+
difference_types = difference_types or [
|
|
375
|
+
"timing",
|
|
376
|
+
"amplitude",
|
|
377
|
+
"pattern",
|
|
378
|
+
"transitions",
|
|
379
|
+
]
|
|
380
|
+
|
|
381
|
+
# Try alignment methods
|
|
382
|
+
if alignment == "auto":
|
|
383
|
+
# Try all methods and pick best correlation
|
|
384
|
+
methods = ["time", "trigger", "pattern"]
|
|
385
|
+
best_corr = -1
|
|
386
|
+
best_method = "time"
|
|
387
|
+
best_aligned = None
|
|
388
|
+
|
|
389
|
+
for method in methods:
|
|
390
|
+
if method == "time":
|
|
391
|
+
d1, d2, offset = _align_time_based(trace1, trace2)
|
|
392
|
+
elif method == "trigger":
|
|
393
|
+
d1, d2, offset = _align_trigger_based(trace1, trace2)
|
|
394
|
+
else: # pattern
|
|
395
|
+
d1, d2, offset = _align_pattern_based(trace1, trace2)
|
|
396
|
+
|
|
397
|
+
# Compute correlation
|
|
398
|
+
if len(d1) > 1:
|
|
399
|
+
d1_norm = (d1 - np.mean(d1)) / (np.std(d1) + 1e-10)
|
|
400
|
+
d2_norm = (d2 - np.mean(d2)) / (np.std(d2) + 1e-10)
|
|
401
|
+
corr = np.corrcoef(d1_norm, d2_norm)[0, 1]
|
|
402
|
+
|
|
403
|
+
if corr > best_corr:
|
|
404
|
+
best_corr = corr
|
|
405
|
+
best_method = method
|
|
406
|
+
best_aligned = (d1, d2, offset)
|
|
407
|
+
|
|
408
|
+
data1, data2, offset = best_aligned # type: ignore[misc]
|
|
409
|
+
alignment_method = f"{best_method}-based"
|
|
410
|
+
else:
|
|
411
|
+
# Use specified method
|
|
412
|
+
if alignment == "time":
|
|
413
|
+
data1, data2, offset = _align_time_based(trace1, trace2)
|
|
414
|
+
elif alignment == "trigger":
|
|
415
|
+
data1, data2, offset = _align_trigger_based(trace1, trace2)
|
|
416
|
+
else: # pattern
|
|
417
|
+
data1, data2, offset = _align_pattern_based(trace1, trace2)
|
|
418
|
+
|
|
419
|
+
alignment_method = f"{alignment}-based"
|
|
420
|
+
|
|
421
|
+
sample_rate = trace1.metadata.sample_rate
|
|
422
|
+
|
|
423
|
+
# Detect differences
|
|
424
|
+
all_differences = []
|
|
425
|
+
|
|
426
|
+
if "timing" in difference_types:
|
|
427
|
+
all_differences.extend(_detect_timing_differences(data1, data2, sample_rate))
|
|
428
|
+
|
|
429
|
+
if "amplitude" in difference_types:
|
|
430
|
+
all_differences.extend(_detect_amplitude_differences(data1, data2, sample_rate))
|
|
431
|
+
|
|
432
|
+
if "pattern" in difference_types:
|
|
433
|
+
all_differences.extend(_detect_pattern_differences(data1, data2, sample_rate))
|
|
434
|
+
|
|
435
|
+
# Sort by impact score (descending)
|
|
436
|
+
all_differences.sort(key=lambda d: d.impact_score, reverse=True)
|
|
437
|
+
|
|
438
|
+
# Filter by severity threshold
|
|
439
|
+
if severity_threshold:
|
|
440
|
+
severity_order = {"INFO": 0, "WARNING": 1, "CRITICAL": 2}
|
|
441
|
+
threshold_level = severity_order.get(severity_threshold, 0)
|
|
442
|
+
|
|
443
|
+
filtered = [
|
|
444
|
+
d for d in all_differences if severity_order.get(d.severity, 0) >= threshold_level
|
|
445
|
+
]
|
|
446
|
+
all_differences = filtered
|
|
447
|
+
|
|
448
|
+
# Compute similarity score
|
|
449
|
+
if len(data1) > 1:
|
|
450
|
+
data1_norm = (data1 - np.mean(data1)) / (np.std(data1) + 1e-10)
|
|
451
|
+
data2_norm = (data2 - np.mean(data2)) / (np.std(data2) + 1e-10)
|
|
452
|
+
correlation = np.corrcoef(data1_norm, data2_norm)[0, 1]
|
|
453
|
+
similarity_score = float((correlation + 1) / 2) # Map [-1,1] to [0,1]
|
|
454
|
+
else:
|
|
455
|
+
similarity_score = 1.0 if len(data1) == 0 or data1[0] == data2[0] else 0.0
|
|
456
|
+
|
|
457
|
+
# Build summary
|
|
458
|
+
if similarity_score > 0.95:
|
|
459
|
+
summary = "Signals are very similar"
|
|
460
|
+
elif similarity_score > 0.85:
|
|
461
|
+
summary = "Signals are similar with minor differences"
|
|
462
|
+
elif similarity_score > 0.70:
|
|
463
|
+
summary = "Signals show moderate differences"
|
|
464
|
+
else:
|
|
465
|
+
summary = "Signals are significantly different"
|
|
466
|
+
|
|
467
|
+
critical_count = sum(1 for d in all_differences if d.severity == "CRITICAL")
|
|
468
|
+
warning_count = sum(1 for d in all_differences if d.severity == "WARNING")
|
|
469
|
+
|
|
470
|
+
if critical_count > 0:
|
|
471
|
+
summary += f" ({critical_count} critical issue(s))"
|
|
472
|
+
elif warning_count > 0:
|
|
473
|
+
summary += f" ({warning_count} warning(s))"
|
|
474
|
+
|
|
475
|
+
# Statistics
|
|
476
|
+
stats = {
|
|
477
|
+
"correlation": float(correlation) if len(data1) > 1 else 1.0,
|
|
478
|
+
"rms_error": float(np.sqrt(np.mean((data1 - data2) ** 2))),
|
|
479
|
+
"max_deviation": float(np.max(np.abs(data1 - data2))),
|
|
480
|
+
"max_deviation_time": float(np.argmax(np.abs(data1 - data2)) / sample_rate),
|
|
481
|
+
"avg_timing_offset": float(offset / sample_rate * 1e9), # ns
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
return TraceDiff(
|
|
485
|
+
summary=summary,
|
|
486
|
+
alignment_method=alignment_method,
|
|
487
|
+
similarity_score=similarity_score,
|
|
488
|
+
differences=all_differences,
|
|
489
|
+
stats=stats,
|
|
490
|
+
)
|
|
491
|
+
|
|
492
|
+
|
|
493
|
+
__all__ = [
|
|
494
|
+
"Difference",
|
|
495
|
+
"TraceDiff",
|
|
496
|
+
"compare_traces",
|
|
497
|
+
]
|