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,46 @@
|
|
|
1
|
+
"""Eye diagram analysis module.
|
|
2
|
+
|
|
3
|
+
This module provides eye diagram generation and metrics for serial
|
|
4
|
+
data signal quality analysis.
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
Example:
|
|
8
|
+
>>> from oscura.analyzers.eye import generate_eye, eye_height, eye_width
|
|
9
|
+
>>> eye = generate_eye(trace, unit_interval=1e-9)
|
|
10
|
+
>>> height = eye_height(eye)
|
|
11
|
+
>>> width = eye_width(eye)
|
|
12
|
+
|
|
13
|
+
References:
|
|
14
|
+
IEEE 802.3: Ethernet Physical Layer Specifications
|
|
15
|
+
OIF CEI: Common Electrical I/O
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from oscura.analyzers.eye.diagram import (
|
|
19
|
+
EyeDiagram,
|
|
20
|
+
generate_eye,
|
|
21
|
+
generate_eye_from_edges,
|
|
22
|
+
)
|
|
23
|
+
from oscura.analyzers.eye.metrics import (
|
|
24
|
+
EyeMetrics,
|
|
25
|
+
crossing_percentage,
|
|
26
|
+
eye_contour,
|
|
27
|
+
eye_height,
|
|
28
|
+
eye_width,
|
|
29
|
+
measure_eye,
|
|
30
|
+
q_factor,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
__all__ = [
|
|
34
|
+
# Diagram generation
|
|
35
|
+
"EyeDiagram",
|
|
36
|
+
# Metrics
|
|
37
|
+
"EyeMetrics",
|
|
38
|
+
"crossing_percentage",
|
|
39
|
+
"eye_contour",
|
|
40
|
+
"eye_height",
|
|
41
|
+
"eye_width",
|
|
42
|
+
"generate_eye",
|
|
43
|
+
"generate_eye_from_edges",
|
|
44
|
+
"measure_eye",
|
|
45
|
+
"q_factor",
|
|
46
|
+
]
|
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
"""Eye diagram generation from serial data.
|
|
2
|
+
|
|
3
|
+
This module generates eye diagrams by folding waveform data
|
|
4
|
+
at the unit interval boundary.
|
|
5
|
+
|
|
6
|
+
Example:
|
|
7
|
+
>>> from oscura.analyzers.eye.diagram import generate_eye
|
|
8
|
+
>>> eye = generate_eye(trace, unit_interval=1e-9)
|
|
9
|
+
>>> print(f"Eye diagram: {eye.n_traces} traces, {eye.samples_per_ui} samples/UI")
|
|
10
|
+
|
|
11
|
+
References:
|
|
12
|
+
IEEE 802.3: Ethernet Physical Layer Specifications
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from dataclasses import dataclass
|
|
18
|
+
from typing import TYPE_CHECKING
|
|
19
|
+
|
|
20
|
+
import numpy as np
|
|
21
|
+
|
|
22
|
+
from oscura.core.exceptions import AnalysisError, InsufficientDataError
|
|
23
|
+
|
|
24
|
+
if TYPE_CHECKING:
|
|
25
|
+
from numpy.typing import NDArray
|
|
26
|
+
|
|
27
|
+
from oscura.core.types import WaveformTrace
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass
|
|
31
|
+
class EyeDiagram:
|
|
32
|
+
"""Eye diagram data structure.
|
|
33
|
+
|
|
34
|
+
Attributes:
|
|
35
|
+
data: 2D array of eye traces (n_traces x samples_per_ui).
|
|
36
|
+
time_axis: Time axis in UI (0.0 to 2.0 for 2-UI eye).
|
|
37
|
+
unit_interval: Unit interval in seconds.
|
|
38
|
+
samples_per_ui: Number of samples per unit interval.
|
|
39
|
+
n_traces: Number of overlaid traces.
|
|
40
|
+
sample_rate: Original sample rate in Hz.
|
|
41
|
+
histogram: Optional 2D histogram (voltage x time bins).
|
|
42
|
+
voltage_bins: Bin edges for voltage axis.
|
|
43
|
+
time_bins: Bin edges for time axis.
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
data: NDArray[np.float64]
|
|
47
|
+
time_axis: NDArray[np.float64]
|
|
48
|
+
unit_interval: float
|
|
49
|
+
samples_per_ui: int
|
|
50
|
+
n_traces: int
|
|
51
|
+
sample_rate: float
|
|
52
|
+
histogram: NDArray[np.float64] | None = None
|
|
53
|
+
voltage_bins: NDArray[np.float64] | None = None
|
|
54
|
+
time_bins: NDArray[np.float64] | None = None
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def generate_eye(
|
|
58
|
+
trace: WaveformTrace,
|
|
59
|
+
unit_interval: float,
|
|
60
|
+
*,
|
|
61
|
+
n_ui: int = 2,
|
|
62
|
+
trigger_level: float = 0.5,
|
|
63
|
+
trigger_edge: str = "rising",
|
|
64
|
+
max_traces: int | None = None,
|
|
65
|
+
generate_histogram: bool = True,
|
|
66
|
+
histogram_bins: tuple[int, int] = (100, 100),
|
|
67
|
+
) -> EyeDiagram:
|
|
68
|
+
"""Generate eye diagram from waveform data.
|
|
69
|
+
|
|
70
|
+
Folds the waveform at unit interval boundaries to create
|
|
71
|
+
an overlaid eye pattern for signal quality analysis.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
trace: Input waveform trace.
|
|
75
|
+
unit_interval: Unit interval (bit period) in seconds.
|
|
76
|
+
n_ui: Number of unit intervals to display (1 or 2).
|
|
77
|
+
trigger_level: Trigger level as fraction of amplitude.
|
|
78
|
+
trigger_edge: Trigger on "rising" or "falling" edges.
|
|
79
|
+
max_traces: Maximum number of traces to include.
|
|
80
|
+
generate_histogram: Generate 2D histogram for persistence.
|
|
81
|
+
histogram_bins: (voltage_bins, time_bins) for histogram.
|
|
82
|
+
|
|
83
|
+
Returns:
|
|
84
|
+
EyeDiagram with overlaid traces and optional histogram.
|
|
85
|
+
|
|
86
|
+
Raises:
|
|
87
|
+
AnalysisError: If unit interval is too short.
|
|
88
|
+
InsufficientDataError: If not enough data for eye generation.
|
|
89
|
+
|
|
90
|
+
Example:
|
|
91
|
+
>>> eye = generate_eye(trace, unit_interval=1e-9)
|
|
92
|
+
>>> print(f"Generated {eye.n_traces} traces")
|
|
93
|
+
|
|
94
|
+
References:
|
|
95
|
+
OIF CEI: Common Electrical I/O Eye Diagram Methodology
|
|
96
|
+
"""
|
|
97
|
+
data = trace.data
|
|
98
|
+
sample_rate = trace.metadata.sample_rate
|
|
99
|
+
1.0 / sample_rate
|
|
100
|
+
|
|
101
|
+
# Calculate samples per UI
|
|
102
|
+
samples_per_ui = round(unit_interval * sample_rate)
|
|
103
|
+
|
|
104
|
+
if samples_per_ui < 4:
|
|
105
|
+
raise AnalysisError(
|
|
106
|
+
f"Unit interval too short: {samples_per_ui} samples/UI. Need at least 4 samples per UI."
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
n_samples = len(data)
|
|
110
|
+
total_ui_samples = samples_per_ui * n_ui
|
|
111
|
+
|
|
112
|
+
if n_samples < total_ui_samples * 2:
|
|
113
|
+
raise InsufficientDataError(
|
|
114
|
+
f"Need at least {total_ui_samples * 2} samples for eye diagram",
|
|
115
|
+
required=total_ui_samples * 2,
|
|
116
|
+
available=n_samples,
|
|
117
|
+
analysis_type="eye_diagram_generation",
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
# Find trigger points
|
|
121
|
+
low = np.percentile(data, 10)
|
|
122
|
+
high = np.percentile(data, 90)
|
|
123
|
+
threshold = low + trigger_level * (high - low)
|
|
124
|
+
|
|
125
|
+
if trigger_edge == "rising":
|
|
126
|
+
trigger_mask = (data[:-1] < threshold) & (data[1:] >= threshold)
|
|
127
|
+
else:
|
|
128
|
+
trigger_mask = (data[:-1] >= threshold) & (data[1:] < threshold)
|
|
129
|
+
|
|
130
|
+
trigger_indices = np.where(trigger_mask)[0]
|
|
131
|
+
|
|
132
|
+
if len(trigger_indices) < 2:
|
|
133
|
+
raise InsufficientDataError(
|
|
134
|
+
"Not enough trigger events for eye diagram",
|
|
135
|
+
required=2,
|
|
136
|
+
available=len(trigger_indices),
|
|
137
|
+
analysis_type="eye_diagram_generation",
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
# Extract eye traces
|
|
141
|
+
eye_traces = []
|
|
142
|
+
half_ui = samples_per_ui // 2 # Start half UI before trigger
|
|
143
|
+
|
|
144
|
+
for trig_idx in trigger_indices:
|
|
145
|
+
start_idx = trig_idx - half_ui
|
|
146
|
+
end_idx = start_idx + total_ui_samples
|
|
147
|
+
|
|
148
|
+
if start_idx >= 0 and end_idx <= n_samples:
|
|
149
|
+
eye_traces.append(data[start_idx:end_idx])
|
|
150
|
+
|
|
151
|
+
if max_traces is not None and len(eye_traces) >= max_traces:
|
|
152
|
+
break
|
|
153
|
+
|
|
154
|
+
if len(eye_traces) == 0:
|
|
155
|
+
raise InsufficientDataError(
|
|
156
|
+
"Could not extract any complete eye traces",
|
|
157
|
+
required=1,
|
|
158
|
+
available=0,
|
|
159
|
+
analysis_type="eye_diagram_generation",
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
# Stack into 2D array
|
|
163
|
+
eye_data = np.array(eye_traces, dtype=np.float64)
|
|
164
|
+
|
|
165
|
+
# Generate time axis in UI
|
|
166
|
+
time_axis = np.linspace(0, n_ui, total_ui_samples, endpoint=False)
|
|
167
|
+
|
|
168
|
+
# Optional: Generate 2D histogram
|
|
169
|
+
histogram = None
|
|
170
|
+
voltage_bins = None
|
|
171
|
+
time_bins = None
|
|
172
|
+
|
|
173
|
+
if generate_histogram:
|
|
174
|
+
# Flatten for histogram
|
|
175
|
+
all_voltages = eye_data.flatten()
|
|
176
|
+
all_times = np.tile(time_axis, len(eye_traces))
|
|
177
|
+
|
|
178
|
+
# Create histogram
|
|
179
|
+
voltage_range = (np.min(all_voltages), np.max(all_voltages))
|
|
180
|
+
time_range = (0, n_ui)
|
|
181
|
+
|
|
182
|
+
histogram, voltage_edges, time_edges = np.histogram2d(
|
|
183
|
+
all_voltages,
|
|
184
|
+
all_times,
|
|
185
|
+
bins=histogram_bins,
|
|
186
|
+
range=[voltage_range, time_range],
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
voltage_bins = voltage_edges
|
|
190
|
+
time_bins = time_edges
|
|
191
|
+
|
|
192
|
+
return EyeDiagram(
|
|
193
|
+
data=eye_data,
|
|
194
|
+
time_axis=time_axis,
|
|
195
|
+
unit_interval=unit_interval,
|
|
196
|
+
samples_per_ui=samples_per_ui,
|
|
197
|
+
n_traces=len(eye_traces),
|
|
198
|
+
sample_rate=sample_rate,
|
|
199
|
+
histogram=histogram,
|
|
200
|
+
voltage_bins=voltage_bins,
|
|
201
|
+
time_bins=time_bins,
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def generate_eye_from_edges(
|
|
206
|
+
trace: WaveformTrace,
|
|
207
|
+
edge_timestamps: NDArray[np.float64],
|
|
208
|
+
*,
|
|
209
|
+
n_ui: int = 2,
|
|
210
|
+
samples_per_ui: int = 100,
|
|
211
|
+
max_traces: int | None = None,
|
|
212
|
+
) -> EyeDiagram:
|
|
213
|
+
"""Generate eye diagram using recovered clock edges.
|
|
214
|
+
|
|
215
|
+
Uses pre-recovered clock edges for triggering, which can provide
|
|
216
|
+
more accurate alignment than threshold-based triggering.
|
|
217
|
+
|
|
218
|
+
Args:
|
|
219
|
+
trace: Input waveform trace.
|
|
220
|
+
edge_timestamps: Array of clock edge timestamps in seconds.
|
|
221
|
+
n_ui: Number of unit intervals to display.
|
|
222
|
+
samples_per_ui: Samples per UI in resampled eye.
|
|
223
|
+
max_traces: Maximum traces to include.
|
|
224
|
+
|
|
225
|
+
Returns:
|
|
226
|
+
EyeDiagram with overlaid traces.
|
|
227
|
+
|
|
228
|
+
Raises:
|
|
229
|
+
InsufficientDataError: If not enough edge timestamps or traces.
|
|
230
|
+
|
|
231
|
+
Example:
|
|
232
|
+
>>> edges = recover_clock_edges(trace)
|
|
233
|
+
>>> eye = generate_eye_from_edges(trace, edges)
|
|
234
|
+
"""
|
|
235
|
+
data = trace.data
|
|
236
|
+
sample_rate = trace.metadata.sample_rate
|
|
237
|
+
|
|
238
|
+
if len(edge_timestamps) < 3:
|
|
239
|
+
raise InsufficientDataError(
|
|
240
|
+
"Need at least 3 edge timestamps",
|
|
241
|
+
required=3,
|
|
242
|
+
available=len(edge_timestamps),
|
|
243
|
+
analysis_type="eye_diagram_generation",
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
# Calculate unit interval from edges
|
|
247
|
+
periods = np.diff(edge_timestamps)
|
|
248
|
+
unit_interval = float(np.median(periods))
|
|
249
|
+
|
|
250
|
+
# Create time vector for original data
|
|
251
|
+
original_time = np.arange(len(data)) / sample_rate
|
|
252
|
+
|
|
253
|
+
# Extract and resample traces around each edge
|
|
254
|
+
eye_traces = []
|
|
255
|
+
total_samples = samples_per_ui * n_ui
|
|
256
|
+
half_ui = unit_interval / 2
|
|
257
|
+
|
|
258
|
+
for edge_time in edge_timestamps:
|
|
259
|
+
# Define window around edge
|
|
260
|
+
start_time = edge_time - half_ui
|
|
261
|
+
end_time = start_time + unit_interval * n_ui
|
|
262
|
+
|
|
263
|
+
if start_time < 0 or end_time > original_time[-1]:
|
|
264
|
+
continue
|
|
265
|
+
|
|
266
|
+
# Find samples within window
|
|
267
|
+
mask = (original_time >= start_time) & (original_time <= end_time)
|
|
268
|
+
window_time = original_time[mask] - start_time
|
|
269
|
+
window_data = data[mask]
|
|
270
|
+
|
|
271
|
+
if len(window_data) < 4:
|
|
272
|
+
continue
|
|
273
|
+
|
|
274
|
+
# Resample to consistent samples_per_ui
|
|
275
|
+
resample_time = np.linspace(0, unit_interval * n_ui, total_samples)
|
|
276
|
+
resampled = np.interp(resample_time, window_time, window_data)
|
|
277
|
+
|
|
278
|
+
eye_traces.append(resampled)
|
|
279
|
+
|
|
280
|
+
if max_traces is not None and len(eye_traces) >= max_traces:
|
|
281
|
+
break
|
|
282
|
+
|
|
283
|
+
if len(eye_traces) == 0:
|
|
284
|
+
raise InsufficientDataError(
|
|
285
|
+
"Could not extract any eye traces",
|
|
286
|
+
required=1,
|
|
287
|
+
available=0,
|
|
288
|
+
analysis_type="eye_diagram_generation",
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
eye_data = np.array(eye_traces, dtype=np.float64)
|
|
292
|
+
time_axis = np.linspace(0, n_ui, total_samples, endpoint=False)
|
|
293
|
+
|
|
294
|
+
return EyeDiagram(
|
|
295
|
+
data=eye_data,
|
|
296
|
+
time_axis=time_axis,
|
|
297
|
+
unit_interval=unit_interval,
|
|
298
|
+
samples_per_ui=samples_per_ui,
|
|
299
|
+
n_traces=len(eye_traces),
|
|
300
|
+
sample_rate=sample_rate,
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def auto_center_eye_diagram(
|
|
305
|
+
eye: EyeDiagram,
|
|
306
|
+
*,
|
|
307
|
+
trigger_fraction: float = 0.5,
|
|
308
|
+
symmetric_range: bool = True,
|
|
309
|
+
) -> EyeDiagram:
|
|
310
|
+
"""Auto-center eye diagram on optimal crossing point.
|
|
311
|
+
|
|
312
|
+
Automatically centers eye diagrams on the optimal trigger point
|
|
313
|
+
and scales amplitude for maximum eye opening visibility with
|
|
314
|
+
symmetric vertical centering.
|
|
315
|
+
|
|
316
|
+
Args:
|
|
317
|
+
eye: Input EyeDiagram to center.
|
|
318
|
+
trigger_fraction: Trigger level as fraction of amplitude (default 0.5 = 50%).
|
|
319
|
+
symmetric_range: Use symmetric amplitude range ±max(abs(signal)).
|
|
320
|
+
|
|
321
|
+
Returns:
|
|
322
|
+
Centered EyeDiagram with adjusted data.
|
|
323
|
+
|
|
324
|
+
Raises:
|
|
325
|
+
ValueError: If trigger_fraction is not in [0, 1].
|
|
326
|
+
|
|
327
|
+
Example:
|
|
328
|
+
>>> eye = generate_eye(trace, unit_interval=1e-9)
|
|
329
|
+
>>> centered = auto_center_eye_diagram(eye)
|
|
330
|
+
>>> # Centered at 50% crossing with symmetric amplitude
|
|
331
|
+
|
|
332
|
+
References:
|
|
333
|
+
VIS-021: Eye Diagram Auto-Centering
|
|
334
|
+
"""
|
|
335
|
+
if not 0 <= trigger_fraction <= 1:
|
|
336
|
+
raise ValueError(f"trigger_fraction must be in [0, 1], got {trigger_fraction}")
|
|
337
|
+
|
|
338
|
+
data = eye.data
|
|
339
|
+
|
|
340
|
+
# Calculate optimal trigger point using histogram-based threshold
|
|
341
|
+
# Find median value (represents middle level)
|
|
342
|
+
np.median(data)
|
|
343
|
+
|
|
344
|
+
# Calculate amplitude range
|
|
345
|
+
low = np.percentile(data, 10)
|
|
346
|
+
high = np.percentile(data, 90)
|
|
347
|
+
amplitude_range = high - low
|
|
348
|
+
|
|
349
|
+
# Trigger threshold at specified fraction
|
|
350
|
+
threshold = low + trigger_fraction * amplitude_range
|
|
351
|
+
|
|
352
|
+
# Find crossing points for each trace
|
|
353
|
+
# A crossing is where signal crosses threshold
|
|
354
|
+
n_traces, samples_per_trace = data.shape
|
|
355
|
+
crossing_indices = []
|
|
356
|
+
|
|
357
|
+
for trace_idx in range(n_traces):
|
|
358
|
+
trace = data[trace_idx, :]
|
|
359
|
+
|
|
360
|
+
# Find zero-crossings relative to threshold
|
|
361
|
+
crossings = np.where((trace[:-1] < threshold) & (trace[1:] >= threshold))[0]
|
|
362
|
+
|
|
363
|
+
if len(crossings) > 0:
|
|
364
|
+
# Use first crossing in this trace
|
|
365
|
+
crossing_indices.append(crossings[0])
|
|
366
|
+
|
|
367
|
+
if len(crossing_indices) == 0:
|
|
368
|
+
# No crossings found, return original
|
|
369
|
+
import warnings
|
|
370
|
+
|
|
371
|
+
warnings.warn(
|
|
372
|
+
"No crossing points found, cannot auto-center eye diagram",
|
|
373
|
+
UserWarning,
|
|
374
|
+
stacklevel=2,
|
|
375
|
+
)
|
|
376
|
+
return eye
|
|
377
|
+
|
|
378
|
+
# Calculate median crossing position
|
|
379
|
+
int(np.median(crossing_indices))
|
|
380
|
+
|
|
381
|
+
# Align all traces to common crossing point
|
|
382
|
+
# This requires resampling/shifting each trace
|
|
383
|
+
aligned_data = np.zeros_like(data)
|
|
384
|
+
target_crossing = samples_per_trace // 2 # Center of trace
|
|
385
|
+
|
|
386
|
+
for trace_idx in range(n_traces):
|
|
387
|
+
trace = data[trace_idx, :]
|
|
388
|
+
|
|
389
|
+
# Find crossing for this trace
|
|
390
|
+
crossings = np.where((trace[:-1] < threshold) & (trace[1:] >= threshold))[0]
|
|
391
|
+
|
|
392
|
+
if len(crossings) > 0:
|
|
393
|
+
crossing = crossings[0]
|
|
394
|
+
shift = target_crossing - crossing
|
|
395
|
+
|
|
396
|
+
# Shift trace by interpolation
|
|
397
|
+
if shift != 0:
|
|
398
|
+
# Simple roll (circular shift)
|
|
399
|
+
aligned_data[trace_idx, :] = np.roll(trace, shift)
|
|
400
|
+
else:
|
|
401
|
+
aligned_data[trace_idx, :] = trace
|
|
402
|
+
else:
|
|
403
|
+
# No crossing, keep original
|
|
404
|
+
aligned_data[trace_idx, :] = trace
|
|
405
|
+
|
|
406
|
+
# Scale amplitude to symmetric range if requested
|
|
407
|
+
if symmetric_range:
|
|
408
|
+
max_abs = np.max(np.abs(aligned_data))
|
|
409
|
+
if max_abs > 0:
|
|
410
|
+
# Center on zero
|
|
411
|
+
aligned_data = aligned_data - np.mean(aligned_data)
|
|
412
|
+
# Scale to ±max for symmetric range
|
|
413
|
+
# No additional scaling needed, data already centered
|
|
414
|
+
|
|
415
|
+
# Create centered eye diagram
|
|
416
|
+
return EyeDiagram(
|
|
417
|
+
data=aligned_data,
|
|
418
|
+
time_axis=eye.time_axis,
|
|
419
|
+
unit_interval=eye.unit_interval,
|
|
420
|
+
samples_per_ui=eye.samples_per_ui,
|
|
421
|
+
n_traces=eye.n_traces,
|
|
422
|
+
sample_rate=eye.sample_rate,
|
|
423
|
+
histogram=None, # Invalidate histogram after centering
|
|
424
|
+
voltage_bins=None,
|
|
425
|
+
time_bins=None,
|
|
426
|
+
)
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
__all__ = [
|
|
430
|
+
"EyeDiagram",
|
|
431
|
+
"auto_center_eye_diagram",
|
|
432
|
+
"generate_eye",
|
|
433
|
+
"generate_eye_from_edges",
|
|
434
|
+
]
|