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
oscura/cli/__init__.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""TraceKit Command-Line Interface.
|
|
2
|
+
|
|
3
|
+
This module provides command-line tools for signal analysis workflows.
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
Example:
|
|
7
|
+
$ oscura --help
|
|
8
|
+
$ oscura characterize signal.wfm
|
|
9
|
+
$ oscura decode uart.wfm --protocol uart
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from oscura.cli.main import cli
|
|
13
|
+
|
|
14
|
+
__all__ = ["cli"]
|
oscura/cli/batch.py
ADDED
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
"""TraceKit Batch Command implementing CLI-004.
|
|
2
|
+
|
|
3
|
+
Provides CLI for batch processing multiple files with parallel execution support.
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
Example:
|
|
7
|
+
$ oscura batch '*.wfm' --analysis characterize
|
|
8
|
+
$ oscura batch 'test_*.wfm' --analysis decode --parallel 4
|
|
9
|
+
$ oscura batch 'captures/*.wfm' --analysis spectrum --save-summary results.csv
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import csv
|
|
15
|
+
import glob
|
|
16
|
+
import logging
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from typing import Any
|
|
19
|
+
|
|
20
|
+
import click
|
|
21
|
+
|
|
22
|
+
from oscura.cli.main import format_output
|
|
23
|
+
|
|
24
|
+
logger = logging.getLogger("oscura.cli.batch")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@click.command() # type: ignore[misc]
|
|
28
|
+
@click.argument("pattern") # type: ignore[misc]
|
|
29
|
+
@click.option( # type: ignore[misc]
|
|
30
|
+
"--analysis",
|
|
31
|
+
type=click.Choice(["characterize", "decode", "spectrum"], case_sensitive=False),
|
|
32
|
+
required=True,
|
|
33
|
+
help="Type of analysis to perform on each file.",
|
|
34
|
+
)
|
|
35
|
+
@click.option( # type: ignore[misc]
|
|
36
|
+
"--parallel",
|
|
37
|
+
type=int,
|
|
38
|
+
default=1,
|
|
39
|
+
help="Number of files to process concurrently (default: 1).",
|
|
40
|
+
)
|
|
41
|
+
@click.option( # type: ignore[misc]
|
|
42
|
+
"--output",
|
|
43
|
+
type=click.Choice(["json", "csv", "html", "table"], case_sensitive=False),
|
|
44
|
+
default="table",
|
|
45
|
+
help="Output format (default: table).",
|
|
46
|
+
)
|
|
47
|
+
@click.option( # type: ignore[misc]
|
|
48
|
+
"--save-summary",
|
|
49
|
+
type=click.Path(),
|
|
50
|
+
default=None,
|
|
51
|
+
help="Save aggregated results to file (CSV format).",
|
|
52
|
+
)
|
|
53
|
+
@click.option( # type: ignore[misc]
|
|
54
|
+
"--continue-on-error",
|
|
55
|
+
is_flag=True,
|
|
56
|
+
help="Continue processing even if individual files fail.",
|
|
57
|
+
)
|
|
58
|
+
@click.pass_context # type: ignore[misc]
|
|
59
|
+
def batch(
|
|
60
|
+
ctx: click.Context,
|
|
61
|
+
pattern: str,
|
|
62
|
+
analysis: str,
|
|
63
|
+
parallel: int,
|
|
64
|
+
output: str,
|
|
65
|
+
save_summary: str | None,
|
|
66
|
+
continue_on_error: bool,
|
|
67
|
+
) -> None:
|
|
68
|
+
"""Batch process multiple files.
|
|
69
|
+
|
|
70
|
+
Processes all files matching the given pattern with the specified analysis.
|
|
71
|
+
Supports parallel processing for faster execution on multi-core systems.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
ctx: Click context object.
|
|
75
|
+
pattern: Glob pattern to match files.
|
|
76
|
+
analysis: Type of analysis (characterize, decode, spectrum).
|
|
77
|
+
parallel: Number of parallel workers.
|
|
78
|
+
output: Output format (json, csv, html, table).
|
|
79
|
+
save_summary: Path to save CSV summary file.
|
|
80
|
+
continue_on_error: Continue processing if individual files fail.
|
|
81
|
+
|
|
82
|
+
Raises:
|
|
83
|
+
Exception: If batch processing fails or no files found.
|
|
84
|
+
|
|
85
|
+
Examples:
|
|
86
|
+
|
|
87
|
+
\b
|
|
88
|
+
# Process all WFM files with characterization
|
|
89
|
+
$ oscura batch '*.wfm' --analysis characterize
|
|
90
|
+
|
|
91
|
+
\b
|
|
92
|
+
# Parallel processing with 4 workers
|
|
93
|
+
$ oscura batch 'test_run_*/*.wfm' \\
|
|
94
|
+
--analysis characterize \\
|
|
95
|
+
--parallel 4 \\
|
|
96
|
+
--save-summary results.csv
|
|
97
|
+
|
|
98
|
+
\b
|
|
99
|
+
# Decode all captures, continue on errors
|
|
100
|
+
$ oscura batch 'captures/*.wfm' \\
|
|
101
|
+
--analysis decode \\
|
|
102
|
+
--continue-on-error
|
|
103
|
+
"""
|
|
104
|
+
verbose = ctx.obj.get("verbose", 0)
|
|
105
|
+
|
|
106
|
+
if verbose:
|
|
107
|
+
logger.info(f"Batch processing pattern: {pattern}")
|
|
108
|
+
logger.info(f"Analysis type: {analysis}")
|
|
109
|
+
logger.info(f"Parallel workers: {parallel}")
|
|
110
|
+
|
|
111
|
+
try:
|
|
112
|
+
# Expand glob pattern
|
|
113
|
+
files = glob.glob(pattern, recursive=True) # noqa: PTH207
|
|
114
|
+
|
|
115
|
+
if not files:
|
|
116
|
+
click.echo(f"No files matched pattern: {pattern}", err=True)
|
|
117
|
+
ctx.exit(1)
|
|
118
|
+
|
|
119
|
+
logger.info(f"Found {len(files)} files to process")
|
|
120
|
+
|
|
121
|
+
# Perform batch analysis
|
|
122
|
+
results = _perform_batch_analysis(
|
|
123
|
+
files=files,
|
|
124
|
+
analysis_type=analysis,
|
|
125
|
+
parallel=parallel,
|
|
126
|
+
continue_on_error=continue_on_error,
|
|
127
|
+
verbose=verbose,
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
# Save summary if requested
|
|
131
|
+
if save_summary:
|
|
132
|
+
_save_summary(results, save_summary)
|
|
133
|
+
logger.info(f"Summary saved to {save_summary}")
|
|
134
|
+
|
|
135
|
+
# Output aggregated results
|
|
136
|
+
summary = _generate_summary(results)
|
|
137
|
+
formatted = format_output(summary, output)
|
|
138
|
+
click.echo(formatted)
|
|
139
|
+
|
|
140
|
+
except Exception as e:
|
|
141
|
+
logger.error(f"Batch processing failed: {e}")
|
|
142
|
+
if verbose > 1:
|
|
143
|
+
raise
|
|
144
|
+
click.echo(f"Error: {e}", err=True)
|
|
145
|
+
ctx.exit(1)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def _perform_batch_analysis(
|
|
149
|
+
files: list[str],
|
|
150
|
+
analysis_type: str,
|
|
151
|
+
parallel: int,
|
|
152
|
+
continue_on_error: bool,
|
|
153
|
+
verbose: int,
|
|
154
|
+
) -> list[dict[str, Any]]:
|
|
155
|
+
"""Perform batch analysis on multiple files.
|
|
156
|
+
|
|
157
|
+
Uses concurrent.futures for parallel processing when parallel > 1.
|
|
158
|
+
|
|
159
|
+
Args:
|
|
160
|
+
files: List of file paths to process.
|
|
161
|
+
analysis_type: Type of analysis to perform.
|
|
162
|
+
parallel: Number of parallel workers.
|
|
163
|
+
continue_on_error: Whether to continue on errors.
|
|
164
|
+
verbose: Verbosity level.
|
|
165
|
+
|
|
166
|
+
Returns:
|
|
167
|
+
List of result dictionaries, one per file.
|
|
168
|
+
|
|
169
|
+
Raises:
|
|
170
|
+
Exception: If analysis fails and continue_on_error is False.
|
|
171
|
+
"""
|
|
172
|
+
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
173
|
+
|
|
174
|
+
def analyze_single_file(file_path: str) -> dict[str, Any]:
|
|
175
|
+
"""Analyze a single file and return results.
|
|
176
|
+
|
|
177
|
+
Args:
|
|
178
|
+
file_path: Path to waveform file to analyze.
|
|
179
|
+
|
|
180
|
+
Returns:
|
|
181
|
+
Dictionary containing analysis results.
|
|
182
|
+
"""
|
|
183
|
+
import numpy as np
|
|
184
|
+
|
|
185
|
+
from oscura.analyzers.waveform.measurements import fall_time, rise_time
|
|
186
|
+
from oscura.analyzers.waveform.spectral import fft, thd
|
|
187
|
+
from oscura.inference import detect_protocol
|
|
188
|
+
from oscura.loaders import load
|
|
189
|
+
|
|
190
|
+
# Load trace
|
|
191
|
+
trace = load(file_path)
|
|
192
|
+
sample_rate = trace.metadata.sample_rate
|
|
193
|
+
|
|
194
|
+
# Base result
|
|
195
|
+
result: dict[str, Any] = {
|
|
196
|
+
"file": str(Path(file_path).name),
|
|
197
|
+
"status": "success",
|
|
198
|
+
"analysis_type": analysis_type,
|
|
199
|
+
"samples": len(trace.data), # type: ignore[union-attr]
|
|
200
|
+
"sample_rate": f"{sample_rate / 1e6:.1f} MHz",
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
# Add analysis-specific results
|
|
204
|
+
if analysis_type == "characterize":
|
|
205
|
+
# Pass WaveformTrace directly to functions (they expect WaveformTrace)
|
|
206
|
+
rt = rise_time(trace) # type: ignore[arg-type]
|
|
207
|
+
ft = fall_time(trace) # type: ignore[arg-type]
|
|
208
|
+
result.update(
|
|
209
|
+
{
|
|
210
|
+
"rise_time": f"{rt * 1e9:.2f} ns" if not np.isnan(rt) else "N/A",
|
|
211
|
+
"fall_time": f"{ft * 1e9:.2f} ns" if not np.isnan(ft) else "N/A",
|
|
212
|
+
}
|
|
213
|
+
)
|
|
214
|
+
elif analysis_type == "decode":
|
|
215
|
+
detected = detect_protocol(trace) # type: ignore[arg-type]
|
|
216
|
+
result.update(
|
|
217
|
+
{
|
|
218
|
+
"protocol": detected.get("protocol", "unknown"),
|
|
219
|
+
"confidence": f"{detected.get('confidence', 0) * 100:.0f}%",
|
|
220
|
+
}
|
|
221
|
+
)
|
|
222
|
+
elif analysis_type == "spectrum":
|
|
223
|
+
# Pass WaveformTrace directly to FFT functions
|
|
224
|
+
freqs, mags = fft(trace) # type: ignore[misc, arg-type]
|
|
225
|
+
if len(mags) > 0:
|
|
226
|
+
peak_idx = int(np.argmax(mags))
|
|
227
|
+
peak_freq = freqs[peak_idx]
|
|
228
|
+
else:
|
|
229
|
+
peak_freq = 0.0
|
|
230
|
+
thd_val = thd(trace) # type: ignore[arg-type]
|
|
231
|
+
result.update(
|
|
232
|
+
{
|
|
233
|
+
"peak_frequency": f"{peak_freq / 1e6:.3f} MHz",
|
|
234
|
+
"thd": f"{thd_val:.1f} dB" if not np.isnan(thd_val) else "N/A",
|
|
235
|
+
}
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
return result
|
|
239
|
+
|
|
240
|
+
results: list[dict[str, Any]] = []
|
|
241
|
+
|
|
242
|
+
if parallel > 1:
|
|
243
|
+
# Parallel processing using ThreadPoolExecutor
|
|
244
|
+
with ThreadPoolExecutor(max_workers=parallel) as executor:
|
|
245
|
+
future_to_file = {executor.submit(analyze_single_file, f): f for f in files}
|
|
246
|
+
|
|
247
|
+
for i, future in enumerate(as_completed(future_to_file), 1):
|
|
248
|
+
file_path = future_to_file[future]
|
|
249
|
+
if verbose:
|
|
250
|
+
logger.info(f"[{i}/{len(files)}] Completed {Path(file_path).name}")
|
|
251
|
+
|
|
252
|
+
try:
|
|
253
|
+
result = future.result()
|
|
254
|
+
results.append(result)
|
|
255
|
+
except Exception as e:
|
|
256
|
+
logger.error(f"Failed to process {file_path}: {e}")
|
|
257
|
+
if continue_on_error:
|
|
258
|
+
results.append(
|
|
259
|
+
{
|
|
260
|
+
"file": str(Path(file_path).name),
|
|
261
|
+
"status": "error",
|
|
262
|
+
"error": str(e),
|
|
263
|
+
}
|
|
264
|
+
)
|
|
265
|
+
else:
|
|
266
|
+
raise
|
|
267
|
+
else:
|
|
268
|
+
# Sequential processing
|
|
269
|
+
for i, file_path in enumerate(files, 1):
|
|
270
|
+
if verbose:
|
|
271
|
+
logger.info(f"[{i}/{len(files)}] Processing {file_path}")
|
|
272
|
+
|
|
273
|
+
try:
|
|
274
|
+
result = analyze_single_file(file_path)
|
|
275
|
+
results.append(result)
|
|
276
|
+
except Exception as e:
|
|
277
|
+
logger.error(f"Failed to process {file_path}: {e}")
|
|
278
|
+
if continue_on_error:
|
|
279
|
+
results.append(
|
|
280
|
+
{
|
|
281
|
+
"file": str(Path(file_path).name),
|
|
282
|
+
"status": "error",
|
|
283
|
+
"error": str(e),
|
|
284
|
+
}
|
|
285
|
+
)
|
|
286
|
+
else:
|
|
287
|
+
raise
|
|
288
|
+
|
|
289
|
+
return results
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def _generate_summary(results: list[dict[str, Any]]) -> dict[str, Any]:
|
|
293
|
+
"""Generate summary statistics from batch results.
|
|
294
|
+
|
|
295
|
+
Args:
|
|
296
|
+
results: List of individual file results.
|
|
297
|
+
|
|
298
|
+
Returns:
|
|
299
|
+
Summary dictionary with aggregated statistics.
|
|
300
|
+
"""
|
|
301
|
+
total = len(results)
|
|
302
|
+
successful = sum(1 for r in results if r.get("status") == "success")
|
|
303
|
+
failed = total - successful
|
|
304
|
+
|
|
305
|
+
summary: dict[str, Any] = {
|
|
306
|
+
"total_files": total,
|
|
307
|
+
"successful": successful,
|
|
308
|
+
"failed": failed,
|
|
309
|
+
"success_rate": f"{successful / total * 100:.1f}%" if total > 0 else "N/A",
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
# Add analysis-specific aggregations
|
|
313
|
+
if results and successful > 0:
|
|
314
|
+
summary["note"] = "Detailed per-file results available in JSON/CSV output"
|
|
315
|
+
|
|
316
|
+
return summary
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
def _save_summary(results: list[dict[str, Any]], output_path: str) -> None:
|
|
320
|
+
"""Save batch results to CSV file.
|
|
321
|
+
|
|
322
|
+
Args:
|
|
323
|
+
results: List of result dictionaries.
|
|
324
|
+
output_path: Path to save CSV file.
|
|
325
|
+
"""
|
|
326
|
+
if not results:
|
|
327
|
+
return
|
|
328
|
+
|
|
329
|
+
# Get all unique keys across all results
|
|
330
|
+
all_keys: set[str] = set()
|
|
331
|
+
for result in results:
|
|
332
|
+
all_keys.update(result.keys())
|
|
333
|
+
|
|
334
|
+
fieldnames = sorted(all_keys)
|
|
335
|
+
|
|
336
|
+
with open(output_path, "w", newline="") as f:
|
|
337
|
+
writer = csv.DictWriter(f, fieldnames=fieldnames)
|
|
338
|
+
writer.writeheader()
|
|
339
|
+
writer.writerows(results)
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
"""TraceKit Characterize Command implementing CLI-002.
|
|
2
|
+
|
|
3
|
+
Provides CLI for buffer/signal characterization with automatic logic family
|
|
4
|
+
detection and optional reference comparison.
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
Example:
|
|
8
|
+
$ oscura characterize 74hc04_output.wfm
|
|
9
|
+
$ oscura characterize signal.wfm --logic-family CMOS_3V3
|
|
10
|
+
$ oscura characterize signal.wfm --compare reference.wfm --output html
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import logging
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from typing import TYPE_CHECKING, Any
|
|
18
|
+
|
|
19
|
+
import click
|
|
20
|
+
|
|
21
|
+
from oscura.cli.main import format_output
|
|
22
|
+
|
|
23
|
+
if TYPE_CHECKING:
|
|
24
|
+
from oscura.core.types import WaveformTrace
|
|
25
|
+
|
|
26
|
+
logger = logging.getLogger("oscura.cli.characterize")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@click.command() # type: ignore[misc]
|
|
30
|
+
@click.argument("file", type=click.Path(exists=True)) # type: ignore[misc]
|
|
31
|
+
@click.option( # type: ignore[misc]
|
|
32
|
+
"--type",
|
|
33
|
+
"analysis_type",
|
|
34
|
+
type=click.Choice(["buffer", "signal", "power"], case_sensitive=False),
|
|
35
|
+
default="buffer",
|
|
36
|
+
help="Type of characterization to perform.",
|
|
37
|
+
)
|
|
38
|
+
@click.option( # type: ignore[misc]
|
|
39
|
+
"--logic-family",
|
|
40
|
+
type=click.Choice(
|
|
41
|
+
["TTL", "CMOS", "CMOS_3V3", "CMOS_5V", "LVTTL", "LVCMOS", "auto"],
|
|
42
|
+
case_sensitive=False,
|
|
43
|
+
),
|
|
44
|
+
default="auto",
|
|
45
|
+
help="Logic family for buffer characterization (default: auto-detect).",
|
|
46
|
+
)
|
|
47
|
+
@click.option( # type: ignore[misc]
|
|
48
|
+
"--compare",
|
|
49
|
+
type=click.Path(exists=True),
|
|
50
|
+
default=None,
|
|
51
|
+
help="Reference file for comparison analysis.",
|
|
52
|
+
)
|
|
53
|
+
@click.option( # type: ignore[misc]
|
|
54
|
+
"--output",
|
|
55
|
+
type=click.Choice(["json", "csv", "html", "table"], case_sensitive=False),
|
|
56
|
+
default="table",
|
|
57
|
+
help="Output format (default: table).",
|
|
58
|
+
)
|
|
59
|
+
@click.option( # type: ignore[misc]
|
|
60
|
+
"--save-report",
|
|
61
|
+
type=click.Path(),
|
|
62
|
+
default=None,
|
|
63
|
+
help="Save HTML report to file.",
|
|
64
|
+
)
|
|
65
|
+
@click.pass_context # type: ignore[misc]
|
|
66
|
+
def characterize(
|
|
67
|
+
ctx: click.Context,
|
|
68
|
+
file: str,
|
|
69
|
+
analysis_type: str,
|
|
70
|
+
logic_family: str,
|
|
71
|
+
compare: str | None,
|
|
72
|
+
output: str,
|
|
73
|
+
save_report: str | None,
|
|
74
|
+
) -> None:
|
|
75
|
+
"""Characterize buffer, signal, or power measurements.
|
|
76
|
+
|
|
77
|
+
Analyzes a waveform file and extracts timing, quality, and performance
|
|
78
|
+
characteristics. Supports automatic logic family detection and optional
|
|
79
|
+
comparison to a reference signal.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
ctx: Click context object.
|
|
83
|
+
file: Path to waveform file to characterize.
|
|
84
|
+
analysis_type: Type of characterization (buffer, signal, power).
|
|
85
|
+
logic_family: Logic family for buffer characterization.
|
|
86
|
+
compare: Path to reference file for comparison analysis.
|
|
87
|
+
output: Output format (json, csv, html, table).
|
|
88
|
+
save_report: Path to save HTML report file.
|
|
89
|
+
|
|
90
|
+
Raises:
|
|
91
|
+
Exception: If characterization fails or file cannot be loaded.
|
|
92
|
+
|
|
93
|
+
Examples:
|
|
94
|
+
|
|
95
|
+
\b
|
|
96
|
+
# Simple buffer characterization
|
|
97
|
+
$ oscura characterize 74hc04_output.wfm
|
|
98
|
+
|
|
99
|
+
\b
|
|
100
|
+
# Full characterization with reference
|
|
101
|
+
$ oscura characterize signal.wfm \\
|
|
102
|
+
--logic-family CMOS_3V3 \\
|
|
103
|
+
--compare golden_reference.wfm \\
|
|
104
|
+
--save-report report.html
|
|
105
|
+
|
|
106
|
+
\b
|
|
107
|
+
# Power analysis
|
|
108
|
+
$ oscura characterize power_rail.wfm --type power --output json
|
|
109
|
+
"""
|
|
110
|
+
verbose = ctx.obj.get("verbose", 0)
|
|
111
|
+
|
|
112
|
+
if verbose:
|
|
113
|
+
logger.info(f"Characterizing: {file}")
|
|
114
|
+
logger.info(f"Analysis type: {analysis_type}")
|
|
115
|
+
logger.info(f"Logic family: {logic_family}")
|
|
116
|
+
|
|
117
|
+
try:
|
|
118
|
+
# Import here to avoid circular imports
|
|
119
|
+
from oscura.loaders import load
|
|
120
|
+
|
|
121
|
+
# Load the main trace
|
|
122
|
+
logger.debug(f"Loading trace from {file}")
|
|
123
|
+
trace = load(file)
|
|
124
|
+
|
|
125
|
+
# Load reference trace if provided
|
|
126
|
+
reference_trace: WaveformTrace | None = None
|
|
127
|
+
if compare:
|
|
128
|
+
logger.debug(f"Loading reference trace from {compare}")
|
|
129
|
+
reference_trace = load(compare) # type: ignore[assignment]
|
|
130
|
+
|
|
131
|
+
# Perform characterization based on type
|
|
132
|
+
results = _perform_characterization(
|
|
133
|
+
trace=trace,
|
|
134
|
+
reference_trace=reference_trace,
|
|
135
|
+
analysis_type=analysis_type,
|
|
136
|
+
logic_family=logic_family,
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
# Add metadata
|
|
140
|
+
results["file"] = str(Path(file).name)
|
|
141
|
+
if compare:
|
|
142
|
+
results["reference_file"] = str(Path(compare).name)
|
|
143
|
+
|
|
144
|
+
# Generate HTML report if requested
|
|
145
|
+
if save_report:
|
|
146
|
+
html_content = format_output(results, "html")
|
|
147
|
+
with open(save_report, "w") as f:
|
|
148
|
+
f.write(html_content)
|
|
149
|
+
logger.info(f"Report saved to {save_report}")
|
|
150
|
+
|
|
151
|
+
# Output results
|
|
152
|
+
formatted = format_output(results, output)
|
|
153
|
+
click.echo(formatted)
|
|
154
|
+
|
|
155
|
+
except Exception as e:
|
|
156
|
+
logger.error(f"Characterization failed: {e}")
|
|
157
|
+
if verbose > 1:
|
|
158
|
+
raise
|
|
159
|
+
click.echo(f"Error: {e}", err=True)
|
|
160
|
+
ctx.exit(1)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def _perform_characterization(
|
|
164
|
+
trace: Any,
|
|
165
|
+
reference_trace: Any | None,
|
|
166
|
+
analysis_type: str,
|
|
167
|
+
logic_family: str,
|
|
168
|
+
) -> dict[str, Any]:
|
|
169
|
+
"""Perform characterization analysis.
|
|
170
|
+
|
|
171
|
+
Calls actual TraceKit analysis functions based on the analysis type.
|
|
172
|
+
|
|
173
|
+
Args:
|
|
174
|
+
trace: Main trace to analyze.
|
|
175
|
+
reference_trace: Optional reference trace for comparison.
|
|
176
|
+
analysis_type: Type of analysis ('buffer', 'signal', 'power').
|
|
177
|
+
logic_family: Logic family for digital analysis.
|
|
178
|
+
|
|
179
|
+
Returns:
|
|
180
|
+
Dictionary of analysis results.
|
|
181
|
+
"""
|
|
182
|
+
import numpy as np
|
|
183
|
+
|
|
184
|
+
from oscura.analyzers.waveform.measurements import (
|
|
185
|
+
fall_time,
|
|
186
|
+
overshoot,
|
|
187
|
+
rise_time,
|
|
188
|
+
undershoot,
|
|
189
|
+
)
|
|
190
|
+
from oscura.comparison.compare import compare_traces, similarity_score
|
|
191
|
+
from oscura.inference import detect_logic_family
|
|
192
|
+
|
|
193
|
+
sample_rate = trace.metadata.sample_rate
|
|
194
|
+
data = trace.data
|
|
195
|
+
|
|
196
|
+
results: dict[str, Any] = {
|
|
197
|
+
"analysis_type": analysis_type,
|
|
198
|
+
"logic_family": logic_family,
|
|
199
|
+
"sample_rate": f"{sample_rate / 1e6:.1f} MHz",
|
|
200
|
+
"samples": len(data),
|
|
201
|
+
"duration": f"{len(data) / sample_rate * 1e3:.3f} ms",
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if analysis_type == "buffer":
|
|
205
|
+
# Buffer characterization using actual workflow (WRK-001)
|
|
206
|
+
# Functions expect WaveformTrace, not ndarray
|
|
207
|
+
rt = rise_time(trace)
|
|
208
|
+
ft = fall_time(trace)
|
|
209
|
+
os_pct = overshoot(trace)
|
|
210
|
+
us_pct = undershoot(trace)
|
|
211
|
+
|
|
212
|
+
results.update(
|
|
213
|
+
{
|
|
214
|
+
"rise_time": f"{rt * 1e9:.2f} ns" if not np.isnan(rt) else "N/A",
|
|
215
|
+
"fall_time": f"{ft * 1e9:.2f} ns" if not np.isnan(ft) else "N/A",
|
|
216
|
+
"overshoot": f"{os_pct:.1f} %" if not np.isnan(os_pct) else "N/A",
|
|
217
|
+
"undershoot": f"{us_pct:.1f} %" if not np.isnan(us_pct) else "N/A",
|
|
218
|
+
"status": "PASS",
|
|
219
|
+
}
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
# Logic family detection
|
|
223
|
+
if logic_family == "auto":
|
|
224
|
+
detected = detect_logic_family(trace)
|
|
225
|
+
# detect_logic_family returns dict with 'primary' key
|
|
226
|
+
primary = detected.get("primary", {})
|
|
227
|
+
results["logic_family_detected"] = primary.get("name", "unknown")
|
|
228
|
+
results["confidence"] = f"{primary.get('confidence', 0) * 100:.0f}%"
|
|
229
|
+
else:
|
|
230
|
+
results["logic_family_detected"] = logic_family
|
|
231
|
+
|
|
232
|
+
elif analysis_type == "signal":
|
|
233
|
+
# Signal analysis
|
|
234
|
+
results.update(
|
|
235
|
+
{
|
|
236
|
+
"amplitude": f"{float(data.max() - data.min()):.3f} V",
|
|
237
|
+
"peak_to_peak": f"{float(data.max() - data.min()):.3f} V",
|
|
238
|
+
"mean": f"{float(data.mean()):.3f} V",
|
|
239
|
+
"rms": f"{float(np.sqrt((data**2).mean())):.3f} V",
|
|
240
|
+
}
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
elif analysis_type == "power":
|
|
244
|
+
# Power analysis - compute power statistics directly from voltage
|
|
245
|
+
# Assume voltage trace, compute power (P = V^2/R, assume R=1 for relative)
|
|
246
|
+
power_data = data**2
|
|
247
|
+
avg_pwr = float(np.mean(power_data))
|
|
248
|
+
peak_pwr = float(np.max(power_data))
|
|
249
|
+
total_energy = float(np.sum(power_data) / sample_rate)
|
|
250
|
+
|
|
251
|
+
results.update(
|
|
252
|
+
{
|
|
253
|
+
"average_power": f"{avg_pwr * 1e3:.3f} mW",
|
|
254
|
+
"peak_power": f"{peak_pwr * 1e3:.3f} mW",
|
|
255
|
+
"energy": f"{total_energy * 1e6:.3f} uJ",
|
|
256
|
+
}
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
# Add comparison results if reference provided
|
|
260
|
+
if reference_trace is not None:
|
|
261
|
+
ref_data = reference_trace.data
|
|
262
|
+
# similarity_score expects WaveformTrace objects
|
|
263
|
+
sim = similarity_score(trace, reference_trace)
|
|
264
|
+
# compare_traces returns ComparisonResult dataclass
|
|
265
|
+
comparison_result = compare_traces(trace, reference_trace)
|
|
266
|
+
|
|
267
|
+
results["comparison"] = {
|
|
268
|
+
"correlation": f"{comparison_result.correlation:.4f}",
|
|
269
|
+
"amplitude_difference": f"{abs(float(data.mean()) - float(ref_data.mean())):.3f} V",
|
|
270
|
+
"similarity": f"{sim * 100:.1f}%",
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
return results
|