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,183 @@
1
+ # Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file.
2
+
3
+ """
4
+ Unit tests for shape module
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import numpy as np
10
+
11
+ from sigima.objects import shape
12
+
13
+
14
+ def test_point_inplace_transform_and_copy() -> None:
15
+ """
16
+ Test inplace transformations and copy for PointCoordinates.
17
+
18
+ Verifies that translation and rotation work inplace, and that copy creates
19
+ an independent object.
20
+ """
21
+ pt = shape.PointCoordinates([1, 2])
22
+ pt2 = pt.copy()
23
+ assert np.allclose(pt.data, pt2.data)
24
+ pt.translate(3, 4)
25
+ assert np.allclose(pt.data, [4, 6])
26
+ pt2.rotate(np.pi, center=(1, 2))
27
+ assert np.allclose(pt2.data, [1, 2]) # Rotating at own center: no change
28
+
29
+
30
+ def test_segment_inplace_transform() -> None:
31
+ """Test inplace transformations for SegmentCoordinates."""
32
+ seg = shape.SegmentCoordinates([0, 0, 1, 1])
33
+ seg2 = seg.copy()
34
+ seg.translate(1, 1)
35
+ assert np.allclose(seg.data, [1, 1, 2, 2])
36
+ seg2.rotate(np.pi / 2, center=(0, 0))
37
+ assert np.allclose(seg2.data, [0, 0, -1, 1])
38
+
39
+
40
+ def test_rectangle_inplace_transform() -> None:
41
+ """
42
+ Test inplace horizontal and vertical flipping for RectangleCoordinates.
43
+
44
+ Verifies that flipping is performed inplace and independently on copies.
45
+ """
46
+ rect = shape.RectangleCoordinates([0.0, 0.0, 2.0, 3.0])
47
+ rect2 = rect.copy()
48
+ rect.fliph(cx=1.5)
49
+ assert np.allclose(rect.data, [1.0, 0.0, 2.0, 3.0])
50
+ rect2.flipv(cy=2.5)
51
+ assert np.allclose(rect2.data, [0.0, 2.0, 2.0, 3.0])
52
+
53
+
54
+ def test_circle_inplace_transform() -> None:
55
+ """
56
+ Test inplace translation and scaling for CircleCoordinates.
57
+
58
+ Verifies that only the center is transformed and the radius remains
59
+ unchanged.
60
+ """
61
+ circ = shape.CircleCoordinates([1, 2, 5])
62
+ circ2 = circ.copy()
63
+ circ.translate(2, -1)
64
+ assert np.allclose(circ.data, [3, 1, 5])
65
+ circ2.scale(2, 2)
66
+ assert np.allclose(circ2.data, [2, 4, 5]) # Only center is scaled
67
+
68
+
69
+ def test_polygon_inplace_transform() -> None:
70
+ """
71
+ Test inplace transpose and rotation for PolygonCoordinates.
72
+
73
+ Verifies that transpose and rotation are performed inplace and
74
+ independently on copies.
75
+ """
76
+ poly = shape.PolygonCoordinates([0, 0, 1, 0, 1, 1, 0, 1])
77
+ poly2 = poly.copy()
78
+ poly.transpose()
79
+ assert np.allclose(poly.data, [0, 0, 0, 1, 1, 1, 1, 0])
80
+ poly2.rotate(np.pi / 2, center=(0, 0))
81
+ assert np.allclose(poly2.data, [0, 0, 0, 1, -1, 1, -1, 0])
82
+
83
+
84
+ def test_inplace_vs_copy() -> None:
85
+ """
86
+ Test that inplace transformation does not affect a copy.
87
+
88
+ Verifies that after translating the original, the copy remains unchanged.
89
+ """
90
+ pt = shape.PointCoordinates([1, 2])
91
+ pt2 = pt.copy()
92
+ pt.translate(1, 1)
93
+ assert not np.allclose(pt.data, pt2.data)
94
+
95
+
96
+ def test_rotate_arbitrary_center() -> None:
97
+ """
98
+ Test rotation around an arbitrary center for all coordinate types.
99
+ """
100
+ pt = shape.PointCoordinates([2, 3])
101
+ pt.rotate(np.pi / 2, center=(1, 1))
102
+ # (2,3) rotated 90° CCW around (1,1) -> ( -1,2 )
103
+ assert np.allclose(pt.data, [-1, 2])
104
+
105
+ rect = shape.RectangleCoordinates([2, 3, 4, 5])
106
+ rect.rotate(np.pi / 2, center=(1, 1))
107
+ expected = [-6, 2, 5, 4]
108
+ assert np.allclose(rect.data, expected)
109
+
110
+ circ = shape.CircleCoordinates([2, 3, 5])
111
+ circ.rotate(np.pi / 2, center=(1, 1))
112
+ assert np.allclose(circ.data, [-1, 2, 5])
113
+
114
+ poly = shape.PolygonCoordinates([2, 3, 4, 5, 6, 7])
115
+ poly.rotate(np.pi / 2, center=(1, 1))
116
+ expected_poly = [-1, 2, -3, 4, -5, 6]
117
+ assert np.allclose(poly.data, expected_poly)
118
+
119
+
120
+ def test_fliph_flipv_arbitrary_axis() -> None:
121
+ """
122
+ Test horizontal and vertical flip with respect to arbitrary axis
123
+ for all coordinate types.
124
+ """
125
+ pt = shape.PointCoordinates([2, 3])
126
+ pt.fliph(cx=1.5)
127
+ assert np.allclose(pt.data, [1.0, 3])
128
+ pt2 = shape.PointCoordinates([2, 3])
129
+ pt2.flipv(cy=2.5)
130
+ assert np.allclose(pt2.data, [2, 2.0])
131
+
132
+ rect = shape.RectangleCoordinates([2, 3, 4, 5])
133
+ rect.fliph(cx=3)
134
+ assert np.allclose(rect.data, [0, 3, 4, 5])
135
+ rect2 = shape.RectangleCoordinates([2, 3, 4, 5])
136
+ rect2.flipv(cy=4)
137
+ assert np.allclose(rect2.data, [2, 0, 4, 5])
138
+
139
+ circ = shape.CircleCoordinates([2, 3, 5])
140
+ circ.fliph(cx=1)
141
+ assert np.allclose(circ.data, [0, 3, 5])
142
+ circ2 = shape.CircleCoordinates([2, 3, 5])
143
+ circ2.flipv(cy=2)
144
+ assert np.allclose(circ2.data, [2, 1, 5])
145
+
146
+ poly = shape.PolygonCoordinates([2, 3, 4, 5])
147
+ poly.fliph(cx=3)
148
+ assert np.allclose(poly.data, [4, 3, 2, 5])
149
+ poly2 = shape.PolygonCoordinates([2, 3, 4, 5])
150
+ poly2.flipv(cy=4)
151
+ assert np.allclose(poly2.data, [2, 5, 4, 3])
152
+
153
+
154
+ def test_transpose_all_types() -> None:
155
+ """
156
+ Test transposition (swap x and y) for all coordinate types.
157
+ """
158
+ pt = shape.PointCoordinates([2, 3])
159
+ pt.transpose()
160
+ assert np.allclose(pt.data, [3, 2])
161
+
162
+ rect = shape.RectangleCoordinates([2, 3, 4, 5])
163
+ rect.transpose()
164
+ assert np.allclose(rect.data, [3, 2, 5, 4])
165
+
166
+ circ = shape.CircleCoordinates([2, 3, 5])
167
+ circ.transpose()
168
+ assert np.allclose(circ.data, [3, 2, 5])
169
+
170
+ poly = shape.PolygonCoordinates([2, 3, 4, 5, 6, 7])
171
+ poly.transpose()
172
+ assert np.allclose(poly.data, [3, 2, 5, 4, 7, 6])
173
+
174
+
175
+ if __name__ == "__main__":
176
+ test_point_inplace_transform_and_copy()
177
+ test_rectangle_inplace_transform()
178
+ test_circle_inplace_transform()
179
+ test_polygon_inplace_transform()
180
+ test_inplace_vs_copy()
181
+ test_rotate_arbitrary_center()
182
+ test_fliph_flipv_arbitrary_axis()
183
+ test_transpose_all_types()
@@ -0,0 +1,138 @@
1
+ # Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file.
2
+
3
+ """
4
+ Statistics unit test
5
+
6
+ Testing the following:
7
+ - Create a signal
8
+ - Compute statistics on signal and compare with expected results
9
+ - Create an image
10
+ - Compute statistics on image and compare with expected results
11
+ """
12
+
13
+ # pylint: disable=invalid-name # Allows short reference names like x, y, ...
14
+
15
+ from __future__ import annotations
16
+
17
+ import numpy as np
18
+ import pytest
19
+ import scipy.integrate as spt
20
+
21
+ import sigima.objects
22
+ import sigima.proc.image
23
+ import sigima.proc.signal
24
+
25
+
26
+ def get_analytical_stats(data: np.ndarray) -> dict[str, float]:
27
+ """Compute analytical statistics for data
28
+
29
+ Args:
30
+ data: Array of data
31
+
32
+ Returns:
33
+ Dictionary with analytical statistics
34
+ """
35
+ results = {}
36
+ if data.shape[0] == 2:
37
+ # This is a signal data (row 0: x, row 1: y)
38
+ results["trapz"] = spt.trapezoid(data[1], data[0])
39
+ data = data[1]
40
+ results.update(
41
+ {
42
+ "min": np.min(data),
43
+ "max": np.max(data),
44
+ "mean": np.mean(data),
45
+ "median": np.median(data),
46
+ "std": np.std(data),
47
+ "snr": np.mean(data) / np.std(data),
48
+ "ptp": np.ptp(data),
49
+ "sum": np.sum(data),
50
+ }
51
+ )
52
+ return results
53
+
54
+
55
+ def create_reference_signal() -> sigima.objects.SignalObj:
56
+ """Create reference signal"""
57
+ param = sigima.objects.GaussParam()
58
+ sig = sigima.objects.create_signal_from_param(param)
59
+ sig.roi = sigima.objects.create_signal_roi(
60
+ [len(sig.x) // 2, len(sig.x) - 1], indices=True
61
+ )
62
+ return sig
63
+
64
+
65
+ def create_reference_image() -> sigima.objects.ImageObj:
66
+ """Create reference image"""
67
+ param = sigima.objects.Gauss2DParam.create(title="2D-Gaussian")
68
+ ima = sigima.objects.create_image_from_param(param)
69
+ dy, dx = ima.data.shape
70
+ ima.roi = sigima.objects.create_image_roi(
71
+ "rectangle",
72
+ [
73
+ [dx // 2, 0, dx, dy],
74
+ [0, 0, dx // 3, dy // 3],
75
+ [dx // 2, dy // 2, dx, dy],
76
+ ],
77
+ )
78
+ return ima
79
+
80
+
81
+ @pytest.mark.validation
82
+ def test_signal_stats_unit() -> None:
83
+ """Validate computed statistics for signals"""
84
+ obj = create_reference_signal()
85
+ table = sigima.proc.signal.stats(obj)
86
+ ref = get_analytical_stats(obj.xydata)
87
+ for key, val in ref.items():
88
+ assert key in table
89
+ assert np.isclose(table[key][0], val), f"Incorrect value for {key}"
90
+
91
+ # Given the fact that signal ROI is set to [len(sig.x) // 2, len(sig.x) - 1],
92
+ # we may check the relationship between the results on the whole signal and the ROI:
93
+ for key, val in ref.items():
94
+ if key in ("trapz", "sum"):
95
+ assert np.isclose(table[key][1], val / 2, rtol=0.02)
96
+ elif key == "median":
97
+ continue
98
+ else:
99
+ assert np.isclose(table[key][1], val, rtol=0.01)
100
+
101
+
102
+ @pytest.mark.validation
103
+ def test_image_stats_unit() -> None:
104
+ """Validate computed statistics for images"""
105
+ obj = create_reference_image()
106
+
107
+ # Ignore "RuntimeWarning: invalid value encountered in scalar divide" in the test
108
+ # (this warning is due to the fact that the 2nd ROI has zero sum of pixel values,
109
+ # hence the mean/std is NaN)
110
+ with np.errstate(invalid="ignore"):
111
+ res = sigima.proc.image.stats(obj)
112
+
113
+ ref = get_analytical_stats(obj.data)
114
+ for key, val in ref.items():
115
+ assert key in res
116
+ assert np.isclose(res[key][0], val, rtol=1e-4, atol=1e-5), (
117
+ f"Incorrect value for {key}"
118
+ )
119
+
120
+ # Given the fact that image ROI is set to
121
+ # [[dx // 2, 0, dx, dy], [0, 0, dx // 3, dy // 3], [dx // 2, dy // 2, dx, dy]],
122
+ # we may check the relationship between the results on the whole image and the ROIs:
123
+ for key, val in ref.items():
124
+ if key == "sum":
125
+ assert np.isclose(res[key][1], val / 2, rtol=0.02)
126
+ assert np.isclose(res[key][3], val / 4, rtol=0.02)
127
+ elif key == "median":
128
+ continue
129
+ else:
130
+ assert np.isclose(res[key][1], val, rtol=0.01)
131
+ assert np.isclose(res[key][3], val, rtol=0.01)
132
+ if key != "snr":
133
+ assert np.isclose(res[key][2], 0.0, atol=0.001)
134
+
135
+
136
+ if __name__ == "__main__":
137
+ test_signal_stats_unit()
138
+ test_image_stats_unit()
@@ -0,0 +1,338 @@
1
+ # Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file.
2
+
3
+ """
4
+ Title formatting system unit tests
5
+
6
+ Testing the configurable title formatting system that allows different applications
7
+ (Sigima standalone vs DataLab integration) to use different title formatting strategies.
8
+
9
+ This test verifies:
10
+ - SimpleTitleFormatter: Human-readable titles for standalone Sigima usage
11
+ - PlaceholderTitleFormatter: DataLab-compatible placeholder titles
12
+ - Configuration system: Setting and getting default formatters
13
+ - Integration: Testing with computation functions from sigima.proc.base
14
+ - Compatibility: Ensuring DataLab-style placeholder resolution works
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ import pytest
20
+
21
+ from sigima import create_signal
22
+ from sigima.proc.base import dst_1_to_1, dst_2_to_1, dst_n_to_1
23
+ from sigima.proc.title_formatting import (
24
+ PlaceholderTitleFormatter,
25
+ SimpleTitleFormatter,
26
+ get_default_title_formatter,
27
+ set_default_title_formatter,
28
+ )
29
+
30
+
31
+ class TestSimpleTitleFormatter:
32
+ """Test suite for SimpleTitleFormatter class."""
33
+
34
+ def test_1_to_1_operations(self):
35
+ """Test SimpleTitleFormatter for 1-to-1 operations."""
36
+ formatter = SimpleTitleFormatter()
37
+
38
+ # Test basic function name formatting
39
+ assert (
40
+ formatter.format_1_to_1_title("gaussian_filter") == "Gaussian Filter Result"
41
+ )
42
+
43
+ # Test with suffix
44
+ result = formatter.format_1_to_1_title("gaussian_filter", "sigma=1.5")
45
+ assert result == "Gaussian Filter Result (sigma=1.5)"
46
+
47
+ # Test with operator
48
+ assert formatter.format_1_to_1_title("+") == "operator_+"
49
+
50
+ def test_n_to_1_operations(self):
51
+ """Test SimpleTitleFormatter for n-to-1 operations."""
52
+ formatter = SimpleTitleFormatter()
53
+
54
+ # Test basic n-to-1 formatting
55
+ result = formatter.format_n_to_1_title("add_signals", 3)
56
+ assert result == "Add Signals of 3 Objects"
57
+
58
+ # Test with suffix
59
+ result = formatter.format_n_to_1_title("mean", 5, "weighted")
60
+ assert result == "Mean of 5 Objects (weighted)"
61
+
62
+ def test_2_to_1_operations(self):
63
+ """Test SimpleTitleFormatter for 2-to-1 operations."""
64
+ formatter = SimpleTitleFormatter()
65
+
66
+ # Test basic 2-to-1 formatting
67
+ assert formatter.format_2_to_1_title("subtract") == "Subtract Result"
68
+
69
+ # Test with operator
70
+ assert formatter.format_2_to_1_title("-") == "Binary Operation -"
71
+
72
+ # Test with suffix
73
+ result = formatter.format_2_to_1_title("divide", "method=euclidean")
74
+ assert result == "Divide Result (method=euclidean)"
75
+
76
+ def test_placeholder_resolution(self):
77
+ """Test SimpleTitleFormatter placeholder resolution (should be unchanged)."""
78
+ formatter = SimpleTitleFormatter()
79
+
80
+ # Should return unchanged for simple formatter
81
+ result = formatter.resolve_placeholder_title("wiener({0})", [])
82
+ assert result == "wiener({0})"
83
+
84
+ def test_humanize_function_name(self):
85
+ """Test the function name humanization logic."""
86
+ formatter = SimpleTitleFormatter()
87
+
88
+ # Test the internal logic through the public interface
89
+ # The formatter converts snake_case to Title Case internally
90
+ result = formatter.format_1_to_1_title("gaussian_filter")
91
+ assert "Gaussian Filter" in result
92
+
93
+ result = formatter.format_1_to_1_title("moving_average")
94
+ assert "Moving Average" in result
95
+
96
+ # Test single words
97
+ result = formatter.format_1_to_1_title("normalize")
98
+ assert "Normalize" in result
99
+
100
+ # Test operators (should be handled specially)
101
+ result = formatter.format_1_to_1_title("+")
102
+ assert "operator_+" in result
103
+
104
+ result = formatter.format_1_to_1_title("-")
105
+ assert "operator_-" in result
106
+
107
+ def test_edge_cases(self):
108
+ """Test edge cases for SimpleTitleFormatter."""
109
+ formatter = SimpleTitleFormatter()
110
+
111
+ # Test empty function name
112
+ assert formatter.format_1_to_1_title("") == " Result"
113
+
114
+ # Test None suffix (should be handled gracefully)
115
+ result = formatter.format_1_to_1_title("test", None)
116
+ assert result == "Test Result"
117
+
118
+ # Test empty suffix
119
+ result = formatter.format_1_to_1_title("test", "")
120
+ assert result == "Test Result"
121
+
122
+
123
+ class TestPlaceholderTitleFormatter:
124
+ """Test suite for PlaceholderTitleFormatter class."""
125
+
126
+ def test_1_to_1_operations(self):
127
+ """Test PlaceholderTitleFormatter for 1-to-1 operations."""
128
+ formatter = PlaceholderTitleFormatter()
129
+
130
+ # Test basic function name formatting
131
+ assert formatter.format_1_to_1_title("wiener") == "wiener({0})"
132
+
133
+ # Test with suffix
134
+ result = formatter.format_1_to_1_title("gaussian_filter", "sigma=1.5")
135
+ assert result == "gaussian_filter({0})|sigma=1.5"
136
+
137
+ def test_n_to_1_operations(self):
138
+ """Test PlaceholderTitleFormatter for n-to-1 operations."""
139
+ formatter = PlaceholderTitleFormatter()
140
+
141
+ # Test basic n-to-1 formatting
142
+ result = formatter.format_n_to_1_title("sum", 3)
143
+ assert result == "sum({0}, {1}, {2})"
144
+
145
+ # Test with suffix
146
+ result = formatter.format_n_to_1_title("mean", 5, "weighted=True")
147
+ assert result == "mean({0}, {1}, {2}, {3}, {4})|weighted=True"
148
+
149
+ def test_2_to_1_operations(self):
150
+ """Test PlaceholderTitleFormatter for 2-to-1 operations."""
151
+ formatter = PlaceholderTitleFormatter()
152
+
153
+ # Test basic 2-to-1 formatting
154
+ assert formatter.format_2_to_1_title("subtract") == "subtract({0}, {1})"
155
+
156
+ # Test with suffix
157
+ result = formatter.format_2_to_1_title("divide", "method=euclidean")
158
+ assert result == "divide({0}, {1})|method=euclidean"
159
+
160
+ def test_placeholder_resolution(self):
161
+ """Test PlaceholderTitleFormatter placeholder resolution."""
162
+ formatter = PlaceholderTitleFormatter()
163
+
164
+ # Create mock objects with simple string values (strings are used as fallback)
165
+ src1 = create_signal("Signal001", x=[1, 2], y=[3, 4])
166
+
167
+ # Test basic placeholder resolution (uses obj1 fallback since no short_id)
168
+ result = formatter.resolve_placeholder_title("wiener({0})", [src1])
169
+ assert result == "wiener(obj1)"
170
+
171
+ # Test with suffix (suffix is preserved in resolution)
172
+ result = formatter.resolve_placeholder_title("gaussian({0})|sigma=2.0", [src1])
173
+ assert result == "gaussian(obj1)|sigma=2.0"
174
+
175
+ # Test empty placeholders list (should raise IndexError)
176
+ with pytest.raises(IndexError):
177
+ formatter.resolve_placeholder_title("wiener({0})", [])
178
+
179
+ def test_edge_cases(self):
180
+ """Test edge cases for PlaceholderTitleFormatter."""
181
+ formatter = PlaceholderTitleFormatter()
182
+
183
+ # Test empty function name
184
+ assert formatter.format_1_to_1_title("") == "({0})"
185
+
186
+ # Test None suffix (should be handled gracefully)
187
+ result = formatter.format_1_to_1_title("test", None)
188
+ assert result == "test({0})"
189
+
190
+ # Test empty suffix
191
+ result = formatter.format_1_to_1_title("test", "")
192
+ assert result == "test({0})"
193
+
194
+ def test_datalab_compatibility(self):
195
+ """Test DataLab-style placeholder resolution compatibility."""
196
+ formatter = PlaceholderTitleFormatter()
197
+
198
+ # Create signal objects for testing
199
+ sig1 = create_signal("Signal001", x=[1, 2], y=[3, 4])
200
+
201
+ # Test typical DataLab usage patterns (uses obj1 fallback without short_id)
202
+ test_cases = [
203
+ ("wiener({0})", [sig1], "wiener(obj1)"),
204
+ ("gaussian({0})|sigma=2.0", [sig1], "gaussian(obj1)|sigma=2.0"),
205
+ ("add({0})|3 objects", [sig1], "add(obj1)|3 objects"),
206
+ ]
207
+
208
+ for placeholder_title, source_titles, expected in test_cases:
209
+ result = formatter.resolve_placeholder_title(
210
+ placeholder_title, source_titles
211
+ )
212
+ assert result == expected
213
+
214
+ # Test empty list case (should raise IndexError)
215
+ with pytest.raises(IndexError):
216
+ formatter.resolve_placeholder_title("fft({0})", [])
217
+
218
+
219
+ class TestFormatterConfiguration:
220
+ """Test suite for the global formatter configuration system."""
221
+
222
+ def test_configuration_system(self):
223
+ """Test the global formatter configuration system."""
224
+ # Store original formatter to restore later
225
+ original_formatter = get_default_title_formatter()
226
+
227
+ try:
228
+ # Test setting SimpleTitleFormatter
229
+ simple_formatter = SimpleTitleFormatter()
230
+ set_default_title_formatter(simple_formatter)
231
+ current_formatter = get_default_title_formatter()
232
+ assert isinstance(current_formatter, SimpleTitleFormatter)
233
+
234
+ # Test setting PlaceholderTitleFormatter
235
+ placeholder_formatter = PlaceholderTitleFormatter()
236
+ set_default_title_formatter(placeholder_formatter)
237
+ current_formatter = get_default_title_formatter()
238
+ assert isinstance(current_formatter, PlaceholderTitleFormatter)
239
+
240
+ finally:
241
+ # Restore original formatter
242
+ set_default_title_formatter(original_formatter)
243
+
244
+
245
+ class TestIntegrationWithComputationFunctions:
246
+ """Test suite for integration with computation functions from sigima.proc.base."""
247
+
248
+ def test_integration_with_dst_1_to_1(self):
249
+ """Test integration with dst_1_to_1 function."""
250
+ original_formatter = get_default_title_formatter()
251
+
252
+ try:
253
+ # Test with SimpleTitleFormatter
254
+ set_default_title_formatter(SimpleTitleFormatter())
255
+ src = create_signal("Test Signal", x=[1, 2, 3], y=[4, 5, 6])
256
+ result = dst_1_to_1(src, "gaussian_filter", "sigma=1.0")
257
+ assert "Gaussian Filter Result" in result.title
258
+ assert "sigma=1.0" in result.title
259
+
260
+ # Test with PlaceholderTitleFormatter
261
+ set_default_title_formatter(PlaceholderTitleFormatter())
262
+ result2 = dst_1_to_1(src, "gaussian_filter", "sigma=1.0")
263
+ assert result2.title == "gaussian_filter({0})|sigma=1.0"
264
+
265
+ finally:
266
+ set_default_title_formatter(original_formatter)
267
+
268
+ def test_integration_with_dst_2_to_1(self):
269
+ """Test integration with dst_2_to_1 function."""
270
+ original_formatter = get_default_title_formatter()
271
+
272
+ try:
273
+ # Test with SimpleTitleFormatter
274
+ set_default_title_formatter(SimpleTitleFormatter())
275
+ src1 = create_signal("Signal1", x=[1, 2, 3], y=[1, 2, 3])
276
+ src2 = create_signal("Signal2", x=[1, 2, 3], y=[4, 5, 6])
277
+ result = dst_2_to_1(src1, src2, "subtract")
278
+ assert result.title == "Subtract Result"
279
+
280
+ # Test with PlaceholderTitleFormatter
281
+ set_default_title_formatter(PlaceholderTitleFormatter())
282
+ result2 = dst_2_to_1(src1, src2, "subtract")
283
+ assert result2.title == "subtract({0}, {1})"
284
+
285
+ finally:
286
+ set_default_title_formatter(original_formatter)
287
+
288
+ def test_integration_with_dst_n_to_1(self):
289
+ """Test integration with dst_n_to_1 function."""
290
+ original_formatter = get_default_title_formatter()
291
+
292
+ try:
293
+ # Test with SimpleTitleFormatter
294
+ set_default_title_formatter(SimpleTitleFormatter())
295
+ signals = [
296
+ create_signal(f"Signal{i}", x=[1, 2, 3], y=[i, i + 1, i + 2])
297
+ for i in range(1, 4)
298
+ ]
299
+ result = dst_n_to_1(signals, "sum")
300
+ assert result.title == "Sum of 3 Objects"
301
+
302
+ # Test with PlaceholderTitleFormatter
303
+ set_default_title_formatter(PlaceholderTitleFormatter())
304
+ result2 = dst_n_to_1(signals, "sum")
305
+ assert result2.title == "sum({0}, {1}, {2})"
306
+
307
+ finally:
308
+ set_default_title_formatter(original_formatter)
309
+
310
+
311
+ class TestFormatterConsistency:
312
+ """Test suite for formatter consistency and compatibility."""
313
+
314
+ def test_formatter_consistency(self):
315
+ """Test that formatters produce consistent results."""
316
+ simple = SimpleTitleFormatter()
317
+ placeholder = PlaceholderTitleFormatter()
318
+
319
+ # Test same function name produces consistent patterns
320
+ func_name = "gaussian_filter"
321
+ suffix = "sigma=1.0"
322
+
323
+ simple_result = simple.format_1_to_1_title(func_name, suffix)
324
+ placeholder_result = placeholder.format_1_to_1_title(func_name, suffix)
325
+
326
+ # Both should include the suffix information
327
+ assert suffix in simple_result
328
+ assert suffix in placeholder_result
329
+
330
+ # Simple should be human-readable
331
+ assert "Gaussian Filter" in simple_result
332
+
333
+ # Placeholder should use function name as-is
334
+ assert func_name in placeholder_result
335
+
336
+
337
+ if __name__ == "__main__":
338
+ pytest.main([__file__])