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,598 @@
|
|
|
1
|
+
"""Signal validation and suitability checking for measurements.
|
|
2
|
+
|
|
3
|
+
This module provides helper functions to determine whether a signal is suitable
|
|
4
|
+
for specific measurements before attempting them. This helps avoid NaN results
|
|
5
|
+
and provides better user feedback.
|
|
6
|
+
|
|
7
|
+
Example:
|
|
8
|
+
>>> from oscura.analyzers.validation import is_suitable_for_frequency, get_valid_measurements
|
|
9
|
+
>>> suitable, reason = is_suitable_for_frequency(trace)
|
|
10
|
+
>>> if suitable:
|
|
11
|
+
... freq = frequency(trace)
|
|
12
|
+
>>> valid_measurements = get_valid_measurements(trace)
|
|
13
|
+
>>> print(f"Applicable measurements: {', '.join(valid_measurements)}")
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
from typing import TYPE_CHECKING
|
|
19
|
+
|
|
20
|
+
import numpy as np
|
|
21
|
+
|
|
22
|
+
if TYPE_CHECKING:
|
|
23
|
+
from oscura.core.types import WaveformTrace
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def is_suitable_for_frequency_measurement(trace: WaveformTrace) -> tuple[bool, str]:
|
|
27
|
+
"""Check if trace is suitable for frequency measurement.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
trace: Input waveform trace.
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
Tuple of (is_suitable, reason). If not suitable, reason explains why.
|
|
34
|
+
|
|
35
|
+
Example:
|
|
36
|
+
>>> suitable, reason = is_suitable_for_frequency_measurement(trace)
|
|
37
|
+
>>> if suitable:
|
|
38
|
+
... freq = frequency(trace)
|
|
39
|
+
... else:
|
|
40
|
+
... print(f"Cannot measure frequency: {reason}")
|
|
41
|
+
"""
|
|
42
|
+
from oscura.analyzers.waveform.measurements import _find_edges
|
|
43
|
+
|
|
44
|
+
data = trace.data
|
|
45
|
+
n = len(data)
|
|
46
|
+
|
|
47
|
+
# Check minimum samples
|
|
48
|
+
if n < 3:
|
|
49
|
+
return False, f"Insufficient samples ({n} < 3)"
|
|
50
|
+
|
|
51
|
+
# Check for variation (DC signal)
|
|
52
|
+
if np.std(data) < 1e-12:
|
|
53
|
+
return False, "Signal has no variation (DC or constant)"
|
|
54
|
+
|
|
55
|
+
# Check for edges
|
|
56
|
+
rising_edges = _find_edges(trace, "rising")
|
|
57
|
+
if len(rising_edges) < 2:
|
|
58
|
+
return (
|
|
59
|
+
False,
|
|
60
|
+
f"Insufficient edges for periodic measurement (found {len(rising_edges)} rising edges, need at least 2)",
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
# Check period consistency (is it periodic?)
|
|
64
|
+
if len(rising_edges) >= 3:
|
|
65
|
+
edge_times = rising_edges * trace.metadata.time_base
|
|
66
|
+
periods = np.diff(edge_times)
|
|
67
|
+
period_cv = np.std(periods) / np.mean(periods) if np.mean(periods) > 0 else float("inf")
|
|
68
|
+
|
|
69
|
+
if period_cv > 0.2:
|
|
70
|
+
return (
|
|
71
|
+
False,
|
|
72
|
+
f"Signal is not periodic (period variation: {period_cv * 100:.1f}% > 20%)",
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
return True, "Signal is suitable for frequency measurement"
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def is_suitable_for_duty_cycle_measurement(trace: WaveformTrace) -> tuple[bool, str]:
|
|
79
|
+
"""Check if trace is suitable for duty cycle measurement.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
trace: Input waveform trace.
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
Tuple of (is_suitable, reason).
|
|
86
|
+
|
|
87
|
+
Example:
|
|
88
|
+
>>> suitable, reason = is_suitable_for_duty_cycle_measurement(trace)
|
|
89
|
+
>>> if suitable:
|
|
90
|
+
... dc = duty_cycle(trace)
|
|
91
|
+
"""
|
|
92
|
+
from oscura.analyzers.waveform.measurements import _find_edges
|
|
93
|
+
|
|
94
|
+
# Check if suitable for frequency first (duty cycle needs periodic signal)
|
|
95
|
+
freq_suitable, freq_reason = is_suitable_for_frequency_measurement(trace)
|
|
96
|
+
if not freq_suitable:
|
|
97
|
+
return False, freq_reason
|
|
98
|
+
|
|
99
|
+
# Need both rising and falling edges
|
|
100
|
+
rising = _find_edges(trace, "rising")
|
|
101
|
+
falling = _find_edges(trace, "falling")
|
|
102
|
+
|
|
103
|
+
if len(rising) == 0:
|
|
104
|
+
return False, "No rising edges detected"
|
|
105
|
+
|
|
106
|
+
if len(falling) == 0:
|
|
107
|
+
return False, "No falling edges detected"
|
|
108
|
+
|
|
109
|
+
return True, "Signal is suitable for duty cycle measurement"
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def is_suitable_for_rise_time_measurement(trace: WaveformTrace) -> tuple[bool, str]:
|
|
113
|
+
"""Check if trace is suitable for rise time measurement.
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
trace: Input waveform trace.
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
Tuple of (is_suitable, reason).
|
|
120
|
+
|
|
121
|
+
Example:
|
|
122
|
+
>>> suitable, reason = is_suitable_for_rise_time_measurement(trace)
|
|
123
|
+
>>> if suitable:
|
|
124
|
+
... rt = rise_time(trace)
|
|
125
|
+
"""
|
|
126
|
+
from oscura.analyzers.waveform.measurements import _find_edges, _find_levels
|
|
127
|
+
|
|
128
|
+
data = trace.data
|
|
129
|
+
n = len(data)
|
|
130
|
+
|
|
131
|
+
if n < 3:
|
|
132
|
+
return False, f"Insufficient samples ({n} < 3)"
|
|
133
|
+
|
|
134
|
+
# Check for amplitude
|
|
135
|
+
low, high = _find_levels(data)
|
|
136
|
+
amplitude = high - low
|
|
137
|
+
|
|
138
|
+
if amplitude <= 0:
|
|
139
|
+
return False, "Signal has no amplitude (flat or inverted)"
|
|
140
|
+
|
|
141
|
+
# Check for rising edges
|
|
142
|
+
rising_edges = _find_edges(trace, "rising")
|
|
143
|
+
|
|
144
|
+
if len(rising_edges) == 0:
|
|
145
|
+
return False, "No rising edges detected"
|
|
146
|
+
|
|
147
|
+
# Check sample rate vs transition time
|
|
148
|
+
# Find a rising transition and count samples across it
|
|
149
|
+
sample_rate = trace.metadata.sample_rate
|
|
150
|
+
low_ref = low + 0.1 * amplitude
|
|
151
|
+
high_ref = low + 0.9 * amplitude
|
|
152
|
+
|
|
153
|
+
# Find first rising transition
|
|
154
|
+
crossings = np.where((data[:-1] < low_ref) & (data[1:] >= low_ref))[0]
|
|
155
|
+
|
|
156
|
+
if len(crossings) > 0:
|
|
157
|
+
idx = crossings[0]
|
|
158
|
+
# Count samples from 10% to 90%
|
|
159
|
+
remaining = data[idx:]
|
|
160
|
+
above_high = remaining >= high_ref
|
|
161
|
+
|
|
162
|
+
if np.any(above_high):
|
|
163
|
+
end_offset = np.argmax(above_high)
|
|
164
|
+
samples_in_transition = end_offset
|
|
165
|
+
|
|
166
|
+
if samples_in_transition < 2:
|
|
167
|
+
est_rise_time = samples_in_transition / sample_rate
|
|
168
|
+
recommended_rate = 10 / est_rise_time
|
|
169
|
+
return (
|
|
170
|
+
False,
|
|
171
|
+
f"Insufficient sample rate for transition (< 2 samples). "
|
|
172
|
+
f"Recommend sample rate > {recommended_rate:.3e} Hz",
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
return True, "Signal is suitable for rise time measurement"
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def is_suitable_for_fall_time_measurement(trace: WaveformTrace) -> tuple[bool, str]:
|
|
179
|
+
"""Check if trace is suitable for fall time measurement.
|
|
180
|
+
|
|
181
|
+
Args:
|
|
182
|
+
trace: Input waveform trace.
|
|
183
|
+
|
|
184
|
+
Returns:
|
|
185
|
+
Tuple of (is_suitable, reason).
|
|
186
|
+
"""
|
|
187
|
+
from oscura.analyzers.waveform.measurements import _find_edges, _find_levels
|
|
188
|
+
|
|
189
|
+
data = trace.data
|
|
190
|
+
n = len(data)
|
|
191
|
+
|
|
192
|
+
if n < 3:
|
|
193
|
+
return False, f"Insufficient samples ({n} < 3)"
|
|
194
|
+
|
|
195
|
+
low, high = _find_levels(data)
|
|
196
|
+
amplitude = high - low
|
|
197
|
+
|
|
198
|
+
if amplitude <= 0:
|
|
199
|
+
return False, "Signal has no amplitude (flat or inverted)"
|
|
200
|
+
|
|
201
|
+
# Check for falling edges
|
|
202
|
+
falling_edges = _find_edges(trace, "falling")
|
|
203
|
+
|
|
204
|
+
if len(falling_edges) == 0:
|
|
205
|
+
return False, "No falling edges detected"
|
|
206
|
+
|
|
207
|
+
return True, "Signal is suitable for fall time measurement"
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def is_suitable_for_jitter_measurement(trace: WaveformTrace) -> tuple[bool, str]:
|
|
211
|
+
"""Check if trace is suitable for jitter measurement.
|
|
212
|
+
|
|
213
|
+
Args:
|
|
214
|
+
trace: Input waveform trace.
|
|
215
|
+
|
|
216
|
+
Returns:
|
|
217
|
+
Tuple of (is_suitable, reason).
|
|
218
|
+
|
|
219
|
+
Example:
|
|
220
|
+
>>> suitable, reason = is_suitable_for_jitter_measurement(trace)
|
|
221
|
+
>>> if suitable:
|
|
222
|
+
... jitter = rms_jitter(trace)
|
|
223
|
+
"""
|
|
224
|
+
from oscura.analyzers.waveform.measurements import _find_edges
|
|
225
|
+
|
|
226
|
+
# Jitter needs periodic signal
|
|
227
|
+
freq_suitable, freq_reason = is_suitable_for_frequency_measurement(trace)
|
|
228
|
+
if not freq_suitable:
|
|
229
|
+
return False, freq_reason
|
|
230
|
+
|
|
231
|
+
# Need at least 3 edges (2 periods minimum)
|
|
232
|
+
edges = _find_edges(trace, "rising")
|
|
233
|
+
|
|
234
|
+
if len(edges) < 3:
|
|
235
|
+
return (
|
|
236
|
+
False,
|
|
237
|
+
f"Insufficient edges for jitter measurement (found {len(edges)}, need at least 3)",
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
return True, "Signal is suitable for jitter measurement"
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def get_valid_measurements(trace: WaveformTrace) -> list[str]:
|
|
244
|
+
"""Get list of measurements that are suitable for this trace.
|
|
245
|
+
|
|
246
|
+
Analyzes the signal characteristics and returns the names of all
|
|
247
|
+
measurement functions that should return valid (non-NaN) results.
|
|
248
|
+
|
|
249
|
+
Args:
|
|
250
|
+
trace: Input waveform trace.
|
|
251
|
+
|
|
252
|
+
Returns:
|
|
253
|
+
List of measurement function names (without parentheses).
|
|
254
|
+
|
|
255
|
+
Example:
|
|
256
|
+
>>> valid = get_valid_measurements(trace)
|
|
257
|
+
>>> print(f"Applicable measurements: {', '.join(valid)}")
|
|
258
|
+
>>> # Then apply only valid measurements
|
|
259
|
+
>>> for meas_name in valid:
|
|
260
|
+
... func = getattr(tk, meas_name)
|
|
261
|
+
... result = func(trace)
|
|
262
|
+
"""
|
|
263
|
+
valid = []
|
|
264
|
+
|
|
265
|
+
# These almost always work (just need data)
|
|
266
|
+
if len(trace.data) > 0:
|
|
267
|
+
valid.extend(["mean", "rms"])
|
|
268
|
+
|
|
269
|
+
if len(trace.data) >= 2:
|
|
270
|
+
valid.append("amplitude")
|
|
271
|
+
|
|
272
|
+
# Check edge-based measurements
|
|
273
|
+
suitable, _ = is_suitable_for_rise_time_measurement(trace)
|
|
274
|
+
if suitable:
|
|
275
|
+
valid.append("rise_time")
|
|
276
|
+
|
|
277
|
+
suitable, _ = is_suitable_for_fall_time_measurement(trace)
|
|
278
|
+
if suitable:
|
|
279
|
+
valid.append("fall_time")
|
|
280
|
+
|
|
281
|
+
# Check frequency/period
|
|
282
|
+
suitable, _ = is_suitable_for_frequency_measurement(trace)
|
|
283
|
+
if suitable:
|
|
284
|
+
valid.extend(["frequency", "period"])
|
|
285
|
+
|
|
286
|
+
# Check duty cycle
|
|
287
|
+
suitable, _ = is_suitable_for_duty_cycle_measurement(trace)
|
|
288
|
+
if suitable:
|
|
289
|
+
valid.append("duty_cycle")
|
|
290
|
+
|
|
291
|
+
# Check jitter
|
|
292
|
+
suitable, _ = is_suitable_for_jitter_measurement(trace)
|
|
293
|
+
if suitable:
|
|
294
|
+
valid.extend(["rms_jitter", "peak_to_peak_jitter"])
|
|
295
|
+
|
|
296
|
+
# Pulse width - needs edges but not necessarily periodic
|
|
297
|
+
from oscura.analyzers.waveform.measurements import _find_edges
|
|
298
|
+
|
|
299
|
+
rising = _find_edges(trace, "rising")
|
|
300
|
+
falling = _find_edges(trace, "falling")
|
|
301
|
+
|
|
302
|
+
if len(rising) > 0 and len(falling) > 0:
|
|
303
|
+
valid.append("pulse_width")
|
|
304
|
+
|
|
305
|
+
# Overshoot/undershoot - check amplitude
|
|
306
|
+
from oscura.analyzers.waveform.measurements import _find_levels
|
|
307
|
+
|
|
308
|
+
if len(trace.data) >= 3:
|
|
309
|
+
low, high = _find_levels(trace.data)
|
|
310
|
+
if high - low > 0:
|
|
311
|
+
valid.extend(["overshoot", "undershoot", "preshoot"])
|
|
312
|
+
|
|
313
|
+
# Slew rate - similar to rise/fall time
|
|
314
|
+
if "rise_time" in valid or "fall_time" in valid:
|
|
315
|
+
valid.append("slew_rate")
|
|
316
|
+
|
|
317
|
+
return valid
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
def analyze_signal_characteristics(trace: WaveformTrace) -> dict[str, bool | int | str | list[str]]:
|
|
321
|
+
"""Perform comprehensive signal characteristic analysis.
|
|
322
|
+
|
|
323
|
+
Determines signal type, edge counts, periodicity, and recommends
|
|
324
|
+
applicable measurements.
|
|
325
|
+
|
|
326
|
+
Args:
|
|
327
|
+
trace: Input waveform trace.
|
|
328
|
+
|
|
329
|
+
Returns:
|
|
330
|
+
Dictionary containing:
|
|
331
|
+
- sufficient_samples: bool - at least 16 samples
|
|
332
|
+
- has_amplitude: bool - signal has variation
|
|
333
|
+
- has_variation: bool - standard deviation > 0
|
|
334
|
+
- has_edges: bool - rising or falling edges detected
|
|
335
|
+
- is_periodic: bool - signal appears periodic
|
|
336
|
+
- edge_count: int - total edges (rising + falling)
|
|
337
|
+
- rising_edge_count: int - number of rising edges
|
|
338
|
+
- falling_edge_count: int - number of falling edges
|
|
339
|
+
- signal_type: str - classified type (dc, periodic_digital, etc.)
|
|
340
|
+
- recommended_measurements: list[str] - suggested measurements
|
|
341
|
+
|
|
342
|
+
Example:
|
|
343
|
+
>>> chars = analyze_signal_characteristics(trace)
|
|
344
|
+
>>> if chars['is_periodic']:
|
|
345
|
+
... print("Signal is periodic")
|
|
346
|
+
... print(f"Frequency measurement recommended: {'frequency' in chars['recommended_measurements']}")
|
|
347
|
+
"""
|
|
348
|
+
from oscura.analyzers.waveform.measurements import _find_edges
|
|
349
|
+
|
|
350
|
+
data = trace.data
|
|
351
|
+
n = len(data)
|
|
352
|
+
|
|
353
|
+
characteristics: dict[str, bool | int | str | list[str]] = {
|
|
354
|
+
"sufficient_samples": n >= 16,
|
|
355
|
+
"has_amplitude": False,
|
|
356
|
+
"has_variation": False,
|
|
357
|
+
"has_edges": False,
|
|
358
|
+
"is_periodic": False,
|
|
359
|
+
"edge_count": 0,
|
|
360
|
+
"rising_edge_count": 0,
|
|
361
|
+
"falling_edge_count": 0,
|
|
362
|
+
"signal_type": "unknown",
|
|
363
|
+
"recommended_measurements": [],
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
# Check variation
|
|
367
|
+
std = np.std(data)
|
|
368
|
+
characteristics["has_variation"] = std > 1e-12
|
|
369
|
+
|
|
370
|
+
# Check amplitude
|
|
371
|
+
amplitude = np.max(data) - np.min(data)
|
|
372
|
+
characteristics["has_amplitude"] = amplitude > 1e-12
|
|
373
|
+
|
|
374
|
+
if not characteristics["has_variation"]:
|
|
375
|
+
characteristics["signal_type"] = "dc"
|
|
376
|
+
characteristics["recommended_measurements"] = ["mean", "rms"]
|
|
377
|
+
return characteristics
|
|
378
|
+
|
|
379
|
+
# Count edges
|
|
380
|
+
rising_edges = _find_edges(trace, "rising")
|
|
381
|
+
falling_edges = _find_edges(trace, "falling")
|
|
382
|
+
|
|
383
|
+
rising_edge_count = len(rising_edges)
|
|
384
|
+
falling_edge_count = len(falling_edges)
|
|
385
|
+
edge_count = rising_edge_count + falling_edge_count
|
|
386
|
+
|
|
387
|
+
characteristics["rising_edge_count"] = rising_edge_count
|
|
388
|
+
characteristics["falling_edge_count"] = falling_edge_count
|
|
389
|
+
characteristics["edge_count"] = edge_count
|
|
390
|
+
characteristics["has_edges"] = edge_count > 0
|
|
391
|
+
|
|
392
|
+
# Check periodicity
|
|
393
|
+
if len(rising_edges) >= 3:
|
|
394
|
+
periods = np.diff(rising_edges)
|
|
395
|
+
period_cv = np.std(periods) / np.mean(periods) if np.mean(periods) > 0 else float("inf")
|
|
396
|
+
|
|
397
|
+
if period_cv < 0.2: # Less than 20% variation
|
|
398
|
+
characteristics["is_periodic"] = True
|
|
399
|
+
|
|
400
|
+
# Classify signal type
|
|
401
|
+
if not characteristics["has_edges"]:
|
|
402
|
+
# No edges - check if analog periodic
|
|
403
|
+
if n >= 16:
|
|
404
|
+
fft_result = np.abs(np.fft.rfft(data - np.mean(data)))
|
|
405
|
+
peak_power = np.max(fft_result[1:]) if len(fft_result) > 1 else 0
|
|
406
|
+
avg_power = np.mean(fft_result[1:]) if len(fft_result) > 1 else 0
|
|
407
|
+
|
|
408
|
+
if peak_power > 10 * avg_power:
|
|
409
|
+
characteristics["signal_type"] = "periodic_analog"
|
|
410
|
+
else:
|
|
411
|
+
characteristics["signal_type"] = "noise"
|
|
412
|
+
else:
|
|
413
|
+
characteristics["signal_type"] = "unknown"
|
|
414
|
+
elif characteristics["is_periodic"]:
|
|
415
|
+
characteristics["signal_type"] = "periodic_digital"
|
|
416
|
+
else:
|
|
417
|
+
characteristics["signal_type"] = "aperiodic_digital"
|
|
418
|
+
|
|
419
|
+
# Recommend measurements
|
|
420
|
+
recommended = get_valid_measurements(trace)
|
|
421
|
+
characteristics["recommended_measurements"] = recommended
|
|
422
|
+
|
|
423
|
+
return characteristics
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
def get_measurement_requirements(measurement_name: str) -> dict[str, str | int | list[str]]:
|
|
427
|
+
"""Get requirements for a specific measurement.
|
|
428
|
+
|
|
429
|
+
Args:
|
|
430
|
+
measurement_name: Name of the measurement function.
|
|
431
|
+
|
|
432
|
+
Returns:
|
|
433
|
+
Dictionary containing:
|
|
434
|
+
- description: str - what the measurement computes
|
|
435
|
+
- min_samples: int - minimum data points needed
|
|
436
|
+
- required_signal_types: list[str] - suitable signal types
|
|
437
|
+
- required_features: list[str] - required signal features
|
|
438
|
+
- common_nan_causes: list[str] - common reasons for NaN
|
|
439
|
+
|
|
440
|
+
Example:
|
|
441
|
+
>>> reqs = get_measurement_requirements('frequency')
|
|
442
|
+
>>> print(f"Minimum samples: {reqs['min_samples']}")
|
|
443
|
+
>>> print(f"Required features: {', '.join(reqs['required_features'])}")
|
|
444
|
+
"""
|
|
445
|
+
requirements = {
|
|
446
|
+
"frequency": {
|
|
447
|
+
"description": "Measures the repetition rate of a periodic signal",
|
|
448
|
+
"min_samples": 3,
|
|
449
|
+
"required_signal_types": ["periodic_digital", "periodic_analog"],
|
|
450
|
+
"required_features": ["edges", "periodic"],
|
|
451
|
+
"common_nan_causes": [
|
|
452
|
+
"DC signal (no transitions)",
|
|
453
|
+
"Aperiodic signal (< 2 edges)",
|
|
454
|
+
"Highly variable period (> 20% variation)",
|
|
455
|
+
],
|
|
456
|
+
},
|
|
457
|
+
"period": {
|
|
458
|
+
"description": "Measures time between consecutive edges",
|
|
459
|
+
"min_samples": 3,
|
|
460
|
+
"required_signal_types": ["periodic_digital", "periodic_analog"],
|
|
461
|
+
"required_features": ["edges", "periodic"],
|
|
462
|
+
"common_nan_causes": [
|
|
463
|
+
"DC signal",
|
|
464
|
+
"Fewer than 2 edges detected",
|
|
465
|
+
"Aperiodic signal",
|
|
466
|
+
],
|
|
467
|
+
},
|
|
468
|
+
"duty_cycle": {
|
|
469
|
+
"description": "Measures ratio of high time to period",
|
|
470
|
+
"min_samples": 3,
|
|
471
|
+
"required_signal_types": ["periodic_digital"],
|
|
472
|
+
"required_features": ["rising_edges", "falling_edges", "periodic"],
|
|
473
|
+
"common_nan_causes": [
|
|
474
|
+
"Non-periodic signal",
|
|
475
|
+
"Missing rising or falling edges",
|
|
476
|
+
"DC signal",
|
|
477
|
+
],
|
|
478
|
+
},
|
|
479
|
+
"rise_time": {
|
|
480
|
+
"description": "Measures time for rising edge transition",
|
|
481
|
+
"min_samples": 3,
|
|
482
|
+
"required_signal_types": ["periodic_digital", "aperiodic_digital", "periodic_analog"],
|
|
483
|
+
"required_features": ["rising_edges", "amplitude"],
|
|
484
|
+
"common_nan_causes": [
|
|
485
|
+
"No rising edges",
|
|
486
|
+
"Insufficient sample rate",
|
|
487
|
+
"DC signal",
|
|
488
|
+
],
|
|
489
|
+
},
|
|
490
|
+
"fall_time": {
|
|
491
|
+
"description": "Measures time for falling edge transition",
|
|
492
|
+
"min_samples": 3,
|
|
493
|
+
"required_signal_types": ["periodic_digital", "aperiodic_digital", "periodic_analog"],
|
|
494
|
+
"required_features": ["falling_edges", "amplitude"],
|
|
495
|
+
"common_nan_causes": [
|
|
496
|
+
"No falling edges",
|
|
497
|
+
"Insufficient sample rate",
|
|
498
|
+
"DC signal",
|
|
499
|
+
],
|
|
500
|
+
},
|
|
501
|
+
"pulse_width": {
|
|
502
|
+
"description": "Measures duration of high or low pulse",
|
|
503
|
+
"min_samples": 3,
|
|
504
|
+
"required_signal_types": ["periodic_digital", "aperiodic_digital"],
|
|
505
|
+
"required_features": ["rising_edges", "falling_edges"],
|
|
506
|
+
"common_nan_causes": [
|
|
507
|
+
"Missing edge pairs",
|
|
508
|
+
"DC signal",
|
|
509
|
+
"Incomplete pulses",
|
|
510
|
+
],
|
|
511
|
+
},
|
|
512
|
+
"amplitude": {
|
|
513
|
+
"description": "Measures peak-to-peak voltage",
|
|
514
|
+
"min_samples": 2,
|
|
515
|
+
"required_signal_types": ["all"],
|
|
516
|
+
"required_features": [],
|
|
517
|
+
"common_nan_causes": ["Fewer than 2 samples"],
|
|
518
|
+
},
|
|
519
|
+
"mean": {
|
|
520
|
+
"description": "Calculates DC level (average voltage)",
|
|
521
|
+
"min_samples": 1,
|
|
522
|
+
"required_signal_types": ["all"],
|
|
523
|
+
"required_features": [],
|
|
524
|
+
"common_nan_causes": ["No data"],
|
|
525
|
+
},
|
|
526
|
+
"rms": {
|
|
527
|
+
"description": "Calculates root-mean-square voltage",
|
|
528
|
+
"min_samples": 1,
|
|
529
|
+
"required_signal_types": ["all"],
|
|
530
|
+
"required_features": [],
|
|
531
|
+
"common_nan_causes": ["No data"],
|
|
532
|
+
},
|
|
533
|
+
"overshoot": {
|
|
534
|
+
"description": "Measures overshoot above high level",
|
|
535
|
+
"min_samples": 3,
|
|
536
|
+
"required_signal_types": ["periodic_digital", "aperiodic_digital"],
|
|
537
|
+
"required_features": ["amplitude"],
|
|
538
|
+
"common_nan_causes": ["No amplitude", "DC signal"],
|
|
539
|
+
},
|
|
540
|
+
"undershoot": {
|
|
541
|
+
"description": "Measures undershoot below low level",
|
|
542
|
+
"min_samples": 3,
|
|
543
|
+
"required_signal_types": ["periodic_digital", "aperiodic_digital"],
|
|
544
|
+
"required_features": ["amplitude"],
|
|
545
|
+
"common_nan_causes": ["No amplitude", "DC signal"],
|
|
546
|
+
},
|
|
547
|
+
"slew_rate": {
|
|
548
|
+
"description": "Measures dV/dt during transitions",
|
|
549
|
+
"min_samples": 3,
|
|
550
|
+
"required_signal_types": ["periodic_digital", "aperiodic_digital"],
|
|
551
|
+
"required_features": ["edges", "amplitude"],
|
|
552
|
+
"common_nan_causes": ["No edges", "No amplitude", "DC signal"],
|
|
553
|
+
},
|
|
554
|
+
"rms_jitter": {
|
|
555
|
+
"description": "Measures timing uncertainty (RMS)",
|
|
556
|
+
"min_samples": 3,
|
|
557
|
+
"required_signal_types": ["periodic_digital"],
|
|
558
|
+
"required_features": ["edges", "periodic"],
|
|
559
|
+
"common_nan_causes": [
|
|
560
|
+
"Fewer than 3 edges",
|
|
561
|
+
"Non-periodic signal",
|
|
562
|
+
"DC signal",
|
|
563
|
+
],
|
|
564
|
+
},
|
|
565
|
+
"peak_to_peak_jitter": {
|
|
566
|
+
"description": "Measures peak-to-peak timing variation",
|
|
567
|
+
"min_samples": 3,
|
|
568
|
+
"required_signal_types": ["periodic_digital"],
|
|
569
|
+
"required_features": ["edges", "periodic"],
|
|
570
|
+
"common_nan_causes": [
|
|
571
|
+
"Fewer than 3 edges",
|
|
572
|
+
"Non-periodic signal",
|
|
573
|
+
"DC signal",
|
|
574
|
+
],
|
|
575
|
+
},
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
default = {
|
|
579
|
+
"description": "Measurement not documented",
|
|
580
|
+
"min_samples": 1,
|
|
581
|
+
"required_signal_types": ["unknown"],
|
|
582
|
+
"required_features": [],
|
|
583
|
+
"common_nan_causes": ["Check measurement documentation"],
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
return requirements.get(measurement_name, default) # type: ignore[return-value]
|
|
587
|
+
|
|
588
|
+
|
|
589
|
+
__all__ = [
|
|
590
|
+
"analyze_signal_characteristics",
|
|
591
|
+
"get_measurement_requirements",
|
|
592
|
+
"get_valid_measurements",
|
|
593
|
+
"is_suitable_for_duty_cycle_measurement",
|
|
594
|
+
"is_suitable_for_fall_time_measurement",
|
|
595
|
+
"is_suitable_for_frequency_measurement",
|
|
596
|
+
"is_suitable_for_jitter_measurement",
|
|
597
|
+
"is_suitable_for_rise_time_measurement",
|
|
598
|
+
]
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""Waveform analysis module.
|
|
2
|
+
|
|
3
|
+
Provides timing and amplitude measurements for analog waveforms.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from oscura.analyzers.waveform.measurements import (
|
|
7
|
+
amplitude,
|
|
8
|
+
duty_cycle,
|
|
9
|
+
fall_time,
|
|
10
|
+
frequency,
|
|
11
|
+
mean,
|
|
12
|
+
measure,
|
|
13
|
+
overshoot,
|
|
14
|
+
period,
|
|
15
|
+
preshoot,
|
|
16
|
+
pulse_width,
|
|
17
|
+
rise_time,
|
|
18
|
+
rms,
|
|
19
|
+
undershoot,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
__all__ = [
|
|
23
|
+
"amplitude",
|
|
24
|
+
"duty_cycle",
|
|
25
|
+
"fall_time",
|
|
26
|
+
"frequency",
|
|
27
|
+
"mean",
|
|
28
|
+
"measure",
|
|
29
|
+
"overshoot",
|
|
30
|
+
"period",
|
|
31
|
+
"preshoot",
|
|
32
|
+
"pulse_width",
|
|
33
|
+
"rise_time",
|
|
34
|
+
"rms",
|
|
35
|
+
"undershoot",
|
|
36
|
+
]
|