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,68 @@
|
|
|
1
|
+
"""Signal triggering and event detection module for TraceKit.
|
|
2
|
+
|
|
3
|
+
Provides oscilloscope-style triggering functionality including edge
|
|
4
|
+
triggering, pattern triggering, pulse width triggering, glitch detection,
|
|
5
|
+
runt pulse detection, and window/zone triggering.
|
|
6
|
+
|
|
7
|
+
Example:
|
|
8
|
+
>>> from oscura.triggering import EdgeTrigger, find_triggers
|
|
9
|
+
>>> trigger = EdgeTrigger(level=1.5, edge="rising")
|
|
10
|
+
>>> events = trigger.find_events(trace)
|
|
11
|
+
>>> # Or use convenience function
|
|
12
|
+
>>> events = find_triggers(trace, "edge", level=1.5, edge="rising")
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from oscura.triggering.base import (
|
|
16
|
+
Trigger,
|
|
17
|
+
TriggerEvent,
|
|
18
|
+
find_triggers,
|
|
19
|
+
)
|
|
20
|
+
from oscura.triggering.edge import (
|
|
21
|
+
EdgeTrigger,
|
|
22
|
+
find_all_edges,
|
|
23
|
+
find_falling_edges,
|
|
24
|
+
find_rising_edges,
|
|
25
|
+
)
|
|
26
|
+
from oscura.triggering.pattern import (
|
|
27
|
+
PatternTrigger,
|
|
28
|
+
find_pattern,
|
|
29
|
+
)
|
|
30
|
+
from oscura.triggering.pulse import (
|
|
31
|
+
PulseWidthTrigger,
|
|
32
|
+
find_glitches,
|
|
33
|
+
find_pulses,
|
|
34
|
+
find_runt_pulses,
|
|
35
|
+
)
|
|
36
|
+
from oscura.triggering.window import (
|
|
37
|
+
WindowTrigger,
|
|
38
|
+
ZoneTrigger,
|
|
39
|
+
check_limits,
|
|
40
|
+
find_window_violations,
|
|
41
|
+
find_zone_events,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
__all__ = [
|
|
45
|
+
# Edge triggering
|
|
46
|
+
"EdgeTrigger",
|
|
47
|
+
# Pattern triggering
|
|
48
|
+
"PatternTrigger",
|
|
49
|
+
# Pulse triggering
|
|
50
|
+
"PulseWidthTrigger",
|
|
51
|
+
# Base
|
|
52
|
+
"Trigger",
|
|
53
|
+
"TriggerEvent",
|
|
54
|
+
# Window triggering
|
|
55
|
+
"WindowTrigger",
|
|
56
|
+
"ZoneTrigger",
|
|
57
|
+
"check_limits",
|
|
58
|
+
"find_all_edges",
|
|
59
|
+
"find_falling_edges",
|
|
60
|
+
"find_glitches",
|
|
61
|
+
"find_pattern",
|
|
62
|
+
"find_pulses",
|
|
63
|
+
"find_rising_edges",
|
|
64
|
+
"find_runt_pulses",
|
|
65
|
+
"find_triggers",
|
|
66
|
+
"find_window_violations",
|
|
67
|
+
"find_zone_events",
|
|
68
|
+
]
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
"""Base classes and utilities for TraceKit triggering module.
|
|
2
|
+
|
|
3
|
+
Provides abstract base class for triggers and common trigger event
|
|
4
|
+
data structure.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from abc import ABC, abstractmethod
|
|
10
|
+
from dataclasses import dataclass, field
|
|
11
|
+
from enum import Enum
|
|
12
|
+
from typing import TYPE_CHECKING, Any, Literal
|
|
13
|
+
|
|
14
|
+
from oscura.core.exceptions import AnalysisError
|
|
15
|
+
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
import numpy as np
|
|
18
|
+
from numpy.typing import NDArray
|
|
19
|
+
|
|
20
|
+
from oscura.core.types import DigitalTrace, WaveformTrace
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class TriggerType(Enum):
|
|
24
|
+
"""Types of trigger events."""
|
|
25
|
+
|
|
26
|
+
RISING_EDGE = "rising_edge"
|
|
27
|
+
FALLING_EDGE = "falling_edge"
|
|
28
|
+
PATTERN_MATCH = "pattern_match"
|
|
29
|
+
PULSE_WIDTH = "pulse_width"
|
|
30
|
+
GLITCH = "glitch"
|
|
31
|
+
RUNT = "runt"
|
|
32
|
+
WINDOW_ENTRY = "window_entry"
|
|
33
|
+
WINDOW_EXIT = "window_exit"
|
|
34
|
+
ZONE_VIOLATION = "zone_violation"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@dataclass
|
|
38
|
+
class TriggerEvent:
|
|
39
|
+
"""Represents a detected trigger event.
|
|
40
|
+
|
|
41
|
+
Attributes:
|
|
42
|
+
timestamp: Time of the trigger event in seconds.
|
|
43
|
+
sample_index: Sample index where trigger occurred.
|
|
44
|
+
event_type: Type of trigger event.
|
|
45
|
+
level: Voltage/signal level at trigger point.
|
|
46
|
+
data: Additional event-specific data.
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
timestamp: float
|
|
50
|
+
sample_index: int
|
|
51
|
+
event_type: TriggerType
|
|
52
|
+
level: float | None = None
|
|
53
|
+
duration: float | None = None
|
|
54
|
+
data: dict[str, Any] = field(default_factory=dict)
|
|
55
|
+
|
|
56
|
+
def __repr__(self) -> str:
|
|
57
|
+
return (
|
|
58
|
+
f"TriggerEvent({self.event_type.value} at t={self.timestamp:.6e}s, "
|
|
59
|
+
f"sample={self.sample_index})"
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class Trigger(ABC):
|
|
64
|
+
"""Abstract base class for all trigger types.
|
|
65
|
+
|
|
66
|
+
Defines the common interface for finding trigger events in traces.
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
@abstractmethod
|
|
70
|
+
def find_events(
|
|
71
|
+
self,
|
|
72
|
+
trace: WaveformTrace | DigitalTrace,
|
|
73
|
+
) -> list[TriggerEvent]:
|
|
74
|
+
"""Find all trigger events in a trace.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
trace: Input trace to search.
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
List of trigger events found.
|
|
81
|
+
"""
|
|
82
|
+
...
|
|
83
|
+
|
|
84
|
+
def find_first(
|
|
85
|
+
self,
|
|
86
|
+
trace: WaveformTrace | DigitalTrace,
|
|
87
|
+
) -> TriggerEvent | None:
|
|
88
|
+
"""Find the first trigger event.
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
trace: Input trace to search.
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
First trigger event, or None if no triggers found.
|
|
95
|
+
"""
|
|
96
|
+
events = self.find_events(trace)
|
|
97
|
+
return events[0] if events else None
|
|
98
|
+
|
|
99
|
+
def count_events(
|
|
100
|
+
self,
|
|
101
|
+
trace: WaveformTrace | DigitalTrace,
|
|
102
|
+
) -> int:
|
|
103
|
+
"""Count trigger events.
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
trace: Input trace to search.
|
|
107
|
+
|
|
108
|
+
Returns:
|
|
109
|
+
Number of trigger events found.
|
|
110
|
+
"""
|
|
111
|
+
return len(self.find_events(trace))
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def find_triggers(
|
|
115
|
+
trace: WaveformTrace | DigitalTrace,
|
|
116
|
+
trigger_type: Literal["edge", "pattern", "pulse_width", "glitch", "runt", "window"],
|
|
117
|
+
**kwargs: Any,
|
|
118
|
+
) -> list[TriggerEvent]:
|
|
119
|
+
"""Unified function to find trigger events.
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
trace: Input trace to search.
|
|
123
|
+
trigger_type: Type of trigger to use.
|
|
124
|
+
**kwargs: Trigger-specific parameters.
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
List of trigger events.
|
|
128
|
+
|
|
129
|
+
Raises:
|
|
130
|
+
AnalysisError: If unknown trigger type.
|
|
131
|
+
|
|
132
|
+
Example:
|
|
133
|
+
>>> events = find_triggers(trace, "edge", level=1.5, edge="rising")
|
|
134
|
+
>>> events = find_triggers(trace, "pulse_width", min_width=1e-6, max_width=2e-6)
|
|
135
|
+
>>> events = find_triggers(trace, "glitch", max_width=50e-9)
|
|
136
|
+
"""
|
|
137
|
+
from oscura.triggering.edge import EdgeTrigger
|
|
138
|
+
from oscura.triggering.pulse import PulseWidthTrigger
|
|
139
|
+
from oscura.triggering.window import WindowTrigger
|
|
140
|
+
|
|
141
|
+
if trigger_type == "edge":
|
|
142
|
+
trigger = EdgeTrigger(
|
|
143
|
+
level=kwargs.get("level", 0.0),
|
|
144
|
+
edge=kwargs.get("edge", "rising"),
|
|
145
|
+
hysteresis=kwargs.get("hysteresis", 0.0),
|
|
146
|
+
)
|
|
147
|
+
elif trigger_type == "pattern":
|
|
148
|
+
from oscura.triggering.pattern import PatternTrigger
|
|
149
|
+
|
|
150
|
+
trigger = PatternTrigger( # type: ignore[assignment]
|
|
151
|
+
pattern=kwargs.get("pattern", []),
|
|
152
|
+
levels=kwargs.get("levels"),
|
|
153
|
+
)
|
|
154
|
+
elif trigger_type == "pulse_width":
|
|
155
|
+
trigger = PulseWidthTrigger( # type: ignore[assignment]
|
|
156
|
+
level=kwargs.get("level", 0.0),
|
|
157
|
+
polarity=kwargs.get("polarity", "positive"),
|
|
158
|
+
min_width=kwargs.get("min_width"),
|
|
159
|
+
max_width=kwargs.get("max_width"),
|
|
160
|
+
)
|
|
161
|
+
elif trigger_type == "glitch":
|
|
162
|
+
from oscura.triggering.pulse import GlitchTrigger
|
|
163
|
+
|
|
164
|
+
trigger = GlitchTrigger( # type: ignore[assignment]
|
|
165
|
+
level=kwargs.get("level", 0.0),
|
|
166
|
+
max_width=kwargs.get("max_width", 100e-9),
|
|
167
|
+
polarity=kwargs.get("polarity", "either"),
|
|
168
|
+
)
|
|
169
|
+
elif trigger_type == "runt":
|
|
170
|
+
from oscura.triggering.pulse import RuntTrigger
|
|
171
|
+
|
|
172
|
+
trigger = RuntTrigger( # type: ignore[assignment]
|
|
173
|
+
low_threshold=kwargs.get("low_threshold", 0.0),
|
|
174
|
+
high_threshold=kwargs.get("high_threshold", 1.0),
|
|
175
|
+
polarity=kwargs.get("polarity", "either"),
|
|
176
|
+
)
|
|
177
|
+
elif trigger_type == "window":
|
|
178
|
+
trigger = WindowTrigger( # type: ignore[assignment]
|
|
179
|
+
low_threshold=kwargs.get("low_threshold", 0.0),
|
|
180
|
+
high_threshold=kwargs.get("high_threshold", 1.0),
|
|
181
|
+
trigger_on=kwargs.get("trigger_on", "exit"),
|
|
182
|
+
)
|
|
183
|
+
else:
|
|
184
|
+
raise AnalysisError(f"Unknown trigger type: {trigger_type}")
|
|
185
|
+
|
|
186
|
+
return trigger.find_events(trace)
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def interpolate_crossing(
|
|
190
|
+
data: NDArray[np.floating[Any]],
|
|
191
|
+
idx: int,
|
|
192
|
+
threshold: float,
|
|
193
|
+
sample_period: float,
|
|
194
|
+
rising: bool = True,
|
|
195
|
+
) -> float:
|
|
196
|
+
"""Interpolate exact threshold crossing time.
|
|
197
|
+
|
|
198
|
+
Args:
|
|
199
|
+
data: Waveform data array.
|
|
200
|
+
idx: Sample index near crossing.
|
|
201
|
+
threshold: Threshold level.
|
|
202
|
+
sample_period: Time between samples.
|
|
203
|
+
rising: True for rising edge, False for falling.
|
|
204
|
+
|
|
205
|
+
Returns:
|
|
206
|
+
Interpolated crossing time in seconds.
|
|
207
|
+
"""
|
|
208
|
+
if idx < 0 or idx >= len(data) - 1:
|
|
209
|
+
return idx * sample_period
|
|
210
|
+
|
|
211
|
+
v1, v2 = data[idx], data[idx + 1]
|
|
212
|
+
|
|
213
|
+
if abs(v2 - v1) < 1e-12:
|
|
214
|
+
return (idx + 0.5) * sample_period
|
|
215
|
+
|
|
216
|
+
# Linear interpolation
|
|
217
|
+
t_offset = (threshold - v1) / (v2 - v1) * sample_period
|
|
218
|
+
t_offset = max(0, min(sample_period, t_offset))
|
|
219
|
+
|
|
220
|
+
return idx * sample_period + t_offset # type: ignore[no-any-return]
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
__all__ = [
|
|
224
|
+
"Trigger",
|
|
225
|
+
"TriggerEvent",
|
|
226
|
+
"TriggerType",
|
|
227
|
+
"find_triggers",
|
|
228
|
+
"interpolate_crossing",
|
|
229
|
+
]
|
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
"""Edge triggering for TraceKit.
|
|
2
|
+
|
|
3
|
+
Provides edge detection with configurable thresholds, hysteresis,
|
|
4
|
+
and edge polarity (rising, falling, or both).
|
|
5
|
+
|
|
6
|
+
Example:
|
|
7
|
+
>>> from oscura.triggering.edge import EdgeTrigger, find_rising_edges
|
|
8
|
+
>>> # Object-oriented approach
|
|
9
|
+
>>> trigger = EdgeTrigger(level=1.5, edge="rising", hysteresis=0.1)
|
|
10
|
+
>>> events = trigger.find_events(trace)
|
|
11
|
+
>>> # Functional approach
|
|
12
|
+
>>> timestamps = find_rising_edges(trace, level=1.5)
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from typing import TYPE_CHECKING, Any, Literal
|
|
18
|
+
|
|
19
|
+
import numpy as np
|
|
20
|
+
|
|
21
|
+
from oscura.core.types import DigitalTrace, WaveformTrace
|
|
22
|
+
from oscura.triggering.base import (
|
|
23
|
+
Trigger,
|
|
24
|
+
TriggerEvent,
|
|
25
|
+
TriggerType,
|
|
26
|
+
interpolate_crossing,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
if TYPE_CHECKING:
|
|
30
|
+
from numpy.typing import NDArray
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class EdgeTrigger(Trigger):
|
|
34
|
+
"""Edge trigger with threshold and optional hysteresis.
|
|
35
|
+
|
|
36
|
+
Detects signal crossings of a threshold level with configurable
|
|
37
|
+
hysteresis for noise immunity.
|
|
38
|
+
|
|
39
|
+
Attributes:
|
|
40
|
+
level: Trigger threshold level.
|
|
41
|
+
edge: Edge type - "rising", "falling", or "either".
|
|
42
|
+
hysteresis: Hysteresis band (Schmitt trigger style).
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
def __init__(
|
|
46
|
+
self,
|
|
47
|
+
level: float,
|
|
48
|
+
edge: Literal["rising", "falling", "either"] = "rising",
|
|
49
|
+
hysteresis: float = 0.0,
|
|
50
|
+
) -> None:
|
|
51
|
+
"""Initialize edge trigger.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
level: Trigger threshold level in signal units (e.g., volts).
|
|
55
|
+
edge: Edge polarity to trigger on.
|
|
56
|
+
hysteresis: Hysteresis band width. Trigger requires signal to
|
|
57
|
+
cross level +/- hysteresis/2 before retriggering.
|
|
58
|
+
"""
|
|
59
|
+
self.level = level
|
|
60
|
+
self.edge = edge
|
|
61
|
+
self.hysteresis = hysteresis
|
|
62
|
+
|
|
63
|
+
def find_events(
|
|
64
|
+
self,
|
|
65
|
+
trace: WaveformTrace | DigitalTrace,
|
|
66
|
+
) -> list[TriggerEvent]:
|
|
67
|
+
"""Find all edge events in the trace.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
trace: Input waveform or digital trace.
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
List of trigger events for each detected edge.
|
|
74
|
+
"""
|
|
75
|
+
if isinstance(trace, DigitalTrace):
|
|
76
|
+
# For digital traces, use edge list if available
|
|
77
|
+
data = trace.data.astype(np.float64)
|
|
78
|
+
else:
|
|
79
|
+
data = trace.data
|
|
80
|
+
|
|
81
|
+
sample_period = trace.metadata.time_base
|
|
82
|
+
events: list[TriggerEvent] = []
|
|
83
|
+
|
|
84
|
+
if self.hysteresis > 0:
|
|
85
|
+
# Schmitt trigger mode
|
|
86
|
+
events = self._find_edges_with_hysteresis(data, sample_period)
|
|
87
|
+
else:
|
|
88
|
+
# Simple threshold crossing
|
|
89
|
+
events = self._find_edges_simple(data, sample_period)
|
|
90
|
+
|
|
91
|
+
return events
|
|
92
|
+
|
|
93
|
+
def _find_edges_simple(
|
|
94
|
+
self,
|
|
95
|
+
data: NDArray[np.floating[Any]],
|
|
96
|
+
sample_period: float,
|
|
97
|
+
) -> list[TriggerEvent]:
|
|
98
|
+
"""Find edges using simple threshold crossing."""
|
|
99
|
+
events: list[TriggerEvent] = []
|
|
100
|
+
|
|
101
|
+
below = data < self.level
|
|
102
|
+
above = data >= self.level
|
|
103
|
+
|
|
104
|
+
if self.edge in ("rising", "either"):
|
|
105
|
+
# Rising: below -> above
|
|
106
|
+
rising_idx = np.where(below[:-1] & above[1:])[0]
|
|
107
|
+
for idx in rising_idx:
|
|
108
|
+
timestamp = interpolate_crossing(data, idx, self.level, sample_period, rising=True)
|
|
109
|
+
events.append(
|
|
110
|
+
TriggerEvent(
|
|
111
|
+
timestamp=timestamp,
|
|
112
|
+
sample_index=int(idx),
|
|
113
|
+
event_type=TriggerType.RISING_EDGE,
|
|
114
|
+
level=float(data[idx + 1]),
|
|
115
|
+
)
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
if self.edge in ("falling", "either"):
|
|
119
|
+
# Falling: above -> below
|
|
120
|
+
falling_idx = np.where(above[:-1] & below[1:])[0]
|
|
121
|
+
for idx in falling_idx:
|
|
122
|
+
timestamp = interpolate_crossing(data, idx, self.level, sample_period, rising=False)
|
|
123
|
+
events.append(
|
|
124
|
+
TriggerEvent(
|
|
125
|
+
timestamp=timestamp,
|
|
126
|
+
sample_index=int(idx),
|
|
127
|
+
event_type=TriggerType.FALLING_EDGE,
|
|
128
|
+
level=float(data[idx + 1]),
|
|
129
|
+
)
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
# Sort by timestamp if we detected both edge types
|
|
133
|
+
if self.edge == "either":
|
|
134
|
+
events.sort(key=lambda e: e.timestamp)
|
|
135
|
+
|
|
136
|
+
return events
|
|
137
|
+
|
|
138
|
+
def _find_edges_with_hysteresis(
|
|
139
|
+
self,
|
|
140
|
+
data: NDArray[np.floating[Any]],
|
|
141
|
+
sample_period: float,
|
|
142
|
+
) -> list[TriggerEvent]:
|
|
143
|
+
"""Find edges using Schmitt trigger with hysteresis."""
|
|
144
|
+
events: list[TriggerEvent] = []
|
|
145
|
+
|
|
146
|
+
high_thresh = self.level + self.hysteresis / 2
|
|
147
|
+
low_thresh = self.level - self.hysteresis / 2
|
|
148
|
+
|
|
149
|
+
# State machine: track if we're currently "high" or "low"
|
|
150
|
+
state = "low" if data[0] < self.level else "high"
|
|
151
|
+
|
|
152
|
+
for i in range(1, len(data)):
|
|
153
|
+
if state == "low" and data[i] >= high_thresh:
|
|
154
|
+
# Rising edge detected
|
|
155
|
+
state = "high"
|
|
156
|
+
if self.edge in ("rising", "either"):
|
|
157
|
+
timestamp = interpolate_crossing(
|
|
158
|
+
data, i - 1, high_thresh, sample_period, rising=True
|
|
159
|
+
)
|
|
160
|
+
events.append(
|
|
161
|
+
TriggerEvent(
|
|
162
|
+
timestamp=timestamp,
|
|
163
|
+
sample_index=i,
|
|
164
|
+
event_type=TriggerType.RISING_EDGE,
|
|
165
|
+
level=float(data[i]),
|
|
166
|
+
)
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
elif state == "high" and data[i] <= low_thresh:
|
|
170
|
+
# Falling edge detected
|
|
171
|
+
state = "low"
|
|
172
|
+
if self.edge in ("falling", "either"):
|
|
173
|
+
timestamp = interpolate_crossing(
|
|
174
|
+
data, i - 1, low_thresh, sample_period, rising=False
|
|
175
|
+
)
|
|
176
|
+
events.append(
|
|
177
|
+
TriggerEvent(
|
|
178
|
+
timestamp=timestamp,
|
|
179
|
+
sample_index=i,
|
|
180
|
+
event_type=TriggerType.FALLING_EDGE,
|
|
181
|
+
level=float(data[i]),
|
|
182
|
+
)
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
return events
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def find_rising_edges(
|
|
189
|
+
trace: WaveformTrace,
|
|
190
|
+
level: float | None = None,
|
|
191
|
+
*,
|
|
192
|
+
hysteresis: float = 0.0,
|
|
193
|
+
return_indices: bool = False,
|
|
194
|
+
) -> NDArray[np.float64] | NDArray[np.int64]:
|
|
195
|
+
"""Find all rising edge timestamps or indices.
|
|
196
|
+
|
|
197
|
+
Args:
|
|
198
|
+
trace: Input waveform trace.
|
|
199
|
+
level: Trigger threshold. If None, uses signal midpoint.
|
|
200
|
+
hysteresis: Hysteresis band for noise immunity.
|
|
201
|
+
return_indices: If True, return sample indices instead of timestamps.
|
|
202
|
+
|
|
203
|
+
Returns:
|
|
204
|
+
Array of timestamps (seconds) or sample indices.
|
|
205
|
+
|
|
206
|
+
Example:
|
|
207
|
+
>>> edges = find_rising_edges(trace, level=1.5)
|
|
208
|
+
>>> print(f"Found {len(edges)} rising edges")
|
|
209
|
+
"""
|
|
210
|
+
if level is None:
|
|
211
|
+
level = (np.min(trace.data) + np.max(trace.data)) / 2
|
|
212
|
+
|
|
213
|
+
trigger = EdgeTrigger(level=level, edge="rising", hysteresis=hysteresis)
|
|
214
|
+
events = trigger.find_events(trace)
|
|
215
|
+
|
|
216
|
+
if return_indices:
|
|
217
|
+
return np.array([e.sample_index for e in events], dtype=np.int64)
|
|
218
|
+
return np.array([e.timestamp for e in events], dtype=np.float64)
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def find_falling_edges(
|
|
222
|
+
trace: WaveformTrace,
|
|
223
|
+
level: float | None = None,
|
|
224
|
+
*,
|
|
225
|
+
hysteresis: float = 0.0,
|
|
226
|
+
return_indices: bool = False,
|
|
227
|
+
) -> NDArray[np.float64] | NDArray[np.int64]:
|
|
228
|
+
"""Find all falling edge timestamps or indices.
|
|
229
|
+
|
|
230
|
+
Args:
|
|
231
|
+
trace: Input waveform trace.
|
|
232
|
+
level: Trigger threshold. If None, uses signal midpoint.
|
|
233
|
+
hysteresis: Hysteresis band for noise immunity.
|
|
234
|
+
return_indices: If True, return sample indices instead of timestamps.
|
|
235
|
+
|
|
236
|
+
Returns:
|
|
237
|
+
Array of timestamps (seconds) or sample indices.
|
|
238
|
+
|
|
239
|
+
Example:
|
|
240
|
+
>>> edges = find_falling_edges(trace, level=1.5)
|
|
241
|
+
"""
|
|
242
|
+
if level is None:
|
|
243
|
+
level = (np.min(trace.data) + np.max(trace.data)) / 2
|
|
244
|
+
|
|
245
|
+
trigger = EdgeTrigger(level=level, edge="falling", hysteresis=hysteresis)
|
|
246
|
+
events = trigger.find_events(trace)
|
|
247
|
+
|
|
248
|
+
if return_indices:
|
|
249
|
+
return np.array([e.sample_index for e in events], dtype=np.int64)
|
|
250
|
+
return np.array([e.timestamp for e in events], dtype=np.float64)
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def find_all_edges(
|
|
254
|
+
trace: WaveformTrace,
|
|
255
|
+
level: float | None = None,
|
|
256
|
+
*,
|
|
257
|
+
hysteresis: float = 0.0,
|
|
258
|
+
) -> tuple[NDArray[np.float64], NDArray[np.bool_]]:
|
|
259
|
+
"""Find all edges (rising and falling) with polarity.
|
|
260
|
+
|
|
261
|
+
Args:
|
|
262
|
+
trace: Input waveform trace.
|
|
263
|
+
level: Trigger threshold. If None, uses signal midpoint.
|
|
264
|
+
hysteresis: Hysteresis band for noise immunity.
|
|
265
|
+
|
|
266
|
+
Returns:
|
|
267
|
+
Tuple of (timestamps, is_rising) where is_rising is True for
|
|
268
|
+
rising edges and False for falling edges.
|
|
269
|
+
|
|
270
|
+
Example:
|
|
271
|
+
>>> timestamps, is_rising = find_all_edges(trace, level=1.5)
|
|
272
|
+
>>> rising = timestamps[is_rising]
|
|
273
|
+
>>> falling = timestamps[~is_rising]
|
|
274
|
+
"""
|
|
275
|
+
if level is None:
|
|
276
|
+
level = (np.min(trace.data) + np.max(trace.data)) / 2
|
|
277
|
+
|
|
278
|
+
trigger = EdgeTrigger(level=level, edge="either", hysteresis=hysteresis)
|
|
279
|
+
events = trigger.find_events(trace)
|
|
280
|
+
|
|
281
|
+
timestamps = np.array([e.timestamp for e in events], dtype=np.float64)
|
|
282
|
+
is_rising = np.array([e.event_type == TriggerType.RISING_EDGE for e in events], dtype=np.bool_)
|
|
283
|
+
|
|
284
|
+
return timestamps, is_rising
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def edge_count(
|
|
288
|
+
trace: WaveformTrace,
|
|
289
|
+
level: float | None = None,
|
|
290
|
+
edge: Literal["rising", "falling", "either"] = "either",
|
|
291
|
+
*,
|
|
292
|
+
hysteresis: float = 0.0,
|
|
293
|
+
) -> int:
|
|
294
|
+
"""Count edges in a trace.
|
|
295
|
+
|
|
296
|
+
Args:
|
|
297
|
+
trace: Input waveform trace.
|
|
298
|
+
level: Trigger threshold. If None, uses signal midpoint.
|
|
299
|
+
edge: Edge type to count.
|
|
300
|
+
hysteresis: Hysteresis band for noise immunity.
|
|
301
|
+
|
|
302
|
+
Returns:
|
|
303
|
+
Number of edges found.
|
|
304
|
+
|
|
305
|
+
Example:
|
|
306
|
+
>>> n_rising = edge_count(trace, level=1.5, edge="rising")
|
|
307
|
+
"""
|
|
308
|
+
if level is None:
|
|
309
|
+
level = (np.min(trace.data) + np.max(trace.data)) / 2
|
|
310
|
+
|
|
311
|
+
trigger = EdgeTrigger(level=level, edge=edge, hysteresis=hysteresis)
|
|
312
|
+
return trigger.count_events(trace)
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
def edge_rate(
|
|
316
|
+
trace: WaveformTrace,
|
|
317
|
+
level: float | None = None,
|
|
318
|
+
edge: Literal["rising", "falling", "either"] = "either",
|
|
319
|
+
*,
|
|
320
|
+
hysteresis: float = 0.0,
|
|
321
|
+
) -> float:
|
|
322
|
+
"""Calculate edge rate (edges per second).
|
|
323
|
+
|
|
324
|
+
Args:
|
|
325
|
+
trace: Input waveform trace.
|
|
326
|
+
level: Trigger threshold.
|
|
327
|
+
edge: Edge type to count.
|
|
328
|
+
hysteresis: Hysteresis band for noise immunity.
|
|
329
|
+
|
|
330
|
+
Returns:
|
|
331
|
+
Edge rate in Hz.
|
|
332
|
+
|
|
333
|
+
Example:
|
|
334
|
+
>>> rate = edge_rate(trace, level=1.5, edge="rising")
|
|
335
|
+
>>> print(f"Toggle rate: {rate} Hz")
|
|
336
|
+
"""
|
|
337
|
+
count = edge_count(trace, level, edge, hysteresis=hysteresis)
|
|
338
|
+
duration = trace.duration
|
|
339
|
+
|
|
340
|
+
if duration <= 0:
|
|
341
|
+
return 0.0
|
|
342
|
+
|
|
343
|
+
return count / duration
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
__all__ = [
|
|
347
|
+
"EdgeTrigger",
|
|
348
|
+
"edge_count",
|
|
349
|
+
"edge_rate",
|
|
350
|
+
"find_all_edges",
|
|
351
|
+
"find_falling_edges",
|
|
352
|
+
"find_rising_edges",
|
|
353
|
+
]
|