sigima 0.0.1.dev0__py3-none-any.whl → 1.0.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.
- sigima/__init__.py +142 -2
- sigima/client/__init__.py +105 -0
- sigima/client/base.py +780 -0
- sigima/client/remote.py +469 -0
- sigima/client/stub.py +814 -0
- sigima/client/utils.py +90 -0
- sigima/config.py +444 -0
- sigima/data/logo/Sigima.svg +135 -0
- sigima/data/tests/annotations.json +798 -0
- sigima/data/tests/curve_fitting/exponential_fit.txt +511 -0
- sigima/data/tests/curve_fitting/gaussian_fit.txt +100 -0
- sigima/data/tests/curve_fitting/piecewiseexponential_fit.txt +1022 -0
- sigima/data/tests/curve_fitting/polynomial_fit.txt +100 -0
- sigima/data/tests/curve_fitting/twohalfgaussian_fit.txt +1000 -0
- sigima/data/tests/curve_formats/bandwidth.txt +201 -0
- sigima/data/tests/curve_formats/boxcar.npy +0 -0
- sigima/data/tests/curve_formats/datetime.txt +1001 -0
- sigima/data/tests/curve_formats/dynamic_parameters.txt +4000 -0
- sigima/data/tests/curve_formats/fw1e2.txt +301 -0
- sigima/data/tests/curve_formats/fwhm.txt +319 -0
- sigima/data/tests/curve_formats/multiple_curves.csv +29 -0
- sigima/data/tests/curve_formats/noised_saw.mat +0 -0
- sigima/data/tests/curve_formats/oscilloscope.csv +111 -0
- sigima/data/tests/curve_formats/other/other2/recursive2.txt +5 -0
- sigima/data/tests/curve_formats/other/recursive1.txt +5 -0
- sigima/data/tests/curve_formats/paracetamol.npy +0 -0
- sigima/data/tests/curve_formats/paracetamol.txt +1010 -0
- sigima/data/tests/curve_formats/paracetamol_dx_dy.csv +1000 -0
- sigima/data/tests/curve_formats/paracetamol_dy.csv +1001 -0
- sigima/data/tests/curve_formats/pulse1.npy +0 -0
- sigima/data/tests/curve_formats/pulse2.npy +0 -0
- sigima/data/tests/curve_formats/simple.txt +5 -0
- sigima/data/tests/curve_formats/spectrum.mca +2139 -0
- sigima/data/tests/curve_formats/square2.npy +0 -0
- sigima/data/tests/curve_formats/step.npy +0 -0
- sigima/data/tests/fabry-perot1.jpg +0 -0
- sigima/data/tests/fabry-perot2.jpg +0 -0
- sigima/data/tests/flower.npy +0 -0
- sigima/data/tests/image_formats/NF 180338201.scor-data +11003 -0
- sigima/data/tests/image_formats/binary_image.npy +0 -0
- sigima/data/tests/image_formats/binary_image.png +0 -0
- sigima/data/tests/image_formats/centroid_test.npy +0 -0
- sigima/data/tests/image_formats/coordinated_text/complex_image.txt +10011 -0
- sigima/data/tests/image_formats/coordinated_text/complex_ref_image.txt +10010 -0
- sigima/data/tests/image_formats/coordinated_text/image.txt +15 -0
- sigima/data/tests/image_formats/coordinated_text/image2.txt +14 -0
- sigima/data/tests/image_formats/coordinated_text/image_no_unit_no_label.txt +14 -0
- sigima/data/tests/image_formats/coordinated_text/image_with_nan.txt +15 -0
- sigima/data/tests/image_formats/coordinated_text/image_with_unit.txt +14 -0
- sigima/data/tests/image_formats/fiber.csv +480 -0
- sigima/data/tests/image_formats/fiber.jpg +0 -0
- sigima/data/tests/image_formats/fiber.png +0 -0
- sigima/data/tests/image_formats/fiber.txt +480 -0
- sigima/data/tests/image_formats/gaussian_spot_with_noise.npy +0 -0
- sigima/data/tests/image_formats/mr-brain.dcm +0 -0
- sigima/data/tests/image_formats/noised_gaussian.mat +0 -0
- sigima/data/tests/image_formats/sif_reader/nd_lum_image_no_glue.sif +0 -0
- sigima/data/tests/image_formats/sif_reader/raman1.sif +0 -0
- sigima/data/tests/image_formats/tiling.txt +10 -0
- sigima/data/tests/image_formats/uint16.tiff +0 -0
- sigima/data/tests/image_formats/uint8.tiff +0 -0
- sigima/data/tests/laser_beam/TEM00_z_13.jpg +0 -0
- sigima/data/tests/laser_beam/TEM00_z_18.jpg +0 -0
- sigima/data/tests/laser_beam/TEM00_z_23.jpg +0 -0
- sigima/data/tests/laser_beam/TEM00_z_30.jpg +0 -0
- sigima/data/tests/laser_beam/TEM00_z_35.jpg +0 -0
- sigima/data/tests/laser_beam/TEM00_z_40.jpg +0 -0
- sigima/data/tests/laser_beam/TEM00_z_45.jpg +0 -0
- sigima/data/tests/laser_beam/TEM00_z_50.jpg +0 -0
- sigima/data/tests/laser_beam/TEM00_z_55.jpg +0 -0
- sigima/data/tests/laser_beam/TEM00_z_60.jpg +0 -0
- sigima/data/tests/laser_beam/TEM00_z_65.jpg +0 -0
- sigima/data/tests/laser_beam/TEM00_z_70.jpg +0 -0
- sigima/data/tests/laser_beam/TEM00_z_75.jpg +0 -0
- sigima/data/tests/laser_beam/TEM00_z_80.jpg +0 -0
- sigima/enums.py +195 -0
- sigima/io/__init__.py +123 -0
- sigima/io/base.py +311 -0
- sigima/io/common/__init__.py +5 -0
- sigima/io/common/basename.py +164 -0
- sigima/io/common/converters.py +189 -0
- sigima/io/common/objmeta.py +181 -0
- sigima/io/common/textreader.py +58 -0
- sigima/io/convenience.py +157 -0
- sigima/io/enums.py +17 -0
- sigima/io/ftlab.py +395 -0
- sigima/io/image/__init__.py +9 -0
- sigima/io/image/base.py +177 -0
- sigima/io/image/formats.py +1016 -0
- sigima/io/image/funcs.py +414 -0
- sigima/io/signal/__init__.py +9 -0
- sigima/io/signal/base.py +129 -0
- sigima/io/signal/formats.py +290 -0
- sigima/io/signal/funcs.py +723 -0
- sigima/objects/__init__.py +260 -0
- sigima/objects/base.py +937 -0
- sigima/objects/image/__init__.py +88 -0
- sigima/objects/image/creation.py +556 -0
- sigima/objects/image/object.py +524 -0
- sigima/objects/image/roi.py +904 -0
- sigima/objects/scalar/__init__.py +57 -0
- sigima/objects/scalar/common.py +215 -0
- sigima/objects/scalar/geometry.py +502 -0
- sigima/objects/scalar/table.py +784 -0
- sigima/objects/shape.py +290 -0
- sigima/objects/signal/__init__.py +133 -0
- sigima/objects/signal/constants.py +27 -0
- sigima/objects/signal/creation.py +1428 -0
- sigima/objects/signal/object.py +444 -0
- sigima/objects/signal/roi.py +274 -0
- sigima/params.py +405 -0
- sigima/proc/__init__.py +96 -0
- sigima/proc/base.py +381 -0
- sigima/proc/decorator.py +330 -0
- sigima/proc/image/__init__.py +513 -0
- sigima/proc/image/arithmetic.py +335 -0
- sigima/proc/image/base.py +260 -0
- sigima/proc/image/detection.py +519 -0
- sigima/proc/image/edges.py +329 -0
- sigima/proc/image/exposure.py +406 -0
- sigima/proc/image/extraction.py +458 -0
- sigima/proc/image/filtering.py +219 -0
- sigima/proc/image/fourier.py +147 -0
- sigima/proc/image/geometry.py +661 -0
- sigima/proc/image/mathops.py +340 -0
- sigima/proc/image/measurement.py +195 -0
- sigima/proc/image/morphology.py +155 -0
- sigima/proc/image/noise.py +107 -0
- sigima/proc/image/preprocessing.py +182 -0
- sigima/proc/image/restoration.py +235 -0
- sigima/proc/image/threshold.py +217 -0
- sigima/proc/image/transformations.py +393 -0
- sigima/proc/signal/__init__.py +376 -0
- sigima/proc/signal/analysis.py +206 -0
- sigima/proc/signal/arithmetic.py +551 -0
- sigima/proc/signal/base.py +262 -0
- sigima/proc/signal/extraction.py +60 -0
- sigima/proc/signal/features.py +310 -0
- sigima/proc/signal/filtering.py +484 -0
- sigima/proc/signal/fitting.py +276 -0
- sigima/proc/signal/fourier.py +259 -0
- sigima/proc/signal/mathops.py +420 -0
- sigima/proc/signal/processing.py +580 -0
- sigima/proc/signal/stability.py +175 -0
- sigima/proc/title_formatting.py +227 -0
- sigima/proc/validation.py +272 -0
- sigima/tests/__init__.py +7 -0
- sigima/tests/common/__init__.py +0 -0
- sigima/tests/common/arithmeticparam_unit_test.py +26 -0
- sigima/tests/common/basename_unit_test.py +126 -0
- sigima/tests/common/client_unit_test.py +412 -0
- sigima/tests/common/converters_unit_test.py +77 -0
- sigima/tests/common/decorator_unit_test.py +176 -0
- sigima/tests/common/examples_unit_test.py +104 -0
- sigima/tests/common/kernel_normalization_unit_test.py +242 -0
- sigima/tests/common/roi_basic_unit_test.py +73 -0
- sigima/tests/common/roi_geometry_unit_test.py +171 -0
- sigima/tests/common/scalar_builder_unit_test.py +142 -0
- sigima/tests/common/scalar_unit_test.py +991 -0
- sigima/tests/common/shape_unit_test.py +183 -0
- sigima/tests/common/stat_unit_test.py +138 -0
- sigima/tests/common/title_formatting_unit_test.py +338 -0
- sigima/tests/common/tools_coordinates_unit_test.py +60 -0
- sigima/tests/common/transformations_unit_test.py +178 -0
- sigima/tests/common/validation_unit_test.py +205 -0
- sigima/tests/conftest.py +129 -0
- sigima/tests/data.py +998 -0
- sigima/tests/env.py +280 -0
- sigima/tests/guiutils.py +163 -0
- sigima/tests/helpers.py +532 -0
- sigima/tests/image/__init__.py +28 -0
- sigima/tests/image/binning_unit_test.py +128 -0
- sigima/tests/image/blob_detection_unit_test.py +312 -0
- sigima/tests/image/centroid_unit_test.py +170 -0
- sigima/tests/image/check_2d_array_unit_test.py +63 -0
- sigima/tests/image/contour_unit_test.py +172 -0
- sigima/tests/image/convolution_unit_test.py +178 -0
- sigima/tests/image/datatype_unit_test.py +67 -0
- sigima/tests/image/edges_unit_test.py +155 -0
- sigima/tests/image/enclosingcircle_unit_test.py +88 -0
- sigima/tests/image/exposure_unit_test.py +223 -0
- sigima/tests/image/fft2d_unit_test.py +189 -0
- sigima/tests/image/filtering_unit_test.py +166 -0
- sigima/tests/image/geometry_unit_test.py +654 -0
- sigima/tests/image/hough_circle_unit_test.py +147 -0
- sigima/tests/image/imageobj_unit_test.py +737 -0
- sigima/tests/image/morphology_unit_test.py +71 -0
- sigima/tests/image/noise_unit_test.py +57 -0
- sigima/tests/image/offset_correction_unit_test.py +72 -0
- sigima/tests/image/operation_unit_test.py +518 -0
- sigima/tests/image/peak2d_limits_unit_test.py +41 -0
- sigima/tests/image/peak2d_unit_test.py +133 -0
- sigima/tests/image/profile_unit_test.py +159 -0
- sigima/tests/image/projections_unit_test.py +121 -0
- sigima/tests/image/restoration_unit_test.py +141 -0
- sigima/tests/image/roi2dparam_unit_test.py +53 -0
- sigima/tests/image/roi_advanced_unit_test.py +588 -0
- sigima/tests/image/roi_grid_unit_test.py +279 -0
- sigima/tests/image/spectrum2d_unit_test.py +40 -0
- sigima/tests/image/threshold_unit_test.py +91 -0
- sigima/tests/io/__init__.py +0 -0
- sigima/tests/io/addnewformat_unit_test.py +125 -0
- sigima/tests/io/convenience_funcs_unit_test.py +470 -0
- sigima/tests/io/coordinated_text_format_unit_test.py +495 -0
- sigima/tests/io/datetime_csv_unit_test.py +198 -0
- sigima/tests/io/imageio_formats_test.py +41 -0
- sigima/tests/io/ioregistry_unit_test.py +69 -0
- sigima/tests/io/objmeta_unit_test.py +87 -0
- sigima/tests/io/readobj_unit_test.py +130 -0
- sigima/tests/io/readwriteobj_unit_test.py +67 -0
- sigima/tests/signal/__init__.py +0 -0
- sigima/tests/signal/analysis_unit_test.py +135 -0
- sigima/tests/signal/check_1d_arrays_unit_test.py +169 -0
- sigima/tests/signal/convolution_unit_test.py +404 -0
- sigima/tests/signal/datetime_unit_test.py +176 -0
- sigima/tests/signal/fft1d_unit_test.py +303 -0
- sigima/tests/signal/filters_unit_test.py +403 -0
- sigima/tests/signal/fitting_unit_test.py +929 -0
- sigima/tests/signal/fwhm_unit_test.py +111 -0
- sigima/tests/signal/noise_unit_test.py +128 -0
- sigima/tests/signal/offset_correction_unit_test.py +34 -0
- sigima/tests/signal/operation_unit_test.py +489 -0
- sigima/tests/signal/peakdetection_unit_test.py +145 -0
- sigima/tests/signal/processing_unit_test.py +657 -0
- sigima/tests/signal/pulse/__init__.py +112 -0
- sigima/tests/signal/pulse/crossing_times_unit_test.py +123 -0
- sigima/tests/signal/pulse/plateau_detection_unit_test.py +102 -0
- sigima/tests/signal/pulse/pulse_unit_test.py +1824 -0
- sigima/tests/signal/roi_advanced_unit_test.py +392 -0
- sigima/tests/signal/signalobj_unit_test.py +603 -0
- sigima/tests/signal/stability_unit_test.py +431 -0
- sigima/tests/signal/uncertainty_unit_test.py +611 -0
- sigima/tests/vistools.py +1030 -0
- sigima/tools/__init__.py +59 -0
- sigima/tools/checks.py +290 -0
- sigima/tools/coordinates.py +308 -0
- sigima/tools/datatypes.py +26 -0
- sigima/tools/image/__init__.py +97 -0
- sigima/tools/image/detection.py +451 -0
- sigima/tools/image/exposure.py +77 -0
- sigima/tools/image/extraction.py +48 -0
- sigima/tools/image/fourier.py +260 -0
- sigima/tools/image/geometry.py +190 -0
- sigima/tools/image/preprocessing.py +165 -0
- sigima/tools/signal/__init__.py +86 -0
- sigima/tools/signal/dynamic.py +254 -0
- sigima/tools/signal/features.py +135 -0
- sigima/tools/signal/filtering.py +171 -0
- sigima/tools/signal/fitting.py +1171 -0
- sigima/tools/signal/fourier.py +466 -0
- sigima/tools/signal/interpolation.py +70 -0
- sigima/tools/signal/peakdetection.py +126 -0
- sigima/tools/signal/pulse.py +1626 -0
- sigima/tools/signal/scaling.py +50 -0
- sigima/tools/signal/stability.py +258 -0
- sigima/tools/signal/windowing.py +90 -0
- sigima/worker.py +79 -0
- sigima-1.0.0.dist-info/METADATA +233 -0
- sigima-1.0.0.dist-info/RECORD +262 -0
- {sigima-0.0.1.dev0.dist-info → sigima-1.0.0.dist-info}/licenses/LICENSE +29 -29
- sigima-0.0.1.dev0.dist-info/METADATA +0 -60
- sigima-0.0.1.dev0.dist-info/RECORD +0 -6
- {sigima-0.0.1.dev0.dist-info → sigima-1.0.0.dist-info}/WHEEL +0 -0
- {sigima-0.0.1.dev0.dist-info → sigima-1.0.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,431 @@
|
|
|
1
|
+
# Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file.
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Signal stability analysis unit test.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
# pylint: disable=invalid-name # Allows short reference names like x, y, ...
|
|
8
|
+
# pylint: disable=duplicate-code
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from typing import Callable
|
|
13
|
+
|
|
14
|
+
import numpy as np
|
|
15
|
+
import pytest
|
|
16
|
+
|
|
17
|
+
import sigima.objects
|
|
18
|
+
import sigima.params
|
|
19
|
+
import sigima.proc.signal
|
|
20
|
+
from sigima.tests.helpers import check_array_result
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def get_optimal_points(test_func: Callable) -> int:
|
|
24
|
+
"""Return optimal number of points for different algorithms.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
test_func: Test function object to determine algorithm type
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
Optimal number of points balancing accuracy and performance
|
|
31
|
+
"""
|
|
32
|
+
func_name = test_func.__name__
|
|
33
|
+
if "overlapping" in func_name or "hadamard" in func_name:
|
|
34
|
+
return 500 # Minimum for meaningful results
|
|
35
|
+
if "modified" in func_name or "total" in func_name:
|
|
36
|
+
return 1000 # Need more for averaging
|
|
37
|
+
# allan_variance, allan_deviation, time_deviation
|
|
38
|
+
return 2000 # Balance between speed and accuracy
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def generate_white_noise(n_points: int, sigma=1.0) -> np.ndarray:
|
|
42
|
+
"""Generate white noise with known characteristics.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
n_points: Number of data points
|
|
46
|
+
sigma: Standard deviation of the white noise (default is 1.0)
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
Array of white noise values
|
|
50
|
+
"""
|
|
51
|
+
return np.random.normal(0, sigma, n_points)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def theoretical_allan_variance_white_noise(tau: float, sigma: float) -> float:
|
|
55
|
+
"""Calculate theoretical Allan variance for white noise.
|
|
56
|
+
|
|
57
|
+
For white noise: AVAR(τ) = σ²/(2τ)
|
|
58
|
+
But the Allan variance is computed as AVAR(τ) = σ²τ/τ = σ²τ because of the
|
|
59
|
+
overlapping nature of the samples.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
tau: Averaging time
|
|
63
|
+
sigma: Standard deviation of the white noise
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
Allan variance value for the given tau and sigma
|
|
67
|
+
"""
|
|
68
|
+
return sigma**2 / tau
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def generate_drift_signal(
|
|
72
|
+
n_points: int, slope: float, intercept: float = 0
|
|
73
|
+
) -> tuple[np.ndarray, np.ndarray]:
|
|
74
|
+
"""Generate a linear drift signal.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
n_points: Number of data points
|
|
78
|
+
slope: Slope of the linear drift
|
|
79
|
+
intercept: Intercept of the linear drift (default is 0)
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
A tuple of (time array, values array)
|
|
83
|
+
"""
|
|
84
|
+
time = np.arange(n_points)
|
|
85
|
+
values = slope * time + intercept
|
|
86
|
+
return time, values
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def theoretical_allan_variance_drift(tau: float, slope: float) -> float:
|
|
90
|
+
"""Theoretical Allan variance for a drift signal.
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
tau: Averaging time
|
|
94
|
+
slope: Slope of the linear drift
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
Allan variance value for the given tau and slope
|
|
98
|
+
"""
|
|
99
|
+
return (slope**2 * tau**2) / 2
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@pytest.mark.validation
|
|
103
|
+
def test_signal_allan_variance():
|
|
104
|
+
"""Test Allan variance computation against theoretical values."""
|
|
105
|
+
n_points = get_optimal_points(test_signal_allan_variance)
|
|
106
|
+
sigma = 1.0
|
|
107
|
+
|
|
108
|
+
# Set random seed for reproducibility
|
|
109
|
+
np.random.seed(42)
|
|
110
|
+
|
|
111
|
+
# Generate and test white noise signal
|
|
112
|
+
time_white = np.arange(n_points)
|
|
113
|
+
values_white = generate_white_noise(n_points, sigma)
|
|
114
|
+
sig1 = sigima.objects.create_signal("White Noise Test", time_white, values_white)
|
|
115
|
+
|
|
116
|
+
# Define Allan variance parameters - limit max_tau for more reliable statistics
|
|
117
|
+
param = sigima.params.AllanVarianceParam()
|
|
118
|
+
param.max_tau = 20 # Limited to ensure sufficient samples for averaging
|
|
119
|
+
|
|
120
|
+
# Compute Allan variance using the high-level function
|
|
121
|
+
res1 = sigima.proc.signal.allan_variance(sig1, param)
|
|
122
|
+
th_av_white = theoretical_allan_variance_white_noise(res1.x, sigma)
|
|
123
|
+
|
|
124
|
+
# Use relative tolerance for white noise (statistical variation scales)
|
|
125
|
+
# 20% tolerance accounts for statistical variance in Allan estimator
|
|
126
|
+
check_array_result(
|
|
127
|
+
"White noise Allan variance",
|
|
128
|
+
res1.y,
|
|
129
|
+
th_av_white,
|
|
130
|
+
rtol=0.20,
|
|
131
|
+
atol=0.005,
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
# Generate and test drift signal (deterministic - same seed not needed)
|
|
135
|
+
slope = 0.01
|
|
136
|
+
time, values = generate_drift_signal(n_points, slope)
|
|
137
|
+
sig2 = sigima.objects.create_signal("Drift Test", time, values)
|
|
138
|
+
|
|
139
|
+
# Compute Allan variance using the high-level function
|
|
140
|
+
res2 = sigima.proc.signal.allan_variance(sig2, param)
|
|
141
|
+
th_av_drift = theoretical_allan_variance_drift(res2.x, slope)
|
|
142
|
+
|
|
143
|
+
# Drift is deterministic, tighter tolerances apply
|
|
144
|
+
check_array_result(
|
|
145
|
+
"Drift Allan variance",
|
|
146
|
+
res2.y,
|
|
147
|
+
th_av_drift,
|
|
148
|
+
rtol=0.05,
|
|
149
|
+
atol=0.0001,
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
@pytest.mark.validation
|
|
154
|
+
def test_signal_allan_deviation():
|
|
155
|
+
"""Test Allan deviation computation against theoretical values."""
|
|
156
|
+
n_points = get_optimal_points(test_signal_allan_deviation)
|
|
157
|
+
sigma = 1.0
|
|
158
|
+
|
|
159
|
+
# Set random seed for reproducibility
|
|
160
|
+
np.random.seed(43)
|
|
161
|
+
|
|
162
|
+
# Generate and test white noise signal
|
|
163
|
+
time_white = np.arange(n_points)
|
|
164
|
+
values_white = generate_white_noise(n_points, sigma)
|
|
165
|
+
sig1 = sigima.objects.create_signal("White Noise Test", time_white, values_white)
|
|
166
|
+
|
|
167
|
+
# Define Allan variance parameters - limit max_tau for more reliable statistics
|
|
168
|
+
param = sigima.params.AllanVarianceParam()
|
|
169
|
+
param.max_tau = 20 # Limited to ensure sufficient samples for averaging
|
|
170
|
+
|
|
171
|
+
# Compute Allan deviation using the high-level function
|
|
172
|
+
res1 = sigima.proc.signal.allan_deviation(sig1, param)
|
|
173
|
+
th_av_white = theoretical_allan_variance_white_noise(res1.x, sigma)
|
|
174
|
+
|
|
175
|
+
# Use relative tolerance for white noise (statistical variation scales)
|
|
176
|
+
# 20% tolerance accounts for statistical variance in Allan estimator
|
|
177
|
+
check_array_result(
|
|
178
|
+
"White noise Allan deviation",
|
|
179
|
+
res1.y,
|
|
180
|
+
np.sqrt(th_av_white),
|
|
181
|
+
rtol=0.20,
|
|
182
|
+
atol=0.005,
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
# Generate and test drift signal (deterministic - same seed not needed)
|
|
186
|
+
slope = 0.01
|
|
187
|
+
time, values = generate_drift_signal(n_points, slope)
|
|
188
|
+
sig2 = sigima.objects.create_signal("Drift Test", time, values)
|
|
189
|
+
|
|
190
|
+
# Compute Allan deviation using the high-level function
|
|
191
|
+
res2 = sigima.proc.signal.allan_deviation(sig2, param)
|
|
192
|
+
th_av_drift = theoretical_allan_variance_drift(res2.x, slope)
|
|
193
|
+
|
|
194
|
+
# Drift is deterministic, tighter tolerances apply
|
|
195
|
+
check_array_result(
|
|
196
|
+
"Drift Allan deviation",
|
|
197
|
+
res2.y,
|
|
198
|
+
np.sqrt(th_av_drift),
|
|
199
|
+
rtol=0.05,
|
|
200
|
+
atol=0.0001,
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
@pytest.mark.validation
|
|
205
|
+
def test_signal_overlapping_allan_variance():
|
|
206
|
+
"""Test Overlapping Allan variance computation."""
|
|
207
|
+
n_points = get_optimal_points(test_signal_overlapping_allan_variance)
|
|
208
|
+
sigma = 1.0
|
|
209
|
+
|
|
210
|
+
# Generate and test white noise signal
|
|
211
|
+
time_white = np.arange(n_points)
|
|
212
|
+
values_white = generate_white_noise(n_points, sigma)
|
|
213
|
+
sig1 = sigima.objects.create_signal("White Noise Test", time_white, values_white)
|
|
214
|
+
|
|
215
|
+
# Define Allan variance parameters
|
|
216
|
+
param = sigima.params.AllanVarianceParam()
|
|
217
|
+
param.max_tau = 50
|
|
218
|
+
|
|
219
|
+
# Compute Overlapping Allan variance using the high-level function
|
|
220
|
+
res1 = sigima.proc.signal.overlapping_allan_variance(sig1, param)
|
|
221
|
+
|
|
222
|
+
# Overlapping Allan variance should produce finite, positive results
|
|
223
|
+
assert len(res1.y) > 0, "Overlapping Allan variance should produce results"
|
|
224
|
+
valid_values = res1.y[~np.isnan(res1.y)]
|
|
225
|
+
assert len(valid_values) > 0, "Overlapping Allan variance should have valid values"
|
|
226
|
+
assert np.all(valid_values > 0), "Overlapping Allan variance should be positive"
|
|
227
|
+
|
|
228
|
+
# For white noise, overlapping Allan variance should be related to the noise level
|
|
229
|
+
# and generally decrease with tau (though not necessarily following exact 1/tau)
|
|
230
|
+
expected_order = sigma**2
|
|
231
|
+
assert np.mean(valid_values) < 10 * expected_order, (
|
|
232
|
+
"Overlapping Allan variance should be reasonable for white noise"
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
# Generate and test drift signal
|
|
236
|
+
slope = 0.01
|
|
237
|
+
time, values = generate_drift_signal(n_points, slope)
|
|
238
|
+
sig2 = sigima.objects.create_signal("Drift Test", time, values)
|
|
239
|
+
|
|
240
|
+
# Compute Overlapping Allan variance using the high-level function
|
|
241
|
+
res2 = sigima.proc.signal.overlapping_allan_variance(sig2, param)
|
|
242
|
+
|
|
243
|
+
# For drift signals, overlapping Allan variance should also be finite and positive
|
|
244
|
+
valid_drift_values = res2.y[~np.isnan(res2.y)]
|
|
245
|
+
assert len(valid_drift_values) > 0, (
|
|
246
|
+
"Overlapping Allan variance should work for drift"
|
|
247
|
+
)
|
|
248
|
+
assert np.all(valid_drift_values > 0), (
|
|
249
|
+
"Overlapping Allan variance should be positive"
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
# Compare with regular Allan variance to ensure overlapping version
|
|
253
|
+
# produces reasonable results
|
|
254
|
+
res_regular = sigima.proc.signal.allan_variance(sig1, param)
|
|
255
|
+
valid_regular = res_regular.y[~np.isnan(res_regular.y)]
|
|
256
|
+
if len(valid_regular) > 0 and len(valid_values) > 0:
|
|
257
|
+
# Results should be of similar order of magnitude
|
|
258
|
+
ratio = np.mean(valid_values) / np.mean(valid_regular)
|
|
259
|
+
assert 0.1 < ratio < 10, (
|
|
260
|
+
"Overlapping and regular Allan variance should be of similar magnitude"
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
@pytest.mark.validation
|
|
265
|
+
def test_signal_modified_allan_variance():
|
|
266
|
+
"""Test Modified Allan variance computation."""
|
|
267
|
+
n_points = get_optimal_points(test_signal_modified_allan_variance)
|
|
268
|
+
sigma = 1.0
|
|
269
|
+
|
|
270
|
+
# Generate and test white noise signal
|
|
271
|
+
time_white = np.arange(n_points)
|
|
272
|
+
values_white = generate_white_noise(n_points, sigma)
|
|
273
|
+
sig1 = sigima.objects.create_signal("White Noise Test", time_white, values_white)
|
|
274
|
+
|
|
275
|
+
# Define Allan variance parameters
|
|
276
|
+
param = sigima.params.AllanVarianceParam()
|
|
277
|
+
param.max_tau = 50
|
|
278
|
+
|
|
279
|
+
# Compute Modified Allan variance using the high-level function
|
|
280
|
+
res1 = sigima.proc.signal.modified_allan_variance(sig1, param)
|
|
281
|
+
|
|
282
|
+
# For white noise, Modified Allan variance should be proportional to 1/tau
|
|
283
|
+
# The exact relationship depends on the specific implementation
|
|
284
|
+
# We check that values are reasonable and decrease with tau
|
|
285
|
+
assert len(res1.y) > 0, "Modified Allan variance should produce results"
|
|
286
|
+
assert np.all(res1.y[~np.isnan(res1.y)] > 0), (
|
|
287
|
+
"Modified Allan variance should be positive"
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
# For white noise, Modified Allan variance typically decreases with tau
|
|
291
|
+
valid_indices = ~np.isnan(res1.y)
|
|
292
|
+
if np.sum(valid_indices) > 1:
|
|
293
|
+
valid_y = res1.y[valid_indices]
|
|
294
|
+
# Check general decreasing trend for first few points
|
|
295
|
+
if len(valid_y) >= 3:
|
|
296
|
+
assert valid_y[2] < valid_y[0], (
|
|
297
|
+
"Modified Allan variance should generally decrease for white noise"
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
@pytest.mark.validation
|
|
302
|
+
def test_signal_hadamard_variance():
|
|
303
|
+
"""Test Hadamard variance computation."""
|
|
304
|
+
n_points = get_optimal_points(test_signal_hadamard_variance)
|
|
305
|
+
sigma = 1.0
|
|
306
|
+
|
|
307
|
+
# Generate and test white noise signal
|
|
308
|
+
time_white = np.arange(n_points)
|
|
309
|
+
values_white = generate_white_noise(n_points, sigma)
|
|
310
|
+
sig1 = sigima.objects.create_signal("White Noise Test", time_white, values_white)
|
|
311
|
+
|
|
312
|
+
# Define Allan variance parameters
|
|
313
|
+
param = sigima.params.AllanVarianceParam()
|
|
314
|
+
param.max_tau = 50
|
|
315
|
+
|
|
316
|
+
# Compute Hadamard variance using the high-level function
|
|
317
|
+
res1 = sigima.proc.signal.hadamard_variance(sig1, param)
|
|
318
|
+
|
|
319
|
+
# For white noise, Hadamard variance should be finite and positive
|
|
320
|
+
assert len(res1.y) > 0, "Hadamard variance should produce results"
|
|
321
|
+
valid_values = res1.y[~np.isnan(res1.y)]
|
|
322
|
+
assert len(valid_values) > 0, "Hadamard variance should have valid values"
|
|
323
|
+
assert np.all(valid_values > 0), "Hadamard variance should be positive"
|
|
324
|
+
|
|
325
|
+
# Generate and test linear drift signal
|
|
326
|
+
# (Hadamard variance is robust to linear drift)
|
|
327
|
+
slope = 0.01
|
|
328
|
+
time, values = generate_drift_signal(n_points, slope)
|
|
329
|
+
sig2 = sigima.objects.create_signal("Drift Test", time, values)
|
|
330
|
+
|
|
331
|
+
# Compute Hadamard variance for drift signal
|
|
332
|
+
res2 = sigima.proc.signal.hadamard_variance(sig2, param)
|
|
333
|
+
|
|
334
|
+
# Hadamard variance should be less sensitive to linear drift than Allan variance
|
|
335
|
+
# For pure linear drift, Hadamard variance should be close to zero or very small
|
|
336
|
+
valid_drift_values = res2.y[~np.isnan(res2.y)]
|
|
337
|
+
if len(valid_drift_values) > 0:
|
|
338
|
+
# Hadamard variance should be smaller for drift signals
|
|
339
|
+
assert np.mean(valid_drift_values) < np.mean(valid_values), (
|
|
340
|
+
"Hadamard variance should be smaller for drift signals than white noise"
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
@pytest.mark.validation
|
|
345
|
+
def test_signal_total_variance():
|
|
346
|
+
"""Test Total variance computation."""
|
|
347
|
+
n_points = get_optimal_points(test_signal_total_variance)
|
|
348
|
+
sigma = 1.0
|
|
349
|
+
|
|
350
|
+
# Generate and test white noise signal
|
|
351
|
+
time_white = np.arange(n_points)
|
|
352
|
+
values_white = generate_white_noise(n_points, sigma)
|
|
353
|
+
sig1 = sigima.objects.create_signal("White Noise Test", time_white, values_white)
|
|
354
|
+
|
|
355
|
+
# Define Allan variance parameters
|
|
356
|
+
param = sigima.params.AllanVarianceParam()
|
|
357
|
+
param.max_tau = 50
|
|
358
|
+
|
|
359
|
+
# Compute Total variance using the high-level function
|
|
360
|
+
res1 = sigima.proc.signal.total_variance(sig1, param)
|
|
361
|
+
|
|
362
|
+
# Total variance should be finite and positive
|
|
363
|
+
assert len(res1.y) > 0, "Total variance should produce results"
|
|
364
|
+
valid_values = res1.y[~np.isnan(res1.y)]
|
|
365
|
+
assert len(valid_values) > 0, "Total variance should have valid values"
|
|
366
|
+
assert np.all(valid_values > 0), "Total variance should be positive"
|
|
367
|
+
|
|
368
|
+
# For white noise, total variance should be related to the noise level
|
|
369
|
+
# and should be of the same order of magnitude as the square of the noise
|
|
370
|
+
expected_order = sigma**2
|
|
371
|
+
assert np.mean(valid_values) < 100 * expected_order, (
|
|
372
|
+
"Total variance should be reasonable for white noise"
|
|
373
|
+
)
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
@pytest.mark.validation
|
|
377
|
+
def test_signal_time_deviation():
|
|
378
|
+
"""Test Time Deviation computation."""
|
|
379
|
+
n_points = get_optimal_points(test_signal_time_deviation)
|
|
380
|
+
sigma = 1.0
|
|
381
|
+
|
|
382
|
+
# Generate and test white noise signal
|
|
383
|
+
time_white = np.arange(n_points)
|
|
384
|
+
values_white = generate_white_noise(n_points, sigma)
|
|
385
|
+
sig1 = sigima.objects.create_signal("White Noise Test", time_white, values_white)
|
|
386
|
+
|
|
387
|
+
# Define Allan variance parameters
|
|
388
|
+
param = sigima.params.AllanVarianceParam()
|
|
389
|
+
param.max_tau = 50
|
|
390
|
+
|
|
391
|
+
# Compute Time Deviation using the high-level function
|
|
392
|
+
res1 = sigima.proc.signal.time_deviation(sig1, param)
|
|
393
|
+
|
|
394
|
+
# Time deviation should be finite and positive
|
|
395
|
+
assert len(res1.y) > 0, "Time deviation should produce results"
|
|
396
|
+
valid_values = res1.y[~np.isnan(res1.y)]
|
|
397
|
+
assert len(valid_values) > 0, "Time deviation should have valid values"
|
|
398
|
+
assert np.all(valid_values > 0), "Time deviation should be positive"
|
|
399
|
+
|
|
400
|
+
# Time deviation is related to Allan variance: TDEV = sqrt(AVAR) * tau
|
|
401
|
+
# So it should increase with tau for white noise
|
|
402
|
+
if len(valid_values) >= 2:
|
|
403
|
+
valid_x = res1.x[~np.isnan(res1.y)]
|
|
404
|
+
# Check that time deviation generally increases with tau for white noise
|
|
405
|
+
correlation = np.corrcoef(valid_x[: len(valid_values)], valid_values)[0, 1]
|
|
406
|
+
assert correlation > 0.5, (
|
|
407
|
+
"Time deviation should generally increase with tau for white noise"
|
|
408
|
+
)
|
|
409
|
+
|
|
410
|
+
# Compare with Allan deviation to verify the relationship
|
|
411
|
+
res_adev = sigima.proc.signal.allan_deviation(sig1, param)
|
|
412
|
+
|
|
413
|
+
# TDEV = ADEV * tau (approximately)
|
|
414
|
+
# Check this relationship for some values
|
|
415
|
+
for i, tau in enumerate(res1.x[: min(5, len(res1.x))]):
|
|
416
|
+
if not np.isnan(res1.y[i]) and not np.isnan(res_adev.y[i]) and tau > 0:
|
|
417
|
+
expected_tdev = res_adev.y[i] * tau
|
|
418
|
+
relative_error = abs(res1.y[i] - expected_tdev) / expected_tdev
|
|
419
|
+
assert relative_error < 0.1, (
|
|
420
|
+
f"Time deviation should match ADEV * tau relationship at tau={tau}"
|
|
421
|
+
)
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
if __name__ == "__main__":
|
|
425
|
+
test_signal_allan_variance()
|
|
426
|
+
test_signal_allan_deviation()
|
|
427
|
+
test_signal_overlapping_allan_variance()
|
|
428
|
+
test_signal_modified_allan_variance()
|
|
429
|
+
test_signal_hadamard_variance()
|
|
430
|
+
test_signal_total_variance()
|
|
431
|
+
test_signal_time_deviation()
|