oscura 0.0.1__py3-none-any.whl → 0.1.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- oscura/__init__.py +813 -8
- oscura/__main__.py +392 -0
- oscura/analyzers/__init__.py +37 -0
- oscura/analyzers/digital/__init__.py +177 -0
- oscura/analyzers/digital/bus.py +691 -0
- oscura/analyzers/digital/clock.py +805 -0
- oscura/analyzers/digital/correlation.py +720 -0
- oscura/analyzers/digital/edges.py +632 -0
- oscura/analyzers/digital/extraction.py +413 -0
- oscura/analyzers/digital/quality.py +878 -0
- oscura/analyzers/digital/signal_quality.py +877 -0
- oscura/analyzers/digital/thresholds.py +708 -0
- oscura/analyzers/digital/timing.py +1104 -0
- oscura/analyzers/eye/__init__.py +46 -0
- oscura/analyzers/eye/diagram.py +434 -0
- oscura/analyzers/eye/metrics.py +555 -0
- oscura/analyzers/jitter/__init__.py +83 -0
- oscura/analyzers/jitter/ber.py +333 -0
- oscura/analyzers/jitter/decomposition.py +759 -0
- oscura/analyzers/jitter/measurements.py +413 -0
- oscura/analyzers/jitter/spectrum.py +220 -0
- oscura/analyzers/measurements.py +40 -0
- oscura/analyzers/packet/__init__.py +171 -0
- oscura/analyzers/packet/daq.py +1077 -0
- oscura/analyzers/packet/metrics.py +437 -0
- oscura/analyzers/packet/parser.py +327 -0
- oscura/analyzers/packet/payload.py +2156 -0
- oscura/analyzers/packet/payload_analysis.py +1312 -0
- oscura/analyzers/packet/payload_extraction.py +236 -0
- oscura/analyzers/packet/payload_patterns.py +670 -0
- oscura/analyzers/packet/stream.py +359 -0
- oscura/analyzers/patterns/__init__.py +266 -0
- oscura/analyzers/patterns/clustering.py +1036 -0
- oscura/analyzers/patterns/discovery.py +539 -0
- oscura/analyzers/patterns/learning.py +797 -0
- oscura/analyzers/patterns/matching.py +1091 -0
- oscura/analyzers/patterns/periodic.py +650 -0
- oscura/analyzers/patterns/sequences.py +767 -0
- oscura/analyzers/power/__init__.py +116 -0
- oscura/analyzers/power/ac_power.py +391 -0
- oscura/analyzers/power/basic.py +383 -0
- oscura/analyzers/power/conduction.py +314 -0
- oscura/analyzers/power/efficiency.py +297 -0
- oscura/analyzers/power/ripple.py +356 -0
- oscura/analyzers/power/soa.py +372 -0
- oscura/analyzers/power/switching.py +479 -0
- oscura/analyzers/protocol/__init__.py +150 -0
- oscura/analyzers/protocols/__init__.py +150 -0
- oscura/analyzers/protocols/base.py +500 -0
- oscura/analyzers/protocols/can.py +620 -0
- oscura/analyzers/protocols/can_fd.py +448 -0
- oscura/analyzers/protocols/flexray.py +405 -0
- oscura/analyzers/protocols/hdlc.py +399 -0
- oscura/analyzers/protocols/i2c.py +368 -0
- oscura/analyzers/protocols/i2s.py +296 -0
- oscura/analyzers/protocols/jtag.py +393 -0
- oscura/analyzers/protocols/lin.py +445 -0
- oscura/analyzers/protocols/manchester.py +333 -0
- oscura/analyzers/protocols/onewire.py +501 -0
- oscura/analyzers/protocols/spi.py +334 -0
- oscura/analyzers/protocols/swd.py +325 -0
- oscura/analyzers/protocols/uart.py +393 -0
- oscura/analyzers/protocols/usb.py +495 -0
- oscura/analyzers/signal_integrity/__init__.py +63 -0
- oscura/analyzers/signal_integrity/embedding.py +294 -0
- oscura/analyzers/signal_integrity/equalization.py +370 -0
- oscura/analyzers/signal_integrity/sparams.py +484 -0
- oscura/analyzers/spectral/__init__.py +53 -0
- oscura/analyzers/spectral/chunked.py +273 -0
- oscura/analyzers/spectral/chunked_fft.py +571 -0
- oscura/analyzers/spectral/chunked_wavelet.py +391 -0
- oscura/analyzers/spectral/fft.py +92 -0
- oscura/analyzers/statistical/__init__.py +250 -0
- oscura/analyzers/statistical/checksum.py +923 -0
- oscura/analyzers/statistical/chunked_corr.py +228 -0
- oscura/analyzers/statistical/classification.py +778 -0
- oscura/analyzers/statistical/entropy.py +1113 -0
- oscura/analyzers/statistical/ngrams.py +614 -0
- oscura/analyzers/statistics/__init__.py +119 -0
- oscura/analyzers/statistics/advanced.py +885 -0
- oscura/analyzers/statistics/basic.py +263 -0
- oscura/analyzers/statistics/correlation.py +630 -0
- oscura/analyzers/statistics/distribution.py +298 -0
- oscura/analyzers/statistics/outliers.py +463 -0
- oscura/analyzers/statistics/streaming.py +93 -0
- oscura/analyzers/statistics/trend.py +520 -0
- oscura/analyzers/validation.py +598 -0
- oscura/analyzers/waveform/__init__.py +36 -0
- oscura/analyzers/waveform/measurements.py +943 -0
- oscura/analyzers/waveform/measurements_with_uncertainty.py +371 -0
- oscura/analyzers/waveform/spectral.py +1689 -0
- oscura/analyzers/waveform/wavelets.py +298 -0
- oscura/api/__init__.py +62 -0
- oscura/api/dsl.py +538 -0
- oscura/api/fluent.py +571 -0
- oscura/api/operators.py +498 -0
- oscura/api/optimization.py +392 -0
- oscura/api/profiling.py +396 -0
- oscura/automotive/__init__.py +73 -0
- oscura/automotive/can/__init__.py +52 -0
- oscura/automotive/can/analysis.py +356 -0
- oscura/automotive/can/checksum.py +250 -0
- oscura/automotive/can/correlation.py +212 -0
- oscura/automotive/can/discovery.py +355 -0
- oscura/automotive/can/message_wrapper.py +375 -0
- oscura/automotive/can/models.py +385 -0
- oscura/automotive/can/patterns.py +381 -0
- oscura/automotive/can/session.py +452 -0
- oscura/automotive/can/state_machine.py +300 -0
- oscura/automotive/can/stimulus_response.py +461 -0
- oscura/automotive/dbc/__init__.py +15 -0
- oscura/automotive/dbc/generator.py +156 -0
- oscura/automotive/dbc/parser.py +146 -0
- oscura/automotive/dtc/__init__.py +30 -0
- oscura/automotive/dtc/database.py +3036 -0
- oscura/automotive/j1939/__init__.py +14 -0
- oscura/automotive/j1939/decoder.py +745 -0
- oscura/automotive/loaders/__init__.py +35 -0
- oscura/automotive/loaders/asc.py +98 -0
- oscura/automotive/loaders/blf.py +77 -0
- oscura/automotive/loaders/csv_can.py +136 -0
- oscura/automotive/loaders/dispatcher.py +136 -0
- oscura/automotive/loaders/mdf.py +331 -0
- oscura/automotive/loaders/pcap.py +132 -0
- oscura/automotive/obd/__init__.py +14 -0
- oscura/automotive/obd/decoder.py +707 -0
- oscura/automotive/uds/__init__.py +48 -0
- oscura/automotive/uds/decoder.py +265 -0
- oscura/automotive/uds/models.py +64 -0
- oscura/automotive/visualization.py +369 -0
- oscura/batch/__init__.py +55 -0
- oscura/batch/advanced.py +627 -0
- oscura/batch/aggregate.py +300 -0
- oscura/batch/analyze.py +139 -0
- oscura/batch/logging.py +487 -0
- oscura/batch/metrics.py +556 -0
- oscura/builders/__init__.py +41 -0
- oscura/builders/signal_builder.py +1131 -0
- oscura/cli/__init__.py +14 -0
- oscura/cli/batch.py +339 -0
- oscura/cli/characterize.py +273 -0
- oscura/cli/compare.py +775 -0
- oscura/cli/decode.py +551 -0
- oscura/cli/main.py +247 -0
- oscura/cli/shell.py +350 -0
- oscura/comparison/__init__.py +66 -0
- oscura/comparison/compare.py +397 -0
- oscura/comparison/golden.py +487 -0
- oscura/comparison/limits.py +391 -0
- oscura/comparison/mask.py +434 -0
- oscura/comparison/trace_diff.py +30 -0
- oscura/comparison/visualization.py +481 -0
- oscura/compliance/__init__.py +70 -0
- oscura/compliance/advanced.py +756 -0
- oscura/compliance/masks.py +363 -0
- oscura/compliance/reporting.py +483 -0
- oscura/compliance/testing.py +298 -0
- oscura/component/__init__.py +38 -0
- oscura/component/impedance.py +365 -0
- oscura/component/reactive.py +598 -0
- oscura/component/transmission_line.py +312 -0
- oscura/config/__init__.py +191 -0
- oscura/config/defaults.py +254 -0
- oscura/config/loader.py +348 -0
- oscura/config/memory.py +271 -0
- oscura/config/migration.py +458 -0
- oscura/config/pipeline.py +1077 -0
- oscura/config/preferences.py +530 -0
- oscura/config/protocol.py +875 -0
- oscura/config/schema.py +713 -0
- oscura/config/settings.py +420 -0
- oscura/config/thresholds.py +599 -0
- oscura/convenience.py +457 -0
- oscura/core/__init__.py +299 -0
- oscura/core/audit.py +457 -0
- oscura/core/backend_selector.py +405 -0
- oscura/core/cache.py +590 -0
- oscura/core/cancellation.py +439 -0
- oscura/core/confidence.py +225 -0
- oscura/core/config.py +506 -0
- oscura/core/correlation.py +216 -0
- oscura/core/cross_domain.py +422 -0
- oscura/core/debug.py +301 -0
- oscura/core/edge_cases.py +541 -0
- oscura/core/exceptions.py +535 -0
- oscura/core/gpu_backend.py +523 -0
- oscura/core/lazy.py +832 -0
- oscura/core/log_query.py +540 -0
- oscura/core/logging.py +931 -0
- oscura/core/logging_advanced.py +952 -0
- oscura/core/memoize.py +171 -0
- oscura/core/memory_check.py +274 -0
- oscura/core/memory_guard.py +290 -0
- oscura/core/memory_limits.py +336 -0
- oscura/core/memory_monitor.py +453 -0
- oscura/core/memory_progress.py +465 -0
- oscura/core/memory_warnings.py +315 -0
- oscura/core/numba_backend.py +362 -0
- oscura/core/performance.py +352 -0
- oscura/core/progress.py +524 -0
- oscura/core/provenance.py +358 -0
- oscura/core/results.py +331 -0
- oscura/core/types.py +504 -0
- oscura/core/uncertainty.py +383 -0
- oscura/discovery/__init__.py +52 -0
- oscura/discovery/anomaly_detector.py +672 -0
- oscura/discovery/auto_decoder.py +415 -0
- oscura/discovery/comparison.py +497 -0
- oscura/discovery/quality_validator.py +528 -0
- oscura/discovery/signal_detector.py +769 -0
- oscura/dsl/__init__.py +73 -0
- oscura/dsl/commands.py +246 -0
- oscura/dsl/interpreter.py +455 -0
- oscura/dsl/parser.py +689 -0
- oscura/dsl/repl.py +172 -0
- oscura/exceptions.py +59 -0
- oscura/exploratory/__init__.py +111 -0
- oscura/exploratory/error_recovery.py +642 -0
- oscura/exploratory/fuzzy.py +513 -0
- oscura/exploratory/fuzzy_advanced.py +786 -0
- oscura/exploratory/legacy.py +831 -0
- oscura/exploratory/parse.py +358 -0
- oscura/exploratory/recovery.py +275 -0
- oscura/exploratory/sync.py +382 -0
- oscura/exploratory/unknown.py +707 -0
- oscura/export/__init__.py +25 -0
- oscura/export/wireshark/README.md +265 -0
- oscura/export/wireshark/__init__.py +47 -0
- oscura/export/wireshark/generator.py +312 -0
- oscura/export/wireshark/lua_builder.py +159 -0
- oscura/export/wireshark/templates/dissector.lua.j2 +92 -0
- oscura/export/wireshark/type_mapping.py +165 -0
- oscura/export/wireshark/validator.py +105 -0
- oscura/exporters/__init__.py +94 -0
- oscura/exporters/csv.py +303 -0
- oscura/exporters/exporters.py +44 -0
- oscura/exporters/hdf5.py +219 -0
- oscura/exporters/html_export.py +701 -0
- oscura/exporters/json_export.py +291 -0
- oscura/exporters/markdown_export.py +367 -0
- oscura/exporters/matlab_export.py +354 -0
- oscura/exporters/npz_export.py +219 -0
- oscura/exporters/spice_export.py +210 -0
- oscura/extensibility/__init__.py +131 -0
- oscura/extensibility/docs.py +752 -0
- oscura/extensibility/extensions.py +1125 -0
- oscura/extensibility/logging.py +259 -0
- oscura/extensibility/measurements.py +485 -0
- oscura/extensibility/plugins.py +414 -0
- oscura/extensibility/registry.py +346 -0
- oscura/extensibility/templates.py +913 -0
- oscura/extensibility/validation.py +651 -0
- oscura/filtering/__init__.py +89 -0
- oscura/filtering/base.py +563 -0
- oscura/filtering/convenience.py +564 -0
- oscura/filtering/design.py +725 -0
- oscura/filtering/filters.py +32 -0
- oscura/filtering/introspection.py +605 -0
- oscura/guidance/__init__.py +24 -0
- oscura/guidance/recommender.py +429 -0
- oscura/guidance/wizard.py +518 -0
- oscura/inference/__init__.py +251 -0
- oscura/inference/active_learning/README.md +153 -0
- oscura/inference/active_learning/__init__.py +38 -0
- oscura/inference/active_learning/lstar.py +257 -0
- oscura/inference/active_learning/observation_table.py +230 -0
- oscura/inference/active_learning/oracle.py +78 -0
- oscura/inference/active_learning/teachers/__init__.py +15 -0
- oscura/inference/active_learning/teachers/simulator.py +192 -0
- oscura/inference/adaptive_tuning.py +453 -0
- oscura/inference/alignment.py +653 -0
- oscura/inference/bayesian.py +943 -0
- oscura/inference/binary.py +1016 -0
- oscura/inference/crc_reverse.py +711 -0
- oscura/inference/logic.py +288 -0
- oscura/inference/message_format.py +1305 -0
- oscura/inference/protocol.py +417 -0
- oscura/inference/protocol_dsl.py +1084 -0
- oscura/inference/protocol_library.py +1230 -0
- oscura/inference/sequences.py +809 -0
- oscura/inference/signal_intelligence.py +1509 -0
- oscura/inference/spectral.py +215 -0
- oscura/inference/state_machine.py +634 -0
- oscura/inference/stream.py +918 -0
- oscura/integrations/__init__.py +59 -0
- oscura/integrations/llm.py +1827 -0
- oscura/jupyter/__init__.py +32 -0
- oscura/jupyter/display.py +268 -0
- oscura/jupyter/magic.py +334 -0
- oscura/loaders/__init__.py +526 -0
- oscura/loaders/binary.py +69 -0
- oscura/loaders/configurable.py +1255 -0
- oscura/loaders/csv.py +26 -0
- oscura/loaders/csv_loader.py +473 -0
- oscura/loaders/hdf5.py +9 -0
- oscura/loaders/hdf5_loader.py +510 -0
- oscura/loaders/lazy.py +370 -0
- oscura/loaders/mmap_loader.py +583 -0
- oscura/loaders/numpy_loader.py +436 -0
- oscura/loaders/pcap.py +432 -0
- oscura/loaders/preprocessing.py +368 -0
- oscura/loaders/rigol.py +287 -0
- oscura/loaders/sigrok.py +321 -0
- oscura/loaders/tdms.py +367 -0
- oscura/loaders/tektronix.py +711 -0
- oscura/loaders/validation.py +584 -0
- oscura/loaders/vcd.py +464 -0
- oscura/loaders/wav.py +233 -0
- oscura/math/__init__.py +45 -0
- oscura/math/arithmetic.py +824 -0
- oscura/math/interpolation.py +413 -0
- oscura/onboarding/__init__.py +39 -0
- oscura/onboarding/help.py +498 -0
- oscura/onboarding/tutorials.py +405 -0
- oscura/onboarding/wizard.py +466 -0
- oscura/optimization/__init__.py +19 -0
- oscura/optimization/parallel.py +440 -0
- oscura/optimization/search.py +532 -0
- oscura/pipeline/__init__.py +43 -0
- oscura/pipeline/base.py +338 -0
- oscura/pipeline/composition.py +242 -0
- oscura/pipeline/parallel.py +448 -0
- oscura/pipeline/pipeline.py +375 -0
- oscura/pipeline/reverse_engineering.py +1119 -0
- oscura/plugins/__init__.py +122 -0
- oscura/plugins/base.py +272 -0
- oscura/plugins/cli.py +497 -0
- oscura/plugins/discovery.py +411 -0
- oscura/plugins/isolation.py +418 -0
- oscura/plugins/lifecycle.py +959 -0
- oscura/plugins/manager.py +493 -0
- oscura/plugins/registry.py +421 -0
- oscura/plugins/versioning.py +372 -0
- oscura/py.typed +0 -0
- oscura/quality/__init__.py +65 -0
- oscura/quality/ensemble.py +740 -0
- oscura/quality/explainer.py +338 -0
- oscura/quality/scoring.py +616 -0
- oscura/quality/warnings.py +456 -0
- oscura/reporting/__init__.py +248 -0
- oscura/reporting/advanced.py +1234 -0
- oscura/reporting/analyze.py +448 -0
- oscura/reporting/argument_preparer.py +596 -0
- oscura/reporting/auto_report.py +507 -0
- oscura/reporting/batch.py +615 -0
- oscura/reporting/chart_selection.py +223 -0
- oscura/reporting/comparison.py +330 -0
- oscura/reporting/config.py +615 -0
- oscura/reporting/content/__init__.py +39 -0
- oscura/reporting/content/executive.py +127 -0
- oscura/reporting/content/filtering.py +191 -0
- oscura/reporting/content/minimal.py +257 -0
- oscura/reporting/content/verbosity.py +162 -0
- oscura/reporting/core.py +508 -0
- oscura/reporting/core_formats/__init__.py +17 -0
- oscura/reporting/core_formats/multi_format.py +210 -0
- oscura/reporting/engine.py +836 -0
- oscura/reporting/export.py +366 -0
- oscura/reporting/formatting/__init__.py +129 -0
- oscura/reporting/formatting/emphasis.py +81 -0
- oscura/reporting/formatting/numbers.py +403 -0
- oscura/reporting/formatting/standards.py +55 -0
- oscura/reporting/formatting.py +466 -0
- oscura/reporting/html.py +578 -0
- oscura/reporting/index.py +590 -0
- oscura/reporting/multichannel.py +296 -0
- oscura/reporting/output.py +379 -0
- oscura/reporting/pdf.py +373 -0
- oscura/reporting/plots.py +731 -0
- oscura/reporting/pptx_export.py +360 -0
- oscura/reporting/renderers/__init__.py +11 -0
- oscura/reporting/renderers/pdf.py +94 -0
- oscura/reporting/sections.py +471 -0
- oscura/reporting/standards.py +680 -0
- oscura/reporting/summary_generator.py +368 -0
- oscura/reporting/tables.py +397 -0
- oscura/reporting/template_system.py +724 -0
- oscura/reporting/templates/__init__.py +15 -0
- oscura/reporting/templates/definition.py +205 -0
- oscura/reporting/templates/index.html +649 -0
- oscura/reporting/templates/index.md +173 -0
- oscura/schemas/__init__.py +158 -0
- oscura/schemas/bus_configuration.json +322 -0
- oscura/schemas/device_mapping.json +182 -0
- oscura/schemas/packet_format.json +418 -0
- oscura/schemas/protocol_definition.json +363 -0
- oscura/search/__init__.py +16 -0
- oscura/search/anomaly.py +292 -0
- oscura/search/context.py +149 -0
- oscura/search/pattern.py +160 -0
- oscura/session/__init__.py +34 -0
- oscura/session/annotations.py +289 -0
- oscura/session/history.py +313 -0
- oscura/session/session.py +445 -0
- oscura/streaming/__init__.py +43 -0
- oscura/streaming/chunked.py +611 -0
- oscura/streaming/progressive.py +393 -0
- oscura/streaming/realtime.py +622 -0
- oscura/testing/__init__.py +54 -0
- oscura/testing/synthetic.py +808 -0
- oscura/triggering/__init__.py +68 -0
- oscura/triggering/base.py +229 -0
- oscura/triggering/edge.py +353 -0
- oscura/triggering/pattern.py +344 -0
- oscura/triggering/pulse.py +581 -0
- oscura/triggering/window.py +453 -0
- oscura/ui/__init__.py +48 -0
- oscura/ui/formatters.py +526 -0
- oscura/ui/progressive_display.py +340 -0
- oscura/utils/__init__.py +99 -0
- oscura/utils/autodetect.py +338 -0
- oscura/utils/buffer.py +389 -0
- oscura/utils/lazy.py +407 -0
- oscura/utils/lazy_imports.py +147 -0
- oscura/utils/memory.py +836 -0
- oscura/utils/memory_advanced.py +1326 -0
- oscura/utils/memory_extensions.py +465 -0
- oscura/utils/progressive.py +352 -0
- oscura/utils/windowing.py +362 -0
- oscura/visualization/__init__.py +321 -0
- oscura/visualization/accessibility.py +526 -0
- oscura/visualization/annotations.py +374 -0
- oscura/visualization/axis_scaling.py +305 -0
- oscura/visualization/colors.py +453 -0
- oscura/visualization/digital.py +337 -0
- oscura/visualization/eye.py +420 -0
- oscura/visualization/histogram.py +281 -0
- oscura/visualization/interactive.py +858 -0
- oscura/visualization/jitter.py +702 -0
- oscura/visualization/keyboard.py +394 -0
- oscura/visualization/layout.py +365 -0
- oscura/visualization/optimization.py +1028 -0
- oscura/visualization/palettes.py +446 -0
- oscura/visualization/plot.py +92 -0
- oscura/visualization/power.py +290 -0
- oscura/visualization/power_extended.py +626 -0
- oscura/visualization/presets.py +467 -0
- oscura/visualization/protocols.py +932 -0
- oscura/visualization/render.py +207 -0
- oscura/visualization/rendering.py +444 -0
- oscura/visualization/reverse_engineering.py +791 -0
- oscura/visualization/signal_integrity.py +808 -0
- oscura/visualization/specialized.py +553 -0
- oscura/visualization/spectral.py +811 -0
- oscura/visualization/styles.py +381 -0
- oscura/visualization/thumbnails.py +311 -0
- oscura/visualization/time_axis.py +351 -0
- oscura/visualization/waveform.py +367 -0
- oscura/workflow/__init__.py +13 -0
- oscura/workflow/dag.py +377 -0
- oscura/workflows/__init__.py +58 -0
- oscura/workflows/compliance.py +280 -0
- oscura/workflows/digital.py +272 -0
- oscura/workflows/multi_trace.py +502 -0
- oscura/workflows/power.py +178 -0
- oscura/workflows/protocol.py +492 -0
- oscura/workflows/reverse_engineering.py +639 -0
- oscura/workflows/signal_integrity.py +227 -0
- oscura-0.1.1.dist-info/METADATA +300 -0
- oscura-0.1.1.dist-info/RECORD +463 -0
- oscura-0.1.1.dist-info/entry_points.txt +2 -0
- {oscura-0.0.1.dist-info → oscura-0.1.1.dist-info}/licenses/LICENSE +1 -1
- oscura-0.0.1.dist-info/METADATA +0 -63
- oscura-0.0.1.dist-info/RECORD +0 -5
- {oscura-0.0.1.dist-info → oscura-0.1.1.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
"""Interpolation and resampling operations for Oscura.
|
|
2
|
+
|
|
3
|
+
This module provides interpolation, resampling, and trace alignment
|
|
4
|
+
functions for waveform data.
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
Example:
|
|
8
|
+
>>> from oscura.math import resample, align_traces
|
|
9
|
+
>>> resampled = resample(trace, new_sample_rate=1e6)
|
|
10
|
+
>>> aligned = align_traces(trace1, trace2)
|
|
11
|
+
|
|
12
|
+
References:
|
|
13
|
+
IEEE 181-2011: Standard for Transitional Waveform Definitions
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import warnings
|
|
19
|
+
from typing import TYPE_CHECKING, Literal
|
|
20
|
+
|
|
21
|
+
import numpy as np
|
|
22
|
+
from scipy import interpolate as sp_interp
|
|
23
|
+
from scipy import signal as sp_signal
|
|
24
|
+
|
|
25
|
+
from oscura.core.exceptions import InsufficientDataError
|
|
26
|
+
from oscura.core.types import TraceMetadata, WaveformTrace
|
|
27
|
+
|
|
28
|
+
if TYPE_CHECKING:
|
|
29
|
+
from numpy.typing import NDArray
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def interpolate(
|
|
33
|
+
trace: WaveformTrace,
|
|
34
|
+
new_time: NDArray[np.float64],
|
|
35
|
+
*,
|
|
36
|
+
method: Literal["linear", "cubic", "nearest", "zero"] = "linear",
|
|
37
|
+
fill_value: float | tuple[float, float] = np.nan,
|
|
38
|
+
channel_name: str | None = None,
|
|
39
|
+
) -> WaveformTrace:
|
|
40
|
+
"""Interpolate trace to new time points.
|
|
41
|
+
|
|
42
|
+
Interpolates the waveform data to a new set of time points using
|
|
43
|
+
the specified interpolation method.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
trace: Input trace.
|
|
47
|
+
new_time: New time points in seconds.
|
|
48
|
+
method: Interpolation method:
|
|
49
|
+
- "linear": Linear interpolation (default)
|
|
50
|
+
- "cubic": Cubic spline interpolation
|
|
51
|
+
- "nearest": Nearest neighbor
|
|
52
|
+
- "zero": Zero-order hold (step function)
|
|
53
|
+
fill_value: Value for points outside original range.
|
|
54
|
+
Can be a single value or (below, above) tuple.
|
|
55
|
+
channel_name: Name for the result trace (optional).
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
Interpolated WaveformTrace at new time points.
|
|
59
|
+
|
|
60
|
+
Raises:
|
|
61
|
+
InsufficientDataError: If trace has insufficient samples.
|
|
62
|
+
ValueError: If interpolation method is unknown.
|
|
63
|
+
|
|
64
|
+
Example:
|
|
65
|
+
>>> new_time = np.linspace(0, 1e-3, 2000)
|
|
66
|
+
>>> interpolated = interpolate(trace, new_time, method="cubic")
|
|
67
|
+
"""
|
|
68
|
+
if len(trace.data) < 2:
|
|
69
|
+
raise InsufficientDataError(
|
|
70
|
+
"Need at least 2 samples for interpolation",
|
|
71
|
+
required=2,
|
|
72
|
+
available=len(trace.data),
|
|
73
|
+
analysis_type="interpolate",
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
original_time = trace.time_vector
|
|
77
|
+
data = trace.data.astype(np.float64)
|
|
78
|
+
|
|
79
|
+
# Create interpolator
|
|
80
|
+
if method == "linear":
|
|
81
|
+
interp_func = sp_interp.interp1d(
|
|
82
|
+
original_time,
|
|
83
|
+
data,
|
|
84
|
+
kind="linear",
|
|
85
|
+
bounds_error=False,
|
|
86
|
+
fill_value=fill_value,
|
|
87
|
+
)
|
|
88
|
+
elif method == "cubic":
|
|
89
|
+
interp_func = sp_interp.interp1d(
|
|
90
|
+
original_time,
|
|
91
|
+
data,
|
|
92
|
+
kind="cubic",
|
|
93
|
+
bounds_error=False,
|
|
94
|
+
fill_value=fill_value,
|
|
95
|
+
)
|
|
96
|
+
elif method == "nearest":
|
|
97
|
+
interp_func = sp_interp.interp1d(
|
|
98
|
+
original_time,
|
|
99
|
+
data,
|
|
100
|
+
kind="nearest",
|
|
101
|
+
bounds_error=False,
|
|
102
|
+
fill_value=fill_value,
|
|
103
|
+
)
|
|
104
|
+
elif method == "zero":
|
|
105
|
+
interp_func = sp_interp.interp1d(
|
|
106
|
+
original_time,
|
|
107
|
+
data,
|
|
108
|
+
kind="zero",
|
|
109
|
+
bounds_error=False,
|
|
110
|
+
fill_value=fill_value,
|
|
111
|
+
)
|
|
112
|
+
else:
|
|
113
|
+
raise ValueError(f"Unknown interpolation method: {method}")
|
|
114
|
+
|
|
115
|
+
# Interpolate
|
|
116
|
+
result_data = interp_func(new_time)
|
|
117
|
+
|
|
118
|
+
# Calculate new sample rate from time points
|
|
119
|
+
if len(new_time) > 1:
|
|
120
|
+
new_sample_rate = 1.0 / np.mean(np.diff(new_time))
|
|
121
|
+
else:
|
|
122
|
+
new_sample_rate = trace.metadata.sample_rate
|
|
123
|
+
|
|
124
|
+
new_metadata = TraceMetadata(
|
|
125
|
+
sample_rate=new_sample_rate,
|
|
126
|
+
vertical_scale=trace.metadata.vertical_scale,
|
|
127
|
+
vertical_offset=trace.metadata.vertical_offset,
|
|
128
|
+
acquisition_time=trace.metadata.acquisition_time,
|
|
129
|
+
trigger_info=trace.metadata.trigger_info,
|
|
130
|
+
source_file=trace.metadata.source_file,
|
|
131
|
+
channel_name=channel_name or f"{trace.metadata.channel_name or 'trace'}_interp",
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
return WaveformTrace(data=result_data.astype(np.float64), metadata=new_metadata)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def resample(
|
|
138
|
+
trace: WaveformTrace,
|
|
139
|
+
new_sample_rate: float | None = None,
|
|
140
|
+
*,
|
|
141
|
+
num_samples: int | None = None,
|
|
142
|
+
method: Literal["fft", "polyphase", "interp"] = "fft",
|
|
143
|
+
anti_alias: bool = True,
|
|
144
|
+
channel_name: str | None = None,
|
|
145
|
+
) -> WaveformTrace:
|
|
146
|
+
"""Resample trace to new sample rate or number of samples.
|
|
147
|
+
|
|
148
|
+
Resamples the waveform to a different sample rate using high-quality
|
|
149
|
+
resampling algorithms. Applies anti-aliasing filter when downsampling.
|
|
150
|
+
|
|
151
|
+
Args:
|
|
152
|
+
trace: Input trace.
|
|
153
|
+
new_sample_rate: Target sample rate in Hz. Mutually exclusive
|
|
154
|
+
with num_samples.
|
|
155
|
+
num_samples: Target number of samples. Mutually exclusive with
|
|
156
|
+
new_sample_rate.
|
|
157
|
+
method: Resampling method:
|
|
158
|
+
- "fft": FFT-based resampling (default, best quality)
|
|
159
|
+
- "polyphase": Polyphase filter resampling (efficient)
|
|
160
|
+
- "interp": Linear interpolation (fastest)
|
|
161
|
+
anti_alias: Apply anti-aliasing filter before downsampling.
|
|
162
|
+
channel_name: Name for the result trace (optional).
|
|
163
|
+
|
|
164
|
+
Returns:
|
|
165
|
+
Resampled WaveformTrace.
|
|
166
|
+
|
|
167
|
+
Raises:
|
|
168
|
+
ValueError: If neither or both rate/samples specified.
|
|
169
|
+
InsufficientDataError: If trace has insufficient samples.
|
|
170
|
+
|
|
171
|
+
Example:
|
|
172
|
+
>>> upsampled = resample(trace, new_sample_rate=2e9)
|
|
173
|
+
>>> downsampled = resample(trace, num_samples=1000)
|
|
174
|
+
|
|
175
|
+
References:
|
|
176
|
+
MEM-012 (downsampling for memory management)
|
|
177
|
+
"""
|
|
178
|
+
if (new_sample_rate is None) == (num_samples is None):
|
|
179
|
+
raise ValueError("Specify exactly one of new_sample_rate or num_samples")
|
|
180
|
+
|
|
181
|
+
if len(trace.data) < 2:
|
|
182
|
+
raise InsufficientDataError(
|
|
183
|
+
"Need at least 2 samples for resampling",
|
|
184
|
+
required=2,
|
|
185
|
+
available=len(trace.data),
|
|
186
|
+
analysis_type="resample",
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
data = trace.data.astype(np.float64)
|
|
190
|
+
original_rate = trace.metadata.sample_rate
|
|
191
|
+
original_samples = len(data)
|
|
192
|
+
|
|
193
|
+
# Calculate target parameters
|
|
194
|
+
if new_sample_rate is not None:
|
|
195
|
+
target_rate = new_sample_rate
|
|
196
|
+
target_samples = round(original_samples * target_rate / original_rate)
|
|
197
|
+
else:
|
|
198
|
+
target_samples = num_samples # type: ignore[assignment]
|
|
199
|
+
target_rate = original_rate * target_samples / original_samples
|
|
200
|
+
|
|
201
|
+
if target_samples < 1:
|
|
202
|
+
raise ValueError("Target number of samples must be at least 1")
|
|
203
|
+
|
|
204
|
+
# REQ: API-019 - Validate Nyquist criterion when downsampling
|
|
205
|
+
if target_rate < original_rate:
|
|
206
|
+
# Estimate maximum frequency using FFT
|
|
207
|
+
fft_data = np.fft.rfft(data)
|
|
208
|
+
fft_freqs = np.fft.rfftfreq(len(data), 1 / original_rate)
|
|
209
|
+
# Find frequency with 90% of max power as max frequency
|
|
210
|
+
power = np.abs(fft_data) ** 2
|
|
211
|
+
power_threshold = 0.01 * np.max(power) # 1% of max power
|
|
212
|
+
significant_freqs = fft_freqs[power > power_threshold]
|
|
213
|
+
if len(significant_freqs) > 0:
|
|
214
|
+
max_frequency = np.max(significant_freqs)
|
|
215
|
+
nyquist_required = 2 * max_frequency
|
|
216
|
+
if target_rate < nyquist_required:
|
|
217
|
+
warnings.warn(
|
|
218
|
+
f"Downsampling to {target_rate:.2e} Hz violates Nyquist criterion. "
|
|
219
|
+
f"Maximum signal frequency is ~{max_frequency:.2e} Hz, "
|
|
220
|
+
f"requiring ≥{nyquist_required:.2e} Hz sample rate. "
|
|
221
|
+
f"Aliasing may occur.",
|
|
222
|
+
UserWarning,
|
|
223
|
+
stacklevel=2,
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
# Check if downsampling and apply anti-alias filter
|
|
227
|
+
if anti_alias and target_samples < original_samples:
|
|
228
|
+
# Lowpass filter at Nyquist of new rate
|
|
229
|
+
nyquist = target_rate / 2
|
|
230
|
+
cutoff = nyquist / original_rate * 2 # Normalized frequency
|
|
231
|
+
if cutoff < 1.0:
|
|
232
|
+
# Design lowpass filter
|
|
233
|
+
b, a = sp_signal.butter(8, min(cutoff * 0.9, 0.99), btype="low")
|
|
234
|
+
data = sp_signal.filtfilt(b, a, data)
|
|
235
|
+
|
|
236
|
+
# Resample
|
|
237
|
+
if method == "fft":
|
|
238
|
+
result_data = sp_signal.resample(data, target_samples)
|
|
239
|
+
elif method == "polyphase":
|
|
240
|
+
# Find rational approximation for polyphase resampling
|
|
241
|
+
from fractions import Fraction
|
|
242
|
+
|
|
243
|
+
ratio = Fraction(target_samples, original_samples).limit_denominator(1000)
|
|
244
|
+
up, down = ratio.numerator, ratio.denominator
|
|
245
|
+
result_data = sp_signal.resample_poly(data, up, down)
|
|
246
|
+
# Trim to exact length
|
|
247
|
+
result_data = result_data[:target_samples]
|
|
248
|
+
elif method == "interp":
|
|
249
|
+
# Simple interpolation
|
|
250
|
+
old_time = np.arange(original_samples) / original_rate
|
|
251
|
+
new_time = np.arange(target_samples) / target_rate
|
|
252
|
+
result_data = np.interp(new_time, old_time, data)
|
|
253
|
+
else:
|
|
254
|
+
raise ValueError(f"Unknown resampling method: {method}")
|
|
255
|
+
|
|
256
|
+
new_metadata = TraceMetadata(
|
|
257
|
+
sample_rate=target_rate,
|
|
258
|
+
vertical_scale=trace.metadata.vertical_scale,
|
|
259
|
+
vertical_offset=trace.metadata.vertical_offset,
|
|
260
|
+
acquisition_time=trace.metadata.acquisition_time,
|
|
261
|
+
trigger_info=trace.metadata.trigger_info,
|
|
262
|
+
source_file=trace.metadata.source_file,
|
|
263
|
+
channel_name=channel_name or f"{trace.metadata.channel_name or 'trace'}_resampled",
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
return WaveformTrace(data=result_data.astype(np.float64), metadata=new_metadata)
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def align_traces(
|
|
270
|
+
trace1: WaveformTrace,
|
|
271
|
+
trace2: WaveformTrace,
|
|
272
|
+
*,
|
|
273
|
+
method: Literal["interpolate", "resample"] = "interpolate",
|
|
274
|
+
reference: Literal["first", "second", "higher"] = "higher",
|
|
275
|
+
channel_names: tuple[str | None, str | None] | None = None,
|
|
276
|
+
) -> tuple[WaveformTrace, WaveformTrace]:
|
|
277
|
+
"""Align two traces to have the same sample rate and length.
|
|
278
|
+
|
|
279
|
+
Adjusts two traces to be compatible for arithmetic operations by
|
|
280
|
+
resampling to a common sample rate and time base.
|
|
281
|
+
|
|
282
|
+
Args:
|
|
283
|
+
trace1: First trace.
|
|
284
|
+
trace2: Second trace.
|
|
285
|
+
method: Alignment method:
|
|
286
|
+
- "interpolate": Interpolate to common time points
|
|
287
|
+
- "resample": Resample to common rate
|
|
288
|
+
reference: Which trace to use as reference:
|
|
289
|
+
- "first": Use trace1's sample rate
|
|
290
|
+
- "second": Use trace2's sample rate
|
|
291
|
+
- "higher": Use the higher sample rate (default)
|
|
292
|
+
channel_names: Optional names for the aligned traces.
|
|
293
|
+
|
|
294
|
+
Returns:
|
|
295
|
+
Tuple of (aligned_trace1, aligned_trace2) with matching parameters.
|
|
296
|
+
|
|
297
|
+
Example:
|
|
298
|
+
>>> aligned1, aligned2 = align_traces(trace1, trace2)
|
|
299
|
+
>>> diff = subtract(aligned1, aligned2)
|
|
300
|
+
"""
|
|
301
|
+
rate1 = trace1.metadata.sample_rate
|
|
302
|
+
rate2 = trace2.metadata.sample_rate
|
|
303
|
+
|
|
304
|
+
# Determine reference sample rate
|
|
305
|
+
if reference == "first":
|
|
306
|
+
target_rate = rate1
|
|
307
|
+
elif reference == "second":
|
|
308
|
+
target_rate = rate2
|
|
309
|
+
else: # "higher"
|
|
310
|
+
target_rate = max(rate1, rate2)
|
|
311
|
+
|
|
312
|
+
# Determine time span (use overlapping portion)
|
|
313
|
+
t1_end = len(trace1.data) / rate1
|
|
314
|
+
t2_end = len(trace2.data) / rate2
|
|
315
|
+
common_end = min(t1_end, t2_end)
|
|
316
|
+
|
|
317
|
+
# Calculate number of samples
|
|
318
|
+
num_samples = round(common_end * target_rate)
|
|
319
|
+
|
|
320
|
+
# Create common time vector
|
|
321
|
+
common_time = np.arange(num_samples) / target_rate
|
|
322
|
+
|
|
323
|
+
name1 = channel_names[0] if channel_names else None
|
|
324
|
+
name2 = channel_names[1] if channel_names else None
|
|
325
|
+
|
|
326
|
+
if method == "interpolate":
|
|
327
|
+
# Interpolate both traces to common time points
|
|
328
|
+
aligned1 = interpolate(trace1, common_time, channel_name=name1)
|
|
329
|
+
aligned2 = interpolate(trace2, common_time, channel_name=name2)
|
|
330
|
+
else: # "resample"
|
|
331
|
+
# Resample both to common rate
|
|
332
|
+
aligned1 = resample(trace1, num_samples=num_samples, channel_name=name1)
|
|
333
|
+
aligned2 = resample(trace2, num_samples=num_samples, channel_name=name2)
|
|
334
|
+
|
|
335
|
+
return aligned1, aligned2
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
def downsample(
|
|
339
|
+
trace: WaveformTrace,
|
|
340
|
+
factor: int,
|
|
341
|
+
*,
|
|
342
|
+
anti_alias: bool = True,
|
|
343
|
+
method: Literal["decimate", "average", "max", "min"] = "decimate",
|
|
344
|
+
channel_name: str | None = None,
|
|
345
|
+
) -> WaveformTrace:
|
|
346
|
+
"""Downsample trace by an integer factor.
|
|
347
|
+
|
|
348
|
+
Reduces the sample rate by keeping every Nth sample (decimate)
|
|
349
|
+
or by aggregating N samples (average/max/min).
|
|
350
|
+
|
|
351
|
+
Args:
|
|
352
|
+
trace: Input trace.
|
|
353
|
+
factor: Downsampling factor (must be >= 1).
|
|
354
|
+
anti_alias: Apply anti-aliasing filter before decimation.
|
|
355
|
+
method: Downsampling method:
|
|
356
|
+
- "decimate": Keep every Nth sample (default)
|
|
357
|
+
- "average": Average every N samples
|
|
358
|
+
- "max": Maximum of every N samples
|
|
359
|
+
- "min": Minimum of every N samples
|
|
360
|
+
channel_name: Name for the result trace (optional).
|
|
361
|
+
|
|
362
|
+
Returns:
|
|
363
|
+
Downsampled WaveformTrace.
|
|
364
|
+
|
|
365
|
+
Raises:
|
|
366
|
+
ValueError: If factor is less than 1 or method is unknown.
|
|
367
|
+
|
|
368
|
+
Example:
|
|
369
|
+
>>> small = downsample(large_trace, factor=10)
|
|
370
|
+
|
|
371
|
+
References:
|
|
372
|
+
MEM-012 (memory management)
|
|
373
|
+
"""
|
|
374
|
+
if factor < 1:
|
|
375
|
+
raise ValueError(f"Factor must be >= 1, got {factor}")
|
|
376
|
+
|
|
377
|
+
if factor == 1:
|
|
378
|
+
return trace # No change needed
|
|
379
|
+
|
|
380
|
+
data = trace.data.astype(np.float64)
|
|
381
|
+
|
|
382
|
+
if anti_alias and method == "decimate":
|
|
383
|
+
# Apply anti-aliasing filter
|
|
384
|
+
nyquist = 0.5 / factor
|
|
385
|
+
b, a = sp_signal.butter(8, min(nyquist * 0.9, 0.99), btype="low")
|
|
386
|
+
data = sp_signal.filtfilt(b, a, data)
|
|
387
|
+
|
|
388
|
+
# Truncate to multiple of factor
|
|
389
|
+
n = len(data) // factor * factor
|
|
390
|
+
data = data[:n]
|
|
391
|
+
|
|
392
|
+
if method == "decimate":
|
|
393
|
+
result_data = data[::factor]
|
|
394
|
+
elif method == "average":
|
|
395
|
+
result_data = data.reshape(-1, factor).mean(axis=1)
|
|
396
|
+
elif method == "max":
|
|
397
|
+
result_data = data.reshape(-1, factor).max(axis=1)
|
|
398
|
+
elif method == "min":
|
|
399
|
+
result_data = data.reshape(-1, factor).min(axis=1)
|
|
400
|
+
else:
|
|
401
|
+
raise ValueError(f"Unknown method: {method}")
|
|
402
|
+
|
|
403
|
+
new_metadata = TraceMetadata(
|
|
404
|
+
sample_rate=trace.metadata.sample_rate / factor,
|
|
405
|
+
vertical_scale=trace.metadata.vertical_scale,
|
|
406
|
+
vertical_offset=trace.metadata.vertical_offset,
|
|
407
|
+
acquisition_time=trace.metadata.acquisition_time,
|
|
408
|
+
trigger_info=trace.metadata.trigger_info,
|
|
409
|
+
source_file=trace.metadata.source_file,
|
|
410
|
+
channel_name=channel_name or f"{trace.metadata.channel_name or 'trace'}_ds{factor}",
|
|
411
|
+
)
|
|
412
|
+
|
|
413
|
+
return WaveformTrace(data=result_data, metadata=new_metadata)
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""Onboarding and help system for Oscura.
|
|
2
|
+
|
|
3
|
+
This package provides interactive tutorials, context-sensitive help,
|
|
4
|
+
and guided analysis features for new users.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from oscura.onboarding.help import (
|
|
8
|
+
explain_result,
|
|
9
|
+
get_example,
|
|
10
|
+
get_help,
|
|
11
|
+
suggest_commands,
|
|
12
|
+
)
|
|
13
|
+
from oscura.onboarding.tutorials import (
|
|
14
|
+
Tutorial,
|
|
15
|
+
TutorialStep,
|
|
16
|
+
get_tutorial,
|
|
17
|
+
list_tutorials,
|
|
18
|
+
run_tutorial,
|
|
19
|
+
)
|
|
20
|
+
from oscura.onboarding.wizard import (
|
|
21
|
+
AnalysisWizard,
|
|
22
|
+
WizardStep,
|
|
23
|
+
run_wizard,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
__all__ = [
|
|
27
|
+
"AnalysisWizard",
|
|
28
|
+
"Tutorial",
|
|
29
|
+
"TutorialStep",
|
|
30
|
+
"WizardStep",
|
|
31
|
+
"explain_result",
|
|
32
|
+
"get_example",
|
|
33
|
+
"get_help",
|
|
34
|
+
"get_tutorial",
|
|
35
|
+
"list_tutorials",
|
|
36
|
+
"run_tutorial",
|
|
37
|
+
"run_wizard",
|
|
38
|
+
"suggest_commands",
|
|
39
|
+
]
|