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,580 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Licensed under the terms of the BSD 3-Clause
4
+ # (see sigima/LICENSE for details)
5
+
6
+ """
7
+ Signal processing operations
8
+ ============================
9
+
10
+ This module provides signal processing operations:
11
+
12
+ - Zero padding
13
+ - Interpolation and resampling
14
+ - Convolution and deconvolution
15
+ - Signal manipulation functions
16
+
17
+ .. note::
18
+
19
+ Most operations use functions from :mod:`sigima.tools.signal` for actual
20
+ computations.
21
+ """
22
+
23
+ from __future__ import annotations
24
+
25
+ import warnings
26
+
27
+ import guidata.dataset as gds
28
+ import numpy as np
29
+ import scipy.integrate as spt
30
+ import scipy.signal as sps
31
+ from guidata.dataset import FuncProp, GetAttrProp
32
+
33
+ from sigima.config import _
34
+ from sigima.config import options as sigima_options
35
+ from sigima.enums import Interpolation1DMethod, NormalizationMethod, WindowingMethod
36
+ from sigima.objects import ROI1DParam, SignalObj
37
+ from sigima.proc.base import ClipParam, NormalizeParam, dst_2_to_1
38
+ from sigima.proc.decorator import computation_function
39
+ from sigima.tools.signal import fourier, interpolation, scaling, windowing
40
+
41
+ from .base import dst_1_to_1, is_uncertainty_data_available, restore_data_outside_roi
42
+
43
+
44
+ class InterpolationParam(gds.DataSet, title=_("Interpolation")):
45
+ """Interpolation parameters"""
46
+
47
+ method = gds.ChoiceItem(
48
+ _("Interpolation method"),
49
+ [
50
+ (Interpolation1DMethod.LINEAR, "Linear"),
51
+ (Interpolation1DMethod.SPLINE, "Spline"),
52
+ (Interpolation1DMethod.QUADRATIC, "Quadratic"),
53
+ (Interpolation1DMethod.CUBIC, "Cubic"),
54
+ (Interpolation1DMethod.BARYCENTRIC, "Barycentric"),
55
+ (Interpolation1DMethod.PCHIP, "PCHIP"),
56
+ ],
57
+ default=Interpolation1DMethod.LINEAR,
58
+ )
59
+ fill_value = gds.FloatItem(
60
+ _("Fill value"),
61
+ default=None,
62
+ help=_(
63
+ "Value to use for points outside the interpolation domain "
64
+ "(used only with linear, cubic and pchip methods)."
65
+ ),
66
+ check=False,
67
+ )
68
+
69
+
70
+ @computation_function()
71
+ def interpolate(src1: SignalObj, src2: SignalObj, p: InterpolationParam) -> SignalObj:
72
+ """Interpolate data with :py:func:`sigima.tools.signal.interpolation.interpolate`.
73
+
74
+ Args:
75
+ src1: Source signal to interpolate.
76
+ src2: Signal with new x-axis.
77
+ p: Parameters.
78
+
79
+ Returns:
80
+ Result signal object.
81
+ """
82
+ suffix = f"method={p.method}"
83
+ if p.fill_value is not None and p.method in (
84
+ Interpolation1DMethod.LINEAR,
85
+ Interpolation1DMethod.CUBIC,
86
+ Interpolation1DMethod.PCHIP,
87
+ ):
88
+ suffix += f", fill_value={p.fill_value}"
89
+ dst = dst_2_to_1(src1, src2, "interpolate", suffix)
90
+ x1, y1 = src1.get_data()
91
+ xnew, _y2 = src2.get_data()
92
+ ynew = interpolation.interpolate(x1, y1, xnew, p.method, p.fill_value)
93
+ dst.set_xydata(xnew, ynew)
94
+ return dst
95
+
96
+
97
+ class Resampling1DParam(InterpolationParam):
98
+ """Resample parameters for 1D signals"""
99
+
100
+ xmin = gds.FloatItem(_("X<sub>min</sub>"), allow_none=True)
101
+ xmax = gds.FloatItem(_("X<sub>max</sub>"), allow_none=True)
102
+ _prop = GetAttrProp("dx_or_nbpts")
103
+ _modes = (("dx", "ΔX"), ("nbpts", _("Number of points")))
104
+ mode = gds.ChoiceItem(_("Mode"), _modes, default="nbpts", radio=True).set_prop(
105
+ "display", store=_prop
106
+ )
107
+ dx = gds.FloatItem("ΔX", allow_none=True).set_prop(
108
+ "display", active=FuncProp(_prop, lambda x: x == "dx")
109
+ )
110
+ nbpts = gds.IntItem(_("Number of points"), allow_none=True).set_prop(
111
+ "display", active=FuncProp(_prop, lambda x: x == "nbpts")
112
+ )
113
+
114
+ def update_from_obj(self, obj: SignalObj) -> None:
115
+ """Update parameters from a signal object."""
116
+ if self.xmin is None:
117
+ self.xmin = obj.x[0]
118
+ if self.xmax is None:
119
+ self.xmax = obj.x[-1]
120
+ if self.dx is None:
121
+ self.dx = obj.x[1] - obj.x[0]
122
+ if self.nbpts is None:
123
+ self.nbpts = len(obj.x)
124
+
125
+
126
+ @computation_function()
127
+ def resampling(src: SignalObj, p: Resampling1DParam) -> SignalObj:
128
+ """Resample data with :py:func:`sigima.tools.signal.interpolation.interpolate`
129
+
130
+ Args:
131
+ src: source signal
132
+ p: parameters
133
+
134
+ Returns:
135
+ Result signal object
136
+ """
137
+ # Create new x-axis based on parameters
138
+ if p.mode == "dx":
139
+ assert p.dx is not None
140
+ xnew = np.arange(p.xmin, p.xmax + p.dx / 2, p.dx)
141
+ else:
142
+ assert p.nbpts is not None
143
+ xnew = np.linspace(p.xmin, p.xmax, p.nbpts)
144
+
145
+ method: Interpolation1DMethod = p.method
146
+ suffix = f"method={method.value}"
147
+ if p.fill_value is not None and method in (
148
+ Interpolation1DMethod.LINEAR,
149
+ Interpolation1DMethod.CUBIC,
150
+ Interpolation1DMethod.PCHIP,
151
+ ):
152
+ suffix += f", fill_value={p.fill_value}"
153
+
154
+ dst = dst_1_to_1(src, "resampling", suffix)
155
+ x, y = src.get_data()
156
+ ynew = interpolation.interpolate(x, y, xnew, method, p.fill_value)
157
+ dst.set_xydata(xnew, ynew)
158
+ return dst
159
+
160
+
161
+ def check_same_sample_rate(src1: SignalObj, src2: SignalObj) -> None:
162
+ """Check if two signals have a constant step size *and* the same sample rate.
163
+
164
+ Args:
165
+ src1: First signal.
166
+ src2: Second signal.
167
+
168
+ Raises:
169
+ ValueError: If the signals do not have a constant step size
170
+ or the same sample rate.
171
+ """
172
+ for sig in (src1, src2):
173
+ if not np.allclose(np.diff(sig.x), sig.x[1] - sig.x[0]):
174
+ raise ValueError(f"Signal {sig.title} must have a constant step size (dx).")
175
+ dx1 = src1.x[1] - src1.x[0]
176
+ dx2 = src2.x[1] - src2.x[0]
177
+ if not np.isclose(dx1, dx2):
178
+ raise ValueError(f"Signals must have the same sample rate (dx): {dx1} != {dx2}")
179
+
180
+
181
+ @computation_function()
182
+ def deconvolution(src1: SignalObj, src2: SignalObj) -> SignalObj:
183
+ """Compute deconvolution.
184
+
185
+ The function computes the deconvolution of a signal using
186
+ :py:func:`sigima_.algorithms.signal.fourier.deconvolve`.
187
+
188
+ Args:
189
+ src1: Source signal.
190
+ src2: Filter signal.
191
+
192
+ Returns:
193
+ Result signal.
194
+
195
+ Notes:
196
+ The kernel normalization behavior can be configured globally using
197
+ ``sigima.config.options.auto_normalize_kernel``.
198
+ """
199
+ check_same_sample_rate(src1, src2)
200
+ dst = dst_2_to_1(src1, src2, "⊛⁻¹", f"filter={src2.title}")
201
+ x1, y1 = src1.get_data()
202
+ _x2, y2 = src2.get_data()
203
+
204
+ # Get kernel normalization option from configuration
205
+ normalize_kernel = sigima_options.auto_normalize_kernel.get()
206
+
207
+ result_y = fourier.deconvolve(
208
+ x1,
209
+ y1,
210
+ y2,
211
+ normalize_kernel_flag=normalize_kernel,
212
+ reg=2.0,
213
+ gain_max=None,
214
+ auto_scale=True,
215
+ )
216
+ dst.set_xydata(x1, result_y, None, None)
217
+ restore_data_outside_roi(dst, src1)
218
+ return dst
219
+
220
+
221
+ @computation_function()
222
+ def normalize(src: SignalObj, p: NormalizeParam) -> SignalObj:
223
+ """Normalize data with :py:func:`sigima.tools.signal.level.normalize`
224
+
225
+ Args:
226
+ src: source signal
227
+ p: parameters
228
+
229
+ Returns:
230
+ Result signal object
231
+ """
232
+ method: NormalizationMethod = p.method
233
+ dst = dst_1_to_1(src, "normalize", f"ref={method.value}")
234
+ x, y = src.get_data()
235
+ normalized_y = scaling.normalize(y, method)
236
+ dst.set_xydata(x, normalized_y)
237
+
238
+ # Uncertainty propagation for normalization
239
+ # σ(y/norm_factor) = σ(y) / norm_factor
240
+ if is_uncertainty_data_available(src):
241
+ with warnings.catch_warnings():
242
+ warnings.simplefilter("ignore", category=RuntimeWarning)
243
+ # Calculate normalization factor
244
+ if method == NormalizationMethod.MAXIMUM:
245
+ norm_factor = np.nanmax(y)
246
+ elif method == NormalizationMethod.AMPLITUDE:
247
+ norm_factor = np.nanmax(y) - np.nanmin(y)
248
+ elif method == NormalizationMethod.AREA:
249
+ norm_factor = np.nansum(y)
250
+ elif method == NormalizationMethod.ENERGY:
251
+ norm_factor = np.sqrt(np.nansum(np.abs(y) ** 2))
252
+ elif method == NormalizationMethod.RMS:
253
+ norm_factor = np.sqrt(np.nanmean(np.abs(y) ** 2))
254
+ else:
255
+ raise RuntimeError(f"Unsupported normalization method: {method}")
256
+
257
+ if norm_factor != 0:
258
+ dst.dy = src.dy / np.abs(norm_factor)
259
+ else:
260
+ dst.dy[:] = np.nan
261
+
262
+ restore_data_outside_roi(dst, src)
263
+ return dst
264
+
265
+
266
+ @computation_function()
267
+ def derivative(src: SignalObj) -> SignalObj:
268
+ """Compute derivative with :py:func:`numpy.gradient`
269
+
270
+ Args:
271
+ src: source signal
272
+
273
+ Returns:
274
+ Result signal object
275
+ """
276
+ dst = dst_1_to_1(src, "derivative")
277
+ x, y = src.get_data()
278
+ dst.set_xydata(x, np.gradient(y, x))
279
+
280
+ # Uncertainty propagation for numerical derivative
281
+ # For gradient using finite differences: σ(dy/dx) ≈ σ(y) / Δx
282
+ if is_uncertainty_data_available(src):
283
+ with warnings.catch_warnings():
284
+ warnings.simplefilter("ignore", category=RuntimeWarning)
285
+ # Use the same gradient approach as numpy.gradient for uncertainty
286
+ dst.dy = np.gradient(src.dy, x)
287
+ dst.dy[np.isinf(dst.dy) | np.isnan(dst.dy)] = np.nan
288
+
289
+ restore_data_outside_roi(dst, src)
290
+ return dst
291
+
292
+
293
+ @computation_function()
294
+ def integral(src: SignalObj) -> SignalObj:
295
+ """Compute integral with :py:func:`scipy.integrate.cumulative_trapezoid`
296
+
297
+ Args:
298
+ src: source signal
299
+
300
+ Returns:
301
+ Result signal object
302
+ """
303
+ dst = dst_1_to_1(src, "integral")
304
+ x, y = src.get_data()
305
+ dst.set_xydata(x, spt.cumulative_trapezoid(y, x, initial=0.0))
306
+
307
+ # Uncertainty propagation for numerical integration
308
+ # For cumulative trapezoidal integration, uncertainties accumulate
309
+ if is_uncertainty_data_available(src):
310
+ # Propagate uncertainties through cumulative trapezoidal rule
311
+ # σ(∫y dx) ≈ √(Σ(σ(y_i) * Δx_i)²) for independent measurements
312
+ dx = np.diff(x)
313
+ dy_squared = src.dy[:-1] ** 2 + src.dy[1:] ** 2 # Trapezoidal rule uncertainty
314
+ # Propagated variance for trapezoidal integration
315
+ dst.dy = np.zeros_like(dst.y) # Initialize uncertainty array
316
+ dst.dy[0] = 0.0 # Initial value has no uncertainty
317
+ dst.dy[1:] = np.sqrt(np.cumsum(dy_squared * (dx**2) / 4))
318
+
319
+ restore_data_outside_roi(dst, src)
320
+ return dst
321
+
322
+
323
+ class XYCalibrateParam(gds.DataSet, title=_("Calibration")):
324
+ """Signal calibration parameters"""
325
+
326
+ axes = (("x", _("X-axis")), ("y", _("Y-axis")))
327
+ axis = gds.ChoiceItem(_("Calibrate"), axes, default="y")
328
+ a = gds.FloatItem("a", default=1.0)
329
+ b = gds.FloatItem("b", default=0.0)
330
+
331
+
332
+ @computation_function()
333
+ def calibration(src: SignalObj, p: XYCalibrateParam) -> SignalObj:
334
+ """Compute linear calibration
335
+
336
+ Args:
337
+ src: source signal
338
+ p: parameters
339
+
340
+ Returns:
341
+ Result signal object
342
+ """
343
+ dst = dst_1_to_1(src, "calibration", f"{p.axis}={p.a}*{p.axis}+{p.b}")
344
+ x, y = src.get_data()
345
+ if p.axis == "x":
346
+ dst.set_xydata(p.a * x + p.b, y, src.dx, src.dy)
347
+ # For X-axis calibration: uncertainties in x are scaled, y unchanged
348
+ if is_uncertainty_data_available(src):
349
+ dst.dx = np.abs(p.a) * src.dx if src.dx is not None else None
350
+ # Y uncertainties remain the same
351
+ else:
352
+ dst.set_xydata(x, p.a * y + p.b, src.dx, src.dy)
353
+ # For Y-axis calibration: σ(a*y + b) = |a| * σ(y)
354
+ if is_uncertainty_data_available(src):
355
+ if dst.dy is not None:
356
+ dst.dy *= np.abs(p.a)
357
+ restore_data_outside_roi(dst, src)
358
+ return dst
359
+
360
+
361
+ @computation_function()
362
+ def clip(src: SignalObj, p: ClipParam) -> SignalObj:
363
+ """Compute maximum data clipping with :py:func:`numpy.clip`
364
+
365
+ Args:
366
+ src: source signal
367
+ p: parameters
368
+
369
+ Returns:
370
+ Result signal object
371
+ """
372
+ dst = dst_1_to_1(src, "clip", f"[{p.lower}, {p.upper}]")
373
+ x, y = src.get_data()
374
+
375
+ # Compute result
376
+ result_y = np.clip(y, p.lower, p.upper)
377
+ dst.set_xydata(x, result_y, src.dx, src.dy)
378
+
379
+ # Uncertainty propagation: σ(clip(y)) = σ(y) where not clipped, 0 where clipped
380
+ if is_uncertainty_data_available(src):
381
+ dst.dy = src.dy.copy()
382
+ if p.lower is not None:
383
+ dst.dy[y <= p.lower] = 0
384
+ if p.upper is not None:
385
+ dst.dy[y >= p.upper] = 0
386
+
387
+ restore_data_outside_roi(dst, src)
388
+ return dst
389
+
390
+
391
+ @computation_function()
392
+ def offset_correction(src: SignalObj, p: ROI1DParam) -> SignalObj:
393
+ """Correct offset: subtract the mean value of the signal in the specified range
394
+ (baseline correction)
395
+
396
+ Args:
397
+ src: source signal
398
+ p: parameters
399
+
400
+ Returns:
401
+ Result signal object
402
+ """
403
+ dst = dst_1_to_1(src, "offset_correction", f"{p.xmin:.3g}≤x≤{p.xmax:.3g}")
404
+ _roi_x, roi_y = p.get_data(src)
405
+ dst.y -= np.mean(roi_y)
406
+ restore_data_outside_roi(dst, src)
407
+ return dst
408
+
409
+
410
+ class DetrendingParam(gds.DataSet, title=_("Detrending")):
411
+ """Detrending parameters"""
412
+
413
+ methods = (("linear", _("Linear")), ("constant", _("Constant")))
414
+ method = gds.ChoiceItem(_("Detrending method"), methods, default="linear")
415
+
416
+
417
+ @computation_function()
418
+ def detrending(src: SignalObj, p: DetrendingParam) -> SignalObj:
419
+ """Detrend data with :py:func:`scipy.signal.detrend`
420
+
421
+ Args:
422
+ src: source signal
423
+ p: parameters
424
+
425
+ Returns:
426
+ Result signal object
427
+ """
428
+ dst = dst_1_to_1(src, "detrending", f"method={p.method}")
429
+ x, y = src.get_data()
430
+ dst.set_xydata(x, sps.detrend(y, type=p.method))
431
+ restore_data_outside_roi(dst, src)
432
+ return dst
433
+
434
+
435
+ class WindowingParam(gds.DataSet, title=_("Windowing")):
436
+ """Windowing parameters."""
437
+
438
+ _meth_prop = gds.GetAttrProp("method")
439
+ method = gds.ChoiceItem(
440
+ _("Method"), WindowingMethod, default=WindowingMethod.HAMMING
441
+ ).set_prop("display", store=_meth_prop)
442
+ alpha = gds.FloatItem(
443
+ "Alpha",
444
+ default=0.5,
445
+ help=_("Shape parameter of the Tukey windowing function"),
446
+ ).set_prop(
447
+ "display", active=gds.FuncProp(_meth_prop, lambda x: x == WindowingMethod.TUKEY)
448
+ )
449
+ beta = gds.FloatItem(
450
+ "Beta",
451
+ default=14.0,
452
+ help=_("Shape parameter of the Kaiser windowing function"),
453
+ ).set_prop(
454
+ "display",
455
+ active=gds.FuncProp(_meth_prop, lambda x: x == WindowingMethod.KAISER),
456
+ )
457
+ sigma = gds.FloatItem(
458
+ "Sigma",
459
+ default=0.5,
460
+ help=_("Shape parameter of the Gaussian windowing function"),
461
+ ).set_prop(
462
+ "display",
463
+ active=gds.FuncProp(_meth_prop, lambda x: x == WindowingMethod.GAUSSIAN),
464
+ )
465
+
466
+
467
+ @computation_function()
468
+ def apply_window(src: SignalObj, p: WindowingParam) -> SignalObj:
469
+ """Compute windowing with :py:func:`sigima.tools.signal.windowing.apply_window`.
470
+
471
+ Args:
472
+ src: Source signal.
473
+ p: Parameters for windowing.
474
+
475
+ Returns:
476
+ Result signal object.
477
+ """
478
+ method: WindowingMethod = p.method
479
+ suffix = f"method={method.value}"
480
+ if method == WindowingMethod.GAUSSIAN:
481
+ suffix += f", sigma={p.sigma:.3f}"
482
+ elif method == WindowingMethod.KAISER:
483
+ suffix += f", beta={p.beta:.3f}"
484
+ elif method == WindowingMethod.TUKEY:
485
+ suffix += f", alpha={p.alpha:.3f}"
486
+ dst = dst_1_to_1(src, "apply_window", suffix)
487
+ assert p.alpha is not None
488
+ dst.y = windowing.apply_window(dst.y, method, p.alpha)
489
+ restore_data_outside_roi(dst, src)
490
+ return dst
491
+
492
+
493
+ @computation_function()
494
+ def reverse_x(src: SignalObj) -> SignalObj:
495
+ """Reverse x-axis
496
+
497
+ Args:
498
+ src: source signal
499
+
500
+ Returns:
501
+ Result signal object
502
+ """
503
+ dst = dst_1_to_1(src, "reverse_x")
504
+ dst.y = dst.y[::-1]
505
+ return dst
506
+
507
+
508
+ @computation_function()
509
+ def convolution(src1: SignalObj, src2: SignalObj) -> SignalObj:
510
+ """Compute convolution of two signals with :py:func:`scipy.signal.convolve`.
511
+
512
+ Args:
513
+ src1: Source signal 1.
514
+ src2: Source signal 2.
515
+
516
+ Returns:
517
+ Result signal.
518
+
519
+ Notes:
520
+ The behavior of kernel normalization is controlled by the global configuration
521
+ option ``sigima.config.options.auto_normalize_kernel``.
522
+ """
523
+ check_same_sample_rate(src1, src2)
524
+ dst = dst_2_to_1(src1, src2, "⊛")
525
+ x1, y1 = src1.get_data()
526
+ _x2, y2 = src2.get_data()
527
+
528
+ # Get configuration option for kernel normalization
529
+ normalize_kernel = sigima_options.auto_normalize_kernel.get()
530
+
531
+ ynew = fourier.convolve(
532
+ x1,
533
+ y1,
534
+ y2,
535
+ normalize_kernel_flag=normalize_kernel,
536
+ )
537
+ dst.set_xydata(x1, ynew, None, None)
538
+ restore_data_outside_roi(dst, src1)
539
+ return dst
540
+
541
+
542
+ def get_nyquist_frequency(obj: SignalObj) -> float:
543
+ """Return the Nyquist frequency of a signal object
544
+
545
+ Args:
546
+ obj: signal object
547
+
548
+ Returns:
549
+ Nyquist frequency
550
+ """
551
+ fs = float(obj.x.size - 1) / (obj.x[-1] - obj.x[0])
552
+ return fs / 2.0
553
+
554
+
555
+ @computation_function()
556
+ def xy_mode(src1: SignalObj, src2: SignalObj) -> SignalObj:
557
+ """Simulate the X-Y mode of an oscilloscope.
558
+
559
+ Use the first signal as the X-axis and the second signal as the Y-axis.
560
+
561
+ Args:
562
+ src1: First input signal (X-axis).
563
+ src2: Second input signal (Y-axis).
564
+
565
+ Returns:
566
+ A signal object representing the X-Y mode.
567
+ """
568
+ dst = dst_2_to_1(src1, src2, "", "X-Y Mode")
569
+ p = Resampling1DParam()
570
+ p.xmin = max(src1.x[0], src2.x[0])
571
+ p.xmax = min(src1.x[-1], src2.x[-1])
572
+ assert p.xmin < p.xmax, "X-Y mode: No overlap between signals."
573
+ p.mode = "nbpts"
574
+ p.nbpts = min(src1.x.size, src2.x.size)
575
+ _, y1 = resampling(src1, p).get_data()
576
+ _, y2 = resampling(src2, p).get_data()
577
+ dst.set_xydata(y1, y2)
578
+ dst.title = "{1} = f({0})"
579
+ restore_data_outside_roi(dst, src1)
580
+ return dst