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,485 @@
|
|
|
1
|
+
"""Custom measurement framework for user-defined measurements.
|
|
2
|
+
|
|
3
|
+
This module implements a framework for defining and registering custom
|
|
4
|
+
measurements that integrate seamlessly with batch processing and export.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import inspect
|
|
10
|
+
from dataclasses import dataclass, field
|
|
11
|
+
from typing import TYPE_CHECKING, Any
|
|
12
|
+
|
|
13
|
+
from .registry import AlgorithmRegistry
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from collections.abc import Callable
|
|
17
|
+
|
|
18
|
+
from ..core.types import WaveformTrace
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass
|
|
22
|
+
class MeasurementDefinition:
|
|
23
|
+
"""Definition of a custom measurement with metadata.
|
|
24
|
+
|
|
25
|
+
Defines a measurement function along with metadata about units, category,
|
|
26
|
+
and documentation. Measurements can be registered globally and used in
|
|
27
|
+
batch processing.
|
|
28
|
+
|
|
29
|
+
Attributes:
|
|
30
|
+
name: Unique name for the measurement.
|
|
31
|
+
func: Callable that computes the measurement.
|
|
32
|
+
units: Units of measurement (e.g., 'V', 'Hz', 's', 'ratio').
|
|
33
|
+
category: Measurement category (e.g., 'amplitude', 'timing', 'frequency').
|
|
34
|
+
description: Human-readable description.
|
|
35
|
+
tags: Optional tags for categorization and search.
|
|
36
|
+
|
|
37
|
+
Example:
|
|
38
|
+
>>> import oscura as tk
|
|
39
|
+
>>> def calculate_crest_factor(trace, **kwargs):
|
|
40
|
+
... peak = abs(trace.data).max()
|
|
41
|
+
... rms = (trace.data ** 2).mean() ** 0.5
|
|
42
|
+
... return peak / rms
|
|
43
|
+
>>> tk.register_measurement(
|
|
44
|
+
... name='crest_factor',
|
|
45
|
+
... func=calculate_crest_factor,
|
|
46
|
+
... units='ratio',
|
|
47
|
+
... category='amplitude'
|
|
48
|
+
... )
|
|
49
|
+
>>> cf = tk.measure(trace, 'crest_factor')
|
|
50
|
+
|
|
51
|
+
Advanced Example:
|
|
52
|
+
>>> # Define measurement with full metadata
|
|
53
|
+
>>> slew_rate_defn = tk.MeasurementDefinition(
|
|
54
|
+
... name='max_slew_rate',
|
|
55
|
+
... func=lambda trace: abs(trace.derivative()).max(),
|
|
56
|
+
... units='V/s',
|
|
57
|
+
... category='edge',
|
|
58
|
+
... description='Maximum slew rate in trace',
|
|
59
|
+
... tags=['edge', 'derivative', 'speed']
|
|
60
|
+
... )
|
|
61
|
+
>>> tk.register_measurement(slew_rate_defn)
|
|
62
|
+
|
|
63
|
+
References:
|
|
64
|
+
API-008: Custom Measurement Framework
|
|
65
|
+
API-006: Algorithm Override Hooks
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
name: str
|
|
69
|
+
func: Callable[[WaveformTrace], float]
|
|
70
|
+
units: str
|
|
71
|
+
category: str
|
|
72
|
+
description: str = ""
|
|
73
|
+
tags: list[str] = field(default_factory=list)
|
|
74
|
+
|
|
75
|
+
def __post_init__(self) -> None:
|
|
76
|
+
"""Validate measurement definition.
|
|
77
|
+
|
|
78
|
+
Raises:
|
|
79
|
+
ValueError: If measurement name is empty.
|
|
80
|
+
TypeError: If func is not callable or has invalid signature.
|
|
81
|
+
"""
|
|
82
|
+
if not self.name:
|
|
83
|
+
raise ValueError("Measurement name cannot be empty")
|
|
84
|
+
|
|
85
|
+
if not callable(self.func):
|
|
86
|
+
raise TypeError(f"Measurement func must be callable, got {type(self.func).__name__}")
|
|
87
|
+
|
|
88
|
+
# Validate function signature
|
|
89
|
+
self._validate_signature()
|
|
90
|
+
|
|
91
|
+
def _validate_signature(self) -> None:
|
|
92
|
+
"""Validate that function has correct signature.
|
|
93
|
+
|
|
94
|
+
Measurement functions should accept (trace, **kwargs) -> float.
|
|
95
|
+
|
|
96
|
+
Raises:
|
|
97
|
+
TypeError: If signature is invalid.
|
|
98
|
+
"""
|
|
99
|
+
sig = inspect.signature(self.func)
|
|
100
|
+
params = list(sig.parameters.values())
|
|
101
|
+
|
|
102
|
+
# Should have at least one parameter (trace)
|
|
103
|
+
if len(params) == 0:
|
|
104
|
+
raise TypeError(
|
|
105
|
+
f"Measurement function must accept at least one parameter "
|
|
106
|
+
f"(trace). Got {self.func.__name__} with no parameters."
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
# Check if first parameter could accept WaveformTrace
|
|
110
|
+
first_param = params[0]
|
|
111
|
+
if first_param.kind in (
|
|
112
|
+
inspect.Parameter.VAR_POSITIONAL,
|
|
113
|
+
inspect.Parameter.VAR_KEYWORD,
|
|
114
|
+
):
|
|
115
|
+
raise TypeError(
|
|
116
|
+
f"First parameter must be a regular parameter (trace), got {first_param.kind}"
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
def __call__(self, trace: WaveformTrace, **kwargs: Any) -> float:
|
|
120
|
+
"""Call measurement function.
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
trace: WaveformTrace to measure.
|
|
124
|
+
**kwargs: Additional parameters for measurement.
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
Measured value.
|
|
128
|
+
|
|
129
|
+
Example:
|
|
130
|
+
>>> defn = MeasurementDefinition(
|
|
131
|
+
... name='peak',
|
|
132
|
+
... func=lambda trace: abs(trace.data).max(),
|
|
133
|
+
... units='V',
|
|
134
|
+
... category='amplitude'
|
|
135
|
+
... )
|
|
136
|
+
>>> value = defn(trace)
|
|
137
|
+
"""
|
|
138
|
+
return self.func(trace, **kwargs)
|
|
139
|
+
|
|
140
|
+
def __repr__(self) -> str:
|
|
141
|
+
"""String representation.
|
|
142
|
+
|
|
143
|
+
Returns:
|
|
144
|
+
String representation of the measurement definition.
|
|
145
|
+
"""
|
|
146
|
+
return (
|
|
147
|
+
f"MeasurementDefinition(name='{self.name}', "
|
|
148
|
+
f"units='{self.units}', category='{self.category}')"
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
class MeasurementRegistry:
|
|
153
|
+
"""Registry for custom measurements.
|
|
154
|
+
|
|
155
|
+
Manages registration and lookup of custom measurements. Integrates with
|
|
156
|
+
the AlgorithmRegistry for storage.
|
|
157
|
+
|
|
158
|
+
Example:
|
|
159
|
+
>>> registry = MeasurementRegistry()
|
|
160
|
+
>>> registry.register(
|
|
161
|
+
... name='crest_factor',
|
|
162
|
+
... func=calculate_crest_factor,
|
|
163
|
+
... units='ratio',
|
|
164
|
+
... category='amplitude'
|
|
165
|
+
... )
|
|
166
|
+
>>> measurement = registry.get('crest_factor')
|
|
167
|
+
>>> value = measurement(trace)
|
|
168
|
+
|
|
169
|
+
References:
|
|
170
|
+
API-008: Custom Measurement Framework
|
|
171
|
+
"""
|
|
172
|
+
|
|
173
|
+
MEASUREMENT_CATEGORY = "measurement"
|
|
174
|
+
|
|
175
|
+
def __init__(self) -> None:
|
|
176
|
+
"""Initialize measurement registry."""
|
|
177
|
+
self._definitions: dict[str, MeasurementDefinition] = {}
|
|
178
|
+
self._algorithm_registry = AlgorithmRegistry()
|
|
179
|
+
|
|
180
|
+
def register(
|
|
181
|
+
self,
|
|
182
|
+
name: str | None = None,
|
|
183
|
+
func: Callable[[WaveformTrace], float] | None = None,
|
|
184
|
+
units: str | None = None,
|
|
185
|
+
category: str | None = None,
|
|
186
|
+
description: str = "",
|
|
187
|
+
tags: list[str] | None = None,
|
|
188
|
+
definition: MeasurementDefinition | None = None,
|
|
189
|
+
) -> None:
|
|
190
|
+
"""Register a custom measurement.
|
|
191
|
+
|
|
192
|
+
Can be called with individual parameters or with a MeasurementDefinition.
|
|
193
|
+
|
|
194
|
+
Args:
|
|
195
|
+
name: Measurement name (required if definition not provided).
|
|
196
|
+
func: Measurement function (required if definition not provided).
|
|
197
|
+
units: Units of measurement (required if definition not provided).
|
|
198
|
+
category: Measurement category (required if definition not provided).
|
|
199
|
+
description: Optional description.
|
|
200
|
+
tags: Optional tags.
|
|
201
|
+
definition: Pre-built MeasurementDefinition (alternative to individual args).
|
|
202
|
+
|
|
203
|
+
Raises:
|
|
204
|
+
ValueError: If required parameters missing or name already exists.
|
|
205
|
+
|
|
206
|
+
Example:
|
|
207
|
+
>>> registry = MeasurementRegistry()
|
|
208
|
+
>>> # Register with individual parameters
|
|
209
|
+
>>> registry.register(
|
|
210
|
+
... name='peak',
|
|
211
|
+
... func=lambda trace: abs(trace.data).max(),
|
|
212
|
+
... units='V',
|
|
213
|
+
... category='amplitude'
|
|
214
|
+
... )
|
|
215
|
+
>>> # Register with definition
|
|
216
|
+
>>> defn = MeasurementDefinition(...)
|
|
217
|
+
>>> registry.register(definition=defn)
|
|
218
|
+
"""
|
|
219
|
+
# Handle definition argument
|
|
220
|
+
if definition is not None:
|
|
221
|
+
defn = definition
|
|
222
|
+
else:
|
|
223
|
+
# Validate required parameters
|
|
224
|
+
if name is None or func is None or units is None or category is None:
|
|
225
|
+
raise ValueError(
|
|
226
|
+
"Must provide either 'definition' or all of (name, func, units, category)"
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
defn = MeasurementDefinition(
|
|
230
|
+
name=name,
|
|
231
|
+
func=func,
|
|
232
|
+
units=units,
|
|
233
|
+
category=category,
|
|
234
|
+
description=description,
|
|
235
|
+
tags=tags or [],
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
# Check for duplicates
|
|
239
|
+
if defn.name in self._definitions:
|
|
240
|
+
raise ValueError(f"Measurement '{defn.name}' already registered")
|
|
241
|
+
|
|
242
|
+
# Register in both registries
|
|
243
|
+
self._definitions[defn.name] = defn
|
|
244
|
+
self._algorithm_registry.register(
|
|
245
|
+
name=defn.name,
|
|
246
|
+
func=defn.func,
|
|
247
|
+
category=self.MEASUREMENT_CATEGORY,
|
|
248
|
+
validate=False, # Already validated by MeasurementDefinition
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
def get(self, name: str) -> MeasurementDefinition:
|
|
252
|
+
"""Get measurement definition by name.
|
|
253
|
+
|
|
254
|
+
Args:
|
|
255
|
+
name: Measurement name.
|
|
256
|
+
|
|
257
|
+
Returns:
|
|
258
|
+
MeasurementDefinition for the measurement.
|
|
259
|
+
|
|
260
|
+
Raises:
|
|
261
|
+
KeyError: If measurement not found.
|
|
262
|
+
|
|
263
|
+
Example:
|
|
264
|
+
>>> measurement = registry.get('crest_factor')
|
|
265
|
+
>>> value = measurement(trace)
|
|
266
|
+
"""
|
|
267
|
+
if name not in self._definitions:
|
|
268
|
+
available = list(self._definitions.keys())
|
|
269
|
+
raise KeyError(f"Measurement '{name}' not found. Available: {available}")
|
|
270
|
+
|
|
271
|
+
return self._definitions[name]
|
|
272
|
+
|
|
273
|
+
def has_measurement(self, name: str) -> bool:
|
|
274
|
+
"""Check if measurement is registered.
|
|
275
|
+
|
|
276
|
+
Args:
|
|
277
|
+
name: Measurement name.
|
|
278
|
+
|
|
279
|
+
Returns:
|
|
280
|
+
True if measurement is registered.
|
|
281
|
+
|
|
282
|
+
Example:
|
|
283
|
+
>>> if registry.has_measurement('crest_factor'):
|
|
284
|
+
... cf = registry.get('crest_factor')(trace)
|
|
285
|
+
"""
|
|
286
|
+
return name in self._definitions
|
|
287
|
+
|
|
288
|
+
def list_measurements(
|
|
289
|
+
self,
|
|
290
|
+
category: str | None = None,
|
|
291
|
+
tags: list[str] | None = None,
|
|
292
|
+
) -> list[str]:
|
|
293
|
+
"""List registered measurements.
|
|
294
|
+
|
|
295
|
+
Args:
|
|
296
|
+
category: Filter by category (optional).
|
|
297
|
+
tags: Filter by tags (optional).
|
|
298
|
+
|
|
299
|
+
Returns:
|
|
300
|
+
List of measurement names.
|
|
301
|
+
|
|
302
|
+
Example:
|
|
303
|
+
>>> # List all measurements
|
|
304
|
+
>>> all_measurements = registry.list_measurements()
|
|
305
|
+
>>> # List amplitude measurements
|
|
306
|
+
>>> amplitude = registry.list_measurements(category='amplitude')
|
|
307
|
+
>>> # List measurements with 'edge' tag
|
|
308
|
+
>>> edge_measurements = registry.list_measurements(tags=['edge'])
|
|
309
|
+
"""
|
|
310
|
+
measurements = []
|
|
311
|
+
|
|
312
|
+
for name, defn in self._definitions.items():
|
|
313
|
+
# Filter by category
|
|
314
|
+
if category is not None and defn.category != category:
|
|
315
|
+
continue
|
|
316
|
+
|
|
317
|
+
# Filter by tags
|
|
318
|
+
if tags is not None and not any(tag in defn.tags for tag in tags):
|
|
319
|
+
continue
|
|
320
|
+
|
|
321
|
+
measurements.append(name)
|
|
322
|
+
|
|
323
|
+
return measurements
|
|
324
|
+
|
|
325
|
+
def get_metadata(self, name: str) -> dict[str, Any]:
|
|
326
|
+
"""Get metadata for a measurement.
|
|
327
|
+
|
|
328
|
+
Args:
|
|
329
|
+
name: Measurement name.
|
|
330
|
+
|
|
331
|
+
Returns:
|
|
332
|
+
Dictionary with measurement metadata.
|
|
333
|
+
|
|
334
|
+
Example:
|
|
335
|
+
>>> metadata = registry.get_metadata('crest_factor')
|
|
336
|
+
>>> print(f"Units: {metadata['units']}")
|
|
337
|
+
>>> print(f"Category: {metadata['category']}")
|
|
338
|
+
"""
|
|
339
|
+
defn = self.get(name)
|
|
340
|
+
return {
|
|
341
|
+
"name": defn.name,
|
|
342
|
+
"units": defn.units,
|
|
343
|
+
"category": defn.category,
|
|
344
|
+
"description": defn.description,
|
|
345
|
+
"tags": defn.tags,
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
def unregister(self, name: str) -> None:
|
|
349
|
+
"""Unregister a measurement.
|
|
350
|
+
|
|
351
|
+
Args:
|
|
352
|
+
name: Measurement name.
|
|
353
|
+
|
|
354
|
+
Example:
|
|
355
|
+
>>> registry.unregister('crest_factor')
|
|
356
|
+
"""
|
|
357
|
+
if name in self._definitions:
|
|
358
|
+
del self._definitions[name]
|
|
359
|
+
|
|
360
|
+
if self._algorithm_registry.has_algorithm(self.MEASUREMENT_CATEGORY, name):
|
|
361
|
+
self._algorithm_registry.unregister(self.MEASUREMENT_CATEGORY, name)
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
# Global measurement registry
|
|
365
|
+
_registry = MeasurementRegistry()
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
def register_measurement(
|
|
369
|
+
name: str | None = None,
|
|
370
|
+
func: Callable[[WaveformTrace], float] | None = None,
|
|
371
|
+
units: str | None = None,
|
|
372
|
+
category: str | None = None,
|
|
373
|
+
description: str = "",
|
|
374
|
+
tags: list[str] | None = None,
|
|
375
|
+
definition: MeasurementDefinition | None = None,
|
|
376
|
+
) -> None:
|
|
377
|
+
"""Register a custom measurement in the global registry.
|
|
378
|
+
|
|
379
|
+
Convenience function for registering measurements without accessing
|
|
380
|
+
the registry directly.
|
|
381
|
+
|
|
382
|
+
Args:
|
|
383
|
+
name: Measurement name.
|
|
384
|
+
func: Measurement function.
|
|
385
|
+
units: Units of measurement.
|
|
386
|
+
category: Measurement category.
|
|
387
|
+
description: Optional description.
|
|
388
|
+
tags: Optional tags.
|
|
389
|
+
definition: Pre-built MeasurementDefinition.
|
|
390
|
+
|
|
391
|
+
Example:
|
|
392
|
+
>>> import oscura as tk
|
|
393
|
+
>>> def calculate_crest_factor(trace, **kwargs):
|
|
394
|
+
... peak = abs(trace.data).max()
|
|
395
|
+
... rms = (trace.data ** 2).mean() ** 0.5
|
|
396
|
+
... return peak / rms
|
|
397
|
+
>>> tk.register_measurement(
|
|
398
|
+
... name='crest_factor',
|
|
399
|
+
... func=calculate_crest_factor,
|
|
400
|
+
... units='ratio',
|
|
401
|
+
... category='amplitude'
|
|
402
|
+
... )
|
|
403
|
+
|
|
404
|
+
References:
|
|
405
|
+
API-008: Custom Measurement Framework
|
|
406
|
+
"""
|
|
407
|
+
_registry.register(
|
|
408
|
+
name=name,
|
|
409
|
+
func=func,
|
|
410
|
+
units=units,
|
|
411
|
+
category=category,
|
|
412
|
+
description=description,
|
|
413
|
+
tags=tags,
|
|
414
|
+
definition=definition,
|
|
415
|
+
)
|
|
416
|
+
|
|
417
|
+
|
|
418
|
+
def measure(trace: WaveformTrace, name: str, **kwargs: Any) -> float:
|
|
419
|
+
"""Execute a registered measurement.
|
|
420
|
+
|
|
421
|
+
Args:
|
|
422
|
+
trace: WaveformTrace to measure.
|
|
423
|
+
name: Measurement name.
|
|
424
|
+
**kwargs: Additional parameters for the measurement.
|
|
425
|
+
|
|
426
|
+
Returns:
|
|
427
|
+
Measured value.
|
|
428
|
+
|
|
429
|
+
Example:
|
|
430
|
+
>>> import oscura as tk
|
|
431
|
+
>>> cf = tk.measure(trace, 'crest_factor')
|
|
432
|
+
>>> print(f"Crest factor: {cf:.2f}")
|
|
433
|
+
|
|
434
|
+
References:
|
|
435
|
+
API-008: Custom Measurement Framework
|
|
436
|
+
"""
|
|
437
|
+
defn = _registry.get(name)
|
|
438
|
+
return defn(trace, **kwargs)
|
|
439
|
+
|
|
440
|
+
|
|
441
|
+
def list_measurements(
|
|
442
|
+
category: str | None = None,
|
|
443
|
+
tags: list[str] | None = None,
|
|
444
|
+
) -> list[str]:
|
|
445
|
+
"""List registered measurements.
|
|
446
|
+
|
|
447
|
+
Args:
|
|
448
|
+
category: Filter by category (optional).
|
|
449
|
+
tags: Filter by tags (optional).
|
|
450
|
+
|
|
451
|
+
Returns:
|
|
452
|
+
List of measurement names.
|
|
453
|
+
|
|
454
|
+
Example:
|
|
455
|
+
>>> import oscura as tk
|
|
456
|
+
>>> measurements = tk.list_measurements(category='amplitude')
|
|
457
|
+
>>> print(f"Amplitude measurements: {measurements}")
|
|
458
|
+
|
|
459
|
+
References:
|
|
460
|
+
API-008: Custom Measurement Framework
|
|
461
|
+
"""
|
|
462
|
+
return _registry.list_measurements(category=category, tags=tags)
|
|
463
|
+
|
|
464
|
+
|
|
465
|
+
def get_measurement_registry() -> MeasurementRegistry:
|
|
466
|
+
"""Get the global measurement registry.
|
|
467
|
+
|
|
468
|
+
Returns:
|
|
469
|
+
Global MeasurementRegistry instance.
|
|
470
|
+
|
|
471
|
+
Example:
|
|
472
|
+
>>> registry = tk.get_measurement_registry()
|
|
473
|
+
>>> metadata = registry.get_metadata('crest_factor')
|
|
474
|
+
"""
|
|
475
|
+
return _registry
|
|
476
|
+
|
|
477
|
+
|
|
478
|
+
__all__ = [
|
|
479
|
+
"MeasurementDefinition",
|
|
480
|
+
"MeasurementRegistry",
|
|
481
|
+
"get_measurement_registry",
|
|
482
|
+
"list_measurements",
|
|
483
|
+
"measure",
|
|
484
|
+
"register_measurement",
|
|
485
|
+
]
|