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,413 @@
|
|
|
1
|
+
"""Digital signal extraction and edge detection.
|
|
2
|
+
|
|
3
|
+
This module provides functions for extracting digital signals from
|
|
4
|
+
analog waveforms and detecting edge transitions.
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
Example:
|
|
8
|
+
>>> from oscura.analyzers.digital import to_digital, detect_edges
|
|
9
|
+
>>> digital = to_digital(analog_trace, threshold=1.4)
|
|
10
|
+
>>> edges = detect_edges(digital, edge_type="rising")
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from typing import TYPE_CHECKING, Any, Literal
|
|
16
|
+
|
|
17
|
+
import numpy as np
|
|
18
|
+
|
|
19
|
+
from oscura.core.exceptions import InsufficientDataError
|
|
20
|
+
from oscura.core.types import DigitalTrace, WaveformTrace
|
|
21
|
+
|
|
22
|
+
if TYPE_CHECKING:
|
|
23
|
+
from numpy.typing import NDArray
|
|
24
|
+
|
|
25
|
+
# Standard logic family threshold constants
|
|
26
|
+
# Reference: Various IC manufacturer datasheets
|
|
27
|
+
LOGIC_FAMILIES: dict[str, dict[str, float]] = {
|
|
28
|
+
"TTL": {
|
|
29
|
+
"VIL_max": 0.8, # Maximum input low voltage
|
|
30
|
+
"VIH_min": 2.0, # Minimum input high voltage
|
|
31
|
+
"VOL_max": 0.4, # Maximum output low voltage
|
|
32
|
+
"VOH_min": 2.4, # Minimum output high voltage
|
|
33
|
+
"VCC": 5.0,
|
|
34
|
+
},
|
|
35
|
+
"CMOS_5V": {
|
|
36
|
+
"VIL_max": 1.5,
|
|
37
|
+
"VIH_min": 3.5,
|
|
38
|
+
"VOL_max": 0.1,
|
|
39
|
+
"VOH_min": 4.9,
|
|
40
|
+
"VCC": 5.0,
|
|
41
|
+
},
|
|
42
|
+
"LVTTL": {
|
|
43
|
+
"VIL_max": 0.8,
|
|
44
|
+
"VIH_min": 1.5,
|
|
45
|
+
"VOL_max": 0.4,
|
|
46
|
+
"VOH_min": 2.4,
|
|
47
|
+
"VCC": 3.3,
|
|
48
|
+
},
|
|
49
|
+
"LVCMOS_3V3": {
|
|
50
|
+
"VIL_max": 0.3 * 3.3, # 30% of VCC
|
|
51
|
+
"VIH_min": 0.7 * 3.3, # 70% of VCC
|
|
52
|
+
"VOL_max": 0.1,
|
|
53
|
+
"VOH_min": 3.2,
|
|
54
|
+
"VCC": 3.3,
|
|
55
|
+
},
|
|
56
|
+
"LVCMOS_2V5": {
|
|
57
|
+
"VIL_max": 0.3 * 2.5,
|
|
58
|
+
"VIH_min": 0.7 * 2.5,
|
|
59
|
+
"VOL_max": 0.1,
|
|
60
|
+
"VOH_min": 2.4,
|
|
61
|
+
"VCC": 2.5,
|
|
62
|
+
},
|
|
63
|
+
"LVCMOS_1V8": {
|
|
64
|
+
"VIL_max": 0.3 * 1.8,
|
|
65
|
+
"VIH_min": 0.7 * 1.8,
|
|
66
|
+
"VOL_max": 0.1,
|
|
67
|
+
"VOH_min": 1.7,
|
|
68
|
+
"VCC": 1.8,
|
|
69
|
+
},
|
|
70
|
+
"LVCMOS_1V2": {
|
|
71
|
+
"VIL_max": 0.3 * 1.2,
|
|
72
|
+
"VIH_min": 0.7 * 1.2,
|
|
73
|
+
"VOL_max": 0.1,
|
|
74
|
+
"VOH_min": 1.1,
|
|
75
|
+
"VCC": 1.2,
|
|
76
|
+
},
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def to_digital(
|
|
81
|
+
trace: WaveformTrace,
|
|
82
|
+
*,
|
|
83
|
+
threshold: float | Literal["auto"] = "auto",
|
|
84
|
+
hysteresis: float | tuple[float, float] | None = None,
|
|
85
|
+
) -> DigitalTrace:
|
|
86
|
+
"""Extract digital signal from analog waveform.
|
|
87
|
+
|
|
88
|
+
Converts an analog waveform to a digital (boolean) signal using
|
|
89
|
+
threshold comparison.
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
trace: Input analog waveform trace.
|
|
93
|
+
threshold: Voltage threshold for conversion. Can be:
|
|
94
|
+
- A float value for fixed threshold
|
|
95
|
+
- "auto" for adaptive threshold (midpoint of 10th-90th percentile)
|
|
96
|
+
hysteresis: Hysteresis for noise immunity. Can be:
|
|
97
|
+
- None: No hysteresis
|
|
98
|
+
- A float: Symmetric hysteresis band around threshold
|
|
99
|
+
- A tuple (low, high): Explicit low and high thresholds
|
|
100
|
+
|
|
101
|
+
Returns:
|
|
102
|
+
DigitalTrace with boolean data and detected edges.
|
|
103
|
+
|
|
104
|
+
Raises:
|
|
105
|
+
InsufficientDataError: If trace has insufficient data.
|
|
106
|
+
|
|
107
|
+
Example:
|
|
108
|
+
>>> digital = to_digital(analog_trace, threshold=1.4)
|
|
109
|
+
>>> print(f"High samples: {digital.data.sum()}")
|
|
110
|
+
|
|
111
|
+
>>> # With hysteresis for noisy signals
|
|
112
|
+
>>> digital = to_digital(analog_trace, threshold=1.4, hysteresis=0.2)
|
|
113
|
+
|
|
114
|
+
References:
|
|
115
|
+
TTL Logic thresholds: VIL_max=0.8V, VIH_min=2.0V
|
|
116
|
+
"""
|
|
117
|
+
if len(trace.data) < 2:
|
|
118
|
+
raise InsufficientDataError(
|
|
119
|
+
"Trace too short for digital extraction",
|
|
120
|
+
required=2,
|
|
121
|
+
available=len(trace.data),
|
|
122
|
+
analysis_type="digital_extraction",
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
# Convert memoryview to ndarray if needed
|
|
126
|
+
data = np.asarray(trace.data)
|
|
127
|
+
|
|
128
|
+
# Determine threshold
|
|
129
|
+
if threshold == "auto":
|
|
130
|
+
# Adaptive threshold: midpoint of 10th-90th percentile
|
|
131
|
+
p10, p90 = np.percentile(data, [10, 90])
|
|
132
|
+
thresh_value = (p10 + p90) / 2.0
|
|
133
|
+
else:
|
|
134
|
+
thresh_value = float(threshold)
|
|
135
|
+
|
|
136
|
+
# Apply threshold with or without hysteresis
|
|
137
|
+
if hysteresis is not None:
|
|
138
|
+
if isinstance(hysteresis, tuple):
|
|
139
|
+
thresh_low, thresh_high = hysteresis
|
|
140
|
+
else:
|
|
141
|
+
thresh_low = thresh_value - hysteresis / 2
|
|
142
|
+
thresh_high = thresh_value + hysteresis / 2
|
|
143
|
+
digital_data = _apply_hysteresis(data, thresh_low, thresh_high)
|
|
144
|
+
else:
|
|
145
|
+
digital_data = data >= thresh_value
|
|
146
|
+
|
|
147
|
+
# Detect edges
|
|
148
|
+
edges = _detect_edges_internal(data, digital_data, trace.metadata.sample_rate, thresh_value)
|
|
149
|
+
|
|
150
|
+
return DigitalTrace(
|
|
151
|
+
data=digital_data,
|
|
152
|
+
metadata=trace.metadata,
|
|
153
|
+
edges=edges,
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def _apply_hysteresis(
|
|
158
|
+
data: NDArray[np.floating[Any]],
|
|
159
|
+
thresh_low: float,
|
|
160
|
+
thresh_high: float,
|
|
161
|
+
) -> NDArray[np.bool_]:
|
|
162
|
+
"""Apply Schmitt trigger-style hysteresis thresholding.
|
|
163
|
+
|
|
164
|
+
Args:
|
|
165
|
+
data: Input analog data.
|
|
166
|
+
thresh_low: Lower threshold (switch to low when below).
|
|
167
|
+
thresh_high: Upper threshold (switch to high when above).
|
|
168
|
+
|
|
169
|
+
Returns:
|
|
170
|
+
Boolean array with hysteresis applied.
|
|
171
|
+
"""
|
|
172
|
+
result = np.zeros(len(data), dtype=np.bool_)
|
|
173
|
+
|
|
174
|
+
# Initial state based on first sample
|
|
175
|
+
state = data[0] >= (thresh_low + thresh_high) / 2
|
|
176
|
+
|
|
177
|
+
for i, value in enumerate(data):
|
|
178
|
+
if state:
|
|
179
|
+
# Currently high, switch low if below thresh_low
|
|
180
|
+
if value < thresh_low:
|
|
181
|
+
state = False
|
|
182
|
+
# Currently low, switch high if above thresh_high
|
|
183
|
+
elif value >= thresh_high:
|
|
184
|
+
state = True
|
|
185
|
+
result[i] = state
|
|
186
|
+
|
|
187
|
+
return result
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def detect_edges(
|
|
191
|
+
trace: WaveformTrace | DigitalTrace,
|
|
192
|
+
*,
|
|
193
|
+
edge_type: Literal["rising", "falling", "both"] = "both",
|
|
194
|
+
threshold: float | Literal["auto"] = "auto",
|
|
195
|
+
) -> NDArray[np.float64]:
|
|
196
|
+
"""Detect edge transitions in a signal.
|
|
197
|
+
|
|
198
|
+
Finds rising and/or falling edges with sub-sample timestamp
|
|
199
|
+
interpolation for improved accuracy.
|
|
200
|
+
|
|
201
|
+
Args:
|
|
202
|
+
trace: Input waveform (analog or digital).
|
|
203
|
+
edge_type: Type of edges to detect:
|
|
204
|
+
- "rising": Low-to-high transitions
|
|
205
|
+
- "falling": High-to-low transitions
|
|
206
|
+
- "both": All transitions
|
|
207
|
+
threshold: Threshold for edge detection (only for analog traces).
|
|
208
|
+
|
|
209
|
+
Returns:
|
|
210
|
+
Array of edge timestamps in seconds.
|
|
211
|
+
|
|
212
|
+
Raises:
|
|
213
|
+
InsufficientDataError: If trace has insufficient data.
|
|
214
|
+
|
|
215
|
+
Example:
|
|
216
|
+
>>> edges = detect_edges(trace, edge_type="rising")
|
|
217
|
+
>>> print(f"Found {len(edges)} rising edges")
|
|
218
|
+
"""
|
|
219
|
+
if len(trace.data) < 2:
|
|
220
|
+
raise InsufficientDataError(
|
|
221
|
+
"Trace too short for edge detection",
|
|
222
|
+
required=2,
|
|
223
|
+
available=len(trace.data),
|
|
224
|
+
analysis_type="edge_detection",
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
# Convert to digital if analog
|
|
228
|
+
digital = to_digital(trace, threshold=threshold) if isinstance(trace, WaveformTrace) else trace
|
|
229
|
+
|
|
230
|
+
# Find transitions - ensure we have a numpy array
|
|
231
|
+
data = np.asarray(digital.data)
|
|
232
|
+
|
|
233
|
+
transitions = np.diff(data.astype(np.int8))
|
|
234
|
+
|
|
235
|
+
# Get edge indices
|
|
236
|
+
if edge_type == "rising":
|
|
237
|
+
edge_indices = np.where(transitions == 1)[0]
|
|
238
|
+
elif edge_type == "falling":
|
|
239
|
+
edge_indices = np.where(transitions == -1)[0]
|
|
240
|
+
else: # both
|
|
241
|
+
edge_indices = np.where(transitions != 0)[0]
|
|
242
|
+
|
|
243
|
+
# Convert indices to timestamps
|
|
244
|
+
sample_period = digital.metadata.time_base
|
|
245
|
+
timestamps = edge_indices.astype(np.float64) * sample_period
|
|
246
|
+
|
|
247
|
+
# Sub-sample interpolation for analog traces
|
|
248
|
+
if isinstance(trace, WaveformTrace) and threshold != "auto":
|
|
249
|
+
thresh_value = float(threshold)
|
|
250
|
+
timestamps = _interpolate_edges(trace.data, edge_indices, sample_period, thresh_value)
|
|
251
|
+
|
|
252
|
+
return timestamps
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def _detect_edges_internal(
|
|
256
|
+
analog_data: NDArray[np.floating[Any]],
|
|
257
|
+
digital_data: NDArray[np.bool_],
|
|
258
|
+
sample_rate: float,
|
|
259
|
+
threshold: float,
|
|
260
|
+
) -> list[tuple[float, bool]]:
|
|
261
|
+
"""Detect edges and return as (timestamp, is_rising) tuples.
|
|
262
|
+
|
|
263
|
+
Args:
|
|
264
|
+
analog_data: Original analog data for interpolation.
|
|
265
|
+
digital_data: Thresholded digital data.
|
|
266
|
+
sample_rate: Sample rate in Hz.
|
|
267
|
+
threshold: Threshold used for conversion.
|
|
268
|
+
|
|
269
|
+
Returns:
|
|
270
|
+
List of (timestamp, is_rising) tuples.
|
|
271
|
+
"""
|
|
272
|
+
sample_period = 1.0 / sample_rate
|
|
273
|
+
transitions = np.diff(digital_data.astype(np.int8))
|
|
274
|
+
|
|
275
|
+
edges: list[tuple[float, bool]] = []
|
|
276
|
+
|
|
277
|
+
# Rising edges
|
|
278
|
+
rising_indices = np.where(transitions == 1)[0]
|
|
279
|
+
for idx in rising_indices:
|
|
280
|
+
# Sub-sample interpolation
|
|
281
|
+
if 0 < idx < len(analog_data) - 1:
|
|
282
|
+
t = _interpolate_crossing(
|
|
283
|
+
analog_data[idx], analog_data[idx + 1], threshold, sample_period
|
|
284
|
+
)
|
|
285
|
+
timestamp = idx * sample_period + t
|
|
286
|
+
else:
|
|
287
|
+
timestamp = idx * sample_period
|
|
288
|
+
edges.append((timestamp, True))
|
|
289
|
+
|
|
290
|
+
# Falling edges
|
|
291
|
+
falling_indices = np.where(transitions == -1)[0]
|
|
292
|
+
for idx in falling_indices:
|
|
293
|
+
if 0 < idx < len(analog_data) - 1:
|
|
294
|
+
t = _interpolate_crossing(
|
|
295
|
+
analog_data[idx], analog_data[idx + 1], threshold, sample_period
|
|
296
|
+
)
|
|
297
|
+
timestamp = idx * sample_period + t
|
|
298
|
+
else:
|
|
299
|
+
timestamp = idx * sample_period
|
|
300
|
+
edges.append((timestamp, False))
|
|
301
|
+
|
|
302
|
+
# Sort by timestamp
|
|
303
|
+
edges.sort(key=lambda x: x[0])
|
|
304
|
+
|
|
305
|
+
return edges
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
def _interpolate_edges(
|
|
309
|
+
data: NDArray[np.floating[Any]],
|
|
310
|
+
edge_indices: NDArray[np.intp],
|
|
311
|
+
sample_period: float,
|
|
312
|
+
threshold: float,
|
|
313
|
+
) -> NDArray[np.float64]:
|
|
314
|
+
"""Interpolate edge timestamps for sub-sample accuracy.
|
|
315
|
+
|
|
316
|
+
Uses linear interpolation between samples to estimate the
|
|
317
|
+
exact crossing point.
|
|
318
|
+
|
|
319
|
+
Args:
|
|
320
|
+
data: Analog data array.
|
|
321
|
+
edge_indices: Indices of detected edges.
|
|
322
|
+
sample_period: Time between samples.
|
|
323
|
+
threshold: Threshold level.
|
|
324
|
+
|
|
325
|
+
Returns:
|
|
326
|
+
Array of interpolated timestamps.
|
|
327
|
+
"""
|
|
328
|
+
timestamps = np.zeros(len(edge_indices), dtype=np.float64)
|
|
329
|
+
|
|
330
|
+
for i, idx in enumerate(edge_indices):
|
|
331
|
+
base_time = idx * sample_period
|
|
332
|
+
|
|
333
|
+
if 0 < idx < len(data) - 1:
|
|
334
|
+
# Linear interpolation between samples
|
|
335
|
+
t = _interpolate_crossing(data[idx], data[idx + 1], threshold, sample_period)
|
|
336
|
+
timestamps[i] = base_time + t
|
|
337
|
+
else:
|
|
338
|
+
timestamps[i] = base_time
|
|
339
|
+
|
|
340
|
+
return timestamps
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
def _interpolate_crossing(
|
|
344
|
+
v1: float,
|
|
345
|
+
v2: float,
|
|
346
|
+
threshold: float,
|
|
347
|
+
sample_period: float,
|
|
348
|
+
) -> float:
|
|
349
|
+
"""Linearly interpolate threshold crossing time.
|
|
350
|
+
|
|
351
|
+
Args:
|
|
352
|
+
v1: Voltage at sample before crossing.
|
|
353
|
+
v2: Voltage at sample after crossing.
|
|
354
|
+
threshold: Threshold level.
|
|
355
|
+
sample_period: Time between samples.
|
|
356
|
+
|
|
357
|
+
Returns:
|
|
358
|
+
Time offset from v1 to crossing point.
|
|
359
|
+
"""
|
|
360
|
+
dv = v2 - v1
|
|
361
|
+
if abs(dv) < 1e-12:
|
|
362
|
+
return sample_period / 2 # Midpoint if no change
|
|
363
|
+
|
|
364
|
+
# Linear interpolation: t = (threshold - v1) / (v2 - v1) * period
|
|
365
|
+
t = (threshold - v1) / dv * sample_period
|
|
366
|
+
return max(0.0, min(sample_period, t))
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
def get_logic_threshold(
|
|
370
|
+
family: str,
|
|
371
|
+
threshold_type: Literal["midpoint", "VIH", "VIL"] = "midpoint",
|
|
372
|
+
) -> float:
|
|
373
|
+
"""Get threshold voltage for a logic family.
|
|
374
|
+
|
|
375
|
+
Args:
|
|
376
|
+
family: Logic family name (e.g., "TTL", "LVCMOS_3V3").
|
|
377
|
+
threshold_type: Type of threshold:
|
|
378
|
+
- "midpoint": Midpoint between VIL_max and VIH_min
|
|
379
|
+
- "VIH": Minimum input high voltage
|
|
380
|
+
- "VIL": Maximum input low voltage
|
|
381
|
+
|
|
382
|
+
Returns:
|
|
383
|
+
Threshold voltage.
|
|
384
|
+
|
|
385
|
+
Raises:
|
|
386
|
+
ValueError: If family or threshold_type is unknown.
|
|
387
|
+
|
|
388
|
+
Example:
|
|
389
|
+
>>> get_logic_threshold("TTL", "midpoint")
|
|
390
|
+
1.4
|
|
391
|
+
"""
|
|
392
|
+
if family not in LOGIC_FAMILIES:
|
|
393
|
+
available = ", ".join(LOGIC_FAMILIES.keys())
|
|
394
|
+
raise ValueError(f"Unknown logic family: {family}. Available: {available}")
|
|
395
|
+
|
|
396
|
+
levels = LOGIC_FAMILIES[family]
|
|
397
|
+
|
|
398
|
+
if threshold_type == "midpoint":
|
|
399
|
+
return (levels["VIL_max"] + levels["VIH_min"]) / 2
|
|
400
|
+
elif threshold_type == "VIH":
|
|
401
|
+
return levels["VIH_min"]
|
|
402
|
+
elif threshold_type == "VIL":
|
|
403
|
+
return levels["VIL_max"]
|
|
404
|
+
else:
|
|
405
|
+
raise ValueError(f"Unknown threshold_type: {threshold_type}")
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
__all__ = [
|
|
409
|
+
"LOGIC_FAMILIES",
|
|
410
|
+
"detect_edges",
|
|
411
|
+
"get_logic_threshold",
|
|
412
|
+
"to_digital",
|
|
413
|
+
]
|