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,276 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Licensed under the terms of the BSD 3-Clause
4
+ # (see sigima/LICENSE for details)
5
+
6
+ """
7
+ Curve fitting operations
8
+ ========================
9
+
10
+ This module provides curve fitting operations for signal objects:
11
+
12
+ - Linear and polynomial fits
13
+ - Gaussian, Lorentzian, and Voigt fits
14
+ - Exponential and CDF fits
15
+
16
+ .. note::
17
+
18
+ Most operations use functions from :mod:`sigima.tools.signal.fitting` for
19
+ actual computations.
20
+ """
21
+
22
+ from __future__ import annotations
23
+
24
+ from typing import Callable
25
+
26
+ import guidata.dataset as gds
27
+ import numpy as np
28
+
29
+ from sigima.config import _
30
+ from sigima.objects import SignalObj
31
+ from sigima.proc.base import dst_2_to_1
32
+ from sigima.proc.decorator import computation_function
33
+ from sigima.tools.signal import fitting
34
+
35
+ from .base import dst_1_to_1
36
+
37
+
38
+ def __generic_fit(
39
+ src: SignalObj,
40
+ fitfunc: Callable[[np.ndarray, np.ndarray], tuple[np.ndarray, dict[str, float]]],
41
+ ) -> SignalObj:
42
+ """Generic fitting function.
43
+
44
+ Args:
45
+ src: source signal
46
+ fitfunc: fitting function
47
+
48
+ Returns:
49
+ Fitting result signal object
50
+ """
51
+ dst = dst_1_to_1(src, fitfunc.__name__)
52
+
53
+ # Fit only on ROI if available
54
+ x_roi = src.x[~src.get_masked_view().mask]
55
+ y_roi = src.get_masked_view().compressed()
56
+ _fitted_y_roi, fit_params = fitfunc(x_roi, y_roi)
57
+
58
+ # Evaluate fit on full x range
59
+ fitted_y = fitting.evaluate_fit(src.x, **fit_params)
60
+ dst.set_xydata(src.x, fitted_y)
61
+
62
+ # Store fit parameters in metadata
63
+ dst.metadata["fit_params"] = fit_params
64
+ return dst
65
+
66
+
67
+ @computation_function()
68
+ def linear_fit(src: SignalObj) -> SignalObj:
69
+ """Compute linear fit with :py:func:`numpy.polyfit`
70
+
71
+ Args:
72
+ src: source signal
73
+
74
+ Returns:
75
+ Result signal object
76
+ """
77
+ return __generic_fit(src, fitting.linear_fit)
78
+
79
+
80
+ class PolynomialFitParam(gds.DataSet, title=_("Polynomial fit")):
81
+ """Polynomial fitting parameters"""
82
+
83
+ degree = gds.IntItem(_("Degree"), 3, min=1, max=10, slider=True)
84
+
85
+
86
+ @computation_function()
87
+ def polynomial_fit(src: SignalObj, p: PolynomialFitParam) -> SignalObj:
88
+ """Compute polynomial fit with :py:func:`numpy.polyfit`
89
+
90
+ Args:
91
+ src: source signal
92
+ p: polynomial fitting parameters
93
+
94
+ Returns:
95
+ Result signal object
96
+ """
97
+ if p.degree < 1:
98
+ raise ValueError("The polynomial degree must be at least 1.")
99
+ return __generic_fit(src, lambda x, y: fitting.polynomial_fit(x, y, p.degree))
100
+
101
+
102
+ @computation_function()
103
+ def gaussian_fit(src: SignalObj) -> SignalObj:
104
+ """Compute Gaussian fit with :py:func:`scipy.optimize.curve_fit`
105
+
106
+ Args:
107
+ src: source signal
108
+
109
+ Returns:
110
+ Result signal object
111
+ """
112
+ return __generic_fit(src, fitting.gaussian_fit)
113
+
114
+
115
+ @computation_function()
116
+ def lorentzian_fit(src: SignalObj) -> SignalObj:
117
+ """Compute Lorentzian fit with :py:func:`scipy.optimize.curve_fit`
118
+
119
+ Args:
120
+ src: source signal
121
+
122
+ Returns:
123
+ Result signal object
124
+ """
125
+ return __generic_fit(src, fitting.lorentzian_fit)
126
+
127
+
128
+ @computation_function()
129
+ def voigt_fit(src: SignalObj) -> SignalObj:
130
+ """Compute Voigt fit with :py:func:`scipy.optimize.curve_fit`
131
+
132
+ Args:
133
+ src: source signal
134
+
135
+ Returns:
136
+ Result signal object
137
+ """
138
+ return __generic_fit(src, fitting.voigt_fit)
139
+
140
+
141
+ @computation_function()
142
+ def exponential_fit(src: SignalObj) -> SignalObj:
143
+ """Compute exponential fit with :py:func:`scipy.optimize.curve_fit`
144
+
145
+ Args:
146
+ src: source signal
147
+
148
+ Returns:
149
+ Result signal object
150
+ """
151
+ return __generic_fit(src, fitting.exponential_fit)
152
+
153
+
154
+ @computation_function()
155
+ def cdf_fit(src: SignalObj) -> SignalObj:
156
+ """Compute CDF fit with :py:func:`scipy.optimize.curve_fit`
157
+
158
+ Args:
159
+ src: source signal
160
+
161
+ Returns:
162
+ Result signal object
163
+ """
164
+ return __generic_fit(src, fitting.cdf_fit)
165
+
166
+
167
+ @computation_function()
168
+ def planckian_fit(src: SignalObj) -> SignalObj:
169
+ """Compute Planckian fit with :py:func:`scipy.optimize.curve_fit`
170
+
171
+ Args:
172
+ src: source signal
173
+
174
+ Returns:
175
+ Result signal object
176
+ """
177
+ return __generic_fit(src, fitting.planckian_fit)
178
+
179
+
180
+ @computation_function()
181
+ def twohalfgaussian_fit(src: SignalObj) -> SignalObj:
182
+ """Compute two-half-Gaussian fit with :py:func:`scipy.optimize.curve_fit`
183
+
184
+ Args:
185
+ src: source signal
186
+
187
+ Returns:
188
+ Result signal object
189
+ """
190
+ return __generic_fit(src, fitting.twohalfgaussian_fit)
191
+
192
+
193
+ @computation_function()
194
+ def sigmoid_fit(src: SignalObj) -> SignalObj:
195
+ """Compute sigmoid fit with :py:func:`scipy.optimize.curve_fit`
196
+
197
+ Args:
198
+ src: source signal
199
+
200
+ Returns:
201
+ Result signal object
202
+ """
203
+ return __generic_fit(src, fitting.sigmoid_fit)
204
+
205
+
206
+ @computation_function()
207
+ def piecewiseexponential_fit(src: SignalObj) -> SignalObj:
208
+ """Compute piecewise exponential fit (raise-decay) with
209
+ :py:func:`scipy.optimize.curve_fit`
210
+
211
+ Args:
212
+ src: source signal
213
+
214
+ Returns:
215
+ Result signal object
216
+ """
217
+ return __generic_fit(src, fitting.piecewiseexponential_fit)
218
+
219
+
220
+ @computation_function()
221
+ def sinusoidal_fit(src: SignalObj) -> SignalObj:
222
+ """Compute sinusoidal fit with :py:func:`scipy.optimize.curve_fit`
223
+
224
+ Args:
225
+ src: source signal
226
+
227
+ Returns:
228
+ Result signal object
229
+ """
230
+ return __generic_fit(src, fitting.sinusoidal_fit)
231
+
232
+
233
+ def extract_fit_params(signal: SignalObj) -> dict[str, float | str]:
234
+ """Extract fit parameters from a fitted signal.
235
+
236
+ Args:
237
+ signal: Signal object containing fit metadata
238
+
239
+ Returns:
240
+ Fit parameters
241
+ """
242
+ if "fit_params" not in signal.metadata:
243
+ raise ValueError("Signal does not contain fit parameters")
244
+ fit_params_dict: dict[str, float | str] = signal.metadata["fit_params"]
245
+ assert "fit_type" in fit_params_dict, "No valid fit parameters found"
246
+ return fit_params_dict
247
+
248
+
249
+ @computation_function()
250
+ def evaluate_fit(src1: SignalObj, src2: SignalObj) -> SignalObj:
251
+ """Evaluate fit function from src1 on the x-axis of src2.
252
+
253
+ This function extracts fit parameters from `src1` (which must contain fit metadata
254
+ from a previous fitting operation) and evaluates the fit function on the x-axis
255
+ of `src2`.
256
+
257
+ Args:
258
+ src1: Signal object containing fit parameters in metadata (from a fit operation)
259
+ src2: Signal object whose x-axis will be used for evaluation
260
+
261
+ Returns:
262
+ New signal with the fit evaluated on src2's x-axis
263
+ """
264
+ fit_params = extract_fit_params(src1)
265
+ dst = dst_2_to_1(src1, src2, "evaluate_fit")
266
+
267
+ # Evaluate fit on src2's x-axis
268
+ x = src2.x
269
+ y = fitting.evaluate_fit(x, **fit_params)
270
+
271
+ dst.set_xydata(x, y)
272
+ dst.title = f"Fitted {fit_params['fit_type']}"
273
+
274
+ # Copy fit parameters to destination metadata
275
+ dst.metadata["fit_params"] = fit_params
276
+ return dst
@@ -0,0 +1,259 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Licensed under the terms of the BSD 3-Clause
4
+ # (see sigima/LICENSE for details)
5
+
6
+ """
7
+ Fourier transform and frequency domain operations
8
+ =================================================
9
+
10
+ This module provides Fourier transform and frequency domain operations:
11
+
12
+ - FFT and inverse FFT
13
+ - Magnitude and phase spectrum
14
+ - Power spectral density (PSD)
15
+
16
+ .. note::
17
+
18
+ Most operations use functions from :mod:`sigima.tools.signal.fourier` for actual
19
+ computations.
20
+ """
21
+
22
+ from __future__ import annotations
23
+
24
+ from math import ceil, log2
25
+
26
+ import guidata.dataset as gds
27
+
28
+ from sigima.config import _
29
+ from sigima.enums import PadLocation1D
30
+ from sigima.objects import SignalObj
31
+ from sigima.proc.base import FFTParam, SpectrumParam
32
+ from sigima.proc.decorator import computation_function
33
+ from sigima.proc.signal.base import dst_1_to_1
34
+ from sigima.tools.signal import fourier
35
+
36
+
37
+ class ZeroPadding1DParam(gds.DataSet, title=_("Zero padding")):
38
+ """ZeroPadding1DParam manages the parameters for applying zero-padding to signals.
39
+
40
+ Attributes:
41
+ strategies: Available strategies ("next_pow2", "double", "triple", "custom").
42
+ strategy: Choice item for selecting the zero-padding strategy.
43
+ locations: Available locations for padding ("append", "prepend", "both").
44
+ location: Choice item for selecting where to add the padding.
45
+ n: Number of points to add as padding (active only for "custom" strategy).
46
+ """
47
+
48
+ def __init__(self, *args, **kwargs) -> None:
49
+ """Initialize zero padding parameters.
50
+
51
+ Args:
52
+ *args: Variable length argument list passed to the superclass.
53
+ **kwargs: Arbitrary keyword arguments passed to the superclass.
54
+ """
55
+ super().__init__(*args, **kwargs)
56
+ self.__obj: SignalObj | None = None
57
+
58
+ def update_from_obj(self, obj: SignalObj) -> None:
59
+ """Update parameters from signal.
60
+
61
+ Args:
62
+ obj: Signal object from which to update the dataset.
63
+ """
64
+ self.__obj = obj
65
+ self.strategy_callback(None, self.strategy)
66
+
67
+ @staticmethod
68
+ def next_power_of_two(size: int) -> int:
69
+ """Compute the next power of two greater than or equal to the given size.
70
+
71
+ Args:
72
+ size: The input integer.
73
+
74
+ Returns:
75
+ The smallest power of two greater than or equal to 'size'.
76
+ """
77
+ return 2 ** (ceil(log2(size)))
78
+
79
+ def strategy_callback(self, _, value):
80
+ """Callback for strategy choice item.
81
+
82
+ Args:
83
+ _: Unused argument (in this context).
84
+ value: The selected strategy value.
85
+ """
86
+ if self.__obj is None:
87
+ return
88
+ assert self.__obj.x is not None
89
+ size = self.__obj.x.size
90
+ if value == "next_pow2":
91
+ self.n = self.next_power_of_two(size) - size
92
+ elif value == "double":
93
+ self.n = size
94
+ elif value == "triple":
95
+ self.n = 2 * size
96
+
97
+ strategies = ("next_pow2", "double", "triple", "custom")
98
+ _prop = gds.GetAttrProp("strategy")
99
+ strategy = gds.ChoiceItem(
100
+ _("Strategy"), zip(strategies, strategies), default=strategies[0]
101
+ ).set_prop("display", store=_prop, callback=strategy_callback)
102
+ location = gds.ChoiceItem(
103
+ _("Location"),
104
+ PadLocation1D,
105
+ default=PadLocation1D.APPEND,
106
+ help=_("Where to add the padding"),
107
+ )
108
+ _func_prop = gds.FuncProp(_prop, lambda x: x == "custom")
109
+ n = gds.IntItem(
110
+ _("Number of points"), min=1, default=1, help=_("Number of points to add")
111
+ ).set_prop("display", active=_func_prop)
112
+
113
+
114
+ @computation_function()
115
+ def zero_padding(src: SignalObj, p: ZeroPadding1DParam) -> SignalObj:
116
+ """Compute zero padding with :py:func:`sigima.tools.signal.fourier.zero_padding`.
117
+
118
+ Args:
119
+ src: Source signal.
120
+ p: Parameters.
121
+
122
+ Returns:
123
+ Result signal object.
124
+ """
125
+ if p.strategy == "custom":
126
+ suffix = f"n={p.n}"
127
+ else:
128
+ suffix = f"strategy={p.strategy}"
129
+
130
+ assert p.n is not None
131
+ if p.location == PadLocation1D.APPEND:
132
+ n_prepend = 0
133
+ n_append = p.n
134
+ elif p.location == PadLocation1D.PREPEND:
135
+ n_prepend = p.n
136
+ n_append = 0
137
+ elif p.location == PadLocation1D.BOTH:
138
+ n_prepend = p.n // 2
139
+ n_append = p.n - n_prepend
140
+ else:
141
+ raise ValueError(f"Invalid padding location: {p.location}")
142
+
143
+ dst = dst_1_to_1(src, "zero_padding", suffix)
144
+ x, y = src.get_data()
145
+ x_padded, y_padded = fourier.zero_padding(x, y, n_prepend, n_append)
146
+ dst.set_xydata(x_padded, y_padded)
147
+
148
+ return dst
149
+
150
+
151
+ @computation_function()
152
+ def fft(src: SignalObj, p: FFTParam | None = None) -> SignalObj:
153
+ """Compute FFT with :py:func:`sigima.tools.signal.fourier.fft1d`.
154
+
155
+ Args:
156
+ src: Source signal.
157
+ p: Parameters.
158
+
159
+ Returns:
160
+ Result signal object.
161
+ """
162
+ dst = dst_1_to_1(src, "fft")
163
+ x, y = src.get_data()
164
+ fft_x, fft_y = fourier.fft1d(x, y, shift=bool(True if p is None else p.shift))
165
+ dst.set_xydata(fft_x, fft_y)
166
+ dst.save_attr_to_metadata("xunit", "Hz" if dst.xunit == "s" else "")
167
+ dst.save_attr_to_metadata("yunit", "")
168
+ dst.save_attr_to_metadata("xlabel", _("Frequency"))
169
+ return dst
170
+
171
+
172
+ @computation_function()
173
+ def ifft(src: SignalObj) -> SignalObj:
174
+ """Compute the inverse FFT with :py:func:`sigima.tools.signal.fourier.ifft1d`.
175
+
176
+ Args:
177
+ src: Source signal.
178
+
179
+ Returns:
180
+ Result signal object.
181
+ """
182
+ dst = dst_1_to_1(src, "ifft")
183
+ f, sp = src.get_data()
184
+ x, y = fourier.ifft1d(f, sp)
185
+ dst.set_xydata(x, y)
186
+ dst.restore_attr_from_metadata("xunit", "s" if src.xunit == "Hz" else "")
187
+ dst.restore_attr_from_metadata("yunit", "")
188
+ dst.restore_attr_from_metadata("xlabel", "")
189
+ return dst
190
+
191
+
192
+ @computation_function()
193
+ def magnitude_spectrum(src: SignalObj, p: SpectrumParam | None = None) -> SignalObj:
194
+ """Compute magnitude spectrum.
195
+
196
+ This function computes the magnitude spectrum of a signal using
197
+ :py:func:`sigima.tools.signal.fourier.magnitude_spectrum`.
198
+
199
+ Args:
200
+ src: Source signal.
201
+ p: Parameters.
202
+
203
+ Returns:
204
+ Result signal object.
205
+ """
206
+ decibel = bool(p is not None and p.decibel)
207
+ dst = dst_1_to_1(src, "magnitude_spectrum", f"dB={decibel}")
208
+ x, y = src.get_data()
209
+ mag_x, mag_y = fourier.magnitude_spectrum(x, y, decibel=decibel)
210
+ dst.set_xydata(mag_x, mag_y)
211
+ dst.xlabel = _("Frequency")
212
+ dst.xunit = "Hz" if dst.xunit == "s" else ""
213
+ dst.yunit = "dB" if decibel else ""
214
+ return dst
215
+
216
+
217
+ @computation_function()
218
+ def phase_spectrum(src: SignalObj) -> SignalObj:
219
+ """Compute phase spectrum.
220
+
221
+ This function computes the phase spectrum of a signal using
222
+ :py:func:`sigima.tools.signal.fourier.phase_spectrum`
223
+
224
+ Args:
225
+ src: Source signal.
226
+
227
+ Returns:
228
+ Result signal object.
229
+ """
230
+ dst = dst_1_to_1(src, "phase_spectrum")
231
+ x, y = src.get_data()
232
+ phase_x, phase_y = fourier.phase_spectrum(x, y)
233
+ dst.set_xydata(phase_x, phase_y)
234
+ dst.xlabel = _("Frequency")
235
+ dst.xunit = "Hz" if dst.xunit == "s" else ""
236
+ dst.yunit = ""
237
+ return dst
238
+
239
+
240
+ @computation_function()
241
+ def psd(src: SignalObj, p: SpectrumParam | None = None) -> SignalObj:
242
+ """Compute power spectral density with :py:func:`sigima.tools.signal.fourier.psd`.
243
+
244
+ Args:
245
+ src: Source signal.
246
+ p: Parameters.
247
+
248
+ Returns:
249
+ Result signal object.
250
+ """
251
+ decibel = p is not None and p.decibel
252
+ dst = dst_1_to_1(src, "psd", f"dB={decibel}")
253
+ x, y = src.get_data()
254
+ psd_x, psd_y = fourier.psd(x, y, decibel=decibel)
255
+ dst.set_xydata(psd_x, psd_y)
256
+ dst.xlabel = _("Frequency")
257
+ dst.xunit = "Hz" if dst.xunit == "s" else ""
258
+ dst.yunit = "dB/Hz" if decibel else ""
259
+ return dst