oscura 0.0.1__py3-none-any.whl → 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- oscura/__init__.py +813 -8
- oscura/__main__.py +392 -0
- oscura/analyzers/__init__.py +37 -0
- oscura/analyzers/digital/__init__.py +177 -0
- oscura/analyzers/digital/bus.py +691 -0
- oscura/analyzers/digital/clock.py +805 -0
- oscura/analyzers/digital/correlation.py +720 -0
- oscura/analyzers/digital/edges.py +632 -0
- oscura/analyzers/digital/extraction.py +413 -0
- oscura/analyzers/digital/quality.py +878 -0
- oscura/analyzers/digital/signal_quality.py +877 -0
- oscura/analyzers/digital/thresholds.py +708 -0
- oscura/analyzers/digital/timing.py +1104 -0
- oscura/analyzers/eye/__init__.py +46 -0
- oscura/analyzers/eye/diagram.py +434 -0
- oscura/analyzers/eye/metrics.py +555 -0
- oscura/analyzers/jitter/__init__.py +83 -0
- oscura/analyzers/jitter/ber.py +333 -0
- oscura/analyzers/jitter/decomposition.py +759 -0
- oscura/analyzers/jitter/measurements.py +413 -0
- oscura/analyzers/jitter/spectrum.py +220 -0
- oscura/analyzers/measurements.py +40 -0
- oscura/analyzers/packet/__init__.py +171 -0
- oscura/analyzers/packet/daq.py +1077 -0
- oscura/analyzers/packet/metrics.py +437 -0
- oscura/analyzers/packet/parser.py +327 -0
- oscura/analyzers/packet/payload.py +2156 -0
- oscura/analyzers/packet/payload_analysis.py +1312 -0
- oscura/analyzers/packet/payload_extraction.py +236 -0
- oscura/analyzers/packet/payload_patterns.py +670 -0
- oscura/analyzers/packet/stream.py +359 -0
- oscura/analyzers/patterns/__init__.py +266 -0
- oscura/analyzers/patterns/clustering.py +1036 -0
- oscura/analyzers/patterns/discovery.py +539 -0
- oscura/analyzers/patterns/learning.py +797 -0
- oscura/analyzers/patterns/matching.py +1091 -0
- oscura/analyzers/patterns/periodic.py +650 -0
- oscura/analyzers/patterns/sequences.py +767 -0
- oscura/analyzers/power/__init__.py +116 -0
- oscura/analyzers/power/ac_power.py +391 -0
- oscura/analyzers/power/basic.py +383 -0
- oscura/analyzers/power/conduction.py +314 -0
- oscura/analyzers/power/efficiency.py +297 -0
- oscura/analyzers/power/ripple.py +356 -0
- oscura/analyzers/power/soa.py +372 -0
- oscura/analyzers/power/switching.py +479 -0
- oscura/analyzers/protocol/__init__.py +150 -0
- oscura/analyzers/protocols/__init__.py +150 -0
- oscura/analyzers/protocols/base.py +500 -0
- oscura/analyzers/protocols/can.py +620 -0
- oscura/analyzers/protocols/can_fd.py +448 -0
- oscura/analyzers/protocols/flexray.py +405 -0
- oscura/analyzers/protocols/hdlc.py +399 -0
- oscura/analyzers/protocols/i2c.py +368 -0
- oscura/analyzers/protocols/i2s.py +296 -0
- oscura/analyzers/protocols/jtag.py +393 -0
- oscura/analyzers/protocols/lin.py +445 -0
- oscura/analyzers/protocols/manchester.py +333 -0
- oscura/analyzers/protocols/onewire.py +501 -0
- oscura/analyzers/protocols/spi.py +334 -0
- oscura/analyzers/protocols/swd.py +325 -0
- oscura/analyzers/protocols/uart.py +393 -0
- oscura/analyzers/protocols/usb.py +495 -0
- oscura/analyzers/signal_integrity/__init__.py +63 -0
- oscura/analyzers/signal_integrity/embedding.py +294 -0
- oscura/analyzers/signal_integrity/equalization.py +370 -0
- oscura/analyzers/signal_integrity/sparams.py +484 -0
- oscura/analyzers/spectral/__init__.py +53 -0
- oscura/analyzers/spectral/chunked.py +273 -0
- oscura/analyzers/spectral/chunked_fft.py +571 -0
- oscura/analyzers/spectral/chunked_wavelet.py +391 -0
- oscura/analyzers/spectral/fft.py +92 -0
- oscura/analyzers/statistical/__init__.py +250 -0
- oscura/analyzers/statistical/checksum.py +923 -0
- oscura/analyzers/statistical/chunked_corr.py +228 -0
- oscura/analyzers/statistical/classification.py +778 -0
- oscura/analyzers/statistical/entropy.py +1113 -0
- oscura/analyzers/statistical/ngrams.py +614 -0
- oscura/analyzers/statistics/__init__.py +119 -0
- oscura/analyzers/statistics/advanced.py +885 -0
- oscura/analyzers/statistics/basic.py +263 -0
- oscura/analyzers/statistics/correlation.py +630 -0
- oscura/analyzers/statistics/distribution.py +298 -0
- oscura/analyzers/statistics/outliers.py +463 -0
- oscura/analyzers/statistics/streaming.py +93 -0
- oscura/analyzers/statistics/trend.py +520 -0
- oscura/analyzers/validation.py +598 -0
- oscura/analyzers/waveform/__init__.py +36 -0
- oscura/analyzers/waveform/measurements.py +943 -0
- oscura/analyzers/waveform/measurements_with_uncertainty.py +371 -0
- oscura/analyzers/waveform/spectral.py +1689 -0
- oscura/analyzers/waveform/wavelets.py +298 -0
- oscura/api/__init__.py +62 -0
- oscura/api/dsl.py +538 -0
- oscura/api/fluent.py +571 -0
- oscura/api/operators.py +498 -0
- oscura/api/optimization.py +392 -0
- oscura/api/profiling.py +396 -0
- oscura/automotive/__init__.py +73 -0
- oscura/automotive/can/__init__.py +52 -0
- oscura/automotive/can/analysis.py +356 -0
- oscura/automotive/can/checksum.py +250 -0
- oscura/automotive/can/correlation.py +212 -0
- oscura/automotive/can/discovery.py +355 -0
- oscura/automotive/can/message_wrapper.py +375 -0
- oscura/automotive/can/models.py +385 -0
- oscura/automotive/can/patterns.py +381 -0
- oscura/automotive/can/session.py +452 -0
- oscura/automotive/can/state_machine.py +300 -0
- oscura/automotive/can/stimulus_response.py +461 -0
- oscura/automotive/dbc/__init__.py +15 -0
- oscura/automotive/dbc/generator.py +156 -0
- oscura/automotive/dbc/parser.py +146 -0
- oscura/automotive/dtc/__init__.py +30 -0
- oscura/automotive/dtc/database.py +3036 -0
- oscura/automotive/j1939/__init__.py +14 -0
- oscura/automotive/j1939/decoder.py +745 -0
- oscura/automotive/loaders/__init__.py +35 -0
- oscura/automotive/loaders/asc.py +98 -0
- oscura/automotive/loaders/blf.py +77 -0
- oscura/automotive/loaders/csv_can.py +136 -0
- oscura/automotive/loaders/dispatcher.py +136 -0
- oscura/automotive/loaders/mdf.py +331 -0
- oscura/automotive/loaders/pcap.py +132 -0
- oscura/automotive/obd/__init__.py +14 -0
- oscura/automotive/obd/decoder.py +707 -0
- oscura/automotive/uds/__init__.py +48 -0
- oscura/automotive/uds/decoder.py +265 -0
- oscura/automotive/uds/models.py +64 -0
- oscura/automotive/visualization.py +369 -0
- oscura/batch/__init__.py +55 -0
- oscura/batch/advanced.py +627 -0
- oscura/batch/aggregate.py +300 -0
- oscura/batch/analyze.py +139 -0
- oscura/batch/logging.py +487 -0
- oscura/batch/metrics.py +556 -0
- oscura/builders/__init__.py +41 -0
- oscura/builders/signal_builder.py +1131 -0
- oscura/cli/__init__.py +14 -0
- oscura/cli/batch.py +339 -0
- oscura/cli/characterize.py +273 -0
- oscura/cli/compare.py +775 -0
- oscura/cli/decode.py +551 -0
- oscura/cli/main.py +247 -0
- oscura/cli/shell.py +350 -0
- oscura/comparison/__init__.py +66 -0
- oscura/comparison/compare.py +397 -0
- oscura/comparison/golden.py +487 -0
- oscura/comparison/limits.py +391 -0
- oscura/comparison/mask.py +434 -0
- oscura/comparison/trace_diff.py +30 -0
- oscura/comparison/visualization.py +481 -0
- oscura/compliance/__init__.py +70 -0
- oscura/compliance/advanced.py +756 -0
- oscura/compliance/masks.py +363 -0
- oscura/compliance/reporting.py +483 -0
- oscura/compliance/testing.py +298 -0
- oscura/component/__init__.py +38 -0
- oscura/component/impedance.py +365 -0
- oscura/component/reactive.py +598 -0
- oscura/component/transmission_line.py +312 -0
- oscura/config/__init__.py +191 -0
- oscura/config/defaults.py +254 -0
- oscura/config/loader.py +348 -0
- oscura/config/memory.py +271 -0
- oscura/config/migration.py +458 -0
- oscura/config/pipeline.py +1077 -0
- oscura/config/preferences.py +530 -0
- oscura/config/protocol.py +875 -0
- oscura/config/schema.py +713 -0
- oscura/config/settings.py +420 -0
- oscura/config/thresholds.py +599 -0
- oscura/convenience.py +457 -0
- oscura/core/__init__.py +299 -0
- oscura/core/audit.py +457 -0
- oscura/core/backend_selector.py +405 -0
- oscura/core/cache.py +590 -0
- oscura/core/cancellation.py +439 -0
- oscura/core/confidence.py +225 -0
- oscura/core/config.py +506 -0
- oscura/core/correlation.py +216 -0
- oscura/core/cross_domain.py +422 -0
- oscura/core/debug.py +301 -0
- oscura/core/edge_cases.py +541 -0
- oscura/core/exceptions.py +535 -0
- oscura/core/gpu_backend.py +523 -0
- oscura/core/lazy.py +832 -0
- oscura/core/log_query.py +540 -0
- oscura/core/logging.py +931 -0
- oscura/core/logging_advanced.py +952 -0
- oscura/core/memoize.py +171 -0
- oscura/core/memory_check.py +274 -0
- oscura/core/memory_guard.py +290 -0
- oscura/core/memory_limits.py +336 -0
- oscura/core/memory_monitor.py +453 -0
- oscura/core/memory_progress.py +465 -0
- oscura/core/memory_warnings.py +315 -0
- oscura/core/numba_backend.py +362 -0
- oscura/core/performance.py +352 -0
- oscura/core/progress.py +524 -0
- oscura/core/provenance.py +358 -0
- oscura/core/results.py +331 -0
- oscura/core/types.py +504 -0
- oscura/core/uncertainty.py +383 -0
- oscura/discovery/__init__.py +52 -0
- oscura/discovery/anomaly_detector.py +672 -0
- oscura/discovery/auto_decoder.py +415 -0
- oscura/discovery/comparison.py +497 -0
- oscura/discovery/quality_validator.py +528 -0
- oscura/discovery/signal_detector.py +769 -0
- oscura/dsl/__init__.py +73 -0
- oscura/dsl/commands.py +246 -0
- oscura/dsl/interpreter.py +455 -0
- oscura/dsl/parser.py +689 -0
- oscura/dsl/repl.py +172 -0
- oscura/exceptions.py +59 -0
- oscura/exploratory/__init__.py +111 -0
- oscura/exploratory/error_recovery.py +642 -0
- oscura/exploratory/fuzzy.py +513 -0
- oscura/exploratory/fuzzy_advanced.py +786 -0
- oscura/exploratory/legacy.py +831 -0
- oscura/exploratory/parse.py +358 -0
- oscura/exploratory/recovery.py +275 -0
- oscura/exploratory/sync.py +382 -0
- oscura/exploratory/unknown.py +707 -0
- oscura/export/__init__.py +25 -0
- oscura/export/wireshark/README.md +265 -0
- oscura/export/wireshark/__init__.py +47 -0
- oscura/export/wireshark/generator.py +312 -0
- oscura/export/wireshark/lua_builder.py +159 -0
- oscura/export/wireshark/templates/dissector.lua.j2 +92 -0
- oscura/export/wireshark/type_mapping.py +165 -0
- oscura/export/wireshark/validator.py +105 -0
- oscura/exporters/__init__.py +94 -0
- oscura/exporters/csv.py +303 -0
- oscura/exporters/exporters.py +44 -0
- oscura/exporters/hdf5.py +219 -0
- oscura/exporters/html_export.py +701 -0
- oscura/exporters/json_export.py +291 -0
- oscura/exporters/markdown_export.py +367 -0
- oscura/exporters/matlab_export.py +354 -0
- oscura/exporters/npz_export.py +219 -0
- oscura/exporters/spice_export.py +210 -0
- oscura/extensibility/__init__.py +131 -0
- oscura/extensibility/docs.py +752 -0
- oscura/extensibility/extensions.py +1125 -0
- oscura/extensibility/logging.py +259 -0
- oscura/extensibility/measurements.py +485 -0
- oscura/extensibility/plugins.py +414 -0
- oscura/extensibility/registry.py +346 -0
- oscura/extensibility/templates.py +913 -0
- oscura/extensibility/validation.py +651 -0
- oscura/filtering/__init__.py +89 -0
- oscura/filtering/base.py +563 -0
- oscura/filtering/convenience.py +564 -0
- oscura/filtering/design.py +725 -0
- oscura/filtering/filters.py +32 -0
- oscura/filtering/introspection.py +605 -0
- oscura/guidance/__init__.py +24 -0
- oscura/guidance/recommender.py +429 -0
- oscura/guidance/wizard.py +518 -0
- oscura/inference/__init__.py +251 -0
- oscura/inference/active_learning/README.md +153 -0
- oscura/inference/active_learning/__init__.py +38 -0
- oscura/inference/active_learning/lstar.py +257 -0
- oscura/inference/active_learning/observation_table.py +230 -0
- oscura/inference/active_learning/oracle.py +78 -0
- oscura/inference/active_learning/teachers/__init__.py +15 -0
- oscura/inference/active_learning/teachers/simulator.py +192 -0
- oscura/inference/adaptive_tuning.py +453 -0
- oscura/inference/alignment.py +653 -0
- oscura/inference/bayesian.py +943 -0
- oscura/inference/binary.py +1016 -0
- oscura/inference/crc_reverse.py +711 -0
- oscura/inference/logic.py +288 -0
- oscura/inference/message_format.py +1305 -0
- oscura/inference/protocol.py +417 -0
- oscura/inference/protocol_dsl.py +1084 -0
- oscura/inference/protocol_library.py +1230 -0
- oscura/inference/sequences.py +809 -0
- oscura/inference/signal_intelligence.py +1509 -0
- oscura/inference/spectral.py +215 -0
- oscura/inference/state_machine.py +634 -0
- oscura/inference/stream.py +918 -0
- oscura/integrations/__init__.py +59 -0
- oscura/integrations/llm.py +1827 -0
- oscura/jupyter/__init__.py +32 -0
- oscura/jupyter/display.py +268 -0
- oscura/jupyter/magic.py +334 -0
- oscura/loaders/__init__.py +526 -0
- oscura/loaders/binary.py +69 -0
- oscura/loaders/configurable.py +1255 -0
- oscura/loaders/csv.py +26 -0
- oscura/loaders/csv_loader.py +473 -0
- oscura/loaders/hdf5.py +9 -0
- oscura/loaders/hdf5_loader.py +510 -0
- oscura/loaders/lazy.py +370 -0
- oscura/loaders/mmap_loader.py +583 -0
- oscura/loaders/numpy_loader.py +436 -0
- oscura/loaders/pcap.py +432 -0
- oscura/loaders/preprocessing.py +368 -0
- oscura/loaders/rigol.py +287 -0
- oscura/loaders/sigrok.py +321 -0
- oscura/loaders/tdms.py +367 -0
- oscura/loaders/tektronix.py +711 -0
- oscura/loaders/validation.py +584 -0
- oscura/loaders/vcd.py +464 -0
- oscura/loaders/wav.py +233 -0
- oscura/math/__init__.py +45 -0
- oscura/math/arithmetic.py +824 -0
- oscura/math/interpolation.py +413 -0
- oscura/onboarding/__init__.py +39 -0
- oscura/onboarding/help.py +498 -0
- oscura/onboarding/tutorials.py +405 -0
- oscura/onboarding/wizard.py +466 -0
- oscura/optimization/__init__.py +19 -0
- oscura/optimization/parallel.py +440 -0
- oscura/optimization/search.py +532 -0
- oscura/pipeline/__init__.py +43 -0
- oscura/pipeline/base.py +338 -0
- oscura/pipeline/composition.py +242 -0
- oscura/pipeline/parallel.py +448 -0
- oscura/pipeline/pipeline.py +375 -0
- oscura/pipeline/reverse_engineering.py +1119 -0
- oscura/plugins/__init__.py +122 -0
- oscura/plugins/base.py +272 -0
- oscura/plugins/cli.py +497 -0
- oscura/plugins/discovery.py +411 -0
- oscura/plugins/isolation.py +418 -0
- oscura/plugins/lifecycle.py +959 -0
- oscura/plugins/manager.py +493 -0
- oscura/plugins/registry.py +421 -0
- oscura/plugins/versioning.py +372 -0
- oscura/py.typed +0 -0
- oscura/quality/__init__.py +65 -0
- oscura/quality/ensemble.py +740 -0
- oscura/quality/explainer.py +338 -0
- oscura/quality/scoring.py +616 -0
- oscura/quality/warnings.py +456 -0
- oscura/reporting/__init__.py +248 -0
- oscura/reporting/advanced.py +1234 -0
- oscura/reporting/analyze.py +448 -0
- oscura/reporting/argument_preparer.py +596 -0
- oscura/reporting/auto_report.py +507 -0
- oscura/reporting/batch.py +615 -0
- oscura/reporting/chart_selection.py +223 -0
- oscura/reporting/comparison.py +330 -0
- oscura/reporting/config.py +615 -0
- oscura/reporting/content/__init__.py +39 -0
- oscura/reporting/content/executive.py +127 -0
- oscura/reporting/content/filtering.py +191 -0
- oscura/reporting/content/minimal.py +257 -0
- oscura/reporting/content/verbosity.py +162 -0
- oscura/reporting/core.py +508 -0
- oscura/reporting/core_formats/__init__.py +17 -0
- oscura/reporting/core_formats/multi_format.py +210 -0
- oscura/reporting/engine.py +836 -0
- oscura/reporting/export.py +366 -0
- oscura/reporting/formatting/__init__.py +129 -0
- oscura/reporting/formatting/emphasis.py +81 -0
- oscura/reporting/formatting/numbers.py +403 -0
- oscura/reporting/formatting/standards.py +55 -0
- oscura/reporting/formatting.py +466 -0
- oscura/reporting/html.py +578 -0
- oscura/reporting/index.py +590 -0
- oscura/reporting/multichannel.py +296 -0
- oscura/reporting/output.py +379 -0
- oscura/reporting/pdf.py +373 -0
- oscura/reporting/plots.py +731 -0
- oscura/reporting/pptx_export.py +360 -0
- oscura/reporting/renderers/__init__.py +11 -0
- oscura/reporting/renderers/pdf.py +94 -0
- oscura/reporting/sections.py +471 -0
- oscura/reporting/standards.py +680 -0
- oscura/reporting/summary_generator.py +368 -0
- oscura/reporting/tables.py +397 -0
- oscura/reporting/template_system.py +724 -0
- oscura/reporting/templates/__init__.py +15 -0
- oscura/reporting/templates/definition.py +205 -0
- oscura/reporting/templates/index.html +649 -0
- oscura/reporting/templates/index.md +173 -0
- oscura/schemas/__init__.py +158 -0
- oscura/schemas/bus_configuration.json +322 -0
- oscura/schemas/device_mapping.json +182 -0
- oscura/schemas/packet_format.json +418 -0
- oscura/schemas/protocol_definition.json +363 -0
- oscura/search/__init__.py +16 -0
- oscura/search/anomaly.py +292 -0
- oscura/search/context.py +149 -0
- oscura/search/pattern.py +160 -0
- oscura/session/__init__.py +34 -0
- oscura/session/annotations.py +289 -0
- oscura/session/history.py +313 -0
- oscura/session/session.py +445 -0
- oscura/streaming/__init__.py +43 -0
- oscura/streaming/chunked.py +611 -0
- oscura/streaming/progressive.py +393 -0
- oscura/streaming/realtime.py +622 -0
- oscura/testing/__init__.py +54 -0
- oscura/testing/synthetic.py +808 -0
- oscura/triggering/__init__.py +68 -0
- oscura/triggering/base.py +229 -0
- oscura/triggering/edge.py +353 -0
- oscura/triggering/pattern.py +344 -0
- oscura/triggering/pulse.py +581 -0
- oscura/triggering/window.py +453 -0
- oscura/ui/__init__.py +48 -0
- oscura/ui/formatters.py +526 -0
- oscura/ui/progressive_display.py +340 -0
- oscura/utils/__init__.py +99 -0
- oscura/utils/autodetect.py +338 -0
- oscura/utils/buffer.py +389 -0
- oscura/utils/lazy.py +407 -0
- oscura/utils/lazy_imports.py +147 -0
- oscura/utils/memory.py +836 -0
- oscura/utils/memory_advanced.py +1326 -0
- oscura/utils/memory_extensions.py +465 -0
- oscura/utils/progressive.py +352 -0
- oscura/utils/windowing.py +362 -0
- oscura/visualization/__init__.py +321 -0
- oscura/visualization/accessibility.py +526 -0
- oscura/visualization/annotations.py +374 -0
- oscura/visualization/axis_scaling.py +305 -0
- oscura/visualization/colors.py +453 -0
- oscura/visualization/digital.py +337 -0
- oscura/visualization/eye.py +420 -0
- oscura/visualization/histogram.py +281 -0
- oscura/visualization/interactive.py +858 -0
- oscura/visualization/jitter.py +702 -0
- oscura/visualization/keyboard.py +394 -0
- oscura/visualization/layout.py +365 -0
- oscura/visualization/optimization.py +1028 -0
- oscura/visualization/palettes.py +446 -0
- oscura/visualization/plot.py +92 -0
- oscura/visualization/power.py +290 -0
- oscura/visualization/power_extended.py +626 -0
- oscura/visualization/presets.py +467 -0
- oscura/visualization/protocols.py +932 -0
- oscura/visualization/render.py +207 -0
- oscura/visualization/rendering.py +444 -0
- oscura/visualization/reverse_engineering.py +791 -0
- oscura/visualization/signal_integrity.py +808 -0
- oscura/visualization/specialized.py +553 -0
- oscura/visualization/spectral.py +811 -0
- oscura/visualization/styles.py +381 -0
- oscura/visualization/thumbnails.py +311 -0
- oscura/visualization/time_axis.py +351 -0
- oscura/visualization/waveform.py +367 -0
- oscura/workflow/__init__.py +13 -0
- oscura/workflow/dag.py +377 -0
- oscura/workflows/__init__.py +58 -0
- oscura/workflows/compliance.py +280 -0
- oscura/workflows/digital.py +272 -0
- oscura/workflows/multi_trace.py +502 -0
- oscura/workflows/power.py +178 -0
- oscura/workflows/protocol.py +492 -0
- oscura/workflows/reverse_engineering.py +639 -0
- oscura/workflows/signal_integrity.py +227 -0
- oscura-0.1.0.dist-info/METADATA +300 -0
- oscura-0.1.0.dist-info/RECORD +463 -0
- oscura-0.1.0.dist-info/entry_points.txt +2 -0
- {oscura-0.0.1.dist-info → oscura-0.1.0.dist-info}/licenses/LICENSE +1 -1
- oscura-0.0.1.dist-info/METADATA +0 -63
- oscura-0.0.1.dist-info/RECORD +0 -5
- {oscura-0.0.1.dist-info → oscura-0.1.0.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
"""Error-tolerant protocol parsing with timestamp correction.
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
This module provides robust protocol decoding that continues after errors
|
|
5
|
+
and timestamp correction for jittery captures.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
from enum import Enum
|
|
10
|
+
from typing import Any, Literal
|
|
11
|
+
|
|
12
|
+
import numpy as np
|
|
13
|
+
from numpy.typing import NDArray
|
|
14
|
+
from scipy import signal
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ErrorTolerance(Enum):
|
|
18
|
+
"""Error tolerance modes for protocol decoding.
|
|
19
|
+
|
|
20
|
+
Attributes:
|
|
21
|
+
STRICT: Abort on first error (backward compatible)
|
|
22
|
+
TOLERANT: Skip error frame, resync, continue (default)
|
|
23
|
+
PERMISSIVE: Best-effort decode, report all errors
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
STRICT = "strict"
|
|
27
|
+
TOLERANT = "tolerant"
|
|
28
|
+
PERMISSIVE = "permissive"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclass
|
|
32
|
+
class DecodedFrame:
|
|
33
|
+
"""Decoded protocol frame with error annotation.
|
|
34
|
+
|
|
35
|
+
Attributes:
|
|
36
|
+
data: Decoded data bytes
|
|
37
|
+
timestamp: Frame timestamp in seconds
|
|
38
|
+
valid: Whether frame is valid or has errors
|
|
39
|
+
error_type: Type of error if invalid (e.g., 'framing', 'parity')
|
|
40
|
+
position: Byte position in original trace
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
data: bytes
|
|
44
|
+
timestamp: float
|
|
45
|
+
valid: bool
|
|
46
|
+
error_type: str | None
|
|
47
|
+
position: int
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@dataclass
|
|
51
|
+
class TimestampCorrection:
|
|
52
|
+
"""Result from timestamp jitter correction.
|
|
53
|
+
|
|
54
|
+
Attributes:
|
|
55
|
+
corrected_timestamps: Array of corrected timestamps
|
|
56
|
+
original_jitter_rms: RMS jitter before correction
|
|
57
|
+
corrected_jitter_rms: RMS jitter after correction
|
|
58
|
+
reduction_ratio: Jitter reduction factor (before/after)
|
|
59
|
+
samples_corrected: Number of samples that were adjusted
|
|
60
|
+
max_correction: Maximum correction applied to any sample
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
corrected_timestamps: NDArray[np.float64]
|
|
64
|
+
original_jitter_rms: float
|
|
65
|
+
corrected_jitter_rms: float
|
|
66
|
+
reduction_ratio: float
|
|
67
|
+
samples_corrected: int
|
|
68
|
+
max_correction: float
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def correct_timestamp_jitter(
|
|
72
|
+
timestamps: NDArray[np.float64],
|
|
73
|
+
expected_rate: float,
|
|
74
|
+
*,
|
|
75
|
+
method: Literal["lowpass", "pll"] = "lowpass",
|
|
76
|
+
max_correction_factor: float = 2.0,
|
|
77
|
+
) -> TimestampCorrection:
|
|
78
|
+
"""Correct timestamp jitter using filtering or PLL model.
|
|
79
|
+
|
|
80
|
+
: Compensates for clock jitter in logic analyzer
|
|
81
|
+
captures (e.g., USB transmission jitter) while preserving phase.
|
|
82
|
+
|
|
83
|
+
Correction constraints (DAQ-003):
|
|
84
|
+
- Max correction per sample: ±max_correction_factor × expected_period # noqa: RUF002, RUF003
|
|
85
|
+
- Filter cutoff: expected_rate / 10 (removes 10× jitter frequency) # noqa: RUF002, RUF003
|
|
86
|
+
- Target reduction: ≥5× for typical USB jitter # noqa: RUF002, RUF003
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
timestamps: Original jittery timestamps in seconds
|
|
90
|
+
expected_rate: Expected nominal sample rate in Hz
|
|
91
|
+
method: Correction method ('lowpass' or 'pll')
|
|
92
|
+
max_correction_factor: Max correction as multiple of period
|
|
93
|
+
|
|
94
|
+
Returns:
|
|
95
|
+
TimestampCorrection with corrected timestamps and metrics
|
|
96
|
+
|
|
97
|
+
Raises:
|
|
98
|
+
ValueError: If timestamps array is empty
|
|
99
|
+
ValueError: If expected_rate <= 0
|
|
100
|
+
ValueError: If max_correction_factor <= 0
|
|
101
|
+
|
|
102
|
+
Examples:
|
|
103
|
+
>>> # Correct jittery timestamps from USB logic analyzer
|
|
104
|
+
>>> import numpy as np
|
|
105
|
+
>>> timestamps = np.linspace(0, 1e-3, 1000)
|
|
106
|
+
>>> jitter = np.random.normal(0, 1e-7, 1000) # 100ns jitter
|
|
107
|
+
>>> jittery = timestamps + jitter
|
|
108
|
+
>>> result = correct_timestamp_jitter(jittery, expected_rate=1e6)
|
|
109
|
+
>>> print(f"Jitter reduced by {result.reduction_ratio:.1f}x")
|
|
110
|
+
|
|
111
|
+
References:
|
|
112
|
+
DAQ-003: Timestamp Jitter Compensation and Clock Correction
|
|
113
|
+
"""
|
|
114
|
+
if len(timestamps) == 0:
|
|
115
|
+
raise ValueError("Timestamps array cannot be empty")
|
|
116
|
+
|
|
117
|
+
if expected_rate <= 0:
|
|
118
|
+
raise ValueError("expected_rate must be positive")
|
|
119
|
+
|
|
120
|
+
if max_correction_factor <= 0:
|
|
121
|
+
raise ValueError("max_correction_factor must be positive")
|
|
122
|
+
|
|
123
|
+
if len(timestamps) < 3:
|
|
124
|
+
# Not enough data to filter
|
|
125
|
+
return TimestampCorrection(
|
|
126
|
+
corrected_timestamps=timestamps.copy(),
|
|
127
|
+
original_jitter_rms=0.0,
|
|
128
|
+
corrected_jitter_rms=0.0,
|
|
129
|
+
reduction_ratio=1.0,
|
|
130
|
+
samples_corrected=0,
|
|
131
|
+
max_correction=0.0,
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
expected_period = 1.0 / expected_rate
|
|
135
|
+
max_correction = max_correction_factor * expected_period
|
|
136
|
+
|
|
137
|
+
# Calculate original jitter
|
|
138
|
+
diffs = np.diff(timestamps)
|
|
139
|
+
original_jitter = diffs - expected_period
|
|
140
|
+
original_jitter_rms = float(np.sqrt(np.mean(original_jitter**2)))
|
|
141
|
+
|
|
142
|
+
# If jitter is negligible (below 1 ns), no correction needed
|
|
143
|
+
# This avoids correcting floating-point rounding errors in perfect timestamps
|
|
144
|
+
if original_jitter_rms < 1e-9:
|
|
145
|
+
return TimestampCorrection(
|
|
146
|
+
corrected_timestamps=timestamps.copy(),
|
|
147
|
+
original_jitter_rms=original_jitter_rms,
|
|
148
|
+
corrected_jitter_rms=original_jitter_rms,
|
|
149
|
+
reduction_ratio=1.0,
|
|
150
|
+
samples_corrected=0,
|
|
151
|
+
max_correction=0.0,
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
if method == "lowpass":
|
|
155
|
+
# Low-pass filter approach
|
|
156
|
+
# Design Butterworth filter: cutoff at expected_rate / 10
|
|
157
|
+
cutoff_freq = expected_rate / 10.0
|
|
158
|
+
nyquist = 0.5 * expected_rate
|
|
159
|
+
|
|
160
|
+
# Ensure cutoff is valid
|
|
161
|
+
if cutoff_freq >= nyquist:
|
|
162
|
+
cutoff_freq = nyquist * 0.8
|
|
163
|
+
|
|
164
|
+
# Design 2nd order Butterworth
|
|
165
|
+
sos = signal.butter(2, cutoff_freq / nyquist, btype="low", output="sos")
|
|
166
|
+
|
|
167
|
+
# Filter the timestamps
|
|
168
|
+
# Need to detrend first to avoid edge effects
|
|
169
|
+
t_mean = np.mean(timestamps)
|
|
170
|
+
t_detrended = timestamps - t_mean
|
|
171
|
+
|
|
172
|
+
# Apply filter
|
|
173
|
+
filtered = signal.sosfiltfilt(sos, t_detrended)
|
|
174
|
+
corrected = filtered + t_mean
|
|
175
|
+
|
|
176
|
+
else: # pll
|
|
177
|
+
# Phase-locked loop model
|
|
178
|
+
# Simple PLL: track expected phase and correct deviations
|
|
179
|
+
corrected = np.zeros_like(timestamps)
|
|
180
|
+
corrected[0] = timestamps[0]
|
|
181
|
+
|
|
182
|
+
# PLL state
|
|
183
|
+
phase = 0.0
|
|
184
|
+
phase_increment = 2 * np.pi * expected_rate
|
|
185
|
+
|
|
186
|
+
for i in range(1, len(timestamps)):
|
|
187
|
+
# Predict next timestamp based on expected rate
|
|
188
|
+
predicted = corrected[i - 1] + expected_period
|
|
189
|
+
|
|
190
|
+
# Measure phase error
|
|
191
|
+
actual = timestamps[i]
|
|
192
|
+
error = actual - predicted
|
|
193
|
+
|
|
194
|
+
# Apply correction with limiting
|
|
195
|
+
correction = np.clip(error * 0.5, -max_correction, max_correction)
|
|
196
|
+
corrected[i] = predicted + correction
|
|
197
|
+
|
|
198
|
+
# Update phase
|
|
199
|
+
phase += phase_increment * (corrected[i] - corrected[i - 1])
|
|
200
|
+
|
|
201
|
+
# Limit corrections to max_correction
|
|
202
|
+
corrections = corrected - timestamps
|
|
203
|
+
exceeded = np.abs(corrections) > max_correction
|
|
204
|
+
corrections[exceeded] = np.sign(corrections[exceeded]) * max_correction
|
|
205
|
+
corrected = timestamps + corrections
|
|
206
|
+
|
|
207
|
+
# Calculate corrected jitter
|
|
208
|
+
corrected_diffs = np.diff(corrected)
|
|
209
|
+
corrected_jitter = corrected_diffs - expected_period
|
|
210
|
+
corrected_jitter_rms = float(np.sqrt(np.mean(corrected_jitter**2)))
|
|
211
|
+
|
|
212
|
+
# Calculate metrics
|
|
213
|
+
samples_corrected = int(np.sum(np.abs(corrections) > 1e-12))
|
|
214
|
+
max_correction_applied = float(np.max(np.abs(corrections)))
|
|
215
|
+
|
|
216
|
+
# original_jitter_rms is always > 0 here (early return handles negligible jitter)
|
|
217
|
+
reduction_ratio = original_jitter_rms / max(corrected_jitter_rms, 1e-15)
|
|
218
|
+
|
|
219
|
+
return TimestampCorrection(
|
|
220
|
+
corrected_timestamps=corrected,
|
|
221
|
+
original_jitter_rms=original_jitter_rms,
|
|
222
|
+
corrected_jitter_rms=corrected_jitter_rms,
|
|
223
|
+
reduction_ratio=reduction_ratio,
|
|
224
|
+
samples_corrected=samples_corrected,
|
|
225
|
+
max_correction=max_correction_applied,
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def decode_with_error_tolerance(
|
|
230
|
+
data: NDArray[np.uint8],
|
|
231
|
+
protocol: Literal["uart", "spi", "i2c", "can"],
|
|
232
|
+
*,
|
|
233
|
+
tolerance: ErrorTolerance = ErrorTolerance.TOLERANT,
|
|
234
|
+
**protocol_params: Any,
|
|
235
|
+
) -> list[DecodedFrame]:
|
|
236
|
+
"""Decode protocol with error tolerance and resynchronization.
|
|
237
|
+
|
|
238
|
+
: Continues decoding after framing/parity/stop-bit
|
|
239
|
+
errors instead of aborting. Applies to all protocol decoders.
|
|
240
|
+
|
|
241
|
+
Error tolerance modes (DAQ-004):
|
|
242
|
+
- STRICT: Abort on first error (backward compatible)
|
|
243
|
+
- TOLERANT: Skip error frame, resync, continue (default)
|
|
244
|
+
- PERMISSIVE: Best-effort decode, report all errors
|
|
245
|
+
|
|
246
|
+
Resynchronization strategies (DAQ-004):
|
|
247
|
+
- UART: Search for next valid start bit + stop bit pattern
|
|
248
|
+
- SPI: Re-align on next CS edge
|
|
249
|
+
- I2C: Search for next START condition
|
|
250
|
+
- CAN: Wait for recessive bus + next SOF
|
|
251
|
+
|
|
252
|
+
Args:
|
|
253
|
+
data: Raw protocol data bytes
|
|
254
|
+
protocol: Protocol type ('uart', 'spi', 'i2c', 'can')
|
|
255
|
+
tolerance: Error tolerance mode
|
|
256
|
+
**protocol_params: Protocol-specific parameters (baud, parity, etc.)
|
|
257
|
+
|
|
258
|
+
Returns:
|
|
259
|
+
List of DecodedFrame objects with data and error annotations
|
|
260
|
+
|
|
261
|
+
Raises:
|
|
262
|
+
ValueError: If protocol not supported
|
|
263
|
+
ValueError: If required protocol_params missing
|
|
264
|
+
Exception: Re-raised in STRICT mode if decoding fails
|
|
265
|
+
|
|
266
|
+
Examples:
|
|
267
|
+
>>> # Decode UART with error tolerance
|
|
268
|
+
>>> data = np.array([0xFF, 0x55, 0xAA, 0x00], dtype=np.uint8)
|
|
269
|
+
>>> frames = decode_with_error_tolerance(
|
|
270
|
+
... data, 'uart', tolerance=ErrorTolerance.TOLERANT, baud=9600
|
|
271
|
+
... )
|
|
272
|
+
>>> valid_frames = [f for f in frames if f.valid]
|
|
273
|
+
|
|
274
|
+
References:
|
|
275
|
+
DAQ-004: Error-Tolerant Protocol Decoding with Resynchronization
|
|
276
|
+
"""
|
|
277
|
+
if protocol not in ("uart", "spi", "i2c", "can"):
|
|
278
|
+
raise ValueError(f"Unsupported protocol: {protocol}")
|
|
279
|
+
|
|
280
|
+
frames: list[DecodedFrame] = []
|
|
281
|
+
pos = 0
|
|
282
|
+
|
|
283
|
+
# Protocol-specific decode logic
|
|
284
|
+
# This is a simplified implementation showing the error handling pattern
|
|
285
|
+
# Full protocol decoders are in oscura.analyzers.protocols
|
|
286
|
+
|
|
287
|
+
if protocol == "uart":
|
|
288
|
+
# UART parameters
|
|
289
|
+
if "baud" not in protocol_params:
|
|
290
|
+
raise ValueError("UART requires 'baud' parameter")
|
|
291
|
+
|
|
292
|
+
# Simplified UART frame extraction with error tolerance
|
|
293
|
+
while pos < len(data):
|
|
294
|
+
try:
|
|
295
|
+
# Try to decode frame at current position
|
|
296
|
+
# This is simplified - real UART decoder would analyze bit timing
|
|
297
|
+
|
|
298
|
+
# Check for valid frame (simplified)
|
|
299
|
+
if pos + 1 >= len(data):
|
|
300
|
+
break
|
|
301
|
+
|
|
302
|
+
frame_data = bytes([data[pos]])
|
|
303
|
+
timestamp = float(pos) / protocol_params["baud"]
|
|
304
|
+
|
|
305
|
+
# Validate frame (simplified - would check start/stop bits)
|
|
306
|
+
is_valid = True
|
|
307
|
+
error_type = None
|
|
308
|
+
|
|
309
|
+
# Example: detect framing error (no proper stop bit)
|
|
310
|
+
if data[pos] == 0xFF: # Example error condition
|
|
311
|
+
is_valid = False
|
|
312
|
+
error_type = "framing"
|
|
313
|
+
|
|
314
|
+
frames.append(
|
|
315
|
+
DecodedFrame(
|
|
316
|
+
data=frame_data,
|
|
317
|
+
timestamp=timestamp,
|
|
318
|
+
valid=is_valid,
|
|
319
|
+
error_type=error_type,
|
|
320
|
+
position=pos,
|
|
321
|
+
)
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
if not is_valid and tolerance == ErrorTolerance.STRICT:
|
|
325
|
+
# Strict mode: abort on error
|
|
326
|
+
break
|
|
327
|
+
elif not is_valid and tolerance == ErrorTolerance.TOLERANT:
|
|
328
|
+
# Tolerant: skip error frame, resync
|
|
329
|
+
# Search for next valid start bit
|
|
330
|
+
pos += 1
|
|
331
|
+
# In real implementation, would search for start bit pattern
|
|
332
|
+
else:
|
|
333
|
+
# Permissive: record error, continue
|
|
334
|
+
pos += 1
|
|
335
|
+
|
|
336
|
+
except Exception:
|
|
337
|
+
if tolerance == ErrorTolerance.STRICT:
|
|
338
|
+
raise
|
|
339
|
+
else:
|
|
340
|
+
# Log error and continue
|
|
341
|
+
pos += 1
|
|
342
|
+
|
|
343
|
+
elif protocol == "spi":
|
|
344
|
+
# SPI: Re-align on CS edge
|
|
345
|
+
# Simplified placeholder
|
|
346
|
+
pass
|
|
347
|
+
|
|
348
|
+
elif protocol == "i2c":
|
|
349
|
+
# I2C: Search for START condition
|
|
350
|
+
# Simplified placeholder
|
|
351
|
+
pass
|
|
352
|
+
|
|
353
|
+
elif protocol == "can":
|
|
354
|
+
# CAN: Wait for SOF after error
|
|
355
|
+
# Simplified placeholder
|
|
356
|
+
pass
|
|
357
|
+
|
|
358
|
+
return frames
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
"""Bit error pattern analysis and capture diagnostics.
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
This module characterizes bit error patterns to diagnose capture quality
|
|
5
|
+
issues (EMI, USB problems, clock jitter) and suggests likely causes.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
from enum import Enum
|
|
10
|
+
|
|
11
|
+
import numpy as np
|
|
12
|
+
from numpy.typing import NDArray
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ErrorPattern(Enum):
|
|
16
|
+
"""Classified error pattern types.
|
|
17
|
+
|
|
18
|
+
Attributes:
|
|
19
|
+
RANDOM: Errors uniformly distributed, no clustering (likely EMI)
|
|
20
|
+
BURST: Errors clustered together (likely USB transmission issue)
|
|
21
|
+
PERIODIC: Errors repeat at regular intervals (likely clock jitter)
|
|
22
|
+
UNKNOWN: Pattern doesn't match known types
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
RANDOM = "random"
|
|
26
|
+
BURST = "burst"
|
|
27
|
+
PERIODIC = "periodic"
|
|
28
|
+
UNKNOWN = "unknown"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclass
|
|
32
|
+
class ErrorAnalysis:
|
|
33
|
+
"""Result from bit error pattern analysis.
|
|
34
|
+
|
|
35
|
+
Attributes:
|
|
36
|
+
bit_error_rate: Ratio of errors to total bits
|
|
37
|
+
error_count: Total number of bit errors detected
|
|
38
|
+
total_bits: Total bits examined
|
|
39
|
+
pattern_type: Classified error pattern (random, burst, periodic)
|
|
40
|
+
mean_error_gap: Mean number of bits between errors
|
|
41
|
+
error_positions: Array of bit positions where errors occurred
|
|
42
|
+
diagnosis: Suggested cause based on pattern
|
|
43
|
+
severity: Error severity level (low, moderate, severe)
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
bit_error_rate: float
|
|
47
|
+
error_count: int
|
|
48
|
+
total_bits: int
|
|
49
|
+
pattern_type: ErrorPattern
|
|
50
|
+
mean_error_gap: float
|
|
51
|
+
error_positions: NDArray[np.int64]
|
|
52
|
+
diagnosis: str
|
|
53
|
+
severity: str
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def analyze_bit_errors(
|
|
57
|
+
received: NDArray[np.uint8],
|
|
58
|
+
expected: NDArray[np.uint8],
|
|
59
|
+
*,
|
|
60
|
+
burst_threshold: int = 100,
|
|
61
|
+
periodicity_threshold: float = 0.1,
|
|
62
|
+
) -> ErrorAnalysis:
|
|
63
|
+
"""Characterize bit error patterns for capture diagnostics.
|
|
64
|
+
|
|
65
|
+
: Analyzes bit errors to diagnose capture quality
|
|
66
|
+
issues and distinguish between EMI, USB problems, and clock jitter.
|
|
67
|
+
|
|
68
|
+
Error pattern classification (DAQ-005):
|
|
69
|
+
- Random: Errors uniformly distributed, no clustering
|
|
70
|
+
- Burst: Errors clustered, mean_gap < 100 bits
|
|
71
|
+
- Periodic: Errors repeat at regular intervals (FFT peak in positions)
|
|
72
|
+
|
|
73
|
+
Diagnosis suggestions (DAQ-005):
|
|
74
|
+
- BER > 0.01: Severe capture issue, check connections
|
|
75
|
+
- BER 0.001-0.01: Moderate errors, reduce sample rate
|
|
76
|
+
- BER < 0.001: Acceptable, likely EMI
|
|
77
|
+
- Burst errors: USB transmission issue
|
|
78
|
+
- Periodic errors: Clock jitter or interference
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
received: Received bit array (actual capture)
|
|
82
|
+
expected: Expected bit array (golden reference)
|
|
83
|
+
burst_threshold: Mean gap threshold for burst classification
|
|
84
|
+
periodicity_threshold: FFT peak threshold for periodic detection
|
|
85
|
+
|
|
86
|
+
Returns:
|
|
87
|
+
ErrorAnalysis with BER, pattern type, and diagnosis
|
|
88
|
+
|
|
89
|
+
Raises:
|
|
90
|
+
ValueError: If received and expected have different lengths
|
|
91
|
+
ValueError: If arrays are empty
|
|
92
|
+
|
|
93
|
+
Examples:
|
|
94
|
+
>>> # Analyze random EMI errors
|
|
95
|
+
>>> import numpy as np
|
|
96
|
+
>>> expected = np.random.randint(0, 2, 10000, dtype=np.uint8)
|
|
97
|
+
>>> received = expected.copy()
|
|
98
|
+
>>> errors = np.random.choice(10000, 50, replace=False)
|
|
99
|
+
>>> received[errors] = 1 - received[errors] # Flip bits
|
|
100
|
+
>>> analysis = analyze_bit_errors(received, expected)
|
|
101
|
+
>>> print(f"BER: {analysis.bit_error_rate:.6f}")
|
|
102
|
+
>>> print(f"Pattern: {analysis.pattern_type.value}")
|
|
103
|
+
|
|
104
|
+
>>> # Analyze burst errors (USB issue)
|
|
105
|
+
>>> received = expected.copy()
|
|
106
|
+
>>> received[1000:1050] = 1 - received[1000:1050] # 50-bit burst
|
|
107
|
+
>>> analysis = analyze_bit_errors(received, expected)
|
|
108
|
+
>>> print(analysis.diagnosis)
|
|
109
|
+
'USB transmission issue'
|
|
110
|
+
|
|
111
|
+
References:
|
|
112
|
+
DAQ-005: Bit Error Pattern Analysis and Capture Diagnostics
|
|
113
|
+
"""
|
|
114
|
+
if len(received) != len(expected):
|
|
115
|
+
raise ValueError("Received and expected arrays must have same length")
|
|
116
|
+
|
|
117
|
+
if len(received) == 0:
|
|
118
|
+
raise ValueError("Arrays cannot be empty")
|
|
119
|
+
|
|
120
|
+
# Find bit errors (XOR)
|
|
121
|
+
errors = received != expected
|
|
122
|
+
error_positions = np.where(errors)[0]
|
|
123
|
+
error_count = len(error_positions)
|
|
124
|
+
total_bits = len(received)
|
|
125
|
+
|
|
126
|
+
# Calculate BER
|
|
127
|
+
bit_error_rate = error_count / total_bits if total_bits > 0 else 0.0
|
|
128
|
+
|
|
129
|
+
if error_count == 0:
|
|
130
|
+
# No errors
|
|
131
|
+
return ErrorAnalysis(
|
|
132
|
+
bit_error_rate=0.0,
|
|
133
|
+
error_count=0,
|
|
134
|
+
total_bits=total_bits,
|
|
135
|
+
pattern_type=ErrorPattern.RANDOM,
|
|
136
|
+
mean_error_gap=float(total_bits),
|
|
137
|
+
error_positions=error_positions,
|
|
138
|
+
diagnosis="No errors detected - good capture quality",
|
|
139
|
+
severity="low",
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
# Calculate error gaps
|
|
143
|
+
if error_count > 1:
|
|
144
|
+
error_gaps = np.diff(error_positions)
|
|
145
|
+
mean_gap = float(np.mean(error_gaps))
|
|
146
|
+
else:
|
|
147
|
+
mean_gap = float(total_bits)
|
|
148
|
+
|
|
149
|
+
# Classify error pattern
|
|
150
|
+
pattern_type = ErrorPattern.UNKNOWN
|
|
151
|
+
diagnosis = ""
|
|
152
|
+
|
|
153
|
+
# Check for burst pattern (errors clustered)
|
|
154
|
+
if error_count > 1 and mean_gap < burst_threshold:
|
|
155
|
+
pattern_type = ErrorPattern.BURST
|
|
156
|
+
diagnosis = "Burst errors detected - likely USB transmission issue"
|
|
157
|
+
|
|
158
|
+
# Check for periodic pattern (FFT analysis)
|
|
159
|
+
# Need at least 10 errors for reliable periodicity detection
|
|
160
|
+
elif error_count >= 10:
|
|
161
|
+
# Create binary error signal
|
|
162
|
+
error_signal = errors.astype(float)
|
|
163
|
+
|
|
164
|
+
# Compute FFT to detect periodicity
|
|
165
|
+
fft = np.fft.rfft(error_signal)
|
|
166
|
+
fft_mag = np.abs(fft[1:]) # Skip DC component
|
|
167
|
+
|
|
168
|
+
if len(fft_mag) > 0:
|
|
169
|
+
# Check if there's a strong peak relative to mean (not just max)
|
|
170
|
+
mean_mag = np.mean(fft_mag)
|
|
171
|
+
max_mag = np.max(fft_mag)
|
|
172
|
+
peak_ratio = max_mag / (mean_mag + 1e-12)
|
|
173
|
+
|
|
174
|
+
# Require strong peak (>10x mean) and exceeds threshold
|
|
175
|
+
if peak_ratio > 10 and (max_mag / (np.max(fft_mag) + 1e-12)) > periodicity_threshold:
|
|
176
|
+
pattern_type = ErrorPattern.PERIODIC
|
|
177
|
+
diagnosis = "Periodic errors detected - likely clock jitter or interference"
|
|
178
|
+
|
|
179
|
+
# If not burst or periodic, classify as random
|
|
180
|
+
if pattern_type == ErrorPattern.UNKNOWN:
|
|
181
|
+
# Check if errors are uniformly distributed
|
|
182
|
+
if error_count > 2:
|
|
183
|
+
# Use coefficient of variation of gaps
|
|
184
|
+
if error_count > 1:
|
|
185
|
+
gap_std = float(np.std(error_gaps))
|
|
186
|
+
gap_cv = gap_std / (mean_gap + 1e-12)
|
|
187
|
+
|
|
188
|
+
if gap_cv < 1.0: # Relatively uniform spacing
|
|
189
|
+
pattern_type = ErrorPattern.RANDOM
|
|
190
|
+
diagnosis = "Random errors detected - likely EMI or noise"
|
|
191
|
+
else:
|
|
192
|
+
diagnosis = "Mixed error pattern - multiple causes possible"
|
|
193
|
+
else:
|
|
194
|
+
pattern_type = ErrorPattern.RANDOM
|
|
195
|
+
diagnosis = "Single error - insufficient data for classification"
|
|
196
|
+
else:
|
|
197
|
+
pattern_type = ErrorPattern.RANDOM
|
|
198
|
+
diagnosis = "Few errors - likely random EMI or noise"
|
|
199
|
+
|
|
200
|
+
# Determine severity based on BER
|
|
201
|
+
if bit_error_rate > 0.01:
|
|
202
|
+
severity = "severe"
|
|
203
|
+
diagnosis += ". SEVERE: Check connections and hardware"
|
|
204
|
+
elif bit_error_rate > 0.001:
|
|
205
|
+
severity = "moderate"
|
|
206
|
+
diagnosis += ". MODERATE: Consider reducing sample rate"
|
|
207
|
+
else:
|
|
208
|
+
severity = "low"
|
|
209
|
+
diagnosis += ". Acceptable error rate"
|
|
210
|
+
|
|
211
|
+
return ErrorAnalysis(
|
|
212
|
+
bit_error_rate=bit_error_rate,
|
|
213
|
+
error_count=error_count,
|
|
214
|
+
total_bits=total_bits,
|
|
215
|
+
pattern_type=pattern_type,
|
|
216
|
+
mean_error_gap=mean_gap,
|
|
217
|
+
error_positions=error_positions,
|
|
218
|
+
diagnosis=diagnosis,
|
|
219
|
+
severity=severity,
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def generate_error_visualization_data(
|
|
224
|
+
analysis: ErrorAnalysis,
|
|
225
|
+
*,
|
|
226
|
+
histogram_bins: int = 50,
|
|
227
|
+
) -> dict[str, NDArray[np.float64]]:
|
|
228
|
+
"""Generate data for error distribution visualization.
|
|
229
|
+
|
|
230
|
+
Creates histogram and timeline data suitable for plotting error patterns.
|
|
231
|
+
|
|
232
|
+
Args:
|
|
233
|
+
analysis: ErrorAnalysis result from analyze_bit_errors()
|
|
234
|
+
histogram_bins: Number of bins for error position histogram
|
|
235
|
+
|
|
236
|
+
Returns:
|
|
237
|
+
Dictionary with 'histogram_counts', 'histogram_edges', and
|
|
238
|
+
'timeline' arrays for visualization
|
|
239
|
+
|
|
240
|
+
Examples:
|
|
241
|
+
>>> # Generate visualization data
|
|
242
|
+
>>> analysis = analyze_bit_errors(received, expected)
|
|
243
|
+
>>> viz_data = generate_error_visualization_data(analysis)
|
|
244
|
+
>>> # Plot with matplotlib
|
|
245
|
+
>>> import matplotlib.pyplot as plt
|
|
246
|
+
>>> plt.hist(analysis.error_positions, bins=viz_data['histogram_edges'])
|
|
247
|
+
>>> plt.xlabel('Bit Position')
|
|
248
|
+
>>> plt.ylabel('Error Count')
|
|
249
|
+
>>> plt.show()
|
|
250
|
+
|
|
251
|
+
References:
|
|
252
|
+
DAQ-005: Bit Error Pattern Analysis and Capture Diagnostics
|
|
253
|
+
"""
|
|
254
|
+
if len(analysis.error_positions) == 0:
|
|
255
|
+
# No errors - return empty data
|
|
256
|
+
return {
|
|
257
|
+
"histogram_counts": np.array([], dtype=np.float64),
|
|
258
|
+
"histogram_edges": np.array([], dtype=np.float64),
|
|
259
|
+
"timeline": np.array([], dtype=np.float64),
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
# Generate histogram over full bit range
|
|
263
|
+
counts, edges = np.histogram(
|
|
264
|
+
analysis.error_positions, bins=histogram_bins, range=(0, analysis.total_bits), density=False
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
# Timeline: binary array with 1s at error positions
|
|
268
|
+
timeline = np.zeros(analysis.total_bits, dtype=np.float64)
|
|
269
|
+
timeline[analysis.error_positions] = 1.0
|
|
270
|
+
|
|
271
|
+
return {
|
|
272
|
+
"histogram_counts": counts.astype(np.float64),
|
|
273
|
+
"histogram_edges": edges.astype(np.float64),
|
|
274
|
+
"timeline": timeline,
|
|
275
|
+
}
|