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,107 @@
1
+ # Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file.
2
+
3
+ """
4
+ Noise addition computation module.
5
+ ----------------------------------
6
+ """
7
+
8
+ # pylint: disable=invalid-name # Allows short reference names like x, y...
9
+
10
+ # Note:
11
+ # ----
12
+ # - All `guidata.dataset.DataSet` parameter classes must also be imported in the
13
+ # `sigima.params` module.
14
+ # - All functions decorated by `computation_function` must be imported in the upper
15
+ # level `sigima.proc.image` module.
16
+
17
+ from __future__ import annotations
18
+
19
+ import guidata.dataset as gds
20
+
21
+ from sigima.objects.base import (
22
+ NormalDistributionParam,
23
+ PoissonDistributionParam,
24
+ UniformDistributionParam,
25
+ )
26
+ from sigima.objects.image import (
27
+ ImageObj,
28
+ NormalDistribution2DParam,
29
+ PoissonDistribution2DParam,
30
+ UniformDistribution2DParam,
31
+ create_image_from_param,
32
+ )
33
+ from sigima.proc.base import dst_1_to_1
34
+ from sigima.proc.decorator import computation_function
35
+ from sigima.proc.image.arithmetic import addition
36
+
37
+
38
+ @computation_function()
39
+ def add_gaussian_noise(src: ImageObj, p: NormalDistributionParam) -> ImageObj:
40
+ """Add Gaussian (normal) noise to the input image.
41
+
42
+ Args:
43
+ src: Source image.
44
+ p: Parameters.
45
+
46
+ Returns:
47
+ Result image object.
48
+ """
49
+ param = NormalDistribution2DParam() # Do not confuse with NormalDistributionParam
50
+ gds.update_dataset(param, p)
51
+ assert src.data is not None
52
+ shape = src.data.shape
53
+ param.height = shape[0]
54
+ param.width = shape[1]
55
+ param.dtype = src.data.dtype
56
+ noise = create_image_from_param(param)
57
+ dst = dst_1_to_1(src, "add_gaussian_noise", f"mu={p.mu},sigma={p.sigma}")
58
+ dst.data = addition([dst, noise]).data
59
+ return dst
60
+
61
+
62
+ @computation_function()
63
+ def add_poisson_noise(src: ImageObj, p: PoissonDistributionParam) -> ImageObj:
64
+ """Add Poisson noise to the input image.
65
+
66
+ Args:
67
+ src: Source image.
68
+ p: Parameters.
69
+
70
+ Returns:
71
+ Result image object.
72
+ """
73
+ param = PoissonDistribution2DParam() # Do not confuse with PoissonDistributionParam
74
+ gds.update_dataset(param, p)
75
+ assert src.data is not None
76
+ shape = src.data.shape
77
+ param.height = shape[0]
78
+ param.width = shape[1]
79
+ param.dtype = src.data.dtype
80
+ noise = create_image_from_param(param)
81
+ dst = dst_1_to_1(src, "add_poisson_noise", f"lam={p.lam}")
82
+ dst.data = addition([dst, noise]).data
83
+ return dst
84
+
85
+
86
+ @computation_function()
87
+ def add_uniform_noise(src: ImageObj, p: UniformDistributionParam) -> ImageObj:
88
+ """Add uniform noise to the input image.
89
+
90
+ Args:
91
+ src: Source image.
92
+ p: Parameters.
93
+
94
+ Returns:
95
+ Result image object.
96
+ """
97
+ param = UniformDistribution2DParam() # Do not confuse with UniformDistributionParam
98
+ gds.update_dataset(param, p)
99
+ assert src.data is not None
100
+ shape = src.data.shape
101
+ param.height = shape[0]
102
+ param.width = shape[1]
103
+ param.dtype = src.data.dtype
104
+ noise = create_image_from_param(param)
105
+ dst = dst_1_to_1(src, "add_uniform_noise", f"low={p.vmin}, high={p.vmax}")
106
+ dst.data = addition([dst, noise]).data
107
+ return dst
@@ -0,0 +1,182 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Image preprocessing functions.
4
+
5
+ This module consolidates preprocessing functions that were previously scattered
6
+ across different modules (exposure, geometry, fourier). All functions in this
7
+ module operate on high-level ImageObj objects and use parameter classes from
8
+ the sigima.proc framework.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import guidata.dataset as gds
14
+ import numpy as np
15
+
16
+ import sigima.enums
17
+ import sigima.tools.image
18
+ from sigima.config import _
19
+ from sigima.enums import PadLocation2D
20
+ from sigima.objects.image import ImageObj
21
+ from sigima.proc.base import dst_1_to_1
22
+ from sigima.proc.decorator import computation_function
23
+
24
+ __all__ = [
25
+ "BinningParam",
26
+ "ZeroPadding2DParam",
27
+ "binning",
28
+ "zero_padding",
29
+ ]
30
+
31
+
32
+ class BinningParam(gds.DataSet):
33
+ """Binning parameters."""
34
+
35
+ sx = gds.IntItem(
36
+ _("Cluster size (X)"),
37
+ default=2,
38
+ min=2,
39
+ help=_("Number of adjacent pixels to be combined together along X-axis."),
40
+ )
41
+ sy = gds.IntItem(
42
+ _("Cluster size (Y)"),
43
+ default=2,
44
+ min=2,
45
+ help=_("Number of adjacent pixels to be combined together along Y-axis."),
46
+ )
47
+ operation = gds.ChoiceItem(
48
+ _("Operation"),
49
+ sigima.enums.BinningOperation,
50
+ default=sigima.enums.BinningOperation.SUM,
51
+ )
52
+ dtypes = ["dtype"] + ImageObj.get_valid_dtypenames()
53
+ dtype_str = gds.ChoiceItem(
54
+ _("Data type"),
55
+ list(zip(dtypes, dtypes)),
56
+ help=_("Output image data type."),
57
+ )
58
+ change_pixel_size = gds.BoolItem(
59
+ _("Change pixel size"),
60
+ default=True,
61
+ help=_(
62
+ "If checked, pixel size is updated according to binning factors. "
63
+ "Users who prefer to work with pixel coordinates may want to uncheck this."
64
+ ),
65
+ )
66
+
67
+
68
+ @computation_function()
69
+ def binning(src: ImageObj, p: BinningParam) -> ImageObj:
70
+ """Binning: image pixel binning (or aggregation).
71
+
72
+ Depending on the algorithm, the input image may be cropped to fit an integer
73
+ number of blocks.
74
+
75
+ Args:
76
+ src: source image
77
+ p: parameters
78
+
79
+ Returns:
80
+ Output image
81
+
82
+ Raises:
83
+ ValueError: if source image has non-uniform coordinates
84
+ """
85
+ if not src.is_uniform_coords:
86
+ raise ValueError("Binning only works with images having uniform coordinates")
87
+ # Create destination image
88
+ dst = dst_1_to_1(
89
+ src,
90
+ "binning",
91
+ f"{p.sx}x{p.sy},{p.operation},change_pixel_size={p.change_pixel_size}",
92
+ )
93
+ dst.data = sigima.tools.image.binning(
94
+ src.data,
95
+ sx=p.sx,
96
+ sy=p.sy,
97
+ operation=p.operation,
98
+ dtype=None if p.dtype_str == "dtype" else p.dtype_str,
99
+ )
100
+ if p.change_pixel_size:
101
+ if not np.isnan(src.dx) and not np.isnan(src.dy):
102
+ # Update coordinates with new pixel spacing
103
+ new_dx = src.dx * p.sx
104
+ new_dy = src.dy * p.sy
105
+ dst.set_uniform_coords(new_dx, new_dy, src.x0, src.y0)
106
+ return dst
107
+
108
+
109
+ class ZeroPadding2DParam(gds.DataSet):
110
+ """Zero padding parameters for 2D images"""
111
+
112
+ def __init__(self, *args, **kwargs) -> None:
113
+ super().__init__(*args, **kwargs)
114
+ self.__obj: ImageObj | None = None
115
+
116
+ def update_from_obj(self, obj: ImageObj) -> None:
117
+ """Update parameters from image"""
118
+ self.__obj = obj
119
+ self.choice_callback(None, self.strategy)
120
+
121
+ def choice_callback(self, item, value): # pylint: disable=unused-argument
122
+ """Callback to update padding values"""
123
+ if self.__obj is None:
124
+ return
125
+ rows, cols = self.__obj.data.shape
126
+ if value == "next_pow2":
127
+ self.rows = 2 ** int(np.ceil(np.log2(rows))) - rows
128
+ self.cols = 2 ** int(np.ceil(np.log2(cols))) - cols
129
+ elif value == "multiple_of_64":
130
+ self.rows = (64 - rows % 64) if rows % 64 != 0 else 0
131
+ self.cols = (64 - cols % 64) if cols % 64 != 0 else 0
132
+
133
+ strategies = ("next_pow2", "multiple_of_64", "custom")
134
+ _prop = gds.GetAttrProp("strategy")
135
+ strategy = gds.ChoiceItem(
136
+ _("Padding strategy"), zip(strategies, strategies), default=strategies[-1]
137
+ ).set_prop("display", store=_prop, callback=choice_callback)
138
+
139
+ _func_prop = gds.FuncProp(_prop, lambda x: x == "custom")
140
+ rows = gds.IntItem(_("Rows to add"), min=0, default=0).set_prop(
141
+ "display", active=_func_prop
142
+ )
143
+ cols = gds.IntItem(_("Columns to add"), min=0, default=0).set_prop(
144
+ "display", active=_func_prop
145
+ )
146
+ position = gds.ChoiceItem(
147
+ _("Padding position"), PadLocation2D, default=PadLocation2D.BOTTOM_RIGHT
148
+ )
149
+
150
+
151
+ @computation_function()
152
+ def zero_padding(
153
+ src: ImageObj,
154
+ p: ZeroPadding2DParam | None = None,
155
+ ) -> ImageObj:
156
+ """Zero-padding: add zeros to image borders.
157
+
158
+ Args:
159
+ src: input image object
160
+ p: parameters
161
+
162
+ Returns:
163
+ Output image object
164
+ """
165
+ if p is None:
166
+ p = ZeroPadding2DParam.create()
167
+
168
+ if p.strategy == "custom":
169
+ suffix = f"rows={p.rows}, cols={p.cols}"
170
+ else:
171
+ suffix = f"strategy={p.strategy}"
172
+ suffix += f", position={p.position}"
173
+
174
+ dst = dst_1_to_1(src, "zero_padding", suffix)
175
+ result = sigima.tools.image.zero_padding(
176
+ src.data,
177
+ rows=p.rows,
178
+ cols=p.cols,
179
+ position=p.position,
180
+ )
181
+ dst.data = result
182
+ return dst
@@ -0,0 +1,235 @@
1
+ # Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file.
2
+
3
+ """
4
+ Restoration computation module
5
+ ------------------------------
6
+
7
+ This module provides image restoration techniques, such as
8
+ denoising, inpainting, and deblurring. These methods aim to recover
9
+ the original quality of images by removing artifacts, noise, or
10
+ distortions.
11
+ """
12
+
13
+ # pylint: disable=invalid-name # Allows short reference names like x, y, ...
14
+
15
+ # Note:
16
+ # ----
17
+ # - All `guidata.dataset.DataSet` parameter classes must also be imported
18
+ # in the `sigima.params` module.
19
+ # - All functions decorated by `computation_function` must be imported in the upper
20
+ # level `sigima.proc.image` module.
21
+
22
+ from __future__ import annotations
23
+
24
+ from typing import TYPE_CHECKING
25
+
26
+ import guidata.dataset as gds
27
+ import numpy as np
28
+ import pywt
29
+ from skimage import morphology, restoration
30
+
31
+ from sigima.config import _
32
+ from sigima.enums import ShrinkageMethod, ThresholdMethod, WaveletMode
33
+ from sigima.objects.image import ImageObj, ROI2DParam
34
+ from sigima.proc.base import dst_1_to_1
35
+ from sigima.proc.decorator import computation_function
36
+ from sigima.proc.image.base import Wrap1to1Func, restore_data_outside_roi
37
+
38
+ if TYPE_CHECKING:
39
+ import sigima.params
40
+
41
+
42
+ # NOTE: Only parameter classes DEFINED in this module should be included in __all__.
43
+ # Parameter classes imported from other modules (like sigima.proc.base) should NOT
44
+ # be re-exported to avoid Sphinx cross-reference conflicts. The sigima.params module
45
+ # serves as the central API point that imports and re-exports all parameter classes.
46
+ __all__ = [
47
+ "DenoiseBilateralParam",
48
+ "DenoiseTVParam",
49
+ "DenoiseWaveletParam",
50
+ "denoise_bilateral",
51
+ "denoise_tophat",
52
+ "denoise_tv",
53
+ "denoise_wavelet",
54
+ "erase",
55
+ ]
56
+
57
+
58
+ class DenoiseTVParam(gds.DataSet):
59
+ """Total Variation denoising parameters"""
60
+
61
+ weight = gds.FloatItem(
62
+ _("Denoising weight"),
63
+ default=0.1,
64
+ min=0,
65
+ nonzero=True,
66
+ help=_(
67
+ "The greater weight, the more denoising "
68
+ "(at the expense of fidelity to input)."
69
+ ),
70
+ )
71
+ eps = gds.FloatItem(
72
+ "Epsilon",
73
+ default=0.0002,
74
+ min=0,
75
+ nonzero=True,
76
+ help=_(
77
+ "Relative difference of the value of the cost function that "
78
+ "determines the stop criterion. The algorithm stops when: "
79
+ "(E_(n-1) - E_n) < eps * E_0"
80
+ ),
81
+ )
82
+ max_num_iter = gds.IntItem(
83
+ _("Max. iterations"),
84
+ default=200,
85
+ min=0,
86
+ nonzero=True,
87
+ help=_("Maximal number of iterations used for the optimization"),
88
+ )
89
+
90
+
91
+ @computation_function()
92
+ def denoise_tv(src: ImageObj, p: DenoiseTVParam) -> ImageObj:
93
+ """Compute Total Variation denoising
94
+ with :py:func:`skimage.restoration.denoise_tv_chambolle`
95
+
96
+ Args:
97
+ src: input image object
98
+ p: parameters
99
+
100
+ Returns:
101
+ Output image object
102
+ """
103
+ return Wrap1to1Func(
104
+ restoration.denoise_tv_chambolle,
105
+ weight=p.weight,
106
+ eps=p.eps,
107
+ max_num_iter=p.max_num_iter,
108
+ func_name="denoise_tv",
109
+ )(src)
110
+
111
+
112
+ class DenoiseBilateralParam(gds.DataSet):
113
+ """Bilateral filter denoising parameters"""
114
+
115
+ sigma_spatial = gds.FloatItem(
116
+ "σ<sub>spatial</sub>",
117
+ default=1.0,
118
+ min=0,
119
+ nonzero=True,
120
+ unit="pixels",
121
+ help=_(
122
+ "Standard deviation for range distance. "
123
+ "A larger value results in averaging of pixels "
124
+ "with larger spatial differences."
125
+ ),
126
+ )
127
+ mode = gds.ChoiceItem(_("Mode"), WaveletMode, default=WaveletMode.CONSTANT)
128
+ cval = gds.FloatItem(
129
+ "cval",
130
+ default=0.0,
131
+ help=_(
132
+ "Used in conjunction with mode 'constant', "
133
+ "the value outside the image boundaries."
134
+ ),
135
+ )
136
+
137
+
138
+ @computation_function()
139
+ def denoise_bilateral(src: ImageObj, p: DenoiseBilateralParam) -> ImageObj:
140
+ """Compute bilateral filter denoising
141
+ with :py:func:`skimage.restoration.denoise_bilateral`
142
+
143
+ Args:
144
+ src: input image object
145
+ p: parameters
146
+
147
+ Returns:
148
+ Output image object
149
+ """
150
+ return Wrap1to1Func(
151
+ restoration.denoise_bilateral,
152
+ sigma_spatial=p.sigma_spatial,
153
+ mode=p.mode,
154
+ cval=p.cval,
155
+ )(src)
156
+
157
+
158
+ class DenoiseWaveletParam(gds.DataSet):
159
+ """Wavelet denoising parameters"""
160
+
161
+ wavelets = pywt.wavelist()
162
+ wavelet = gds.ChoiceItem(
163
+ _("Wavelet"), list(zip(wavelets, wavelets)), default="sym9"
164
+ )
165
+ mode = gds.ChoiceItem(_("Mode"), ThresholdMethod, default=ThresholdMethod.SOFT)
166
+ method = gds.ChoiceItem(
167
+ _("Method"), ShrinkageMethod, default=ShrinkageMethod.VISU_SHRINK
168
+ )
169
+
170
+
171
+ @computation_function()
172
+ def denoise_wavelet(src: ImageObj, p: DenoiseWaveletParam) -> ImageObj:
173
+ """Compute Wavelet denoising
174
+ with :py:func:`skimage.restoration.denoise_wavelet`
175
+
176
+ Args:
177
+ src: input image object
178
+ p: parameters
179
+
180
+ Returns:
181
+ Output image object
182
+ """
183
+ return Wrap1to1Func(
184
+ restoration.denoise_wavelet, wavelet=p.wavelet, mode=p.mode, method=p.method
185
+ )(src)
186
+
187
+
188
+ @computation_function()
189
+ def denoise_tophat(src: ImageObj, p: sigima.params.MorphologyParam) -> ImageObj:
190
+ """Denoise using White Top-Hat
191
+ with :py:func:`skimage.morphology.white_tophat`
192
+
193
+ Args:
194
+ src: input image object
195
+ p: parameters
196
+
197
+ Returns:
198
+ Output image object
199
+ """
200
+ dst = dst_1_to_1(src, "denoise_tophat", f"radius={p.radius}")
201
+ dst.data = src.data - morphology.white_tophat(src.data, morphology.disk(p.radius))
202
+ restore_data_outside_roi(dst, src)
203
+ return dst
204
+
205
+
206
+ @computation_function()
207
+ def erase(src: ImageObj, p: ROI2DParam | list[ROI2DParam]) -> ImageObj:
208
+ """Erase an area of the image using the mean value of the image.
209
+
210
+ .. note::
211
+
212
+ The erased area is defined by a region of interest (ROI) parameter set.
213
+ This ROI must not be mistaken with the ROI of the image object. If the
214
+ image object has a ROI, it is not used in this processing, except to
215
+ restore the data outside the ROI (as in all other processing).
216
+
217
+ Args:
218
+ src: input image object
219
+ p: parameters defining the area to erase (region of interest)
220
+
221
+ Returns:
222
+ Output image object
223
+ """
224
+ params = [p] if isinstance(p, ROI2DParam) else p
225
+ suffix = None
226
+ if len(params) == 1:
227
+ suffix = params[0].get_suffix()
228
+ dst = dst_1_to_1(src, "erase", suffix)
229
+ for param in params:
230
+ value = np.nanmean(param.get_data(src))
231
+ erase_roi = param.to_single_roi(src)
232
+ mask = erase_roi.to_mask(src)
233
+ dst.data[~mask] = value
234
+ restore_data_outside_roi(dst, src)
235
+ return dst