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.
Files changed (264) hide show
  1. sigima/__init__.py +142 -2
  2. sigima/client/__init__.py +105 -0
  3. sigima/client/base.py +780 -0
  4. sigima/client/remote.py +469 -0
  5. sigima/client/stub.py +814 -0
  6. sigima/client/utils.py +90 -0
  7. sigima/config.py +444 -0
  8. sigima/data/logo/Sigima.svg +135 -0
  9. sigima/data/tests/annotations.json +798 -0
  10. sigima/data/tests/curve_fitting/exponential_fit.txt +511 -0
  11. sigima/data/tests/curve_fitting/gaussian_fit.txt +100 -0
  12. sigima/data/tests/curve_fitting/piecewiseexponential_fit.txt +1022 -0
  13. sigima/data/tests/curve_fitting/polynomial_fit.txt +100 -0
  14. sigima/data/tests/curve_fitting/twohalfgaussian_fit.txt +1000 -0
  15. sigima/data/tests/curve_formats/bandwidth.txt +201 -0
  16. sigima/data/tests/curve_formats/boxcar.npy +0 -0
  17. sigima/data/tests/curve_formats/datetime.txt +1001 -0
  18. sigima/data/tests/curve_formats/dynamic_parameters.txt +4000 -0
  19. sigima/data/tests/curve_formats/fw1e2.txt +301 -0
  20. sigima/data/tests/curve_formats/fwhm.txt +319 -0
  21. sigima/data/tests/curve_formats/multiple_curves.csv +29 -0
  22. sigima/data/tests/curve_formats/noised_saw.mat +0 -0
  23. sigima/data/tests/curve_formats/oscilloscope.csv +111 -0
  24. sigima/data/tests/curve_formats/other/other2/recursive2.txt +5 -0
  25. sigima/data/tests/curve_formats/other/recursive1.txt +5 -0
  26. sigima/data/tests/curve_formats/paracetamol.npy +0 -0
  27. sigima/data/tests/curve_formats/paracetamol.txt +1010 -0
  28. sigima/data/tests/curve_formats/paracetamol_dx_dy.csv +1000 -0
  29. sigima/data/tests/curve_formats/paracetamol_dy.csv +1001 -0
  30. sigima/data/tests/curve_formats/pulse1.npy +0 -0
  31. sigima/data/tests/curve_formats/pulse2.npy +0 -0
  32. sigima/data/tests/curve_formats/simple.txt +5 -0
  33. sigima/data/tests/curve_formats/spectrum.mca +2139 -0
  34. sigima/data/tests/curve_formats/square2.npy +0 -0
  35. sigima/data/tests/curve_formats/step.npy +0 -0
  36. sigima/data/tests/fabry-perot1.jpg +0 -0
  37. sigima/data/tests/fabry-perot2.jpg +0 -0
  38. sigima/data/tests/flower.npy +0 -0
  39. sigima/data/tests/image_formats/NF 180338201.scor-data +11003 -0
  40. sigima/data/tests/image_formats/binary_image.npy +0 -0
  41. sigima/data/tests/image_formats/binary_image.png +0 -0
  42. sigima/data/tests/image_formats/centroid_test.npy +0 -0
  43. sigima/data/tests/image_formats/coordinated_text/complex_image.txt +10011 -0
  44. sigima/data/tests/image_formats/coordinated_text/complex_ref_image.txt +10010 -0
  45. sigima/data/tests/image_formats/coordinated_text/image.txt +15 -0
  46. sigima/data/tests/image_formats/coordinated_text/image2.txt +14 -0
  47. sigima/data/tests/image_formats/coordinated_text/image_no_unit_no_label.txt +14 -0
  48. sigima/data/tests/image_formats/coordinated_text/image_with_nan.txt +15 -0
  49. sigima/data/tests/image_formats/coordinated_text/image_with_unit.txt +14 -0
  50. sigima/data/tests/image_formats/fiber.csv +480 -0
  51. sigima/data/tests/image_formats/fiber.jpg +0 -0
  52. sigima/data/tests/image_formats/fiber.png +0 -0
  53. sigima/data/tests/image_formats/fiber.txt +480 -0
  54. sigima/data/tests/image_formats/gaussian_spot_with_noise.npy +0 -0
  55. sigima/data/tests/image_formats/mr-brain.dcm +0 -0
  56. sigima/data/tests/image_formats/noised_gaussian.mat +0 -0
  57. sigima/data/tests/image_formats/sif_reader/nd_lum_image_no_glue.sif +0 -0
  58. sigima/data/tests/image_formats/sif_reader/raman1.sif +0 -0
  59. sigima/data/tests/image_formats/tiling.txt +10 -0
  60. sigima/data/tests/image_formats/uint16.tiff +0 -0
  61. sigima/data/tests/image_formats/uint8.tiff +0 -0
  62. sigima/data/tests/laser_beam/TEM00_z_13.jpg +0 -0
  63. sigima/data/tests/laser_beam/TEM00_z_18.jpg +0 -0
  64. sigima/data/tests/laser_beam/TEM00_z_23.jpg +0 -0
  65. sigima/data/tests/laser_beam/TEM00_z_30.jpg +0 -0
  66. sigima/data/tests/laser_beam/TEM00_z_35.jpg +0 -0
  67. sigima/data/tests/laser_beam/TEM00_z_40.jpg +0 -0
  68. sigima/data/tests/laser_beam/TEM00_z_45.jpg +0 -0
  69. sigima/data/tests/laser_beam/TEM00_z_50.jpg +0 -0
  70. sigima/data/tests/laser_beam/TEM00_z_55.jpg +0 -0
  71. sigima/data/tests/laser_beam/TEM00_z_60.jpg +0 -0
  72. sigima/data/tests/laser_beam/TEM00_z_65.jpg +0 -0
  73. sigima/data/tests/laser_beam/TEM00_z_70.jpg +0 -0
  74. sigima/data/tests/laser_beam/TEM00_z_75.jpg +0 -0
  75. sigima/data/tests/laser_beam/TEM00_z_80.jpg +0 -0
  76. sigima/enums.py +195 -0
  77. sigima/io/__init__.py +123 -0
  78. sigima/io/base.py +311 -0
  79. sigima/io/common/__init__.py +5 -0
  80. sigima/io/common/basename.py +164 -0
  81. sigima/io/common/converters.py +189 -0
  82. sigima/io/common/objmeta.py +181 -0
  83. sigima/io/common/textreader.py +58 -0
  84. sigima/io/convenience.py +157 -0
  85. sigima/io/enums.py +17 -0
  86. sigima/io/ftlab.py +395 -0
  87. sigima/io/image/__init__.py +9 -0
  88. sigima/io/image/base.py +177 -0
  89. sigima/io/image/formats.py +1016 -0
  90. sigima/io/image/funcs.py +414 -0
  91. sigima/io/signal/__init__.py +9 -0
  92. sigima/io/signal/base.py +129 -0
  93. sigima/io/signal/formats.py +290 -0
  94. sigima/io/signal/funcs.py +723 -0
  95. sigima/objects/__init__.py +260 -0
  96. sigima/objects/base.py +937 -0
  97. sigima/objects/image/__init__.py +88 -0
  98. sigima/objects/image/creation.py +556 -0
  99. sigima/objects/image/object.py +524 -0
  100. sigima/objects/image/roi.py +904 -0
  101. sigima/objects/scalar/__init__.py +57 -0
  102. sigima/objects/scalar/common.py +215 -0
  103. sigima/objects/scalar/geometry.py +502 -0
  104. sigima/objects/scalar/table.py +784 -0
  105. sigima/objects/shape.py +290 -0
  106. sigima/objects/signal/__init__.py +133 -0
  107. sigima/objects/signal/constants.py +27 -0
  108. sigima/objects/signal/creation.py +1428 -0
  109. sigima/objects/signal/object.py +444 -0
  110. sigima/objects/signal/roi.py +274 -0
  111. sigima/params.py +405 -0
  112. sigima/proc/__init__.py +96 -0
  113. sigima/proc/base.py +381 -0
  114. sigima/proc/decorator.py +330 -0
  115. sigima/proc/image/__init__.py +513 -0
  116. sigima/proc/image/arithmetic.py +335 -0
  117. sigima/proc/image/base.py +260 -0
  118. sigima/proc/image/detection.py +519 -0
  119. sigima/proc/image/edges.py +329 -0
  120. sigima/proc/image/exposure.py +406 -0
  121. sigima/proc/image/extraction.py +458 -0
  122. sigima/proc/image/filtering.py +219 -0
  123. sigima/proc/image/fourier.py +147 -0
  124. sigima/proc/image/geometry.py +661 -0
  125. sigima/proc/image/mathops.py +340 -0
  126. sigima/proc/image/measurement.py +195 -0
  127. sigima/proc/image/morphology.py +155 -0
  128. sigima/proc/image/noise.py +107 -0
  129. sigima/proc/image/preprocessing.py +182 -0
  130. sigima/proc/image/restoration.py +235 -0
  131. sigima/proc/image/threshold.py +217 -0
  132. sigima/proc/image/transformations.py +393 -0
  133. sigima/proc/signal/__init__.py +376 -0
  134. sigima/proc/signal/analysis.py +206 -0
  135. sigima/proc/signal/arithmetic.py +551 -0
  136. sigima/proc/signal/base.py +262 -0
  137. sigima/proc/signal/extraction.py +60 -0
  138. sigima/proc/signal/features.py +310 -0
  139. sigima/proc/signal/filtering.py +484 -0
  140. sigima/proc/signal/fitting.py +276 -0
  141. sigima/proc/signal/fourier.py +259 -0
  142. sigima/proc/signal/mathops.py +420 -0
  143. sigima/proc/signal/processing.py +580 -0
  144. sigima/proc/signal/stability.py +175 -0
  145. sigima/proc/title_formatting.py +227 -0
  146. sigima/proc/validation.py +272 -0
  147. sigima/tests/__init__.py +7 -0
  148. sigima/tests/common/__init__.py +0 -0
  149. sigima/tests/common/arithmeticparam_unit_test.py +26 -0
  150. sigima/tests/common/basename_unit_test.py +126 -0
  151. sigima/tests/common/client_unit_test.py +412 -0
  152. sigima/tests/common/converters_unit_test.py +77 -0
  153. sigima/tests/common/decorator_unit_test.py +176 -0
  154. sigima/tests/common/examples_unit_test.py +104 -0
  155. sigima/tests/common/kernel_normalization_unit_test.py +242 -0
  156. sigima/tests/common/roi_basic_unit_test.py +73 -0
  157. sigima/tests/common/roi_geometry_unit_test.py +171 -0
  158. sigima/tests/common/scalar_builder_unit_test.py +142 -0
  159. sigima/tests/common/scalar_unit_test.py +991 -0
  160. sigima/tests/common/shape_unit_test.py +183 -0
  161. sigima/tests/common/stat_unit_test.py +138 -0
  162. sigima/tests/common/title_formatting_unit_test.py +338 -0
  163. sigima/tests/common/tools_coordinates_unit_test.py +60 -0
  164. sigima/tests/common/transformations_unit_test.py +178 -0
  165. sigima/tests/common/validation_unit_test.py +205 -0
  166. sigima/tests/conftest.py +129 -0
  167. sigima/tests/data.py +998 -0
  168. sigima/tests/env.py +280 -0
  169. sigima/tests/guiutils.py +163 -0
  170. sigima/tests/helpers.py +532 -0
  171. sigima/tests/image/__init__.py +28 -0
  172. sigima/tests/image/binning_unit_test.py +128 -0
  173. sigima/tests/image/blob_detection_unit_test.py +312 -0
  174. sigima/tests/image/centroid_unit_test.py +170 -0
  175. sigima/tests/image/check_2d_array_unit_test.py +63 -0
  176. sigima/tests/image/contour_unit_test.py +172 -0
  177. sigima/tests/image/convolution_unit_test.py +178 -0
  178. sigima/tests/image/datatype_unit_test.py +67 -0
  179. sigima/tests/image/edges_unit_test.py +155 -0
  180. sigima/tests/image/enclosingcircle_unit_test.py +88 -0
  181. sigima/tests/image/exposure_unit_test.py +223 -0
  182. sigima/tests/image/fft2d_unit_test.py +189 -0
  183. sigima/tests/image/filtering_unit_test.py +166 -0
  184. sigima/tests/image/geometry_unit_test.py +654 -0
  185. sigima/tests/image/hough_circle_unit_test.py +147 -0
  186. sigima/tests/image/imageobj_unit_test.py +737 -0
  187. sigima/tests/image/morphology_unit_test.py +71 -0
  188. sigima/tests/image/noise_unit_test.py +57 -0
  189. sigima/tests/image/offset_correction_unit_test.py +72 -0
  190. sigima/tests/image/operation_unit_test.py +518 -0
  191. sigima/tests/image/peak2d_limits_unit_test.py +41 -0
  192. sigima/tests/image/peak2d_unit_test.py +133 -0
  193. sigima/tests/image/profile_unit_test.py +159 -0
  194. sigima/tests/image/projections_unit_test.py +121 -0
  195. sigima/tests/image/restoration_unit_test.py +141 -0
  196. sigima/tests/image/roi2dparam_unit_test.py +53 -0
  197. sigima/tests/image/roi_advanced_unit_test.py +588 -0
  198. sigima/tests/image/roi_grid_unit_test.py +279 -0
  199. sigima/tests/image/spectrum2d_unit_test.py +40 -0
  200. sigima/tests/image/threshold_unit_test.py +91 -0
  201. sigima/tests/io/__init__.py +0 -0
  202. sigima/tests/io/addnewformat_unit_test.py +125 -0
  203. sigima/tests/io/convenience_funcs_unit_test.py +470 -0
  204. sigima/tests/io/coordinated_text_format_unit_test.py +495 -0
  205. sigima/tests/io/datetime_csv_unit_test.py +198 -0
  206. sigima/tests/io/imageio_formats_test.py +41 -0
  207. sigima/tests/io/ioregistry_unit_test.py +69 -0
  208. sigima/tests/io/objmeta_unit_test.py +87 -0
  209. sigima/tests/io/readobj_unit_test.py +130 -0
  210. sigima/tests/io/readwriteobj_unit_test.py +67 -0
  211. sigima/tests/signal/__init__.py +0 -0
  212. sigima/tests/signal/analysis_unit_test.py +135 -0
  213. sigima/tests/signal/check_1d_arrays_unit_test.py +169 -0
  214. sigima/tests/signal/convolution_unit_test.py +404 -0
  215. sigima/tests/signal/datetime_unit_test.py +176 -0
  216. sigima/tests/signal/fft1d_unit_test.py +303 -0
  217. sigima/tests/signal/filters_unit_test.py +403 -0
  218. sigima/tests/signal/fitting_unit_test.py +929 -0
  219. sigima/tests/signal/fwhm_unit_test.py +111 -0
  220. sigima/tests/signal/noise_unit_test.py +128 -0
  221. sigima/tests/signal/offset_correction_unit_test.py +34 -0
  222. sigima/tests/signal/operation_unit_test.py +489 -0
  223. sigima/tests/signal/peakdetection_unit_test.py +145 -0
  224. sigima/tests/signal/processing_unit_test.py +657 -0
  225. sigima/tests/signal/pulse/__init__.py +112 -0
  226. sigima/tests/signal/pulse/crossing_times_unit_test.py +123 -0
  227. sigima/tests/signal/pulse/plateau_detection_unit_test.py +102 -0
  228. sigima/tests/signal/pulse/pulse_unit_test.py +1824 -0
  229. sigima/tests/signal/roi_advanced_unit_test.py +392 -0
  230. sigima/tests/signal/signalobj_unit_test.py +603 -0
  231. sigima/tests/signal/stability_unit_test.py +431 -0
  232. sigima/tests/signal/uncertainty_unit_test.py +611 -0
  233. sigima/tests/vistools.py +1030 -0
  234. sigima/tools/__init__.py +59 -0
  235. sigima/tools/checks.py +290 -0
  236. sigima/tools/coordinates.py +308 -0
  237. sigima/tools/datatypes.py +26 -0
  238. sigima/tools/image/__init__.py +97 -0
  239. sigima/tools/image/detection.py +451 -0
  240. sigima/tools/image/exposure.py +77 -0
  241. sigima/tools/image/extraction.py +48 -0
  242. sigima/tools/image/fourier.py +260 -0
  243. sigima/tools/image/geometry.py +190 -0
  244. sigima/tools/image/preprocessing.py +165 -0
  245. sigima/tools/signal/__init__.py +86 -0
  246. sigima/tools/signal/dynamic.py +254 -0
  247. sigima/tools/signal/features.py +135 -0
  248. sigima/tools/signal/filtering.py +171 -0
  249. sigima/tools/signal/fitting.py +1171 -0
  250. sigima/tools/signal/fourier.py +466 -0
  251. sigima/tools/signal/interpolation.py +70 -0
  252. sigima/tools/signal/peakdetection.py +126 -0
  253. sigima/tools/signal/pulse.py +1626 -0
  254. sigima/tools/signal/scaling.py +50 -0
  255. sigima/tools/signal/stability.py +258 -0
  256. sigima/tools/signal/windowing.py +90 -0
  257. sigima/worker.py +79 -0
  258. sigima-1.0.0.dist-info/METADATA +233 -0
  259. sigima-1.0.0.dist-info/RECORD +262 -0
  260. {sigima-0.0.1.dev0.dist-info → sigima-1.0.0.dist-info}/licenses/LICENSE +29 -29
  261. sigima-0.0.1.dev0.dist-info/METADATA +0 -60
  262. sigima-0.0.1.dev0.dist-info/RECORD +0 -6
  263. {sigima-0.0.1.dev0.dist-info → sigima-1.0.0.dist-info}/WHEEL +0 -0
  264. {sigima-0.0.1.dev0.dist-info → sigima-1.0.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,611 @@
1
+ # Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file.
2
+
3
+ """
4
+ Unit tests for uncertainty propagation in signal operations
5
+
6
+ Features from signal processing functions that include uncertainty propagation.
7
+ This test covers the mathematical functions (sqrt, log10, exp, clip, absolute,
8
+ real, imag) and arithmetic operations (addition, average, product, difference,
9
+ constant operations).
10
+ """
11
+
12
+ # pylint: disable=invalid-name # Allows short reference names like x, y, ...
13
+
14
+ from __future__ import annotations
15
+
16
+ import warnings
17
+ from typing import Callable
18
+
19
+ import numpy as np
20
+
21
+ import sigima.enums
22
+ import sigima.objects
23
+ import sigima.params
24
+ import sigima.proc.signal
25
+ import sigima.tests.data
26
+ from sigima.tests.helpers import check_array_result
27
+
28
+
29
+ def __create_signal_with_uncertainty() -> sigima.objects.SignalObj:
30
+ """Create a signal with uncertainty data for testing."""
31
+ obj = sigima.tests.data.create_periodic_signal(sigima.objects.SignalTypes.COSINE)
32
+ obj.dy = 0.1 * np.abs(obj.y) + 0.01 # 10% relative + 0.01 absolute
33
+ return obj
34
+
35
+
36
+ def __create_signal_without_uncertainty() -> sigima.objects.SignalObj:
37
+ """Create a signal without uncertainty data for testing."""
38
+ obj = sigima.tests.data.create_periodic_signal(sigima.objects.SignalTypes.COSINE)
39
+ obj.dy = None
40
+ return obj
41
+
42
+
43
+ def __verify_uncertainty_propagation(
44
+ func: Callable[[sigima.objects.SignalObj], sigima.objects.SignalObj],
45
+ param: sigima.params.GaussianParam
46
+ | sigima.params.MovingAverageParam
47
+ | sigima.params.MovingMedianParam
48
+ | None = None,
49
+ ) -> None:
50
+ """Test uncertainty propagation for a given signal processing function."""
51
+ src = __create_signal_with_uncertainty()
52
+ if param is None:
53
+ result = func(src)
54
+ else:
55
+ result = func(src, param)
56
+
57
+ # Check that uncertainties are propagated (should be unchanged for filters)
58
+ assert result.dy is not None, "Uncertainty should be propagated"
59
+ check_array_result("Uncertainty propagation", result.dy, src.dy)
60
+
61
+ # Test without uncertainty
62
+ src = __create_signal_without_uncertainty()
63
+ result_no_unc = func(src)
64
+ assert result_no_unc.dy is None, (
65
+ "Uncertainty should be None when input has no uncertainty"
66
+ )
67
+
68
+
69
+ def test_exp_uncertainty_propagation() -> None:
70
+ """Test uncertainty propagation for exponential function."""
71
+ # Test with uncertainty
72
+ src = __create_signal_with_uncertainty()
73
+ result = sigima.proc.signal.exp(src)
74
+
75
+ # Check result values
76
+ check_array_result("Exponential values", result.y, np.exp(src.y))
77
+
78
+ # Check uncertainty propagation: σ(eʸ) = eʸ * σ(y) = dst.y * σ(y)
79
+ expected_dy = np.abs(result.y) * src.dy
80
+ check_array_result("Exponential uncertainty propagation", result.dy, expected_dy)
81
+
82
+ # Test without uncertainty
83
+ src_no_unc = __create_signal_without_uncertainty()
84
+ result_no_unc = sigima.proc.signal.exp(src_no_unc)
85
+ assert result_no_unc.dy is None, (
86
+ "Uncertainty should be None when input has no uncertainty"
87
+ )
88
+
89
+
90
+ def test_sqrt_uncertainty_propagation() -> None:
91
+ """Test uncertainty propagation for square root function."""
92
+ # Test with uncertainty
93
+ src = __create_signal_with_uncertainty()
94
+
95
+ # Suppress warnings for sqrt of negative values in test data
96
+ with warnings.catch_warnings():
97
+ warnings.simplefilter("ignore", category=RuntimeWarning)
98
+ result = sigima.proc.signal.sqrt(src)
99
+
100
+ # Check result values
101
+ with warnings.catch_warnings():
102
+ warnings.simplefilter("ignore", category=RuntimeWarning)
103
+ check_array_result("Square root values", result.y, np.sqrt(src.y))
104
+
105
+ # Check uncertainty propagation: σ(√y) = σ(y) / (2√y)
106
+ with warnings.catch_warnings():
107
+ warnings.simplefilter("ignore", category=RuntimeWarning)
108
+ expected_dy = src.dy / (2 * np.sqrt(src.y))
109
+ expected_dy[np.isinf(expected_dy) | np.isnan(expected_dy)] = np.nan
110
+
111
+ check_array_result("Square root uncertainty propagation", result.dy, expected_dy)
112
+
113
+ # Test without uncertainty
114
+ src_no_unc = __create_signal_without_uncertainty()
115
+ with warnings.catch_warnings():
116
+ warnings.simplefilter("ignore", category=RuntimeWarning)
117
+ result_no_unc = sigima.proc.signal.sqrt(src_no_unc)
118
+ assert result_no_unc.dy is None, (
119
+ "Uncertainty should be None when input has no uncertainty"
120
+ )
121
+
122
+
123
+ def test_power_uncertainty_propagation() -> None:
124
+ """Test uncertainty propagation for power function."""
125
+ # Test with uncertainty
126
+ src = __create_signal_with_uncertainty()
127
+ p = 3.0
128
+ param = sigima.params.PowerParam.create(power=p)
129
+ result = sigima.proc.signal.power(src, param)
130
+
131
+ # Check result values
132
+ check_array_result("Power values", result.y, src.y**p)
133
+
134
+ # Check uncertainty propagation: σ(yᵖ) = |p * y^(p-1)| * σ(y)
135
+ with warnings.catch_warnings():
136
+ warnings.simplefilter("ignore", category=RuntimeWarning)
137
+ expected_dy = np.abs(p * src.y ** (p - 1)) * src.dy
138
+ expected_dy[np.isinf(expected_dy) | np.isnan(expected_dy)] = np.nan
139
+
140
+ check_array_result("Power uncertainty propagation", result.dy, expected_dy)
141
+
142
+ # Test without uncertainty
143
+ src_no_unc = __create_signal_without_uncertainty()
144
+ result_no_unc = sigima.proc.signal.power(src_no_unc, param)
145
+ assert result_no_unc.dy is None, (
146
+ "Uncertainty should be None when input has no uncertainty"
147
+ )
148
+
149
+
150
+ def test_log10_uncertainty_propagation() -> None:
151
+ """Test uncertainty propagation for log10 function."""
152
+ # Test with uncertainty - use positive values to avoid log domain issues
153
+ src = __create_signal_with_uncertainty()
154
+ src.y = np.abs(src.y) + 1.0 # Ensure positive values
155
+ result = sigima.proc.signal.log10(src)
156
+
157
+ # Check result values
158
+ check_array_result("Log10 values", result.y, np.log10(src.y))
159
+
160
+ # Check uncertainty propagation: σ(log₁₀(y)) = σ(y) / (y * ln(10))
161
+ with warnings.catch_warnings():
162
+ warnings.simplefilter("ignore", category=RuntimeWarning)
163
+ expected_dy = src.dy / (src.y * np.log(10))
164
+ expected_dy[np.isinf(expected_dy) | np.isnan(expected_dy)] = np.nan
165
+
166
+ check_array_result("Log10 uncertainty propagation", result.dy, expected_dy)
167
+
168
+ # Test without uncertainty
169
+ src_no_unc = __create_signal_without_uncertainty()
170
+ src_no_unc.y = np.abs(src_no_unc.y) + 1.0 # Ensure positive values
171
+ result_no_unc = sigima.proc.signal.log10(src_no_unc)
172
+ assert result_no_unc.dy is None, (
173
+ "Uncertainty should be None when input has no uncertainty"
174
+ )
175
+
176
+
177
+ def test_clip_uncertainty_propagation() -> None:
178
+ """Test uncertainty propagation for clipping function."""
179
+ # Test with uncertainty
180
+ src = __create_signal_with_uncertainty()
181
+
182
+ # Test clipping with both limits
183
+ param = sigima.params.ClipParam.create(lower=-0.5, upper=0.5)
184
+ result = sigima.proc.signal.clip(src, param)
185
+
186
+ # Check result values
187
+ expected_y = np.clip(src.y, param.lower, param.upper)
188
+ check_array_result("Clip values", result.y, expected_y)
189
+
190
+ # Check uncertainty propagation: σ(clip(y)) = σ(y) where not clipped,
191
+ # 0 where clipped
192
+ expected_dy = src.dy.copy()
193
+ expected_dy[src.y <= param.lower] = 0
194
+ expected_dy[src.y >= param.upper] = 0
195
+ check_array_result("Clip uncertainty propagation", result.dy, expected_dy)
196
+
197
+ # Test without uncertainty
198
+ src_no_unc = __create_signal_without_uncertainty()
199
+ result_no_unc = sigima.proc.signal.clip(src_no_unc, param)
200
+ assert result_no_unc.dy is None, (
201
+ "Uncertainty should be None when input has no uncertainty"
202
+ )
203
+
204
+
205
+ def test_normalize_uncertainty_propagation() -> None:
206
+ """Test uncertainty propagation for normalization function."""
207
+ # Test different normalization methods
208
+ for method in sigima.enums.NormalizationMethod:
209
+ # Test with uncertainty
210
+ src = __create_signal_with_uncertainty()
211
+ param = sigima.params.NormalizeParam()
212
+ param.method = method
213
+ result = sigima.proc.signal.normalize(src, param)
214
+
215
+ # Check that uncertainties are propagated appropriately for each method
216
+ assert result.dy is not None, f"Uncertainty should be propagated for {method}"
217
+
218
+ # For most methods, uncertainty should be non-zero where input uncertainty
219
+ # exists
220
+ if method != sigima.enums.NormalizationMethod.AMPLITUDE:
221
+ # For non-amplitude methods, check that uncertainties exist and are finite
222
+ assert np.any(np.isfinite(result.dy)), (
223
+ f"Some uncertainties should be finite for {method}"
224
+ )
225
+ else:
226
+ # For amplitude normalization, check the specific uncertainty formula
227
+ # σ(amplitude_norm(y)) = σ(y) / (max(y) - min(y))
228
+ denom = np.max(src.y) - np.min(src.y)
229
+ if denom != 0:
230
+ expected_dy = src.dy / denom
231
+ check_array_result(
232
+ "Amplitude normalize uncertainty propagation",
233
+ result.dy,
234
+ expected_dy,
235
+ )
236
+
237
+ # Test without uncertainty
238
+ src_no_unc = __create_signal_without_uncertainty()
239
+ result_no_unc = sigima.proc.signal.normalize(src_no_unc, param)
240
+ assert result_no_unc.dy is None, (
241
+ f"Uncertainty should be None when input has no uncertainty for {method}"
242
+ )
243
+
244
+
245
+ def test_derivative_uncertainty_propagation() -> None:
246
+ """Test uncertainty propagation for derivative function."""
247
+ # Test with uncertainty
248
+ src = __create_signal_with_uncertainty()
249
+ result = sigima.proc.signal.derivative(src)
250
+
251
+ # Check that uncertainties are propagated
252
+ assert result.dy is not None, "Uncertainty should be propagated"
253
+
254
+ # For numerical derivatives, the uncertainty depends on the finite difference scheme
255
+ # numpy.gradient uses central differences for interior points:
256
+ # dy/dx ≈ (y[i+1] - y[i-1]) / (x[i+1] - x[i-1])
257
+ # So σ(dy/dx) ≈ sqrt(σ(y[i+1])² + σ(y[i-1])²) / (x[i+1] - x[i-1])
258
+
259
+ # For a more general test, we verify that:
260
+ # 1. Uncertainties exist and are finite where input uncertainties exist
261
+ # 2. The uncertainty scaling is reasonable compared to input uncertainties
262
+ assert np.any(np.isfinite(result.dy)), "Some uncertainties should be finite"
263
+
264
+ # The derivative uncertainty should generally be larger than input uncertainty
265
+ # due to the division by dx (assuming dx < 1 for typical signals)
266
+ x = src.x
267
+ typical_dx = np.median(np.diff(x))
268
+ if typical_dx > 0:
269
+ # Expected rough scaling: derivative uncertainty ~ input uncertainty / dx
270
+ expected_scale = 1.0 / typical_dx
271
+ # Allow for significant variation due to the complexity of gradient calculation
272
+ max_ratio = np.nanmax(result.dy / src.dy)
273
+ assert max_ratio > 0.1 * expected_scale, (
274
+ f"Derivative uncertainty scaling seems too small: {max_ratio} vs "
275
+ f"expected ~{expected_scale}"
276
+ )
277
+
278
+ # Test without uncertainty
279
+ src_no_unc = __create_signal_without_uncertainty()
280
+ result_no_unc = sigima.proc.signal.derivative(src_no_unc)
281
+ assert result_no_unc.dy is None, (
282
+ "Uncertainty should be None when input has no uncertainty"
283
+ )
284
+
285
+
286
+ def test_integral_uncertainty_propagation() -> None:
287
+ """Test uncertainty propagation for integral function."""
288
+ # Test with uncertainty
289
+ src = __create_signal_with_uncertainty()
290
+ result = sigima.proc.signal.integral(src)
291
+
292
+ # Check that uncertainties are propagated
293
+ assert result.dy is not None, "Uncertainty should be propagated"
294
+
295
+ # For cumulative integration, uncertainties should accumulate
296
+ # The first point should have zero uncertainty (initial value)
297
+ assert result.dy[0] == 0.0, "Initial integral value should have zero uncertainty"
298
+
299
+ # For cumulative trapezoidal integration, uncertainties should generally increase
300
+ # as we accumulate more measurements
301
+ # Check that uncertainties are non-decreasing (allowing for numerical precision)
302
+ diff_dy = np.diff(result.dy)
303
+ # Allow small negative differences due to numerical precision
304
+ assert np.all(diff_dy >= -1e-10), "Integral uncertainties should generally increase"
305
+
306
+ # The integral uncertainties should be finite and non-negative
307
+ assert np.all(np.isfinite(result.dy)), "All integral uncertainties should be finite"
308
+ assert np.all(result.dy >= 0), "All integral uncertainties should be non-negative"
309
+
310
+ # Integral uncertainty should be positive (assuming we have some integration range)
311
+ max_integral_uncertainty = np.max(result.dy)
312
+ assert max_integral_uncertainty > 0, (
313
+ "Maximum integral uncertainty should be positive"
314
+ )
315
+
316
+ # Validate the uncertainty propagation formula implementation
317
+ # The integral function uses: σ(∫y dx) ≈ √(Σ(σ(y_i) * Δx_i)²) for trapezoidal rule
318
+ # Specifically: dy_squared = src.dy[:-1]² + src.dy[1:]²
319
+ # and dst.dy[1:] = √(cumsum(dy_squared * dx² / 4))
320
+ dx = np.diff(src.x)
321
+ dy_squared = src.dy[:-1] ** 2 + src.dy[1:] ** 2
322
+ expected_uncertainties = np.zeros_like(result.dy)
323
+ expected_uncertainties[0] = 0.0 # Initial value
324
+ expected_uncertainties[1:] = np.sqrt(np.cumsum(dy_squared * (dx**2) / 4))
325
+
326
+ # The computed uncertainties should match the expected formula
327
+ check_array_result(
328
+ "Integral uncertainty propagation", result.dy, expected_uncertainties
329
+ )
330
+
331
+ # Test without uncertainty
332
+ src_no_unc = __create_signal_without_uncertainty()
333
+ result_no_unc = sigima.proc.signal.integral(src_no_unc)
334
+ assert result_no_unc.dy is None, (
335
+ "Uncertainty should be None when input has no uncertainty"
336
+ )
337
+
338
+
339
+ def test_calibration_uncertainty_propagation() -> None:
340
+ """Test uncertainty propagation for calibration function."""
341
+ # Test with uncertainty
342
+ src = __create_signal_with_uncertainty()
343
+ # Add X uncertainty for testing X-axis calibration
344
+ src.dx = 0.05 * np.abs(src.x) + 0.001 # 5% relative + 0.001 absolute
345
+
346
+ # Test Y-axis calibration: y' = a*y + b
347
+ a, b = 2.5, 0.3
348
+ param = sigima.params.XYCalibrateParam.create(axis="y", a=a, b=b)
349
+ result = sigima.proc.signal.calibration(src, param)
350
+
351
+ # Check uncertainty propagation: σ(a*y + b) = |a| * σ(y)
352
+ expected_dy = np.abs(a) * src.dy
353
+ check_array_result(
354
+ "Y-axis calibration uncertainty propagation", result.dy, expected_dy
355
+ )
356
+
357
+ # Test X-axis calibration: x' = a*x + b
358
+ param = sigima.params.XYCalibrateParam.create(axis="x", a=a, b=b)
359
+ result = sigima.proc.signal.calibration(src, param)
360
+
361
+ # Check X uncertainty propagation: σ(a*x + b) = |a| * σ(x)
362
+ if src.dx is not None:
363
+ expected_dx = np.abs(a) * src.dx
364
+ check_array_result(
365
+ "X-axis calibration uncertainty propagation", result.dx, expected_dx
366
+ )
367
+
368
+ # Y uncertainties should remain the same for x-axis calibration
369
+ check_array_result("X-axis calibration dy unchanged", result.dy, src.dy)
370
+
371
+ # Test with negative scaling factor to check absolute value
372
+ a_neg = -1.5
373
+ param_neg = sigima.params.XYCalibrateParam.create(axis="y", a=a_neg, b=b)
374
+ result_neg = sigima.proc.signal.calibration(src, param_neg)
375
+ expected_dy_neg = np.abs(a_neg) * src.dy
376
+ check_array_result(
377
+ "Y-axis calibration negative scaling uncertainty",
378
+ result_neg.dy,
379
+ expected_dy_neg,
380
+ )
381
+
382
+ # Test without uncertainty
383
+ src_no_unc = __create_signal_without_uncertainty()
384
+ result_no_unc = sigima.proc.signal.calibration(src_no_unc, param)
385
+ assert result_no_unc.dy is None, (
386
+ "Uncertainty should be None when input has no uncertainty"
387
+ )
388
+
389
+
390
+ def test_absolute_uncertainty_propagation() -> None:
391
+ """Test uncertainty propagation for absolute value function."""
392
+ __verify_uncertainty_propagation(sigima.proc.signal.absolute)
393
+
394
+
395
+ def test_real_uncertainty_propagation() -> None:
396
+ """Test uncertainty propagation for real part function."""
397
+ __verify_uncertainty_propagation(sigima.proc.signal.real)
398
+
399
+
400
+ def test_imag_uncertainty_propagation() -> None:
401
+ """Test uncertainty propagation for imaginary part function."""
402
+ # Test with uncertainty
403
+ src = __create_signal_with_uncertainty()
404
+ result = sigima.proc.signal.imag(src)
405
+
406
+ # Check result values
407
+ check_array_result("Imaginary part values", result.y, np.imag(src.y))
408
+
409
+ # Check uncertainty propagation: uncertainties unchanged for imaginary part
410
+ check_array_result("Imaginary part uncertainty propagation", result.dy, src.dy)
411
+
412
+ # Test without uncertainty
413
+ src_no_unc = __create_signal_without_uncertainty()
414
+ result_no_unc = sigima.proc.signal.imag(src_no_unc)
415
+ assert result_no_unc.dy is None, (
416
+ "Uncertainty should be None when input has no uncertainty"
417
+ )
418
+
419
+
420
+ def test_is_uncertainty_data_available() -> None:
421
+ """Test the is_uncertainty_data_available helper function."""
422
+ # Single signal with uncertainty
423
+ src_with = __create_signal_with_uncertainty()
424
+ assert sigima.proc.signal.is_uncertainty_data_available(src_with), (
425
+ "Should return True for signal with uncertainty"
426
+ )
427
+
428
+ # Single signal without uncertainty
429
+ src_without = __create_signal_without_uncertainty()
430
+ assert not sigima.proc.signal.is_uncertainty_data_available(src_without), (
431
+ "Should return False for signal without uncertainty"
432
+ )
433
+
434
+ # List of signals - all with uncertainty
435
+ src_list_with = [__create_signal_with_uncertainty() for _ in range(3)]
436
+ assert sigima.proc.signal.is_uncertainty_data_available(src_list_with), (
437
+ "Should return True for list where all signals have uncertainty"
438
+ )
439
+
440
+ # List of signals - mixed
441
+ src_list_mixed = [
442
+ __create_signal_with_uncertainty(),
443
+ __create_signal_without_uncertainty(),
444
+ ]
445
+ assert not sigima.proc.signal.is_uncertainty_data_available(src_list_mixed), (
446
+ "Should return False for list with mixed uncertainty availability"
447
+ )
448
+
449
+ # List of signals - all without uncertainty
450
+ src_list_without = [__create_signal_without_uncertainty() for _ in range(3)]
451
+ assert not sigima.proc.signal.is_uncertainty_data_available(src_list_without), (
452
+ "Should return False for list where no signals have uncertainty"
453
+ )
454
+
455
+
456
+ def test_inverse_uncertainty_propagation() -> None:
457
+ """Test uncertainty propagation for signal inversion."""
458
+ # Test with uncertainty
459
+ src = __create_signal_with_uncertainty()
460
+ # Ensure values are not too close to zero to avoid division issues
461
+ src.y = src.y + 2.0 # Shift away from zero
462
+ result = sigima.proc.signal.inverse(src)
463
+
464
+ # Check result values
465
+ with warnings.catch_warnings():
466
+ warnings.simplefilter("ignore", category=RuntimeWarning)
467
+ expected_y = 1.0 / src.y
468
+ expected_y[np.isinf(expected_y)] = np.nan
469
+ check_array_result("Inverse values", result.y, expected_y)
470
+
471
+ # Check uncertainty propagation: σ(1/y) = |1/y| * σ(y) / |y| = σ(y) / |y|²
472
+ with warnings.catch_warnings():
473
+ warnings.simplefilter("ignore", category=RuntimeWarning)
474
+ expected_dy = np.abs(result.y) * src.dy / np.abs(src.y)
475
+ expected_dy[np.isinf(expected_dy)] = np.nan
476
+ check_array_result("Inverse uncertainty propagation", result.dy, expected_dy)
477
+
478
+ # Test without uncertainty
479
+ src_no_unc = __create_signal_without_uncertainty()
480
+ src_no_unc.y = src_no_unc.y + 2.0 # Shift away from zero
481
+ result_no_unc = sigima.proc.signal.inverse(src_no_unc)
482
+ assert result_no_unc.dy is None, (
483
+ "Uncertainty should be None when input has no uncertainty"
484
+ )
485
+
486
+
487
+ def test_gaussian_filter_uncertainty_propagation() -> None:
488
+ """Test uncertainty propagation for Gaussian filter."""
489
+ param = sigima.params.GaussianParam.create(sigma=2.0)
490
+ __verify_uncertainty_propagation(sigima.proc.signal.gaussian_filter, param)
491
+
492
+
493
+ def test_wiener_filter_uncertainty_propagation() -> None:
494
+ """Test uncertainty propagation for Wiener filter."""
495
+ __verify_uncertainty_propagation(sigima.proc.signal.wiener)
496
+
497
+
498
+ def test_moving_average_uncertainty_propagation() -> None:
499
+ """Test uncertainty propagation for moving average filter."""
500
+ param = sigima.params.MovingAverageParam.create(n=5)
501
+ __verify_uncertainty_propagation(sigima.proc.signal.moving_average, param)
502
+
503
+
504
+ def test_moving_median_uncertainty_propagation() -> None:
505
+ """Test uncertainty propagation for moving median filter."""
506
+ param = sigima.params.MovingMedianParam.create(n=5)
507
+ __verify_uncertainty_propagation(sigima.proc.signal.moving_median, param)
508
+
509
+
510
+ def test_wrap1to1func_basic_behavior() -> None:
511
+ """Test basic Wrap1to1Func behavior with uncertainty propagation.
512
+
513
+ Wrap1to1Func should preserve uncertainty unchanged for any wrapped function.
514
+ """
515
+ # Test with a mathematical function (np.sin)
516
+ # Note: This tests the wrapper behavior, not the direct sin function
517
+ compute_sin_wrapped = sigima.proc.signal.Wrap1to1Func(np.sin)
518
+
519
+ # Test with uncertainty
520
+ src = __create_signal_with_uncertainty()
521
+ result = compute_sin_wrapped(src)
522
+
523
+ # Check result values
524
+ check_array_result("Wrapped sin values", result.y, np.sin(src.y))
525
+
526
+ # Check uncertainty propagation (should be unchanged when using Wrap1to1Func)
527
+ check_array_result("Wrapped sin uncertainty propagation", result.dy, src.dy)
528
+
529
+ # Test with a custom function
530
+ def custom_multiply(y):
531
+ """Custom function: multiply by 3."""
532
+ return 3 * y
533
+
534
+ compute_custom = sigima.proc.signal.Wrap1to1Func(custom_multiply)
535
+
536
+ result_custom = compute_custom(src)
537
+
538
+ # Check result values
539
+ check_array_result("Custom multiply values", result_custom.y, 3 * src.y)
540
+
541
+ # Check uncertainty propagation (should be unchanged for any wrapped function)
542
+ check_array_result(
543
+ "Custom multiply uncertainty propagation", result_custom.dy, src.dy
544
+ )
545
+
546
+ # Test without uncertainty
547
+ src_no_unc = __create_signal_without_uncertainty()
548
+ result_no_unc = compute_custom(src_no_unc)
549
+ assert result_no_unc.dy is None, (
550
+ "Uncertainty should be None when input has no uncertainty"
551
+ )
552
+
553
+
554
+ def test_wrap1to1func_with_args_kwargs() -> None:
555
+ """Test Wrap1to1Func with additional args and kwargs."""
556
+
557
+ def power_func(y, power=2):
558
+ """Raise y to a power."""
559
+ return y**power
560
+
561
+ # Test with power=3 using kwargs
562
+ compute_power = sigima.proc.signal.Wrap1to1Func(power_func, power=3)
563
+
564
+ src = __create_signal_with_uncertainty()
565
+ result = compute_power(src)
566
+
567
+ # Check result values
568
+ check_array_result("Power 3 values", result.y, src.y**3)
569
+
570
+ # Check uncertainty propagation (should be unchanged when using Wrap1to1Func)
571
+ # Note: This is different from the mathematical uncertainty propagation
572
+ # which would be σ(y³) = 3 * y² * σ(y)
573
+ check_array_result("Power 3 uncertainty propagation", result.dy, src.dy)
574
+
575
+ # Test with positional arguments
576
+ def multiply_add(y, multiplier, addend):
577
+ """Custom function: y * multiplier + addend."""
578
+ return y * multiplier + addend
579
+
580
+ compute_multiply_add = sigima.proc.signal.Wrap1to1Func(multiply_add, 2, addend=5)
581
+
582
+ result_multiply_add = compute_multiply_add(src)
583
+
584
+ # Check result values
585
+ expected_y = src.y * 2 + 5
586
+ check_array_result("Multiply-add values", result_multiply_add.y, expected_y)
587
+
588
+ # Check uncertainty propagation (preserved unchanged)
589
+ check_array_result(
590
+ "Multiply-add uncertainty propagation", result_multiply_add.dy, src.dy
591
+ )
592
+
593
+
594
+ if __name__ == "__main__":
595
+ test_sqrt_uncertainty_propagation()
596
+ test_log10_uncertainty_propagation()
597
+ test_exp_uncertainty_propagation()
598
+ test_clip_uncertainty_propagation()
599
+ test_derivative_uncertainty_propagation()
600
+ test_integral_uncertainty_propagation()
601
+ test_absolute_uncertainty_propagation()
602
+ test_real_uncertainty_propagation()
603
+ test_imag_uncertainty_propagation()
604
+ test_is_uncertainty_data_available()
605
+ test_inverse_uncertainty_propagation()
606
+ test_gaussian_filter_uncertainty_propagation()
607
+ test_wiener_filter_uncertainty_propagation()
608
+ test_moving_average_uncertainty_propagation()
609
+ test_moving_median_uncertainty_propagation()
610
+ test_wrap1to1func_basic_behavior()
611
+ test_wrap1to1func_with_args_kwargs()