oscura 0.0.1__py3-none-any.whl → 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- oscura/__init__.py +813 -8
- oscura/__main__.py +392 -0
- oscura/analyzers/__init__.py +37 -0
- oscura/analyzers/digital/__init__.py +177 -0
- oscura/analyzers/digital/bus.py +691 -0
- oscura/analyzers/digital/clock.py +805 -0
- oscura/analyzers/digital/correlation.py +720 -0
- oscura/analyzers/digital/edges.py +632 -0
- oscura/analyzers/digital/extraction.py +413 -0
- oscura/analyzers/digital/quality.py +878 -0
- oscura/analyzers/digital/signal_quality.py +877 -0
- oscura/analyzers/digital/thresholds.py +708 -0
- oscura/analyzers/digital/timing.py +1104 -0
- oscura/analyzers/eye/__init__.py +46 -0
- oscura/analyzers/eye/diagram.py +434 -0
- oscura/analyzers/eye/metrics.py +555 -0
- oscura/analyzers/jitter/__init__.py +83 -0
- oscura/analyzers/jitter/ber.py +333 -0
- oscura/analyzers/jitter/decomposition.py +759 -0
- oscura/analyzers/jitter/measurements.py +413 -0
- oscura/analyzers/jitter/spectrum.py +220 -0
- oscura/analyzers/measurements.py +40 -0
- oscura/analyzers/packet/__init__.py +171 -0
- oscura/analyzers/packet/daq.py +1077 -0
- oscura/analyzers/packet/metrics.py +437 -0
- oscura/analyzers/packet/parser.py +327 -0
- oscura/analyzers/packet/payload.py +2156 -0
- oscura/analyzers/packet/payload_analysis.py +1312 -0
- oscura/analyzers/packet/payload_extraction.py +236 -0
- oscura/analyzers/packet/payload_patterns.py +670 -0
- oscura/analyzers/packet/stream.py +359 -0
- oscura/analyzers/patterns/__init__.py +266 -0
- oscura/analyzers/patterns/clustering.py +1036 -0
- oscura/analyzers/patterns/discovery.py +539 -0
- oscura/analyzers/patterns/learning.py +797 -0
- oscura/analyzers/patterns/matching.py +1091 -0
- oscura/analyzers/patterns/periodic.py +650 -0
- oscura/analyzers/patterns/sequences.py +767 -0
- oscura/analyzers/power/__init__.py +116 -0
- oscura/analyzers/power/ac_power.py +391 -0
- oscura/analyzers/power/basic.py +383 -0
- oscura/analyzers/power/conduction.py +314 -0
- oscura/analyzers/power/efficiency.py +297 -0
- oscura/analyzers/power/ripple.py +356 -0
- oscura/analyzers/power/soa.py +372 -0
- oscura/analyzers/power/switching.py +479 -0
- oscura/analyzers/protocol/__init__.py +150 -0
- oscura/analyzers/protocols/__init__.py +150 -0
- oscura/analyzers/protocols/base.py +500 -0
- oscura/analyzers/protocols/can.py +620 -0
- oscura/analyzers/protocols/can_fd.py +448 -0
- oscura/analyzers/protocols/flexray.py +405 -0
- oscura/analyzers/protocols/hdlc.py +399 -0
- oscura/analyzers/protocols/i2c.py +368 -0
- oscura/analyzers/protocols/i2s.py +296 -0
- oscura/analyzers/protocols/jtag.py +393 -0
- oscura/analyzers/protocols/lin.py +445 -0
- oscura/analyzers/protocols/manchester.py +333 -0
- oscura/analyzers/protocols/onewire.py +501 -0
- oscura/analyzers/protocols/spi.py +334 -0
- oscura/analyzers/protocols/swd.py +325 -0
- oscura/analyzers/protocols/uart.py +393 -0
- oscura/analyzers/protocols/usb.py +495 -0
- oscura/analyzers/signal_integrity/__init__.py +63 -0
- oscura/analyzers/signal_integrity/embedding.py +294 -0
- oscura/analyzers/signal_integrity/equalization.py +370 -0
- oscura/analyzers/signal_integrity/sparams.py +484 -0
- oscura/analyzers/spectral/__init__.py +53 -0
- oscura/analyzers/spectral/chunked.py +273 -0
- oscura/analyzers/spectral/chunked_fft.py +571 -0
- oscura/analyzers/spectral/chunked_wavelet.py +391 -0
- oscura/analyzers/spectral/fft.py +92 -0
- oscura/analyzers/statistical/__init__.py +250 -0
- oscura/analyzers/statistical/checksum.py +923 -0
- oscura/analyzers/statistical/chunked_corr.py +228 -0
- oscura/analyzers/statistical/classification.py +778 -0
- oscura/analyzers/statistical/entropy.py +1113 -0
- oscura/analyzers/statistical/ngrams.py +614 -0
- oscura/analyzers/statistics/__init__.py +119 -0
- oscura/analyzers/statistics/advanced.py +885 -0
- oscura/analyzers/statistics/basic.py +263 -0
- oscura/analyzers/statistics/correlation.py +630 -0
- oscura/analyzers/statistics/distribution.py +298 -0
- oscura/analyzers/statistics/outliers.py +463 -0
- oscura/analyzers/statistics/streaming.py +93 -0
- oscura/analyzers/statistics/trend.py +520 -0
- oscura/analyzers/validation.py +598 -0
- oscura/analyzers/waveform/__init__.py +36 -0
- oscura/analyzers/waveform/measurements.py +943 -0
- oscura/analyzers/waveform/measurements_with_uncertainty.py +371 -0
- oscura/analyzers/waveform/spectral.py +1689 -0
- oscura/analyzers/waveform/wavelets.py +298 -0
- oscura/api/__init__.py +62 -0
- oscura/api/dsl.py +538 -0
- oscura/api/fluent.py +571 -0
- oscura/api/operators.py +498 -0
- oscura/api/optimization.py +392 -0
- oscura/api/profiling.py +396 -0
- oscura/automotive/__init__.py +73 -0
- oscura/automotive/can/__init__.py +52 -0
- oscura/automotive/can/analysis.py +356 -0
- oscura/automotive/can/checksum.py +250 -0
- oscura/automotive/can/correlation.py +212 -0
- oscura/automotive/can/discovery.py +355 -0
- oscura/automotive/can/message_wrapper.py +375 -0
- oscura/automotive/can/models.py +385 -0
- oscura/automotive/can/patterns.py +381 -0
- oscura/automotive/can/session.py +452 -0
- oscura/automotive/can/state_machine.py +300 -0
- oscura/automotive/can/stimulus_response.py +461 -0
- oscura/automotive/dbc/__init__.py +15 -0
- oscura/automotive/dbc/generator.py +156 -0
- oscura/automotive/dbc/parser.py +146 -0
- oscura/automotive/dtc/__init__.py +30 -0
- oscura/automotive/dtc/database.py +3036 -0
- oscura/automotive/j1939/__init__.py +14 -0
- oscura/automotive/j1939/decoder.py +745 -0
- oscura/automotive/loaders/__init__.py +35 -0
- oscura/automotive/loaders/asc.py +98 -0
- oscura/automotive/loaders/blf.py +77 -0
- oscura/automotive/loaders/csv_can.py +136 -0
- oscura/automotive/loaders/dispatcher.py +136 -0
- oscura/automotive/loaders/mdf.py +331 -0
- oscura/automotive/loaders/pcap.py +132 -0
- oscura/automotive/obd/__init__.py +14 -0
- oscura/automotive/obd/decoder.py +707 -0
- oscura/automotive/uds/__init__.py +48 -0
- oscura/automotive/uds/decoder.py +265 -0
- oscura/automotive/uds/models.py +64 -0
- oscura/automotive/visualization.py +369 -0
- oscura/batch/__init__.py +55 -0
- oscura/batch/advanced.py +627 -0
- oscura/batch/aggregate.py +300 -0
- oscura/batch/analyze.py +139 -0
- oscura/batch/logging.py +487 -0
- oscura/batch/metrics.py +556 -0
- oscura/builders/__init__.py +41 -0
- oscura/builders/signal_builder.py +1131 -0
- oscura/cli/__init__.py +14 -0
- oscura/cli/batch.py +339 -0
- oscura/cli/characterize.py +273 -0
- oscura/cli/compare.py +775 -0
- oscura/cli/decode.py +551 -0
- oscura/cli/main.py +247 -0
- oscura/cli/shell.py +350 -0
- oscura/comparison/__init__.py +66 -0
- oscura/comparison/compare.py +397 -0
- oscura/comparison/golden.py +487 -0
- oscura/comparison/limits.py +391 -0
- oscura/comparison/mask.py +434 -0
- oscura/comparison/trace_diff.py +30 -0
- oscura/comparison/visualization.py +481 -0
- oscura/compliance/__init__.py +70 -0
- oscura/compliance/advanced.py +756 -0
- oscura/compliance/masks.py +363 -0
- oscura/compliance/reporting.py +483 -0
- oscura/compliance/testing.py +298 -0
- oscura/component/__init__.py +38 -0
- oscura/component/impedance.py +365 -0
- oscura/component/reactive.py +598 -0
- oscura/component/transmission_line.py +312 -0
- oscura/config/__init__.py +191 -0
- oscura/config/defaults.py +254 -0
- oscura/config/loader.py +348 -0
- oscura/config/memory.py +271 -0
- oscura/config/migration.py +458 -0
- oscura/config/pipeline.py +1077 -0
- oscura/config/preferences.py +530 -0
- oscura/config/protocol.py +875 -0
- oscura/config/schema.py +713 -0
- oscura/config/settings.py +420 -0
- oscura/config/thresholds.py +599 -0
- oscura/convenience.py +457 -0
- oscura/core/__init__.py +299 -0
- oscura/core/audit.py +457 -0
- oscura/core/backend_selector.py +405 -0
- oscura/core/cache.py +590 -0
- oscura/core/cancellation.py +439 -0
- oscura/core/confidence.py +225 -0
- oscura/core/config.py +506 -0
- oscura/core/correlation.py +216 -0
- oscura/core/cross_domain.py +422 -0
- oscura/core/debug.py +301 -0
- oscura/core/edge_cases.py +541 -0
- oscura/core/exceptions.py +535 -0
- oscura/core/gpu_backend.py +523 -0
- oscura/core/lazy.py +832 -0
- oscura/core/log_query.py +540 -0
- oscura/core/logging.py +931 -0
- oscura/core/logging_advanced.py +952 -0
- oscura/core/memoize.py +171 -0
- oscura/core/memory_check.py +274 -0
- oscura/core/memory_guard.py +290 -0
- oscura/core/memory_limits.py +336 -0
- oscura/core/memory_monitor.py +453 -0
- oscura/core/memory_progress.py +465 -0
- oscura/core/memory_warnings.py +315 -0
- oscura/core/numba_backend.py +362 -0
- oscura/core/performance.py +352 -0
- oscura/core/progress.py +524 -0
- oscura/core/provenance.py +358 -0
- oscura/core/results.py +331 -0
- oscura/core/types.py +504 -0
- oscura/core/uncertainty.py +383 -0
- oscura/discovery/__init__.py +52 -0
- oscura/discovery/anomaly_detector.py +672 -0
- oscura/discovery/auto_decoder.py +415 -0
- oscura/discovery/comparison.py +497 -0
- oscura/discovery/quality_validator.py +528 -0
- oscura/discovery/signal_detector.py +769 -0
- oscura/dsl/__init__.py +73 -0
- oscura/dsl/commands.py +246 -0
- oscura/dsl/interpreter.py +455 -0
- oscura/dsl/parser.py +689 -0
- oscura/dsl/repl.py +172 -0
- oscura/exceptions.py +59 -0
- oscura/exploratory/__init__.py +111 -0
- oscura/exploratory/error_recovery.py +642 -0
- oscura/exploratory/fuzzy.py +513 -0
- oscura/exploratory/fuzzy_advanced.py +786 -0
- oscura/exploratory/legacy.py +831 -0
- oscura/exploratory/parse.py +358 -0
- oscura/exploratory/recovery.py +275 -0
- oscura/exploratory/sync.py +382 -0
- oscura/exploratory/unknown.py +707 -0
- oscura/export/__init__.py +25 -0
- oscura/export/wireshark/README.md +265 -0
- oscura/export/wireshark/__init__.py +47 -0
- oscura/export/wireshark/generator.py +312 -0
- oscura/export/wireshark/lua_builder.py +159 -0
- oscura/export/wireshark/templates/dissector.lua.j2 +92 -0
- oscura/export/wireshark/type_mapping.py +165 -0
- oscura/export/wireshark/validator.py +105 -0
- oscura/exporters/__init__.py +94 -0
- oscura/exporters/csv.py +303 -0
- oscura/exporters/exporters.py +44 -0
- oscura/exporters/hdf5.py +219 -0
- oscura/exporters/html_export.py +701 -0
- oscura/exporters/json_export.py +291 -0
- oscura/exporters/markdown_export.py +367 -0
- oscura/exporters/matlab_export.py +354 -0
- oscura/exporters/npz_export.py +219 -0
- oscura/exporters/spice_export.py +210 -0
- oscura/extensibility/__init__.py +131 -0
- oscura/extensibility/docs.py +752 -0
- oscura/extensibility/extensions.py +1125 -0
- oscura/extensibility/logging.py +259 -0
- oscura/extensibility/measurements.py +485 -0
- oscura/extensibility/plugins.py +414 -0
- oscura/extensibility/registry.py +346 -0
- oscura/extensibility/templates.py +913 -0
- oscura/extensibility/validation.py +651 -0
- oscura/filtering/__init__.py +89 -0
- oscura/filtering/base.py +563 -0
- oscura/filtering/convenience.py +564 -0
- oscura/filtering/design.py +725 -0
- oscura/filtering/filters.py +32 -0
- oscura/filtering/introspection.py +605 -0
- oscura/guidance/__init__.py +24 -0
- oscura/guidance/recommender.py +429 -0
- oscura/guidance/wizard.py +518 -0
- oscura/inference/__init__.py +251 -0
- oscura/inference/active_learning/README.md +153 -0
- oscura/inference/active_learning/__init__.py +38 -0
- oscura/inference/active_learning/lstar.py +257 -0
- oscura/inference/active_learning/observation_table.py +230 -0
- oscura/inference/active_learning/oracle.py +78 -0
- oscura/inference/active_learning/teachers/__init__.py +15 -0
- oscura/inference/active_learning/teachers/simulator.py +192 -0
- oscura/inference/adaptive_tuning.py +453 -0
- oscura/inference/alignment.py +653 -0
- oscura/inference/bayesian.py +943 -0
- oscura/inference/binary.py +1016 -0
- oscura/inference/crc_reverse.py +711 -0
- oscura/inference/logic.py +288 -0
- oscura/inference/message_format.py +1305 -0
- oscura/inference/protocol.py +417 -0
- oscura/inference/protocol_dsl.py +1084 -0
- oscura/inference/protocol_library.py +1230 -0
- oscura/inference/sequences.py +809 -0
- oscura/inference/signal_intelligence.py +1509 -0
- oscura/inference/spectral.py +215 -0
- oscura/inference/state_machine.py +634 -0
- oscura/inference/stream.py +918 -0
- oscura/integrations/__init__.py +59 -0
- oscura/integrations/llm.py +1827 -0
- oscura/jupyter/__init__.py +32 -0
- oscura/jupyter/display.py +268 -0
- oscura/jupyter/magic.py +334 -0
- oscura/loaders/__init__.py +526 -0
- oscura/loaders/binary.py +69 -0
- oscura/loaders/configurable.py +1255 -0
- oscura/loaders/csv.py +26 -0
- oscura/loaders/csv_loader.py +473 -0
- oscura/loaders/hdf5.py +9 -0
- oscura/loaders/hdf5_loader.py +510 -0
- oscura/loaders/lazy.py +370 -0
- oscura/loaders/mmap_loader.py +583 -0
- oscura/loaders/numpy_loader.py +436 -0
- oscura/loaders/pcap.py +432 -0
- oscura/loaders/preprocessing.py +368 -0
- oscura/loaders/rigol.py +287 -0
- oscura/loaders/sigrok.py +321 -0
- oscura/loaders/tdms.py +367 -0
- oscura/loaders/tektronix.py +711 -0
- oscura/loaders/validation.py +584 -0
- oscura/loaders/vcd.py +464 -0
- oscura/loaders/wav.py +233 -0
- oscura/math/__init__.py +45 -0
- oscura/math/arithmetic.py +824 -0
- oscura/math/interpolation.py +413 -0
- oscura/onboarding/__init__.py +39 -0
- oscura/onboarding/help.py +498 -0
- oscura/onboarding/tutorials.py +405 -0
- oscura/onboarding/wizard.py +466 -0
- oscura/optimization/__init__.py +19 -0
- oscura/optimization/parallel.py +440 -0
- oscura/optimization/search.py +532 -0
- oscura/pipeline/__init__.py +43 -0
- oscura/pipeline/base.py +338 -0
- oscura/pipeline/composition.py +242 -0
- oscura/pipeline/parallel.py +448 -0
- oscura/pipeline/pipeline.py +375 -0
- oscura/pipeline/reverse_engineering.py +1119 -0
- oscura/plugins/__init__.py +122 -0
- oscura/plugins/base.py +272 -0
- oscura/plugins/cli.py +497 -0
- oscura/plugins/discovery.py +411 -0
- oscura/plugins/isolation.py +418 -0
- oscura/plugins/lifecycle.py +959 -0
- oscura/plugins/manager.py +493 -0
- oscura/plugins/registry.py +421 -0
- oscura/plugins/versioning.py +372 -0
- oscura/py.typed +0 -0
- oscura/quality/__init__.py +65 -0
- oscura/quality/ensemble.py +740 -0
- oscura/quality/explainer.py +338 -0
- oscura/quality/scoring.py +616 -0
- oscura/quality/warnings.py +456 -0
- oscura/reporting/__init__.py +248 -0
- oscura/reporting/advanced.py +1234 -0
- oscura/reporting/analyze.py +448 -0
- oscura/reporting/argument_preparer.py +596 -0
- oscura/reporting/auto_report.py +507 -0
- oscura/reporting/batch.py +615 -0
- oscura/reporting/chart_selection.py +223 -0
- oscura/reporting/comparison.py +330 -0
- oscura/reporting/config.py +615 -0
- oscura/reporting/content/__init__.py +39 -0
- oscura/reporting/content/executive.py +127 -0
- oscura/reporting/content/filtering.py +191 -0
- oscura/reporting/content/minimal.py +257 -0
- oscura/reporting/content/verbosity.py +162 -0
- oscura/reporting/core.py +508 -0
- oscura/reporting/core_formats/__init__.py +17 -0
- oscura/reporting/core_formats/multi_format.py +210 -0
- oscura/reporting/engine.py +836 -0
- oscura/reporting/export.py +366 -0
- oscura/reporting/formatting/__init__.py +129 -0
- oscura/reporting/formatting/emphasis.py +81 -0
- oscura/reporting/formatting/numbers.py +403 -0
- oscura/reporting/formatting/standards.py +55 -0
- oscura/reporting/formatting.py +466 -0
- oscura/reporting/html.py +578 -0
- oscura/reporting/index.py +590 -0
- oscura/reporting/multichannel.py +296 -0
- oscura/reporting/output.py +379 -0
- oscura/reporting/pdf.py +373 -0
- oscura/reporting/plots.py +731 -0
- oscura/reporting/pptx_export.py +360 -0
- oscura/reporting/renderers/__init__.py +11 -0
- oscura/reporting/renderers/pdf.py +94 -0
- oscura/reporting/sections.py +471 -0
- oscura/reporting/standards.py +680 -0
- oscura/reporting/summary_generator.py +368 -0
- oscura/reporting/tables.py +397 -0
- oscura/reporting/template_system.py +724 -0
- oscura/reporting/templates/__init__.py +15 -0
- oscura/reporting/templates/definition.py +205 -0
- oscura/reporting/templates/index.html +649 -0
- oscura/reporting/templates/index.md +173 -0
- oscura/schemas/__init__.py +158 -0
- oscura/schemas/bus_configuration.json +322 -0
- oscura/schemas/device_mapping.json +182 -0
- oscura/schemas/packet_format.json +418 -0
- oscura/schemas/protocol_definition.json +363 -0
- oscura/search/__init__.py +16 -0
- oscura/search/anomaly.py +292 -0
- oscura/search/context.py +149 -0
- oscura/search/pattern.py +160 -0
- oscura/session/__init__.py +34 -0
- oscura/session/annotations.py +289 -0
- oscura/session/history.py +313 -0
- oscura/session/session.py +445 -0
- oscura/streaming/__init__.py +43 -0
- oscura/streaming/chunked.py +611 -0
- oscura/streaming/progressive.py +393 -0
- oscura/streaming/realtime.py +622 -0
- oscura/testing/__init__.py +54 -0
- oscura/testing/synthetic.py +808 -0
- oscura/triggering/__init__.py +68 -0
- oscura/triggering/base.py +229 -0
- oscura/triggering/edge.py +353 -0
- oscura/triggering/pattern.py +344 -0
- oscura/triggering/pulse.py +581 -0
- oscura/triggering/window.py +453 -0
- oscura/ui/__init__.py +48 -0
- oscura/ui/formatters.py +526 -0
- oscura/ui/progressive_display.py +340 -0
- oscura/utils/__init__.py +99 -0
- oscura/utils/autodetect.py +338 -0
- oscura/utils/buffer.py +389 -0
- oscura/utils/lazy.py +407 -0
- oscura/utils/lazy_imports.py +147 -0
- oscura/utils/memory.py +836 -0
- oscura/utils/memory_advanced.py +1326 -0
- oscura/utils/memory_extensions.py +465 -0
- oscura/utils/progressive.py +352 -0
- oscura/utils/windowing.py +362 -0
- oscura/visualization/__init__.py +321 -0
- oscura/visualization/accessibility.py +526 -0
- oscura/visualization/annotations.py +374 -0
- oscura/visualization/axis_scaling.py +305 -0
- oscura/visualization/colors.py +453 -0
- oscura/visualization/digital.py +337 -0
- oscura/visualization/eye.py +420 -0
- oscura/visualization/histogram.py +281 -0
- oscura/visualization/interactive.py +858 -0
- oscura/visualization/jitter.py +702 -0
- oscura/visualization/keyboard.py +394 -0
- oscura/visualization/layout.py +365 -0
- oscura/visualization/optimization.py +1028 -0
- oscura/visualization/palettes.py +446 -0
- oscura/visualization/plot.py +92 -0
- oscura/visualization/power.py +290 -0
- oscura/visualization/power_extended.py +626 -0
- oscura/visualization/presets.py +467 -0
- oscura/visualization/protocols.py +932 -0
- oscura/visualization/render.py +207 -0
- oscura/visualization/rendering.py +444 -0
- oscura/visualization/reverse_engineering.py +791 -0
- oscura/visualization/signal_integrity.py +808 -0
- oscura/visualization/specialized.py +553 -0
- oscura/visualization/spectral.py +811 -0
- oscura/visualization/styles.py +381 -0
- oscura/visualization/thumbnails.py +311 -0
- oscura/visualization/time_axis.py +351 -0
- oscura/visualization/waveform.py +367 -0
- oscura/workflow/__init__.py +13 -0
- oscura/workflow/dag.py +377 -0
- oscura/workflows/__init__.py +58 -0
- oscura/workflows/compliance.py +280 -0
- oscura/workflows/digital.py +272 -0
- oscura/workflows/multi_trace.py +502 -0
- oscura/workflows/power.py +178 -0
- oscura/workflows/protocol.py +492 -0
- oscura/workflows/reverse_engineering.py +639 -0
- oscura/workflows/signal_integrity.py +227 -0
- oscura-0.1.0.dist-info/METADATA +300 -0
- oscura-0.1.0.dist-info/RECORD +463 -0
- oscura-0.1.0.dist-info/entry_points.txt +2 -0
- {oscura-0.0.1.dist-info → oscura-0.1.0.dist-info}/licenses/LICENSE +1 -1
- oscura-0.0.1.dist-info/METADATA +0 -63
- oscura-0.0.1.dist-info/RECORD +0 -5
- {oscura-0.0.1.dist-info → oscura-0.1.0.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,564 @@
|
|
|
1
|
+
"""Convenience filtering functions for TraceKit.
|
|
2
|
+
|
|
3
|
+
Provides simple one-call filter functions for common operations like
|
|
4
|
+
moving average, median filter, Savitzky-Golay smoothing, and matched
|
|
5
|
+
filtering.
|
|
6
|
+
|
|
7
|
+
Example:
|
|
8
|
+
>>> from oscura.filtering.convenience import low_pass, moving_average
|
|
9
|
+
>>> filtered = low_pass(trace, cutoff=1e6)
|
|
10
|
+
>>> smoothed = moving_average(trace, window_size=11)
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from typing import TYPE_CHECKING, Any, Literal
|
|
16
|
+
|
|
17
|
+
import numpy as np
|
|
18
|
+
from scipy import ndimage, signal
|
|
19
|
+
|
|
20
|
+
from oscura.core.exceptions import AnalysisError
|
|
21
|
+
from oscura.core.types import WaveformTrace
|
|
22
|
+
from oscura.filtering.design import (
|
|
23
|
+
BandPassFilter,
|
|
24
|
+
BandStopFilter,
|
|
25
|
+
HighPassFilter,
|
|
26
|
+
LowPassFilter,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
if TYPE_CHECKING:
|
|
30
|
+
from numpy.typing import NDArray
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def low_pass(
|
|
34
|
+
trace: WaveformTrace,
|
|
35
|
+
cutoff: float,
|
|
36
|
+
*,
|
|
37
|
+
order: int = 4,
|
|
38
|
+
filter_type: Literal[
|
|
39
|
+
"butterworth", "chebyshev1", "chebyshev2", "bessel", "elliptic"
|
|
40
|
+
] = "butterworth",
|
|
41
|
+
) -> WaveformTrace:
|
|
42
|
+
"""Apply low-pass filter to trace.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
trace: Input waveform trace.
|
|
46
|
+
cutoff: Cutoff frequency in Hz.
|
|
47
|
+
order: Filter order (default 4).
|
|
48
|
+
filter_type: Type of filter (default Butterworth).
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
Filtered waveform trace.
|
|
52
|
+
|
|
53
|
+
Example:
|
|
54
|
+
>>> filtered = low_pass(trace, cutoff=1e6)
|
|
55
|
+
"""
|
|
56
|
+
filt = LowPassFilter(
|
|
57
|
+
cutoff=cutoff,
|
|
58
|
+
sample_rate=trace.metadata.sample_rate,
|
|
59
|
+
order=order,
|
|
60
|
+
filter_type=filter_type,
|
|
61
|
+
)
|
|
62
|
+
result = filt.apply(trace)
|
|
63
|
+
if isinstance(result, WaveformTrace):
|
|
64
|
+
return result
|
|
65
|
+
return result.trace
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def high_pass(
|
|
69
|
+
trace: WaveformTrace,
|
|
70
|
+
cutoff: float,
|
|
71
|
+
*,
|
|
72
|
+
order: int = 4,
|
|
73
|
+
filter_type: Literal[
|
|
74
|
+
"butterworth", "chebyshev1", "chebyshev2", "bessel", "elliptic"
|
|
75
|
+
] = "butterworth",
|
|
76
|
+
) -> WaveformTrace:
|
|
77
|
+
"""Apply high-pass filter to trace.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
trace: Input waveform trace.
|
|
81
|
+
cutoff: Cutoff frequency in Hz.
|
|
82
|
+
order: Filter order (default 4).
|
|
83
|
+
filter_type: Type of filter (default Butterworth).
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
Filtered waveform trace.
|
|
87
|
+
|
|
88
|
+
Example:
|
|
89
|
+
>>> filtered = high_pass(trace, cutoff=100) # Remove DC and low frequencies
|
|
90
|
+
"""
|
|
91
|
+
filt = HighPassFilter(
|
|
92
|
+
cutoff=cutoff,
|
|
93
|
+
sample_rate=trace.metadata.sample_rate,
|
|
94
|
+
order=order,
|
|
95
|
+
filter_type=filter_type,
|
|
96
|
+
)
|
|
97
|
+
result = filt.apply(trace)
|
|
98
|
+
if isinstance(result, WaveformTrace):
|
|
99
|
+
return result
|
|
100
|
+
return result.trace
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def band_pass(
|
|
104
|
+
trace: WaveformTrace,
|
|
105
|
+
low: float,
|
|
106
|
+
high: float,
|
|
107
|
+
*,
|
|
108
|
+
order: int = 4,
|
|
109
|
+
filter_type: Literal[
|
|
110
|
+
"butterworth", "chebyshev1", "chebyshev2", "bessel", "elliptic"
|
|
111
|
+
] = "butterworth",
|
|
112
|
+
) -> WaveformTrace:
|
|
113
|
+
"""Apply band-pass filter to trace.
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
trace: Input waveform trace.
|
|
117
|
+
low: Lower cutoff frequency in Hz.
|
|
118
|
+
high: Upper cutoff frequency in Hz.
|
|
119
|
+
order: Filter order (default 4).
|
|
120
|
+
filter_type: Type of filter (default Butterworth).
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
Filtered waveform trace.
|
|
124
|
+
|
|
125
|
+
Example:
|
|
126
|
+
>>> filtered = band_pass(trace, low=1e3, high=10e3)
|
|
127
|
+
"""
|
|
128
|
+
filt = BandPassFilter(
|
|
129
|
+
low=low,
|
|
130
|
+
high=high,
|
|
131
|
+
sample_rate=trace.metadata.sample_rate,
|
|
132
|
+
order=order,
|
|
133
|
+
filter_type=filter_type,
|
|
134
|
+
)
|
|
135
|
+
result = filt.apply(trace)
|
|
136
|
+
if isinstance(result, WaveformTrace):
|
|
137
|
+
return result
|
|
138
|
+
return result.trace
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def band_stop(
|
|
142
|
+
trace: WaveformTrace,
|
|
143
|
+
low: float,
|
|
144
|
+
high: float,
|
|
145
|
+
*,
|
|
146
|
+
order: int = 4,
|
|
147
|
+
filter_type: Literal[
|
|
148
|
+
"butterworth", "chebyshev1", "chebyshev2", "bessel", "elliptic"
|
|
149
|
+
] = "butterworth",
|
|
150
|
+
) -> WaveformTrace:
|
|
151
|
+
"""Apply band-stop (notch) filter to trace.
|
|
152
|
+
|
|
153
|
+
Args:
|
|
154
|
+
trace: Input waveform trace.
|
|
155
|
+
low: Lower cutoff frequency in Hz.
|
|
156
|
+
high: Upper cutoff frequency in Hz.
|
|
157
|
+
order: Filter order (default 4).
|
|
158
|
+
filter_type: Type of filter (default Butterworth).
|
|
159
|
+
|
|
160
|
+
Returns:
|
|
161
|
+
Filtered waveform trace.
|
|
162
|
+
|
|
163
|
+
Example:
|
|
164
|
+
>>> filtered = band_stop(trace, low=59, high=61) # Remove 60 Hz
|
|
165
|
+
"""
|
|
166
|
+
filt = BandStopFilter(
|
|
167
|
+
low=low,
|
|
168
|
+
high=high,
|
|
169
|
+
sample_rate=trace.metadata.sample_rate,
|
|
170
|
+
order=order,
|
|
171
|
+
filter_type=filter_type,
|
|
172
|
+
)
|
|
173
|
+
result = filt.apply(trace)
|
|
174
|
+
if isinstance(result, WaveformTrace):
|
|
175
|
+
return result
|
|
176
|
+
return result.trace
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def notch_filter(
|
|
180
|
+
trace: WaveformTrace,
|
|
181
|
+
freq: float,
|
|
182
|
+
*,
|
|
183
|
+
q_factor: float = 30.0,
|
|
184
|
+
) -> WaveformTrace:
|
|
185
|
+
"""Apply narrow notch filter at specified frequency.
|
|
186
|
+
|
|
187
|
+
Uses a band-stop Butterworth filter with bandwidth determined by Q factor.
|
|
188
|
+
Bandwidth (Hz) = freq / Q
|
|
189
|
+
|
|
190
|
+
Args:
|
|
191
|
+
trace: Input waveform trace.
|
|
192
|
+
freq: Center frequency to notch out in Hz.
|
|
193
|
+
q_factor: Quality factor (higher = narrower notch). Default 30.
|
|
194
|
+
|
|
195
|
+
Returns:
|
|
196
|
+
Filtered waveform trace.
|
|
197
|
+
|
|
198
|
+
Raises:
|
|
199
|
+
AnalysisError: If notch frequency exceeds Nyquist frequency.
|
|
200
|
+
|
|
201
|
+
Example:
|
|
202
|
+
>>> filtered = notch_filter(trace, freq=60, q_factor=30) # Remove 60 Hz line noise
|
|
203
|
+
"""
|
|
204
|
+
sample_rate = trace.metadata.sample_rate
|
|
205
|
+
|
|
206
|
+
if freq >= sample_rate / 2:
|
|
207
|
+
raise AnalysisError(
|
|
208
|
+
f"Notch frequency {freq} Hz must be less than Nyquist {sample_rate / 2} Hz"
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
# Calculate bandwidth from Q factor: BW = f0 / Q
|
|
212
|
+
bandwidth = freq / q_factor
|
|
213
|
+
|
|
214
|
+
# Design band-stop filter centered at freq with calculated bandwidth
|
|
215
|
+
# Use 4th order Butterworth for good attenuation
|
|
216
|
+
low = max(freq - bandwidth / 2, 0.1) # Avoid zero frequency
|
|
217
|
+
high = min(freq + bandwidth / 2, sample_rate / 2 - 1) # Stay below Nyquist
|
|
218
|
+
|
|
219
|
+
# Normalize frequencies
|
|
220
|
+
wn = [low / (sample_rate / 2), high / (sample_rate / 2)]
|
|
221
|
+
|
|
222
|
+
# Design bandstop filter
|
|
223
|
+
sos = signal.butter(4, wn, btype="bandstop", output="sos")
|
|
224
|
+
|
|
225
|
+
# Apply zero-phase filter
|
|
226
|
+
filtered_data = signal.sosfiltfilt(sos, trace.data)
|
|
227
|
+
|
|
228
|
+
return WaveformTrace(
|
|
229
|
+
data=filtered_data.astype(np.float64),
|
|
230
|
+
metadata=trace.metadata,
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def moving_average(
|
|
235
|
+
trace: WaveformTrace,
|
|
236
|
+
window_size: int,
|
|
237
|
+
*,
|
|
238
|
+
mode: Literal["same", "valid", "full"] = "same",
|
|
239
|
+
) -> WaveformTrace:
|
|
240
|
+
"""Apply moving average filter.
|
|
241
|
+
|
|
242
|
+
Simple FIR filter with uniform weights.
|
|
243
|
+
|
|
244
|
+
Args:
|
|
245
|
+
trace: Input waveform trace.
|
|
246
|
+
window_size: Number of samples in averaging window (must be odd for 'same' mode).
|
|
247
|
+
mode: Convolution mode - "same" preserves length.
|
|
248
|
+
|
|
249
|
+
Returns:
|
|
250
|
+
Filtered waveform trace.
|
|
251
|
+
|
|
252
|
+
Raises:
|
|
253
|
+
AnalysisError: If window_size is not positive or exceeds data length.
|
|
254
|
+
|
|
255
|
+
Example:
|
|
256
|
+
>>> smoothed = moving_average(trace, window_size=11)
|
|
257
|
+
"""
|
|
258
|
+
if window_size < 1:
|
|
259
|
+
raise AnalysisError(f"Window size must be positive, got {window_size}")
|
|
260
|
+
|
|
261
|
+
if window_size > len(trace.data):
|
|
262
|
+
raise AnalysisError(f"Window size {window_size} exceeds data length {len(trace.data)}")
|
|
263
|
+
|
|
264
|
+
kernel = np.ones(window_size) / window_size
|
|
265
|
+
filtered_data = np.convolve(trace.data, kernel, mode=mode)
|
|
266
|
+
|
|
267
|
+
return WaveformTrace(
|
|
268
|
+
data=filtered_data.astype(np.float64),
|
|
269
|
+
metadata=trace.metadata,
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def median_filter(
|
|
274
|
+
trace: WaveformTrace,
|
|
275
|
+
kernel_size: int,
|
|
276
|
+
) -> WaveformTrace:
|
|
277
|
+
"""Apply median filter for spike/impulse noise removal.
|
|
278
|
+
|
|
279
|
+
Non-linear filter that preserves edges while removing outliers.
|
|
280
|
+
|
|
281
|
+
Args:
|
|
282
|
+
trace: Input waveform trace.
|
|
283
|
+
kernel_size: Size of the median filter kernel (must be odd).
|
|
284
|
+
|
|
285
|
+
Returns:
|
|
286
|
+
Filtered waveform trace.
|
|
287
|
+
|
|
288
|
+
Raises:
|
|
289
|
+
AnalysisError: If kernel_size is not positive or not odd.
|
|
290
|
+
|
|
291
|
+
Example:
|
|
292
|
+
>>> cleaned = median_filter(trace, kernel_size=5) # Remove impulse noise
|
|
293
|
+
"""
|
|
294
|
+
if kernel_size < 1:
|
|
295
|
+
raise AnalysisError(f"Kernel size must be positive, got {kernel_size}")
|
|
296
|
+
|
|
297
|
+
if kernel_size % 2 == 0:
|
|
298
|
+
raise AnalysisError(f"Kernel size must be odd, got {kernel_size}")
|
|
299
|
+
|
|
300
|
+
filtered_data = ndimage.median_filter(trace.data, size=kernel_size)
|
|
301
|
+
|
|
302
|
+
return WaveformTrace(
|
|
303
|
+
data=filtered_data.astype(np.float64),
|
|
304
|
+
metadata=trace.metadata,
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
def savgol_filter(
|
|
309
|
+
trace: WaveformTrace,
|
|
310
|
+
window_length: int,
|
|
311
|
+
polyorder: int,
|
|
312
|
+
*,
|
|
313
|
+
deriv: int = 0,
|
|
314
|
+
) -> WaveformTrace:
|
|
315
|
+
"""Apply Savitzky-Golay smoothing filter.
|
|
316
|
+
|
|
317
|
+
Smooths data while preserving higher moments (peaks, etc.) better
|
|
318
|
+
than simple moving average.
|
|
319
|
+
|
|
320
|
+
Args:
|
|
321
|
+
trace: Input waveform trace.
|
|
322
|
+
window_length: Length of filter window (must be odd and > polyorder).
|
|
323
|
+
polyorder: Order of polynomial used in fitting.
|
|
324
|
+
deriv: Derivative order (0 = smoothing, 1 = first derivative, etc.).
|
|
325
|
+
|
|
326
|
+
Returns:
|
|
327
|
+
Filtered waveform trace.
|
|
328
|
+
|
|
329
|
+
Raises:
|
|
330
|
+
AnalysisError: If window_length is not odd or polyorder is invalid.
|
|
331
|
+
|
|
332
|
+
Example:
|
|
333
|
+
>>> smoothed = savgol_filter(trace, window_length=11, polyorder=3)
|
|
334
|
+
"""
|
|
335
|
+
if window_length % 2 == 0:
|
|
336
|
+
raise AnalysisError(f"Window length must be odd, got {window_length}")
|
|
337
|
+
|
|
338
|
+
if polyorder >= window_length:
|
|
339
|
+
raise AnalysisError(
|
|
340
|
+
f"Polynomial order {polyorder} must be less than window length {window_length}"
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
filtered_data = signal.savgol_filter(trace.data, window_length, polyorder, deriv=deriv)
|
|
344
|
+
|
|
345
|
+
return WaveformTrace(
|
|
346
|
+
data=filtered_data.astype(np.float64),
|
|
347
|
+
metadata=trace.metadata,
|
|
348
|
+
)
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
def matched_filter(
|
|
352
|
+
trace: WaveformTrace,
|
|
353
|
+
template: NDArray[np.floating[Any]],
|
|
354
|
+
*,
|
|
355
|
+
normalize: bool = True,
|
|
356
|
+
) -> WaveformTrace:
|
|
357
|
+
"""Apply matched filter for pulse detection.
|
|
358
|
+
|
|
359
|
+
Correlates the input with a known pulse template to detect
|
|
360
|
+
occurrences of that pulse shape.
|
|
361
|
+
|
|
362
|
+
Args:
|
|
363
|
+
trace: Input waveform trace.
|
|
364
|
+
template: Template pulse to match.
|
|
365
|
+
normalize: If True, normalize template for unit energy.
|
|
366
|
+
|
|
367
|
+
Returns:
|
|
368
|
+
Matched filter output trace. Peaks indicate template matches.
|
|
369
|
+
|
|
370
|
+
Raises:
|
|
371
|
+
AnalysisError: If template is empty or exceeds data length.
|
|
372
|
+
|
|
373
|
+
Example:
|
|
374
|
+
>>> # Detect a specific pulse shape
|
|
375
|
+
>>> pulse_template = np.array([0, 0.5, 1.0, 0.5, 0])
|
|
376
|
+
>>> match_output = matched_filter(trace, pulse_template)
|
|
377
|
+
>>> # Find peaks in match_output for detection
|
|
378
|
+
"""
|
|
379
|
+
if len(template) == 0:
|
|
380
|
+
raise AnalysisError("Template cannot be empty")
|
|
381
|
+
|
|
382
|
+
if len(template) > len(trace.data):
|
|
383
|
+
raise AnalysisError(
|
|
384
|
+
f"Template length {len(template)} exceeds data length {len(trace.data)}"
|
|
385
|
+
)
|
|
386
|
+
|
|
387
|
+
# Matched filter is correlation with time-reversed template
|
|
388
|
+
h = template[::-1].copy()
|
|
389
|
+
|
|
390
|
+
if normalize:
|
|
391
|
+
energy = np.sum(h**2)
|
|
392
|
+
if energy > 0:
|
|
393
|
+
h = h / np.sqrt(energy)
|
|
394
|
+
|
|
395
|
+
# Correlate (convolve with time-reversed template)
|
|
396
|
+
output = np.convolve(trace.data, h, mode="same")
|
|
397
|
+
|
|
398
|
+
return WaveformTrace(
|
|
399
|
+
data=output.astype(np.float64),
|
|
400
|
+
metadata=trace.metadata,
|
|
401
|
+
)
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
def exponential_moving_average(
|
|
405
|
+
trace: WaveformTrace,
|
|
406
|
+
alpha: float,
|
|
407
|
+
) -> WaveformTrace:
|
|
408
|
+
"""Apply exponential moving average (EMA) filter.
|
|
409
|
+
|
|
410
|
+
IIR filter with exponential decay weighting.
|
|
411
|
+
|
|
412
|
+
Args:
|
|
413
|
+
trace: Input waveform trace.
|
|
414
|
+
alpha: Smoothing factor (0 < alpha <= 1). Higher = less smoothing.
|
|
415
|
+
|
|
416
|
+
Returns:
|
|
417
|
+
Filtered waveform trace.
|
|
418
|
+
|
|
419
|
+
Raises:
|
|
420
|
+
AnalysisError: If alpha is not in range (0, 1].
|
|
421
|
+
|
|
422
|
+
Example:
|
|
423
|
+
>>> smoothed = exponential_moving_average(trace, alpha=0.1)
|
|
424
|
+
"""
|
|
425
|
+
if not 0 < alpha <= 1:
|
|
426
|
+
raise AnalysisError(f"Alpha must be in (0, 1], got {alpha}")
|
|
427
|
+
|
|
428
|
+
# EMA as IIR filter: y[n] = alpha * x[n] + (1 - alpha) * y[n-1]
|
|
429
|
+
# Transfer function: H(z) = alpha / (1 - (1-alpha) * z^-1)
|
|
430
|
+
b = np.array([alpha])
|
|
431
|
+
a = np.array([1.0, -(1 - alpha)])
|
|
432
|
+
|
|
433
|
+
filtered_data = signal.lfilter(b, a, trace.data)
|
|
434
|
+
|
|
435
|
+
return WaveformTrace(
|
|
436
|
+
data=filtered_data.astype(np.float64),
|
|
437
|
+
metadata=trace.metadata,
|
|
438
|
+
)
|
|
439
|
+
|
|
440
|
+
|
|
441
|
+
def gaussian_filter(
|
|
442
|
+
trace: WaveformTrace,
|
|
443
|
+
sigma: float,
|
|
444
|
+
) -> WaveformTrace:
|
|
445
|
+
"""Apply Gaussian smoothing filter.
|
|
446
|
+
|
|
447
|
+
Smooth with Gaussian kernel of specified standard deviation.
|
|
448
|
+
|
|
449
|
+
Args:
|
|
450
|
+
trace: Input waveform trace.
|
|
451
|
+
sigma: Standard deviation of Gaussian kernel in samples.
|
|
452
|
+
|
|
453
|
+
Returns:
|
|
454
|
+
Filtered waveform trace.
|
|
455
|
+
|
|
456
|
+
Raises:
|
|
457
|
+
AnalysisError: If sigma is not positive.
|
|
458
|
+
|
|
459
|
+
Example:
|
|
460
|
+
>>> smoothed = gaussian_filter(trace, sigma=3.0)
|
|
461
|
+
"""
|
|
462
|
+
if sigma <= 0:
|
|
463
|
+
raise AnalysisError(f"Sigma must be positive, got {sigma}")
|
|
464
|
+
|
|
465
|
+
filtered_data = ndimage.gaussian_filter1d(trace.data, sigma)
|
|
466
|
+
|
|
467
|
+
return WaveformTrace(
|
|
468
|
+
data=filtered_data.astype(np.float64),
|
|
469
|
+
metadata=trace.metadata,
|
|
470
|
+
)
|
|
471
|
+
|
|
472
|
+
|
|
473
|
+
def differentiate(
|
|
474
|
+
trace: WaveformTrace,
|
|
475
|
+
*,
|
|
476
|
+
order: int = 1,
|
|
477
|
+
) -> WaveformTrace:
|
|
478
|
+
"""Compute numerical derivative of trace.
|
|
479
|
+
|
|
480
|
+
Uses numpy gradient for smooth differentiation.
|
|
481
|
+
|
|
482
|
+
Args:
|
|
483
|
+
trace: Input waveform trace.
|
|
484
|
+
order: Derivative order (1 = first derivative, 2 = second, etc.).
|
|
485
|
+
|
|
486
|
+
Returns:
|
|
487
|
+
Differentiated waveform trace. Units change (V -> V/s, etc.).
|
|
488
|
+
|
|
489
|
+
Raises:
|
|
490
|
+
AnalysisError: If order is not positive.
|
|
491
|
+
|
|
492
|
+
Example:
|
|
493
|
+
>>> velocity = differentiate(position_trace)
|
|
494
|
+
>>> acceleration = differentiate(position_trace, order=2)
|
|
495
|
+
"""
|
|
496
|
+
if order < 1:
|
|
497
|
+
raise AnalysisError(f"Derivative order must be positive, got {order}")
|
|
498
|
+
|
|
499
|
+
sample_period = trace.metadata.time_base
|
|
500
|
+
result = trace.data.copy()
|
|
501
|
+
|
|
502
|
+
for _ in range(order):
|
|
503
|
+
result = np.gradient(result, sample_period)
|
|
504
|
+
|
|
505
|
+
return WaveformTrace(
|
|
506
|
+
data=result.astype(np.float64),
|
|
507
|
+
metadata=trace.metadata,
|
|
508
|
+
)
|
|
509
|
+
|
|
510
|
+
|
|
511
|
+
def integrate(
|
|
512
|
+
trace: WaveformTrace,
|
|
513
|
+
*,
|
|
514
|
+
method: Literal["cumtrapz", "cumsum"] = "cumtrapz",
|
|
515
|
+
initial: float = 0.0,
|
|
516
|
+
) -> WaveformTrace:
|
|
517
|
+
"""Compute numerical integral of trace.
|
|
518
|
+
|
|
519
|
+
Args:
|
|
520
|
+
trace: Input waveform trace.
|
|
521
|
+
method: Integration method - "cumtrapz" (trapezoidal) or "cumsum".
|
|
522
|
+
initial: Initial value at t=0.
|
|
523
|
+
|
|
524
|
+
Returns:
|
|
525
|
+
Integrated waveform trace. Units change (V -> V*s, etc.).
|
|
526
|
+
|
|
527
|
+
Raises:
|
|
528
|
+
AnalysisError: If method is not recognized.
|
|
529
|
+
|
|
530
|
+
Example:
|
|
531
|
+
>>> position = integrate(velocity_trace)
|
|
532
|
+
"""
|
|
533
|
+
sample_period = trace.metadata.time_base
|
|
534
|
+
|
|
535
|
+
if method == "cumtrapz":
|
|
536
|
+
from scipy.integrate import cumulative_trapezoid
|
|
537
|
+
|
|
538
|
+
result = cumulative_trapezoid(trace.data, dx=sample_period, initial=initial)
|
|
539
|
+
elif method == "cumsum":
|
|
540
|
+
result = np.cumsum(trace.data) * sample_period + initial
|
|
541
|
+
else:
|
|
542
|
+
raise AnalysisError(f"Unknown integration method: {method}")
|
|
543
|
+
|
|
544
|
+
return WaveformTrace(
|
|
545
|
+
data=result.astype(np.float64),
|
|
546
|
+
metadata=trace.metadata,
|
|
547
|
+
)
|
|
548
|
+
|
|
549
|
+
|
|
550
|
+
__all__ = [
|
|
551
|
+
"band_pass",
|
|
552
|
+
"band_stop",
|
|
553
|
+
"differentiate",
|
|
554
|
+
"exponential_moving_average",
|
|
555
|
+
"gaussian_filter",
|
|
556
|
+
"high_pass",
|
|
557
|
+
"integrate",
|
|
558
|
+
"low_pass",
|
|
559
|
+
"matched_filter",
|
|
560
|
+
"median_filter",
|
|
561
|
+
"moving_average",
|
|
562
|
+
"notch_filter",
|
|
563
|
+
"savgol_filter",
|
|
564
|
+
]
|