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/core/config.py
ADDED
|
@@ -0,0 +1,506 @@
|
|
|
1
|
+
"""TraceKit configuration loading and management.
|
|
2
|
+
|
|
3
|
+
This module provides configuration loading from YAML files with support
|
|
4
|
+
for nested structures, user overrides, and schema validation.
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
Example:
|
|
8
|
+
>>> from oscura.core.config import load_config
|
|
9
|
+
>>> config = load_config()
|
|
10
|
+
>>> print(config["defaults"]["sample_rate"])
|
|
11
|
+
1000000.0
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import copy
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from typing import Any
|
|
19
|
+
|
|
20
|
+
import numpy as np
|
|
21
|
+
|
|
22
|
+
try:
|
|
23
|
+
import yaml
|
|
24
|
+
|
|
25
|
+
YAML_AVAILABLE = True
|
|
26
|
+
except ImportError:
|
|
27
|
+
YAML_AVAILABLE = False
|
|
28
|
+
|
|
29
|
+
from oscura.core.exceptions import ConfigurationError
|
|
30
|
+
|
|
31
|
+
# Default configuration values
|
|
32
|
+
DEFAULT_CONFIG: dict[str, Any] = {
|
|
33
|
+
"version": "1.0",
|
|
34
|
+
"defaults": {
|
|
35
|
+
"sample_rate": 1e6, # 1 MHz default
|
|
36
|
+
"window_function": "hann",
|
|
37
|
+
"fft_size": 1024,
|
|
38
|
+
},
|
|
39
|
+
"loaders": {
|
|
40
|
+
"auto_detect": True,
|
|
41
|
+
"formats": ["wfm", "csv", "npz", "hdf5", "tdms", "vcd", "sr", "wav"],
|
|
42
|
+
"tektronix": {"byte_order": "little"},
|
|
43
|
+
"csv": {"delimiter": ",", "skip_header": 0},
|
|
44
|
+
},
|
|
45
|
+
"measurements": {
|
|
46
|
+
"rise_time": {"ref_levels": [0.1, 0.9]},
|
|
47
|
+
"fall_time": {"ref_levels": [0.9, 0.1]},
|
|
48
|
+
"frequency": {"min_periods": 3},
|
|
49
|
+
},
|
|
50
|
+
"spectral": {
|
|
51
|
+
"default_window": "hann",
|
|
52
|
+
"overlap": 0.5,
|
|
53
|
+
"nfft": None, # Auto-determine from signal length
|
|
54
|
+
},
|
|
55
|
+
"visualization": {
|
|
56
|
+
"default_style": "seaborn",
|
|
57
|
+
"figure_size": [10, 6],
|
|
58
|
+
"dpi": 100,
|
|
59
|
+
},
|
|
60
|
+
"export": {
|
|
61
|
+
"csv": {"precision": 6},
|
|
62
|
+
"hdf5": {"compression": "gzip", "compression_opts": 4},
|
|
63
|
+
},
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _deep_merge(base: dict[str, Any], override: dict[str, Any]) -> dict[str, Any]:
|
|
68
|
+
"""Recursively merge two dictionaries.
|
|
69
|
+
|
|
70
|
+
Values from override take precedence. Nested dictionaries are
|
|
71
|
+
merged recursively.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
base: Base dictionary.
|
|
75
|
+
override: Dictionary with values to override.
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
Merged dictionary.
|
|
79
|
+
"""
|
|
80
|
+
result = base.copy()
|
|
81
|
+
|
|
82
|
+
for key, value in override.items():
|
|
83
|
+
if key in result and isinstance(result[key], dict) and isinstance(value, dict):
|
|
84
|
+
result[key] = _deep_merge(result[key], value)
|
|
85
|
+
else:
|
|
86
|
+
result[key] = value
|
|
87
|
+
|
|
88
|
+
return result
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def load_config(
|
|
92
|
+
config_path: str | Path | None = None,
|
|
93
|
+
*,
|
|
94
|
+
use_defaults: bool = True,
|
|
95
|
+
) -> dict[str, Any]:
|
|
96
|
+
"""Load configuration from YAML file.
|
|
97
|
+
|
|
98
|
+
Loads configuration from the specified path and optionally merges
|
|
99
|
+
with default values. If no path is specified, looks for configuration
|
|
100
|
+
in standard locations.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
config_path: Path to YAML configuration file. If None, searches
|
|
104
|
+
for config in standard locations.
|
|
105
|
+
use_defaults: If True, merge loaded config with defaults.
|
|
106
|
+
|
|
107
|
+
Returns:
|
|
108
|
+
Configuration dictionary.
|
|
109
|
+
|
|
110
|
+
Raises:
|
|
111
|
+
ConfigurationError: If configuration file cannot be loaded or parsed.
|
|
112
|
+
|
|
113
|
+
Example:
|
|
114
|
+
>>> config = load_config()
|
|
115
|
+
>>> print(config["defaults"]["sample_rate"])
|
|
116
|
+
1000000.0
|
|
117
|
+
|
|
118
|
+
>>> config = load_config("~/.oscura/config.yaml")
|
|
119
|
+
>>> print(config["measurements"]["rise_time"]["ref_levels"])
|
|
120
|
+
[0.1, 0.9]
|
|
121
|
+
"""
|
|
122
|
+
config: dict[str, Any] = {}
|
|
123
|
+
|
|
124
|
+
if use_defaults:
|
|
125
|
+
config = copy.deepcopy(DEFAULT_CONFIG)
|
|
126
|
+
|
|
127
|
+
if config_path is None:
|
|
128
|
+
# Search standard locations
|
|
129
|
+
search_paths = [
|
|
130
|
+
Path.cwd() / "oscura.yaml",
|
|
131
|
+
Path.cwd() / ".oscura.yaml",
|
|
132
|
+
Path.home() / ".oscura" / "config.yaml",
|
|
133
|
+
Path.home() / ".config" / "oscura" / "config.yaml",
|
|
134
|
+
]
|
|
135
|
+
|
|
136
|
+
for path in search_paths:
|
|
137
|
+
if path.exists():
|
|
138
|
+
config_path = path
|
|
139
|
+
break
|
|
140
|
+
|
|
141
|
+
if config_path is not None:
|
|
142
|
+
config_path = Path(config_path).expanduser()
|
|
143
|
+
|
|
144
|
+
if not config_path.exists():
|
|
145
|
+
raise ConfigurationError(
|
|
146
|
+
"Configuration file not found",
|
|
147
|
+
config_key=str(config_path),
|
|
148
|
+
fix_hint=f"Create configuration file at {config_path}",
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
if not YAML_AVAILABLE:
|
|
152
|
+
raise ConfigurationError(
|
|
153
|
+
"YAML support not available",
|
|
154
|
+
details="PyYAML package is required for configuration loading",
|
|
155
|
+
fix_hint="Install PyYAML: pip install pyyaml",
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
try:
|
|
159
|
+
with open(config_path, encoding="utf-8") as f:
|
|
160
|
+
user_config = yaml.safe_load(f)
|
|
161
|
+
except yaml.YAMLError as e:
|
|
162
|
+
raise ConfigurationError(
|
|
163
|
+
"Failed to parse configuration file",
|
|
164
|
+
config_key=str(config_path),
|
|
165
|
+
details=str(e),
|
|
166
|
+
) from e
|
|
167
|
+
except OSError as e:
|
|
168
|
+
raise ConfigurationError(
|
|
169
|
+
"Failed to read configuration file",
|
|
170
|
+
config_key=str(config_path),
|
|
171
|
+
details=str(e),
|
|
172
|
+
) from e
|
|
173
|
+
|
|
174
|
+
if user_config is not None:
|
|
175
|
+
config = _deep_merge(config, user_config) if use_defaults else user_config
|
|
176
|
+
|
|
177
|
+
return config
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def validate_config(config: dict[str, Any]) -> bool:
|
|
181
|
+
"""Validate configuration against schema.
|
|
182
|
+
|
|
183
|
+
Checks that required fields exist and have valid types.
|
|
184
|
+
|
|
185
|
+
Args:
|
|
186
|
+
config: Configuration dictionary to validate.
|
|
187
|
+
|
|
188
|
+
Returns:
|
|
189
|
+
True if configuration is valid.
|
|
190
|
+
|
|
191
|
+
Raises:
|
|
192
|
+
ConfigurationError: If configuration is invalid.
|
|
193
|
+
|
|
194
|
+
Example:
|
|
195
|
+
>>> config = load_config()
|
|
196
|
+
>>> validate_config(config)
|
|
197
|
+
True
|
|
198
|
+
"""
|
|
199
|
+
# Required top-level sections
|
|
200
|
+
required_sections = ["defaults", "loaders"]
|
|
201
|
+
|
|
202
|
+
for section in required_sections:
|
|
203
|
+
if section not in config:
|
|
204
|
+
raise ConfigurationError(
|
|
205
|
+
f"Missing required configuration section: {section}",
|
|
206
|
+
config_key=section,
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
# Validate defaults section
|
|
210
|
+
defaults = config.get("defaults", {})
|
|
211
|
+
if "sample_rate" in defaults:
|
|
212
|
+
sample_rate = defaults["sample_rate"]
|
|
213
|
+
if not isinstance(sample_rate, int | float) or sample_rate <= 0:
|
|
214
|
+
raise ConfigurationError(
|
|
215
|
+
"Invalid sample_rate in defaults",
|
|
216
|
+
config_key="defaults.sample_rate",
|
|
217
|
+
expected_type="positive number",
|
|
218
|
+
actual_value=sample_rate,
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
# Validate loaders section
|
|
222
|
+
loaders = config.get("loaders", {})
|
|
223
|
+
if "formats" in loaders:
|
|
224
|
+
formats = loaders["formats"]
|
|
225
|
+
if not isinstance(formats, list):
|
|
226
|
+
raise ConfigurationError(
|
|
227
|
+
"Invalid formats in loaders",
|
|
228
|
+
config_key="loaders.formats",
|
|
229
|
+
expected_type="list of strings",
|
|
230
|
+
actual_value=type(formats).__name__,
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
# Validate measurements section
|
|
234
|
+
measurements = config.get("measurements", {})
|
|
235
|
+
for name, settings in measurements.items():
|
|
236
|
+
if "ref_levels" in settings:
|
|
237
|
+
ref_levels = settings["ref_levels"]
|
|
238
|
+
if not isinstance(ref_levels, list) or len(ref_levels) != 2:
|
|
239
|
+
raise ConfigurationError(
|
|
240
|
+
f"Invalid ref_levels for {name}",
|
|
241
|
+
config_key=f"measurements.{name}.ref_levels",
|
|
242
|
+
expected_type="list of 2 numbers",
|
|
243
|
+
actual_value=ref_levels,
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
return True
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def get_config_value(
|
|
250
|
+
config: dict[str, Any],
|
|
251
|
+
key_path: str,
|
|
252
|
+
default: Any = None,
|
|
253
|
+
) -> Any:
|
|
254
|
+
"""Get a configuration value by dot-separated path.
|
|
255
|
+
|
|
256
|
+
Args:
|
|
257
|
+
config: Configuration dictionary.
|
|
258
|
+
key_path: Dot-separated path to the value (e.g., "defaults.sample_rate").
|
|
259
|
+
default: Default value if key not found.
|
|
260
|
+
|
|
261
|
+
Returns:
|
|
262
|
+
Configuration value or default.
|
|
263
|
+
|
|
264
|
+
Example:
|
|
265
|
+
>>> config = load_config()
|
|
266
|
+
>>> get_config_value(config, "defaults.sample_rate", 1e6)
|
|
267
|
+
1000000.0
|
|
268
|
+
>>> get_config_value(config, "unknown.key", "default")
|
|
269
|
+
'default'
|
|
270
|
+
"""
|
|
271
|
+
keys = key_path.split(".")
|
|
272
|
+
value = config
|
|
273
|
+
|
|
274
|
+
for key in keys:
|
|
275
|
+
if isinstance(value, dict) and key in value:
|
|
276
|
+
value = value[key]
|
|
277
|
+
else:
|
|
278
|
+
return default
|
|
279
|
+
|
|
280
|
+
return value
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def save_config(config: dict[str, Any], config_path: str | Path) -> None:
|
|
284
|
+
"""Save configuration to YAML file.
|
|
285
|
+
|
|
286
|
+
Args:
|
|
287
|
+
config: Configuration dictionary to save.
|
|
288
|
+
config_path: Path to save configuration to.
|
|
289
|
+
|
|
290
|
+
Raises:
|
|
291
|
+
ConfigurationError: If configuration cannot be saved.
|
|
292
|
+
|
|
293
|
+
Example:
|
|
294
|
+
>>> config = load_config()
|
|
295
|
+
>>> config["defaults"]["sample_rate"] = 2e6
|
|
296
|
+
>>> save_config(config, "~/my_config.yaml")
|
|
297
|
+
"""
|
|
298
|
+
if not YAML_AVAILABLE:
|
|
299
|
+
raise ConfigurationError(
|
|
300
|
+
"YAML support not available",
|
|
301
|
+
details="PyYAML package is required for configuration saving",
|
|
302
|
+
fix_hint="Install PyYAML: pip install pyyaml",
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
config_path = Path(config_path).expanduser()
|
|
306
|
+
|
|
307
|
+
# Create parent directory if needed
|
|
308
|
+
config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
309
|
+
|
|
310
|
+
try:
|
|
311
|
+
with open(config_path, "w", encoding="utf-8") as f:
|
|
312
|
+
yaml.dump(config, f, default_flow_style=False, sort_keys=False)
|
|
313
|
+
except OSError as e:
|
|
314
|
+
raise ConfigurationError(
|
|
315
|
+
"Failed to save configuration file",
|
|
316
|
+
config_key=str(config_path),
|
|
317
|
+
details=str(e),
|
|
318
|
+
) from e
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
class SmartDefaults:
|
|
322
|
+
"""Intelligent defaults configuration.
|
|
323
|
+
|
|
324
|
+
Provides smart default parameters that work for 80% of use cases,
|
|
325
|
+
with automatic parameter selection based on signal characteristics.
|
|
326
|
+
|
|
327
|
+
All auto-selected parameters are logged with rationale for transparency.
|
|
328
|
+
Users can override any parameter.
|
|
329
|
+
|
|
330
|
+
Example:
|
|
331
|
+
>>> from oscura.core.config import SmartDefaults
|
|
332
|
+
>>> defaults = SmartDefaults()
|
|
333
|
+
>>> # Get smart default for FFT size based on signal length
|
|
334
|
+
>>> fft_size = defaults.get_fft_size(signal_length=10000)
|
|
335
|
+
>>> print(fft_size)
|
|
336
|
+
8192
|
|
337
|
+
|
|
338
|
+
References:
|
|
339
|
+
Best practices from scipy.signal, matplotlib, and numpy
|
|
340
|
+
"""
|
|
341
|
+
|
|
342
|
+
def __init__(self, verbose: bool = False):
|
|
343
|
+
"""Initialize SmartDefaults.
|
|
344
|
+
|
|
345
|
+
Args:
|
|
346
|
+
verbose: If True, log parameter selection rationale.
|
|
347
|
+
"""
|
|
348
|
+
self.verbose = verbose
|
|
349
|
+
self._log_buffer: list[str] = []
|
|
350
|
+
|
|
351
|
+
def _log(self, message: str) -> None:
|
|
352
|
+
"""Log a parameter selection message.
|
|
353
|
+
|
|
354
|
+
Args:
|
|
355
|
+
message: Log message.
|
|
356
|
+
"""
|
|
357
|
+
self._log_buffer.append(message)
|
|
358
|
+
if self.verbose:
|
|
359
|
+
print(f"[SmartDefaults] {message}")
|
|
360
|
+
|
|
361
|
+
def get_fft_size(
|
|
362
|
+
self,
|
|
363
|
+
signal_length: int,
|
|
364
|
+
*,
|
|
365
|
+
min_size: int = 256,
|
|
366
|
+
max_size: int = 2**16,
|
|
367
|
+
) -> int:
|
|
368
|
+
"""Get smart default FFT size.
|
|
369
|
+
|
|
370
|
+
Args:
|
|
371
|
+
signal_length: Length of signal in samples.
|
|
372
|
+
min_size: Minimum FFT size.
|
|
373
|
+
max_size: Maximum FFT size.
|
|
374
|
+
|
|
375
|
+
Returns:
|
|
376
|
+
Recommended FFT size (power of 2).
|
|
377
|
+
"""
|
|
378
|
+
# Next power of 2 at or above signal length
|
|
379
|
+
nfft = 2 ** int(np.ceil(np.log2(signal_length)))
|
|
380
|
+
|
|
381
|
+
# Clamp to reasonable range
|
|
382
|
+
nfft = max(min_size, min(nfft, max_size))
|
|
383
|
+
|
|
384
|
+
self._log(
|
|
385
|
+
f"FFT size: {nfft} (signal_length={signal_length}, "
|
|
386
|
+
f"next_power_of_2={2 ** int(np.ceil(np.log2(signal_length)))})"
|
|
387
|
+
)
|
|
388
|
+
|
|
389
|
+
return nfft # type: ignore[no-any-return]
|
|
390
|
+
|
|
391
|
+
def get_window_function(
|
|
392
|
+
self,
|
|
393
|
+
application: str = "general",
|
|
394
|
+
*,
|
|
395
|
+
dynamic_range_db: float = 60.0,
|
|
396
|
+
) -> str:
|
|
397
|
+
"""Get smart default window function.
|
|
398
|
+
|
|
399
|
+
Args:
|
|
400
|
+
application: Application type ('general', 'narrowband', 'transient').
|
|
401
|
+
dynamic_range_db: Required dynamic range in dB.
|
|
402
|
+
|
|
403
|
+
Returns:
|
|
404
|
+
Window function name.
|
|
405
|
+
"""
|
|
406
|
+
if application == "transient":
|
|
407
|
+
window = "boxcar"
|
|
408
|
+
reason = "rectangular window for transient analysis"
|
|
409
|
+
elif dynamic_range_db > 80:
|
|
410
|
+
window = "blackman-harris"
|
|
411
|
+
reason = f"high dynamic range ({dynamic_range_db} dB) requires Blackman-Harris"
|
|
412
|
+
elif dynamic_range_db > 60:
|
|
413
|
+
window = "blackman"
|
|
414
|
+
reason = f"moderate-high dynamic range ({dynamic_range_db} dB) uses Blackman"
|
|
415
|
+
elif application == "narrowband":
|
|
416
|
+
window = "flattop"
|
|
417
|
+
reason = "narrowband analysis uses flat-top for amplitude accuracy"
|
|
418
|
+
else:
|
|
419
|
+
window = "hann"
|
|
420
|
+
reason = "general purpose uses Hann window"
|
|
421
|
+
|
|
422
|
+
self._log(f"Window function: {window} ({reason})")
|
|
423
|
+
|
|
424
|
+
return window
|
|
425
|
+
|
|
426
|
+
def get_overlap(
|
|
427
|
+
self,
|
|
428
|
+
method: str = "welch",
|
|
429
|
+
window: str = "hann",
|
|
430
|
+
) -> float:
|
|
431
|
+
"""Get smart default overlap for windowed methods.
|
|
432
|
+
|
|
433
|
+
Args:
|
|
434
|
+
method: Analysis method ('welch', 'bartlett', 'stft').
|
|
435
|
+
window: Window function name.
|
|
436
|
+
|
|
437
|
+
Returns:
|
|
438
|
+
Overlap fraction (0-1).
|
|
439
|
+
"""
|
|
440
|
+
if method == "bartlett":
|
|
441
|
+
overlap = 0.0
|
|
442
|
+
reason = "Bartlett method uses no overlap"
|
|
443
|
+
elif window in ["hann", "hamming", "blackman"]:
|
|
444
|
+
overlap = 0.5
|
|
445
|
+
reason = f"{window} window typically uses 50% overlap"
|
|
446
|
+
elif window == "blackman-harris":
|
|
447
|
+
overlap = 0.75
|
|
448
|
+
reason = "Blackman-Harris uses 75% overlap for smoothness"
|
|
449
|
+
else:
|
|
450
|
+
overlap = 0.5
|
|
451
|
+
reason = "default 50% overlap for general windows"
|
|
452
|
+
|
|
453
|
+
self._log(f"Overlap: {overlap * 100:.0f}% ({reason})")
|
|
454
|
+
|
|
455
|
+
return overlap
|
|
456
|
+
|
|
457
|
+
def get_reference_levels(
|
|
458
|
+
self,
|
|
459
|
+
measurement: str = "rise_time",
|
|
460
|
+
) -> tuple[float, float]:
|
|
461
|
+
"""Get smart default reference levels for timing measurements.
|
|
462
|
+
|
|
463
|
+
Args:
|
|
464
|
+
measurement: Measurement type ('rise_time', 'fall_time', etc.).
|
|
465
|
+
|
|
466
|
+
Returns:
|
|
467
|
+
Tuple of (low_level, high_level) as fractions (0-1).
|
|
468
|
+
"""
|
|
469
|
+
if measurement in ["rise_time", "slew_rate"]:
|
|
470
|
+
levels = (0.1, 0.9)
|
|
471
|
+
reason = "10%-90% is IEEE 181-2011 standard for rise time"
|
|
472
|
+
elif measurement == "fall_time":
|
|
473
|
+
levels = (0.9, 0.1)
|
|
474
|
+
reason = "90%-10% for fall time per IEEE 181-2011"
|
|
475
|
+
elif measurement in ["propagation_delay", "setup_time", "hold_time"]:
|
|
476
|
+
levels = (0.5, 0.5)
|
|
477
|
+
reason = "50% threshold for timing measurements"
|
|
478
|
+
else:
|
|
479
|
+
levels = (0.1, 0.9)
|
|
480
|
+
reason = "default 10%-90% for general measurements"
|
|
481
|
+
|
|
482
|
+
self._log(f"Reference levels: {levels[0]:.0%}-{levels[1]:.0%} ({reason})")
|
|
483
|
+
|
|
484
|
+
return levels
|
|
485
|
+
|
|
486
|
+
def get_log_messages(self) -> list[str]:
|
|
487
|
+
"""Get all logged parameter selection messages.
|
|
488
|
+
|
|
489
|
+
Returns:
|
|
490
|
+
List of log messages.
|
|
491
|
+
"""
|
|
492
|
+
return self._log_buffer.copy()
|
|
493
|
+
|
|
494
|
+
def clear_log(self) -> None:
|
|
495
|
+
"""Clear the log buffer."""
|
|
496
|
+
self._log_buffer.clear()
|
|
497
|
+
|
|
498
|
+
|
|
499
|
+
__all__ = [
|
|
500
|
+
"DEFAULT_CONFIG",
|
|
501
|
+
"SmartDefaults",
|
|
502
|
+
"get_config_value",
|
|
503
|
+
"load_config",
|
|
504
|
+
"save_config",
|
|
505
|
+
"validate_config",
|
|
506
|
+
]
|