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,541 @@
|
|
|
1
|
+
"""Edge case handling utilities for TraceKit.
|
|
2
|
+
|
|
3
|
+
This module provides utilities for gracefully handling edge cases including
|
|
4
|
+
empty inputs, single-sample traces, and NaN/Inf values.
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
Example:
|
|
8
|
+
>>> from oscura.core.edge_cases import (
|
|
9
|
+
... validate_signal,
|
|
10
|
+
... handle_empty_trace,
|
|
11
|
+
... sanitize_signal
|
|
12
|
+
... )
|
|
13
|
+
>>> validated = validate_signal(signal, min_samples=10)
|
|
14
|
+
>>> clean_signal = sanitize_signal(noisy_signal)
|
|
15
|
+
|
|
16
|
+
References:
|
|
17
|
+
- IEEE 754 floating-point standard
|
|
18
|
+
- NumPy NaN handling best practices
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import warnings
|
|
24
|
+
from typing import TYPE_CHECKING
|
|
25
|
+
|
|
26
|
+
import numpy as np
|
|
27
|
+
|
|
28
|
+
if TYPE_CHECKING:
|
|
29
|
+
from numpy.typing import NDArray
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class EmptyTraceError(Exception):
|
|
33
|
+
"""Exception raised when trace has no data.
|
|
34
|
+
|
|
35
|
+
: Empty trace returns informative error, not crash.
|
|
36
|
+
|
|
37
|
+
Example:
|
|
38
|
+
>>> from oscura.core.edge_cases import EmptyTraceError
|
|
39
|
+
>>> raise EmptyTraceError("Cannot analyze empty trace (0 samples)")
|
|
40
|
+
|
|
41
|
+
References:
|
|
42
|
+
EDGE-002: Graceful Empty/Short Signal Handling
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
def __init__(self, message: str = "Trace is empty (0 samples)") -> None:
|
|
46
|
+
"""Initialize EmptyTraceError.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
message: Error message (default: "Trace is empty (0 samples)")
|
|
50
|
+
"""
|
|
51
|
+
super().__init__(message)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class InsufficientSamplesError(Exception):
|
|
55
|
+
"""Exception raised when trace has insufficient samples.
|
|
56
|
+
|
|
57
|
+
: Too-short trace warns and adapts.
|
|
58
|
+
|
|
59
|
+
Attributes:
|
|
60
|
+
required: Minimum samples required
|
|
61
|
+
available: Actual samples available
|
|
62
|
+
|
|
63
|
+
Example:
|
|
64
|
+
>>> from oscura.core.edge_cases import InsufficientSamplesError
|
|
65
|
+
>>> raise InsufficientSamplesError("Need at least 100 samples", 100, 10)
|
|
66
|
+
|
|
67
|
+
References:
|
|
68
|
+
EDGE-002: Graceful Empty/Short Signal Handling
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
def __init__(self, message: str, required: int, available: int) -> None:
|
|
72
|
+
"""Initialize InsufficientSamplesError.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
message: Error message
|
|
76
|
+
required: Minimum samples required
|
|
77
|
+
available: Actual samples available
|
|
78
|
+
"""
|
|
79
|
+
self.required = required
|
|
80
|
+
self.available = available
|
|
81
|
+
full_message = f"{message} (required: {required}, available: {available})"
|
|
82
|
+
super().__init__(full_message)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def validate_signal(
|
|
86
|
+
signal: NDArray[np.float64],
|
|
87
|
+
*,
|
|
88
|
+
min_samples: int = 1,
|
|
89
|
+
allow_empty: bool = False,
|
|
90
|
+
name: str = "signal",
|
|
91
|
+
) -> NDArray[np.float64]:
|
|
92
|
+
"""Validate signal array for basic requirements.
|
|
93
|
+
|
|
94
|
+
: Empty trace returns informative error, not crash.
|
|
95
|
+
Checks for empty arrays and minimum sample requirements.
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
signal: Input signal array
|
|
99
|
+
min_samples: Minimum required samples (default: 1)
|
|
100
|
+
allow_empty: Allow empty arrays (default: False)
|
|
101
|
+
name: Signal name for error messages (default: "signal")
|
|
102
|
+
|
|
103
|
+
Returns:
|
|
104
|
+
Validated signal array
|
|
105
|
+
|
|
106
|
+
Raises:
|
|
107
|
+
EmptyTraceError: If signal is empty and allow_empty=False
|
|
108
|
+
InsufficientSamplesError: If signal has fewer than min_samples
|
|
109
|
+
ValueError: If signal is not 1D or has invalid shape
|
|
110
|
+
|
|
111
|
+
Example:
|
|
112
|
+
>>> import numpy as np
|
|
113
|
+
>>> from oscura.core.edge_cases import validate_signal
|
|
114
|
+
>>> signal = np.array([1.0, 2.0, 3.0])
|
|
115
|
+
>>> validated = validate_signal(signal, min_samples=2)
|
|
116
|
+
>>> # Empty signal raises error
|
|
117
|
+
>>> validate_signal(np.array([])) # Raises EmptyTraceError
|
|
118
|
+
|
|
119
|
+
References:
|
|
120
|
+
EDGE-002: Graceful Empty/Short Signal Handling
|
|
121
|
+
"""
|
|
122
|
+
# Check if array
|
|
123
|
+
if not isinstance(signal, np.ndarray):
|
|
124
|
+
raise ValueError(f"{name} must be a numpy array, got {type(signal)}")
|
|
125
|
+
|
|
126
|
+
# Check dimensions
|
|
127
|
+
if signal.ndim != 1:
|
|
128
|
+
raise ValueError(f"{name} must be 1-dimensional, got {signal.ndim}D")
|
|
129
|
+
|
|
130
|
+
# Check for empty
|
|
131
|
+
n_samples = len(signal)
|
|
132
|
+
if n_samples == 0:
|
|
133
|
+
if allow_empty:
|
|
134
|
+
return signal
|
|
135
|
+
else:
|
|
136
|
+
raise EmptyTraceError(f"{name} is empty (0 samples)")
|
|
137
|
+
|
|
138
|
+
# Check minimum samples
|
|
139
|
+
if n_samples < min_samples:
|
|
140
|
+
raise InsufficientSamplesError(
|
|
141
|
+
f"{name} has too few samples",
|
|
142
|
+
required=min_samples,
|
|
143
|
+
available=n_samples,
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
return signal
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def handle_empty_trace(default_value: float = np.nan) -> NDArray[np.float64]:
|
|
150
|
+
"""Return a safe default for empty trace operations.
|
|
151
|
+
|
|
152
|
+
: Empty trace returns informative error, not crash.
|
|
153
|
+
Provides graceful fallback for operations on empty traces.
|
|
154
|
+
|
|
155
|
+
Args:
|
|
156
|
+
default_value: Default value to return (default: NaN)
|
|
157
|
+
|
|
158
|
+
Returns:
|
|
159
|
+
Single-element array with default value
|
|
160
|
+
|
|
161
|
+
Example:
|
|
162
|
+
>>> from oscura.core.edge_cases import handle_empty_trace
|
|
163
|
+
>>> result = handle_empty_trace(0.0)
|
|
164
|
+
>>> print(result)
|
|
165
|
+
[0.]
|
|
166
|
+
|
|
167
|
+
References:
|
|
168
|
+
EDGE-002: Graceful Empty/Short Signal Handling
|
|
169
|
+
"""
|
|
170
|
+
return np.array([default_value])
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def check_single_sample(
|
|
174
|
+
signal: NDArray[np.float64],
|
|
175
|
+
operation: str = "operation",
|
|
176
|
+
) -> bool:
|
|
177
|
+
"""Check if signal has only one sample and warn.
|
|
178
|
+
|
|
179
|
+
: Handle traces with 1 sample.
|
|
180
|
+
Warns user that statistical operations may not be meaningful.
|
|
181
|
+
|
|
182
|
+
Args:
|
|
183
|
+
signal: Input signal
|
|
184
|
+
operation: Operation name for warning message
|
|
185
|
+
|
|
186
|
+
Returns:
|
|
187
|
+
True if signal has only 1 sample
|
|
188
|
+
|
|
189
|
+
Example:
|
|
190
|
+
>>> import numpy as np
|
|
191
|
+
>>> from oscura.core.edge_cases import check_single_sample
|
|
192
|
+
>>> signal = np.array([42.0])
|
|
193
|
+
>>> if check_single_sample(signal, "FFT"):
|
|
194
|
+
... print("Cannot compute FFT on single sample")
|
|
195
|
+
|
|
196
|
+
References:
|
|
197
|
+
EDGE-002: Graceful Empty/Short Signal Handling
|
|
198
|
+
"""
|
|
199
|
+
if len(signal) == 1:
|
|
200
|
+
warnings.warn(
|
|
201
|
+
f"Signal has only 1 sample. {operation} may not produce meaningful results.",
|
|
202
|
+
UserWarning,
|
|
203
|
+
stacklevel=2,
|
|
204
|
+
)
|
|
205
|
+
return True
|
|
206
|
+
return False
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def sanitize_signal(
|
|
210
|
+
signal: NDArray[np.float64],
|
|
211
|
+
*,
|
|
212
|
+
replace_nan: float | str = "interpolate",
|
|
213
|
+
replace_inf: float | str = "clip",
|
|
214
|
+
warn: bool = True,
|
|
215
|
+
) -> NDArray[np.float64]:
|
|
216
|
+
"""Remove or replace NaN and Inf values in signal.
|
|
217
|
+
|
|
218
|
+
: Handle NaN and Inf values gracefully.
|
|
219
|
+
Cleans signal data for robust analysis.
|
|
220
|
+
|
|
221
|
+
Args:
|
|
222
|
+
signal: Input signal array
|
|
223
|
+
replace_nan: How to handle NaN:
|
|
224
|
+
- "interpolate": Linear interpolation (default)
|
|
225
|
+
- "zero": Replace with 0
|
|
226
|
+
- "remove": Remove samples (changes length)
|
|
227
|
+
- float: Replace with specific value
|
|
228
|
+
replace_inf: How to handle Inf:
|
|
229
|
+
- "clip": Clip to min/max of finite values (default)
|
|
230
|
+
- "zero": Replace with 0
|
|
231
|
+
- "remove": Remove samples (changes length)
|
|
232
|
+
- float: Replace with specific value
|
|
233
|
+
warn: Issue warning if NaN/Inf found (default: True)
|
|
234
|
+
|
|
235
|
+
Returns:
|
|
236
|
+
Sanitized signal array
|
|
237
|
+
|
|
238
|
+
Raises:
|
|
239
|
+
ValueError: If replace_nan or replace_inf option is invalid.
|
|
240
|
+
|
|
241
|
+
Example:
|
|
242
|
+
>>> import numpy as np
|
|
243
|
+
>>> from oscura.core.edge_cases import sanitize_signal
|
|
244
|
+
>>> signal = np.array([1.0, np.nan, 3.0, np.inf, 5.0])
|
|
245
|
+
>>> clean = sanitize_signal(signal)
|
|
246
|
+
>>> print(clean)
|
|
247
|
+
[1. 2. 3. 5. 5.]
|
|
248
|
+
|
|
249
|
+
References:
|
|
250
|
+
EDGE-003: NaN/Inf Handling
|
|
251
|
+
"""
|
|
252
|
+
signal = signal.copy() # Don't modify input
|
|
253
|
+
n_nan = np.sum(np.isnan(signal))
|
|
254
|
+
n_inf = np.sum(np.isinf(signal))
|
|
255
|
+
|
|
256
|
+
# Warn if issues found
|
|
257
|
+
if warn and (n_nan > 0 or n_inf > 0):
|
|
258
|
+
warnings.warn(
|
|
259
|
+
f"Signal contains {n_nan} NaN and {n_inf} Inf values. Applying sanitization.",
|
|
260
|
+
UserWarning,
|
|
261
|
+
stacklevel=2,
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
# Handle NaN
|
|
265
|
+
if n_nan > 0:
|
|
266
|
+
if replace_nan == "interpolate":
|
|
267
|
+
signal = _interpolate_nan(signal)
|
|
268
|
+
elif replace_nan == "zero":
|
|
269
|
+
signal[np.isnan(signal)] = 0.0
|
|
270
|
+
elif replace_nan == "remove":
|
|
271
|
+
signal = signal[~np.isnan(signal)]
|
|
272
|
+
elif isinstance(replace_nan, int | float):
|
|
273
|
+
signal[np.isnan(signal)] = float(replace_nan)
|
|
274
|
+
else:
|
|
275
|
+
raise ValueError(f"Invalid replace_nan option: {replace_nan}")
|
|
276
|
+
|
|
277
|
+
# Handle Inf
|
|
278
|
+
if n_inf > 0:
|
|
279
|
+
if replace_inf == "clip":
|
|
280
|
+
finite_mask = np.isfinite(signal)
|
|
281
|
+
if np.any(finite_mask):
|
|
282
|
+
min_val = np.min(signal[finite_mask])
|
|
283
|
+
max_val = np.max(signal[finite_mask])
|
|
284
|
+
signal[signal == np.inf] = max_val
|
|
285
|
+
signal[signal == -np.inf] = min_val
|
|
286
|
+
else:
|
|
287
|
+
signal[np.isinf(signal)] = 0.0
|
|
288
|
+
elif replace_inf == "zero":
|
|
289
|
+
signal[np.isinf(signal)] = 0.0
|
|
290
|
+
elif replace_inf == "remove":
|
|
291
|
+
signal = signal[~np.isinf(signal)]
|
|
292
|
+
elif isinstance(replace_inf, int | float):
|
|
293
|
+
signal[np.isinf(signal)] = float(replace_inf)
|
|
294
|
+
else:
|
|
295
|
+
raise ValueError(f"Invalid replace_inf option: {replace_inf}")
|
|
296
|
+
|
|
297
|
+
return signal
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def _interpolate_nan(signal: NDArray[np.float64]) -> NDArray[np.float64]:
|
|
301
|
+
"""Interpolate NaN values using linear interpolation.
|
|
302
|
+
|
|
303
|
+
Args:
|
|
304
|
+
signal: Signal with NaN values
|
|
305
|
+
|
|
306
|
+
Returns:
|
|
307
|
+
Signal with NaN values interpolated
|
|
308
|
+
|
|
309
|
+
References:
|
|
310
|
+
EDGE-003: NaN/Inf Handling
|
|
311
|
+
"""
|
|
312
|
+
# Find NaN locations
|
|
313
|
+
nan_mask = np.isnan(signal)
|
|
314
|
+
|
|
315
|
+
if not np.any(nan_mask):
|
|
316
|
+
return signal
|
|
317
|
+
|
|
318
|
+
# Get valid indices and values
|
|
319
|
+
valid_mask = ~nan_mask
|
|
320
|
+
if not np.any(valid_mask):
|
|
321
|
+
# All NaN - replace with zeros
|
|
322
|
+
return np.zeros_like(signal)
|
|
323
|
+
|
|
324
|
+
valid_indices = np.where(valid_mask)[0]
|
|
325
|
+
valid_values = signal[valid_mask]
|
|
326
|
+
|
|
327
|
+
# Interpolate
|
|
328
|
+
nan_indices = np.where(nan_mask)[0]
|
|
329
|
+
interpolated = np.interp(nan_indices, valid_indices, valid_values)
|
|
330
|
+
|
|
331
|
+
# Replace NaN with interpolated values
|
|
332
|
+
result = signal.copy()
|
|
333
|
+
result[nan_mask] = interpolated
|
|
334
|
+
|
|
335
|
+
return result
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
def check_signal_quality(
|
|
339
|
+
signal: NDArray[np.float64],
|
|
340
|
+
*,
|
|
341
|
+
clipping_threshold: float = 0.95,
|
|
342
|
+
noise_floor_db: float = -60.0,
|
|
343
|
+
dc_offset_max: float = 0.1,
|
|
344
|
+
) -> SignalQualityReport:
|
|
345
|
+
"""Check signal quality and detect common issues.
|
|
346
|
+
|
|
347
|
+
: Detect clipping, noise floor, and DC offset problems.
|
|
348
|
+
Analyzes signal for quality issues that may affect results.
|
|
349
|
+
|
|
350
|
+
Args:
|
|
351
|
+
signal: Input signal array
|
|
352
|
+
clipping_threshold: Fraction of range for clipping detection (default: 0.95)
|
|
353
|
+
noise_floor_db: Expected noise floor in dB (default: -60)
|
|
354
|
+
dc_offset_max: Maximum acceptable DC offset (default: 0.1)
|
|
355
|
+
|
|
356
|
+
Returns:
|
|
357
|
+
SignalQualityReport with detected issues
|
|
358
|
+
|
|
359
|
+
Example:
|
|
360
|
+
>>> import numpy as np
|
|
361
|
+
>>> from oscura.core.edge_cases import check_signal_quality
|
|
362
|
+
>>> signal = np.random.randn(1000) + 0.5 # Signal with DC offset
|
|
363
|
+
>>> quality = check_signal_quality(signal, dc_offset_max=0.1)
|
|
364
|
+
>>> if quality.dc_offset_excessive:
|
|
365
|
+
... print(f"DC offset: {quality.dc_offset:.3f}")
|
|
366
|
+
|
|
367
|
+
References:
|
|
368
|
+
EDGE-001: Signal Quality Warnings
|
|
369
|
+
"""
|
|
370
|
+
# Calculate statistics
|
|
371
|
+
min_val = float(np.min(signal))
|
|
372
|
+
max_val = float(np.max(signal))
|
|
373
|
+
mean_val = float(np.mean(signal))
|
|
374
|
+
std_val = float(np.std(signal))
|
|
375
|
+
|
|
376
|
+
# Check for clipping
|
|
377
|
+
signal_range = max_val - min_val
|
|
378
|
+
clipping_detected = False
|
|
379
|
+
clipping_percent = 0.0
|
|
380
|
+
|
|
381
|
+
if signal_range > 0:
|
|
382
|
+
# Count samples near limits
|
|
383
|
+
upper_thresh = min_val + signal_range * clipping_threshold
|
|
384
|
+
lower_thresh = min_val + signal_range * (1 - clipping_threshold)
|
|
385
|
+
|
|
386
|
+
n_clipped = np.sum((signal >= upper_thresh) | (signal <= lower_thresh))
|
|
387
|
+
clipping_percent = float(100.0 * n_clipped / len(signal))
|
|
388
|
+
clipping_detected = clipping_percent > 1.0 # >1% clipping
|
|
389
|
+
|
|
390
|
+
# Check noise floor (estimate SNR)
|
|
391
|
+
if std_val > 0:
|
|
392
|
+
snr_db = 20 * np.log10(abs(mean_val) / std_val) if abs(mean_val) > 0 else -np.inf
|
|
393
|
+
else:
|
|
394
|
+
snr_db = np.inf
|
|
395
|
+
|
|
396
|
+
high_noise = snr_db < noise_floor_db
|
|
397
|
+
|
|
398
|
+
# Check DC offset
|
|
399
|
+
dc_offset = abs(mean_val)
|
|
400
|
+
dc_offset_excessive = dc_offset > dc_offset_max
|
|
401
|
+
|
|
402
|
+
return SignalQualityReport(
|
|
403
|
+
clipping_detected=clipping_detected,
|
|
404
|
+
clipping_percent=clipping_percent,
|
|
405
|
+
adc_min=min_val,
|
|
406
|
+
adc_max=max_val,
|
|
407
|
+
high_noise=high_noise,
|
|
408
|
+
noise_floor_db=float(snr_db),
|
|
409
|
+
snr_db=float(snr_db),
|
|
410
|
+
dc_offset_excessive=dc_offset_excessive,
|
|
411
|
+
dc_offset=dc_offset,
|
|
412
|
+
)
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
class SignalQualityReport:
|
|
416
|
+
"""Report of signal quality issues.
|
|
417
|
+
|
|
418
|
+
: Warnings included in measurement results.
|
|
419
|
+
|
|
420
|
+
Attributes:
|
|
421
|
+
clipping_detected: Whether clipping was detected
|
|
422
|
+
clipping_percent: Percentage of samples clipped
|
|
423
|
+
adc_min: Minimum signal value
|
|
424
|
+
adc_max: Maximum signal value
|
|
425
|
+
high_noise: Whether noise floor is excessive
|
|
426
|
+
noise_floor_db: Estimated noise floor in dB
|
|
427
|
+
snr_db: Signal-to-noise ratio in dB
|
|
428
|
+
dc_offset_excessive: Whether DC offset is excessive
|
|
429
|
+
dc_offset: DC offset value
|
|
430
|
+
|
|
431
|
+
Example:
|
|
432
|
+
>>> from oscura.core.edge_cases import check_signal_quality
|
|
433
|
+
>>> quality = check_signal_quality(signal)
|
|
434
|
+
>>> print(quality.summary())
|
|
435
|
+
|
|
436
|
+
References:
|
|
437
|
+
EDGE-001: Signal Quality Warnings
|
|
438
|
+
"""
|
|
439
|
+
|
|
440
|
+
def __init__(
|
|
441
|
+
self,
|
|
442
|
+
*,
|
|
443
|
+
clipping_detected: bool = False,
|
|
444
|
+
clipping_percent: float = 0.0,
|
|
445
|
+
adc_min: float = 0.0,
|
|
446
|
+
adc_max: float = 0.0,
|
|
447
|
+
high_noise: bool = False,
|
|
448
|
+
noise_floor_db: float = 0.0,
|
|
449
|
+
snr_db: float = 0.0,
|
|
450
|
+
dc_offset_excessive: bool = False,
|
|
451
|
+
dc_offset: float = 0.0,
|
|
452
|
+
) -> None:
|
|
453
|
+
"""Initialize SignalQualityReport.
|
|
454
|
+
|
|
455
|
+
Args:
|
|
456
|
+
clipping_detected: Clipping detected flag
|
|
457
|
+
clipping_percent: Percentage of clipped samples
|
|
458
|
+
adc_min: Minimum signal value
|
|
459
|
+
adc_max: Maximum signal value
|
|
460
|
+
high_noise: High noise flag
|
|
461
|
+
noise_floor_db: Noise floor in dB
|
|
462
|
+
snr_db: Signal-to-noise ratio in dB
|
|
463
|
+
dc_offset_excessive: Excessive DC offset flag
|
|
464
|
+
dc_offset: DC offset value
|
|
465
|
+
"""
|
|
466
|
+
self.clipping_detected = clipping_detected
|
|
467
|
+
self.clipping_percent = clipping_percent
|
|
468
|
+
self.adc_min = adc_min
|
|
469
|
+
self.adc_max = adc_max
|
|
470
|
+
self.high_noise = high_noise
|
|
471
|
+
self.noise_floor_db = noise_floor_db
|
|
472
|
+
self.snr_db = snr_db
|
|
473
|
+
self.dc_offset_excessive = dc_offset_excessive
|
|
474
|
+
self.dc_offset = dc_offset
|
|
475
|
+
|
|
476
|
+
def has_issues(self) -> bool:
|
|
477
|
+
"""Check if any quality issues were detected.
|
|
478
|
+
|
|
479
|
+
Returns:
|
|
480
|
+
True if any issues found
|
|
481
|
+
|
|
482
|
+
Example:
|
|
483
|
+
>>> if quality.has_issues():
|
|
484
|
+
... print(quality.summary())
|
|
485
|
+
|
|
486
|
+
References:
|
|
487
|
+
EDGE-001: Signal Quality Warnings
|
|
488
|
+
"""
|
|
489
|
+
return self.clipping_detected or self.high_noise or self.dc_offset_excessive
|
|
490
|
+
|
|
491
|
+
def summary(self) -> str:
|
|
492
|
+
"""Get text summary of quality issues.
|
|
493
|
+
|
|
494
|
+
Returns:
|
|
495
|
+
Summary string
|
|
496
|
+
|
|
497
|
+
Example:
|
|
498
|
+
>>> print(quality.summary())
|
|
499
|
+
Signal Quality Report:
|
|
500
|
+
✓ No clipping detected
|
|
501
|
+
⚠ High noise floor: -45.2 dB
|
|
502
|
+
✓ DC offset within limits
|
|
503
|
+
|
|
504
|
+
References:
|
|
505
|
+
EDGE-001: Signal Quality Warnings
|
|
506
|
+
"""
|
|
507
|
+
lines = ["Signal Quality Report:"]
|
|
508
|
+
|
|
509
|
+
# Clipping
|
|
510
|
+
if self.clipping_detected:
|
|
511
|
+
lines.append(f" ⚠ Clipping detected: {self.clipping_percent:.1f}% of samples")
|
|
512
|
+
lines.append(f" ADC range: {self.adc_min:.3f} to {self.adc_max:.3f}")
|
|
513
|
+
else:
|
|
514
|
+
lines.append(" ✓ No clipping detected")
|
|
515
|
+
|
|
516
|
+
# Noise
|
|
517
|
+
if self.high_noise:
|
|
518
|
+
lines.append(f" ⚠ High noise floor: {self.noise_floor_db:.1f} dB")
|
|
519
|
+
lines.append(f" SNR: {self.snr_db:.1f} dB")
|
|
520
|
+
else:
|
|
521
|
+
lines.append(f" ✓ Noise floor acceptable (SNR: {self.snr_db:.1f} dB)")
|
|
522
|
+
|
|
523
|
+
# DC offset
|
|
524
|
+
if self.dc_offset_excessive:
|
|
525
|
+
lines.append(f" ⚠ DC offset: {self.dc_offset:.3f}")
|
|
526
|
+
else:
|
|
527
|
+
lines.append(" ✓ DC offset within limits")
|
|
528
|
+
|
|
529
|
+
return "\n".join(lines)
|
|
530
|
+
|
|
531
|
+
|
|
532
|
+
__all__ = [
|
|
533
|
+
"EmptyTraceError",
|
|
534
|
+
"InsufficientSamplesError",
|
|
535
|
+
"SignalQualityReport",
|
|
536
|
+
"check_signal_quality",
|
|
537
|
+
"check_single_sample",
|
|
538
|
+
"handle_empty_trace",
|
|
539
|
+
"sanitize_signal",
|
|
540
|
+
"validate_signal",
|
|
541
|
+
]
|