oscura 0.0.1__py3-none-any.whl → 0.1.1__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.1.dist-info/METADATA +300 -0
- oscura-0.1.1.dist-info/RECORD +463 -0
- oscura-0.1.1.dist-info/entry_points.txt +2 -0
- {oscura-0.0.1.dist-info → oscura-0.1.1.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.1.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,581 @@
|
|
|
1
|
+
"""Pulse width and glitch triggering for Oscura.
|
|
2
|
+
|
|
3
|
+
Provides pulse width triggering, glitch detection, and runt pulse
|
|
4
|
+
detection for signal integrity analysis.
|
|
5
|
+
|
|
6
|
+
Example:
|
|
7
|
+
>>> from oscura.triggering.pulse import PulseWidthTrigger, find_glitches
|
|
8
|
+
>>> # Find pulses between 100ns and 200ns
|
|
9
|
+
>>> trigger = PulseWidthTrigger(level=1.5, min_width=100e-9, max_width=200e-9)
|
|
10
|
+
>>> events = trigger.find_events(trace)
|
|
11
|
+
>>> # Find glitches shorter than 50ns
|
|
12
|
+
>>> glitches = find_glitches(trace, max_width=50e-9)
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from dataclasses import dataclass
|
|
18
|
+
from typing import Literal
|
|
19
|
+
|
|
20
|
+
import numpy as np
|
|
21
|
+
|
|
22
|
+
from oscura.core.exceptions import AnalysisError
|
|
23
|
+
from oscura.core.types import DigitalTrace, WaveformTrace
|
|
24
|
+
from oscura.triggering.base import (
|
|
25
|
+
Trigger,
|
|
26
|
+
TriggerEvent,
|
|
27
|
+
TriggerType,
|
|
28
|
+
interpolate_crossing,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass
|
|
33
|
+
class PulseInfo:
|
|
34
|
+
"""Information about a detected pulse.
|
|
35
|
+
|
|
36
|
+
Attributes:
|
|
37
|
+
start_time: Start time of pulse in seconds.
|
|
38
|
+
end_time: End time of pulse in seconds.
|
|
39
|
+
width: Pulse width in seconds.
|
|
40
|
+
polarity: "positive" or "negative".
|
|
41
|
+
start_index: Sample index at pulse start.
|
|
42
|
+
end_index: Sample index at pulse end.
|
|
43
|
+
amplitude: Peak amplitude during pulse.
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
start_time: float
|
|
47
|
+
end_time: float
|
|
48
|
+
width: float
|
|
49
|
+
polarity: Literal["positive", "negative"]
|
|
50
|
+
start_index: int
|
|
51
|
+
end_index: int
|
|
52
|
+
amplitude: float
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class PulseWidthTrigger(Trigger):
|
|
56
|
+
"""Pulse width trigger for detecting pulses in a width range.
|
|
57
|
+
|
|
58
|
+
Triggers on pulses that fall within the specified width range.
|
|
59
|
+
|
|
60
|
+
Attributes:
|
|
61
|
+
level: Threshold level for pulse detection.
|
|
62
|
+
polarity: Pulse polarity - "positive", "negative", or "either".
|
|
63
|
+
min_width: Minimum pulse width (None for no minimum).
|
|
64
|
+
max_width: Maximum pulse width (None for no maximum).
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
def __init__(
|
|
68
|
+
self,
|
|
69
|
+
level: float,
|
|
70
|
+
polarity: Literal["positive", "negative", "either"] = "positive",
|
|
71
|
+
min_width: float | None = None,
|
|
72
|
+
max_width: float | None = None,
|
|
73
|
+
) -> None:
|
|
74
|
+
"""Initialize pulse width trigger.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
level: Threshold level for pulse detection.
|
|
78
|
+
polarity: Pulse polarity to detect.
|
|
79
|
+
min_width: Minimum pulse width in seconds.
|
|
80
|
+
max_width: Maximum pulse width in seconds.
|
|
81
|
+
|
|
82
|
+
Raises:
|
|
83
|
+
AnalysisError: If min_width is greater than max_width.
|
|
84
|
+
"""
|
|
85
|
+
self.level = level
|
|
86
|
+
self.polarity = polarity
|
|
87
|
+
self.min_width = min_width
|
|
88
|
+
self.max_width = max_width
|
|
89
|
+
|
|
90
|
+
if min_width is not None and max_width is not None and min_width > max_width:
|
|
91
|
+
raise AnalysisError("min_width cannot be greater than max_width")
|
|
92
|
+
|
|
93
|
+
def find_events(
|
|
94
|
+
self,
|
|
95
|
+
trace: WaveformTrace | DigitalTrace,
|
|
96
|
+
) -> list[TriggerEvent]:
|
|
97
|
+
"""Find pulses matching the width criteria.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
trace: Input trace.
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
List of trigger events for matching pulses.
|
|
104
|
+
"""
|
|
105
|
+
pulses = self._find_all_pulses(trace)
|
|
106
|
+
|
|
107
|
+
# Filter by width
|
|
108
|
+
events: list[TriggerEvent] = []
|
|
109
|
+
for pulse in pulses:
|
|
110
|
+
if self.min_width is not None and pulse.width < self.min_width:
|
|
111
|
+
continue
|
|
112
|
+
if self.max_width is not None and pulse.width > self.max_width:
|
|
113
|
+
continue
|
|
114
|
+
|
|
115
|
+
events.append(
|
|
116
|
+
TriggerEvent(
|
|
117
|
+
timestamp=pulse.start_time,
|
|
118
|
+
sample_index=pulse.start_index,
|
|
119
|
+
event_type=TriggerType.PULSE_WIDTH,
|
|
120
|
+
level=pulse.amplitude,
|
|
121
|
+
duration=pulse.width,
|
|
122
|
+
data={
|
|
123
|
+
"polarity": pulse.polarity,
|
|
124
|
+
"end_time": pulse.end_time,
|
|
125
|
+
"end_index": pulse.end_index,
|
|
126
|
+
},
|
|
127
|
+
)
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
return events
|
|
131
|
+
|
|
132
|
+
def _find_all_pulses(
|
|
133
|
+
self,
|
|
134
|
+
trace: WaveformTrace | DigitalTrace,
|
|
135
|
+
) -> list[PulseInfo]:
|
|
136
|
+
"""Find all pulses in the trace."""
|
|
137
|
+
if isinstance(trace, DigitalTrace):
|
|
138
|
+
data = trace.data.astype(np.float64)
|
|
139
|
+
level = 0.5
|
|
140
|
+
else:
|
|
141
|
+
data = trace.data
|
|
142
|
+
level = self.level
|
|
143
|
+
|
|
144
|
+
sample_period = trace.metadata.time_base
|
|
145
|
+
pulses: list[PulseInfo] = []
|
|
146
|
+
|
|
147
|
+
# Find all threshold crossings
|
|
148
|
+
above = data >= level
|
|
149
|
+
below = data < level
|
|
150
|
+
|
|
151
|
+
# Rising edges: transition from below to above
|
|
152
|
+
rising = np.where(below[:-1] & above[1:])[0]
|
|
153
|
+
# Falling edges: transition from above to below
|
|
154
|
+
falling = np.where(above[:-1] & below[1:])[0]
|
|
155
|
+
|
|
156
|
+
if self.polarity in ("positive", "either"):
|
|
157
|
+
# Positive pulses: rising -> falling
|
|
158
|
+
for r_idx in rising:
|
|
159
|
+
# Find next falling edge
|
|
160
|
+
next_falling = falling[falling > r_idx]
|
|
161
|
+
if len(next_falling) == 0:
|
|
162
|
+
continue
|
|
163
|
+
f_idx = next_falling[0]
|
|
164
|
+
|
|
165
|
+
start_time = interpolate_crossing(data, r_idx, level, sample_period, True)
|
|
166
|
+
end_time = interpolate_crossing(data, f_idx, level, sample_period, False)
|
|
167
|
+
width = end_time - start_time
|
|
168
|
+
|
|
169
|
+
# Get peak amplitude
|
|
170
|
+
pulse_data = data[r_idx : f_idx + 1]
|
|
171
|
+
amplitude = float(np.max(pulse_data)) if len(pulse_data) > 0 else level
|
|
172
|
+
|
|
173
|
+
pulses.append(
|
|
174
|
+
PulseInfo(
|
|
175
|
+
start_time=start_time,
|
|
176
|
+
end_time=end_time,
|
|
177
|
+
width=width,
|
|
178
|
+
polarity="positive",
|
|
179
|
+
start_index=int(r_idx),
|
|
180
|
+
end_index=int(f_idx),
|
|
181
|
+
amplitude=amplitude,
|
|
182
|
+
)
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
if self.polarity in ("negative", "either"):
|
|
186
|
+
# Negative pulses: falling -> rising
|
|
187
|
+
for f_idx in falling:
|
|
188
|
+
# Find next rising edge
|
|
189
|
+
next_rising = rising[rising > f_idx]
|
|
190
|
+
if len(next_rising) == 0:
|
|
191
|
+
continue
|
|
192
|
+
r_idx = next_rising[0]
|
|
193
|
+
|
|
194
|
+
start_time = interpolate_crossing(data, f_idx, level, sample_period, False)
|
|
195
|
+
end_time = interpolate_crossing(data, r_idx, level, sample_period, True)
|
|
196
|
+
width = end_time - start_time
|
|
197
|
+
|
|
198
|
+
# Get peak (minimum) amplitude
|
|
199
|
+
pulse_data = data[f_idx : r_idx + 1]
|
|
200
|
+
amplitude = float(np.min(pulse_data)) if len(pulse_data) > 0 else level
|
|
201
|
+
|
|
202
|
+
pulses.append(
|
|
203
|
+
PulseInfo(
|
|
204
|
+
start_time=start_time,
|
|
205
|
+
end_time=end_time,
|
|
206
|
+
width=width,
|
|
207
|
+
polarity="negative",
|
|
208
|
+
start_index=int(f_idx),
|
|
209
|
+
end_index=int(r_idx),
|
|
210
|
+
amplitude=amplitude,
|
|
211
|
+
)
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
# Sort by start time
|
|
215
|
+
pulses.sort(key=lambda p: p.start_time)
|
|
216
|
+
return pulses
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
class GlitchTrigger(Trigger):
|
|
220
|
+
"""Glitch trigger for detecting narrow pulses.
|
|
221
|
+
|
|
222
|
+
Glitches are pulses shorter than a maximum width threshold.
|
|
223
|
+
|
|
224
|
+
Attributes:
|
|
225
|
+
level: Threshold level.
|
|
226
|
+
max_width: Maximum pulse width to be considered a glitch.
|
|
227
|
+
polarity: Glitch polarity - "positive", "negative", or "either".
|
|
228
|
+
"""
|
|
229
|
+
|
|
230
|
+
def __init__(
|
|
231
|
+
self,
|
|
232
|
+
level: float,
|
|
233
|
+
max_width: float = 100e-9,
|
|
234
|
+
polarity: Literal["positive", "negative", "either"] = "either",
|
|
235
|
+
) -> None:
|
|
236
|
+
"""Initialize glitch trigger.
|
|
237
|
+
|
|
238
|
+
Args:
|
|
239
|
+
level: Threshold level.
|
|
240
|
+
max_width: Maximum pulse width to trigger (in seconds).
|
|
241
|
+
polarity: Glitch polarity to detect.
|
|
242
|
+
"""
|
|
243
|
+
self.level = level
|
|
244
|
+
self.max_width = max_width
|
|
245
|
+
self.polarity = polarity
|
|
246
|
+
|
|
247
|
+
def find_events(
|
|
248
|
+
self,
|
|
249
|
+
trace: WaveformTrace | DigitalTrace,
|
|
250
|
+
) -> list[TriggerEvent]:
|
|
251
|
+
"""Find all glitches in the trace.
|
|
252
|
+
|
|
253
|
+
Args:
|
|
254
|
+
trace: Input trace.
|
|
255
|
+
|
|
256
|
+
Returns:
|
|
257
|
+
List of trigger events for each glitch.
|
|
258
|
+
"""
|
|
259
|
+
pulse_trigger = PulseWidthTrigger(
|
|
260
|
+
level=self.level,
|
|
261
|
+
polarity=self.polarity,
|
|
262
|
+
min_width=None,
|
|
263
|
+
max_width=self.max_width,
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
events = pulse_trigger.find_events(trace)
|
|
267
|
+
|
|
268
|
+
# Reclassify as glitch events
|
|
269
|
+
for event in events:
|
|
270
|
+
event.event_type = TriggerType.GLITCH
|
|
271
|
+
|
|
272
|
+
return events
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
class RuntTrigger(Trigger):
|
|
276
|
+
"""Runt pulse trigger for detecting incomplete transitions.
|
|
277
|
+
|
|
278
|
+
Runt pulses cross one threshold but not the other, indicating
|
|
279
|
+
incomplete signal transitions.
|
|
280
|
+
|
|
281
|
+
Attributes:
|
|
282
|
+
low_threshold: Lower threshold level.
|
|
283
|
+
high_threshold: Upper threshold level.
|
|
284
|
+
polarity: Runt polarity - "positive", "negative", or "either".
|
|
285
|
+
"""
|
|
286
|
+
|
|
287
|
+
def __init__(
|
|
288
|
+
self,
|
|
289
|
+
low_threshold: float,
|
|
290
|
+
high_threshold: float,
|
|
291
|
+
polarity: Literal["positive", "negative", "either"] = "either",
|
|
292
|
+
) -> None:
|
|
293
|
+
"""Initialize runt trigger.
|
|
294
|
+
|
|
295
|
+
Args:
|
|
296
|
+
low_threshold: Lower threshold (e.g., logic low).
|
|
297
|
+
high_threshold: Upper threshold (e.g., logic high).
|
|
298
|
+
polarity: "positive" for rising runts, "negative" for falling.
|
|
299
|
+
|
|
300
|
+
Raises:
|
|
301
|
+
AnalysisError: If low_threshold is not less than high_threshold.
|
|
302
|
+
"""
|
|
303
|
+
if low_threshold >= high_threshold:
|
|
304
|
+
raise AnalysisError("low_threshold must be less than high_threshold")
|
|
305
|
+
|
|
306
|
+
self.low_threshold = low_threshold
|
|
307
|
+
self.high_threshold = high_threshold
|
|
308
|
+
self.polarity = polarity
|
|
309
|
+
|
|
310
|
+
def find_events(
|
|
311
|
+
self,
|
|
312
|
+
trace: WaveformTrace | DigitalTrace,
|
|
313
|
+
) -> list[TriggerEvent]:
|
|
314
|
+
"""Find all runt pulses in the trace.
|
|
315
|
+
|
|
316
|
+
Args:
|
|
317
|
+
trace: Input trace.
|
|
318
|
+
|
|
319
|
+
Returns:
|
|
320
|
+
List of trigger events for each runt pulse.
|
|
321
|
+
"""
|
|
322
|
+
if isinstance(trace, DigitalTrace):
|
|
323
|
+
# Digital traces don't have runts
|
|
324
|
+
return []
|
|
325
|
+
|
|
326
|
+
data = trace.data
|
|
327
|
+
sample_period = trace.metadata.time_base
|
|
328
|
+
events: list[TriggerEvent] = []
|
|
329
|
+
|
|
330
|
+
# Track signal zones
|
|
331
|
+
# Zone 0: below low_threshold
|
|
332
|
+
# Zone 1: between thresholds
|
|
333
|
+
# Zone 2: above high_threshold
|
|
334
|
+
def get_zone(value: float) -> int:
|
|
335
|
+
if value < self.low_threshold:
|
|
336
|
+
return 0
|
|
337
|
+
elif value > self.high_threshold:
|
|
338
|
+
return 2
|
|
339
|
+
else:
|
|
340
|
+
return 1
|
|
341
|
+
|
|
342
|
+
zones = np.array([get_zone(v) for v in data])
|
|
343
|
+
|
|
344
|
+
# Find runt pulses: transitions that enter zone 1 but don't reach the other side
|
|
345
|
+
i = 0
|
|
346
|
+
while i < len(zones) - 1:
|
|
347
|
+
curr_zone = zones[i]
|
|
348
|
+
|
|
349
|
+
if curr_zone == 0:
|
|
350
|
+
# Starting low - look for positive runt
|
|
351
|
+
if self.polarity in ("positive", "either"):
|
|
352
|
+
# Find transition to zone 1
|
|
353
|
+
if zones[i + 1] == 1:
|
|
354
|
+
start_idx = i
|
|
355
|
+
# Track through zone 1
|
|
356
|
+
j = i + 1
|
|
357
|
+
while j < len(zones) and zones[j] == 1:
|
|
358
|
+
j += 1
|
|
359
|
+
if j < len(zones) and zones[j] == 0:
|
|
360
|
+
# Returned to low without reaching high - RUNT
|
|
361
|
+
peak = float(np.max(data[start_idx : j + 1]))
|
|
362
|
+
events.append(
|
|
363
|
+
TriggerEvent(
|
|
364
|
+
timestamp=start_idx * sample_period,
|
|
365
|
+
sample_index=start_idx,
|
|
366
|
+
event_type=TriggerType.RUNT,
|
|
367
|
+
level=peak,
|
|
368
|
+
duration=(j - start_idx) * sample_period,
|
|
369
|
+
data={
|
|
370
|
+
"polarity": "positive",
|
|
371
|
+
"expected_high": self.high_threshold,
|
|
372
|
+
"actual_peak": peak,
|
|
373
|
+
},
|
|
374
|
+
)
|
|
375
|
+
)
|
|
376
|
+
i = j
|
|
377
|
+
continue
|
|
378
|
+
|
|
379
|
+
elif curr_zone == 2:
|
|
380
|
+
# Starting high - look for negative runt
|
|
381
|
+
if self.polarity in ("negative", "either") and zones[i + 1] == 1:
|
|
382
|
+
start_idx = i
|
|
383
|
+
j = i + 1
|
|
384
|
+
while j < len(zones) and zones[j] == 1:
|
|
385
|
+
j += 1
|
|
386
|
+
if j < len(zones) and zones[j] == 2:
|
|
387
|
+
# Returned to high without reaching low - RUNT
|
|
388
|
+
trough = float(np.min(data[start_idx : j + 1]))
|
|
389
|
+
events.append(
|
|
390
|
+
TriggerEvent(
|
|
391
|
+
timestamp=start_idx * sample_period,
|
|
392
|
+
sample_index=start_idx,
|
|
393
|
+
event_type=TriggerType.RUNT,
|
|
394
|
+
level=trough,
|
|
395
|
+
duration=(j - start_idx) * sample_period,
|
|
396
|
+
data={
|
|
397
|
+
"polarity": "negative",
|
|
398
|
+
"expected_low": self.low_threshold,
|
|
399
|
+
"actual_trough": trough,
|
|
400
|
+
},
|
|
401
|
+
)
|
|
402
|
+
)
|
|
403
|
+
i = j
|
|
404
|
+
continue
|
|
405
|
+
|
|
406
|
+
i += 1
|
|
407
|
+
|
|
408
|
+
return events
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
def find_pulses(
|
|
412
|
+
trace: WaveformTrace,
|
|
413
|
+
*,
|
|
414
|
+
level: float | None = None,
|
|
415
|
+
polarity: Literal["positive", "negative", "either"] = "positive",
|
|
416
|
+
min_width: float | None = None,
|
|
417
|
+
max_width: float | None = None,
|
|
418
|
+
) -> list[TriggerEvent]:
|
|
419
|
+
"""Find pulses matching width criteria.
|
|
420
|
+
|
|
421
|
+
Args:
|
|
422
|
+
trace: Input waveform trace.
|
|
423
|
+
level: Threshold level. If None, uses 50% of amplitude.
|
|
424
|
+
polarity: Pulse polarity to find.
|
|
425
|
+
min_width: Minimum pulse width in seconds.
|
|
426
|
+
max_width: Maximum pulse width in seconds.
|
|
427
|
+
|
|
428
|
+
Returns:
|
|
429
|
+
List of trigger events for matching pulses.
|
|
430
|
+
|
|
431
|
+
Example:
|
|
432
|
+
>>> # Find all positive pulses between 1us and 10us
|
|
433
|
+
>>> pulses = find_pulses(trace, min_width=1e-6, max_width=10e-6)
|
|
434
|
+
"""
|
|
435
|
+
if level is None:
|
|
436
|
+
level = (np.min(trace.data) + np.max(trace.data)) / 2
|
|
437
|
+
|
|
438
|
+
trigger = PulseWidthTrigger(
|
|
439
|
+
level=level,
|
|
440
|
+
polarity=polarity,
|
|
441
|
+
min_width=min_width,
|
|
442
|
+
max_width=max_width,
|
|
443
|
+
)
|
|
444
|
+
return trigger.find_events(trace)
|
|
445
|
+
|
|
446
|
+
|
|
447
|
+
def find_glitches(
|
|
448
|
+
trace: WaveformTrace,
|
|
449
|
+
max_width: float = 100e-9,
|
|
450
|
+
*,
|
|
451
|
+
level: float | None = None,
|
|
452
|
+
polarity: Literal["positive", "negative", "either"] = "either",
|
|
453
|
+
) -> list[TriggerEvent]:
|
|
454
|
+
"""Find glitches (narrow pulses) in a trace.
|
|
455
|
+
|
|
456
|
+
Args:
|
|
457
|
+
trace: Input waveform trace.
|
|
458
|
+
max_width: Maximum width to be considered a glitch (default 100ns).
|
|
459
|
+
level: Threshold level. If None, uses 50% of amplitude.
|
|
460
|
+
polarity: Glitch polarity to find.
|
|
461
|
+
|
|
462
|
+
Returns:
|
|
463
|
+
List of trigger events for each glitch.
|
|
464
|
+
|
|
465
|
+
Example:
|
|
466
|
+
>>> # Find all glitches shorter than 50ns
|
|
467
|
+
>>> glitches = find_glitches(trace, max_width=50e-9)
|
|
468
|
+
>>> print(f"Found {len(glitches)} glitches")
|
|
469
|
+
"""
|
|
470
|
+
if level is None:
|
|
471
|
+
level = (np.min(trace.data) + np.max(trace.data)) / 2
|
|
472
|
+
|
|
473
|
+
trigger = GlitchTrigger(
|
|
474
|
+
level=level,
|
|
475
|
+
max_width=max_width,
|
|
476
|
+
polarity=polarity,
|
|
477
|
+
)
|
|
478
|
+
return trigger.find_events(trace)
|
|
479
|
+
|
|
480
|
+
|
|
481
|
+
def find_runt_pulses(
|
|
482
|
+
trace: WaveformTrace,
|
|
483
|
+
low_threshold: float | None = None,
|
|
484
|
+
high_threshold: float | None = None,
|
|
485
|
+
*,
|
|
486
|
+
polarity: Literal["positive", "negative", "either"] = "either",
|
|
487
|
+
) -> list[TriggerEvent]:
|
|
488
|
+
"""Find runt pulses (incomplete transitions) in a trace.
|
|
489
|
+
|
|
490
|
+
Args:
|
|
491
|
+
trace: Input waveform trace.
|
|
492
|
+
low_threshold: Lower threshold. If None, uses 20% of amplitude.
|
|
493
|
+
high_threshold: Upper threshold. If None, uses 80% of amplitude.
|
|
494
|
+
polarity: Runt polarity to find.
|
|
495
|
+
|
|
496
|
+
Returns:
|
|
497
|
+
List of trigger events for each runt pulse.
|
|
498
|
+
|
|
499
|
+
Example:
|
|
500
|
+
>>> # Find runts using standard 20%/80% thresholds
|
|
501
|
+
>>> runts = find_runt_pulses(trace)
|
|
502
|
+
>>> for runt in runts:
|
|
503
|
+
... print(f"Runt at {runt.timestamp*1e6:.2f} us")
|
|
504
|
+
"""
|
|
505
|
+
if low_threshold is None:
|
|
506
|
+
amplitude = np.max(trace.data) - np.min(trace.data)
|
|
507
|
+
low_threshold = np.min(trace.data) + 0.2 * amplitude
|
|
508
|
+
|
|
509
|
+
if high_threshold is None:
|
|
510
|
+
amplitude = np.max(trace.data) - np.min(trace.data)
|
|
511
|
+
high_threshold = np.min(trace.data) + 0.8 * amplitude
|
|
512
|
+
|
|
513
|
+
trigger = RuntTrigger(
|
|
514
|
+
low_threshold=low_threshold,
|
|
515
|
+
high_threshold=high_threshold,
|
|
516
|
+
polarity=polarity,
|
|
517
|
+
)
|
|
518
|
+
return trigger.find_events(trace)
|
|
519
|
+
|
|
520
|
+
|
|
521
|
+
def pulse_statistics(
|
|
522
|
+
trace: WaveformTrace,
|
|
523
|
+
*,
|
|
524
|
+
level: float | None = None,
|
|
525
|
+
polarity: Literal["positive", "negative"] = "positive",
|
|
526
|
+
) -> dict[str, float]:
|
|
527
|
+
"""Calculate pulse width statistics.
|
|
528
|
+
|
|
529
|
+
Args:
|
|
530
|
+
trace: Input waveform trace.
|
|
531
|
+
level: Threshold level.
|
|
532
|
+
polarity: Pulse polarity to analyze.
|
|
533
|
+
|
|
534
|
+
Returns:
|
|
535
|
+
Dictionary with pulse statistics:
|
|
536
|
+
- count: Number of pulses
|
|
537
|
+
- min_width: Minimum pulse width
|
|
538
|
+
- max_width: Maximum pulse width
|
|
539
|
+
- mean_width: Mean pulse width
|
|
540
|
+
- std_width: Standard deviation of pulse widths
|
|
541
|
+
|
|
542
|
+
Example:
|
|
543
|
+
>>> stats = pulse_statistics(trace)
|
|
544
|
+
>>> print(f"Mean pulse width: {stats['mean_width']*1e6:.2f} us")
|
|
545
|
+
"""
|
|
546
|
+
if level is None:
|
|
547
|
+
level = (np.min(trace.data) + np.max(trace.data)) / 2
|
|
548
|
+
|
|
549
|
+
trigger = PulseWidthTrigger(level=level, polarity=polarity)
|
|
550
|
+
events = trigger.find_events(trace)
|
|
551
|
+
|
|
552
|
+
if len(events) == 0:
|
|
553
|
+
return {
|
|
554
|
+
"count": 0,
|
|
555
|
+
"min_width": np.nan,
|
|
556
|
+
"max_width": np.nan,
|
|
557
|
+
"mean_width": np.nan,
|
|
558
|
+
"std_width": np.nan,
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
widths = np.array([e.duration for e in events if e.duration is not None])
|
|
562
|
+
|
|
563
|
+
return {
|
|
564
|
+
"count": len(widths),
|
|
565
|
+
"min_width": float(np.min(widths)),
|
|
566
|
+
"max_width": float(np.max(widths)),
|
|
567
|
+
"mean_width": float(np.mean(widths)),
|
|
568
|
+
"std_width": float(np.std(widths)),
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
|
|
572
|
+
__all__ = [
|
|
573
|
+
"GlitchTrigger",
|
|
574
|
+
"PulseInfo",
|
|
575
|
+
"PulseWidthTrigger",
|
|
576
|
+
"RuntTrigger",
|
|
577
|
+
"find_glitches",
|
|
578
|
+
"find_pulses",
|
|
579
|
+
"find_runt_pulses",
|
|
580
|
+
"pulse_statistics",
|
|
581
|
+
]
|