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,344 @@
|
|
|
1
|
+
"""Pattern triggering for TraceKit.
|
|
2
|
+
|
|
3
|
+
Provides digital pattern matching for multi-channel logic signals.
|
|
4
|
+
Supports exact matches, wildcards, and edge conditions.
|
|
5
|
+
|
|
6
|
+
Example:
|
|
7
|
+
>>> from oscura.triggering.pattern import PatternTrigger, find_pattern
|
|
8
|
+
>>> # Find pattern 1010 on 4 channels
|
|
9
|
+
>>> trigger = PatternTrigger(pattern=[1, 0, 1, 0])
|
|
10
|
+
>>> events = trigger.find_events(trace)
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from typing import TYPE_CHECKING, Literal
|
|
16
|
+
|
|
17
|
+
import numpy as np
|
|
18
|
+
|
|
19
|
+
from oscura.core.exceptions import AnalysisError
|
|
20
|
+
from oscura.core.types import DigitalTrace, WaveformTrace
|
|
21
|
+
from oscura.triggering.base import (
|
|
22
|
+
Trigger,
|
|
23
|
+
TriggerEvent,
|
|
24
|
+
TriggerType,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
if TYPE_CHECKING:
|
|
28
|
+
from numpy.typing import NDArray
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class PatternTrigger(Trigger):
|
|
32
|
+
"""Pattern trigger for multi-bit digital pattern matching.
|
|
33
|
+
|
|
34
|
+
Detects when a digital signal or set of signals matches a
|
|
35
|
+
specified pattern.
|
|
36
|
+
|
|
37
|
+
For single-channel waveforms, the pattern specifies a sequence
|
|
38
|
+
of high/low states that must occur consecutively.
|
|
39
|
+
|
|
40
|
+
Attributes:
|
|
41
|
+
pattern: Pattern to match (list of 0, 1, or None for don't care).
|
|
42
|
+
levels: Threshold levels for converting analog to digital.
|
|
43
|
+
match_type: Type of match - "exact", "any", or "sequence".
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
def __init__(
|
|
47
|
+
self,
|
|
48
|
+
pattern: list[int | None],
|
|
49
|
+
levels: float | list[float] | None = None,
|
|
50
|
+
match_type: Literal["exact", "sequence"] = "sequence",
|
|
51
|
+
) -> None:
|
|
52
|
+
"""Initialize pattern trigger.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
pattern: Pattern to match. Values are 0, 1, or None (don't care).
|
|
56
|
+
For multi-channel, this is the pattern across channels.
|
|
57
|
+
For single-channel sequence, this is the bit sequence.
|
|
58
|
+
levels: Threshold level(s) for analog-to-digital conversion.
|
|
59
|
+
If None, uses 50% of signal amplitude.
|
|
60
|
+
match_type: "exact" matches pattern at each sample,
|
|
61
|
+
"sequence" finds the pattern as a sequence in time.
|
|
62
|
+
|
|
63
|
+
Raises:
|
|
64
|
+
AnalysisError: If pattern contains invalid values.
|
|
65
|
+
"""
|
|
66
|
+
self.pattern = pattern
|
|
67
|
+
self.levels = levels
|
|
68
|
+
self.match_type = match_type
|
|
69
|
+
|
|
70
|
+
# Validate pattern
|
|
71
|
+
for val in pattern:
|
|
72
|
+
if val is not None and val not in (0, 1):
|
|
73
|
+
raise AnalysisError(f"Pattern values must be 0, 1, or None, got {val}")
|
|
74
|
+
|
|
75
|
+
def find_events(
|
|
76
|
+
self,
|
|
77
|
+
trace: WaveformTrace | DigitalTrace,
|
|
78
|
+
) -> list[TriggerEvent]:
|
|
79
|
+
"""Find pattern matches in the trace.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
trace: Input trace (single channel for sequence matching).
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
List of trigger events for each pattern match.
|
|
86
|
+
"""
|
|
87
|
+
# Convert to digital if needed
|
|
88
|
+
if isinstance(trace, DigitalTrace):
|
|
89
|
+
digital = trace.data
|
|
90
|
+
else:
|
|
91
|
+
level = self._get_level(trace)
|
|
92
|
+
digital = trace.data >= level
|
|
93
|
+
|
|
94
|
+
sample_period = trace.metadata.time_base
|
|
95
|
+
events: list[TriggerEvent] = []
|
|
96
|
+
|
|
97
|
+
if self.match_type == "sequence":
|
|
98
|
+
events = self._find_sequence_matches(digital, sample_period)
|
|
99
|
+
else:
|
|
100
|
+
events = self._find_exact_matches(digital, sample_period)
|
|
101
|
+
|
|
102
|
+
return events
|
|
103
|
+
|
|
104
|
+
def _get_level(self, trace: WaveformTrace) -> float:
|
|
105
|
+
"""Get threshold level for analog-to-digital conversion."""
|
|
106
|
+
if isinstance(self.levels, int | float):
|
|
107
|
+
return float(self.levels)
|
|
108
|
+
elif self.levels is None:
|
|
109
|
+
return (np.min(trace.data) + np.max(trace.data)) / 2 # type: ignore[no-any-return]
|
|
110
|
+
else:
|
|
111
|
+
# Multi-channel case - use first level for single trace
|
|
112
|
+
return float(self.levels[0])
|
|
113
|
+
|
|
114
|
+
def _find_sequence_matches(
|
|
115
|
+
self,
|
|
116
|
+
digital: NDArray[np.bool_],
|
|
117
|
+
sample_period: float,
|
|
118
|
+
) -> list[TriggerEvent]:
|
|
119
|
+
"""Find pattern as a sequence in the data."""
|
|
120
|
+
events: list[TriggerEvent] = []
|
|
121
|
+
|
|
122
|
+
# Convert pattern to expected transitions
|
|
123
|
+
pattern_len = len(self.pattern)
|
|
124
|
+
pattern_arr = np.array([p if p is not None else -1 for p in self.pattern])
|
|
125
|
+
|
|
126
|
+
# Slide pattern across data
|
|
127
|
+
for i in range(len(digital) - pattern_len + 1):
|
|
128
|
+
segment = digital[i : i + pattern_len].astype(np.int8)
|
|
129
|
+
|
|
130
|
+
# Check match (ignoring don't care values)
|
|
131
|
+
match = True
|
|
132
|
+
for j, p in enumerate(pattern_arr):
|
|
133
|
+
if p >= 0 and segment[j] != p:
|
|
134
|
+
match = False
|
|
135
|
+
break
|
|
136
|
+
|
|
137
|
+
if match:
|
|
138
|
+
events.append(
|
|
139
|
+
TriggerEvent(
|
|
140
|
+
timestamp=i * sample_period,
|
|
141
|
+
sample_index=i,
|
|
142
|
+
event_type=TriggerType.PATTERN_MATCH,
|
|
143
|
+
duration=pattern_len * sample_period,
|
|
144
|
+
data={"pattern": self.pattern},
|
|
145
|
+
)
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
return events
|
|
149
|
+
|
|
150
|
+
def _find_exact_matches(
|
|
151
|
+
self,
|
|
152
|
+
digital: NDArray[np.bool_],
|
|
153
|
+
sample_period: float,
|
|
154
|
+
) -> list[TriggerEvent]:
|
|
155
|
+
"""Find exact pattern matches at each sample."""
|
|
156
|
+
events: list[TriggerEvent] = []
|
|
157
|
+
|
|
158
|
+
# For single channel, check if current value matches first pattern bit
|
|
159
|
+
pattern_val = self.pattern[0]
|
|
160
|
+
if pattern_val is None:
|
|
161
|
+
# Don't care - matches everything
|
|
162
|
+
return events
|
|
163
|
+
|
|
164
|
+
expected = bool(pattern_val)
|
|
165
|
+
prev_match = digital[0] == expected
|
|
166
|
+
|
|
167
|
+
for i in range(1, len(digital)):
|
|
168
|
+
curr_match = digital[i] == expected
|
|
169
|
+
if curr_match and not prev_match:
|
|
170
|
+
# Transition to matching state
|
|
171
|
+
events.append(
|
|
172
|
+
TriggerEvent(
|
|
173
|
+
timestamp=i * sample_period,
|
|
174
|
+
sample_index=i,
|
|
175
|
+
event_type=TriggerType.PATTERN_MATCH,
|
|
176
|
+
data={"pattern": self.pattern},
|
|
177
|
+
)
|
|
178
|
+
)
|
|
179
|
+
prev_match = curr_match
|
|
180
|
+
|
|
181
|
+
return events
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
class MultiChannelPatternTrigger(Trigger):
|
|
185
|
+
"""Pattern trigger for multiple parallel channels.
|
|
186
|
+
|
|
187
|
+
Triggers when all channels simultaneously match the specified pattern.
|
|
188
|
+
|
|
189
|
+
Example:
|
|
190
|
+
>>> trigger = MultiChannelPatternTrigger(
|
|
191
|
+
... pattern=[1, 0, 1, None], # Ch0=1, Ch1=0, Ch2=1, Ch3=don't care
|
|
192
|
+
... levels=[1.5, 1.5, 1.5, 1.5]
|
|
193
|
+
... )
|
|
194
|
+
"""
|
|
195
|
+
|
|
196
|
+
def __init__(
|
|
197
|
+
self,
|
|
198
|
+
pattern: list[int | None],
|
|
199
|
+
levels: list[float] | None = None,
|
|
200
|
+
) -> None:
|
|
201
|
+
"""Initialize multi-channel pattern trigger.
|
|
202
|
+
|
|
203
|
+
Args:
|
|
204
|
+
pattern: Pattern for each channel (0, 1, or None for don't care).
|
|
205
|
+
levels: Threshold level for each channel.
|
|
206
|
+
"""
|
|
207
|
+
self.pattern = pattern
|
|
208
|
+
self.levels = levels
|
|
209
|
+
|
|
210
|
+
def find_events(
|
|
211
|
+
self,
|
|
212
|
+
traces: list[WaveformTrace | DigitalTrace], # type: ignore[override]
|
|
213
|
+
) -> list[TriggerEvent]:
|
|
214
|
+
"""Find pattern matches across multiple channels.
|
|
215
|
+
|
|
216
|
+
Args:
|
|
217
|
+
traces: List of traces (one per channel).
|
|
218
|
+
|
|
219
|
+
Returns:
|
|
220
|
+
List of trigger events where pattern matches.
|
|
221
|
+
|
|
222
|
+
Raises:
|
|
223
|
+
AnalysisError: If number of traces doesn't match pattern length.
|
|
224
|
+
"""
|
|
225
|
+
if len(traces) != len(self.pattern):
|
|
226
|
+
raise AnalysisError(
|
|
227
|
+
f"Number of traces ({len(traces)}) must match pattern length ({len(self.pattern)})"
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
# Convert all traces to digital
|
|
231
|
+
digitals: list[NDArray[np.bool_]] = []
|
|
232
|
+
for i, trace in enumerate(traces):
|
|
233
|
+
if isinstance(trace, DigitalTrace):
|
|
234
|
+
digitals.append(trace.data)
|
|
235
|
+
else:
|
|
236
|
+
if self.levels is not None:
|
|
237
|
+
level = self.levels[i]
|
|
238
|
+
else:
|
|
239
|
+
level = (np.min(trace.data) + np.max(trace.data)) / 2
|
|
240
|
+
digitals.append(trace.data >= level)
|
|
241
|
+
|
|
242
|
+
# Find samples where all channels match pattern
|
|
243
|
+
sample_period = traces[0].metadata.time_base
|
|
244
|
+
n_samples = min(len(d) for d in digitals)
|
|
245
|
+
events: list[TriggerEvent] = []
|
|
246
|
+
|
|
247
|
+
prev_match = False
|
|
248
|
+
for i in range(n_samples):
|
|
249
|
+
curr_match = True
|
|
250
|
+
for _j, (digital, pattern_val) in enumerate(zip(digitals, self.pattern, strict=False)):
|
|
251
|
+
if pattern_val is not None and digital[i] != bool(pattern_val):
|
|
252
|
+
curr_match = False
|
|
253
|
+
break
|
|
254
|
+
|
|
255
|
+
if curr_match and not prev_match:
|
|
256
|
+
# Transition to matching state
|
|
257
|
+
events.append(
|
|
258
|
+
TriggerEvent(
|
|
259
|
+
timestamp=i * sample_period,
|
|
260
|
+
sample_index=i,
|
|
261
|
+
event_type=TriggerType.PATTERN_MATCH,
|
|
262
|
+
data={"pattern": self.pattern},
|
|
263
|
+
)
|
|
264
|
+
)
|
|
265
|
+
prev_match = curr_match
|
|
266
|
+
|
|
267
|
+
return events
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def find_pattern(
|
|
271
|
+
trace: WaveformTrace | DigitalTrace,
|
|
272
|
+
pattern: list[int | None],
|
|
273
|
+
*,
|
|
274
|
+
level: float | None = None,
|
|
275
|
+
return_indices: bool = False,
|
|
276
|
+
) -> NDArray[np.float64] | NDArray[np.int64]:
|
|
277
|
+
"""Find all occurrences of a bit pattern in a trace.
|
|
278
|
+
|
|
279
|
+
Args:
|
|
280
|
+
trace: Input trace.
|
|
281
|
+
pattern: Bit pattern to find (0, 1, None for don't care).
|
|
282
|
+
level: Threshold level. If None, uses 50% of amplitude.
|
|
283
|
+
return_indices: If True, return sample indices instead of timestamps.
|
|
284
|
+
|
|
285
|
+
Returns:
|
|
286
|
+
Array of timestamps or indices where pattern was found.
|
|
287
|
+
|
|
288
|
+
Example:
|
|
289
|
+
>>> # Find start bits (0 followed by data)
|
|
290
|
+
>>> starts = find_pattern(trace, [0, 1, 1])
|
|
291
|
+
"""
|
|
292
|
+
trigger = PatternTrigger(pattern=pattern, levels=level)
|
|
293
|
+
events = trigger.find_events(trace)
|
|
294
|
+
|
|
295
|
+
if return_indices:
|
|
296
|
+
return np.array([e.sample_index for e in events], dtype=np.int64)
|
|
297
|
+
return np.array([e.timestamp for e in events], dtype=np.float64)
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def find_bit_sequence(
|
|
301
|
+
trace: WaveformTrace,
|
|
302
|
+
bits: str,
|
|
303
|
+
*,
|
|
304
|
+
level: float | None = None,
|
|
305
|
+
) -> list[TriggerEvent]:
|
|
306
|
+
"""Find a specific bit sequence in a trace.
|
|
307
|
+
|
|
308
|
+
Args:
|
|
309
|
+
trace: Input waveform trace.
|
|
310
|
+
bits: Bit string (e.g., "10101010", "1X0X" where X is don't care).
|
|
311
|
+
level: Threshold level for digitization.
|
|
312
|
+
|
|
313
|
+
Returns:
|
|
314
|
+
List of trigger events for each match.
|
|
315
|
+
|
|
316
|
+
Raises:
|
|
317
|
+
AnalysisError: If invalid bit character in bits string.
|
|
318
|
+
|
|
319
|
+
Example:
|
|
320
|
+
>>> events = find_bit_sequence(trace, "10110")
|
|
321
|
+
>>> events = find_bit_sequence(trace, "1XX0") # X = don't care
|
|
322
|
+
"""
|
|
323
|
+
# Convert string to pattern list
|
|
324
|
+
pattern: list[int | None] = []
|
|
325
|
+
for char in bits:
|
|
326
|
+
if char == "0":
|
|
327
|
+
pattern.append(0)
|
|
328
|
+
elif char == "1":
|
|
329
|
+
pattern.append(1)
|
|
330
|
+
elif char.upper() == "X":
|
|
331
|
+
pattern.append(None)
|
|
332
|
+
else:
|
|
333
|
+
raise AnalysisError(f"Invalid bit character: {char}")
|
|
334
|
+
|
|
335
|
+
trigger = PatternTrigger(pattern=pattern, levels=level)
|
|
336
|
+
return trigger.find_events(trace)
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
__all__ = [
|
|
340
|
+
"MultiChannelPatternTrigger",
|
|
341
|
+
"PatternTrigger",
|
|
342
|
+
"find_bit_sequence",
|
|
343
|
+
"find_pattern",
|
|
344
|
+
]
|