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,172 @@
1
+ # Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file.
2
+
3
+ """
4
+ Contour finding test
5
+ """
6
+
7
+ # pylint: disable=invalid-name # Allows short reference names like x, y, ...
8
+ # pylint: disable=duplicate-code
9
+
10
+ import sys
11
+ import time
12
+
13
+ import numpy as np
14
+ import pytest
15
+
16
+ from sigima.enums import ContourShape
17
+ from sigima.tests import guiutils
18
+ from sigima.tests.data import get_peak2d_data
19
+ from sigima.tests.env import execenv
20
+ from sigima.tests.helpers import check_scalar_result
21
+ from sigima.tools import coordinates
22
+ from sigima.tools.image import get_2d_peaks_coords, get_contour_shapes
23
+
24
+
25
+ def create_contour_shape_items(data, shape):
26
+ """Create plotpy items for a specific contour shape.
27
+
28
+ Args:
29
+ data: Input data array
30
+ shape: ContourShape enum value
31
+
32
+ Returns:
33
+ List of plotpy items representing the detected contours
34
+ """
35
+ # pylint: disable=import-outside-toplevel
36
+ from plotpy.builder import make
37
+
38
+ items = []
39
+ coords = get_contour_shapes(data, shape=shape)
40
+ execenv.print(f"Coordinates ({shape}s): {coords}")
41
+ for shapeargs in coords:
42
+ if shape == ContourShape.CIRCLE:
43
+ xc, yc, r = shapeargs
44
+ x0, y0, x1, y1 = coordinates.circle_to_diameter(xc, yc, r)
45
+ item = make.circle(x0, y0, x1, y1)
46
+ elif shape == ContourShape.ELLIPSE:
47
+ xc, yc, a, b, theta = shapeargs
48
+ coords_ellipse = coordinates.ellipse_to_diameters(xc, yc, a, b, theta)
49
+ x0, y0, x1, y1, x2, y2, x3, y3 = coords_ellipse
50
+ item = make.ellipse(x0, y0, x1, y1, x2, y2, x3, y3)
51
+ else: # ContourShape.POLYGON
52
+ # `shapeargs` is a flattened array of x, y coordinates
53
+ x, y = shapeargs[::2], shapeargs[1::2]
54
+ item = make.polygon(x, y, closed=False)
55
+ items.append(item)
56
+ return items
57
+
58
+
59
+ @pytest.mark.gui
60
+ def test_contour_interactive():
61
+ """2D peak detection test"""
62
+ data, _coords = get_peak2d_data()
63
+ with guiutils.lazy_qt_app_context(force=True):
64
+ # pylint: disable=import-outside-toplevel
65
+ from plotpy.builder import make
66
+
67
+ from sigima.tests import vistools
68
+
69
+ items = [make.image(data, interpolation="linear", colormap="hsv")]
70
+ t0 = time.time()
71
+ peak_coords = get_2d_peaks_coords(data)
72
+ dt = time.time() - t0
73
+ for x, y in peak_coords:
74
+ items.append(make.marker((x, y)))
75
+ execenv.print(f"Calculation time: {int(dt * 1e3):d} ms\n", file=sys.stderr)
76
+ execenv.print(f"Peak coordinates: {peak_coords}")
77
+
78
+ # Add contour shapes for all shape types
79
+ for shape in ContourShape:
80
+ items.extend(create_contour_shape_items(data, shape))
81
+
82
+ vistools.view_image_items(items)
83
+
84
+
85
+ @pytest.mark.validation
86
+ def test_contour_shape() -> None:
87
+ """Test contour shape computation function"""
88
+ # Create test data with known shapes
89
+ data, _expected_coords = get_peak2d_data()
90
+
91
+ # Test each contour shape type
92
+ for shape in ContourShape:
93
+ execenv.print(f"Testing contour shape: {shape}")
94
+
95
+ # Get contour shapes from the function
96
+ detected_shapes = get_contour_shapes(data, shape=shape)
97
+ execenv.print(f"Detected {len(detected_shapes)} {shape}(s)")
98
+
99
+ # Basic validation checks
100
+ assert isinstance(detected_shapes, np.ndarray), (
101
+ f"get_contour_shapes should return numpy array for {shape}"
102
+ )
103
+
104
+ if len(detected_shapes) > 0:
105
+ # Check that we detected at least some shapes
106
+ execenv.print(f"Successfully detected contours for {shape}")
107
+
108
+ # Validate shape-specific properties
109
+ if shape == ContourShape.CIRCLE:
110
+ # For circles: [xc, yc, r]
111
+ assert detected_shapes.shape[1] == 3, (
112
+ "Circle contours should have 3 parameters (xc, yc, r)"
113
+ )
114
+ # Check that radius values are positive
115
+ radii = detected_shapes[:, 2]
116
+ assert np.all(radii > 0), "All circle radii should be positive"
117
+ check_scalar_result(
118
+ "Circle radius range",
119
+ np.mean(radii),
120
+ np.mean(radii), # Just check it's finite
121
+ rtol=1.0,
122
+ )
123
+
124
+ elif shape == ContourShape.ELLIPSE:
125
+ # For ellipses: [xc, yc, a, b, theta]
126
+ assert detected_shapes.shape[1] == 5, (
127
+ "Ellipse contours should have 5 parameters (xc, yc, a, b, theta)"
128
+ )
129
+ # Check that semi-axes are positive
130
+ a_values = detected_shapes[:, 2]
131
+ b_values = detected_shapes[:, 3]
132
+ assert np.all(a_values > 0), (
133
+ "All ellipse semi-axes 'a' should be positive"
134
+ )
135
+ assert np.all(b_values > 0), (
136
+ "All ellipse semi-axes 'b' should be positive"
137
+ )
138
+ check_scalar_result(
139
+ "Ellipse semi-axis 'a' range",
140
+ np.mean(a_values),
141
+ np.mean(a_values), # Just check it's finite
142
+ rtol=1.0,
143
+ )
144
+
145
+ elif shape == ContourShape.POLYGON:
146
+ # For polygons: flattened x,y coordinates
147
+ # Shape should be (n_contours, max_points) where max_points is even
148
+ assert detected_shapes.shape[1] % 2 == 0, (
149
+ "Polygon contours should have even number of coordinates "
150
+ "(x,y pairs)"
151
+ )
152
+ # Check that we have valid coordinates (not all NaN)
153
+ valid_coords = ~np.isnan(detected_shapes)
154
+ assert np.any(valid_coords), (
155
+ "Polygon should have some valid coordinates"
156
+ )
157
+
158
+ # Check that the function handles different threshold levels
159
+ for level in [0.3, 0.5, 0.7]:
160
+ shapes_at_level = get_contour_shapes(data, shape=shape, level=level)
161
+ assert isinstance(shapes_at_level, np.ndarray), (
162
+ f"get_contour_shapes should return numpy array for {shape} "
163
+ f"at level {level}"
164
+ )
165
+ execenv.print(f" At level {level}: detected {len(shapes_at_level)} shapes")
166
+
167
+ execenv.print("All contour shape tests passed!")
168
+
169
+
170
+ if __name__ == "__main__":
171
+ test_contour_interactive()
172
+ test_contour_shape()
@@ -0,0 +1,178 @@
1
+ # Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file.
2
+
3
+ """Unit tests for image convolution/deconvolution features."""
4
+
5
+ from __future__ import annotations
6
+
7
+ from contextlib import contextmanager
8
+
9
+ import numpy as np
10
+ import pytest
11
+ import scipy.signal as sps
12
+
13
+ from sigima.config import options as sigima_options
14
+ from sigima.objects import create_image, create_image_from_param
15
+ from sigima.objects.image import Gauss2DParam, ImageObj, Zero2DParam
16
+ from sigima.proc.image.mathops import convolution, deconvolution
17
+ from sigima.tests import guiutils
18
+ from sigima.tests.helpers import check_array_result
19
+ from sigima.tools.image import deconvolve
20
+
21
+
22
+ @contextmanager
23
+ def disable_kernel_normalization():
24
+ """Context manager to temporarily disable kernel normalization."""
25
+ # Save current values
26
+ original_auto_normalize = sigima_options.auto_normalize_kernel.get()
27
+
28
+ # Disable for test
29
+ sigima_options.auto_normalize_kernel.set(False)
30
+
31
+ try:
32
+ yield
33
+ finally:
34
+ # Restore original values
35
+ sigima_options.auto_normalize_kernel.set(original_auto_normalize)
36
+
37
+
38
+ def _generate_rectangle_image(title: str = "Rectangle", size: int = 32) -> ImageObj:
39
+ """Generate a test square image with a rectangle in the center."""
40
+ data = np.zeros((size, size), dtype=np.float64)
41
+ data[size // 5 : 2 * size // 5, size // 7 : 5 * size // 7] = 1.0
42
+ img = create_image(title, data)
43
+ return img
44
+
45
+
46
+ def _generate_image(size: int = 32) -> ImageObj:
47
+ """Generate a test square image.
48
+
49
+ Args:
50
+ size: The dimension of the square image to generate.
51
+
52
+ Returns:
53
+ An image object.
54
+ """
55
+ # Gaussian image.
56
+ gauss_img = create_image_from_param(Gauss2DParam.create(height=size, width=size))
57
+ return gauss_img
58
+
59
+
60
+ def _generate_gaussian_kernel(size: int = 32, sigma: float = 1.0) -> ImageObj:
61
+ """Generate a Gaussian kernel image.
62
+
63
+ Args:
64
+ size: The dimension of the square kernel to generate.
65
+ sigma: The standard deviation of the Gaussian.
66
+
67
+ Returns:
68
+ An image object.
69
+ """
70
+ kernel = create_image_from_param(
71
+ Gauss2DParam.create(height=size, width=size, sigma=sigma)
72
+ )
73
+ return kernel
74
+
75
+
76
+ def _generate_identity_kernel(size: int = 7) -> ImageObj:
77
+ """Generate an identity kernel image.
78
+
79
+ Args:
80
+ size: The dimension of the square kernel to generate (must be odd).
81
+
82
+ Returns:
83
+ An image object.
84
+ """
85
+ assert size % 2 == 1, "Identity kernel size must be odd."
86
+ kernel = create_image_from_param(Zero2DParam.create(height=size, width=size))
87
+ assert kernel.data is not None
88
+ kernel.data[size // 2, size // 2] = 1.0
89
+ return kernel
90
+
91
+
92
+ def __convolve_image(kernel: ImageObj, size: int = 32) -> tuple[ImageObj, ImageObj]:
93
+ """Generate a test image and its convolution with a Gaussian kernel.
94
+
95
+ Returns:
96
+ A tuple (source image, kernel, convolved image).
97
+ """
98
+ original = _generate_rectangle_image(size=size)
99
+ assert original.data is not None
100
+ convolved = convolution(original, kernel)
101
+ assert convolved.data is not None
102
+ return original, convolved
103
+
104
+
105
+ @pytest.mark.validation
106
+ def test_image_convolution(size: int = 32) -> None:
107
+ """Validation test for the image convolution processing.
108
+
109
+ Note: This test disables kernel normalization to compare against raw scipy results.
110
+ """
111
+ with disable_kernel_normalization():
112
+ # Test with a Gaussian kernel.
113
+ kernel = _generate_gaussian_kernel(size=size, sigma=1.0)
114
+ original, convolved = __convolve_image(kernel, size=size)
115
+ exp = sps.convolve(original.data, kernel.data, mode="same", method="auto")
116
+ check_array_result("Convolution", convolved.data, exp)
117
+ guiutils.view_images_side_by_side_if_gui(
118
+ [original, kernel, convolved],
119
+ ["Original", "Kernel", "Convolved"],
120
+ title="Image Convolution Test: Gaussian Kernel",
121
+ )
122
+ # Test with an identity kernel.
123
+ kernel = _generate_identity_kernel(size=7)
124
+ original, convolved = __convolve_image(kernel, size=size)
125
+ # check_array_result("Convolution identity", convolved.data, original.data)
126
+ guiutils.view_images_side_by_side_if_gui(
127
+ [original, kernel, convolved],
128
+ ["Original", "Kernel", "Convolved"],
129
+ title="Image Convolution Test: Identity Kernel",
130
+ )
131
+
132
+
133
+ @pytest.mark.validation
134
+ def test_image_deconvolution(size: int = 32) -> None:
135
+ """Validation test for image deconvolution.
136
+
137
+ Note: This test disables kernel normalization to compare against expected results.
138
+ """
139
+ with disable_kernel_normalization():
140
+ # Test with an identity kernel.
141
+ kernel = _generate_identity_kernel(size=31)
142
+ original, convolved = __convolve_image(kernel, size=size)
143
+ deconvolved = deconvolution(convolved, kernel)
144
+ guiutils.view_images_side_by_side_if_gui(
145
+ [original, kernel, convolved, deconvolved],
146
+ ["Original", "Kernel", "Convolved", "Deconvolved"],
147
+ title="Image Deconvolution Test: Identity Kernel",
148
+ )
149
+ check_array_result("Deconvolution identity", deconvolved.data, original.data)
150
+
151
+ # # Test with a Gaussian kernel.
152
+ kernel = _generate_gaussian_kernel(size=31, sigma=1.0)
153
+ original, convolved = __convolve_image(kernel, size=64)
154
+ deconvolved = deconvolution(convolved, kernel)
155
+ # check_array_result("Deconvolution Gaussian", deconvolved.data, original.data)
156
+ guiutils.view_images_side_by_side_if_gui(
157
+ [original, kernel, convolved, deconvolved],
158
+ ["Original", "Kernel", "Convolved", "Deconvolved"],
159
+ title="Image Deconvolution Test: Gaussian Kernel",
160
+ )
161
+
162
+
163
+ def test_tools_image_deconvolve_null_kernel() -> None:
164
+ """Test deconvolution with a null kernel."""
165
+ size = 32
166
+ src = _generate_image(size)
167
+ assert src.data is not None
168
+ kernel = create_image_from_param(Zero2DParam.create(height=size, width=size))
169
+ assert kernel.data is not None
170
+ with pytest.raises(ValueError, match="Deconvolution kernel cannot be null."):
171
+ deconvolve(src.data, kernel.data)
172
+
173
+
174
+ if __name__ == "__main__":
175
+ guiutils.enable_gui()
176
+ test_image_convolution()
177
+ test_image_deconvolution()
178
+ test_tools_image_deconvolve_null_kernel()
@@ -0,0 +1,67 @@
1
+ # Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file.
2
+
3
+ """
4
+ Unit tests for image data types
5
+ -------------------------------
6
+
7
+ """
8
+
9
+ # pylint: disable=invalid-name # Allows short reference names like x, y, ...
10
+
11
+ from __future__ import annotations
12
+
13
+ import numpy as np
14
+
15
+ from sigima.objects import ImageDatatypes
16
+ from sigima.tests.env import execenv
17
+ from sigima.tools.datatypes import clip_astype
18
+
19
+
20
+ def get_integer_datatypes() -> list[ImageDatatypes]:
21
+ """Return all integer data types."""
22
+ return [dtype for dtype in ImageDatatypes if "int" in dtype.name.lower()]
23
+
24
+
25
+ def test_clip_astype():
26
+ """Test `clip_astype` algorithm"""
27
+ # Test that function do nothing for certain data types
28
+ for dtype1 in ImageDatatypes:
29
+ for dtype2 in ImageDatatypes:
30
+ if not np.issubdtype(dtype2.value, np.integer):
31
+ continue
32
+ if np.issubdtype(dtype1.value, np.integer):
33
+ info1 = np.iinfo(dtype1.value)
34
+ else:
35
+ info1 = np.finfo(dtype1.value)
36
+ info2 = np.iinfo(dtype2.value)
37
+ if info2.min < info1.min or info2.max > info1.max:
38
+ continue
39
+ data = np.array([0, 1, 2, 3, 4, 5], dtype=dtype1.value)
40
+ txt = f"No change: {dtype1.value} -> {dtype2.value}"
41
+ execenv.print(txt, end="... ")
42
+ assert np.array_equal(clip_astype(data, dtype2.value), data), txt
43
+ execenv.print("OK")
44
+
45
+ # Test that function handles overflow for integer data types
46
+ for dtype in get_integer_datatypes():
47
+ maxval = np.iinfo(dtype.value).max
48
+ data1 = np.array([maxval], dtype=dtype.value)
49
+ data2 = clip_astype(data1.astype(float) + 1, dtype.value)
50
+ txt = f"Overflow: {dtype.value}"
51
+ execenv.print(txt, end="... ")
52
+ assert data2[0] == maxval, txt
53
+ execenv.print("OK")
54
+
55
+ # Test that function handles underflow for integer data types
56
+ for dtype in get_integer_datatypes():
57
+ minval = np.iinfo(dtype.value).min
58
+ data1 = np.array([minval], dtype=dtype.value)
59
+ data2 = clip_astype(data1.astype(float) - 1, dtype.value)
60
+ txt = f"Underflow: {dtype.value}"
61
+ execenv.print(txt, end="... ")
62
+ assert data2[0] == minval, txt
63
+ execenv.print("OK")
64
+
65
+
66
+ if __name__ == "__main__":
67
+ test_clip_astype()
@@ -0,0 +1,155 @@
1
+ # Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file.
2
+
3
+ """
4
+ Unit tests for edge detection computation functions.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import pytest
10
+ from skimage import feature, filters, util
11
+
12
+ import sigima.objects
13
+ import sigima.params
14
+ import sigima.proc.image
15
+ from sigima.tests.data import get_test_image
16
+ from sigima.tests.helpers import check_array_result
17
+
18
+
19
+ @pytest.mark.validation
20
+ def test_canny() -> None:
21
+ """Validation test for the image Canny edge detection processing."""
22
+ # See [1] in sigima\tests\image\__init__.py for more details about the validation.
23
+ src = get_test_image("flower.npy")
24
+ p = sigima.params.CannyParam.create(
25
+ sigma=1.0, low_threshold=0.1, high_threshold=0.2
26
+ )
27
+ dst = sigima.proc.image.canny(src, p)
28
+ exp = util.img_as_ubyte(
29
+ feature.canny(
30
+ src.data,
31
+ sigma=p.sigma,
32
+ low_threshold=p.low_threshold,
33
+ high_threshold=p.high_threshold,
34
+ use_quantiles=p.use_quantiles,
35
+ mode=p.mode,
36
+ cval=p.cval,
37
+ )
38
+ )
39
+ check_array_result(
40
+ f"Canny[sigma={p.sigma},low_threshold={p.low_threshold},"
41
+ f"high_threshold={p.high_threshold}]",
42
+ dst.data,
43
+ exp,
44
+ )
45
+
46
+
47
+ def __generic_edge_validation(method: str) -> None:
48
+ """Generic test for edge detection methods."""
49
+ # See [1] in sigima\tests\image\__init__.py for more details about the validation.
50
+ src = get_test_image("flower.npy")
51
+ dst: sigima.objects.ImageObj = getattr(sigima.proc.image, method)(src)
52
+ exp = getattr(filters, method)(src.data)
53
+ check_array_result(f"{method.capitalize()}", dst.data, exp)
54
+
55
+
56
+ @pytest.mark.validation
57
+ def test_roberts() -> None:
58
+ """Validation test for the image Roberts edge detection processing."""
59
+ __generic_edge_validation("roberts")
60
+
61
+
62
+ @pytest.mark.validation
63
+ def test_prewitt() -> None:
64
+ """Validation test for the image Prewitt edge detection processing."""
65
+ __generic_edge_validation("prewitt")
66
+
67
+
68
+ @pytest.mark.validation
69
+ def test_prewitt_h() -> None:
70
+ """Validation test for the image horizontal Prewitt edge detection processing."""
71
+ __generic_edge_validation("prewitt_h")
72
+
73
+
74
+ @pytest.mark.validation
75
+ def test_prewitt_v() -> None:
76
+ """Validation test for the image vertical Prewitt edge detection processing."""
77
+ __generic_edge_validation("prewitt_v")
78
+
79
+
80
+ @pytest.mark.validation
81
+ def test_sobel() -> None:
82
+ """Validation test for the image Sobel edge detection processing."""
83
+ __generic_edge_validation("sobel")
84
+
85
+
86
+ @pytest.mark.validation
87
+ def test_sobel_h() -> None:
88
+ """Validation test for the image horizontal Sobel edge detection processing."""
89
+ __generic_edge_validation("sobel_h")
90
+
91
+
92
+ @pytest.mark.validation
93
+ def test_sobel_v() -> None:
94
+ """Validation test for the image vertical Sobel edge detection processing."""
95
+ __generic_edge_validation("sobel_v")
96
+
97
+
98
+ @pytest.mark.validation
99
+ def test_scharr() -> None:
100
+ """Validation test for the image Scharr edge detection processing."""
101
+ __generic_edge_validation("scharr")
102
+
103
+
104
+ @pytest.mark.validation
105
+ def test_scharr_h() -> None:
106
+ """Validation test for the image horizontal Scharr edge detection processing."""
107
+ __generic_edge_validation("scharr_h")
108
+
109
+
110
+ @pytest.mark.validation
111
+ def test_scharr_v() -> None:
112
+ """Validation test for the image vertical Scharr edge detection processing."""
113
+ __generic_edge_validation("scharr_v")
114
+
115
+
116
+ @pytest.mark.validation
117
+ def test_farid() -> None:
118
+ """Validation test for the image Farid edge detection processing."""
119
+ __generic_edge_validation("farid")
120
+
121
+
122
+ @pytest.mark.validation
123
+ def test_farid_h() -> None:
124
+ """Validation test for the image horizontal Farid edge detection processing."""
125
+ __generic_edge_validation("farid_h")
126
+
127
+
128
+ @pytest.mark.validation
129
+ def test_farid_v() -> None:
130
+ """Validation test for the image vertical Farid edge detection processing."""
131
+ __generic_edge_validation("farid_v")
132
+
133
+
134
+ @pytest.mark.validation
135
+ def test_laplace() -> None:
136
+ """Validation test for the image Laplace edge detection processing."""
137
+ __generic_edge_validation("laplace")
138
+
139
+
140
+ if __name__ == "__main__":
141
+ test_canny()
142
+ test_roberts()
143
+ test_prewitt()
144
+ test_prewitt_h()
145
+ test_prewitt_v()
146
+ test_sobel()
147
+ test_sobel_h()
148
+ test_sobel_v()
149
+ test_scharr()
150
+ test_scharr_h()
151
+ test_scharr_v()
152
+ test_farid()
153
+ test_farid_h()
154
+ test_farid_v()
155
+ test_laplace()
@@ -0,0 +1,88 @@
1
+ # Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file.
2
+
3
+ """
4
+ Enclosing circle test
5
+
6
+ Testing enclsoing circle function on various test images.
7
+ """
8
+
9
+ # pylint: disable=invalid-name # Allows short reference names like x, y, ...
10
+ # pylint: disable=duplicate-code
11
+
12
+ import pytest
13
+
14
+ import sigima.tools.image
15
+ from sigima.config import _
16
+ from sigima.proc.image import enclosing_circle
17
+ from sigima.tests import guiutils
18
+ from sigima.tests.data import RingParam, create_ring_image, get_laser_spot_data
19
+ from sigima.tests.env import execenv
20
+ from sigima.tests.helpers import check_scalar_result
21
+
22
+
23
+ def __enclosingcircle_test(data):
24
+ """Enclosing circle test function"""
25
+ # pylint: disable=import-outside-toplevel
26
+ from plotpy.builder import make
27
+
28
+ from sigima.tests import vistools
29
+
30
+ items = []
31
+ items += [make.image(data, interpolation="nearest", eliminate_outliers=1.0)]
32
+
33
+ # Computing centroid coordinates
34
+ row, col = sigima.tools.image.get_centroid_auto(data)
35
+ label = _("Centroid") + " (%d, %d)"
36
+ execenv.print(label % (row, col))
37
+ cursor = make.xcursor(col, row, label=label)
38
+ cursor.set_resizable(False)
39
+ cursor.set_movable(False)
40
+ items.append(cursor)
41
+
42
+ x, y, radius = sigima.tools.image.get_enclosing_circle(data)
43
+ circle = make.circle(x - radius, y - radius, x + radius, y + radius)
44
+ circle.set_readonly(True)
45
+ circle.set_resizable(False)
46
+ circle.set_movable(False)
47
+ items.append(circle)
48
+ execenv.print(x, y, radius)
49
+ execenv.print("")
50
+
51
+ vistools.view_image_items(items)
52
+
53
+
54
+ @pytest.mark.gui
55
+ def test_enclosing_circle_interactive():
56
+ """Interactive test for enclosing circle computation."""
57
+ with guiutils.lazy_qt_app_context(force=True):
58
+ for data in get_laser_spot_data():
59
+ __enclosingcircle_test(data)
60
+
61
+
62
+ @pytest.mark.validation
63
+ def test_image_enclosing_circle():
64
+ """Test enclosing circle on a ring image."""
65
+ p = RingParam.create(
66
+ image_size=200,
67
+ xc=100,
68
+ yc=100,
69
+ radius=30,
70
+ thickness=5,
71
+ )
72
+ # Create a ring image, so that the outer circle radius is radius + thickness:
73
+ obj = create_ring_image(p)
74
+ execenv.print("Testing enclosing circle on a ring image...")
75
+ ex, ey, er = sigima.tools.image.get_enclosing_circle(obj.data)
76
+ geometry = enclosing_circle(obj)
77
+ x, y, r = geometry.coords[0]
78
+ execenv.print(geometry)
79
+ assert ex == x and ey == y and er == r, (
80
+ f"Enclosing circle test failed: expected ({ex}, {ey}, {er}), "
81
+ f"got ({x}, {y}, {r})"
82
+ )
83
+ check_scalar_result("Enclosing circle", er, p.radius + p.thickness, rtol=0.002)
84
+
85
+
86
+ if __name__ == "__main__":
87
+ test_enclosing_circle_interactive()
88
+ test_image_enclosing_circle()