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,528 @@
|
|
|
1
|
+
"""Data quality assessment for signal analysis.
|
|
2
|
+
|
|
3
|
+
This module assesses whether captured data is sufficient and of adequate
|
|
4
|
+
quality for meaningful analysis.
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
Example:
|
|
8
|
+
>>> from oscura.discovery import assess_data_quality
|
|
9
|
+
>>> quality = assess_data_quality(trace)
|
|
10
|
+
>>> print(f"Status: {quality.status}")
|
|
11
|
+
>>> for metric in quality.metrics:
|
|
12
|
+
... print(f"{metric.name}: {metric.status}")
|
|
13
|
+
|
|
14
|
+
References:
|
|
15
|
+
IEEE 1241-2010: ADC Terminology and Test Methods
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
from dataclasses import dataclass, field
|
|
21
|
+
from typing import TYPE_CHECKING, Any, Literal
|
|
22
|
+
|
|
23
|
+
import numpy as np
|
|
24
|
+
|
|
25
|
+
from oscura.analyzers.statistics.basic import basic_stats
|
|
26
|
+
from oscura.core.types import DigitalTrace, WaveformTrace
|
|
27
|
+
|
|
28
|
+
if TYPE_CHECKING:
|
|
29
|
+
from numpy.typing import NDArray
|
|
30
|
+
|
|
31
|
+
QualityStatus = Literal["PASS", "WARNING", "FAIL"]
|
|
32
|
+
AnalysisScenario = Literal["protocol_decode", "timing_analysis", "fft", "eye_diagram", "general"]
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass
|
|
36
|
+
class QualityMetric:
|
|
37
|
+
"""Individual quality metric result.
|
|
38
|
+
|
|
39
|
+
Attributes:
|
|
40
|
+
name: Metric name (e.g., "Sample Rate", "Resolution").
|
|
41
|
+
status: Quality status (PASS, WARNING, FAIL).
|
|
42
|
+
passed: Whether metric passes minimum requirements.
|
|
43
|
+
current_value: Measured value.
|
|
44
|
+
required_value: Required value for this scenario.
|
|
45
|
+
unit: Unit of measurement.
|
|
46
|
+
margin_percent: Margin relative to requirement (positive = good).
|
|
47
|
+
explanation: Plain-language explanation if failed.
|
|
48
|
+
recommendation: Actionable recommendation to fix issue.
|
|
49
|
+
|
|
50
|
+
Example:
|
|
51
|
+
>>> metric = QualityMetric(
|
|
52
|
+
... name="Sample Rate",
|
|
53
|
+
... status="WARNING",
|
|
54
|
+
... passed=False,
|
|
55
|
+
... current_value=50.0,
|
|
56
|
+
... required_value=100.0,
|
|
57
|
+
... unit="MS/s"
|
|
58
|
+
... )
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
name: str
|
|
62
|
+
status: QualityStatus
|
|
63
|
+
passed: bool
|
|
64
|
+
current_value: float
|
|
65
|
+
required_value: float
|
|
66
|
+
unit: str
|
|
67
|
+
margin_percent: float = 0.0
|
|
68
|
+
explanation: str = ""
|
|
69
|
+
recommendation: str = ""
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@dataclass
|
|
73
|
+
class DataQuality:
|
|
74
|
+
"""Overall data quality assessment result.
|
|
75
|
+
|
|
76
|
+
Attributes:
|
|
77
|
+
status: Overall quality status (PASS, WARNING, FAIL).
|
|
78
|
+
confidence: Assessment confidence (0.0-1.0).
|
|
79
|
+
metrics: List of individual quality metrics.
|
|
80
|
+
improvement_suggestions: Suggested improvements if quality is poor.
|
|
81
|
+
|
|
82
|
+
Example:
|
|
83
|
+
>>> quality = assess_data_quality(trace)
|
|
84
|
+
>>> if quality.status != "PASS":
|
|
85
|
+
... print("Quality issues detected:")
|
|
86
|
+
... for metric in quality.metrics:
|
|
87
|
+
... if not metric.passed:
|
|
88
|
+
... print(f" - {metric.name}: {metric.explanation}")
|
|
89
|
+
"""
|
|
90
|
+
|
|
91
|
+
status: QualityStatus
|
|
92
|
+
confidence: float
|
|
93
|
+
metrics: list[QualityMetric] = field(default_factory=list)
|
|
94
|
+
improvement_suggestions: list[dict[str, str]] = field(default_factory=list)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def assess_data_quality(
|
|
98
|
+
trace: WaveformTrace | DigitalTrace,
|
|
99
|
+
*,
|
|
100
|
+
scenario: AnalysisScenario = "general",
|
|
101
|
+
protocol_params: dict[str, Any] | None = None,
|
|
102
|
+
strict_mode: bool = False,
|
|
103
|
+
) -> DataQuality:
|
|
104
|
+
"""Assess whether captured data is adequate for analysis.
|
|
105
|
+
|
|
106
|
+
Evaluates sample rate, resolution, duration, and noise level against
|
|
107
|
+
scenario-specific requirements.
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
trace: Input waveform or digital trace.
|
|
111
|
+
scenario: Analysis scenario for scenario-specific thresholds.
|
|
112
|
+
protocol_params: Protocol-specific parameters (e.g., clock frequency).
|
|
113
|
+
strict_mode: If True, fail on any warnings.
|
|
114
|
+
|
|
115
|
+
Returns:
|
|
116
|
+
DataQuality assessment with overall status and individual metrics.
|
|
117
|
+
|
|
118
|
+
Raises:
|
|
119
|
+
ValueError: If trace is empty or invalid.
|
|
120
|
+
|
|
121
|
+
Example:
|
|
122
|
+
>>> quality = assess_data_quality(trace, scenario='protocol_decode')
|
|
123
|
+
>>> print(f"Overall: {quality.status} (confidence: {quality.confidence:.2f})")
|
|
124
|
+
>>> for metric in quality.metrics:
|
|
125
|
+
... if metric.status != 'PASS':
|
|
126
|
+
... print(f"Issue: {metric.name} - {metric.explanation}")
|
|
127
|
+
... print(f"Fix: {metric.recommendation}")
|
|
128
|
+
|
|
129
|
+
References:
|
|
130
|
+
DISC-009: Data Quality Assessment
|
|
131
|
+
"""
|
|
132
|
+
# Validate input
|
|
133
|
+
if len(trace) == 0:
|
|
134
|
+
raise ValueError("Cannot assess quality of empty trace")
|
|
135
|
+
|
|
136
|
+
# Get signal data
|
|
137
|
+
if isinstance(trace, WaveformTrace):
|
|
138
|
+
data = trace.data
|
|
139
|
+
sample_rate = trace.metadata.sample_rate
|
|
140
|
+
is_analog = True
|
|
141
|
+
else:
|
|
142
|
+
data = trace.data.astype(np.float64)
|
|
143
|
+
sample_rate = trace.metadata.sample_rate
|
|
144
|
+
is_analog = False
|
|
145
|
+
|
|
146
|
+
# Compute basic statistics
|
|
147
|
+
stats = basic_stats(data)
|
|
148
|
+
voltage_swing = stats["max"] - stats["min"]
|
|
149
|
+
|
|
150
|
+
# Protocol parameters
|
|
151
|
+
if protocol_params is None:
|
|
152
|
+
protocol_params = {}
|
|
153
|
+
|
|
154
|
+
# Assess individual metrics
|
|
155
|
+
metrics: list[QualityMetric] = []
|
|
156
|
+
|
|
157
|
+
# 1. Sample Rate Assessment
|
|
158
|
+
sample_rate_metric = _assess_sample_rate(sample_rate, data, stats, scenario, protocol_params)
|
|
159
|
+
metrics.append(sample_rate_metric)
|
|
160
|
+
|
|
161
|
+
# 2. Resolution Assessment
|
|
162
|
+
resolution_metric = _assess_resolution(data, voltage_swing, stats, is_analog, scenario)
|
|
163
|
+
metrics.append(resolution_metric)
|
|
164
|
+
|
|
165
|
+
# 3. Duration Assessment
|
|
166
|
+
duration_metric = _assess_duration(len(data), sample_rate, data, scenario, protocol_params)
|
|
167
|
+
metrics.append(duration_metric)
|
|
168
|
+
|
|
169
|
+
# 4. Noise Level Assessment
|
|
170
|
+
noise_metric = _assess_noise(data, voltage_swing, stats, scenario)
|
|
171
|
+
metrics.append(noise_metric)
|
|
172
|
+
|
|
173
|
+
# Determine overall status
|
|
174
|
+
failed_metrics = [m for m in metrics if m.status == "FAIL"]
|
|
175
|
+
warning_metrics = [m for m in metrics if m.status == "WARNING"]
|
|
176
|
+
|
|
177
|
+
if failed_metrics or (strict_mode and warning_metrics):
|
|
178
|
+
overall_status: QualityStatus = "FAIL"
|
|
179
|
+
elif warning_metrics:
|
|
180
|
+
overall_status = "WARNING"
|
|
181
|
+
else:
|
|
182
|
+
overall_status = "PASS"
|
|
183
|
+
|
|
184
|
+
# Calculate confidence (higher when more metrics pass)
|
|
185
|
+
passed_count = sum(1 for m in metrics if m.passed)
|
|
186
|
+
confidence = round(0.5 + (passed_count / len(metrics)) * 0.5, 2)
|
|
187
|
+
|
|
188
|
+
# Generate improvement suggestions
|
|
189
|
+
suggestions = []
|
|
190
|
+
for metric in metrics:
|
|
191
|
+
if not metric.passed and metric.recommendation:
|
|
192
|
+
suggestions.append(
|
|
193
|
+
{
|
|
194
|
+
"action": metric.recommendation,
|
|
195
|
+
"expected_benefit": f"Improves {metric.name.lower()} to required level",
|
|
196
|
+
"difficulty_level": "Easy"
|
|
197
|
+
if "setting" in metric.recommendation.lower()
|
|
198
|
+
else "Medium",
|
|
199
|
+
}
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
return DataQuality(
|
|
203
|
+
status=overall_status,
|
|
204
|
+
confidence=confidence,
|
|
205
|
+
metrics=metrics,
|
|
206
|
+
improvement_suggestions=suggestions,
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def _assess_sample_rate(
|
|
211
|
+
sample_rate: float,
|
|
212
|
+
data: NDArray[np.floating[Any]],
|
|
213
|
+
stats: dict[str, float],
|
|
214
|
+
scenario: AnalysisScenario,
|
|
215
|
+
protocol_params: dict[str, Any],
|
|
216
|
+
) -> QualityMetric:
|
|
217
|
+
"""Assess sample rate adequacy.
|
|
218
|
+
|
|
219
|
+
Args:
|
|
220
|
+
sample_rate: Sample rate in Hz.
|
|
221
|
+
data: Signal data array.
|
|
222
|
+
stats: Basic statistics.
|
|
223
|
+
scenario: Analysis scenario.
|
|
224
|
+
protocol_params: Protocol-specific parameters.
|
|
225
|
+
|
|
226
|
+
Returns:
|
|
227
|
+
QualityMetric for sample rate.
|
|
228
|
+
"""
|
|
229
|
+
# Estimate signal frequency
|
|
230
|
+
mean_val = stats["mean"]
|
|
231
|
+
crossings = np.where(np.diff(np.sign(data - mean_val)) != 0)[0]
|
|
232
|
+
|
|
233
|
+
if len(crossings) >= 2:
|
|
234
|
+
avg_half_period = np.mean(np.diff(crossings))
|
|
235
|
+
signal_freq = sample_rate / (avg_half_period * 2) if avg_half_period > 0 else 0
|
|
236
|
+
else:
|
|
237
|
+
signal_freq = 0
|
|
238
|
+
|
|
239
|
+
# Determine required sample rate based on scenario
|
|
240
|
+
if scenario == "protocol_decode":
|
|
241
|
+
# Need 10x the bit rate
|
|
242
|
+
if "clock_freq_mhz" in protocol_params:
|
|
243
|
+
clock_freq = protocol_params["clock_freq_mhz"] * 1e6
|
|
244
|
+
required_rate = clock_freq * 10
|
|
245
|
+
elif signal_freq > 0:
|
|
246
|
+
required_rate = signal_freq * 10
|
|
247
|
+
else:
|
|
248
|
+
required_rate = 10e6 # Default 10 MS/s minimum
|
|
249
|
+
elif scenario == "timing_analysis":
|
|
250
|
+
# Need 100x the edge rate
|
|
251
|
+
required_rate = signal_freq * 100 if signal_freq > 0 else 100e6
|
|
252
|
+
elif scenario == "fft":
|
|
253
|
+
# Nyquist + 20%
|
|
254
|
+
required_rate = signal_freq * 2.4 if signal_freq > 0 else 10e6
|
|
255
|
+
elif scenario == "eye_diagram":
|
|
256
|
+
# Need high oversampling
|
|
257
|
+
required_rate = signal_freq * 50 if signal_freq > 0 else 100e6
|
|
258
|
+
else: # general
|
|
259
|
+
# At least 10x signal frequency
|
|
260
|
+
required_rate = signal_freq * 10 if signal_freq > 0 else 10e6
|
|
261
|
+
|
|
262
|
+
# Calculate margin
|
|
263
|
+
margin_percent = ((sample_rate - required_rate) / required_rate) * 100
|
|
264
|
+
|
|
265
|
+
# Determine status
|
|
266
|
+
if margin_percent >= 0:
|
|
267
|
+
status: QualityStatus = "PASS"
|
|
268
|
+
passed = True
|
|
269
|
+
explanation = ""
|
|
270
|
+
recommendation = ""
|
|
271
|
+
elif margin_percent >= -20:
|
|
272
|
+
status = "WARNING"
|
|
273
|
+
passed = False
|
|
274
|
+
explanation = f"Sample rate is {abs(margin_percent):.0f}% below recommended"
|
|
275
|
+
recommendation = f"Increase sample rate to {required_rate / 1e6:.0f} MS/s (currently {sample_rate / 1e6:.0f} MS/s)"
|
|
276
|
+
else:
|
|
277
|
+
status = "FAIL"
|
|
278
|
+
passed = False
|
|
279
|
+
explanation = f"Sample rate is critically low ({abs(margin_percent):.0f}% below required)"
|
|
280
|
+
recommendation = f"Increase sample rate to at least {required_rate / 1e6:.0f} MS/s"
|
|
281
|
+
|
|
282
|
+
return QualityMetric(
|
|
283
|
+
name="Sample Rate",
|
|
284
|
+
status=status,
|
|
285
|
+
passed=passed,
|
|
286
|
+
current_value=sample_rate / 1e6,
|
|
287
|
+
required_value=required_rate / 1e6,
|
|
288
|
+
unit="MS/s",
|
|
289
|
+
margin_percent=margin_percent,
|
|
290
|
+
explanation=explanation,
|
|
291
|
+
recommendation=recommendation,
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def _assess_resolution(
|
|
296
|
+
data: NDArray[np.floating[Any]],
|
|
297
|
+
voltage_swing: float,
|
|
298
|
+
stats: dict[str, float],
|
|
299
|
+
is_analog: bool,
|
|
300
|
+
scenario: AnalysisScenario,
|
|
301
|
+
) -> QualityMetric:
|
|
302
|
+
"""Assess vertical resolution adequacy.
|
|
303
|
+
|
|
304
|
+
Args:
|
|
305
|
+
data: Signal data array.
|
|
306
|
+
voltage_swing: Peak-to-peak voltage.
|
|
307
|
+
stats: Basic statistics.
|
|
308
|
+
is_analog: Whether signal is analog.
|
|
309
|
+
scenario: Analysis scenario.
|
|
310
|
+
|
|
311
|
+
Returns:
|
|
312
|
+
QualityMetric for resolution.
|
|
313
|
+
"""
|
|
314
|
+
# Estimate effective number of bits (ENOB)
|
|
315
|
+
if voltage_swing > 0:
|
|
316
|
+
# Approximate ENOB from noise level
|
|
317
|
+
noise_rms = stats["std"]
|
|
318
|
+
snr_linear = (voltage_swing / 2) / (noise_rms + 1e-12)
|
|
319
|
+
snr_db = 20 * np.log10(snr_linear) if snr_linear > 0 else 0
|
|
320
|
+
else:
|
|
321
|
+
# No voltage swing - cannot assess SNR meaningfully
|
|
322
|
+
# Treat as infinite SNR (perfect) since there's no dynamic range
|
|
323
|
+
snr_db = 100.0
|
|
324
|
+
|
|
325
|
+
# Determine required resolution
|
|
326
|
+
if scenario in ("protocol_decode", "timing_analysis"):
|
|
327
|
+
required_snr = 20.0 # dB
|
|
328
|
+
elif scenario in ("fft", "eye_diagram"):
|
|
329
|
+
required_snr = 40.0 # dB
|
|
330
|
+
else:
|
|
331
|
+
required_snr = 20.0 # dB
|
|
332
|
+
|
|
333
|
+
# Use SNR for assessment
|
|
334
|
+
current_snr = snr_db
|
|
335
|
+
margin_percent = ((current_snr - required_snr) / required_snr) * 100
|
|
336
|
+
|
|
337
|
+
# Determine status
|
|
338
|
+
if current_snr >= required_snr:
|
|
339
|
+
status: QualityStatus = "PASS"
|
|
340
|
+
passed = True
|
|
341
|
+
explanation = ""
|
|
342
|
+
recommendation = ""
|
|
343
|
+
elif current_snr >= required_snr * 0.8:
|
|
344
|
+
status = "WARNING"
|
|
345
|
+
passed = False
|
|
346
|
+
explanation = f"SNR is {abs(margin_percent):.0f}% below recommended ({current_snr:.1f} dB)"
|
|
347
|
+
recommendation = "Reduce noise sources or increase signal amplitude"
|
|
348
|
+
else:
|
|
349
|
+
status = "FAIL"
|
|
350
|
+
passed = False
|
|
351
|
+
explanation = f"SNR is critically low ({current_snr:.1f} dB, need {required_snr:.0f} dB)"
|
|
352
|
+
recommendation = "Significantly improve signal quality or use higher resolution capture"
|
|
353
|
+
|
|
354
|
+
return QualityMetric(
|
|
355
|
+
name="Resolution",
|
|
356
|
+
status=status,
|
|
357
|
+
passed=passed,
|
|
358
|
+
current_value=current_snr,
|
|
359
|
+
required_value=required_snr,
|
|
360
|
+
unit="dB SNR",
|
|
361
|
+
margin_percent=margin_percent,
|
|
362
|
+
explanation=explanation,
|
|
363
|
+
recommendation=recommendation,
|
|
364
|
+
)
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
def _assess_duration(
|
|
368
|
+
n_samples: int,
|
|
369
|
+
sample_rate: float,
|
|
370
|
+
data: NDArray[np.floating[Any]],
|
|
371
|
+
scenario: AnalysisScenario,
|
|
372
|
+
protocol_params: dict[str, Any],
|
|
373
|
+
) -> QualityMetric:
|
|
374
|
+
"""Assess capture duration adequacy.
|
|
375
|
+
|
|
376
|
+
Args:
|
|
377
|
+
n_samples: Number of samples.
|
|
378
|
+
sample_rate: Sample rate in Hz.
|
|
379
|
+
data: Signal data array.
|
|
380
|
+
scenario: Analysis scenario.
|
|
381
|
+
protocol_params: Protocol-specific parameters.
|
|
382
|
+
|
|
383
|
+
Returns:
|
|
384
|
+
QualityMetric for duration.
|
|
385
|
+
"""
|
|
386
|
+
duration_sec = n_samples / sample_rate
|
|
387
|
+
|
|
388
|
+
# Estimate signal period
|
|
389
|
+
mean_val = np.mean(data)
|
|
390
|
+
crossings = np.where(np.diff(np.sign(data - mean_val)) != 0)[0]
|
|
391
|
+
|
|
392
|
+
if len(crossings) >= 2:
|
|
393
|
+
avg_half_period = np.mean(np.diff(crossings))
|
|
394
|
+
signal_period = (avg_half_period * 2) / sample_rate
|
|
395
|
+
num_periods = duration_sec / signal_period if signal_period > 0 else 0
|
|
396
|
+
else:
|
|
397
|
+
num_periods = 0
|
|
398
|
+
signal_period = duration_sec / 10 # Assume at least 10 periods
|
|
399
|
+
|
|
400
|
+
# Determine required duration
|
|
401
|
+
if scenario in {"protocol_decode", "timing_analysis"}:
|
|
402
|
+
required_periods = 100
|
|
403
|
+
elif scenario == "fft":
|
|
404
|
+
required_periods = 10 # Need enough for frequency resolution
|
|
405
|
+
elif scenario == "eye_diagram":
|
|
406
|
+
required_periods = 1000 # Need many UIs
|
|
407
|
+
else:
|
|
408
|
+
required_periods = 100
|
|
409
|
+
|
|
410
|
+
required_duration = required_periods * signal_period
|
|
411
|
+
margin_percent = (
|
|
412
|
+
((duration_sec - required_duration) / required_duration) * 100
|
|
413
|
+
if required_duration > 0
|
|
414
|
+
else 100
|
|
415
|
+
)
|
|
416
|
+
|
|
417
|
+
# Determine status
|
|
418
|
+
if num_periods >= required_periods or margin_percent >= 0:
|
|
419
|
+
status: QualityStatus = "PASS"
|
|
420
|
+
passed = True
|
|
421
|
+
explanation = ""
|
|
422
|
+
recommendation = ""
|
|
423
|
+
elif num_periods >= required_periods * 0.5:
|
|
424
|
+
status = "WARNING"
|
|
425
|
+
passed = False
|
|
426
|
+
explanation = f"Captured only {num_periods:.0f} signal periods, recommended minimum is {required_periods}"
|
|
427
|
+
recommendation = f"Increase capture duration to at least {required_duration * 1e3:.1f} ms (currently {duration_sec * 1e3:.1f} ms)"
|
|
428
|
+
else:
|
|
429
|
+
status = "FAIL"
|
|
430
|
+
passed = False
|
|
431
|
+
explanation = f"Capture duration is critically short ({num_periods:.0f} periods)"
|
|
432
|
+
recommendation = f"Increase capture duration to at least {required_duration * 1e3:.1f} ms"
|
|
433
|
+
|
|
434
|
+
return QualityMetric(
|
|
435
|
+
name="Duration",
|
|
436
|
+
status=status,
|
|
437
|
+
passed=passed,
|
|
438
|
+
current_value=duration_sec * 1e3,
|
|
439
|
+
required_value=required_duration * 1e3,
|
|
440
|
+
unit="ms",
|
|
441
|
+
margin_percent=margin_percent,
|
|
442
|
+
explanation=explanation,
|
|
443
|
+
recommendation=recommendation,
|
|
444
|
+
)
|
|
445
|
+
|
|
446
|
+
|
|
447
|
+
def _assess_noise(
|
|
448
|
+
data: NDArray[np.floating[Any]],
|
|
449
|
+
voltage_swing: float,
|
|
450
|
+
stats: dict[str, float],
|
|
451
|
+
scenario: AnalysisScenario,
|
|
452
|
+
) -> QualityMetric:
|
|
453
|
+
"""Assess noise level.
|
|
454
|
+
|
|
455
|
+
Args:
|
|
456
|
+
data: Signal data array.
|
|
457
|
+
voltage_swing: Peak-to-peak voltage.
|
|
458
|
+
stats: Basic statistics.
|
|
459
|
+
scenario: Analysis scenario.
|
|
460
|
+
|
|
461
|
+
Returns:
|
|
462
|
+
QualityMetric for noise level.
|
|
463
|
+
"""
|
|
464
|
+
if voltage_swing == 0:
|
|
465
|
+
# No signal swing, can't assess noise
|
|
466
|
+
return QualityMetric(
|
|
467
|
+
name="Noise Level",
|
|
468
|
+
status="PASS",
|
|
469
|
+
passed=True,
|
|
470
|
+
current_value=0.0,
|
|
471
|
+
required_value=0.0,
|
|
472
|
+
unit="% of swing",
|
|
473
|
+
margin_percent=100.0,
|
|
474
|
+
)
|
|
475
|
+
|
|
476
|
+
# Noise RMS as percentage of swing
|
|
477
|
+
noise_rms = stats["std"]
|
|
478
|
+
noise_percent = (noise_rms / voltage_swing) * 100
|
|
479
|
+
|
|
480
|
+
# Determine acceptable noise level
|
|
481
|
+
if scenario in ("protocol_decode", "timing_analysis"):
|
|
482
|
+
max_noise_percent = 10.0
|
|
483
|
+
elif scenario in ("fft", "eye_diagram"):
|
|
484
|
+
max_noise_percent = 5.0
|
|
485
|
+
else:
|
|
486
|
+
max_noise_percent = 10.0
|
|
487
|
+
|
|
488
|
+
margin_percent = ((max_noise_percent - noise_percent) / max_noise_percent) * 100
|
|
489
|
+
|
|
490
|
+
# Determine status
|
|
491
|
+
if noise_percent <= max_noise_percent:
|
|
492
|
+
status: QualityStatus = "PASS"
|
|
493
|
+
passed = True
|
|
494
|
+
explanation = ""
|
|
495
|
+
recommendation = ""
|
|
496
|
+
elif noise_percent <= max_noise_percent * 1.5:
|
|
497
|
+
status = "WARNING"
|
|
498
|
+
passed = False
|
|
499
|
+
explanation = f"Noise level is {noise_percent:.1f}% of signal swing (max recommended: {max_noise_percent:.0f}%)"
|
|
500
|
+
recommendation = "Reduce noise sources, check grounding, or use averaging"
|
|
501
|
+
else:
|
|
502
|
+
status = "FAIL"
|
|
503
|
+
passed = False
|
|
504
|
+
explanation = f"Noise level is critically high ({noise_percent:.1f}% of swing)"
|
|
505
|
+
recommendation = (
|
|
506
|
+
"Significantly reduce noise through better probing, shielding, or bandwidth limiting"
|
|
507
|
+
)
|
|
508
|
+
|
|
509
|
+
return QualityMetric(
|
|
510
|
+
name="Noise Level",
|
|
511
|
+
status=status,
|
|
512
|
+
passed=passed,
|
|
513
|
+
current_value=noise_percent,
|
|
514
|
+
required_value=max_noise_percent,
|
|
515
|
+
unit="% of swing",
|
|
516
|
+
margin_percent=margin_percent,
|
|
517
|
+
explanation=explanation,
|
|
518
|
+
recommendation=recommendation,
|
|
519
|
+
)
|
|
520
|
+
|
|
521
|
+
|
|
522
|
+
__all__ = [
|
|
523
|
+
"AnalysisScenario",
|
|
524
|
+
"DataQuality",
|
|
525
|
+
"QualityMetric",
|
|
526
|
+
"QualityStatus",
|
|
527
|
+
"assess_data_quality",
|
|
528
|
+
]
|