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,520 @@
|
|
|
1
|
+
"""Trend detection and analysis for signal data.
|
|
2
|
+
|
|
3
|
+
This module provides linear trend detection, drift analysis, and
|
|
4
|
+
detrending functions for identifying systematic changes in signals.
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
Example:
|
|
8
|
+
>>> from oscura.analyzers.statistics.trend import (
|
|
9
|
+
... detect_trend, detrend, moving_average
|
|
10
|
+
... )
|
|
11
|
+
>>> result = detect_trend(trace)
|
|
12
|
+
>>> print(f"Slope: {result['slope']:.2e} V/s")
|
|
13
|
+
>>> detrended = detrend(trace)
|
|
14
|
+
|
|
15
|
+
References:
|
|
16
|
+
Montgomery, D. C. (2012). Introduction to Statistical Quality Control
|
|
17
|
+
NIST Engineering Statistics Handbook
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
from dataclasses import dataclass
|
|
23
|
+
from typing import TYPE_CHECKING, Any, Literal
|
|
24
|
+
|
|
25
|
+
import numpy as np
|
|
26
|
+
from scipy import stats
|
|
27
|
+
|
|
28
|
+
from oscura.core.types import WaveformTrace
|
|
29
|
+
|
|
30
|
+
if TYPE_CHECKING:
|
|
31
|
+
from numpy.typing import NDArray
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@dataclass
|
|
35
|
+
class TrendResult:
|
|
36
|
+
"""Result of trend analysis.
|
|
37
|
+
|
|
38
|
+
Attributes:
|
|
39
|
+
slope: Trend slope (units per second).
|
|
40
|
+
intercept: Trend intercept (at t=0).
|
|
41
|
+
r_squared: Coefficient of determination.
|
|
42
|
+
p_value: Statistical significance (p < 0.05 is significant).
|
|
43
|
+
std_error: Standard error of slope estimate.
|
|
44
|
+
is_significant: Whether trend is statistically significant.
|
|
45
|
+
trend_line: Fitted trend values at each sample.
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
slope: float
|
|
49
|
+
intercept: float
|
|
50
|
+
r_squared: float
|
|
51
|
+
p_value: float
|
|
52
|
+
std_error: float
|
|
53
|
+
is_significant: bool
|
|
54
|
+
trend_line: NDArray[np.float64]
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def detect_trend(
|
|
58
|
+
trace: WaveformTrace | NDArray[np.floating[Any]],
|
|
59
|
+
*,
|
|
60
|
+
significance_level: float = 0.05,
|
|
61
|
+
sample_rate: float | None = None,
|
|
62
|
+
) -> TrendResult:
|
|
63
|
+
"""Detect linear trend in signal data.
|
|
64
|
+
|
|
65
|
+
Fits a linear regression and tests for statistical significance.
|
|
66
|
+
Reports slope, R-squared, and whether drift is significant.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
trace: Input trace or numpy array.
|
|
70
|
+
significance_level: P-value threshold for significance (default 0.05).
|
|
71
|
+
sample_rate: Sample rate in Hz (required for array input).
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
TrendResult with trend analysis.
|
|
75
|
+
|
|
76
|
+
Raises:
|
|
77
|
+
ValueError: If trace is array and sample_rate is not provided.
|
|
78
|
+
|
|
79
|
+
Example:
|
|
80
|
+
>>> result = detect_trend(trace)
|
|
81
|
+
>>> if result.is_significant:
|
|
82
|
+
... print(f"Significant drift: {result.slope:.2e} V/s")
|
|
83
|
+
... print(f"R-squared: {result.r_squared:.4f}")
|
|
84
|
+
|
|
85
|
+
References:
|
|
86
|
+
NIST Engineering Statistics Handbook Section 6.6
|
|
87
|
+
"""
|
|
88
|
+
if isinstance(trace, WaveformTrace):
|
|
89
|
+
data = trace.data
|
|
90
|
+
fs = trace.metadata.sample_rate
|
|
91
|
+
else:
|
|
92
|
+
data = trace
|
|
93
|
+
if sample_rate is None:
|
|
94
|
+
raise ValueError("sample_rate required when trace is array")
|
|
95
|
+
fs = sample_rate
|
|
96
|
+
|
|
97
|
+
n = len(data)
|
|
98
|
+
|
|
99
|
+
if n < 3:
|
|
100
|
+
return TrendResult(
|
|
101
|
+
slope=np.nan,
|
|
102
|
+
intercept=np.nan,
|
|
103
|
+
r_squared=np.nan,
|
|
104
|
+
p_value=np.nan,
|
|
105
|
+
std_error=np.nan,
|
|
106
|
+
is_significant=False,
|
|
107
|
+
trend_line=np.full(n, np.nan, dtype=np.float64),
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
# Time axis in seconds
|
|
111
|
+
t = np.arange(n) / fs
|
|
112
|
+
|
|
113
|
+
# Linear regression
|
|
114
|
+
result = stats.linregress(t, data)
|
|
115
|
+
|
|
116
|
+
slope = float(result.slope)
|
|
117
|
+
intercept = float(result.intercept)
|
|
118
|
+
r_squared = float(result.rvalue**2)
|
|
119
|
+
p_value = float(result.pvalue)
|
|
120
|
+
std_error = float(result.stderr)
|
|
121
|
+
is_significant = p_value < significance_level
|
|
122
|
+
|
|
123
|
+
# Compute trend line
|
|
124
|
+
trend_line = intercept + slope * t
|
|
125
|
+
|
|
126
|
+
return TrendResult(
|
|
127
|
+
slope=slope,
|
|
128
|
+
intercept=intercept,
|
|
129
|
+
r_squared=r_squared,
|
|
130
|
+
p_value=p_value,
|
|
131
|
+
std_error=std_error,
|
|
132
|
+
is_significant=is_significant,
|
|
133
|
+
trend_line=trend_line.astype(np.float64),
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def detrend(
|
|
138
|
+
trace: WaveformTrace | NDArray[np.floating[Any]],
|
|
139
|
+
*,
|
|
140
|
+
method: Literal["linear", "constant", "polynomial"] = "linear",
|
|
141
|
+
order: int = 1,
|
|
142
|
+
return_trend: bool = False,
|
|
143
|
+
sample_rate: float | None = None,
|
|
144
|
+
) -> NDArray[np.float64] | tuple[NDArray[np.float64], NDArray[np.float64]]:
|
|
145
|
+
"""Remove trend from signal data.
|
|
146
|
+
|
|
147
|
+
Subtracts fitted trend to isolate fluctuations around baseline.
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
trace: Input trace or numpy array.
|
|
151
|
+
method: Detrending method:
|
|
152
|
+
- "constant": Remove mean (DC offset)
|
|
153
|
+
- "linear": Remove linear trend (default)
|
|
154
|
+
- "polynomial": Remove polynomial trend
|
|
155
|
+
order: Polynomial order (for method="polynomial").
|
|
156
|
+
return_trend: If True, also return the removed trend.
|
|
157
|
+
sample_rate: Sample rate in Hz (required for array input, only for linear).
|
|
158
|
+
|
|
159
|
+
Returns:
|
|
160
|
+
Detrended data array.
|
|
161
|
+
If return_trend=True, returns (detrended, trend).
|
|
162
|
+
|
|
163
|
+
Raises:
|
|
164
|
+
ValueError: If method is not recognized.
|
|
165
|
+
|
|
166
|
+
Example:
|
|
167
|
+
>>> detrended = detrend(trace, method="linear")
|
|
168
|
+
>>> # Or get the trend too
|
|
169
|
+
>>> detrended, trend = detrend(trace, return_trend=True)
|
|
170
|
+
"""
|
|
171
|
+
if isinstance(trace, WaveformTrace):
|
|
172
|
+
data = trace.data.astype(np.float64)
|
|
173
|
+
fs = trace.metadata.sample_rate
|
|
174
|
+
else:
|
|
175
|
+
data = np.array(trace, dtype=np.float64)
|
|
176
|
+
fs = sample_rate if sample_rate else 1.0
|
|
177
|
+
|
|
178
|
+
n = len(data)
|
|
179
|
+
|
|
180
|
+
if method == "constant":
|
|
181
|
+
trend = np.full(n, np.mean(data), dtype=np.float64)
|
|
182
|
+
|
|
183
|
+
elif method == "linear":
|
|
184
|
+
result = detect_trend(trace, sample_rate=fs)
|
|
185
|
+
trend = result.trend_line
|
|
186
|
+
|
|
187
|
+
elif method == "polynomial":
|
|
188
|
+
t = np.arange(n)
|
|
189
|
+
coeffs = np.polyfit(t, data, order)
|
|
190
|
+
trend = np.polyval(coeffs, t)
|
|
191
|
+
|
|
192
|
+
else:
|
|
193
|
+
raise ValueError(f"Unknown method: {method}")
|
|
194
|
+
|
|
195
|
+
detrended = data - trend
|
|
196
|
+
|
|
197
|
+
if return_trend:
|
|
198
|
+
return detrended, trend.astype(np.float64)
|
|
199
|
+
return detrended
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def moving_average(
|
|
203
|
+
trace: WaveformTrace | NDArray[np.floating[Any]],
|
|
204
|
+
*,
|
|
205
|
+
window_size: int,
|
|
206
|
+
method: Literal["simple", "exponential", "weighted"] = "simple",
|
|
207
|
+
alpha: float = 0.1,
|
|
208
|
+
) -> NDArray[np.float64]:
|
|
209
|
+
"""Compute moving average of signal.
|
|
210
|
+
|
|
211
|
+
Smooths signal by averaging over sliding window.
|
|
212
|
+
|
|
213
|
+
Args:
|
|
214
|
+
trace: Input trace or numpy array.
|
|
215
|
+
window_size: Size of averaging window in samples.
|
|
216
|
+
method: Averaging method:
|
|
217
|
+
- "simple": Simple moving average (default)
|
|
218
|
+
- "exponential": Exponential moving average
|
|
219
|
+
- "weighted": Linearly weighted moving average
|
|
220
|
+
alpha: Smoothing factor for exponential method (0-1).
|
|
221
|
+
|
|
222
|
+
Returns:
|
|
223
|
+
Smoothed signal array (same length as input).
|
|
224
|
+
|
|
225
|
+
Raises:
|
|
226
|
+
ValueError: If method is not recognized.
|
|
227
|
+
|
|
228
|
+
Example:
|
|
229
|
+
>>> smoothed = moving_average(trace, window_size=10)
|
|
230
|
+
>>> # Exponential smoothing
|
|
231
|
+
>>> ema = moving_average(trace, window_size=10, method="exponential", alpha=0.2)
|
|
232
|
+
"""
|
|
233
|
+
if isinstance(trace, WaveformTrace):
|
|
234
|
+
data = trace.data.astype(np.float64)
|
|
235
|
+
else:
|
|
236
|
+
data = np.array(trace, dtype=np.float64)
|
|
237
|
+
|
|
238
|
+
n = len(data)
|
|
239
|
+
|
|
240
|
+
window_size = min(window_size, n)
|
|
241
|
+
|
|
242
|
+
if window_size < 1:
|
|
243
|
+
return data.copy()
|
|
244
|
+
|
|
245
|
+
if method == "simple":
|
|
246
|
+
# Simple moving average using convolution
|
|
247
|
+
kernel = np.ones(window_size) / window_size
|
|
248
|
+
# Pad for same output length
|
|
249
|
+
padded = np.pad(data, (window_size - 1, 0), mode="edge")
|
|
250
|
+
result = np.convolve(padded, kernel, mode="valid")
|
|
251
|
+
|
|
252
|
+
elif method == "exponential":
|
|
253
|
+
# Exponential moving average
|
|
254
|
+
result = np.zeros(n, dtype=np.float64)
|
|
255
|
+
result[0] = data[0]
|
|
256
|
+
for i in range(1, n):
|
|
257
|
+
result[i] = alpha * data[i] + (1 - alpha) * result[i - 1]
|
|
258
|
+
|
|
259
|
+
elif method == "weighted":
|
|
260
|
+
# Linearly weighted moving average
|
|
261
|
+
weights = np.arange(1, window_size + 1, dtype=np.float64)
|
|
262
|
+
weights = weights / np.sum(weights)
|
|
263
|
+
|
|
264
|
+
padded = np.pad(data, (window_size - 1, 0), mode="edge")
|
|
265
|
+
result = np.convolve(padded, weights, mode="valid")
|
|
266
|
+
|
|
267
|
+
else:
|
|
268
|
+
raise ValueError(f"Unknown method: {method}")
|
|
269
|
+
|
|
270
|
+
return result.astype(np.float64)
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def detect_drift_segments(
|
|
274
|
+
trace: WaveformTrace | NDArray[np.floating[Any]],
|
|
275
|
+
*,
|
|
276
|
+
segment_size: int,
|
|
277
|
+
threshold_slope: float | None = None,
|
|
278
|
+
sample_rate: float | None = None,
|
|
279
|
+
) -> list[dict]: # type: ignore[type-arg]
|
|
280
|
+
"""Detect segments with significant drift.
|
|
281
|
+
|
|
282
|
+
Divides signal into segments and identifies those with
|
|
283
|
+
statistically significant linear trends.
|
|
284
|
+
|
|
285
|
+
Args:
|
|
286
|
+
trace: Input trace or numpy array.
|
|
287
|
+
segment_size: Size of each segment in samples.
|
|
288
|
+
threshold_slope: Minimum slope magnitude to flag (units/second).
|
|
289
|
+
If None, uses statistical significance.
|
|
290
|
+
sample_rate: Sample rate in Hz (required for array input).
|
|
291
|
+
|
|
292
|
+
Returns:
|
|
293
|
+
List of dictionaries describing drift segments:
|
|
294
|
+
- start_sample: Start index of segment
|
|
295
|
+
- end_sample: End index of segment
|
|
296
|
+
- start_time: Start time in seconds
|
|
297
|
+
- end_time: End time in seconds
|
|
298
|
+
- slope: Trend slope
|
|
299
|
+
- r_squared: Coefficient of determination
|
|
300
|
+
|
|
301
|
+
Raises:
|
|
302
|
+
ValueError: If trace is array and sample_rate is not provided.
|
|
303
|
+
|
|
304
|
+
Example:
|
|
305
|
+
>>> segments = detect_drift_segments(trace, segment_size=1000)
|
|
306
|
+
>>> for seg in segments:
|
|
307
|
+
... print(f"Drift at {seg['start_time']:.3f}s: {seg['slope']:.2e} V/s")
|
|
308
|
+
"""
|
|
309
|
+
if isinstance(trace, WaveformTrace):
|
|
310
|
+
data = trace.data
|
|
311
|
+
fs = trace.metadata.sample_rate
|
|
312
|
+
else:
|
|
313
|
+
data = trace
|
|
314
|
+
if sample_rate is None:
|
|
315
|
+
raise ValueError("sample_rate required when trace is array")
|
|
316
|
+
fs = sample_rate
|
|
317
|
+
|
|
318
|
+
n = len(data)
|
|
319
|
+
drift_segments = []
|
|
320
|
+
|
|
321
|
+
for start in range(0, n, segment_size):
|
|
322
|
+
end = min(start + segment_size, n)
|
|
323
|
+
|
|
324
|
+
if end - start < 10: # Need minimum points for regression
|
|
325
|
+
continue
|
|
326
|
+
|
|
327
|
+
segment_data = data[start:end]
|
|
328
|
+
segment_trace = segment_data # Array
|
|
329
|
+
|
|
330
|
+
result = detect_trend(segment_trace, sample_rate=fs)
|
|
331
|
+
|
|
332
|
+
# Check if drift is significant
|
|
333
|
+
is_drift = result.is_significant
|
|
334
|
+
if threshold_slope is not None:
|
|
335
|
+
is_drift = is_drift and abs(result.slope) >= threshold_slope
|
|
336
|
+
|
|
337
|
+
if is_drift:
|
|
338
|
+
drift_segments.append(
|
|
339
|
+
{
|
|
340
|
+
"start_sample": start,
|
|
341
|
+
"end_sample": end,
|
|
342
|
+
"start_time": start / fs,
|
|
343
|
+
"end_time": end / fs,
|
|
344
|
+
"slope": result.slope,
|
|
345
|
+
"r_squared": result.r_squared,
|
|
346
|
+
"p_value": result.p_value,
|
|
347
|
+
}
|
|
348
|
+
)
|
|
349
|
+
|
|
350
|
+
return drift_segments
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
def change_point_detection(
|
|
354
|
+
trace: WaveformTrace | NDArray[np.floating[Any]],
|
|
355
|
+
*,
|
|
356
|
+
min_segment_size: int = 10,
|
|
357
|
+
penalty: float | None = None,
|
|
358
|
+
) -> list[int]:
|
|
359
|
+
"""Detect change points in signal level or trend.
|
|
360
|
+
|
|
361
|
+
Identifies locations where the signal characteristics change
|
|
362
|
+
significantly, using a simple CUSUM-based approach.
|
|
363
|
+
|
|
364
|
+
Args:
|
|
365
|
+
trace: Input trace or numpy array.
|
|
366
|
+
min_segment_size: Minimum samples between change points.
|
|
367
|
+
penalty: Penalty for adding change points (controls sensitivity).
|
|
368
|
+
If None, auto-selected based on signal variance.
|
|
369
|
+
|
|
370
|
+
Returns:
|
|
371
|
+
List of sample indices where changes occur.
|
|
372
|
+
|
|
373
|
+
Example:
|
|
374
|
+
>>> change_points = change_point_detection(trace)
|
|
375
|
+
>>> for cp in change_points:
|
|
376
|
+
... print(f"Change at sample {cp}")
|
|
377
|
+
"""
|
|
378
|
+
data = trace.data if isinstance(trace, WaveformTrace) else np.array(trace, dtype=np.float64)
|
|
379
|
+
|
|
380
|
+
n = len(data)
|
|
381
|
+
|
|
382
|
+
if n < 2 * min_segment_size:
|
|
383
|
+
return []
|
|
384
|
+
|
|
385
|
+
# Auto-select penalty if not provided
|
|
386
|
+
if penalty is None:
|
|
387
|
+
penalty = np.var(data) * 2
|
|
388
|
+
|
|
389
|
+
# Simple binary segmentation using mean-shift cost
|
|
390
|
+
change_points = []
|
|
391
|
+
segments = [(0, n)]
|
|
392
|
+
|
|
393
|
+
while segments:
|
|
394
|
+
start, end = segments.pop(0)
|
|
395
|
+
segment = data[start:end]
|
|
396
|
+
seg_len = len(segment)
|
|
397
|
+
|
|
398
|
+
if seg_len < 2 * min_segment_size:
|
|
399
|
+
continue
|
|
400
|
+
|
|
401
|
+
# Find best split point
|
|
402
|
+
best_cost_reduction = -np.inf
|
|
403
|
+
best_split = None
|
|
404
|
+
|
|
405
|
+
for split in range(min_segment_size, seg_len - min_segment_size):
|
|
406
|
+
left = segment[:split]
|
|
407
|
+
right = segment[split:]
|
|
408
|
+
|
|
409
|
+
# Cost = sum of squared deviations from segment mean
|
|
410
|
+
cost_whole = np.sum((segment - np.mean(segment)) ** 2)
|
|
411
|
+
cost_left = np.sum((left - np.mean(left)) ** 2)
|
|
412
|
+
cost_right = np.sum((right - np.mean(right)) ** 2)
|
|
413
|
+
|
|
414
|
+
cost_reduction = cost_whole - (cost_left + cost_right) - penalty
|
|
415
|
+
|
|
416
|
+
if cost_reduction > best_cost_reduction:
|
|
417
|
+
best_cost_reduction = cost_reduction
|
|
418
|
+
best_split = split
|
|
419
|
+
|
|
420
|
+
# If significant cost reduction, add change point
|
|
421
|
+
if best_split is not None and best_cost_reduction > 0:
|
|
422
|
+
cp = start + best_split
|
|
423
|
+
change_points.append(cp)
|
|
424
|
+
|
|
425
|
+
# Add new segments to process
|
|
426
|
+
segments.append((start, cp))
|
|
427
|
+
segments.append((cp, end))
|
|
428
|
+
|
|
429
|
+
change_points.sort()
|
|
430
|
+
return change_points
|
|
431
|
+
|
|
432
|
+
|
|
433
|
+
def piecewise_linear_fit(
|
|
434
|
+
trace: WaveformTrace | NDArray[np.floating[Any]],
|
|
435
|
+
*,
|
|
436
|
+
n_segments: int = 3,
|
|
437
|
+
sample_rate: float | None = None,
|
|
438
|
+
) -> dict: # type: ignore[type-arg]
|
|
439
|
+
"""Fit piecewise linear model to signal.
|
|
440
|
+
|
|
441
|
+
Divides signal into segments and fits linear trends to each.
|
|
442
|
+
|
|
443
|
+
Args:
|
|
444
|
+
trace: Input trace or numpy array.
|
|
445
|
+
n_segments: Number of segments to fit.
|
|
446
|
+
sample_rate: Sample rate in Hz (required for array input).
|
|
447
|
+
|
|
448
|
+
Returns:
|
|
449
|
+
Dictionary with fit results:
|
|
450
|
+
- breakpoints: Sample indices of segment boundaries
|
|
451
|
+
- segments: List of (slope, intercept) for each segment
|
|
452
|
+
- fitted: Full fitted signal
|
|
453
|
+
- residuals: Fitting residuals
|
|
454
|
+
|
|
455
|
+
Raises:
|
|
456
|
+
ValueError: If trace is array and sample_rate is not provided.
|
|
457
|
+
|
|
458
|
+
Example:
|
|
459
|
+
>>> result = piecewise_linear_fit(trace, n_segments=4)
|
|
460
|
+
>>> print(f"Breakpoints: {result['breakpoints']}")
|
|
461
|
+
"""
|
|
462
|
+
if isinstance(trace, WaveformTrace):
|
|
463
|
+
data = trace.data
|
|
464
|
+
fs = trace.metadata.sample_rate
|
|
465
|
+
else:
|
|
466
|
+
data = np.array(trace, dtype=np.float64)
|
|
467
|
+
if sample_rate is None:
|
|
468
|
+
raise ValueError("sample_rate required when trace is array")
|
|
469
|
+
fs = sample_rate
|
|
470
|
+
|
|
471
|
+
n = len(data)
|
|
472
|
+
|
|
473
|
+
# Calculate segment boundaries
|
|
474
|
+
segment_size = n // n_segments
|
|
475
|
+
breakpoints = [i * segment_size for i in range(1, n_segments)]
|
|
476
|
+
breakpoints = [0, *breakpoints, n]
|
|
477
|
+
|
|
478
|
+
# Fit each segment
|
|
479
|
+
segments = []
|
|
480
|
+
fitted = np.zeros(n, dtype=np.float64)
|
|
481
|
+
|
|
482
|
+
for i in range(len(breakpoints) - 1):
|
|
483
|
+
start = breakpoints[i]
|
|
484
|
+
end = breakpoints[i + 1]
|
|
485
|
+
|
|
486
|
+
segment_data = data[start:end]
|
|
487
|
+
t = np.arange(len(segment_data)) / fs
|
|
488
|
+
|
|
489
|
+
if len(t) >= 2:
|
|
490
|
+
slope, intercept = np.polyfit(t, segment_data, 1)
|
|
491
|
+
fitted[start:end] = intercept + slope * t
|
|
492
|
+
segments.append(
|
|
493
|
+
{
|
|
494
|
+
"slope": float(slope),
|
|
495
|
+
"intercept": float(intercept),
|
|
496
|
+
"start": start,
|
|
497
|
+
"end": end,
|
|
498
|
+
}
|
|
499
|
+
)
|
|
500
|
+
|
|
501
|
+
residuals = data - fitted
|
|
502
|
+
|
|
503
|
+
return {
|
|
504
|
+
"breakpoints": breakpoints,
|
|
505
|
+
"segments": segments,
|
|
506
|
+
"fitted": fitted,
|
|
507
|
+
"residuals": residuals,
|
|
508
|
+
"rmse": float(np.sqrt(np.mean(residuals**2))),
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
|
|
512
|
+
__all__ = [
|
|
513
|
+
"TrendResult",
|
|
514
|
+
"change_point_detection",
|
|
515
|
+
"detect_drift_segments",
|
|
516
|
+
"detect_trend",
|
|
517
|
+
"detrend",
|
|
518
|
+
"moving_average",
|
|
519
|
+
"piecewise_linear_fit",
|
|
520
|
+
]
|