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,596 @@
|
|
|
1
|
+
"""Argument preparation for analysis functions.
|
|
2
|
+
|
|
3
|
+
This module handles automatic argument detection and preparation for analysis functions,
|
|
4
|
+
including data type detection, parameter inference, and intelligent defaults.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import inspect
|
|
10
|
+
import logging
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import TYPE_CHECKING, Any
|
|
13
|
+
|
|
14
|
+
import numpy as np
|
|
15
|
+
|
|
16
|
+
from oscura.core.types import TraceMetadata, WaveformTrace
|
|
17
|
+
|
|
18
|
+
if TYPE_CHECKING:
|
|
19
|
+
from collections.abc import Callable
|
|
20
|
+
|
|
21
|
+
logger = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class ArgumentPreparer:
|
|
25
|
+
"""Prepares arguments for analysis functions automatically.
|
|
26
|
+
|
|
27
|
+
This class examines function signatures and prepares appropriate arguments
|
|
28
|
+
from input data, handling type conversions, parameter detection, and
|
|
29
|
+
intelligent defaults.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
def __init__(self, input_path: Path | None = None, default_sample_rate: float = 1e6):
|
|
33
|
+
"""Initialize argument preparer.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
input_path: Path to input file (used for filename-based detection).
|
|
37
|
+
default_sample_rate: Default sample rate when not available in data.
|
|
38
|
+
"""
|
|
39
|
+
self._input_path = input_path
|
|
40
|
+
self._default_sample_rate = default_sample_rate
|
|
41
|
+
|
|
42
|
+
def prepare_arguments(
|
|
43
|
+
self, func: Callable[..., Any], data: Any
|
|
44
|
+
) -> tuple[list[Any] | None, dict[str, Any]]:
|
|
45
|
+
"""Prepare arguments for an analysis function.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
func: Function to prepare arguments for.
|
|
49
|
+
data: Input data object.
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
Tuple of (args_list, kwargs_dict), or (None, {}) if not applicable.
|
|
53
|
+
"""
|
|
54
|
+
sig = inspect.signature(func)
|
|
55
|
+
params = list(sig.parameters.keys())
|
|
56
|
+
|
|
57
|
+
if not params:
|
|
58
|
+
return [], {}
|
|
59
|
+
|
|
60
|
+
first_param = params[0]
|
|
61
|
+
|
|
62
|
+
# Handle specialized data types
|
|
63
|
+
if self._is_eye_diagram(data):
|
|
64
|
+
return self._handle_eye_diagram(data, first_param, sig)
|
|
65
|
+
|
|
66
|
+
if self._is_sparam_data(data):
|
|
67
|
+
return self._handle_sparam_data(data, first_param, sig)
|
|
68
|
+
|
|
69
|
+
# Check type annotation for special handling
|
|
70
|
+
first_param_info = sig.parameters.get(first_param)
|
|
71
|
+
param_annotation = first_param_info.annotation if first_param_info else None
|
|
72
|
+
annotation_str = str(param_annotation) if param_annotation else ""
|
|
73
|
+
|
|
74
|
+
# Handle packet data
|
|
75
|
+
if "PacketInfo" in annotation_str or first_param == "packets":
|
|
76
|
+
return self._handle_packet_data(data)
|
|
77
|
+
|
|
78
|
+
# Extract raw data and determine if trace
|
|
79
|
+
is_trace, raw_data, sample_rate = self._extract_raw_data(data)
|
|
80
|
+
|
|
81
|
+
if raw_data is None or (hasattr(raw_data, "__len__") and len(raw_data) == 0):
|
|
82
|
+
return None, {}
|
|
83
|
+
|
|
84
|
+
# Build kwargs with auto-detected parameters
|
|
85
|
+
kwargs = self._build_kwargs(params, sig, raw_data, sample_rate)
|
|
86
|
+
|
|
87
|
+
# Handle trace wrapper creation if needed
|
|
88
|
+
if not is_trace and self._needs_trace_wrapper(first_param, annotation_str):
|
|
89
|
+
data, is_trace = self._create_trace_wrapper(raw_data, sample_rate, data)
|
|
90
|
+
if data is None:
|
|
91
|
+
return None, {}
|
|
92
|
+
|
|
93
|
+
# Return appropriate arguments based on first parameter
|
|
94
|
+
return self._build_args(first_param, annotation_str, data, is_trace, raw_data, kwargs, sig)
|
|
95
|
+
|
|
96
|
+
def _is_eye_diagram(self, data: Any) -> bool:
|
|
97
|
+
"""Check if data is an EyeDiagram object."""
|
|
98
|
+
return hasattr(data, "samples_per_ui") and hasattr(data, "time_axis")
|
|
99
|
+
|
|
100
|
+
def _is_sparam_data(self, data: Any) -> bool:
|
|
101
|
+
"""Check if data is S-parameter data."""
|
|
102
|
+
return hasattr(data, "s_matrix") and hasattr(data, "frequencies")
|
|
103
|
+
|
|
104
|
+
def _handle_eye_diagram(
|
|
105
|
+
self, data: Any, first_param: str, sig: inspect.Signature
|
|
106
|
+
) -> tuple[list[Any] | None, dict[str, Any]]:
|
|
107
|
+
"""Handle EyeDiagram data."""
|
|
108
|
+
if first_param == "eye" or "EyeDiagram" in str(sig.parameters.get(first_param, "")):
|
|
109
|
+
return [data], {}
|
|
110
|
+
return None, {}
|
|
111
|
+
|
|
112
|
+
def _handle_sparam_data(
|
|
113
|
+
self, data: Any, first_param: str, sig: inspect.Signature
|
|
114
|
+
) -> tuple[list[Any] | None, dict[str, Any]]:
|
|
115
|
+
"""Handle S-parameter data."""
|
|
116
|
+
if first_param in ("s_params", "s_param", "s_data", "sparams"):
|
|
117
|
+
return [data], {}
|
|
118
|
+
if "SParameter" in str(sig.parameters.get(first_param, "")):
|
|
119
|
+
return [data], {}
|
|
120
|
+
return None, {}
|
|
121
|
+
|
|
122
|
+
def _handle_packet_data(self, data: Any) -> tuple[list[Any] | None, dict[str, Any]]:
|
|
123
|
+
"""Handle packet data - convert to PacketInfo objects if needed."""
|
|
124
|
+
if isinstance(data, list):
|
|
125
|
+
if data and hasattr(data[0], "timestamp"):
|
|
126
|
+
return [data], {}
|
|
127
|
+
elif data and isinstance(data[0], dict):
|
|
128
|
+
try:
|
|
129
|
+
from oscura.analyzers.packet.metrics import PacketInfo
|
|
130
|
+
|
|
131
|
+
packets = [
|
|
132
|
+
PacketInfo(
|
|
133
|
+
timestamp=p.get("timestamp", 0.0),
|
|
134
|
+
size=p.get("size", 0),
|
|
135
|
+
sequence=p.get("sequence"),
|
|
136
|
+
)
|
|
137
|
+
for p in data
|
|
138
|
+
]
|
|
139
|
+
return [packets], {}
|
|
140
|
+
except Exception as e:
|
|
141
|
+
logger.debug(f"Failed to convert to PacketInfo: {e}")
|
|
142
|
+
return None, {}
|
|
143
|
+
return None, {}
|
|
144
|
+
|
|
145
|
+
def _extract_raw_data(self, data: Any) -> tuple[bool, Any, float]:
|
|
146
|
+
"""Extract raw data array, determine if trace, and get sample rate.
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
Tuple of (is_trace, raw_data, sample_rate).
|
|
150
|
+
"""
|
|
151
|
+
is_trace = hasattr(data, "data") and hasattr(data, "metadata")
|
|
152
|
+
|
|
153
|
+
if is_trace:
|
|
154
|
+
raw_data = data.data
|
|
155
|
+
sample_rate = self._get_sample_rate_from_data(data)
|
|
156
|
+
elif isinstance(data, np.ndarray):
|
|
157
|
+
raw_data = data
|
|
158
|
+
sample_rate = self._default_sample_rate
|
|
159
|
+
elif isinstance(data, bytes | bytearray):
|
|
160
|
+
raw_data = np.frombuffer(data, dtype=np.uint8)
|
|
161
|
+
sample_rate = 1.0 # Binary data context
|
|
162
|
+
else:
|
|
163
|
+
# Try to convert to array
|
|
164
|
+
try:
|
|
165
|
+
raw_data = np.array(data) if hasattr(data, "__iter__") else None
|
|
166
|
+
except (ValueError, TypeError):
|
|
167
|
+
raw_data = None
|
|
168
|
+
sample_rate = self._default_sample_rate
|
|
169
|
+
|
|
170
|
+
return is_trace, raw_data, sample_rate
|
|
171
|
+
|
|
172
|
+
def _get_sample_rate_from_data(self, data: Any) -> float:
|
|
173
|
+
"""Get sample rate from data metadata or use default."""
|
|
174
|
+
if hasattr(data, "metadata") and hasattr(data.metadata, "sample_rate"):
|
|
175
|
+
sample_rate = data.metadata.sample_rate
|
|
176
|
+
if sample_rate is not None and sample_rate > 0:
|
|
177
|
+
return float(sample_rate)
|
|
178
|
+
return self._default_sample_rate
|
|
179
|
+
|
|
180
|
+
def _build_kwargs(
|
|
181
|
+
self,
|
|
182
|
+
params: list[str],
|
|
183
|
+
sig: inspect.Signature,
|
|
184
|
+
raw_data: Any,
|
|
185
|
+
sample_rate: float,
|
|
186
|
+
) -> dict[str, Any]:
|
|
187
|
+
"""Build kwargs dictionary with auto-detected parameters."""
|
|
188
|
+
kwargs: dict[str, Any] = {}
|
|
189
|
+
|
|
190
|
+
# Add sample rate parameters
|
|
191
|
+
kwargs.update(self._add_sample_rate_params(params, sample_rate))
|
|
192
|
+
|
|
193
|
+
# Add digital domain parameters
|
|
194
|
+
kwargs.update(self._add_digital_params(params, sig, raw_data))
|
|
195
|
+
|
|
196
|
+
# Add frequency domain parameters
|
|
197
|
+
kwargs.update(self._add_frequency_params(params, sig, raw_data, sample_rate))
|
|
198
|
+
|
|
199
|
+
# Add noise/threshold parameters
|
|
200
|
+
kwargs.update(self._add_noise_params(params, sig, raw_data))
|
|
201
|
+
|
|
202
|
+
# Add window/width parameters
|
|
203
|
+
kwargs.update(self._add_window_params(params, sig, raw_data, sample_rate))
|
|
204
|
+
|
|
205
|
+
return kwargs
|
|
206
|
+
|
|
207
|
+
def _add_sample_rate_params(self, params: list[str], sample_rate: float) -> dict[str, Any]:
|
|
208
|
+
"""Add sample rate related parameters."""
|
|
209
|
+
kwargs = {}
|
|
210
|
+
if "sample_rate" in params:
|
|
211
|
+
kwargs["sample_rate"] = sample_rate
|
|
212
|
+
if "fs" in params:
|
|
213
|
+
kwargs["fs"] = sample_rate
|
|
214
|
+
if "rate" in params:
|
|
215
|
+
kwargs["rate"] = sample_rate
|
|
216
|
+
return kwargs
|
|
217
|
+
|
|
218
|
+
def _add_digital_params(
|
|
219
|
+
self, params: list[str], sig: inspect.Signature, raw_data: Any
|
|
220
|
+
) -> dict[str, Any]:
|
|
221
|
+
"""Add digital domain parameters (baud_rate, logic_family)."""
|
|
222
|
+
kwargs: dict[str, Any] = {}
|
|
223
|
+
|
|
224
|
+
# Baud rate detection
|
|
225
|
+
if "baud_rate" in params:
|
|
226
|
+
param_info = sig.parameters.get("baud_rate")
|
|
227
|
+
has_default = (
|
|
228
|
+
param_info is not None and param_info.default is not inspect.Parameter.empty
|
|
229
|
+
)
|
|
230
|
+
if not has_default or (param_info and param_info.default is None):
|
|
231
|
+
detected_baud = self._detect_baud_rate_from_filename()
|
|
232
|
+
if detected_baud is not None:
|
|
233
|
+
kwargs["baud_rate"] = detected_baud
|
|
234
|
+
|
|
235
|
+
# Logic family detection
|
|
236
|
+
if "logic_family" in params:
|
|
237
|
+
param_info = sig.parameters.get("logic_family")
|
|
238
|
+
has_default = (
|
|
239
|
+
param_info is not None and param_info.default is not inspect.Parameter.empty
|
|
240
|
+
)
|
|
241
|
+
if not has_default or (param_info and param_info.default in (None, "auto")):
|
|
242
|
+
try:
|
|
243
|
+
detected_family: Any = self._detect_logic_family(raw_data)
|
|
244
|
+
kwargs["logic_family"] = detected_family
|
|
245
|
+
except Exception as e:
|
|
246
|
+
logger.debug(f"Could not auto-detect logic family: {e}")
|
|
247
|
+
|
|
248
|
+
return kwargs
|
|
249
|
+
|
|
250
|
+
def _add_frequency_params(
|
|
251
|
+
self, params: list[str], sig: inspect.Signature, raw_data: Any, sample_rate: float
|
|
252
|
+
) -> dict[str, Any]:
|
|
253
|
+
"""Add frequency range parameters."""
|
|
254
|
+
kwargs = {}
|
|
255
|
+
|
|
256
|
+
if "freq_min" in params or "freq_max" in params:
|
|
257
|
+
try:
|
|
258
|
+
freq_range = self._detect_frequency_range(raw_data, sample_rate)
|
|
259
|
+
if freq_range is not None:
|
|
260
|
+
min_freq, max_freq = freq_range
|
|
261
|
+
|
|
262
|
+
if "freq_min" in params:
|
|
263
|
+
if self._param_needs_value(sig, "freq_min"):
|
|
264
|
+
kwargs["freq_min"] = min_freq
|
|
265
|
+
|
|
266
|
+
if "freq_max" in params:
|
|
267
|
+
if self._param_needs_value(sig, "freq_max"):
|
|
268
|
+
kwargs["freq_max"] = max_freq
|
|
269
|
+
except Exception as e:
|
|
270
|
+
logger.debug(f"Could not auto-detect frequency range: {e}")
|
|
271
|
+
|
|
272
|
+
return kwargs
|
|
273
|
+
|
|
274
|
+
def _add_noise_params(
|
|
275
|
+
self, params: list[str], sig: inspect.Signature, raw_data: Any
|
|
276
|
+
) -> dict[str, Any]:
|
|
277
|
+
"""Add noise/threshold parameters."""
|
|
278
|
+
kwargs = {}
|
|
279
|
+
|
|
280
|
+
if "noise_threshold" in params or "snr_threshold" in params:
|
|
281
|
+
try:
|
|
282
|
+
noise_floor = self._detect_noise_floor(raw_data)
|
|
283
|
+
if noise_floor is not None:
|
|
284
|
+
if "noise_threshold" in params and self._param_needs_value(
|
|
285
|
+
sig, "noise_threshold"
|
|
286
|
+
):
|
|
287
|
+
kwargs["noise_threshold"] = noise_floor * 3.0
|
|
288
|
+
|
|
289
|
+
if "snr_threshold" in params and self._param_needs_value(sig, "snr_threshold"):
|
|
290
|
+
signal_rms = float(np.std(raw_data))
|
|
291
|
+
if noise_floor > 0:
|
|
292
|
+
detected_snr = signal_rms / noise_floor
|
|
293
|
+
kwargs["snr_threshold"] = detected_snr / 2.0
|
|
294
|
+
except Exception as e:
|
|
295
|
+
logger.debug(f"Could not auto-detect noise floor: {e}")
|
|
296
|
+
|
|
297
|
+
# Protocol hints for baud rate
|
|
298
|
+
if "baud_rate" in params and "baud_rate" not in kwargs:
|
|
299
|
+
try:
|
|
300
|
+
protocol_hints = self._detect_protocol_hints(raw_data, self._default_sample_rate)
|
|
301
|
+
if "detected_baud" in protocol_hints:
|
|
302
|
+
if self._param_needs_value(sig, "baud_rate"):
|
|
303
|
+
kwargs["baud_rate"] = protocol_hints["detected_baud"]
|
|
304
|
+
logger.debug(
|
|
305
|
+
f"Using protocol-detected baud rate: {protocol_hints['detected_baud']} bps"
|
|
306
|
+
)
|
|
307
|
+
except Exception as e:
|
|
308
|
+
logger.debug(f"Could not use protocol hints for baud detection: {e}")
|
|
309
|
+
|
|
310
|
+
return kwargs
|
|
311
|
+
|
|
312
|
+
def _add_window_params(
|
|
313
|
+
self, params: list[str], sig: inspect.Signature, raw_data: Any, sample_rate: float
|
|
314
|
+
) -> dict[str, Any]:
|
|
315
|
+
"""Add window size and width parameters."""
|
|
316
|
+
kwargs: dict[str, Any] = {}
|
|
317
|
+
data_length = len(raw_data) if hasattr(raw_data, "__len__") else 0
|
|
318
|
+
|
|
319
|
+
if "window_size" in params:
|
|
320
|
+
if self._param_needs_value(sig, "window_size") and "window_size" not in kwargs:
|
|
321
|
+
window_size: Any = max(10, data_length // 10)
|
|
322
|
+
kwargs["window_size"] = window_size
|
|
323
|
+
logger.debug(f"Using auto-detected window_size: {kwargs['window_size']}")
|
|
324
|
+
|
|
325
|
+
if "min_width" in params:
|
|
326
|
+
if self._param_needs_value(sig, "min_width") and "min_width" not in kwargs:
|
|
327
|
+
min_width: Any = max(1e-9, 10.0 / sample_rate)
|
|
328
|
+
kwargs["min_width"] = min_width
|
|
329
|
+
logger.debug(f"Using auto-detected min_width: {kwargs['min_width']:.2e}s")
|
|
330
|
+
|
|
331
|
+
if "max_width" in params:
|
|
332
|
+
if self._param_needs_value(sig, "max_width") and "max_width" not in kwargs:
|
|
333
|
+
total_duration = data_length / sample_rate if data_length > 0 else 1e-3
|
|
334
|
+
max_width: Any = min(1e-3, total_duration)
|
|
335
|
+
kwargs["max_width"] = max_width
|
|
336
|
+
logger.debug(f"Using auto-detected max_width: {kwargs['max_width']:.2e}s")
|
|
337
|
+
|
|
338
|
+
if "threshold" in params and "threshold" not in kwargs:
|
|
339
|
+
param_info = sig.parameters.get("threshold")
|
|
340
|
+
has_default = (
|
|
341
|
+
param_info is not None and param_info.default is not inspect.Parameter.empty
|
|
342
|
+
)
|
|
343
|
+
if not has_default or (param_info and param_info.default in (None, "auto")):
|
|
344
|
+
try:
|
|
345
|
+
if isinstance(raw_data, np.ndarray) and raw_data.size > 0:
|
|
346
|
+
threshold: Any = float(np.median(raw_data))
|
|
347
|
+
kwargs["threshold"] = threshold
|
|
348
|
+
logger.debug(f"Using auto-detected threshold: {kwargs['threshold']:.3f}")
|
|
349
|
+
except Exception as e:
|
|
350
|
+
logger.debug(f"Could not auto-detect threshold: {e}")
|
|
351
|
+
|
|
352
|
+
if "window_duration" in params:
|
|
353
|
+
if self._param_needs_value(sig, "window_duration") and "window_duration" not in kwargs:
|
|
354
|
+
total_duration = data_length / sample_rate if data_length > 0 else 1.0
|
|
355
|
+
window_duration: Any = min(1.0, total_duration / 10.0)
|
|
356
|
+
kwargs["window_duration"] = window_duration
|
|
357
|
+
logger.debug(
|
|
358
|
+
f"Using auto-detected window_duration: {kwargs['window_duration']:.3f}s"
|
|
359
|
+
)
|
|
360
|
+
|
|
361
|
+
return kwargs
|
|
362
|
+
|
|
363
|
+
def _param_needs_value(self, sig: inspect.Signature, param_name: str) -> bool:
|
|
364
|
+
"""Check if parameter needs a value (no default or default is None)."""
|
|
365
|
+
param_info = sig.parameters.get(param_name)
|
|
366
|
+
if param_info is None:
|
|
367
|
+
return False
|
|
368
|
+
has_default = param_info.default is not inspect.Parameter.empty
|
|
369
|
+
return not has_default or param_info.default is None
|
|
370
|
+
|
|
371
|
+
def _needs_trace_wrapper(self, first_param: str, annotation_str: str) -> bool:
|
|
372
|
+
"""Check if function needs trace wrapper."""
|
|
373
|
+
return (
|
|
374
|
+
"Trace" in annotation_str or "WaveformTrace" in annotation_str or first_param == "trace"
|
|
375
|
+
)
|
|
376
|
+
|
|
377
|
+
def _create_trace_wrapper(
|
|
378
|
+
self, raw_data: Any, sample_rate: float, original_data: Any
|
|
379
|
+
) -> tuple[Any, bool]:
|
|
380
|
+
"""Create WaveformTrace wrapper for raw array data."""
|
|
381
|
+
try:
|
|
382
|
+
trace_data = np.asarray(raw_data) if isinstance(raw_data, memoryview) else raw_data
|
|
383
|
+
metadata = TraceMetadata(sample_rate=sample_rate)
|
|
384
|
+
data = WaveformTrace(data=trace_data, metadata=metadata)
|
|
385
|
+
logger.debug("Created WaveformTrace wrapper for raw array data")
|
|
386
|
+
return data, True
|
|
387
|
+
except Exception as e:
|
|
388
|
+
logger.debug(f"Could not create trace wrapper: {e}")
|
|
389
|
+
return None, False
|
|
390
|
+
|
|
391
|
+
def _build_args(
|
|
392
|
+
self,
|
|
393
|
+
first_param: str,
|
|
394
|
+
annotation_str: str,
|
|
395
|
+
data: Any,
|
|
396
|
+
is_trace: bool,
|
|
397
|
+
raw_data: Any,
|
|
398
|
+
kwargs: dict[str, Any],
|
|
399
|
+
sig: inspect.Signature,
|
|
400
|
+
) -> tuple[list[Any] | None, dict[str, Any]]:
|
|
401
|
+
"""Build the final args list based on first parameter."""
|
|
402
|
+
# If function expects WaveformTrace and we have a trace
|
|
403
|
+
if is_trace and (
|
|
404
|
+
"Trace" in annotation_str or "WaveformTrace" in annotation_str or first_param == "trace"
|
|
405
|
+
):
|
|
406
|
+
return [data], kwargs
|
|
407
|
+
|
|
408
|
+
# Common data parameter names
|
|
409
|
+
if first_param in ("data", "signal", "x", "samples", "waveform"):
|
|
410
|
+
return [raw_data], kwargs
|
|
411
|
+
|
|
412
|
+
# Trace expected but not available
|
|
413
|
+
if first_param == "trace" and not is_trace:
|
|
414
|
+
return None, {}
|
|
415
|
+
|
|
416
|
+
# Edge timestamps
|
|
417
|
+
if first_param == "edges":
|
|
418
|
+
return self._extract_edges(data, is_trace, kwargs)
|
|
419
|
+
|
|
420
|
+
# Period measurements
|
|
421
|
+
if first_param == "periods":
|
|
422
|
+
return self._extract_periods(data, is_trace, kwargs)
|
|
423
|
+
|
|
424
|
+
# Bytes data
|
|
425
|
+
if first_param in ("stream", "data") and "bytes" in annotation_str:
|
|
426
|
+
return self._convert_to_bytes(data, raw_data, kwargs)
|
|
427
|
+
|
|
428
|
+
if first_param == "bytes" or (first_param == "data" and "bytes" in str(sig)):
|
|
429
|
+
return self._convert_to_bytes(data, raw_data, kwargs)
|
|
430
|
+
|
|
431
|
+
# Default: pass raw data
|
|
432
|
+
return [raw_data], kwargs
|
|
433
|
+
|
|
434
|
+
def _extract_edges(
|
|
435
|
+
self, data: Any, is_trace: bool, kwargs: dict[str, Any]
|
|
436
|
+
) -> tuple[list[Any] | None, dict[str, Any]]:
|
|
437
|
+
"""Extract edge timestamps from data."""
|
|
438
|
+
try:
|
|
439
|
+
from oscura.analyzers.digital import detect_edges
|
|
440
|
+
|
|
441
|
+
if is_trace:
|
|
442
|
+
edges = detect_edges(data)
|
|
443
|
+
edge_times = edges.tolist() if len(edges) > 0 else []
|
|
444
|
+
if len(edge_times) < 3:
|
|
445
|
+
return None, {}
|
|
446
|
+
return [edge_times], kwargs
|
|
447
|
+
except Exception:
|
|
448
|
+
pass
|
|
449
|
+
return None, {}
|
|
450
|
+
|
|
451
|
+
def _extract_periods(
|
|
452
|
+
self, data: Any, is_trace: bool, kwargs: dict[str, Any]
|
|
453
|
+
) -> tuple[list[Any] | None, dict[str, Any]]:
|
|
454
|
+
"""Extract period measurements from data."""
|
|
455
|
+
try:
|
|
456
|
+
if is_trace:
|
|
457
|
+
from oscura.analyzers.waveform.measurements import period
|
|
458
|
+
|
|
459
|
+
periods_result = period(data, return_all=True)
|
|
460
|
+
if isinstance(periods_result, np.ndarray) and len(periods_result) >= 3:
|
|
461
|
+
return [periods_result], kwargs
|
|
462
|
+
except Exception as e:
|
|
463
|
+
logger.debug(f"Could not compute periods: {e}")
|
|
464
|
+
return None, {}
|
|
465
|
+
|
|
466
|
+
def _convert_to_bytes(
|
|
467
|
+
self, data: Any, raw_data: Any, kwargs: dict[str, Any]
|
|
468
|
+
) -> tuple[list[Any] | None, dict[str, Any]]:
|
|
469
|
+
"""Convert data to bytes."""
|
|
470
|
+
if isinstance(data, bytes | bytearray):
|
|
471
|
+
return [data], kwargs
|
|
472
|
+
elif isinstance(raw_data, np.ndarray) or hasattr(raw_data, "astype"):
|
|
473
|
+
return [raw_data.astype(np.uint8).tobytes()], kwargs
|
|
474
|
+
return None, {}
|
|
475
|
+
|
|
476
|
+
# Detection methods
|
|
477
|
+
|
|
478
|
+
def _detect_baud_rate_from_filename(self) -> float | None:
|
|
479
|
+
"""Extract baud rate from filename patterns."""
|
|
480
|
+
if self._input_path is None:
|
|
481
|
+
return None
|
|
482
|
+
|
|
483
|
+
import re
|
|
484
|
+
|
|
485
|
+
patterns = [
|
|
486
|
+
r"(\d+(?:\.\d+)?)[_\s]*[Mm]?baud",
|
|
487
|
+
r"(\d+(?:\.\d+)?)[_\s]*bps",
|
|
488
|
+
r"baud[_-]?(\d+)",
|
|
489
|
+
]
|
|
490
|
+
filename = self._input_path.stem.lower()
|
|
491
|
+
|
|
492
|
+
for pattern in patterns:
|
|
493
|
+
match = re.search(pattern, filename, re.IGNORECASE)
|
|
494
|
+
if match:
|
|
495
|
+
value = float(match.group(1))
|
|
496
|
+
matched_text = filename[match.start() : match.end()].lower()
|
|
497
|
+
if "m" in matched_text and "baud" in matched_text:
|
|
498
|
+
value *= 1_000_000
|
|
499
|
+
logger.debug(
|
|
500
|
+
f"Detected baud rate from filename '{self._input_path.name}': {value} bps"
|
|
501
|
+
)
|
|
502
|
+
return value
|
|
503
|
+
|
|
504
|
+
return None
|
|
505
|
+
|
|
506
|
+
def _detect_logic_family(self, data: np.ndarray[Any, Any]) -> str:
|
|
507
|
+
"""Detect logic family from voltage levels."""
|
|
508
|
+
vmax = float(np.max(data))
|
|
509
|
+
vmin = float(np.min(data))
|
|
510
|
+
voltage_swing = vmax - vmin
|
|
511
|
+
|
|
512
|
+
if voltage_swing < 1.0:
|
|
513
|
+
logic_family = "LVDS"
|
|
514
|
+
elif voltage_swing < 2.0:
|
|
515
|
+
logic_family = "LVCMOS18"
|
|
516
|
+
elif voltage_swing < 3.0:
|
|
517
|
+
logic_family = "LVCMOS25"
|
|
518
|
+
elif voltage_swing < 4.0:
|
|
519
|
+
logic_family = "LVCMOS33"
|
|
520
|
+
else:
|
|
521
|
+
logic_family = "TTL"
|
|
522
|
+
|
|
523
|
+
logger.debug(
|
|
524
|
+
f"Detected logic family from voltage swing {voltage_swing:.2f}V: {logic_family}"
|
|
525
|
+
)
|
|
526
|
+
return logic_family
|
|
527
|
+
|
|
528
|
+
def _detect_frequency_range(
|
|
529
|
+
self, data: np.ndarray[Any, Any], sample_rate: float
|
|
530
|
+
) -> tuple[float, float] | None:
|
|
531
|
+
"""Detect dominant frequency range from FFT analysis."""
|
|
532
|
+
try:
|
|
533
|
+
fft_result = np.fft.rfft(data - np.mean(data))
|
|
534
|
+
freqs = np.fft.rfftfreq(len(data), d=1.0 / sample_rate)
|
|
535
|
+
magnitude = np.abs(fft_result)
|
|
536
|
+
|
|
537
|
+
threshold = 0.1 * np.max(magnitude)
|
|
538
|
+
significant = freqs[magnitude > threshold]
|
|
539
|
+
|
|
540
|
+
if len(significant) > 0:
|
|
541
|
+
min_freq = float(np.min(significant))
|
|
542
|
+
max_freq = float(np.max(significant))
|
|
543
|
+
logger.debug(f"Detected frequency range: {min_freq:.2f} Hz - {max_freq:.2f} Hz")
|
|
544
|
+
return (min_freq, max_freq)
|
|
545
|
+
return None
|
|
546
|
+
except Exception as e:
|
|
547
|
+
logger.debug(f"Frequency range detection failed: {e}")
|
|
548
|
+
return None
|
|
549
|
+
|
|
550
|
+
def _detect_noise_floor(self, data: np.ndarray[Any, Any]) -> float | None:
|
|
551
|
+
"""Estimate noise floor using median absolute deviation."""
|
|
552
|
+
try:
|
|
553
|
+
try:
|
|
554
|
+
from scipy import stats
|
|
555
|
+
|
|
556
|
+
mad = stats.median_abs_deviation(data, scale="normal")
|
|
557
|
+
logger.debug(f"Detected noise floor (scipy MAD): {mad:.6f}")
|
|
558
|
+
return float(mad)
|
|
559
|
+
except ImportError:
|
|
560
|
+
median = np.median(data)
|
|
561
|
+
mad = np.median(np.abs(data - median)) * 1.4826
|
|
562
|
+
logger.debug(f"Detected noise floor (numpy MAD): {mad:.6f}")
|
|
563
|
+
return float(mad)
|
|
564
|
+
except Exception as e:
|
|
565
|
+
logger.debug(f"Noise floor detection failed: {e}")
|
|
566
|
+
return None
|
|
567
|
+
|
|
568
|
+
def _detect_protocol_hints(
|
|
569
|
+
self, data: np.ndarray[Any, Any], sample_rate: float
|
|
570
|
+
) -> dict[str, Any]:
|
|
571
|
+
"""Detect hints about potential protocols in the signal."""
|
|
572
|
+
hints: dict[str, Any] = {}
|
|
573
|
+
try:
|
|
574
|
+
zero_crossings = np.where(np.diff(np.sign(data - np.mean(data))))[0]
|
|
575
|
+
if len(zero_crossings) > 10:
|
|
576
|
+
intervals = np.diff(zero_crossings) / sample_rate
|
|
577
|
+
avg_interval = float(np.median(intervals))
|
|
578
|
+
|
|
579
|
+
common_bauds = [300, 1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200]
|
|
580
|
+
for baud in common_bauds:
|
|
581
|
+
expected_interval = 1.0 / baud
|
|
582
|
+
if 0.8 < avg_interval / expected_interval < 1.2:
|
|
583
|
+
hints["detected_baud"] = baud
|
|
584
|
+
logger.debug(f"Protocol hint: detected baud rate {baud} bps")
|
|
585
|
+
break
|
|
586
|
+
|
|
587
|
+
if len(zero_crossings) > 20:
|
|
588
|
+
interval_std = float(np.std(np.diff(zero_crossings)))
|
|
589
|
+
regularity = "high" if interval_std < 2 else "medium" if interval_std < 5 else "low"
|
|
590
|
+
hints["clock_regularity"] = regularity
|
|
591
|
+
logger.debug(f"Protocol hint: clock regularity {regularity}")
|
|
592
|
+
|
|
593
|
+
except Exception as e:
|
|
594
|
+
logger.debug(f"Protocol hints detection failed: {e}")
|
|
595
|
+
|
|
596
|
+
return hints
|