oscura 0.0.1__py3-none-any.whl → 0.1.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- oscura/__init__.py +813 -8
- oscura/__main__.py +392 -0
- oscura/analyzers/__init__.py +37 -0
- oscura/analyzers/digital/__init__.py +177 -0
- oscura/analyzers/digital/bus.py +691 -0
- oscura/analyzers/digital/clock.py +805 -0
- oscura/analyzers/digital/correlation.py +720 -0
- oscura/analyzers/digital/edges.py +632 -0
- oscura/analyzers/digital/extraction.py +413 -0
- oscura/analyzers/digital/quality.py +878 -0
- oscura/analyzers/digital/signal_quality.py +877 -0
- oscura/analyzers/digital/thresholds.py +708 -0
- oscura/analyzers/digital/timing.py +1104 -0
- oscura/analyzers/eye/__init__.py +46 -0
- oscura/analyzers/eye/diagram.py +434 -0
- oscura/analyzers/eye/metrics.py +555 -0
- oscura/analyzers/jitter/__init__.py +83 -0
- oscura/analyzers/jitter/ber.py +333 -0
- oscura/analyzers/jitter/decomposition.py +759 -0
- oscura/analyzers/jitter/measurements.py +413 -0
- oscura/analyzers/jitter/spectrum.py +220 -0
- oscura/analyzers/measurements.py +40 -0
- oscura/analyzers/packet/__init__.py +171 -0
- oscura/analyzers/packet/daq.py +1077 -0
- oscura/analyzers/packet/metrics.py +437 -0
- oscura/analyzers/packet/parser.py +327 -0
- oscura/analyzers/packet/payload.py +2156 -0
- oscura/analyzers/packet/payload_analysis.py +1312 -0
- oscura/analyzers/packet/payload_extraction.py +236 -0
- oscura/analyzers/packet/payload_patterns.py +670 -0
- oscura/analyzers/packet/stream.py +359 -0
- oscura/analyzers/patterns/__init__.py +266 -0
- oscura/analyzers/patterns/clustering.py +1036 -0
- oscura/analyzers/patterns/discovery.py +539 -0
- oscura/analyzers/patterns/learning.py +797 -0
- oscura/analyzers/patterns/matching.py +1091 -0
- oscura/analyzers/patterns/periodic.py +650 -0
- oscura/analyzers/patterns/sequences.py +767 -0
- oscura/analyzers/power/__init__.py +116 -0
- oscura/analyzers/power/ac_power.py +391 -0
- oscura/analyzers/power/basic.py +383 -0
- oscura/analyzers/power/conduction.py +314 -0
- oscura/analyzers/power/efficiency.py +297 -0
- oscura/analyzers/power/ripple.py +356 -0
- oscura/analyzers/power/soa.py +372 -0
- oscura/analyzers/power/switching.py +479 -0
- oscura/analyzers/protocol/__init__.py +150 -0
- oscura/analyzers/protocols/__init__.py +150 -0
- oscura/analyzers/protocols/base.py +500 -0
- oscura/analyzers/protocols/can.py +620 -0
- oscura/analyzers/protocols/can_fd.py +448 -0
- oscura/analyzers/protocols/flexray.py +405 -0
- oscura/analyzers/protocols/hdlc.py +399 -0
- oscura/analyzers/protocols/i2c.py +368 -0
- oscura/analyzers/protocols/i2s.py +296 -0
- oscura/analyzers/protocols/jtag.py +393 -0
- oscura/analyzers/protocols/lin.py +445 -0
- oscura/analyzers/protocols/manchester.py +333 -0
- oscura/analyzers/protocols/onewire.py +501 -0
- oscura/analyzers/protocols/spi.py +334 -0
- oscura/analyzers/protocols/swd.py +325 -0
- oscura/analyzers/protocols/uart.py +393 -0
- oscura/analyzers/protocols/usb.py +495 -0
- oscura/analyzers/signal_integrity/__init__.py +63 -0
- oscura/analyzers/signal_integrity/embedding.py +294 -0
- oscura/analyzers/signal_integrity/equalization.py +370 -0
- oscura/analyzers/signal_integrity/sparams.py +484 -0
- oscura/analyzers/spectral/__init__.py +53 -0
- oscura/analyzers/spectral/chunked.py +273 -0
- oscura/analyzers/spectral/chunked_fft.py +571 -0
- oscura/analyzers/spectral/chunked_wavelet.py +391 -0
- oscura/analyzers/spectral/fft.py +92 -0
- oscura/analyzers/statistical/__init__.py +250 -0
- oscura/analyzers/statistical/checksum.py +923 -0
- oscura/analyzers/statistical/chunked_corr.py +228 -0
- oscura/analyzers/statistical/classification.py +778 -0
- oscura/analyzers/statistical/entropy.py +1113 -0
- oscura/analyzers/statistical/ngrams.py +614 -0
- oscura/analyzers/statistics/__init__.py +119 -0
- oscura/analyzers/statistics/advanced.py +885 -0
- oscura/analyzers/statistics/basic.py +263 -0
- oscura/analyzers/statistics/correlation.py +630 -0
- oscura/analyzers/statistics/distribution.py +298 -0
- oscura/analyzers/statistics/outliers.py +463 -0
- oscura/analyzers/statistics/streaming.py +93 -0
- oscura/analyzers/statistics/trend.py +520 -0
- oscura/analyzers/validation.py +598 -0
- oscura/analyzers/waveform/__init__.py +36 -0
- oscura/analyzers/waveform/measurements.py +943 -0
- oscura/analyzers/waveform/measurements_with_uncertainty.py +371 -0
- oscura/analyzers/waveform/spectral.py +1689 -0
- oscura/analyzers/waveform/wavelets.py +298 -0
- oscura/api/__init__.py +62 -0
- oscura/api/dsl.py +538 -0
- oscura/api/fluent.py +571 -0
- oscura/api/operators.py +498 -0
- oscura/api/optimization.py +392 -0
- oscura/api/profiling.py +396 -0
- oscura/automotive/__init__.py +73 -0
- oscura/automotive/can/__init__.py +52 -0
- oscura/automotive/can/analysis.py +356 -0
- oscura/automotive/can/checksum.py +250 -0
- oscura/automotive/can/correlation.py +212 -0
- oscura/automotive/can/discovery.py +355 -0
- oscura/automotive/can/message_wrapper.py +375 -0
- oscura/automotive/can/models.py +385 -0
- oscura/automotive/can/patterns.py +381 -0
- oscura/automotive/can/session.py +452 -0
- oscura/automotive/can/state_machine.py +300 -0
- oscura/automotive/can/stimulus_response.py +461 -0
- oscura/automotive/dbc/__init__.py +15 -0
- oscura/automotive/dbc/generator.py +156 -0
- oscura/automotive/dbc/parser.py +146 -0
- oscura/automotive/dtc/__init__.py +30 -0
- oscura/automotive/dtc/database.py +3036 -0
- oscura/automotive/j1939/__init__.py +14 -0
- oscura/automotive/j1939/decoder.py +745 -0
- oscura/automotive/loaders/__init__.py +35 -0
- oscura/automotive/loaders/asc.py +98 -0
- oscura/automotive/loaders/blf.py +77 -0
- oscura/automotive/loaders/csv_can.py +136 -0
- oscura/automotive/loaders/dispatcher.py +136 -0
- oscura/automotive/loaders/mdf.py +331 -0
- oscura/automotive/loaders/pcap.py +132 -0
- oscura/automotive/obd/__init__.py +14 -0
- oscura/automotive/obd/decoder.py +707 -0
- oscura/automotive/uds/__init__.py +48 -0
- oscura/automotive/uds/decoder.py +265 -0
- oscura/automotive/uds/models.py +64 -0
- oscura/automotive/visualization.py +369 -0
- oscura/batch/__init__.py +55 -0
- oscura/batch/advanced.py +627 -0
- oscura/batch/aggregate.py +300 -0
- oscura/batch/analyze.py +139 -0
- oscura/batch/logging.py +487 -0
- oscura/batch/metrics.py +556 -0
- oscura/builders/__init__.py +41 -0
- oscura/builders/signal_builder.py +1131 -0
- oscura/cli/__init__.py +14 -0
- oscura/cli/batch.py +339 -0
- oscura/cli/characterize.py +273 -0
- oscura/cli/compare.py +775 -0
- oscura/cli/decode.py +551 -0
- oscura/cli/main.py +247 -0
- oscura/cli/shell.py +350 -0
- oscura/comparison/__init__.py +66 -0
- oscura/comparison/compare.py +397 -0
- oscura/comparison/golden.py +487 -0
- oscura/comparison/limits.py +391 -0
- oscura/comparison/mask.py +434 -0
- oscura/comparison/trace_diff.py +30 -0
- oscura/comparison/visualization.py +481 -0
- oscura/compliance/__init__.py +70 -0
- oscura/compliance/advanced.py +756 -0
- oscura/compliance/masks.py +363 -0
- oscura/compliance/reporting.py +483 -0
- oscura/compliance/testing.py +298 -0
- oscura/component/__init__.py +38 -0
- oscura/component/impedance.py +365 -0
- oscura/component/reactive.py +598 -0
- oscura/component/transmission_line.py +312 -0
- oscura/config/__init__.py +191 -0
- oscura/config/defaults.py +254 -0
- oscura/config/loader.py +348 -0
- oscura/config/memory.py +271 -0
- oscura/config/migration.py +458 -0
- oscura/config/pipeline.py +1077 -0
- oscura/config/preferences.py +530 -0
- oscura/config/protocol.py +875 -0
- oscura/config/schema.py +713 -0
- oscura/config/settings.py +420 -0
- oscura/config/thresholds.py +599 -0
- oscura/convenience.py +457 -0
- oscura/core/__init__.py +299 -0
- oscura/core/audit.py +457 -0
- oscura/core/backend_selector.py +405 -0
- oscura/core/cache.py +590 -0
- oscura/core/cancellation.py +439 -0
- oscura/core/confidence.py +225 -0
- oscura/core/config.py +506 -0
- oscura/core/correlation.py +216 -0
- oscura/core/cross_domain.py +422 -0
- oscura/core/debug.py +301 -0
- oscura/core/edge_cases.py +541 -0
- oscura/core/exceptions.py +535 -0
- oscura/core/gpu_backend.py +523 -0
- oscura/core/lazy.py +832 -0
- oscura/core/log_query.py +540 -0
- oscura/core/logging.py +931 -0
- oscura/core/logging_advanced.py +952 -0
- oscura/core/memoize.py +171 -0
- oscura/core/memory_check.py +274 -0
- oscura/core/memory_guard.py +290 -0
- oscura/core/memory_limits.py +336 -0
- oscura/core/memory_monitor.py +453 -0
- oscura/core/memory_progress.py +465 -0
- oscura/core/memory_warnings.py +315 -0
- oscura/core/numba_backend.py +362 -0
- oscura/core/performance.py +352 -0
- oscura/core/progress.py +524 -0
- oscura/core/provenance.py +358 -0
- oscura/core/results.py +331 -0
- oscura/core/types.py +504 -0
- oscura/core/uncertainty.py +383 -0
- oscura/discovery/__init__.py +52 -0
- oscura/discovery/anomaly_detector.py +672 -0
- oscura/discovery/auto_decoder.py +415 -0
- oscura/discovery/comparison.py +497 -0
- oscura/discovery/quality_validator.py +528 -0
- oscura/discovery/signal_detector.py +769 -0
- oscura/dsl/__init__.py +73 -0
- oscura/dsl/commands.py +246 -0
- oscura/dsl/interpreter.py +455 -0
- oscura/dsl/parser.py +689 -0
- oscura/dsl/repl.py +172 -0
- oscura/exceptions.py +59 -0
- oscura/exploratory/__init__.py +111 -0
- oscura/exploratory/error_recovery.py +642 -0
- oscura/exploratory/fuzzy.py +513 -0
- oscura/exploratory/fuzzy_advanced.py +786 -0
- oscura/exploratory/legacy.py +831 -0
- oscura/exploratory/parse.py +358 -0
- oscura/exploratory/recovery.py +275 -0
- oscura/exploratory/sync.py +382 -0
- oscura/exploratory/unknown.py +707 -0
- oscura/export/__init__.py +25 -0
- oscura/export/wireshark/README.md +265 -0
- oscura/export/wireshark/__init__.py +47 -0
- oscura/export/wireshark/generator.py +312 -0
- oscura/export/wireshark/lua_builder.py +159 -0
- oscura/export/wireshark/templates/dissector.lua.j2 +92 -0
- oscura/export/wireshark/type_mapping.py +165 -0
- oscura/export/wireshark/validator.py +105 -0
- oscura/exporters/__init__.py +94 -0
- oscura/exporters/csv.py +303 -0
- oscura/exporters/exporters.py +44 -0
- oscura/exporters/hdf5.py +219 -0
- oscura/exporters/html_export.py +701 -0
- oscura/exporters/json_export.py +291 -0
- oscura/exporters/markdown_export.py +367 -0
- oscura/exporters/matlab_export.py +354 -0
- oscura/exporters/npz_export.py +219 -0
- oscura/exporters/spice_export.py +210 -0
- oscura/extensibility/__init__.py +131 -0
- oscura/extensibility/docs.py +752 -0
- oscura/extensibility/extensions.py +1125 -0
- oscura/extensibility/logging.py +259 -0
- oscura/extensibility/measurements.py +485 -0
- oscura/extensibility/plugins.py +414 -0
- oscura/extensibility/registry.py +346 -0
- oscura/extensibility/templates.py +913 -0
- oscura/extensibility/validation.py +651 -0
- oscura/filtering/__init__.py +89 -0
- oscura/filtering/base.py +563 -0
- oscura/filtering/convenience.py +564 -0
- oscura/filtering/design.py +725 -0
- oscura/filtering/filters.py +32 -0
- oscura/filtering/introspection.py +605 -0
- oscura/guidance/__init__.py +24 -0
- oscura/guidance/recommender.py +429 -0
- oscura/guidance/wizard.py +518 -0
- oscura/inference/__init__.py +251 -0
- oscura/inference/active_learning/README.md +153 -0
- oscura/inference/active_learning/__init__.py +38 -0
- oscura/inference/active_learning/lstar.py +257 -0
- oscura/inference/active_learning/observation_table.py +230 -0
- oscura/inference/active_learning/oracle.py +78 -0
- oscura/inference/active_learning/teachers/__init__.py +15 -0
- oscura/inference/active_learning/teachers/simulator.py +192 -0
- oscura/inference/adaptive_tuning.py +453 -0
- oscura/inference/alignment.py +653 -0
- oscura/inference/bayesian.py +943 -0
- oscura/inference/binary.py +1016 -0
- oscura/inference/crc_reverse.py +711 -0
- oscura/inference/logic.py +288 -0
- oscura/inference/message_format.py +1305 -0
- oscura/inference/protocol.py +417 -0
- oscura/inference/protocol_dsl.py +1084 -0
- oscura/inference/protocol_library.py +1230 -0
- oscura/inference/sequences.py +809 -0
- oscura/inference/signal_intelligence.py +1509 -0
- oscura/inference/spectral.py +215 -0
- oscura/inference/state_machine.py +634 -0
- oscura/inference/stream.py +918 -0
- oscura/integrations/__init__.py +59 -0
- oscura/integrations/llm.py +1827 -0
- oscura/jupyter/__init__.py +32 -0
- oscura/jupyter/display.py +268 -0
- oscura/jupyter/magic.py +334 -0
- oscura/loaders/__init__.py +526 -0
- oscura/loaders/binary.py +69 -0
- oscura/loaders/configurable.py +1255 -0
- oscura/loaders/csv.py +26 -0
- oscura/loaders/csv_loader.py +473 -0
- oscura/loaders/hdf5.py +9 -0
- oscura/loaders/hdf5_loader.py +510 -0
- oscura/loaders/lazy.py +370 -0
- oscura/loaders/mmap_loader.py +583 -0
- oscura/loaders/numpy_loader.py +436 -0
- oscura/loaders/pcap.py +432 -0
- oscura/loaders/preprocessing.py +368 -0
- oscura/loaders/rigol.py +287 -0
- oscura/loaders/sigrok.py +321 -0
- oscura/loaders/tdms.py +367 -0
- oscura/loaders/tektronix.py +711 -0
- oscura/loaders/validation.py +584 -0
- oscura/loaders/vcd.py +464 -0
- oscura/loaders/wav.py +233 -0
- oscura/math/__init__.py +45 -0
- oscura/math/arithmetic.py +824 -0
- oscura/math/interpolation.py +413 -0
- oscura/onboarding/__init__.py +39 -0
- oscura/onboarding/help.py +498 -0
- oscura/onboarding/tutorials.py +405 -0
- oscura/onboarding/wizard.py +466 -0
- oscura/optimization/__init__.py +19 -0
- oscura/optimization/parallel.py +440 -0
- oscura/optimization/search.py +532 -0
- oscura/pipeline/__init__.py +43 -0
- oscura/pipeline/base.py +338 -0
- oscura/pipeline/composition.py +242 -0
- oscura/pipeline/parallel.py +448 -0
- oscura/pipeline/pipeline.py +375 -0
- oscura/pipeline/reverse_engineering.py +1119 -0
- oscura/plugins/__init__.py +122 -0
- oscura/plugins/base.py +272 -0
- oscura/plugins/cli.py +497 -0
- oscura/plugins/discovery.py +411 -0
- oscura/plugins/isolation.py +418 -0
- oscura/plugins/lifecycle.py +959 -0
- oscura/plugins/manager.py +493 -0
- oscura/plugins/registry.py +421 -0
- oscura/plugins/versioning.py +372 -0
- oscura/py.typed +0 -0
- oscura/quality/__init__.py +65 -0
- oscura/quality/ensemble.py +740 -0
- oscura/quality/explainer.py +338 -0
- oscura/quality/scoring.py +616 -0
- oscura/quality/warnings.py +456 -0
- oscura/reporting/__init__.py +248 -0
- oscura/reporting/advanced.py +1234 -0
- oscura/reporting/analyze.py +448 -0
- oscura/reporting/argument_preparer.py +596 -0
- oscura/reporting/auto_report.py +507 -0
- oscura/reporting/batch.py +615 -0
- oscura/reporting/chart_selection.py +223 -0
- oscura/reporting/comparison.py +330 -0
- oscura/reporting/config.py +615 -0
- oscura/reporting/content/__init__.py +39 -0
- oscura/reporting/content/executive.py +127 -0
- oscura/reporting/content/filtering.py +191 -0
- oscura/reporting/content/minimal.py +257 -0
- oscura/reporting/content/verbosity.py +162 -0
- oscura/reporting/core.py +508 -0
- oscura/reporting/core_formats/__init__.py +17 -0
- oscura/reporting/core_formats/multi_format.py +210 -0
- oscura/reporting/engine.py +836 -0
- oscura/reporting/export.py +366 -0
- oscura/reporting/formatting/__init__.py +129 -0
- oscura/reporting/formatting/emphasis.py +81 -0
- oscura/reporting/formatting/numbers.py +403 -0
- oscura/reporting/formatting/standards.py +55 -0
- oscura/reporting/formatting.py +466 -0
- oscura/reporting/html.py +578 -0
- oscura/reporting/index.py +590 -0
- oscura/reporting/multichannel.py +296 -0
- oscura/reporting/output.py +379 -0
- oscura/reporting/pdf.py +373 -0
- oscura/reporting/plots.py +731 -0
- oscura/reporting/pptx_export.py +360 -0
- oscura/reporting/renderers/__init__.py +11 -0
- oscura/reporting/renderers/pdf.py +94 -0
- oscura/reporting/sections.py +471 -0
- oscura/reporting/standards.py +680 -0
- oscura/reporting/summary_generator.py +368 -0
- oscura/reporting/tables.py +397 -0
- oscura/reporting/template_system.py +724 -0
- oscura/reporting/templates/__init__.py +15 -0
- oscura/reporting/templates/definition.py +205 -0
- oscura/reporting/templates/index.html +649 -0
- oscura/reporting/templates/index.md +173 -0
- oscura/schemas/__init__.py +158 -0
- oscura/schemas/bus_configuration.json +322 -0
- oscura/schemas/device_mapping.json +182 -0
- oscura/schemas/packet_format.json +418 -0
- oscura/schemas/protocol_definition.json +363 -0
- oscura/search/__init__.py +16 -0
- oscura/search/anomaly.py +292 -0
- oscura/search/context.py +149 -0
- oscura/search/pattern.py +160 -0
- oscura/session/__init__.py +34 -0
- oscura/session/annotations.py +289 -0
- oscura/session/history.py +313 -0
- oscura/session/session.py +445 -0
- oscura/streaming/__init__.py +43 -0
- oscura/streaming/chunked.py +611 -0
- oscura/streaming/progressive.py +393 -0
- oscura/streaming/realtime.py +622 -0
- oscura/testing/__init__.py +54 -0
- oscura/testing/synthetic.py +808 -0
- oscura/triggering/__init__.py +68 -0
- oscura/triggering/base.py +229 -0
- oscura/triggering/edge.py +353 -0
- oscura/triggering/pattern.py +344 -0
- oscura/triggering/pulse.py +581 -0
- oscura/triggering/window.py +453 -0
- oscura/ui/__init__.py +48 -0
- oscura/ui/formatters.py +526 -0
- oscura/ui/progressive_display.py +340 -0
- oscura/utils/__init__.py +99 -0
- oscura/utils/autodetect.py +338 -0
- oscura/utils/buffer.py +389 -0
- oscura/utils/lazy.py +407 -0
- oscura/utils/lazy_imports.py +147 -0
- oscura/utils/memory.py +836 -0
- oscura/utils/memory_advanced.py +1326 -0
- oscura/utils/memory_extensions.py +465 -0
- oscura/utils/progressive.py +352 -0
- oscura/utils/windowing.py +362 -0
- oscura/visualization/__init__.py +321 -0
- oscura/visualization/accessibility.py +526 -0
- oscura/visualization/annotations.py +374 -0
- oscura/visualization/axis_scaling.py +305 -0
- oscura/visualization/colors.py +453 -0
- oscura/visualization/digital.py +337 -0
- oscura/visualization/eye.py +420 -0
- oscura/visualization/histogram.py +281 -0
- oscura/visualization/interactive.py +858 -0
- oscura/visualization/jitter.py +702 -0
- oscura/visualization/keyboard.py +394 -0
- oscura/visualization/layout.py +365 -0
- oscura/visualization/optimization.py +1028 -0
- oscura/visualization/palettes.py +446 -0
- oscura/visualization/plot.py +92 -0
- oscura/visualization/power.py +290 -0
- oscura/visualization/power_extended.py +626 -0
- oscura/visualization/presets.py +467 -0
- oscura/visualization/protocols.py +932 -0
- oscura/visualization/render.py +207 -0
- oscura/visualization/rendering.py +444 -0
- oscura/visualization/reverse_engineering.py +791 -0
- oscura/visualization/signal_integrity.py +808 -0
- oscura/visualization/specialized.py +553 -0
- oscura/visualization/spectral.py +811 -0
- oscura/visualization/styles.py +381 -0
- oscura/visualization/thumbnails.py +311 -0
- oscura/visualization/time_axis.py +351 -0
- oscura/visualization/waveform.py +367 -0
- oscura/workflow/__init__.py +13 -0
- oscura/workflow/dag.py +377 -0
- oscura/workflows/__init__.py +58 -0
- oscura/workflows/compliance.py +280 -0
- oscura/workflows/digital.py +272 -0
- oscura/workflows/multi_trace.py +502 -0
- oscura/workflows/power.py +178 -0
- oscura/workflows/protocol.py +492 -0
- oscura/workflows/reverse_engineering.py +639 -0
- oscura/workflows/signal_integrity.py +227 -0
- oscura-0.1.1.dist-info/METADATA +300 -0
- oscura-0.1.1.dist-info/RECORD +463 -0
- oscura-0.1.1.dist-info/entry_points.txt +2 -0
- {oscura-0.0.1.dist-info → oscura-0.1.1.dist-info}/licenses/LICENSE +1 -1
- oscura-0.0.1.dist-info/METADATA +0 -63
- oscura-0.0.1.dist-info/RECORD +0 -5
- {oscura-0.0.1.dist-info → oscura-0.1.1.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
"""Mask testing for Oscura.
|
|
2
|
+
|
|
3
|
+
This module provides mask-based pass/fail testing for waveforms,
|
|
4
|
+
including eye diagram masks and custom polygon masks.
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
Example:
|
|
8
|
+
>>> from oscura.comparison import mask_test, eye_mask
|
|
9
|
+
>>> mask = eye_mask(0.5, 0.4, 0.3)
|
|
10
|
+
>>> result = mask_test(trace, mask)
|
|
11
|
+
|
|
12
|
+
References:
|
|
13
|
+
IEEE 802.3: Ethernet eye diagram mask specifications
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
from dataclasses import dataclass, field
|
|
19
|
+
from typing import TYPE_CHECKING, Any, Literal
|
|
20
|
+
|
|
21
|
+
import numpy as np
|
|
22
|
+
|
|
23
|
+
from oscura.core.exceptions import AnalysisError
|
|
24
|
+
|
|
25
|
+
if TYPE_CHECKING:
|
|
26
|
+
from numpy.typing import NDArray
|
|
27
|
+
|
|
28
|
+
from oscura.core.types import WaveformTrace
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclass
|
|
32
|
+
class MaskRegion:
|
|
33
|
+
"""A region in a mask definition.
|
|
34
|
+
|
|
35
|
+
Represents a polygon region that waveform data must avoid
|
|
36
|
+
(violation region) or must stay within (boundary region).
|
|
37
|
+
|
|
38
|
+
Attributes:
|
|
39
|
+
vertices: List of (x, y) vertices defining the polygon.
|
|
40
|
+
region_type: "violation" (must avoid) or "boundary" (must stay within).
|
|
41
|
+
name: Optional name for the region.
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
vertices: list[tuple[float, float]]
|
|
45
|
+
region_type: Literal["violation", "boundary"] = "violation"
|
|
46
|
+
name: str = ""
|
|
47
|
+
|
|
48
|
+
def contains_point(self, x: float, y: float) -> bool:
|
|
49
|
+
"""Check if a point is inside the polygon.
|
|
50
|
+
|
|
51
|
+
Uses ray casting algorithm for point-in-polygon test.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
x: X coordinate.
|
|
55
|
+
y: Y coordinate.
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
True if point is inside the polygon.
|
|
59
|
+
"""
|
|
60
|
+
n = len(self.vertices)
|
|
61
|
+
inside = False
|
|
62
|
+
|
|
63
|
+
j = n - 1
|
|
64
|
+
for i in range(n):
|
|
65
|
+
xi, yi = self.vertices[i]
|
|
66
|
+
xj, yj = self.vertices[j]
|
|
67
|
+
|
|
68
|
+
if ((yi > y) != (yj > y)) and (x < (xj - xi) * (y - yi) / (yj - yi) + xi):
|
|
69
|
+
inside = not inside
|
|
70
|
+
j = i
|
|
71
|
+
|
|
72
|
+
return inside
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@dataclass
|
|
76
|
+
class Mask:
|
|
77
|
+
"""Mask definition for waveform testing.
|
|
78
|
+
|
|
79
|
+
A mask consists of one or more regions that define pass/fail criteria
|
|
80
|
+
for waveform data.
|
|
81
|
+
|
|
82
|
+
Attributes:
|
|
83
|
+
regions: List of MaskRegion polygons.
|
|
84
|
+
name: Name of the mask.
|
|
85
|
+
x_unit: Unit for X axis (e.g., "UI", "ns", "samples").
|
|
86
|
+
y_unit: Unit for Y axis (e.g., "V", "mV", "normalized").
|
|
87
|
+
description: Optional description.
|
|
88
|
+
"""
|
|
89
|
+
|
|
90
|
+
regions: list[MaskRegion] = field(default_factory=list)
|
|
91
|
+
name: str = "mask"
|
|
92
|
+
x_unit: str = "UI"
|
|
93
|
+
y_unit: str = "V"
|
|
94
|
+
description: str = ""
|
|
95
|
+
|
|
96
|
+
def add_region(
|
|
97
|
+
self,
|
|
98
|
+
vertices: list[tuple[float, float]],
|
|
99
|
+
region_type: Literal["violation", "boundary"] = "violation",
|
|
100
|
+
name: str = "",
|
|
101
|
+
) -> None:
|
|
102
|
+
"""Add a region to the mask.
|
|
103
|
+
|
|
104
|
+
Args:
|
|
105
|
+
vertices: List of (x, y) vertices.
|
|
106
|
+
region_type: "violation" or "boundary".
|
|
107
|
+
name: Optional region name.
|
|
108
|
+
"""
|
|
109
|
+
self.regions.append(MaskRegion(vertices, region_type, name))
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
@dataclass
|
|
113
|
+
class MaskTestResult:
|
|
114
|
+
"""Result of a mask test.
|
|
115
|
+
|
|
116
|
+
Attributes:
|
|
117
|
+
passed: True if all samples pass the mask test.
|
|
118
|
+
num_violations: Number of samples violating the mask.
|
|
119
|
+
violation_rate: Fraction of samples violating the mask.
|
|
120
|
+
violation_points: List of (x, y) coordinates that violated.
|
|
121
|
+
violations_by_region: Count of violations per region.
|
|
122
|
+
margin: Estimated margin to mask boundary.
|
|
123
|
+
"""
|
|
124
|
+
|
|
125
|
+
passed: bool
|
|
126
|
+
num_violations: int
|
|
127
|
+
violation_rate: float
|
|
128
|
+
violation_points: list[tuple[float, float]] = field(default_factory=list)
|
|
129
|
+
violations_by_region: dict[str, int] = field(default_factory=dict)
|
|
130
|
+
margin: float | None = None
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def create_mask(
|
|
134
|
+
regions: list[dict], # type: ignore[type-arg]
|
|
135
|
+
*,
|
|
136
|
+
name: str = "custom_mask",
|
|
137
|
+
x_unit: str = "samples",
|
|
138
|
+
y_unit: str = "V",
|
|
139
|
+
) -> Mask:
|
|
140
|
+
"""Create a mask from region definitions.
|
|
141
|
+
|
|
142
|
+
Args:
|
|
143
|
+
regions: List of region dicts with 'vertices' and optional
|
|
144
|
+
'type' and 'name' keys.
|
|
145
|
+
name: Mask name.
|
|
146
|
+
x_unit: X axis unit.
|
|
147
|
+
y_unit: Y axis unit.
|
|
148
|
+
|
|
149
|
+
Returns:
|
|
150
|
+
Mask instance.
|
|
151
|
+
|
|
152
|
+
Example:
|
|
153
|
+
>>> mask = create_mask([
|
|
154
|
+
... {"vertices": [(0, 0.5), (0.5, 0.5), (0.5, -0.5), (0, -0.5)],
|
|
155
|
+
... "type": "violation", "name": "center"}
|
|
156
|
+
... ])
|
|
157
|
+
"""
|
|
158
|
+
mask = Mask(name=name, x_unit=x_unit, y_unit=y_unit)
|
|
159
|
+
|
|
160
|
+
for region in regions:
|
|
161
|
+
vertices = region["vertices"]
|
|
162
|
+
region_type = region.get("type", "violation")
|
|
163
|
+
region_name = region.get("name", "")
|
|
164
|
+
mask.add_region(vertices, region_type, region_name)
|
|
165
|
+
|
|
166
|
+
return mask
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def eye_mask(
|
|
170
|
+
eye_width: float = 0.5,
|
|
171
|
+
eye_height: float = 0.4,
|
|
172
|
+
center_height: float = 0.3,
|
|
173
|
+
*,
|
|
174
|
+
x_margin: float = 0.0,
|
|
175
|
+
y_margin: float = 0.1,
|
|
176
|
+
unit_interval: float = 1.0,
|
|
177
|
+
amplitude: float = 1.0,
|
|
178
|
+
) -> Mask:
|
|
179
|
+
"""Create a standard eye diagram mask.
|
|
180
|
+
|
|
181
|
+
Creates a hexagonal eye mask with center violation region and
|
|
182
|
+
optional boundary regions based on eye opening parameters.
|
|
183
|
+
|
|
184
|
+
Args:
|
|
185
|
+
eye_width: Width of eye opening (fraction of UI).
|
|
186
|
+
eye_height: Height of eye opening (fraction of amplitude).
|
|
187
|
+
center_height: Height of center violation region.
|
|
188
|
+
x_margin: X margin for boundary (fraction of UI). Reserved for future use.
|
|
189
|
+
y_margin: Y margin for boundary (fraction of amplitude).
|
|
190
|
+
unit_interval: Duration of unit interval.
|
|
191
|
+
amplitude: Signal amplitude.
|
|
192
|
+
|
|
193
|
+
Returns:
|
|
194
|
+
Mask for eye diagram testing.
|
|
195
|
+
|
|
196
|
+
Example:
|
|
197
|
+
>>> mask = eye_mask(0.5, 0.4) # Standard 50% width, 40% height
|
|
198
|
+
>>> # Creates violation region in center of eye
|
|
199
|
+
"""
|
|
200
|
+
mask = Mask(
|
|
201
|
+
name="eye_mask",
|
|
202
|
+
x_unit="UI",
|
|
203
|
+
y_unit="normalized",
|
|
204
|
+
description=f"Eye mask: {eye_width * 100:.0f}% width, {eye_height * 100:.0f}% height",
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
# Scale parameters
|
|
208
|
+
ui = unit_interval
|
|
209
|
+
amp = amplitude
|
|
210
|
+
|
|
211
|
+
# Center violation region (hexagonal)
|
|
212
|
+
# Points arranged clockwise from left
|
|
213
|
+
center_width = eye_width * ui
|
|
214
|
+
center_top = eye_height * amp / 2
|
|
215
|
+
center_bottom = -eye_height * amp / 2
|
|
216
|
+
mid_width = center_width * 0.7 # Narrower at top/bottom
|
|
217
|
+
|
|
218
|
+
center_vertices = [
|
|
219
|
+
(-center_width / 2, 0), # Left
|
|
220
|
+
(-mid_width / 2, center_top), # Upper left
|
|
221
|
+
(mid_width / 2, center_top), # Upper right
|
|
222
|
+
(center_width / 2, 0), # Right
|
|
223
|
+
(mid_width / 2, center_bottom), # Lower right
|
|
224
|
+
(-mid_width / 2, center_bottom), # Lower left
|
|
225
|
+
]
|
|
226
|
+
mask.add_region(center_vertices, "violation", "eye_center")
|
|
227
|
+
|
|
228
|
+
# Top violation region (above eye)
|
|
229
|
+
top_y = amp / 2 + y_margin * amp
|
|
230
|
+
top_vertices = [
|
|
231
|
+
(-ui / 2, center_top + center_height * amp),
|
|
232
|
+
(ui / 2, center_top + center_height * amp),
|
|
233
|
+
(ui / 2, top_y),
|
|
234
|
+
(-ui / 2, top_y),
|
|
235
|
+
]
|
|
236
|
+
mask.add_region(top_vertices, "violation", "top")
|
|
237
|
+
|
|
238
|
+
# Bottom violation region (below eye)
|
|
239
|
+
bottom_y = -amp / 2 - y_margin * amp
|
|
240
|
+
bottom_vertices = [
|
|
241
|
+
(-ui / 2, bottom_y),
|
|
242
|
+
(ui / 2, bottom_y),
|
|
243
|
+
(ui / 2, center_bottom - center_height * amp),
|
|
244
|
+
(-ui / 2, center_bottom - center_height * amp),
|
|
245
|
+
]
|
|
246
|
+
mask.add_region(bottom_vertices, "violation", "bottom")
|
|
247
|
+
|
|
248
|
+
return mask
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def mask_test(
|
|
252
|
+
trace: WaveformTrace,
|
|
253
|
+
mask: Mask,
|
|
254
|
+
*,
|
|
255
|
+
x_data: NDArray[np.floating[Any]] | None = None,
|
|
256
|
+
normalize: bool = True,
|
|
257
|
+
sample_rate: float | None = None,
|
|
258
|
+
) -> MaskTestResult:
|
|
259
|
+
"""Test waveform against a mask.
|
|
260
|
+
|
|
261
|
+
Checks if any samples of the waveform violate the mask regions.
|
|
262
|
+
|
|
263
|
+
Args:
|
|
264
|
+
trace: Input waveform trace.
|
|
265
|
+
mask: Mask to test against.
|
|
266
|
+
x_data: X coordinates for each sample (if different from time).
|
|
267
|
+
normalize: Normalize Y data to [-1, 1] range.
|
|
268
|
+
sample_rate: Sample rate override.
|
|
269
|
+
|
|
270
|
+
Returns:
|
|
271
|
+
MaskTestResult with pass/fail status and violation details.
|
|
272
|
+
|
|
273
|
+
Example:
|
|
274
|
+
>>> result = mask_test(eye_trace, mask)
|
|
275
|
+
>>> print(f"Violations: {result.num_violations}")
|
|
276
|
+
"""
|
|
277
|
+
# Get Y data
|
|
278
|
+
y_data = trace.data.astype(np.float64)
|
|
279
|
+
|
|
280
|
+
# Get or create X data
|
|
281
|
+
if x_data is None:
|
|
282
|
+
x_data = np.arange(len(y_data), dtype=np.float64)
|
|
283
|
+
|
|
284
|
+
# Normalize if requested
|
|
285
|
+
if normalize:
|
|
286
|
+
y_min, y_max = np.min(y_data), np.max(y_data)
|
|
287
|
+
if y_max - y_min > 0:
|
|
288
|
+
y_data = 2 * (y_data - y_min) / (y_max - y_min) - 1
|
|
289
|
+
|
|
290
|
+
# Test each point against mask regions
|
|
291
|
+
violations: list[tuple[float, float]] = []
|
|
292
|
+
violations_by_region: dict[str, int] = {}
|
|
293
|
+
|
|
294
|
+
for region in mask.regions:
|
|
295
|
+
region_name = region.name or "unnamed"
|
|
296
|
+
violations_by_region[region_name] = 0
|
|
297
|
+
|
|
298
|
+
if region.region_type == "violation":
|
|
299
|
+
# Check if points are inside violation region
|
|
300
|
+
for i, (x, y) in enumerate(zip(x_data, y_data, strict=False)): # noqa: B007
|
|
301
|
+
if region.contains_point(float(x), float(y)):
|
|
302
|
+
violations.append((float(x), float(y)))
|
|
303
|
+
violations_by_region[region_name] += 1
|
|
304
|
+
|
|
305
|
+
elif region.region_type == "boundary":
|
|
306
|
+
# Check if points are outside boundary region
|
|
307
|
+
for i, (x, y) in enumerate(zip(x_data, y_data, strict=False)): # noqa: B007
|
|
308
|
+
if not region.contains_point(float(x), float(y)):
|
|
309
|
+
violations.append((float(x), float(y)))
|
|
310
|
+
violations_by_region[region_name] += 1
|
|
311
|
+
|
|
312
|
+
# Remove duplicates
|
|
313
|
+
unique_violations = list(set(violations))
|
|
314
|
+
num_violations = len(unique_violations)
|
|
315
|
+
violation_rate = num_violations / len(y_data) if len(y_data) > 0 else 0.0
|
|
316
|
+
|
|
317
|
+
# Estimate margin (simplified - distance to nearest mask edge)
|
|
318
|
+
margin = None
|
|
319
|
+
if num_violations == 0 and mask.regions:
|
|
320
|
+
# Find minimum distance to any violation region
|
|
321
|
+
min_dist = float("inf")
|
|
322
|
+
for region in mask.regions:
|
|
323
|
+
if region.region_type == "violation":
|
|
324
|
+
for x, y in zip(x_data, y_data, strict=False):
|
|
325
|
+
for i in range(len(region.vertices)):
|
|
326
|
+
x1, y1 = region.vertices[i]
|
|
327
|
+
x2, y2 = region.vertices[(i + 1) % len(region.vertices)]
|
|
328
|
+
# Distance to line segment
|
|
329
|
+
dist = _point_to_segment_distance(x, y, x1, y1, x2, y2)
|
|
330
|
+
min_dist = min(min_dist, dist)
|
|
331
|
+
margin = min_dist if min_dist != float("inf") else None
|
|
332
|
+
|
|
333
|
+
return MaskTestResult(
|
|
334
|
+
passed=num_violations == 0,
|
|
335
|
+
num_violations=num_violations,
|
|
336
|
+
violation_rate=violation_rate,
|
|
337
|
+
violation_points=unique_violations,
|
|
338
|
+
violations_by_region=violations_by_region,
|
|
339
|
+
margin=margin,
|
|
340
|
+
)
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
def _point_to_segment_distance(
|
|
344
|
+
px: float, py: float, x1: float, y1: float, x2: float, y2: float
|
|
345
|
+
) -> float:
|
|
346
|
+
"""Calculate distance from point to line segment."""
|
|
347
|
+
dx = x2 - x1
|
|
348
|
+
dy = y2 - y1
|
|
349
|
+
length_sq = dx * dx + dy * dy
|
|
350
|
+
|
|
351
|
+
if length_sq == 0:
|
|
352
|
+
# Segment is a point
|
|
353
|
+
return np.sqrt((px - x1) ** 2 + (py - y1) ** 2) # type: ignore[no-any-return]
|
|
354
|
+
|
|
355
|
+
# Project point onto line
|
|
356
|
+
t = max(0, min(1, ((px - x1) * dx + (py - y1) * dy) / length_sq))
|
|
357
|
+
proj_x = x1 + t * dx
|
|
358
|
+
proj_y = y1 + t * dy
|
|
359
|
+
|
|
360
|
+
return float(np.sqrt((px - proj_x) ** 2 + (py - proj_y) ** 2))
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
def eye_diagram_mask_test(
|
|
364
|
+
eye_data: NDArray[np.floating[Any]],
|
|
365
|
+
*,
|
|
366
|
+
eye_width: float = 0.5,
|
|
367
|
+
eye_height: float = 0.4,
|
|
368
|
+
unit_interval: float = 1.0,
|
|
369
|
+
) -> MaskTestResult:
|
|
370
|
+
"""Specialized eye diagram mask test.
|
|
371
|
+
|
|
372
|
+
Tests 2D eye diagram data against a standard eye mask.
|
|
373
|
+
|
|
374
|
+
Args:
|
|
375
|
+
eye_data: 2D array of shape (num_traces, samples_per_ui).
|
|
376
|
+
eye_width: Eye opening width (fraction of UI).
|
|
377
|
+
eye_height: Eye opening height (fraction of amplitude).
|
|
378
|
+
unit_interval: Duration of unit interval in samples.
|
|
379
|
+
|
|
380
|
+
Returns:
|
|
381
|
+
MaskTestResult for the eye diagram.
|
|
382
|
+
|
|
383
|
+
Raises:
|
|
384
|
+
AnalysisError: If eye data is not a 2D array.
|
|
385
|
+
"""
|
|
386
|
+
if eye_data.ndim != 2:
|
|
387
|
+
raise AnalysisError("Eye data must be 2D array (num_traces x samples_per_ui)")
|
|
388
|
+
|
|
389
|
+
num_traces, samples_per_ui = eye_data.shape
|
|
390
|
+
|
|
391
|
+
# Create mask
|
|
392
|
+
mask = eye_mask(
|
|
393
|
+
eye_width=eye_width,
|
|
394
|
+
eye_height=eye_height,
|
|
395
|
+
unit_interval=unit_interval,
|
|
396
|
+
amplitude=1.0,
|
|
397
|
+
)
|
|
398
|
+
|
|
399
|
+
# Normalize data
|
|
400
|
+
flat_data = eye_data.flatten()
|
|
401
|
+
y_min, y_max = np.min(flat_data), np.max(flat_data)
|
|
402
|
+
normalized = 2 * (eye_data - y_min) / (y_max - y_min) - 1 if y_max - y_min > 0 else eye_data
|
|
403
|
+
|
|
404
|
+
# Create X coordinates (relative to UI center)
|
|
405
|
+
x_coords = np.linspace(-0.5, 0.5, samples_per_ui) * unit_interval
|
|
406
|
+
|
|
407
|
+
# Test all traces
|
|
408
|
+
violations: list[tuple[float, float]] = []
|
|
409
|
+
violations_by_region: dict[str, int] = {r.name or "unnamed": 0 for r in mask.regions}
|
|
410
|
+
|
|
411
|
+
for trace_idx in range(num_traces):
|
|
412
|
+
for sample_idx in range(samples_per_ui):
|
|
413
|
+
x = float(x_coords[sample_idx])
|
|
414
|
+
y = float(normalized[trace_idx, sample_idx])
|
|
415
|
+
|
|
416
|
+
for region in mask.regions:
|
|
417
|
+
if region.region_type == "violation":
|
|
418
|
+
if region.contains_point(x, y):
|
|
419
|
+
violations.append((x, y))
|
|
420
|
+
region_name = region.name or "unnamed"
|
|
421
|
+
violations_by_region[region_name] += 1
|
|
422
|
+
|
|
423
|
+
unique_violations = list(set(violations))
|
|
424
|
+
num_violations = len(unique_violations)
|
|
425
|
+
total_points = num_traces * samples_per_ui
|
|
426
|
+
|
|
427
|
+
return MaskTestResult(
|
|
428
|
+
passed=num_violations == 0,
|
|
429
|
+
num_violations=num_violations,
|
|
430
|
+
violation_rate=num_violations / total_points if total_points > 0 else 0,
|
|
431
|
+
violation_points=unique_violations,
|
|
432
|
+
violations_by_region=violations_by_region,
|
|
433
|
+
margin=None,
|
|
434
|
+
)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""Intelligent trace comparison and difference detection.
|
|
2
|
+
|
|
3
|
+
This module provides automatic trace comparison with alignment, difference
|
|
4
|
+
detection, and plain-language explanations. It is a wrapper around the
|
|
5
|
+
discovery.comparison module to maintain API compatibility.
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
Example:
|
|
9
|
+
>>> from oscura.comparison import compare_traces
|
|
10
|
+
>>> diff = compare_traces(trace1, trace2)
|
|
11
|
+
>>> for d in diff.differences:
|
|
12
|
+
... print(f"{d.category}: {d.description}")
|
|
13
|
+
|
|
14
|
+
References:
|
|
15
|
+
Oscura Auto-Discovery Specification
|
|
16
|
+
Phase 34 Task-245
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
# Re-export everything from discovery.comparison
|
|
20
|
+
from oscura.discovery.comparison import (
|
|
21
|
+
Difference,
|
|
22
|
+
TraceDiff,
|
|
23
|
+
compare_traces,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
__all__ = [
|
|
27
|
+
"Difference",
|
|
28
|
+
"TraceDiff",
|
|
29
|
+
"compare_traces",
|
|
30
|
+
]
|