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,335 @@
1
+ # Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file.
2
+
3
+ """
4
+ Arithmetic computation module
5
+ -----------------------------
6
+
7
+ This module provides arithmetic operations for images, such as pixel-wise addition,
8
+ subtraction, multiplication, division, as well as operations with constants
9
+ and combined arithmetic formulas.
10
+
11
+ Main features include:
12
+
13
+ - Pixel-wise addition, subtraction, multiplication, and division between images
14
+ - Application of arithmetic operations with constants to images
15
+ - Support for quadratic difference and general arithmetic expressions
16
+
17
+ These functions are typically used for basic algebraic processing and normalization
18
+ of image data.
19
+ """
20
+
21
+ # pylint: disable=invalid-name # Allows short reference names like x, y, ...
22
+
23
+ # Note:
24
+ # ----
25
+ # - All `guidata.dataset.DataSet` parameter classes must also be imported
26
+ # in the `sigima.params` module.
27
+ # - All functions decorated by `computation_function` must be imported in the upper
28
+ # level `sigima.proc.image` module.
29
+
30
+ from __future__ import annotations
31
+
32
+ import warnings
33
+
34
+ import numpy as np
35
+
36
+ from sigima.enums import MathOperator
37
+ from sigima.objects.image import ImageObj
38
+ from sigima.proc.base import (
39
+ ArithmeticParam,
40
+ ConstantParam,
41
+ dst_1_to_1,
42
+ dst_2_to_1,
43
+ dst_n_to_1,
44
+ )
45
+ from sigima.proc.decorator import computation_function
46
+ from sigima.proc.image.base import restore_data_outside_roi
47
+ from sigima.tools.datatypes import clip_astype
48
+
49
+ # NOTE: Only parameter classes DEFINED in this module should be included in __all__.
50
+ # Parameter classes imported from other modules (like sigima.proc.base) should NOT
51
+ # be re-exported to avoid Sphinx cross-reference conflicts. The sigima.params module
52
+ # serves as the central API point that imports and re-exports all parameter classes.
53
+ __all__ = [
54
+ "addition",
55
+ "addition_constant",
56
+ "arithmetic",
57
+ "average",
58
+ "difference",
59
+ "difference_constant",
60
+ "division",
61
+ "division",
62
+ "division_constant",
63
+ "product",
64
+ "product_constant",
65
+ "quadratic_difference",
66
+ "standard_deviation",
67
+ ]
68
+
69
+ # MARK: compute_n_to_1 functions -------------------------------------------------------
70
+ # Functions with N input images and 1 output image
71
+ # --------------------------------------------------------------------------------------
72
+ # Those functions are perfoming a computation on N input images and return a single
73
+ # output image. If we were only executing these functions locally, we would not need
74
+ # to define them here, but since we are using the multiprocessing module, we need to
75
+ # define them here so that they can be pickled and sent to the worker processes.
76
+ # Also, we need to systematically return the output image object, even if it is already
77
+ # modified in place, because the multiprocessing module will not be able to retrieve
78
+ # the modified object from the worker processes.
79
+
80
+
81
+ @computation_function()
82
+ def addition(src_list: list[ImageObj]) -> ImageObj:
83
+ """Add images in the list and return the result image object
84
+
85
+ Args:
86
+ src_list: list of input image objects
87
+
88
+ Returns:
89
+ Output image object (modified in place)
90
+ """
91
+ dst = dst_n_to_1(src_list, "Σ") # `dst` data is initialized to `src_list[0]` data
92
+ for src in src_list[1:]:
93
+ dst.data = np.add(dst.data, src.data, dtype=float)
94
+ restore_data_outside_roi(dst, src_list[0])
95
+ return dst
96
+
97
+
98
+ @computation_function()
99
+ def average(src_list: list[ImageObj]) -> ImageObj:
100
+ """Compute the average of images in the list and return the result image object
101
+
102
+ Args:
103
+ src_list: list of input image objects
104
+
105
+ Returns:
106
+ Output image object (modified in place)
107
+ """
108
+ dst = dst_n_to_1(src_list, "µ") # `dst` data is initialized to `src_list[0]` data
109
+ for src in src_list[1:]:
110
+ dst.data = np.add(dst.data, src.data, dtype=float)
111
+ dst.data /= len(src_list)
112
+ restore_data_outside_roi(dst, src_list[0])
113
+ return dst
114
+
115
+
116
+ @computation_function()
117
+ def standard_deviation(src_list: list[ImageObj]) -> ImageObj:
118
+ """Compute the element-wise standard deviation of multiple images.
119
+
120
+ The first image in the list defines the "base" image. All other images are
121
+ used to compute the element-wise standard deviation with the base image.
122
+
123
+ .. note::
124
+
125
+ If all images share the same region of interest (ROI), the standard deviation
126
+ is computed only within the ROI.
127
+
128
+ .. warning::
129
+
130
+ It is assumed that all images have the same size and x-coordinates.
131
+
132
+ Args:
133
+ src_list: List of source images.
134
+
135
+ Returns:
136
+ Image object representing the standard deviation of the source images.
137
+ """
138
+ dst = dst_n_to_1(src_list, "𝜎") # `dst` data is initialized to `src_list[0]` data
139
+ assert dst.data is not None
140
+ y_array = np.array([src.data for src in src_list], dtype=dst.data.dtype)
141
+ dst.data = np.std(y_array, axis=0, ddof=0)
142
+ restore_data_outside_roi(dst, src_list[0])
143
+ return dst
144
+
145
+
146
+ @computation_function()
147
+ def product(src_list: list[ImageObj]) -> ImageObj:
148
+ """Multiply images in the list and return the result image object
149
+
150
+ Args:
151
+ src_list: list of input image objects
152
+
153
+ Returns:
154
+ Output image object (modified in place)
155
+ """
156
+ dst = dst_n_to_1(src_list, "Π") # `dst` data is initialized to `src_list[0]` data
157
+ for src in src_list[1:]:
158
+ dst.data = np.multiply(dst.data, src.data, dtype=float)
159
+ restore_data_outside_roi(dst, src_list[0])
160
+ return dst
161
+
162
+
163
+ @computation_function()
164
+ def addition_constant(src: ImageObj, p: ConstantParam) -> ImageObj:
165
+ """Add **dst** and a constant value and return the new result image object
166
+
167
+ Args:
168
+ src: input image object
169
+ p: constant value
170
+
171
+ Returns:
172
+ Result image object **src** + **p.value** (new object)
173
+ """
174
+ # For the addition of a constant value, we convert the constant value to the same
175
+ # data type as the input image, for consistency.
176
+ value = np.array(p.value).astype(dtype=src.data.dtype)
177
+ dst = dst_1_to_1(src, "+", str(value))
178
+ dst.data = np.add(src.data, value, dtype=float)
179
+ restore_data_outside_roi(dst, src)
180
+ return dst
181
+
182
+
183
+ @computation_function()
184
+ def difference_constant(src: ImageObj, p: ConstantParam) -> ImageObj:
185
+ """Subtract a constant value from an image and return the new result image object
186
+
187
+ Args:
188
+ src: input image object
189
+ p: constant value
190
+
191
+ Returns:
192
+ Result image object **src** - **p.value** (new object)
193
+ """
194
+ # For the subtraction of a constant value, we convert the constant value to the same
195
+ # data type as the input image, for consistency.
196
+ value = np.array(p.value).astype(dtype=src.data.dtype)
197
+ dst = dst_1_to_1(src, "-", str(value))
198
+ dst.data = np.subtract(src.data, value, dtype=float)
199
+ restore_data_outside_roi(dst, src)
200
+ return dst
201
+
202
+
203
+ @computation_function()
204
+ def product_constant(src: ImageObj, p: ConstantParam) -> ImageObj:
205
+ """Multiply **dst** by a constant value and return the new result image object
206
+
207
+ Args:
208
+ src: input image object
209
+ p: constant value
210
+
211
+ Returns:
212
+ Result image object **src** * **p.value** (new object)
213
+ """
214
+ # For the multiplication by a constant value, we do not convert the constant value
215
+ # to the same data type as the input image, because we want to allow the user to
216
+ # multiply an image by a constant value of a different data type. The final data
217
+ # type conversion ensures that the output image has the same data type as the input
218
+ # image.
219
+ dst = dst_1_to_1(src, "×", str(p.value))
220
+ dst.data = np.multiply(src.data, p.value, dtype=float)
221
+ restore_data_outside_roi(dst, src)
222
+ return dst
223
+
224
+
225
+ @computation_function()
226
+ def division_constant(src: ImageObj, p: ConstantParam) -> ImageObj:
227
+ """Divide an image by a constant value and return the new result image object
228
+
229
+ Args:
230
+ src: input image object
231
+ p: constant value
232
+
233
+ Returns:
234
+ Result image object **src** / **p.value** (new object)
235
+ """
236
+ # For the division by a constant value, we do not convert the constant value to the
237
+ # same data type as the input image, because we want to allow the user to divide an
238
+ # image by a constant value of a different data type. The final data type conversion
239
+ # ensures that the output image has the same data type as the input image.
240
+ dst = dst_1_to_1(src, "/", str(p.value))
241
+ dst.data = np.divide(src.data, p.value, dtype=float)
242
+ restore_data_outside_roi(dst, src)
243
+ return dst
244
+
245
+
246
+ # MARK: compute_2_to_1 functions -------------------------------------------------------
247
+ # Functions with N input images + 1 input image and N output images
248
+ # --------------------------------------------------------------------------------------
249
+
250
+
251
+ @computation_function()
252
+ def arithmetic(src1: ImageObj, src2: ImageObj, p: ArithmeticParam) -> ImageObj:
253
+ """Compute arithmetic operation on two images
254
+
255
+ Args:
256
+ src1: input image object
257
+ src2: input image object
258
+ p: arithmetic parameters
259
+
260
+ Returns:
261
+ Result image object
262
+ """
263
+ initial_dtype = src1.data.dtype
264
+ title = p.operation.replace("obj1", "{0}").replace("obj2", "{1}")
265
+ dst = src1.copy(title=title)
266
+ o, a, b = p.operator, p.factor, p.constant
267
+ # Apply operator
268
+ if o in (MathOperator.MULTIPLY, MathOperator.DIVIDE) and a == 0.0:
269
+ dst.data = np.ones_like(src1.data) * b
270
+ elif o == MathOperator.ADD:
271
+ dst.data = np.add(src1.data, src2.data, dtype=float) * a + b
272
+ elif o == MathOperator.SUBTRACT:
273
+ dst.data = np.subtract(src1.data, src2.data, dtype=float) * a + b
274
+ elif o == MathOperator.MULTIPLY:
275
+ dst.data = np.multiply(src1.data, src2.data, dtype=float) * a + b
276
+ elif o == MathOperator.DIVIDE:
277
+ dst.data = np.divide(src1.data, src2.data, dtype=float) * a + b
278
+ # Eventually convert to initial data type
279
+ if p.restore_dtype:
280
+ dst.data = clip_astype(dst.data, initial_dtype)
281
+ restore_data_outside_roi(dst, src1)
282
+ return dst
283
+
284
+
285
+ @computation_function()
286
+ def difference(src1: ImageObj, src2: ImageObj) -> ImageObj:
287
+ """Compute difference between two images
288
+
289
+ Args:
290
+ src1: input image object
291
+ src2: input image object
292
+
293
+ Returns:
294
+ Result image object **src1** - **src2** (new object)
295
+ """
296
+ dst = dst_2_to_1(src1, src2, "-")
297
+ dst.data = np.subtract(src1.data, src2.data, dtype=float)
298
+ restore_data_outside_roi(dst, src1)
299
+ return dst
300
+
301
+
302
+ @computation_function()
303
+ def quadratic_difference(src1: ImageObj, src2: ImageObj) -> ImageObj:
304
+ """Compute quadratic difference between two images
305
+
306
+ Args:
307
+ src1: input image object
308
+ src2: input image object
309
+
310
+ Returns:
311
+ Result image object (**src1** - **src2**) / sqrt(2.0) (new object)
312
+ """
313
+ dst = dst_2_to_1(src1, src2, "quadratic_difference")
314
+ dst.data = np.subtract(src1.data, src2.data, dtype=float) / np.sqrt(2.0)
315
+ restore_data_outside_roi(dst, src1)
316
+ return dst
317
+
318
+
319
+ @computation_function()
320
+ def division(src1: ImageObj, src2: ImageObj) -> ImageObj:
321
+ """Compute division between two images
322
+
323
+ Args:
324
+ src1: input image object
325
+ src2: input image object
326
+
327
+ Returns:
328
+ Result image object **src1** / **src2** (new object)
329
+ """
330
+ dst = dst_2_to_1(src1, src2, "/")
331
+ with warnings.catch_warnings():
332
+ warnings.simplefilter("ignore", category=RuntimeWarning)
333
+ dst.data = np.divide(src1.data, src2.data, dtype=float)
334
+ restore_data_outside_roi(dst, src1)
335
+ return dst
@@ -0,0 +1,260 @@
1
+ # Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file.
2
+
3
+ """
4
+ Base computation module
5
+ -----------------------
6
+
7
+ This module provides core classes and utility functions that serve as building blocks
8
+ for the other computation modules.
9
+
10
+ Main features include:
11
+
12
+ - Generic helper functions used across image processing modules
13
+ - Core wrappers and infrastructure for computation functions
14
+
15
+ Intended primarily for internal use, these tools support consistent API design
16
+ and code reuse.
17
+ """
18
+
19
+ from __future__ import annotations
20
+
21
+ from collections.abc import Callable
22
+ from typing import Any
23
+
24
+ import numpy as np
25
+
26
+ from sigima.objects import NO_ROI, GeometryResult, ImageObj, KindShape, SignalObj
27
+ from sigima.proc.base import dst_1_to_1, new_signal_result
28
+
29
+ # NOTE: Only parameter classes DEFINED in this module should be included in __all__.
30
+ # Parameter classes imported from other modules (like sigima.proc.base) should NOT
31
+ # be re-exported to avoid Sphinx cross-reference conflicts. The sigima.params module
32
+ # serves as the central API point that imports and re-exports all parameter classes.
33
+ __all__ = [
34
+ "Wrap1to1Func",
35
+ "compute_geometry_from_obj",
36
+ "dst_1_to_1_signal",
37
+ "restore_data_outside_roi",
38
+ ]
39
+
40
+
41
+ def restore_data_outside_roi(dst: ImageObj, src: ImageObj) -> None:
42
+ """Restore data outside the Region Of Interest (ROI) of the input image
43
+ after a computation, only if the input image has a ROI,
44
+ and if the output image has the same ROI as the input image,
45
+ and if the data types are compatible,
46
+ and if the shapes are the same.
47
+ Otherwise, do nothing.
48
+
49
+ Args:
50
+ dst: output image object
51
+ src: input image object
52
+ """
53
+ if src.maskdata is not None and dst.maskdata is not None:
54
+ if (
55
+ np.array_equal(src.maskdata, dst.maskdata)
56
+ and (
57
+ dst.data.dtype == src.data.dtype
58
+ or not np.issubdtype(dst.data.dtype, np.integer)
59
+ )
60
+ and dst.data.shape == src.data.shape
61
+ ):
62
+ dst.data[src.maskdata] = src.data[src.maskdata]
63
+
64
+
65
+ class Wrap1to1Func:
66
+ """Wrap a 1 array → 1 array function to produce a 1 image → 1 image function,
67
+ which can be used as a Sigima computation function and inside DataLab's
68
+ infrastructure to perform computations with the Image Processor object.
69
+
70
+ This wrapping mechanism using a class is necessary for the resulted function to be
71
+ pickable by the ``multiprocessing`` module.
72
+
73
+ The instance of this wrapper is callable and returns a
74
+ :class:`sigima.objects.ImageObj` object.
75
+
76
+ Example:
77
+
78
+ >>> import numpy as np
79
+ >>> from sigima.proc.image import Wrap1to1Func
80
+ >>> import sigima.objects
81
+ >>> def add_noise(data):
82
+ ... return data + np.random.random(data.shape)
83
+ >>> compute_add_noise = Wrap1to1Func(add_noise)
84
+ >>> data= np.ones((100, 100))
85
+ >>> ima0 = sigima.objects.create_image("Example", data)
86
+ >>> ima1 = compute_add_noise(ima0)
87
+
88
+ Args:
89
+ func: 1 array → 1 array function
90
+ *args: Additional positional arguments to pass to the function
91
+ **kwargs: Additional keyword arguments to pass to the function
92
+
93
+ .. note::
94
+
95
+ If `func_name` is provided in the keyword arguments, it will be used as the
96
+ function name instead of the default name derived from the function itself.
97
+ """
98
+
99
+ def __init__(self, func: Callable, *args: Any, **kwargs: Any) -> None:
100
+ self.func = func
101
+ self.args = args
102
+ self.kwargs = kwargs
103
+ self.__name__ = self.kwargs.pop("func_name", func.__name__)
104
+ self.__doc__ = func.__doc__
105
+ self.__call__.__func__.__doc__ = self.func.__doc__
106
+
107
+ def __call__(self, src: ImageObj) -> ImageObj:
108
+ """Compute the function on the input image and return the result image
109
+
110
+ Args:
111
+ src: input image object
112
+
113
+ Returns:
114
+ Output image object
115
+ """
116
+ suffix = ", ".join(
117
+ [str(arg) for arg in self.args]
118
+ + [f"{k}={v}" for k, v in self.kwargs.items() if v is not None]
119
+ )
120
+ dst = dst_1_to_1(src, self.__name__, suffix)
121
+ dst.data = self.func(src.data, *self.args, **self.kwargs)
122
+ restore_data_outside_roi(dst, src)
123
+ return dst
124
+
125
+
126
+ def dst_1_to_1_signal(src: ImageObj, name: str, suffix: str | None = None) -> SignalObj:
127
+ """Create a result signal object, for processing functions that take a single
128
+ image object as input and return a single signal object (1-to-1-signal).
129
+
130
+ Args:
131
+ src: input image object
132
+ name: name of the processing function
133
+
134
+ Returns:
135
+ Output signal object
136
+ """
137
+ return new_signal_result(
138
+ src, name, suffix, (src.xunit, src.zunit), (src.xlabel, src.zlabel)
139
+ )
140
+
141
+
142
+ def compute_geometry_from_obj(
143
+ title: str,
144
+ shape: KindShape,
145
+ obj: ImageObj,
146
+ func: Callable,
147
+ *args: Any,
148
+ ) -> GeometryResult | None:
149
+ """Compute a geometry shape from an image object by executing a computation function
150
+ on the data of the image object, for each ROI (Region Of Interest) in the image.
151
+
152
+ Args:
153
+ title: result title
154
+ shape: result shape kind
155
+ obj: input image object
156
+ func: computation function
157
+ *args: computation function arguments
158
+
159
+ Returns:
160
+ A geometry result object or None if no result is found.
161
+
162
+ .. important::
163
+ **Coordinate Conversion**: This function automatically converts coordinates
164
+ from pixel units (image indices) to physical units using the image object's
165
+ calibration information.
166
+
167
+ - **Input**: Computation function returns coordinates in pixel units
168
+ - **Output**: GeometryResult with coordinates in physical units (e.g., mm, µm)
169
+
170
+ The conversion is performed using the image's calibration parameters:
171
+ ``physical_x = obj.dx * pixel_x + obj.x0`` and
172
+ ``physical_y = obj.dy * pixel_y + obj.y0``
173
+
174
+ .. warning::
175
+
176
+ The computation function must take either a single argument (the data) or
177
+ multiple arguments (the data followed by the computation parameters).
178
+
179
+ Moreover, the computation function must return a single value or a NumPy array
180
+ containing the result of the computation. This array contains the coordinates
181
+ of points, polygons, circles or ellipses in the form [[x, y], ...], or
182
+ [[x0, y0, x1, y1, ...], ...], or [[x0, y0, r], ...], or
183
+ [[x0, y0, a, b, theta], ...].
184
+
185
+ Example:
186
+ >>> # func returns pixel coordinates like [[10, 20], [30, 40]]
187
+ >>> result = compute_geometry_from_obj(
188
+ ... "Points", KindShape.POINT, image_obj, func
189
+ ... )
190
+ >>> # result.coords now contains physical coordinates like [[0.5, 1.0],
191
+ >>> # [1.5, 2.0]]
192
+
193
+ See Also:
194
+ :class:`~sigima.objects.scalar.GeometryResult`: The result object that stores
195
+ physical coordinates.
196
+ """
197
+ rows: list[np.ndarray] = []
198
+ num_cols: list[int] = []
199
+ roi_idx: list[int] = []
200
+ for i_roi in obj.iterate_roi_indices():
201
+ data_roi = obj.get_data(i_roi)
202
+ if args is None:
203
+ coords: np.ndarray = func(data_roi)
204
+ else:
205
+ coords: np.ndarray = func(data_roi, *args)
206
+
207
+ # This is a very long condition, but it's still quite readable, so we keep it
208
+ # as is and disable the pylint warning.
209
+ #
210
+ # pylint: disable=too-many-boolean-expressions
211
+ if not isinstance(coords, np.ndarray) or (
212
+ (
213
+ coords.ndim != 2
214
+ or coords.shape[1] < 2
215
+ or (coords.shape[1] > 5 and coords.shape[1] % 2 != 0)
216
+ )
217
+ and coords.size > 0
218
+ ):
219
+ raise ValueError(
220
+ f"Computation function {func.__name__} must return a NumPy array "
221
+ f"containing coordinates of points, polygons, circles or ellipses "
222
+ f"(in the form [[x, y], ...], or [[x0, y0, x1, y1, ...], ...], or "
223
+ f"[[x0, y0, r], ...], or [[x0, y0, a, b, theta], ...]), or an empty "
224
+ f"array."
225
+ )
226
+
227
+ if coords.size:
228
+ coords = np.array(coords, dtype=float)
229
+ if coords.shape[1] % 2 == 0:
230
+ # Coordinates are in the form [x0, y0, x1, y1, ...]
231
+ colx, coly = slice(None, None, 2), slice(1, None, 2)
232
+ else:
233
+ # Circle [x0, y0, r] or ellipse coordinates [x0, y0, a, b, theta]
234
+ colx, coly = 0, 1
235
+ coords[:, colx] = obj.dx * coords[:, colx] + obj.x0
236
+ coords[:, coly] = obj.dy * coords[:, coly] + obj.y0
237
+ if obj.roi is not None:
238
+ x0, y0, _x1, _y1 = obj.roi.get_single_roi(i_roi).get_bounding_box(obj)
239
+ coords[:, colx] += x0 - obj.x0
240
+ coords[:, coly] += y0 - obj.y0
241
+
242
+ rows.append(coords)
243
+ num_cols.append(coords.shape[1])
244
+ roi_idx.extend([NO_ROI if i_roi is None else int(i_roi)] * coords.shape[0])
245
+ if rows:
246
+ if len(set(num_cols)) != 1:
247
+ # This happens when the number of columns is not the same for all ROIs.
248
+ # As of now, this happens only for polygon contours.
249
+ # We need to pad the arrays with NaNs.
250
+ max_cols = max(num_cols)
251
+ num_rows = sum(coords.shape[0] for coords in rows)
252
+ array = np.full((num_rows, max_cols), np.nan)
253
+ start = 0
254
+ for row in rows:
255
+ array[start : start + row.shape[0], : row.shape[1]] = row
256
+ start += row.shape[0]
257
+ else:
258
+ array = np.vstack(rows)
259
+ return GeometryResult(title, shape, array, np.asarray(roi_idx, dtype=int))
260
+ return None