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,60 @@
1
+ # Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file.
2
+
3
+ """
4
+ Unit tests for :py:mod:`sigima.tools.coordinates`.
5
+
6
+ This module verifies the correctness of coordinate conversion functions.
7
+ """
8
+
9
+ import numpy as np
10
+ import pytest
11
+
12
+ from sigima.tests.helpers import check_array_result
13
+ from sigima.tools.coordinates import polar_to_complex
14
+
15
+ polar_to_complex_parameters = [
16
+ (
17
+ np.array([1.0, 2.0, 3.0]),
18
+ np.array([0.0, 90.0, 180.0]),
19
+ "°",
20
+ np.array([1 + 0j, 0 + 2j, -3 + 0j]),
21
+ ),
22
+ (
23
+ np.array([1.0, 2.0, 3.0]),
24
+ np.array([0.0, np.pi / 2, np.pi]),
25
+ "rad",
26
+ np.array([1 + 0j, 0 + 2j, -3 + 0j]),
27
+ ),
28
+ ]
29
+
30
+
31
+ @pytest.mark.parametrize("r, theta, unit, expected", polar_to_complex_parameters)
32
+ def test_polar_to_complex(r, theta, unit, expected):
33
+ """Test :py:func:`sigima.tools.polar_to_complex` with valid input.
34
+
35
+ Args:
36
+ r: The radial coordinates.
37
+ theta: The angular coordinates.
38
+ unit: The unit of the angular coordinates ("°" or "rad").
39
+ expected: The expected complex coordinates.
40
+ """
41
+ z = polar_to_complex(r, theta, unit=unit)
42
+ check_array_result(f"polar_to_complex_{unit}", z, expected)
43
+
44
+
45
+ def test_polar_to_complex_invalid_unit():
46
+ """Test :py:func:`sigima.tools.polar_to_complex` with invalid unit.
47
+
48
+ Ensure :py:func:`sigima.tools.polar_to_complex` raises ValueError for unsupported
49
+ unit.
50
+ """
51
+ r = np.array([1.0])
52
+ theta = np.array([0.0])
53
+ with pytest.raises(ValueError):
54
+ polar_to_complex(r, theta, unit="foo")
55
+
56
+
57
+ if __name__ == "__main__":
58
+ for parameters in polar_to_complex_parameters:
59
+ test_polar_to_complex(*parameters)
60
+ test_polar_to_complex_invalid_unit()
@@ -0,0 +1,178 @@
1
+ # Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file.
2
+
3
+ """
4
+ Unit tests for transformations module
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import numpy as np
10
+
11
+ from sigima.objects.scalar import GeometryResult, KindShape
12
+ from sigima.objects.shape import PointCoordinates
13
+ from sigima.proc.image.transformations import GeometryTransformer, transformer
14
+
15
+
16
+ def test_geometry_transformer_singleton() -> None:
17
+ """
18
+ Test that GeometryTransformer follows singleton pattern.
19
+ """
20
+ t1 = GeometryTransformer()
21
+ t2 = GeometryTransformer()
22
+ assert t1 is t2
23
+ assert t1 is transformer
24
+
25
+
26
+ def test_transform_geometry_result_point() -> None:
27
+ """
28
+ Test transformation of GeometryResult with POINT coordinates.
29
+ """
30
+ # Create a GeometryResult with point coordinates
31
+ coords = np.array([[1.0, 2.0], [3.0, 4.0]])
32
+ geometry = GeometryResult(
33
+ title="Test Points",
34
+ kind=KindShape.POINT,
35
+ coords=coords,
36
+ roi_indices=None,
37
+ attrs={},
38
+ )
39
+
40
+ # Test rotation
41
+ rotated = transformer.rotate(geometry, np.pi / 2, center=(0, 0))
42
+ expected_coords = np.array([[-2.0, 1.0], [-4.0, 3.0]])
43
+ assert np.allclose(rotated.coords, expected_coords)
44
+ assert rotated.title == geometry.title
45
+ assert rotated.kind == geometry.kind
46
+
47
+ # Test translation
48
+ translated = transformer.translate(geometry, 10.0, 20.0)
49
+ expected_coords = np.array([[11.0, 22.0], [13.0, 24.0]])
50
+ assert np.allclose(translated.coords, expected_coords)
51
+
52
+ # Original should be unchanged
53
+ assert np.allclose(geometry.coords, coords)
54
+
55
+
56
+ def test_transform_geometry_result_rectangle() -> None:
57
+ """
58
+ Test transformation of GeometryResult with RECTANGLE coordinates.
59
+ """
60
+ # Create a GeometryResult with rectangle coordinates (x0, y0, dx, dy)
61
+ coords = np.array([[0.0, 0.0, 3.0, 1.0], [10.0, 10.0, 2.0, 4.0]])
62
+ geometry = GeometryResult(
63
+ title="Test Rectangles",
64
+ kind=KindShape.RECTANGLE,
65
+ coords=coords,
66
+ roi_indices=None,
67
+ attrs={},
68
+ )
69
+
70
+ # Test horizontal flip around x=1
71
+ flipped = transformer.fliph(geometry, cx=1.0)
72
+ expected_coords = np.array([[-1.0, 0.0, 3.0, 1.0], [-10.0, 10.0, 2.0, 4.0]])
73
+ assert np.allclose(flipped.coords, expected_coords)
74
+
75
+ # Test transpose
76
+ transposed = transformer.transpose(geometry)
77
+ expected_coords = np.array([[0.0, 0.0, 1.0, 3.0], [10.0, 10.0, 4.0, 2.0]])
78
+ assert np.allclose(transposed.coords, expected_coords)
79
+
80
+
81
+ def test_transform_geometry_result_circle() -> None:
82
+ """
83
+ Test transformation of GeometryResult with CIRCLE coordinates.
84
+ """
85
+ # Create a GeometryResult with circle coordinates
86
+ coords = np.array([[1.0, 2.0, 5.0], [10.0, 20.0, 10.0]])
87
+ geometry = GeometryResult(
88
+ title="Test Circles",
89
+ kind=KindShape.CIRCLE,
90
+ coords=coords,
91
+ roi_indices=None,
92
+ attrs={},
93
+ )
94
+
95
+ # Test scaling (only center should be scaled, radius unchanged)
96
+ scaled = transformer.scale(geometry, 2.0, 3.0, center=(0, 0))
97
+ expected_coords = np.array([[2.0, 6.0, 5.0], [20.0, 60.0, 10.0]])
98
+ assert np.allclose(scaled.coords, expected_coords)
99
+
100
+
101
+ def test_geometry_transformer_generic_methods() -> None:
102
+ """
103
+ Test generic transform_geometry method.
104
+ """
105
+ coords = np.array([[1.0, 2.0]])
106
+ geometry = GeometryResult(
107
+ title="Test Point",
108
+ kind=KindShape.POINT,
109
+ coords=coords,
110
+ roi_indices=None,
111
+ attrs={},
112
+ )
113
+
114
+ # Test generic method
115
+ rotated = transformer.transform_geometry(
116
+ geometry, "rotate", angle=np.pi / 2, center=(0, 0)
117
+ )
118
+ expected_coords = np.array([[-2.0, 1.0]])
119
+ assert np.allclose(rotated.coords, expected_coords)
120
+
121
+
122
+ def test_unsupported_geometry_kind() -> None:
123
+ """
124
+ Test error handling for unsupported geometry kinds.
125
+ """
126
+ # Create invalid geometry kind (this would need to be added to KindShape enum)
127
+ try:
128
+ # This should raise an error if we had an unsupported kind
129
+ # For now, all defined kinds are supported
130
+ pass
131
+ except ValueError:
132
+ pass
133
+
134
+
135
+ def test_unsupported_operation() -> None:
136
+ """
137
+ Test error handling for unsupported operations.
138
+ """
139
+ coords = np.array([[1.0, 2.0]])
140
+ geometry = GeometryResult(
141
+ title="Test Point",
142
+ kind=KindShape.POINT,
143
+ coords=coords,
144
+ roi_indices=None,
145
+ attrs={},
146
+ )
147
+
148
+ try:
149
+ transformer.transform_geometry(geometry, "invalid_operation")
150
+ assert False, "Should have raised ValueError"
151
+ except ValueError as e:
152
+ assert "Unknown operation" in str(e)
153
+
154
+
155
+ def test_direct_coordinate_transformation() -> None:
156
+ """
157
+ Test that transformations use the shape coordinate system correctly.
158
+ """
159
+ # Test that our transformer produces the same results as direct shape
160
+ # coordinate usage
161
+ coords = np.array([[2.0, 3.0]])
162
+ geometry = GeometryResult(
163
+ title="Test Point",
164
+ kind=KindShape.POINT,
165
+ coords=coords,
166
+ roi_indices=None,
167
+ attrs={},
168
+ )
169
+
170
+ # Transform using transformer
171
+ rotated_geometry = transformer.rotate(geometry, np.pi / 2, center=(1, 1))
172
+
173
+ # Transform using shape coordinates directly
174
+ shape_coords = PointCoordinates([2.0, 3.0])
175
+ shape_coords.rotate(np.pi / 2, center=(1, 1))
176
+
177
+ # Results should be identical
178
+ assert np.allclose(rotated_geometry.coords[0], shape_coords.data)
@@ -0,0 +1,205 @@
1
+ # Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file.
2
+
3
+ """
4
+ Testing validation test introspection and CSV generation.
5
+ """
6
+
7
+ import os.path as osp
8
+
9
+ import sigima.tests as tests_pkg
10
+ from sigima.proc.decorator import find_computation_functions
11
+ from sigima.proc.validation import (
12
+ ValidationStatistics,
13
+ generate_valid_test_names_for_function,
14
+ get_validation_tests,
15
+ )
16
+ from sigima.tests.helpers import WorkdirRestoringTempDir
17
+
18
+
19
+ def __generate_all_valid_test_names() -> set[str]:
20
+ """Generate all valid test names for all computation functions.
21
+
22
+ Returns:
23
+ Set of all valid test names that could test computation functions
24
+ """
25
+ computation_functions = find_computation_functions()
26
+ valid_test_names = set()
27
+
28
+ for module_name, func_name, _ in computation_functions:
29
+ names = generate_valid_test_names_for_function(module_name, func_name)
30
+ valid_test_names.update(names)
31
+
32
+ return valid_test_names
33
+
34
+
35
+ def test_validation_statistics() -> None:
36
+ """Test validation statistics introspection and CSV generation."""
37
+ stats = ValidationStatistics()
38
+ stats.collect_validation_status(verbose=True)
39
+ stats.get_validation_info()
40
+ with WorkdirRestoringTempDir() as tmpdir:
41
+ stats.generate_csv_files(tmpdir)
42
+ stats.generate_statistics_csv(tmpdir)
43
+
44
+
45
+ def test_validation_missing_tests() -> None:
46
+ """Test that all computation functions have validation tests.
47
+
48
+ This test ensures that all computation functions (those decorated with
49
+ @computation_function) have corresponding validation tests
50
+ marked with @pytest.mark.validation.
51
+ """
52
+ # Get all functions marked with @pytest.mark.validation
53
+ validation_tests = get_validation_tests(tests_pkg)
54
+
55
+ # Get all computation functions that should have validation tests
56
+ computation_functions = find_computation_functions()
57
+ required_functions = [
58
+ (module_name, func_name) for module_name, func_name, _ in computation_functions
59
+ ]
60
+
61
+ # Check each required function to see if it has a corresponding validation test
62
+ missing_validation_tests = []
63
+
64
+ for module_name, func_name in required_functions:
65
+ valid_test_names = generate_valid_test_names_for_function(
66
+ module_name, func_name
67
+ )
68
+
69
+ # Check if any of the valid test names exist in validation tests
70
+ has_validation_test = any(
71
+ test_name in [vt[0] for vt in validation_tests]
72
+ for test_name in valid_test_names
73
+ )
74
+
75
+ if not has_validation_test:
76
+ missing_validation_tests.append(f"{module_name}.{func_name}")
77
+
78
+ # Report any missing validation tests
79
+ if missing_validation_tests:
80
+ error_messages = []
81
+ error_messages.append(
82
+ "The following computation functions are missing "
83
+ "validation tests marked with @pytest.mark.validation:"
84
+ )
85
+ for func_name in missing_validation_tests:
86
+ error_messages.append(f" - {func_name}")
87
+ error_messages.append("")
88
+ error_messages.append(f"Found {len(missing_validation_tests)} missing cases.")
89
+ error_messages.append(
90
+ "Please add validation tests for these computation functions."
91
+ )
92
+
93
+ raise AssertionError("\n".join(error_messages))
94
+
95
+
96
+ def test_validation_decorator_only_on_computation_functions() -> None:
97
+ """Test that @pytest.mark.validation is only used on computation function tests.
98
+
99
+ This test ensures that validation tests marked with @pytest.mark.validation
100
+ are only used for testing actual computation functions (those decorated with
101
+ @computation_function). Test functions for non-computation functions (like
102
+ I/O convenience functions) should not have this decorator.
103
+ """
104
+ # Get all functions marked with @pytest.mark.validation
105
+ validation_tests = get_validation_tests(tests_pkg)
106
+
107
+ # Get all valid test names for computation functions
108
+ valid_test_names = __generate_all_valid_test_names()
109
+
110
+ # Check each validation test to see if it corresponds to a computation function
111
+ invalid_validation_tests = []
112
+
113
+ for test_name, test_path, line_number in validation_tests:
114
+ if test_name not in valid_test_names:
115
+ # This validation test doesn't correspond to any computation function
116
+ rel_path = osp.relpath(test_path, start=osp.dirname(tests_pkg.__file__))
117
+ module_parts = rel_path.replace(osp.sep, ".").replace(".py", "")
118
+ module_name = f"sigima.tests.{module_parts}"
119
+ invalid_validation_tests.append((test_name, module_name, line_number))
120
+
121
+ # Report any invalid validation tests
122
+ if invalid_validation_tests:
123
+ error_messages = []
124
+ error_messages.append(
125
+ "Found @pytest.mark.validation decorator on tests that don't test "
126
+ "computation functions:"
127
+ )
128
+ for test_name, module_name, line_number in invalid_validation_tests:
129
+ # Convert module path back to file path for clickable links
130
+ file_path = (
131
+ module_name.replace("sigima.tests.", "").replace(".", "\\") + ".py"
132
+ )
133
+ error_messages.append(f" - {file_path}:{line_number} ({test_name})")
134
+ error_messages.append("")
135
+ error_messages.append(f"Found {len(invalid_validation_tests)} invalid cases.")
136
+ error_messages.append(
137
+ "The @pytest.mark.validation decorator should only be used on "
138
+ "test functions that test computation functions (those decorated with "
139
+ "@computation_function). Please remove this decorator from test functions "
140
+ "that test non-computation functions."
141
+ )
142
+
143
+ raise AssertionError("\n".join(error_messages))
144
+
145
+
146
+ def test_computation_functions_documented_in_features() -> None:
147
+ """Test that all computation functions are documented in doc/features.rst.
148
+
149
+ This test ensures that all computation functions (those decorated with
150
+ @computation_function) are documented in the features.rst file with
151
+ proper Sphinx :func: references using simplified paths like
152
+ sigima.proc.image.function_name or sigima.proc.signal.function_name.
153
+ """
154
+ # Read the features.rst file
155
+ doc_dir = osp.join(osp.dirname(tests_pkg.__file__), "..", "..", "doc")
156
+ features_rst_path = osp.join(doc_dir, "user_guide", "features.rst")
157
+
158
+ if not osp.exists(features_rst_path):
159
+ raise AssertionError(f"Documentation file not found: {features_rst_path}")
160
+
161
+ with open(features_rst_path, encoding="utf-8") as f:
162
+ features_content = f.read()
163
+
164
+ # Get all computation functions
165
+ computation_functions = find_computation_functions()
166
+
167
+ # Check each computation function to see if it's documented
168
+ missing_documentation = []
169
+
170
+ for module_name, func_name, _ in computation_functions:
171
+ # Build the expected documentation reference using simplified path
172
+ # The module_name is like "sigima.proc.image" or "sigima.proc.signal"
173
+ module_path = module_name.split("sigima.proc.")[-1]
174
+ expected_ref = f"sigima.proc.{module_path}.{func_name}"
175
+
176
+ # Check if this reference exists in the documentation
177
+ if expected_ref not in features_content:
178
+ missing_documentation.append((module_name, func_name, expected_ref))
179
+
180
+ # Report any missing documentation
181
+ if missing_documentation:
182
+ error_messages = []
183
+ error_messages.append(
184
+ "The following computation functions are missing from doc/features.rst:"
185
+ )
186
+ for module_name, func_name, expected_ref in missing_documentation:
187
+ error_messages.append(f" - {func_name} ({expected_ref})")
188
+ error_messages.append("")
189
+ error_messages.append(f"Found {len(missing_documentation)} missing cases.")
190
+ error_messages.append(
191
+ "Please add documentation references for these computation functions "
192
+ "in doc/features.rst using the format:"
193
+ )
194
+ error_messages.append(
195
+ " :func:`function_name <sigima.proc.module.function_name>`"
196
+ )
197
+
198
+ raise AssertionError("\n".join(error_messages))
199
+
200
+
201
+ if __name__ == "__main__":
202
+ test_validation_statistics()
203
+ test_validation_missing_tests()
204
+ test_validation_decorator_only_on_computation_functions()
205
+ test_computation_functions_documented_in_features()
@@ -0,0 +1,129 @@
1
+ # Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file.
2
+
3
+ """
4
+ `sigima` pytest configuration
5
+ -----------------------------
6
+
7
+ This file contains the configuration for running pytest in `sigima`. It is
8
+ executed before running any tests.
9
+ """
10
+
11
+ import os
12
+ import os.path as osp
13
+
14
+ import guidata
15
+ import h5py
16
+ import numpy
17
+ import pytest
18
+ import scipy
19
+ import skimage
20
+ from guidata.config import ValidationMode, set_validation_mode
21
+ from guidata.utils.gitreport import format_git_info_for_pytest, get_git_info_for_modules
22
+
23
+ import sigima
24
+ from sigima.proc.validation import ValidationStatistics
25
+ from sigima.tests import SIGIMA_TESTS_GUI_ENV, env, helpers
26
+
27
+ # Set validation mode to STRICT for all tests
28
+ set_validation_mode(ValidationMode.STRICT)
29
+
30
+ # Turn on unattended mode for executing tests without user interaction
31
+ env.execenv.unattended = True
32
+ env.execenv.verbose = "quiet"
33
+
34
+ INITIAL_CWD = os.getcwd()
35
+
36
+
37
+ def pytest_addoption(parser):
38
+ """Add custom command line options to pytest."""
39
+ parser.addoption(
40
+ "--show-windows",
41
+ action="store_true",
42
+ default=False,
43
+ help="Display Qt windows during tests (disables QT_QPA_PLATFORM=offscreen)",
44
+ )
45
+ parser.addoption(
46
+ "--gui", action="store_true", default=False, help="Run tests that require a GUI"
47
+ )
48
+
49
+
50
+ def pytest_report_header(config): # pylint: disable=unused-argument
51
+ """Add additional information to the pytest report header."""
52
+ infolist = [
53
+ f"sigima {sigima.__version__}",
54
+ f" guidata {guidata.__version__},",
55
+ f" NumPy {numpy.__version__}, SciPy {scipy.__version__}, "
56
+ f"h5py {h5py.__version__}, scikit-image {skimage.__version__}",
57
+ ]
58
+ try:
59
+ import cv2 # pylint: disable=import-outside-toplevel
60
+
61
+ infolist[-1] += f", OpenCV {cv2.__version__}"
62
+ except ImportError:
63
+ pass
64
+ envlist = []
65
+ for vname in ("SIGIMA_DATA", "PYTHONPATH", "DEBUG", "QT_API", "QT_QPA_PLATFORM"):
66
+ value = os.environ.get(vname, "")
67
+ if value:
68
+ if vname == "PYTHONPATH":
69
+ pathlist = value.split(os.pathsep)
70
+ envlist.append(f" {vname}:")
71
+ envlist.extend(f" {p}" for p in pathlist if p)
72
+ else:
73
+ envlist.append(f" {vname}: {value}")
74
+ if envlist:
75
+ infolist.append("Environment variables:")
76
+ infolist.extend(envlist)
77
+ infolist.append("Test paths:")
78
+ for test_path in helpers.get_test_paths():
79
+ test_path = osp.abspath(test_path)
80
+ infolist.append(f" {test_path}")
81
+
82
+ # Git information for all modules using the new gitreport module
83
+ modules_config = [
84
+ ("Sigima", sigima, "."), # Sigima uses current directory
85
+ ("guidata", guidata, None),
86
+ ]
87
+ git_repos = get_git_info_for_modules(modules_config)
88
+ git_info_lines = format_git_info_for_pytest(git_repos, "Sigima")
89
+ if git_info_lines:
90
+ infolist.extend(git_info_lines)
91
+
92
+ infolist.extend(ValidationStatistics().get_validation_info())
93
+ return infolist
94
+
95
+
96
+ def pytest_configure(config):
97
+ """Add custom markers to pytest."""
98
+ if config.option.durations is None:
99
+ config.option.durations = 10 # Default to showing 10 slowest tests
100
+ config.addinivalue_line(
101
+ "markers",
102
+ "validation: mark a test as a validation test (ground truth or analytical)",
103
+ )
104
+ if not config.getoption("--show-windows"):
105
+ os.environ.setdefault("QT_QPA_PLATFORM", "offscreen")
106
+ config.addinivalue_line("markers", "gui: mark test as requiring GUI")
107
+ if config.getoption("--gui"):
108
+ os.environ[SIGIMA_TESTS_GUI_ENV] = "1"
109
+ else:
110
+ os.environ.pop(SIGIMA_TESTS_GUI_ENV, None)
111
+
112
+
113
+ def pytest_collection_modifyitems(config, items):
114
+ """Modify collected test items based on command line options."""
115
+ if config.getoption("--gui"):
116
+ return # User requested GUI tests
117
+
118
+ skip_gui = pytest.mark.skip(reason="GUI test: run with --gui")
119
+
120
+ for item in items:
121
+ if "gui" in item.keywords:
122
+ item.add_marker(skip_gui)
123
+
124
+
125
+ @pytest.fixture(autouse=True)
126
+ def reset_cwd(request): # pylint: disable=unused-argument
127
+ """Reset the current working directory to the initial one after each test."""
128
+ yield
129
+ os.chdir(INITIAL_CWD)