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
oscura/api/operators.py
ADDED
|
@@ -0,0 +1,498 @@
|
|
|
1
|
+
"""Pythonic operators and utilities for signal analysis.
|
|
2
|
+
|
|
3
|
+
This module provides Pythonic operators, time-based indexing,
|
|
4
|
+
automatic unit conversion, and convenience utilities.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import re
|
|
10
|
+
from dataclasses import dataclass
|
|
11
|
+
from typing import TYPE_CHECKING, Any
|
|
12
|
+
|
|
13
|
+
import numpy as np
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from collections.abc import Callable
|
|
17
|
+
|
|
18
|
+
from numpy.typing import NDArray
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
"TimeIndex",
|
|
22
|
+
"UnitConverter",
|
|
23
|
+
"convert_units",
|
|
24
|
+
"make_pipeable",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
# =============================================================================
|
|
29
|
+
# =============================================================================
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class TimeIndex:
|
|
33
|
+
"""Time-based indexing for trace data.
|
|
34
|
+
|
|
35
|
+
Allows slicing trace data using time values instead of sample indices.
|
|
36
|
+
|
|
37
|
+
Example:
|
|
38
|
+
>>> ti = TimeIndex(data, sample_rate=1e9)
|
|
39
|
+
>>> # Get first 1 millisecond
|
|
40
|
+
>>> segment = ti["0ms":"1ms"]
|
|
41
|
+
>>> # Get from 100us to 200us
|
|
42
|
+
>>> segment = ti["100us":"200us"]
|
|
43
|
+
|
|
44
|
+
References:
|
|
45
|
+
API-016: Time-Based Indexing
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
# Time unit multipliers to seconds
|
|
49
|
+
TIME_UNITS = { # noqa: RUF012
|
|
50
|
+
"s": 1.0,
|
|
51
|
+
"ms": 1e-3,
|
|
52
|
+
"us": 1e-6,
|
|
53
|
+
"ns": 1e-9,
|
|
54
|
+
"ps": 1e-12,
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
def __init__(self, data: NDArray[np.float64], sample_rate: float, start_time: float = 0.0):
|
|
58
|
+
"""Initialize time indexer.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
data: Trace data array
|
|
62
|
+
sample_rate: Sample rate in Hz
|
|
63
|
+
start_time: Start time offset in seconds
|
|
64
|
+
"""
|
|
65
|
+
self._data = data
|
|
66
|
+
self._sample_rate = sample_rate
|
|
67
|
+
self._start_time = start_time
|
|
68
|
+
|
|
69
|
+
@property
|
|
70
|
+
def duration(self) -> float:
|
|
71
|
+
"""Get trace duration in seconds."""
|
|
72
|
+
return len(self._data) / self._sample_rate
|
|
73
|
+
|
|
74
|
+
@property
|
|
75
|
+
def time_axis(self) -> NDArray[np.float64]:
|
|
76
|
+
"""Get time axis array."""
|
|
77
|
+
return np.arange(len(self._data)) / self._sample_rate + self._start_time
|
|
78
|
+
|
|
79
|
+
def _parse_time(self, time_str: str) -> float:
|
|
80
|
+
"""Parse time string to seconds.
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
time_str: Time string (e.g., "100ms", "1.5us")
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
Time in seconds
|
|
87
|
+
|
|
88
|
+
Raises:
|
|
89
|
+
ValueError: If time format is invalid or unit is unknown.
|
|
90
|
+
"""
|
|
91
|
+
# Match number with optional unit
|
|
92
|
+
match = re.match(r"([-+]?\d*\.?\d+)\s*([a-zA-Z]*)", time_str.strip())
|
|
93
|
+
if not match:
|
|
94
|
+
raise ValueError(f"Invalid time format: {time_str}")
|
|
95
|
+
|
|
96
|
+
value = float(match.group(1))
|
|
97
|
+
unit = match.group(2).lower() or "s"
|
|
98
|
+
|
|
99
|
+
if unit not in self.TIME_UNITS:
|
|
100
|
+
raise ValueError(
|
|
101
|
+
f"Unknown time unit: {unit}. Valid units: {list(self.TIME_UNITS.keys())}"
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
return value * self.TIME_UNITS[unit]
|
|
105
|
+
|
|
106
|
+
def _time_to_index(self, time_seconds: float) -> int:
|
|
107
|
+
"""Convert time to sample index.
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
time_seconds: Time in seconds
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
Sample index
|
|
114
|
+
"""
|
|
115
|
+
relative_time = time_seconds - self._start_time
|
|
116
|
+
index = int(relative_time * self._sample_rate)
|
|
117
|
+
return max(0, min(index, len(self._data) - 1))
|
|
118
|
+
|
|
119
|
+
def at(self, time: str | float) -> float:
|
|
120
|
+
"""Get value at specific time.
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
time: Time as string or float (seconds)
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
Value at that time
|
|
127
|
+
"""
|
|
128
|
+
if isinstance(time, str):
|
|
129
|
+
time = self._parse_time(time)
|
|
130
|
+
index = self._time_to_index(time)
|
|
131
|
+
return float(self._data[index])
|
|
132
|
+
|
|
133
|
+
def slice(
|
|
134
|
+
self, start: str | float | None = None, end: str | float | None = None
|
|
135
|
+
) -> NDArray[np.float64]:
|
|
136
|
+
"""Slice data by time range.
|
|
137
|
+
|
|
138
|
+
Args:
|
|
139
|
+
start: Start time
|
|
140
|
+
end: End time
|
|
141
|
+
|
|
142
|
+
Returns:
|
|
143
|
+
Sliced data array
|
|
144
|
+
"""
|
|
145
|
+
if start is not None:
|
|
146
|
+
if isinstance(start, str):
|
|
147
|
+
start = self._parse_time(start)
|
|
148
|
+
start_idx = self._time_to_index(start)
|
|
149
|
+
else:
|
|
150
|
+
start_idx = 0
|
|
151
|
+
|
|
152
|
+
if end is not None:
|
|
153
|
+
if isinstance(end, str):
|
|
154
|
+
end = self._parse_time(end)
|
|
155
|
+
end_idx = self._time_to_index(end)
|
|
156
|
+
else:
|
|
157
|
+
end_idx = len(self._data)
|
|
158
|
+
|
|
159
|
+
return self._data[start_idx:end_idx]
|
|
160
|
+
|
|
161
|
+
def __getitem__(self, key: slice | str | float) -> NDArray[np.float64] | float: # type: ignore[valid-type]
|
|
162
|
+
"""Enable bracket notation for time-based indexing.
|
|
163
|
+
|
|
164
|
+
Args:
|
|
165
|
+
key: Slice with time strings, or single time
|
|
166
|
+
|
|
167
|
+
Returns:
|
|
168
|
+
Sliced data or single value
|
|
169
|
+
"""
|
|
170
|
+
if isinstance(key, slice):
|
|
171
|
+
return self.slice(key.start, key.stop)
|
|
172
|
+
else:
|
|
173
|
+
return self.at(key)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
# =============================================================================
|
|
177
|
+
# =============================================================================
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
@dataclass
|
|
181
|
+
class Unit:
|
|
182
|
+
"""Unit definition with conversion factor.
|
|
183
|
+
|
|
184
|
+
Attributes:
|
|
185
|
+
name: Unit name
|
|
186
|
+
symbol: Unit symbol
|
|
187
|
+
factor: Conversion factor to base unit
|
|
188
|
+
base_unit: Base unit name
|
|
189
|
+
"""
|
|
190
|
+
|
|
191
|
+
name: str
|
|
192
|
+
symbol: str
|
|
193
|
+
factor: float
|
|
194
|
+
base_unit: str
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
class UnitConverter:
|
|
198
|
+
"""Automatic unit conversion for measurements.
|
|
199
|
+
|
|
200
|
+
Supports common electrical and signal analysis units with
|
|
201
|
+
automatic prefix handling (mV, uV, MHz, etc.).
|
|
202
|
+
|
|
203
|
+
Example:
|
|
204
|
+
>>> converter = UnitConverter()
|
|
205
|
+
>>> converter.convert(1000, "mV", "V")
|
|
206
|
+
1.0
|
|
207
|
+
>>> converter.auto_scale(0.000001, "V")
|
|
208
|
+
(1.0, "uV")
|
|
209
|
+
|
|
210
|
+
References:
|
|
211
|
+
API-018: Automatic Unit Conversion
|
|
212
|
+
"""
|
|
213
|
+
|
|
214
|
+
# SI prefixes
|
|
215
|
+
SI_PREFIXES = { # noqa: RUF012
|
|
216
|
+
"P": 1e15, # peta
|
|
217
|
+
"T": 1e12, # tera
|
|
218
|
+
"G": 1e9, # giga
|
|
219
|
+
"M": 1e6, # mega
|
|
220
|
+
"k": 1e3, # kilo
|
|
221
|
+
"": 1.0, # base
|
|
222
|
+
"m": 1e-3, # milli
|
|
223
|
+
"u": 1e-6, # micro
|
|
224
|
+
"n": 1e-9, # nano
|
|
225
|
+
"p": 1e-12, # pico
|
|
226
|
+
"f": 1e-15, # femto
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
# Base units
|
|
230
|
+
BASE_UNITS = { # noqa: RUF012
|
|
231
|
+
"V": "voltage",
|
|
232
|
+
"A": "current",
|
|
233
|
+
"W": "power",
|
|
234
|
+
"Hz": "frequency",
|
|
235
|
+
"s": "time",
|
|
236
|
+
"F": "capacitance",
|
|
237
|
+
"H": "inductance",
|
|
238
|
+
"Ohm": "resistance",
|
|
239
|
+
"dB": "decibel",
|
|
240
|
+
"dBm": "power_dbm",
|
|
241
|
+
"dBV": "voltage_dbv",
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
def __init__(self) -> None:
|
|
245
|
+
"""Initialize converter."""
|
|
246
|
+
self._custom_units: dict[str, Unit] = {}
|
|
247
|
+
|
|
248
|
+
def _parse_unit(self, unit_str: str) -> tuple[float, str]:
|
|
249
|
+
"""Parse unit string into prefix multiplier and base unit.
|
|
250
|
+
|
|
251
|
+
Args:
|
|
252
|
+
unit_str: Unit string (e.g., "mV", "MHz")
|
|
253
|
+
|
|
254
|
+
Returns:
|
|
255
|
+
Tuple of (multiplier, base_unit)
|
|
256
|
+
"""
|
|
257
|
+
# Check for dB-based units first
|
|
258
|
+
for db_unit in ("dBm", "dBV", "dB"):
|
|
259
|
+
if unit_str.endswith(db_unit):
|
|
260
|
+
prefix = unit_str[: -len(db_unit)]
|
|
261
|
+
multiplier = self.SI_PREFIXES.get(prefix, 1.0)
|
|
262
|
+
return multiplier, db_unit
|
|
263
|
+
|
|
264
|
+
# Check for other base units
|
|
265
|
+
for base in sorted(self.BASE_UNITS.keys(), key=len, reverse=True):
|
|
266
|
+
if unit_str.endswith(base):
|
|
267
|
+
prefix = unit_str[: -len(base)]
|
|
268
|
+
multiplier = self.SI_PREFIXES.get(prefix, 1.0)
|
|
269
|
+
return multiplier, base
|
|
270
|
+
|
|
271
|
+
# No recognized base unit
|
|
272
|
+
return 1.0, unit_str
|
|
273
|
+
|
|
274
|
+
def convert(self, value: float, from_unit: str, to_unit: str) -> float:
|
|
275
|
+
"""Convert value between units.
|
|
276
|
+
|
|
277
|
+
Args:
|
|
278
|
+
value: Value to convert
|
|
279
|
+
from_unit: Source unit
|
|
280
|
+
to_unit: Target unit
|
|
281
|
+
|
|
282
|
+
Returns:
|
|
283
|
+
Converted value
|
|
284
|
+
|
|
285
|
+
Raises:
|
|
286
|
+
ValueError: If units are incompatible
|
|
287
|
+
"""
|
|
288
|
+
from_mult, from_base = self._parse_unit(from_unit)
|
|
289
|
+
to_mult, to_base = self._parse_unit(to_unit)
|
|
290
|
+
|
|
291
|
+
# Check compatibility
|
|
292
|
+
if from_base != to_base:
|
|
293
|
+
# Special handling for dB conversions
|
|
294
|
+
if from_base == "dBm" and to_base == "W":
|
|
295
|
+
return 10 ** ((value * from_mult - 30) / 10) / to_mult
|
|
296
|
+
elif from_base == "W" and to_base == "dBm":
|
|
297
|
+
return (10 * np.log10(value * from_mult) + 30) / to_mult # type: ignore[no-any-return]
|
|
298
|
+
elif from_base == "dBV" and to_base == "V":
|
|
299
|
+
return 10 ** ((value * from_mult) / 20) / to_mult
|
|
300
|
+
elif from_base == "V" and to_base == "dBV":
|
|
301
|
+
return 20 * np.log10(value * from_mult) / to_mult # type: ignore[no-any-return]
|
|
302
|
+
else:
|
|
303
|
+
raise ValueError(f"Cannot convert between {from_base} and {to_base}")
|
|
304
|
+
|
|
305
|
+
# Simple conversion
|
|
306
|
+
return value * from_mult / to_mult
|
|
307
|
+
|
|
308
|
+
def auto_scale(self, value: float, base_unit: str) -> tuple[float, str]:
|
|
309
|
+
"""Automatically scale value to appropriate prefix.
|
|
310
|
+
|
|
311
|
+
Args:
|
|
312
|
+
value: Value in base units
|
|
313
|
+
base_unit: Base unit string
|
|
314
|
+
|
|
315
|
+
Returns:
|
|
316
|
+
Tuple of (scaled_value, unit_string)
|
|
317
|
+
|
|
318
|
+
Example:
|
|
319
|
+
>>> converter.auto_scale(0.000001, "V")
|
|
320
|
+
(1.0, "uV")
|
|
321
|
+
"""
|
|
322
|
+
abs_value = abs(value) if value != 0 else 1
|
|
323
|
+
|
|
324
|
+
# Find appropriate prefix
|
|
325
|
+
prefixes_ordered = [
|
|
326
|
+
("P", 1e15),
|
|
327
|
+
("T", 1e12),
|
|
328
|
+
("G", 1e9),
|
|
329
|
+
("M", 1e6),
|
|
330
|
+
("k", 1e3),
|
|
331
|
+
("", 1.0),
|
|
332
|
+
("m", 1e-3),
|
|
333
|
+
("u", 1e-6),
|
|
334
|
+
("n", 1e-9),
|
|
335
|
+
("p", 1e-12),
|
|
336
|
+
("f", 1e-15),
|
|
337
|
+
]
|
|
338
|
+
|
|
339
|
+
for prefix, factor in prefixes_ordered:
|
|
340
|
+
scaled = abs_value / factor
|
|
341
|
+
if 1.0 <= scaled < 1000.0 or prefix == "f":
|
|
342
|
+
return value / factor, f"{prefix}{base_unit}"
|
|
343
|
+
|
|
344
|
+
# Default to base unit
|
|
345
|
+
return value, base_unit
|
|
346
|
+
|
|
347
|
+
def format_value(self, value: float, base_unit: str, precision: int = 3) -> str:
|
|
348
|
+
"""Format value with automatic scaling.
|
|
349
|
+
|
|
350
|
+
Args:
|
|
351
|
+
value: Value in base units
|
|
352
|
+
base_unit: Base unit string
|
|
353
|
+
precision: Decimal precision
|
|
354
|
+
|
|
355
|
+
Returns:
|
|
356
|
+
Formatted string
|
|
357
|
+
"""
|
|
358
|
+
scaled, unit = self.auto_scale(value, base_unit)
|
|
359
|
+
return f"{scaled:.{precision}g} {unit}"
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
def convert_units(value: float, from_unit: str, to_unit: str) -> float:
|
|
363
|
+
"""Convert value between units.
|
|
364
|
+
|
|
365
|
+
Convenience function for unit conversion.
|
|
366
|
+
|
|
367
|
+
Args:
|
|
368
|
+
value: Value to convert
|
|
369
|
+
from_unit: Source unit
|
|
370
|
+
to_unit: Target unit
|
|
371
|
+
|
|
372
|
+
Returns:
|
|
373
|
+
Converted value
|
|
374
|
+
|
|
375
|
+
Example:
|
|
376
|
+
>>> convert_units(1000, "mV", "V")
|
|
377
|
+
1.0
|
|
378
|
+
>>> convert_units(1, "MHz", "Hz")
|
|
379
|
+
1000000.0
|
|
380
|
+
|
|
381
|
+
References:
|
|
382
|
+
API-018: Automatic Unit Conversion
|
|
383
|
+
"""
|
|
384
|
+
return UnitConverter().convert(value, from_unit, to_unit)
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
# =============================================================================
|
|
388
|
+
# =============================================================================
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
class PipeableFunction:
|
|
392
|
+
"""Wrapper for making functions pipeable with >> operator.
|
|
393
|
+
|
|
394
|
+
Example:
|
|
395
|
+
>>> @make_pipeable
|
|
396
|
+
>>> def lowpass(data, cutoff):
|
|
397
|
+
... return filtered_data
|
|
398
|
+
>>> result = data >> lowpass(cutoff=1e6) >> normalize()
|
|
399
|
+
|
|
400
|
+
References:
|
|
401
|
+
API-015: Pythonic Operators
|
|
402
|
+
"""
|
|
403
|
+
|
|
404
|
+
def __init__(self, func: Callable, *args: Any, **kwargs: Any): # type: ignore[type-arg]
|
|
405
|
+
"""Initialize pipeable function.
|
|
406
|
+
|
|
407
|
+
Args:
|
|
408
|
+
func: Function to wrap
|
|
409
|
+
*args: Positional arguments
|
|
410
|
+
**kwargs: Keyword arguments
|
|
411
|
+
"""
|
|
412
|
+
self._func = func
|
|
413
|
+
self._args = args
|
|
414
|
+
self._kwargs = kwargs
|
|
415
|
+
|
|
416
|
+
def __call__(self, data: Any) -> Any:
|
|
417
|
+
"""Call function with data as first argument.
|
|
418
|
+
|
|
419
|
+
Args:
|
|
420
|
+
data: Input data
|
|
421
|
+
|
|
422
|
+
Returns:
|
|
423
|
+
Function result
|
|
424
|
+
"""
|
|
425
|
+
return self._func(data, *self._args, **self._kwargs)
|
|
426
|
+
|
|
427
|
+
def __rrshift__(self, other: Any) -> Any:
|
|
428
|
+
"""Enable data >> func() syntax.
|
|
429
|
+
|
|
430
|
+
Args:
|
|
431
|
+
other: Left operand (data)
|
|
432
|
+
|
|
433
|
+
Returns:
|
|
434
|
+
Function result
|
|
435
|
+
"""
|
|
436
|
+
return self(other)
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
def make_pipeable(func: Callable) -> Callable: # type: ignore[type-arg]
|
|
440
|
+
"""Decorator to make function pipeable with >> operator.
|
|
441
|
+
|
|
442
|
+
Args:
|
|
443
|
+
func: Function to wrap
|
|
444
|
+
|
|
445
|
+
Returns:
|
|
446
|
+
Wrapper that returns PipeableFunction
|
|
447
|
+
|
|
448
|
+
Example:
|
|
449
|
+
>>> @make_pipeable
|
|
450
|
+
>>> def scale(data, factor):
|
|
451
|
+
... return data * factor
|
|
452
|
+
>>> result = data >> scale(factor=2)
|
|
453
|
+
|
|
454
|
+
References:
|
|
455
|
+
API-015: Pythonic Operators
|
|
456
|
+
"""
|
|
457
|
+
|
|
458
|
+
def wrapper(*args: Any, **kwargs: Any) -> PipeableFunction:
|
|
459
|
+
return PipeableFunction(func, *args, **kwargs)
|
|
460
|
+
|
|
461
|
+
wrapper.__name__ = func.__name__
|
|
462
|
+
wrapper.__doc__ = func.__doc__
|
|
463
|
+
return wrapper
|
|
464
|
+
|
|
465
|
+
|
|
466
|
+
# Create pipeable versions of common operations
|
|
467
|
+
@make_pipeable
|
|
468
|
+
def scale(data: NDArray[np.float64], factor: float) -> NDArray[np.float64]:
|
|
469
|
+
"""Scale data by factor."""
|
|
470
|
+
return data * factor
|
|
471
|
+
|
|
472
|
+
|
|
473
|
+
@make_pipeable
|
|
474
|
+
def offset(data: NDArray[np.float64], value: float) -> NDArray[np.float64]:
|
|
475
|
+
"""Add offset to data."""
|
|
476
|
+
return data + value
|
|
477
|
+
|
|
478
|
+
|
|
479
|
+
@make_pipeable
|
|
480
|
+
def clip_values(data: NDArray[np.float64], low: float, high: float) -> NDArray[np.float64]:
|
|
481
|
+
"""Clip data to range."""
|
|
482
|
+
return np.clip(data, low, high)
|
|
483
|
+
|
|
484
|
+
|
|
485
|
+
@make_pipeable
|
|
486
|
+
def normalize_data(data: NDArray[np.float64], method: str = "minmax") -> NDArray[np.float64]:
|
|
487
|
+
"""Normalize data."""
|
|
488
|
+
if method == "minmax":
|
|
489
|
+
dmin, dmax = data.min(), data.max()
|
|
490
|
+
if dmax - dmin > 0:
|
|
491
|
+
result: NDArray[np.float64] = (data - dmin) / (dmax - dmin)
|
|
492
|
+
return result
|
|
493
|
+
elif method == "zscore":
|
|
494
|
+
std = data.std()
|
|
495
|
+
if std > 0:
|
|
496
|
+
result_z: NDArray[np.float64] = (data - data.mean()) / std
|
|
497
|
+
return result_z
|
|
498
|
+
return data
|