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,397 @@
|
|
|
1
|
+
"""Trace comparison functions for TraceKit.
|
|
2
|
+
|
|
3
|
+
This module provides functions for comparing waveform traces including
|
|
4
|
+
difference calculation, correlation, and similarity scoring.
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
Example:
|
|
8
|
+
>>> from oscura.comparison import compare_traces, similarity_score
|
|
9
|
+
>>> result = compare_traces(trace1, trace2)
|
|
10
|
+
>>> score = similarity_score(trace1, trace2)
|
|
11
|
+
|
|
12
|
+
References:
|
|
13
|
+
IEEE 181-2011: Standard for Transitional Waveform Definitions
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import warnings
|
|
19
|
+
from dataclasses import dataclass
|
|
20
|
+
from typing import TYPE_CHECKING, Literal
|
|
21
|
+
|
|
22
|
+
import numpy as np
|
|
23
|
+
from scipy import signal as sp_signal
|
|
24
|
+
from scipy import stats
|
|
25
|
+
|
|
26
|
+
from oscura.core.types import TraceMetadata, WaveformTrace
|
|
27
|
+
|
|
28
|
+
if TYPE_CHECKING:
|
|
29
|
+
from numpy.typing import NDArray
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass
|
|
33
|
+
class ComparisonResult:
|
|
34
|
+
"""Result of a trace comparison operation.
|
|
35
|
+
|
|
36
|
+
Attributes:
|
|
37
|
+
match: True if traces are considered matching.
|
|
38
|
+
similarity: Similarity score (0.0 to 1.0).
|
|
39
|
+
max_difference: Maximum absolute difference.
|
|
40
|
+
rms_difference: RMS of the difference.
|
|
41
|
+
correlation: Correlation coefficient.
|
|
42
|
+
difference_trace: Difference waveform (optional).
|
|
43
|
+
violations: Indices where difference exceeds threshold.
|
|
44
|
+
statistics: Additional comparison statistics.
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
match: bool
|
|
48
|
+
similarity: float
|
|
49
|
+
max_difference: float
|
|
50
|
+
rms_difference: float
|
|
51
|
+
correlation: float
|
|
52
|
+
difference_trace: WaveformTrace | None = None
|
|
53
|
+
violations: NDArray[np.int64] | None = None
|
|
54
|
+
statistics: dict | None = None # type: ignore[type-arg]
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def difference(
|
|
58
|
+
trace1: WaveformTrace,
|
|
59
|
+
trace2: WaveformTrace,
|
|
60
|
+
*,
|
|
61
|
+
normalize: bool = False,
|
|
62
|
+
channel_name: str | None = None,
|
|
63
|
+
) -> WaveformTrace:
|
|
64
|
+
"""Compute difference between two traces.
|
|
65
|
+
|
|
66
|
+
Calculates the element-wise difference (trace1 - trace2). Traces
|
|
67
|
+
are aligned to the shorter length.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
trace1: First trace.
|
|
71
|
+
trace2: Second trace.
|
|
72
|
+
normalize: Normalize difference to percentage of reference range.
|
|
73
|
+
channel_name: Name for the result trace.
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
WaveformTrace containing the difference.
|
|
77
|
+
|
|
78
|
+
Raises:
|
|
79
|
+
ValueError: If input traces contain NaN or Inf values.
|
|
80
|
+
|
|
81
|
+
Example:
|
|
82
|
+
>>> diff = difference(measured, reference)
|
|
83
|
+
>>> max_error = np.max(np.abs(diff.data))
|
|
84
|
+
"""
|
|
85
|
+
# Get data
|
|
86
|
+
data1 = trace1.data.astype(np.float64)
|
|
87
|
+
data2 = trace2.data.astype(np.float64)
|
|
88
|
+
|
|
89
|
+
# Check for NaN/Inf values
|
|
90
|
+
if np.any(~np.isfinite(data1)) or np.any(~np.isfinite(data2)):
|
|
91
|
+
raise ValueError("Input traces contain NaN or Inf values")
|
|
92
|
+
|
|
93
|
+
# Align lengths
|
|
94
|
+
min_len = min(len(data1), len(data2))
|
|
95
|
+
data1 = data1[:min_len]
|
|
96
|
+
data2 = data2[:min_len]
|
|
97
|
+
|
|
98
|
+
# Compute difference
|
|
99
|
+
diff = data1 - data2
|
|
100
|
+
|
|
101
|
+
if normalize:
|
|
102
|
+
# Normalize to percentage of reference range
|
|
103
|
+
ref_range = np.ptp(data2)
|
|
104
|
+
if ref_range > 0:
|
|
105
|
+
diff = (diff / ref_range) * 100.0
|
|
106
|
+
|
|
107
|
+
new_metadata = TraceMetadata(
|
|
108
|
+
sample_rate=trace1.metadata.sample_rate,
|
|
109
|
+
vertical_scale=None,
|
|
110
|
+
vertical_offset=None,
|
|
111
|
+
acquisition_time=trace1.metadata.acquisition_time,
|
|
112
|
+
trigger_info=trace1.metadata.trigger_info,
|
|
113
|
+
source_file=trace1.metadata.source_file,
|
|
114
|
+
channel_name=channel_name or "difference",
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
return WaveformTrace(data=diff, metadata=new_metadata)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def correlation(
|
|
121
|
+
trace1: WaveformTrace,
|
|
122
|
+
trace2: WaveformTrace,
|
|
123
|
+
*,
|
|
124
|
+
mode: Literal["full", "same", "valid"] = "same",
|
|
125
|
+
normalize: bool = True,
|
|
126
|
+
) -> tuple[NDArray[np.float64], NDArray[np.float64]]:
|
|
127
|
+
"""Compute cross-correlation between two traces.
|
|
128
|
+
|
|
129
|
+
Calculates the cross-correlation of two waveforms, useful for
|
|
130
|
+
finding time delays and pattern matching.
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
trace1: First trace.
|
|
134
|
+
trace2: Second trace.
|
|
135
|
+
mode: Correlation mode:
|
|
136
|
+
- "full": Full correlation (length N+M-1)
|
|
137
|
+
- "same": Same length as longer input
|
|
138
|
+
- "valid": Only overlapping region
|
|
139
|
+
normalize: Normalize to correlation coefficient (-1 to 1).
|
|
140
|
+
|
|
141
|
+
Returns:
|
|
142
|
+
Tuple of (lags, correlation_values).
|
|
143
|
+
|
|
144
|
+
Example:
|
|
145
|
+
>>> lags, corr = correlation(trace1, trace2)
|
|
146
|
+
>>> delay = lags[np.argmax(corr)]
|
|
147
|
+
"""
|
|
148
|
+
data1 = trace1.data.astype(np.float64)
|
|
149
|
+
data2 = trace2.data.astype(np.float64)
|
|
150
|
+
|
|
151
|
+
if normalize:
|
|
152
|
+
# Normalize inputs
|
|
153
|
+
data1 = (data1 - np.mean(data1)) / (np.std(data1) + 1e-10)
|
|
154
|
+
data2 = (data2 - np.mean(data2)) / (np.std(data2) + 1e-10)
|
|
155
|
+
|
|
156
|
+
# Compute cross-correlation
|
|
157
|
+
corr = sp_signal.correlate(data1, data2, mode=mode)
|
|
158
|
+
|
|
159
|
+
if normalize:
|
|
160
|
+
# Normalize by length for correlation coefficient
|
|
161
|
+
corr = corr / len(data1)
|
|
162
|
+
|
|
163
|
+
# Compute lag axis in samples
|
|
164
|
+
if mode == "full":
|
|
165
|
+
lags = np.arange(-(len(data2) - 1), len(data1))
|
|
166
|
+
elif mode == "same":
|
|
167
|
+
lags = np.arange(-len(data1) // 2, len(data1) - len(data1) // 2)
|
|
168
|
+
else: # valid
|
|
169
|
+
lags = np.arange(0, len(data1) - len(data2) + 1)
|
|
170
|
+
|
|
171
|
+
return lags.astype(np.float64), corr
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def similarity_score(
|
|
175
|
+
trace1: WaveformTrace,
|
|
176
|
+
trace2: WaveformTrace,
|
|
177
|
+
*,
|
|
178
|
+
method: Literal["correlation", "rms", "mse", "cosine"] = "correlation",
|
|
179
|
+
normalize_amplitude: bool = True,
|
|
180
|
+
normalize_offset: bool = True,
|
|
181
|
+
) -> float:
|
|
182
|
+
"""Compute similarity score between two traces.
|
|
183
|
+
|
|
184
|
+
Returns a score from 0.0 (completely different) to 1.0 (identical).
|
|
185
|
+
|
|
186
|
+
Args:
|
|
187
|
+
trace1: First trace.
|
|
188
|
+
trace2: Second trace.
|
|
189
|
+
method: Similarity metric:
|
|
190
|
+
- "correlation": Pearson correlation coefficient (default)
|
|
191
|
+
- "rms": 1 - normalized RMS difference
|
|
192
|
+
- "mse": 1 - normalized mean squared error
|
|
193
|
+
- "cosine": Cosine similarity
|
|
194
|
+
normalize_amplitude: Normalize amplitude before comparison.
|
|
195
|
+
normalize_offset: Remove DC offset before comparison.
|
|
196
|
+
|
|
197
|
+
Returns:
|
|
198
|
+
Similarity score (0.0 to 1.0).
|
|
199
|
+
|
|
200
|
+
Raises:
|
|
201
|
+
ValueError: If input traces contain NaN or Inf values.
|
|
202
|
+
|
|
203
|
+
Example:
|
|
204
|
+
>>> score = similarity_score(measured, reference)
|
|
205
|
+
>>> if score > 0.95:
|
|
206
|
+
... print("Traces match")
|
|
207
|
+
"""
|
|
208
|
+
# Get data
|
|
209
|
+
data1 = trace1.data.astype(np.float64).copy()
|
|
210
|
+
data2 = trace2.data.astype(np.float64).copy()
|
|
211
|
+
|
|
212
|
+
# Check for NaN/Inf values
|
|
213
|
+
if np.any(~np.isfinite(data1)) or np.any(~np.isfinite(data2)):
|
|
214
|
+
raise ValueError("Input traces contain NaN or Inf values")
|
|
215
|
+
|
|
216
|
+
# Align lengths
|
|
217
|
+
min_len = min(len(data1), len(data2))
|
|
218
|
+
data1 = data1[:min_len]
|
|
219
|
+
data2 = data2[:min_len]
|
|
220
|
+
|
|
221
|
+
# Normalize offset (remove DC)
|
|
222
|
+
if normalize_offset:
|
|
223
|
+
data1 = data1 - np.mean(data1)
|
|
224
|
+
data2 = data2 - np.mean(data2)
|
|
225
|
+
|
|
226
|
+
# Normalize amplitude
|
|
227
|
+
if normalize_amplitude:
|
|
228
|
+
std1 = np.std(data1)
|
|
229
|
+
std2 = np.std(data2)
|
|
230
|
+
if std1 > 0:
|
|
231
|
+
data1 = data1 / std1
|
|
232
|
+
if std2 > 0:
|
|
233
|
+
data2 = data2 / std2
|
|
234
|
+
|
|
235
|
+
if method == "correlation":
|
|
236
|
+
# Pearson correlation coefficient
|
|
237
|
+
# Handle constant inputs gracefully
|
|
238
|
+
with warnings.catch_warnings():
|
|
239
|
+
warnings.filterwarnings("ignore", category=stats.ConstantInputWarning)
|
|
240
|
+
try:
|
|
241
|
+
r, _ = stats.pearsonr(data1, data2)
|
|
242
|
+
# Handle NaN result (constant traces after normalization)
|
|
243
|
+
if np.isnan(r):
|
|
244
|
+
# If both traces are constant and identical, perfect match
|
|
245
|
+
if np.allclose(data1, data2, equal_nan=False):
|
|
246
|
+
r = 1.0
|
|
247
|
+
else:
|
|
248
|
+
r = 0.0
|
|
249
|
+
except Exception:
|
|
250
|
+
r = 0.0
|
|
251
|
+
# Map from [-1, 1] to [0, 1]
|
|
252
|
+
return float((r + 1) / 2)
|
|
253
|
+
|
|
254
|
+
elif method == "rms":
|
|
255
|
+
# RMS-based similarity
|
|
256
|
+
rms_diff = np.sqrt(np.mean((data1 - data2) ** 2))
|
|
257
|
+
rms_ref = np.sqrt(np.mean(data2**2)) + 1e-10
|
|
258
|
+
return float(max(0, 1 - rms_diff / rms_ref))
|
|
259
|
+
|
|
260
|
+
elif method == "mse":
|
|
261
|
+
# MSE-based similarity
|
|
262
|
+
mse = np.mean((data1 - data2) ** 2)
|
|
263
|
+
var_ref = np.var(data2) + 1e-10
|
|
264
|
+
return float(max(0, 1 - mse / var_ref))
|
|
265
|
+
|
|
266
|
+
elif method == "cosine":
|
|
267
|
+
# Cosine similarity
|
|
268
|
+
dot = np.dot(data1, data2)
|
|
269
|
+
norm1 = np.linalg.norm(data1) + 1e-10
|
|
270
|
+
norm2 = np.linalg.norm(data2) + 1e-10
|
|
271
|
+
cosine = dot / (norm1 * norm2)
|
|
272
|
+
# Map from [-1, 1] to [0, 1]
|
|
273
|
+
return float((cosine + 1) / 2)
|
|
274
|
+
|
|
275
|
+
else:
|
|
276
|
+
raise ValueError(f"Unknown similarity method: {method}")
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def compare_traces(
|
|
280
|
+
trace1: WaveformTrace,
|
|
281
|
+
trace2: WaveformTrace,
|
|
282
|
+
*,
|
|
283
|
+
tolerance: float | None = None,
|
|
284
|
+
tolerance_pct: float | None = None,
|
|
285
|
+
method: Literal["absolute", "relative", "statistical"] = "absolute",
|
|
286
|
+
include_difference: bool = True,
|
|
287
|
+
) -> ComparisonResult:
|
|
288
|
+
"""Compare two traces and determine if they match.
|
|
289
|
+
|
|
290
|
+
Comprehensive comparison of two waveforms including difference
|
|
291
|
+
analysis, correlation, and match determination.
|
|
292
|
+
|
|
293
|
+
Args:
|
|
294
|
+
trace1: First trace (typically measured).
|
|
295
|
+
trace2: Second trace (typically reference).
|
|
296
|
+
tolerance: Absolute tolerance for matching.
|
|
297
|
+
tolerance_pct: Percentage tolerance (0-100) relative to reference range.
|
|
298
|
+
method: Comparison method:
|
|
299
|
+
- "absolute": Compare absolute values
|
|
300
|
+
- "relative": Compare relative to reference
|
|
301
|
+
- "statistical": Use statistical tests
|
|
302
|
+
include_difference: Include difference trace in result.
|
|
303
|
+
|
|
304
|
+
Returns:
|
|
305
|
+
ComparisonResult with match status and statistics.
|
|
306
|
+
|
|
307
|
+
Raises:
|
|
308
|
+
ValueError: If method is unknown.
|
|
309
|
+
|
|
310
|
+
Example:
|
|
311
|
+
>>> result = compare_traces(measured, golden, tolerance=0.01)
|
|
312
|
+
>>> if result.match:
|
|
313
|
+
... print(f"Match! Similarity: {result.similarity:.1%}")
|
|
314
|
+
"""
|
|
315
|
+
# Get data
|
|
316
|
+
data1 = trace1.data.astype(np.float64)
|
|
317
|
+
data2 = trace2.data.astype(np.float64)
|
|
318
|
+
|
|
319
|
+
# Align lengths
|
|
320
|
+
min_len = min(len(data1), len(data2))
|
|
321
|
+
data1 = data1[:min_len]
|
|
322
|
+
data2 = data2[:min_len]
|
|
323
|
+
|
|
324
|
+
# Compute difference
|
|
325
|
+
diff = data1 - data2
|
|
326
|
+
|
|
327
|
+
# Compute statistics
|
|
328
|
+
max_diff = float(np.max(np.abs(diff)))
|
|
329
|
+
rms_diff = float(np.sqrt(np.mean(diff**2)))
|
|
330
|
+
|
|
331
|
+
# Compute correlation
|
|
332
|
+
if len(data1) > 1:
|
|
333
|
+
# Handle constant inputs (e.g., DC signals) gracefully
|
|
334
|
+
with warnings.catch_warnings():
|
|
335
|
+
warnings.filterwarnings("ignore", category=stats.ConstantInputWarning)
|
|
336
|
+
try:
|
|
337
|
+
corr, _ = stats.pearsonr(data1, data2)
|
|
338
|
+
except Exception:
|
|
339
|
+
# Fallback for any correlation computation issues
|
|
340
|
+
corr = 0.0
|
|
341
|
+
else:
|
|
342
|
+
corr = 1.0 if data1[0] == data2[0] else 0.0
|
|
343
|
+
|
|
344
|
+
# Compute similarity score
|
|
345
|
+
sim_score = similarity_score(trace1, trace2)
|
|
346
|
+
|
|
347
|
+
# Determine tolerance
|
|
348
|
+
if tolerance is None and tolerance_pct is not None:
|
|
349
|
+
ref_range = float(np.ptp(data2))
|
|
350
|
+
tolerance = ref_range * tolerance_pct / 100.0
|
|
351
|
+
elif tolerance is None:
|
|
352
|
+
# Default: 1% of reference range
|
|
353
|
+
ref_range = float(np.ptp(data2))
|
|
354
|
+
tolerance = ref_range * 0.01
|
|
355
|
+
|
|
356
|
+
# Find violations
|
|
357
|
+
violations = np.where(np.abs(diff) > tolerance)[0]
|
|
358
|
+
|
|
359
|
+
# Determine match
|
|
360
|
+
if method == "absolute":
|
|
361
|
+
match = max_diff <= tolerance
|
|
362
|
+
elif method == "relative":
|
|
363
|
+
ref_range = float(np.ptp(data2)) + 1e-10
|
|
364
|
+
relative_max = max_diff / ref_range
|
|
365
|
+
match = relative_max <= (tolerance_pct or 1.0) / 100.0
|
|
366
|
+
elif method == "statistical":
|
|
367
|
+
# Use t-test for statistical matching
|
|
368
|
+
_, p_value = stats.ttest_rel(data1, data2)
|
|
369
|
+
match = p_value > 0.05 # No significant difference
|
|
370
|
+
else:
|
|
371
|
+
raise ValueError(f"Unknown method: {method}")
|
|
372
|
+
|
|
373
|
+
# Create difference trace if requested
|
|
374
|
+
diff_trace = None
|
|
375
|
+
if include_difference:
|
|
376
|
+
diff_trace = difference(trace1, trace2, channel_name="comparison_diff")
|
|
377
|
+
|
|
378
|
+
# Compute additional statistics
|
|
379
|
+
statistics = {
|
|
380
|
+
"mean_difference": float(np.mean(diff)),
|
|
381
|
+
"std_difference": float(np.std(diff)),
|
|
382
|
+
"median_difference": float(np.median(diff)),
|
|
383
|
+
"num_violations": len(violations),
|
|
384
|
+
"violation_rate": len(violations) / min_len if min_len > 0 else 0,
|
|
385
|
+
"p_value": float(stats.ttest_rel(data1, data2)[1]) if len(data1) > 1 else 1.0,
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
return ComparisonResult(
|
|
389
|
+
match=match,
|
|
390
|
+
similarity=sim_score,
|
|
391
|
+
max_difference=max_diff,
|
|
392
|
+
rms_difference=rms_diff,
|
|
393
|
+
correlation=float(corr),
|
|
394
|
+
difference_trace=diff_trace,
|
|
395
|
+
violations=violations if len(violations) > 0 else None,
|
|
396
|
+
statistics=statistics,
|
|
397
|
+
)
|