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,518 @@
1
+ # Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file.
2
+
3
+ """Unit tests for image operations."""
4
+
5
+ # pylint: disable=invalid-name # Allows short reference names like x, y, ...
6
+
7
+ from __future__ import annotations
8
+
9
+ import warnings
10
+ from typing import Generator
11
+
12
+ import numpy as np
13
+ import pytest
14
+
15
+ import sigima.objects
16
+ import sigima.params
17
+ import sigima.proc.image
18
+ from sigima.enums import AngleUnit, MathOperator
19
+ from sigima.objects.image import ImageObj
20
+ from sigima.proc.base import AngleUnitParam
21
+ from sigima.proc.image import complex_from_magnitude_phase, complex_from_real_imag
22
+ from sigima.tests import guiutils
23
+ from sigima.tests.data import (
24
+ create_noisy_gaussian_image,
25
+ iterate_noisy_image_couples,
26
+ iterate_noisy_images,
27
+ )
28
+ from sigima.tests.env import execenv
29
+ from sigima.tests.helpers import check_array_result
30
+ from sigima.tools.coordinates import polar_to_complex
31
+
32
+
33
+ def __create_n_images(n: int = 100) -> list[sigima.objects.ImageObj]:
34
+ """Create a list of N different images for testing."""
35
+ images = []
36
+ for i in range(n):
37
+ param = sigima.objects.NewImageParam.create(
38
+ dtype=sigima.objects.ImageDatatypes.FLOAT32,
39
+ height=128,
40
+ width=128,
41
+ )
42
+ img = create_noisy_gaussian_image(param, level=(i + 1) * 0.1)
43
+ images.append(img)
44
+ return images
45
+
46
+
47
+ @pytest.mark.validation
48
+ def test_image_addition() -> None:
49
+ """Image addition test."""
50
+ execenv.print("*** Testing image addition:")
51
+ for ima1, ima2 in iterate_noisy_image_couples(size=128):
52
+ dtype1, dtype2 = ima1.data.dtype, ima2.data.dtype
53
+ execenv.print(f" {dtype1} += {dtype2}: ", end="")
54
+ exp = ima1.data.astype(float) + ima2.data.astype(float)
55
+ ima3 = sigima.proc.image.addition([ima1, ima2])
56
+ check_array_result("Image addition", ima3.data, exp)
57
+ imalist = __create_n_images()
58
+ n = len(imalist)
59
+ ima3 = sigima.proc.image.addition(imalist)
60
+ res = ima3.data
61
+ exp = np.zeros_like(ima3.data)
62
+ for ima in imalist:
63
+ exp += ima.data
64
+ check_array_result(f" Addition of {n} images", res, exp)
65
+
66
+
67
+ @pytest.mark.validation
68
+ def test_image_average() -> None:
69
+ """Image average test."""
70
+ execenv.print("*** Testing image average:")
71
+ for ima1, ima2 in iterate_noisy_image_couples(size=128):
72
+ dtype1, dtype2 = ima1.data.dtype, ima2.data.dtype
73
+ execenv.print(f" µ({dtype1},{dtype2}): ", end="")
74
+ exp = (ima1.data.astype(float) + ima2.data.astype(float)) / 2.0
75
+ ima3 = sigima.proc.image.average([ima1, ima2])
76
+ check_array_result("Image average", ima3.data, exp)
77
+ imalist = __create_n_images()
78
+ n = len(imalist)
79
+ ima3 = sigima.proc.image.average(imalist)
80
+ res = ima3.data
81
+ exp = np.zeros_like(ima3.data)
82
+ for ima in imalist:
83
+ exp += ima.data
84
+ exp /= n
85
+ check_array_result(f" Average of {n} images", res, exp)
86
+
87
+
88
+ @pytest.mark.validation
89
+ def test_image_standard_deviation() -> None:
90
+ """Image standard deviation test."""
91
+ imalist = __create_n_images()
92
+ n = len(imalist)
93
+ s1 = sigima.proc.image.standard_deviation(imalist)
94
+ assert s1.data is not None
95
+ exp = np.zeros_like(s1.data)
96
+ average = np.mean([ima.data for ima in imalist if ima.data is not None], axis=0)
97
+ for ima in imalist:
98
+ exp += (ima.data - average) ** 2
99
+ exp = np.sqrt(exp / n)
100
+ check_array_result(f"Standard Deviation of {n} images", s1.data, exp)
101
+
102
+
103
+ @pytest.mark.validation
104
+ def test_image_difference() -> None:
105
+ """Image difference test."""
106
+ execenv.print("*** Testing image difference:")
107
+ for ima1, ima2 in iterate_noisy_image_couples(size=128):
108
+ dtype1, dtype2 = ima1.data.dtype, ima2.data.dtype
109
+ execenv.print(f" {dtype1} -= {dtype2}: ", end="")
110
+ exp = ima1.data.astype(float) - ima2.data.astype(float)
111
+ ima3 = sigima.proc.image.difference(ima1, ima2)
112
+ check_array_result("Image difference", ima3.data, exp)
113
+
114
+
115
+ @pytest.mark.validation
116
+ def test_image_quadratic_difference() -> None:
117
+ """Quadratic difference test."""
118
+ execenv.print("*** Testing quadratic difference:")
119
+ for ima1, ima2 in iterate_noisy_image_couples(size=128):
120
+ dtype1, dtype2 = ima1.data.dtype, ima2.data.dtype
121
+ execenv.print(f" ({dtype1} - {dtype2})/√2: ", end="")
122
+ exp = (ima1.data.astype(float) - ima2.data.astype(float)) / np.sqrt(2)
123
+ ima3 = sigima.proc.image.quadratic_difference(ima1, ima2)
124
+ check_array_result("Image quadratic difference", ima3.data, exp)
125
+
126
+
127
+ @pytest.mark.validation
128
+ def test_image_product() -> None:
129
+ """Image multiplication test."""
130
+ execenv.print("*** Testing image multiplication:")
131
+ for ima1, ima2 in iterate_noisy_image_couples(size=128):
132
+ dtype1, dtype2 = ima1.data.dtype, ima2.data.dtype
133
+ execenv.print(f" {dtype1} *= {dtype2}: ", end="")
134
+ exp = ima1.data.astype(float) * ima2.data.astype(float)
135
+ ima3 = sigima.proc.image.product([ima1, ima2])
136
+ check_array_result("Image multiplication", ima3.data, exp)
137
+ imalist = __create_n_images()
138
+ n = len(imalist)
139
+ ima3 = sigima.proc.image.product(imalist)
140
+ res = ima3.data
141
+ exp = np.ones_like(ima3.data)
142
+ for ima in imalist:
143
+ exp *= ima.data
144
+ check_array_result(f" Multiplication of {n} images", res, exp)
145
+
146
+
147
+ @pytest.mark.validation
148
+ def test_image_division() -> None:
149
+ """Image division test."""
150
+ execenv.print("*** Testing image division:")
151
+ for ima1, ima2 in iterate_noisy_image_couples(size=128):
152
+ ima2.data = np.ones_like(ima2.data)
153
+ dtype1, dtype2 = ima1.data.dtype, ima2.data.dtype
154
+ execenv.print(f" {dtype1} /= {dtype2}: ", end="")
155
+ exp = ima1.data.astype(float) / ima2.data.astype(float)
156
+ ima3 = sigima.proc.image.division(ima1, ima2)
157
+ if not np.allclose(ima3.data, exp):
158
+ guiutils.view_images_side_by_side_if_gui(
159
+ [ima1.data, ima2.data, ima3.data], ["ima1", "ima2", "ima3"]
160
+ )
161
+ check_array_result("Image division", ima3.data, exp)
162
+
163
+
164
+ def __constparam(value: float) -> sigima.params.ConstantParam:
165
+ """Create a constant parameter."""
166
+ return sigima.params.ConstantParam.create(value=value)
167
+
168
+
169
+ def __iterate_image_with_constant() -> Generator[
170
+ tuple[sigima.objects.ImageObj, sigima.params.ConstantParam], None, None
171
+ ]:
172
+ """Iterate over all possible image and constant couples for testing."""
173
+ size = 128
174
+ for dtype in sigima.objects.ImageDatatypes:
175
+ param = sigima.objects.NewImageParam.create(
176
+ dtype=dtype, height=size, width=size
177
+ )
178
+ ima = create_noisy_gaussian_image(param, level=0.0)
179
+ for value in (-1.0, 3.14, 5.0):
180
+ p = __constparam(value)
181
+ yield ima, p
182
+
183
+
184
+ @pytest.mark.validation
185
+ def test_image_addition_constant() -> None:
186
+ """Image addition with constant test."""
187
+ execenv.print("*** Testing image addition with constant:")
188
+ for ima1, p in __iterate_image_with_constant():
189
+ dtype1 = ima1.data.dtype
190
+ execenv.print(f" {dtype1} += constant ({p.value}): ", end="")
191
+ expvalue = np.array(p.value).astype(dtype=dtype1)
192
+ exp = ima1.data.astype(float) + expvalue
193
+ ima2 = sigima.proc.image.addition_constant(ima1, p)
194
+ check_array_result(f"Image + constant ({p.value})", ima2.data, exp)
195
+
196
+
197
+ @pytest.mark.validation
198
+ def test_image_difference_constant() -> None:
199
+ """Image difference with constant test."""
200
+ execenv.print("*** Testing image difference with constant:")
201
+ for ima1, p in __iterate_image_with_constant():
202
+ dtype1 = ima1.data.dtype
203
+ execenv.print(f" {dtype1} -= constant ({p.value}): ", end="")
204
+ expvalue = np.array(p.value).astype(dtype=dtype1)
205
+ exp = ima1.data.astype(float) - expvalue
206
+ ima2 = sigima.proc.image.difference_constant(ima1, p)
207
+ check_array_result(f"Image - constant ({p.value})", ima2.data, exp)
208
+
209
+
210
+ @pytest.mark.validation
211
+ def test_image_product_constant() -> None:
212
+ """Image multiplication by constant test."""
213
+ execenv.print("*** Testing image multiplication by constant:")
214
+ for ima1, p in __iterate_image_with_constant():
215
+ dtype1 = ima1.data.dtype
216
+ execenv.print(f" {dtype1} *= constant ({p.value}): ", end="")
217
+ exp = ima1.data.astype(float) * p.value
218
+ ima2 = sigima.proc.image.product_constant(ima1, p)
219
+ check_array_result(f"Image x constant ({p.value})", ima2.data, exp)
220
+
221
+
222
+ @pytest.mark.validation
223
+ def test_image_division_constant() -> None:
224
+ """Image division by constant test."""
225
+ execenv.print("*** Testing image division by constant:")
226
+ for ima1, p in __iterate_image_with_constant():
227
+ dtype1 = ima1.data.dtype
228
+ execenv.print(f" {dtype1} /= constant ({p.value}): ", end="")
229
+ exp = ima1.data.astype(float) / p.value
230
+ ima2 = sigima.proc.image.division_constant(ima1, p)
231
+ check_array_result(f"Image / constant ({p.value})", ima2.data, exp)
232
+
233
+
234
+ @pytest.mark.validation
235
+ def test_image_arithmetic() -> None:
236
+ """Image arithmetic test."""
237
+ execenv.print("*** Testing image arithmetic:")
238
+ # pylint: disable=too-many-nested-blocks
239
+ for ima1, ima2 in iterate_noisy_image_couples(size=128):
240
+ dtype1 = ima1.data.dtype
241
+ p = sigima.params.ArithmeticParam.create()
242
+ for o in MathOperator:
243
+ p.operator = o
244
+ for a in (0.0, 1.0, 2.0):
245
+ p.factor = a
246
+ for b in (0.0, 1.0, 2.0):
247
+ p.constant = b
248
+ ima2.data = np.clip(ima2.data, 1, None) # Avoid division by zero
249
+ ima3 = sigima.proc.image.arithmetic(ima1, ima2, p)
250
+ if o in (MathOperator.MULTIPLY, MathOperator.DIVIDE) and a == 0.0:
251
+ exp = np.ones_like(ima1.data) * b
252
+ elif o == MathOperator.ADD:
253
+ exp = np.add(ima1.data, ima2.data, dtype=float) * a + b
254
+ elif o == MathOperator.MULTIPLY:
255
+ exp = np.multiply(ima1.data, ima2.data, dtype=float) * a + b
256
+ elif o == MathOperator.SUBTRACT:
257
+ exp = np.subtract(ima1.data, ima2.data, dtype=float) * a + b
258
+ elif o == MathOperator.DIVIDE:
259
+ exp = np.divide(ima1.data, ima2.data, dtype=float) * a + b
260
+ if p.restore_dtype:
261
+ if np.issubdtype(dtype1, np.integer):
262
+ iinfo1 = np.iinfo(dtype1)
263
+ exp = np.clip(exp, iinfo1.min, iinfo1.max)
264
+ exp = exp.astype(dtype1)
265
+ check_array_result(
266
+ f"Arithmetic [{p.get_operation()}]", ima3.data, exp
267
+ )
268
+
269
+
270
+ @pytest.mark.validation
271
+ def test_image_inverse() -> None:
272
+ """Image inverse test."""
273
+ execenv.print("*** Testing image inverse:")
274
+ for ima1 in iterate_noisy_images(size=128):
275
+ execenv.print(f" 1/({ima1.data.dtype}): ", end="")
276
+ with warnings.catch_warnings():
277
+ warnings.simplefilter("ignore", category=RuntimeWarning)
278
+ exp = np.reciprocal(ima1.data, dtype=float)
279
+ exp[np.isinf(exp)] = np.nan
280
+ ima2 = sigima.proc.image.inverse(ima1)
281
+ check_array_result("Image inverse", ima2.data, exp)
282
+
283
+
284
+ @pytest.mark.validation
285
+ def test_image_absolute() -> None:
286
+ """Image absolute value test."""
287
+ execenv.print("*** Testing image absolute value:")
288
+ for ima1 in iterate_noisy_images(size=128):
289
+ execenv.print(f" abs({ima1.data.dtype}): ", end="")
290
+ exp = np.abs(ima1.data)
291
+ ima2 = sigima.proc.image.absolute(ima1)
292
+ check_array_result("Absolute value", ima2.data, exp)
293
+
294
+
295
+ @pytest.mark.validation
296
+ def test_image_real() -> None:
297
+ """Image real part test."""
298
+ execenv.print("*** Testing image real part:")
299
+ for ima1 in iterate_noisy_images(size=128):
300
+ execenv.print(f" re({ima1.data.dtype}): ", end="")
301
+ exp = np.real(ima1.data)
302
+ ima2 = sigima.proc.image.real(ima1)
303
+ check_array_result("Real part", ima2.data, exp)
304
+
305
+
306
+ @pytest.mark.validation
307
+ def test_image_imag() -> None:
308
+ """Image imaginary part test."""
309
+ execenv.print("*** Testing image imaginary part:")
310
+ for ima1 in iterate_noisy_images(size=128):
311
+ execenv.print(f" im({ima1.data.dtype}): ", end="")
312
+ exp = np.imag(ima1.data)
313
+ ima2 = sigima.proc.image.imag(ima1)
314
+ check_array_result("Imaginary part", ima2.data, exp)
315
+
316
+
317
+ @pytest.mark.validation
318
+ def test_image_complex_from_real_imag() -> None:
319
+ """Test :py:func:`sigima.proc.image.complex_from_real_imag`."""
320
+ real = np.ones((4, 4))
321
+ ima = np.arange(16).reshape(4, 4)
322
+ ima_real = ImageObj("real")
323
+ ima_real.data = real
324
+ ima_imag = ImageObj("imag")
325
+ ima_imag.data = ima
326
+ result = complex_from_real_imag(ima_real, ima_imag)
327
+ check_array_result(
328
+ "complex_from_real_imag",
329
+ result.data,
330
+ real + 1j * ima,
331
+ )
332
+
333
+
334
+ @pytest.mark.validation
335
+ def test_image_phase() -> None:
336
+ """Image phase test."""
337
+ execenv.print("*** Testing image phase:")
338
+ for base_image in iterate_noisy_images():
339
+ # Create a complex image for testing
340
+ assert base_image.data is not None, "Input image data is None."
341
+ complex_data = base_image.data.astype(np.complex128)
342
+ complex_data += 1j * (0.5 * base_image.data + 1.0)
343
+ complex_image = base_image.copy()
344
+ complex_image.data = complex_data
345
+
346
+ # Test phase extraction in radians without unwrapping
347
+ param_rad = sigima.params.PhaseParam.create(unit=AngleUnit.RADIAN, unwrap=False)
348
+ result_rad = sigima.proc.image.phase(complex_image, param_rad)
349
+ assert result_rad.data is not None, "Phase in radians data is None."
350
+ expected_rad = np.angle(complex_image.data, deg=False)
351
+ check_array_result("Phase in radians", result_rad.data, expected_rad)
352
+
353
+ # Test phase extraction in degrees without unwrapping
354
+ param_deg = sigima.params.PhaseParam.create(unit=AngleUnit.DEGREE, unwrap=False)
355
+ result_deg = sigima.proc.image.phase(complex_image, param_deg)
356
+ assert result_deg.data is not None, "Phase in degrees data is None."
357
+ expected_deg = np.angle(complex_image.data, deg=True)
358
+ check_array_result("Phase in degrees", result_deg.data, expected_deg)
359
+
360
+ # Test phase extraction in radians with unwrapping
361
+ param_rad_unwrap = sigima.params.PhaseParam.create(
362
+ unit=AngleUnit.RADIAN, unwrap=True
363
+ )
364
+ result_rad_unwrap = sigima.proc.image.phase(complex_image, param_rad_unwrap)
365
+ expected_rad_unwrap = np.unwrap(np.angle(complex_image.data, deg=False))
366
+ assert result_rad_unwrap.data is not None, (
367
+ "Phase in radians with unwrapping data is None."
368
+ )
369
+ check_array_result(
370
+ "Phase in radians with unwrapping",
371
+ result_rad_unwrap.data,
372
+ expected_rad_unwrap,
373
+ )
374
+
375
+ # Test phase extraction in degrees with unwrapping
376
+ param_deg_unwrap = sigima.params.PhaseParam.create(
377
+ unit=AngleUnit.DEGREE, unwrap=True
378
+ )
379
+ result_deg_unwrap = sigima.proc.image.phase(complex_image, param_deg_unwrap)
380
+ expected_deg_unwrap = np.unwrap(
381
+ np.angle(complex_image.data, deg=True), period=360.0
382
+ )
383
+ assert result_deg_unwrap.data is not None, (
384
+ "Phase in degrees with unwrapping data is None."
385
+ )
386
+ check_array_result(
387
+ "Phase in degrees with unwrapping",
388
+ result_deg_unwrap.data,
389
+ expected_deg_unwrap,
390
+ )
391
+
392
+
393
+ MAGNITUDE_PHASE_TEST_CASES = [
394
+ (np.linspace(0, np.pi, 16).reshape(4, 4), AngleUnit.RADIAN),
395
+ (np.linspace(0, 360, 16).reshape(4, 4), AngleUnit.DEGREE),
396
+ ]
397
+
398
+
399
+ @pytest.mark.parametrize("phase, unit", MAGNITUDE_PHASE_TEST_CASES)
400
+ @pytest.mark.validation
401
+ def test_image_complex_from_magnitude_phase(phase: np.ndarray, unit: AngleUnit) -> None:
402
+ """Test :py:func:`sigima.proc.image.complex_from_magnitude_phase`.
403
+
404
+ Args:
405
+ phase (np.ndarray): Angles in radians or degrees.
406
+ unit (AngleUnit): Unit of the angles, either radian or degree.
407
+ """
408
+ magnitude = np.full((4, 4), 2.0)
409
+ # Create image instances for magnitude and phase
410
+ ima_mag = ImageObj("magnitude")
411
+ ima_mag.data = magnitude
412
+ ima_phase = ImageObj("phase")
413
+ ima_phase.data = phase
414
+ # Create complex signal from magnitude and phase
415
+ p = AngleUnitParam.create(unit=unit)
416
+ result = complex_from_magnitude_phase(ima_mag, ima_phase, p)
417
+ unit_str = "rad" if p.unit == AngleUnit.RADIAN else "°"
418
+ check_array_result(
419
+ "complex_from_magnitude_phase",
420
+ result.data,
421
+ polar_to_complex(magnitude, phase, unit=unit_str),
422
+ )
423
+
424
+
425
+ def __test_all_complex_from_magnitude_phase() -> None:
426
+ """Test all combinations of magnitude and phase."""
427
+ for phase, unit in MAGNITUDE_PHASE_TEST_CASES:
428
+ test_image_complex_from_magnitude_phase(phase, unit)
429
+
430
+
431
+ def __get_numpy_info(dtype: np.dtype) -> np.generic:
432
+ """Get numpy info for a given data type."""
433
+ if np.issubdtype(dtype, np.integer):
434
+ return np.iinfo(dtype)
435
+ return np.finfo(dtype)
436
+
437
+
438
+ @pytest.mark.validation
439
+ def test_image_astype() -> None:
440
+ """Image type conversion test."""
441
+ execenv.print("*** Testing image type conversion:")
442
+ for ima1 in iterate_noisy_images(size=128):
443
+ for dtype_str in sigima.objects.ImageObj.get_valid_dtypenames():
444
+ dtype1_str = str(ima1.data.dtype)
445
+ execenv.print(f" {dtype1_str} -> {dtype_str}: ", end="")
446
+ dtype_exp = np.dtype(dtype_str)
447
+ info_exp = __get_numpy_info(dtype_exp)
448
+ info_ima1 = __get_numpy_info(ima1.data.dtype)
449
+ if info_exp.min < info_ima1.min or info_exp.max > info_ima1.max:
450
+ continue
451
+ exp = np.clip(ima1.data, info_exp.min, info_exp.max).astype(dtype_exp)
452
+ p = sigima.params.DataTypeIParam.create(dtype_str=dtype_str)
453
+ ima2 = sigima.proc.image.astype(ima1, p)
454
+ check_array_result(
455
+ f"Image astype({dtype1_str}->{dtype_str})", ima2.data, exp
456
+ )
457
+
458
+
459
+ @pytest.mark.validation
460
+ def test_image_exp() -> None:
461
+ """Image exponential test."""
462
+ execenv.print("*** Testing image exponential:")
463
+ with np.errstate(over="ignore"):
464
+ for ima1 in iterate_noisy_images(size=128):
465
+ execenv.print(f" exp({ima1.data.dtype}): ", end="")
466
+ exp = np.exp(ima1.data)
467
+ ima2 = sigima.proc.image.exp(ima1)
468
+ check_array_result("Image exp", ima2.data, exp)
469
+
470
+
471
+ @pytest.mark.validation
472
+ def test_image_log10() -> None:
473
+ """Image base-10 logarithm test."""
474
+ execenv.print("*** Testing image base-10 logarithm:")
475
+ with np.errstate(over="ignore"):
476
+ for ima1 in iterate_noisy_images(size=128):
477
+ execenv.print(f" log10({ima1.data.dtype}): ", end="")
478
+ exp = np.log10(np.exp(ima1.data))
479
+ ima2 = sigima.proc.image.log10(sigima.proc.image.exp(ima1))
480
+ check_array_result("Image log10", ima2.data, exp)
481
+
482
+
483
+ @pytest.mark.validation
484
+ def test_image_log10_z_plus_n() -> None:
485
+ """Image log(1+n) test."""
486
+ execenv.print("*** Testing image log(1+n):")
487
+ with np.errstate(over="ignore"):
488
+ for ima1 in iterate_noisy_images(size=128):
489
+ execenv.print(f" log1p({ima1.data.dtype}): ", end="")
490
+ p = sigima.params.Log10ZPlusNParam.create(n=2.0)
491
+ exp = np.log10(ima1.data + p.n)
492
+ ima2 = sigima.proc.image.log10_z_plus_n(ima1, p)
493
+ check_array_result("Image log1p", ima2.data, exp)
494
+
495
+
496
+ if __name__ == "__main__":
497
+ guiutils.enable_gui()
498
+ test_image_addition()
499
+ test_image_average()
500
+ test_image_product()
501
+ test_image_division()
502
+ test_image_difference()
503
+ test_image_quadratic_difference()
504
+ test_image_addition_constant()
505
+ test_image_product_constant()
506
+ test_image_difference_constant()
507
+ test_image_division_constant()
508
+ test_image_arithmetic()
509
+ test_image_inverse()
510
+ test_image_absolute()
511
+ test_image_real()
512
+ test_image_imag()
513
+ test_image_phase()
514
+ __test_all_complex_from_magnitude_phase()
515
+ test_image_astype()
516
+ test_image_exp()
517
+ test_image_log10()
518
+ test_image_log10_z_plus_n()
@@ -0,0 +1,41 @@
1
+ # Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file.
2
+
3
+ """
4
+ Image peak detection test: testing algorithm limits
5
+ """
6
+
7
+ # pylint: disable=invalid-name # Allows short reference names like x, y, ...
8
+
9
+ import pytest
10
+
11
+ from sigima.tests.data import get_peak2d_data
12
+ from sigima.tests.env import execenv
13
+ from sigima.tests.image.peak2d_unit_test import (
14
+ exec_image_peak_detection_func,
15
+ )
16
+ from sigima.tools.image import get_2d_peaks_coords
17
+
18
+
19
+ @pytest.mark.skip(reason="Limit testing, not required for automated testing")
20
+ def test_peak2d_limit():
21
+ """2D peak detection test"""
22
+ # pylint: disable=import-outside-toplevel
23
+ from guidata.qthelpers import qt_app_context
24
+
25
+ with qt_app_context():
26
+ execenv.print("Testing peak detection algorithm with random generated data:")
27
+ for idx in range(100):
28
+ execenv.print(f" Iteration #{idx:02d}: ", end="")
29
+ generated_data, _coords = get_peak2d_data(multi=True)
30
+ coords = get_2d_peaks_coords(generated_data)
31
+ if coords.shape[0] != 4:
32
+ execenv.print(f"KO - {coords.shape[0]}/4 peaks were detected")
33
+ exec_image_peak_detection_func(generated_data)
34
+ else:
35
+ execenv.print("OK")
36
+ # Showing results for last generated sample
37
+ exec_image_peak_detection_func(generated_data)
38
+
39
+
40
+ if __name__ == "__main__":
41
+ test_peak2d_limit()
@@ -0,0 +1,133 @@
1
+ # Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file.
2
+
3
+ """
4
+ Image peak detection test
5
+ """
6
+
7
+ # pylint: disable=invalid-name # Allows short reference names like x, y, ...
8
+ # pylint: disable=duplicate-code
9
+
10
+ from __future__ import annotations
11
+
12
+ import time
13
+
14
+ import numpy as np
15
+ import pytest
16
+
17
+ import sigima.objects
18
+ import sigima.params
19
+ import sigima.proc.image
20
+ from sigima.tests import guiutils
21
+ from sigima.tests.data import get_peak2d_data
22
+ from sigima.tests.env import execenv
23
+ from sigima.tests.helpers import check_array_result
24
+ from sigima.tools.image import get_2d_peaks_coords
25
+
26
+
27
+ def exec_image_peak_detection_func(data: np.ndarray) -> np.ndarray:
28
+ """Execute image peak detection function
29
+
30
+ Args:
31
+ data: 2D image data
32
+
33
+ Returns:
34
+ 2D array of peak coordinates
35
+ """
36
+ t0 = time.time()
37
+ coords = get_2d_peaks_coords(data)
38
+ dt = time.time() - t0
39
+ execenv.print(f"Calculation time: {int(dt * 1e3):d} ms")
40
+ execenv.print(f" => {coords.tolist()}")
41
+ return coords
42
+
43
+
44
+ def view_image_peak_detection(data: np.ndarray, coords: np.ndarray) -> None:
45
+ """View image peak detection results
46
+
47
+ Args:
48
+ data: 2D image data
49
+ coords: Coordinates of detected peaks (shape: (n, 2))
50
+ """
51
+ # pylint: disable=import-outside-toplevel
52
+ from plotpy.builder import make
53
+
54
+ from sigima.tests.vistools import view_image_items
55
+
56
+ execenv.print("Peak detection results:")
57
+ items = [make.image(data, interpolation="linear", colormap="hsv")]
58
+ for x, y in coords:
59
+ items.append(make.marker((x, y)))
60
+ view_image_items(
61
+ items, name=view_image_peak_detection.__name__, title="Peak Detection"
62
+ )
63
+
64
+
65
+ def test_peak2d_unit():
66
+ """2D peak detection unit test"""
67
+ data, coords_expected = get_peak2d_data(seed=1, multi=False)
68
+ coords = exec_image_peak_detection_func(data)
69
+ assert coords.shape == coords_expected.shape, (
70
+ f"Expected {coords_expected.shape[0]} peaks, got {coords.shape[0]}"
71
+ )
72
+ # Absolute tolerance is set to 2 pixels, as coordinates are in pixel units
73
+ # and the algorithm may detect peaks at slightly different pixel locations
74
+ # Convert coordinates to float64 for dtype compatibility with expected results
75
+ coords_float = coords.astype(np.float64)
76
+ check_array_result(
77
+ "Peak coords (sigima.tools.image.)",
78
+ coords_float,
79
+ coords_expected,
80
+ atol=2,
81
+ sort=True,
82
+ )
83
+
84
+
85
+ @pytest.mark.validation
86
+ def test_image_peak_detection():
87
+ """2D peak detection unit test"""
88
+ data, coords_expected = get_peak2d_data(seed=1, multi=False)
89
+ for create_rois in (True, False):
90
+ obj = sigima.objects.create_image("peak2d_unit_test", data=data)
91
+ param = sigima.params.Peak2DDetectionParam.create(create_rois=create_rois)
92
+ geometry = sigima.proc.image.peak_detection(obj, param)
93
+ coords = geometry.coords
94
+ assert coords.shape == coords_expected.shape, (
95
+ f"Expected {coords_expected.shape[0]} peaks, got {coords.shape[0]}"
96
+ )
97
+ # Absolute tolerance is set to 2 pixels, as coordinates are in pixel units
98
+ # and the algorithm may detect peaks at slightly different pixel locations
99
+ check_array_result(
100
+ "Peak coords (comp.)", coords, coords_expected, atol=2, sort=True
101
+ )
102
+ if create_rois:
103
+ assert obj.roi is not None, "ROI should be created"
104
+ assert len(obj.roi) == coords.shape[0], (
105
+ f"Expected {coords.shape[0]} ROIs, got {len(obj.roi)}"
106
+ )
107
+ for i, roi in enumerate(obj.roi):
108
+ # Check that ROIs are rectangles
109
+ assert isinstance(roi, sigima.objects.RectangularROI), (
110
+ f"Expected RectangularROI, got {type(roi)}"
111
+ )
112
+ # Check that ROIs are correctly positioned
113
+ x0, y0, x1, y1 = roi.get_bounding_box(obj)
114
+ x, y = coords[i]
115
+ assert x0 <= x < x1, f"ROI {i} x0={x0}, x={x}, x1={x1} does not match"
116
+ assert y0 <= y < y1, f"ROI {i} y0={y0}, y={y}, y1={y1} does not match"
117
+ else:
118
+ assert obj.roi is None, "ROI should not be created"
119
+
120
+
121
+ @pytest.mark.gui
122
+ def test_peak2d_interactive():
123
+ """2D peak detection interactive test"""
124
+ data, _coords = get_peak2d_data(multi=False)
125
+ coords = exec_image_peak_detection_func(data)
126
+ with guiutils.lazy_qt_app_context(force=True):
127
+ view_image_peak_detection(data, coords)
128
+
129
+
130
+ if __name__ == "__main__":
131
+ test_peak2d_unit()
132
+ test_image_peak_detection()
133
+ test_peak2d_interactive()