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,551 @@
1
+ # Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file.
2
+
3
+ """
4
+ Arithmetic operations on signals
5
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import warnings
11
+ from typing import TYPE_CHECKING
12
+
13
+ import numpy as np
14
+
15
+ from sigima.enums import MathOperator, SignalsToImageOrientation
16
+ from sigima.objects import SignalObj, create_image
17
+ from sigima.proc.base import (
18
+ ArithmeticParam,
19
+ ConstantParam,
20
+ SignalsToImageParam,
21
+ dst_1_to_1,
22
+ dst_2_to_1,
23
+ dst_n_to_1,
24
+ )
25
+ from sigima.proc.decorator import computation_function
26
+ from sigima.proc.signal.base import (
27
+ is_uncertainty_data_available,
28
+ restore_data_outside_roi,
29
+ signals_dy_to_array,
30
+ signals_y_to_array,
31
+ )
32
+ from sigima.proc.signal.mathops import inverse
33
+ from sigima.tools.signal import scaling
34
+
35
+ if TYPE_CHECKING:
36
+ from sigima.objects import ImageObj
37
+
38
+
39
+ @computation_function()
40
+ def addition(src_list: list[SignalObj]) -> SignalObj:
41
+ """Compute the element-wise sum of multiple signals.
42
+
43
+ The first signal in the list defines the "base" signal. All other signals are added
44
+ element-wise to the base signal.
45
+
46
+ .. note::
47
+
48
+ If all signals share the same region of interest (ROI), the sum is performed
49
+ only within the ROI.
50
+
51
+ .. note::
52
+
53
+ Uncertainties are propagated.
54
+
55
+ .. warning::
56
+
57
+ It is assumed that all signals have the same size and x-coordinates.
58
+
59
+ Args:
60
+ src_list: List of source signals.
61
+
62
+ Returns:
63
+ Signal object representing the sum of the source signals.
64
+ """
65
+ dst = dst_n_to_1(src_list, "Σ") # `dst` data is initialized to `src_list[0]` data.
66
+ dst.y = np.sum(signals_y_to_array(src_list), axis=0)
67
+ if is_uncertainty_data_available(src_list):
68
+ dst.dy = np.sqrt(np.sum(signals_dy_to_array(src_list) ** 2, axis=0))
69
+ restore_data_outside_roi(dst, src_list[0])
70
+ return dst
71
+
72
+
73
+ @computation_function()
74
+ def average(src_list: list[SignalObj]) -> SignalObj:
75
+ """Compute the element-wise average of multiple signals.
76
+
77
+ The first signal in the list defines the "base" signal. All other signals are
78
+ averaged element-wise with the base signal.
79
+
80
+ .. note::
81
+
82
+ If all signals share the same region of interest (ROI), the average is performed
83
+ only within the ROI.
84
+
85
+ .. note::
86
+
87
+ Uncertainties are propagated.
88
+
89
+ .. warning::
90
+
91
+ It is assumed that all signals have the same size and x-coordinates.
92
+
93
+ Args:
94
+ src_list: List of source signals.
95
+
96
+ Returns:
97
+ Signal object representing the average of the source signals.
98
+ """
99
+ dst = dst_n_to_1(src_list, "µ") # `dst` data is initialized to `src_list[0]` data.
100
+ dst.y = np.mean(signals_y_to_array(src_list), axis=0)
101
+ if is_uncertainty_data_available(src_list):
102
+ dy_array = signals_dy_to_array(src_list)
103
+ dst.dy = np.sqrt(np.sum(dy_array**2, axis=0)) / len(src_list)
104
+ restore_data_outside_roi(dst, src_list[0])
105
+ return dst
106
+
107
+
108
+ @computation_function()
109
+ def standard_deviation(src_list: list[SignalObj]) -> SignalObj:
110
+ """Compute the element-wise standard deviation of multiple signals.
111
+
112
+ The first signal in the list defines the "base" signal. All other signals are
113
+ used to compute the element-wise standard deviation with the base signal.
114
+
115
+ .. note::
116
+
117
+ If all signals share the same region of interest (ROI), the standard deviation
118
+ is computed only within the ROI.
119
+
120
+ .. warning::
121
+
122
+ It is assumed that all signals have the same size and x-coordinates.
123
+
124
+ Args:
125
+ src_list: List of source signals.
126
+
127
+ Returns:
128
+ Signal object representing the standard deviation of the source signals.
129
+ """
130
+ dst = dst_n_to_1(src_list, "𝜎") # `dst` data is initialized to `src_list[0]` data
131
+ dst.y = np.std(signals_y_to_array(src_list), axis=0, ddof=0)
132
+ if is_uncertainty_data_available(src_list):
133
+ dst.dy = dst.y / np.sqrt(2 * (len(src_list) - 1))
134
+ restore_data_outside_roi(dst, src_list[0])
135
+ return dst
136
+
137
+
138
+ @computation_function()
139
+ def product(src_list: list[SignalObj]) -> SignalObj:
140
+ """Compute the element-wise product of multiple signals.
141
+
142
+ The first signal in the list defines the "base" signal. All other signals are
143
+ multiplied element-wise with the base signal.
144
+
145
+ .. note::
146
+
147
+ If all signals share the same region of interest (ROI), the product is performed
148
+ only within the ROI.
149
+
150
+ .. note::
151
+
152
+ Uncertainties are propagated.
153
+
154
+ .. warning::
155
+
156
+ It is assumed that all signals have the same size and x-coordinates.
157
+
158
+ Args:
159
+ src_list: List of source signals.
160
+
161
+ Returns:
162
+ Signal object representing the product of the source signals.
163
+ """
164
+ dst = dst_n_to_1(src_list, "Π") # `dst` data is initialized to `src_list[0]` data.
165
+ y_array = signals_y_to_array(src_list)
166
+ dst.y = np.prod(y_array, axis=0)
167
+ if is_uncertainty_data_available(src_list):
168
+ dy_array = signals_dy_to_array(src_list)
169
+ with warnings.catch_warnings():
170
+ warnings.simplefilter("ignore", category=RuntimeWarning)
171
+ uncertainty = np.abs(dst.y) * np.sqrt(
172
+ np.sum((dy_array / y_array) ** 2, axis=0)
173
+ )
174
+ uncertainty[np.isinf(uncertainty)] = np.nan
175
+ dst.dy = uncertainty
176
+ restore_data_outside_roi(dst, src_list[0])
177
+ return dst
178
+
179
+
180
+ @computation_function()
181
+ def addition_constant(src: SignalObj, p: ConstantParam) -> SignalObj:
182
+ """Compute the sum of a signal and a constant value.
183
+
184
+ The function adds a constant value to each element of the input signal.
185
+
186
+ .. note::
187
+
188
+ If the signal has a region of interest (ROI), the addition is performed
189
+ only within the ROI.
190
+
191
+ .. note::
192
+
193
+ Uncertainties are propagated.
194
+
195
+ Args:
196
+ src: Input signal object.
197
+ p: Constant value.
198
+
199
+ Returns:
200
+ Result signal object representing the sum of the input signal and the constant.
201
+ """
202
+ # Uncertainty propagation: dst_1_to_1() copies all data including uncertainties.
203
+ # For addition with constant: σ(y + c) = σ(y), so no modification needed.
204
+ dst = dst_1_to_1(src, "+", str(p.value))
205
+ dst.y += p.value
206
+ restore_data_outside_roi(dst, src)
207
+ return dst
208
+
209
+
210
+ @computation_function()
211
+ def difference_constant(src: SignalObj, p: ConstantParam) -> SignalObj:
212
+ """Compute the difference between a signal and a constant value.
213
+
214
+ The function subtracts a constant value from each element of the input signal.
215
+
216
+ .. note::
217
+
218
+ If the signal has a region of interest (ROI), the subtraction is performed
219
+ only within the ROI.
220
+
221
+ .. note::
222
+
223
+ Uncertainties are propagated.
224
+
225
+ Args:
226
+ src: Input signal object.
227
+ p: Constant value.
228
+
229
+ Returns:
230
+ Result signal object representing the difference between the input signal and
231
+ the constant.
232
+ """
233
+ # Uncertainty propagation: dst_1_to_1() copies all data including uncertainties.
234
+ # For subtraction with constant: σ(y - c) = σ(y), so no modification needed.
235
+ dst = dst_1_to_1(src, "-", str(p.value))
236
+ dst.y -= p.value
237
+ restore_data_outside_roi(dst, src)
238
+ return dst
239
+
240
+
241
+ @computation_function()
242
+ def product_constant(src: SignalObj, p: ConstantParam) -> SignalObj:
243
+ """Compute the product of a signal and a constant value.
244
+
245
+ The function multiplies each element of the input signal by a constant value.
246
+
247
+ .. note::
248
+
249
+ If the signal has a region of interest (ROI), the multiplication is performed
250
+ only within the ROI.
251
+
252
+ .. note::
253
+
254
+ Uncertainties are propagated.
255
+
256
+ Args:
257
+ src: Input signal object.
258
+ p: Constant value.
259
+
260
+ Returns:
261
+ Result signal object representing the product of the input signal and the
262
+ constant.
263
+ """
264
+ assert p.value is not None
265
+ # Uncertainty propagation: dst_1_to_1() copies all data including uncertainties.
266
+ # For multiplication with constant: σ(c*y) = |c| * σ(y), so modification needed.
267
+ dst = dst_1_to_1(src, "×", str(p.value))
268
+ dst.y *= p.value
269
+ if is_uncertainty_data_available(src):
270
+ dst.dy *= np.abs(p.value) # Modify in-place since dy already copied from src
271
+ restore_data_outside_roi(dst, src)
272
+ return dst
273
+
274
+
275
+ @computation_function()
276
+ def division_constant(src: SignalObj, p: ConstantParam) -> SignalObj:
277
+ """Compute the division of a signal by a constant value.
278
+
279
+ The function divides each element of the input signal by a constant value.
280
+
281
+ .. note::
282
+
283
+ If the signal has a region of interest (ROI), the division is performed
284
+ only within the ROI.
285
+
286
+ .. note::
287
+
288
+ Uncertainties are propagated.
289
+
290
+ Args:
291
+ src: Input signal object.
292
+ p: Constant value.
293
+
294
+ Returns:
295
+ Result signal object representing the division of the input signal by the
296
+ constant.
297
+ """
298
+ assert p.value is not None
299
+ # Uncertainty propagation: dst_1_to_1() copies all data including uncertainties.
300
+ # For division with constant: σ(y/c) = σ(y) / |c|, so modification needed.
301
+ dst = dst_1_to_1(src, "/", str(p.value))
302
+ with warnings.catch_warnings():
303
+ warnings.simplefilter("ignore", category=RuntimeWarning)
304
+ dst.y /= p.value
305
+ dst.y[np.isinf(dst.y)] = np.nan
306
+ if is_uncertainty_data_available(src):
307
+ dst.dy /= np.abs(p.value) # Modify in-place since dy already copied
308
+ dst.dy[np.isinf(dst.dy)] = np.nan
309
+ restore_data_outside_roi(dst, src)
310
+ return dst
311
+
312
+
313
+ @computation_function()
314
+ def arithmetic(src1: SignalObj, src2: SignalObj, p: ArithmeticParam) -> SignalObj:
315
+ """Perform an arithmetic operation on two signals.
316
+
317
+ The function applies the specified arithmetic operation to each element of the input
318
+ signals.
319
+
320
+ .. note::
321
+
322
+ The operation is performed only within the region of interest of `src1`.
323
+
324
+ .. note::
325
+
326
+ Uncertainties are propagated.
327
+
328
+ .. warning::
329
+
330
+ It is assumed that both signals have the same size and x-coordinates.
331
+
332
+ Args:
333
+ src1: First input signal.
334
+ src2: Second input signal.
335
+ p: Arithmetic operation parameters.
336
+
337
+ Returns:
338
+ Result signal object representing the arithmetic operation on the input signals.
339
+ """
340
+ initial_dtype = src1.xydata.dtype
341
+ title = p.operation.replace("obj1", "{0}").replace("obj2", "{1}")
342
+ dst = src1.copy(title=title)
343
+ a = ConstantParam.create(value=p.factor)
344
+ b = ConstantParam.create(value=p.constant)
345
+ if p.operator == MathOperator.ADD:
346
+ dst = addition_constant(product_constant(addition([src1, src2]), a), b)
347
+ elif p.operator == MathOperator.SUBTRACT:
348
+ dst = addition_constant(product_constant(difference(src1, src2), a), b)
349
+ elif p.operator == MathOperator.MULTIPLY:
350
+ dst = addition_constant(product_constant(product([src1, src2]), a), b)
351
+ elif p.operator == MathOperator.DIVIDE:
352
+ dst = addition_constant(product_constant(product([src1, inverse(src2)]), a), b)
353
+ # Eventually convert to initial data type
354
+ if p.restore_dtype:
355
+ dst.xydata = dst.xydata.astype(initial_dtype)
356
+ restore_data_outside_roi(dst, src1)
357
+ return dst
358
+
359
+
360
+ @computation_function()
361
+ def difference(src1: SignalObj, src2: SignalObj) -> SignalObj:
362
+ """Compute the element-wise difference between two signals.
363
+
364
+ The function subtracts each element of the second signal from the corresponding
365
+ element of the first signal.
366
+
367
+ .. note::
368
+
369
+ If both signals share the same region of interest (ROI), the difference is
370
+ performed only within the ROI.
371
+
372
+ .. note::
373
+
374
+ Uncertainties are propagated.
375
+
376
+ .. warning::
377
+
378
+ It is assumed that both signals have the same size and x-coordinates.
379
+
380
+ Args:
381
+ src1: First input signal.
382
+ src2: Second input signal.
383
+
384
+ Returns:
385
+ Result signal object representing the difference between the input signals.
386
+ """
387
+ dst = dst_2_to_1(src1, src2, "-")
388
+ dst.y = src1.y - src2.y
389
+ if is_uncertainty_data_available([src1, src2]):
390
+ dy_array = signals_dy_to_array([src1, src2])
391
+ dst.dy = np.sqrt(np.sum(dy_array**2, axis=0))
392
+ restore_data_outside_roi(dst, src1)
393
+ return dst
394
+
395
+
396
+ @computation_function()
397
+ def quadratic_difference(src1: SignalObj, src2: SignalObj) -> SignalObj:
398
+ """Compute the normalized difference between two signals.
399
+
400
+ The function computes the element-wise difference between the two signals and
401
+ divides the result by sqrt(2.0).
402
+
403
+ .. note::
404
+
405
+ If both signals share the same region of interest (ROI), the operation is
406
+ performed only within the ROI.
407
+
408
+ .. note::
409
+
410
+ Uncertainties are propagated. For two input signals with identical standard
411
+ deviations, the standard deviation of the output signal equals the standard
412
+ deviation of each of the input signals.
413
+
414
+ .. warning::
415
+
416
+ It is assumed that both signals have the same size and x-coordinates.
417
+
418
+ Args:
419
+ src1: First input signal.
420
+ src2: Second input signal.
421
+
422
+ Returns:
423
+ Result signal object representing the quadratic difference between the input
424
+ signals.
425
+ """
426
+ norm = ConstantParam.create(value=1.0 / np.sqrt(2.0))
427
+ return product_constant(difference(src1, src2), norm)
428
+
429
+
430
+ @computation_function()
431
+ def division(src1: SignalObj, src2: SignalObj) -> SignalObj:
432
+ """Compute the element-wise division between two signals.
433
+
434
+ The function divides each element of the first signal by the corresponding element
435
+ of the second signal.
436
+
437
+ .. note::
438
+
439
+ If both signals share the same region of interest (ROI), the division is
440
+ performed only within the ROI.
441
+
442
+ .. note::
443
+
444
+ Uncertainties are propagated.
445
+
446
+ .. warning::
447
+
448
+ It is assumed that both signals have the same size and x-coordinates.
449
+
450
+ Args:
451
+ src1: First input signal.
452
+ src2: Second input signal.
453
+
454
+ Returns:
455
+ Result signal object representing the division of the input signals.
456
+ """
457
+ dst = product([src1, inverse(src2)])
458
+ return dst
459
+
460
+
461
+ def signals_to_array(
462
+ signals: list[SignalObj], attr: str = "y", dtype: np.dtype | None = None
463
+ ) -> np.ndarray:
464
+ """Create an array from a list of signals.
465
+
466
+ Args:
467
+ signals: List of signal objects.
468
+ attr: Name of the attribute to extract ("y", "dy", etc.). Defaults to "y".
469
+ dtype: Desired type for the output array. If None, use the first signal's dtype.
470
+
471
+ Returns:
472
+ A NumPy array stacking the specified attribute from all signals.
473
+
474
+ Raises:
475
+ ValueError: If the signals list is empty.
476
+ """
477
+ if not signals:
478
+ raise ValueError("The signal list is empty.")
479
+ if dtype is None:
480
+ dtype = getattr(signals[0], attr).dtype
481
+ arr = np.array([getattr(sig, attr) for sig in signals], dtype=dtype)
482
+ return arr
483
+
484
+
485
+ @computation_function()
486
+ def signals_to_image(src_list: list[SignalObj], p: SignalsToImageParam) -> ImageObj:
487
+ """Combine multiple signals into an image.
488
+
489
+ The function takes a list of signals and combines them into a 2D image,
490
+ arranging them either as rows or columns based on the specified orientation.
491
+ Optionally, each signal can be normalized before combining.
492
+
493
+ .. note::
494
+
495
+ All signals must have the same size (number of data points).
496
+
497
+ .. note::
498
+
499
+ If normalization is enabled, each signal is normalized independently
500
+ using the specified normalization method before being added to the image.
501
+
502
+ Args:
503
+ src_list: List of source signals to combine.
504
+ p: Parameters specifying orientation and normalization options.
505
+
506
+ Returns:
507
+ Image object representing the combined signals.
508
+
509
+ Raises:
510
+ ValueError: If the signal list is empty or signals have different sizes.
511
+ """
512
+ if not src_list:
513
+ raise ValueError("The signal list is empty.")
514
+
515
+ # Check that all signals have the same size
516
+ sizes = [len(sig.y) for sig in src_list]
517
+ if len(set(sizes)) > 1:
518
+ raise ValueError(
519
+ f"All signals must have the same size. Found sizes: {set(sizes)}"
520
+ )
521
+
522
+ # Prepare data array
523
+ y_array = signals_y_to_array(src_list)
524
+
525
+ # Normalize if requested
526
+ if p.normalize:
527
+ for i in range(len(src_list)):
528
+ y_array[i] = scaling.normalize(y_array[i], p.normalize_method)
529
+
530
+ # Arrange as rows or columns
531
+ if p.orientation == SignalsToImageOrientation.COLUMNS:
532
+ data = y_array.T
533
+ else: # ROWS
534
+ data = y_array
535
+
536
+ # Create the result image
537
+ suffix_parts = [f"n={len(src_list)}", f"orientation={p.orientation}"]
538
+ if p.normalize:
539
+ suffix_parts.append(f"norm={p.normalize_method}")
540
+ suffix = ", ".join(suffix_parts)
541
+ title = f"combined_signals|{suffix}"
542
+
543
+ dst = create_image(title, data)
544
+ if p.orientation == SignalsToImageOrientation.ROWS:
545
+ dst.xlabel = src_list[0].ylabel
546
+ else:
547
+ dst.ylabel = src_list[0].ylabel
548
+ dst.zlabel = src_list[0].ylabel
549
+ dst.zunit = src_list[0].yunit
550
+
551
+ return dst