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,176 @@
1
+ # Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file.
2
+
3
+ """
4
+ Testing the decorator for computation functions.
5
+
6
+ This test checks:
7
+ - The decorator can be applied to a function
8
+ - The function can be called with and without DataSet parameters
9
+ - The metadata is correctly set and can be introspected
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import guidata.dataset as gds
15
+ import numpy as np
16
+
17
+ from sigima.objects import ImageObj, SignalObj, create_image, create_signal
18
+ from sigima.proc.base import dst_1_to_1
19
+ from sigima.proc.decorator import (
20
+ computation_function,
21
+ get_computation_metadata,
22
+ is_computation_function,
23
+ )
24
+ from sigima.tests.helpers import check_array_result
25
+
26
+
27
+ class DummySignalParam(gds.DataSet):
28
+ """Dummy DataSet for testing purposes"""
29
+
30
+ a = gds.FloatItem("X value", default=1.0)
31
+ b = gds.FloatItem("Y value", default=5.0)
32
+ methods = (("linear", "Linear"), ("quadratic", "Quadratic"))
33
+ method = gds.ChoiceItem("Method", choices=methods, default="linear")
34
+
35
+
36
+ SCF_NAME = "dummy_signal_func"
37
+ SCF_DESCRIPTION = "A dummy signal function"
38
+
39
+
40
+ @computation_function(name=SCF_NAME, description=SCF_DESCRIPTION)
41
+ def dummy_signal_func(src: SignalObj, p: DummySignalParam) -> SignalObj:
42
+ """A dummy function that adds two parameters from a DataSet.
43
+
44
+ Args:
45
+ src: The source SignalObj.
46
+ param: The parameters from the DummySignalParam DataSet.
47
+
48
+ Returns:
49
+ The signal with the operation applied.
50
+ """
51
+ dst = dst_1_to_1(src, SCF_NAME, f"x={p.a:.3f}, y={p.b:.3f}")
52
+ if p.method == "linear":
53
+ dst.y = src.y + src.x * p.a + p.b
54
+ else: # Quadratic method
55
+ dst.y = src.y + src.x**2 * p.a + p.b
56
+ return dst
57
+
58
+
59
+ class DummyImageParam(gds.DataSet):
60
+ """Dummy DataSet for testing purposes"""
61
+
62
+ alpha = gds.FloatItem("Alpha value", default=0.5)
63
+
64
+
65
+ ICF_NAME = "dummy_image_func"
66
+ ICF_DESCRIPTION = "A dummy image function"
67
+
68
+
69
+ @computation_function(name=ICF_NAME, description=ICF_DESCRIPTION)
70
+ def dummy_image_func(src: ImageObj, param: DummyImageParam) -> ImageObj:
71
+ """A dummy function that applies a simple operation based on a DataSet parameter.
72
+
73
+ Args:
74
+ src: The source ImageObj.
75
+ param: The parameters from the DummyImageParam DataSet.
76
+
77
+ Returns:
78
+ The image with the operation applied.
79
+ """
80
+ dst = dst_1_to_1(src, ICF_NAME, f"sigma={param.alpha:.3f}")
81
+ dst.data = src.data * param.alpha # Simplified operation for testing
82
+ return dst
83
+
84
+
85
+ def test_signal_decorator_marker() -> None:
86
+ """Test the computation function decorator marker for signals"""
87
+ # Check if the function is marked as a computation function
88
+ assert is_computation_function(dummy_signal_func)
89
+
90
+
91
+ def test_signal_decorator_metadata() -> None:
92
+ """Test the computation function decorator metadata for signals"""
93
+ # Check if the metadata is correctly set
94
+ metadata = get_computation_metadata(dummy_signal_func)
95
+ assert metadata.name == SCF_NAME
96
+ assert metadata.description == SCF_DESCRIPTION
97
+
98
+
99
+ def test_signal_decorator_signature() -> None:
100
+ """Test the computation function decorator signature for signals"""
101
+ x = np.linspace(0, 10, 100)
102
+ orig = create_signal("test_signal", x=x, y=x)
103
+
104
+ # Call the function with a DataSet parameter
105
+ p = DummySignalParam.create(a=3.0, b=4.0, method="quadratic")
106
+ res_ds = dummy_signal_func(orig, p)
107
+ name = "Signal[DataSet parameter]"
108
+ check_array_result(f"{name} x", res_ds.x, orig.x)
109
+ check_array_result(f"{name} y", res_ds.y, orig.y + orig.x**2 * 3.0 + 4.0)
110
+
111
+ # Call the function with keyword arguments
112
+ # pylint: disable=no-value-for-parameter
113
+ res_kw = dummy_signal_func(orig, a=3.0, b=4.0)
114
+ name = "Signal[keyword arguments]"
115
+ check_array_result(f"{name} x", res_kw.x, orig.x)
116
+ check_array_result(f"{name} y", res_kw.y, orig.y + orig.x * 3.0 + 4.0)
117
+
118
+ # Call the function with both DataSet and keyword arguments
119
+ # The DataSet should take precedence, kwargs for DataSet items are ignored
120
+ res_both = dummy_signal_func(orig, p, a=100.0, b=200.0, method="linear")
121
+ # The result should match the DataSet values, not the conflicting kwargs
122
+ check_array_result(
123
+ "Signal[DataSet param + kwargs: DataSet wins] x", res_both.x, orig.x
124
+ )
125
+ check_array_result(
126
+ "Signal[DataSet param + kwargs: DataSet wins] y",
127
+ res_both.y,
128
+ orig.y + orig.x**2 * 3.0 + 4.0,
129
+ )
130
+
131
+
132
+ def test_image_decorator_marker() -> None:
133
+ """Test the computation function decorator marker for images"""
134
+ # Check if the function is marked as a computation function
135
+ assert is_computation_function(dummy_image_func)
136
+
137
+
138
+ def test_image_decorator_metadata() -> None:
139
+ """Test the computation function decorator metadata for images"""
140
+ # Check if the metadata is correctly set
141
+ metadata = get_computation_metadata(dummy_image_func)
142
+ assert metadata.name == ICF_NAME
143
+ assert metadata.description == ICF_DESCRIPTION
144
+
145
+
146
+ def test_image_decorator_signature() -> None:
147
+ """Test the computation function decorator signature for images"""
148
+ orig = create_image("test_image", data=np.random.rand(64, 64))
149
+
150
+ # Call the function with an ImageObj and DummyImageParam
151
+ p = DummyImageParam.create(alpha=0.8)
152
+ res_ds = dummy_image_func(orig, p)
153
+ check_array_result("Image data", res_ds.data, orig.data * p.alpha)
154
+
155
+ # Call the function with keyword arguments
156
+ # pylint: disable=no-value-for-parameter
157
+ res_kw = dummy_image_func(orig, alpha=0.8)
158
+ check_array_result("Image data", res_kw.data, orig.data * 0.8)
159
+
160
+ # Call the function with both DataSet and keyword arguments
161
+ # The DataSet should take precedence, kwargs for DataSet items are ignored
162
+ res_both = dummy_image_func(orig, p, alpha=0.4)
163
+ check_array_result(
164
+ "Image data [DataSet param + kwargs: DataSet wins]",
165
+ res_both.data,
166
+ orig.data * p.alpha,
167
+ )
168
+
169
+
170
+ if __name__ == "__main__":
171
+ test_signal_decorator_marker()
172
+ test_signal_decorator_metadata()
173
+ test_signal_decorator_signature()
174
+ test_image_decorator_marker()
175
+ test_image_decorator_metadata()
176
+ test_image_decorator_signature()
@@ -0,0 +1,104 @@
1
+ # Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file.
2
+
3
+ """
4
+ Test for documentation examples
5
+
6
+ This module automatically discovers and executes all Python files in the
7
+ doc/examples directory to ensure they run without errors.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import importlib.util
13
+ import sys
14
+ from pathlib import Path
15
+
16
+ import pytest
17
+
18
+ import sigima
19
+ from sigima.client.stub import patch_simpleremoteproxy_for_stub
20
+
21
+ # Check if plotpy is available
22
+ try:
23
+ import plotpy # pylint: disable=unused-import # noqa: F401
24
+
25
+ PLOTPY_AVAILABLE = True
26
+ except ImportError:
27
+ PLOTPY_AVAILABLE = False
28
+
29
+
30
+ def get_example_dir() -> Path:
31
+ """Get the path to the examples directory."""
32
+ return Path(sigima.__file__).parent.parent / "doc" / "examples"
33
+
34
+
35
+ def get_example_files() -> list[Path]:
36
+ """Get all Python files in doc/examples directory."""
37
+ examples_dir = get_example_dir()
38
+ if not examples_dir.exists():
39
+ return []
40
+
41
+ return [
42
+ f
43
+ for f in examples_dir.glob("*.py")
44
+ if not f.name.startswith("_") and f.name != "__init__.py"
45
+ ]
46
+
47
+
48
+ def test_examples_directory_exists() -> None:
49
+ """Test that the examples directory exists and contains Python files."""
50
+ examples_dir = get_example_dir()
51
+ assert examples_dir.exists(), "doc/examples directory should exist"
52
+
53
+ python_files = list(examples_dir.rglob("*.py"))
54
+ assert len(python_files) > 0, "doc/examples should contain at least one Python file"
55
+
56
+
57
+ @pytest.mark.skipif(not PLOTPY_AVAILABLE, reason="PlotPy not installed")
58
+ @pytest.mark.parametrize("example_file", get_example_files())
59
+ def test_example_execution(example_file: Path) -> None:
60
+ """Test that each example file can be executed without errors."""
61
+ # Special handling for datalab_client.py - needs a stub server
62
+ if example_file.name == "datalab_client.py":
63
+ # Use the utility to patch SimpleRemoteProxy for stub server
64
+ stub_server = patch_simpleremoteproxy_for_stub()
65
+ try:
66
+ # Load and execute the module
67
+ _execute_example_module(example_file)
68
+ finally:
69
+ # Clean up the stub server
70
+ stub_server.stop()
71
+ else:
72
+ # Normal execution for other examples
73
+ _execute_example_module(example_file)
74
+
75
+
76
+ def _execute_example_module(example_file: Path) -> None:
77
+ """Execute an example module file.
78
+
79
+ Args:
80
+ example_file: Path to the example Python file to execute
81
+ """
82
+ # Load the module
83
+ spec = importlib.util.spec_from_file_location(
84
+ f"example_{example_file.stem}", str(example_file)
85
+ )
86
+ module = importlib.util.module_from_spec(spec)
87
+
88
+ # Add the example directory to sys.path temporarily
89
+ examples_dir = str(example_file.parent)
90
+ if examples_dir not in sys.path:
91
+ sys.path.insert(0, examples_dir)
92
+
93
+ try:
94
+ # Execute the module
95
+ spec.loader.exec_module(module)
96
+ finally:
97
+ # Clean up sys.path
98
+ if examples_dir in sys.path:
99
+ sys.path.remove(examples_dir)
100
+
101
+
102
+ if __name__ == "__main__":
103
+ # Run the tests when executed directly
104
+ pytest.main([__file__, "-v"])
@@ -0,0 +1,242 @@
1
+ # Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file.
2
+
3
+ """Unit tests for kernel normalization features in convolution/deconvolution."""
4
+
5
+ from __future__ import annotations
6
+
7
+ import numpy as np
8
+ import pytest
9
+
10
+ from sigima.config import options as sigima_options
11
+ from sigima.objects import create_image, create_signal
12
+ from sigima.objects.image import ImageObj
13
+ from sigima.objects.signal import SignalObj
14
+ from sigima.proc.image.mathops import convolution as image_convolution
15
+ from sigima.proc.image.mathops import deconvolution as image_deconvolution
16
+ from sigima.proc.signal import convolution as signal_convolution
17
+ from sigima.proc.signal import deconvolution as signal_deconvolution
18
+
19
+
20
+ def _generate_test_signal(size: int = 100) -> SignalObj:
21
+ """Generate a simple test signal.
22
+
23
+ Args:
24
+ size: The size of the signal to generate.
25
+
26
+ Returns:
27
+ A signal object.
28
+ """
29
+ x = np.linspace(0, 10, size)
30
+ y = np.sin(x) + 0.5 * np.sin(3 * x)
31
+ return create_signal("Test Signal", x, y)
32
+
33
+
34
+ def _generate_unnormalized_signal_kernel(size: int = 100) -> SignalObj:
35
+ """Generate an unnormalized Gaussian-like kernel for signal processing.
36
+
37
+ Args:
38
+ size: The size of the kernel.
39
+
40
+ Returns:
41
+ A signal object representing an unnormalized kernel.
42
+
43
+ Notes:
44
+ The kernel uses the same x-axis range and size as _generate_test_signal
45
+ to ensure compatible sample rates for convolution and deconvolution.
46
+ """
47
+ x = np.linspace(0, 10, size)
48
+ y = np.exp(-((x - 5) ** 2) / 2) # Centered Gaussian
49
+ y *= 2.0 # Make it unnormalized (sum != 1.0)
50
+ return create_signal("Unnormalized Kernel", x, y)
51
+
52
+
53
+ def _generate_test_image(size: int = 64) -> ImageObj:
54
+ """Generate a simple test image.
55
+
56
+ Args:
57
+ size: The dimension of the square image to generate.
58
+
59
+ Returns:
60
+ An image object.
61
+ """
62
+ data = np.random.rand(size, size)
63
+ return create_image("Test Image", data)
64
+
65
+
66
+ def _generate_unnormalized_image_kernel(size: int = 5) -> ImageObj:
67
+ """Generate an unnormalized Gaussian-like kernel for image processing.
68
+
69
+ Args:
70
+ size: The dimension of the square kernel to generate.
71
+
72
+ Returns:
73
+ An image object representing an unnormalized kernel.
74
+ """
75
+ kernel = np.outer(
76
+ np.exp(-(np.linspace(-2, 2, size) ** 2)),
77
+ np.exp(-(np.linspace(-2, 2, size) ** 2)),
78
+ )
79
+ kernel *= 2.0 # Make it unnormalized (sum != 1.0)
80
+ return create_image("Unnormalized Kernel", kernel)
81
+
82
+
83
+ class TestKernelNormalizationSignal:
84
+ """Test suite for signal kernel normalization in convolution."""
85
+
86
+ @pytest.fixture(autouse=True)
87
+ def setup_and_teardown(self):
88
+ """Store initial option values and restore them after each test."""
89
+ # Store initial values
90
+ initial_auto_normalize = sigima_options.auto_normalize_kernel.get()
91
+
92
+ yield
93
+
94
+ # Restore initial values
95
+ sigima_options.auto_normalize_kernel.set(initial_auto_normalize)
96
+
97
+ def test_signal_convolution_auto_normalization_enabled(self):
98
+ """Test that auto-normalization works correctly for convolution."""
99
+ # Setup: auto-normalization enabled
100
+ sigima_options.auto_normalize_kernel.set(True)
101
+
102
+ signal = _generate_test_signal()
103
+ kernel = _generate_unnormalized_signal_kernel()
104
+
105
+ # Execute with auto-normalization
106
+ result = signal_convolution(signal, kernel)
107
+
108
+ # Verify result exists and has same shape as input
109
+ assert result is not None
110
+ assert result.data is not None
111
+ assert result.data.shape == signal.data.shape
112
+
113
+ def test_signal_convolution_auto_normalization_disabled(self):
114
+ """Test convolution with auto-normalization disabled."""
115
+ # Setup: auto-normalization disabled
116
+ sigima_options.auto_normalize_kernel.set(False)
117
+
118
+ signal = _generate_test_signal()
119
+ kernel = _generate_unnormalized_signal_kernel()
120
+
121
+ # Execute without auto-normalization
122
+ result = signal_convolution(signal, kernel)
123
+
124
+ # Verify result exists and has same shape as input
125
+ assert result is not None
126
+ assert result.data is not None
127
+ assert result.data.shape == signal.data.shape
128
+
129
+ def test_signal_deconvolution_auto_normalization_enabled(self):
130
+ """Test that auto-normalization works correctly for deconvolution."""
131
+ # Setup: auto-normalization enabled
132
+ sigima_options.auto_normalize_kernel.set(True)
133
+
134
+ signal = _generate_test_signal()
135
+ kernel = _generate_unnormalized_signal_kernel()
136
+
137
+ # Execute with auto-normalization
138
+ result = signal_deconvolution(signal, kernel)
139
+
140
+ # Verify result exists and has same shape as input
141
+ assert result is not None
142
+ assert result.data is not None
143
+ assert result.data.shape == signal.data.shape
144
+
145
+ def test_signal_deconvolution_auto_normalization_disabled(self):
146
+ """Test deconvolution with auto-normalization disabled."""
147
+ # Setup: auto-normalization disabled
148
+ sigima_options.auto_normalize_kernel.set(False)
149
+
150
+ signal = _generate_test_signal()
151
+ kernel = _generate_unnormalized_signal_kernel()
152
+
153
+ # Execute without auto-normalization
154
+ result = signal_deconvolution(signal, kernel)
155
+
156
+ # Verify result exists and has same shape as input
157
+ assert result is not None
158
+ assert result.data is not None
159
+ assert result.data.shape == signal.data.shape
160
+
161
+
162
+ class TestKernelNormalizationImage:
163
+ """Test suite for image kernel normalization in convolution and deconvolution."""
164
+
165
+ @pytest.fixture(autouse=True)
166
+ def setup_and_teardown(self):
167
+ """Store initial option values and restore them after each test."""
168
+ # Store initial values
169
+ initial_auto_normalize = sigima_options.auto_normalize_kernel.get()
170
+
171
+ yield
172
+
173
+ # Restore initial values
174
+ sigima_options.auto_normalize_kernel.set(initial_auto_normalize)
175
+
176
+ def test_image_convolution_auto_normalization_enabled(self):
177
+ """Test that auto-normalization works correctly for convolution."""
178
+ # Setup: auto-normalization enabled
179
+ sigima_options.auto_normalize_kernel.set(True)
180
+
181
+ image = _generate_test_image()
182
+ kernel = _generate_unnormalized_image_kernel()
183
+
184
+ # Execute with auto-normalization
185
+ result = image_convolution(image, kernel)
186
+
187
+ # Verify result exists and has same shape as input
188
+ assert result is not None
189
+ assert result.data is not None
190
+ assert result.data.shape == image.data.shape
191
+
192
+ def test_image_convolution_auto_normalization_disabled(self):
193
+ """Test convolution with auto-normalization disabled."""
194
+ # Setup: auto-normalization disabled
195
+ sigima_options.auto_normalize_kernel.set(False)
196
+
197
+ image = _generate_test_image()
198
+ kernel = _generate_unnormalized_image_kernel()
199
+
200
+ # Execute without auto-normalization
201
+ result = image_convolution(image, kernel)
202
+
203
+ # Verify result exists and has same shape as input
204
+ assert result is not None
205
+ assert result.data is not None
206
+ assert result.data.shape == image.data.shape
207
+
208
+ def test_image_deconvolution_auto_normalization_enabled(self):
209
+ """Test that auto-normalization works correctly for deconvolution."""
210
+ # Setup: auto-normalization enabled
211
+ sigima_options.auto_normalize_kernel.set(True)
212
+
213
+ image = _generate_test_image()
214
+ kernel = _generate_unnormalized_image_kernel()
215
+
216
+ # Execute with auto-normalization
217
+ result = image_deconvolution(image, kernel)
218
+
219
+ # Verify result exists and has same shape as input
220
+ assert result is not None
221
+ assert result.data is not None
222
+ assert result.data.shape == image.data.shape
223
+
224
+ def test_image_deconvolution_auto_normalization_disabled(self):
225
+ """Test deconvolution with auto-normalization disabled."""
226
+ # Setup: auto-normalization disabled
227
+ sigima_options.auto_normalize_kernel.set(False)
228
+
229
+ image = _generate_test_image()
230
+ kernel = _generate_unnormalized_image_kernel()
231
+
232
+ # Execute without auto-normalization
233
+ result = image_deconvolution(image, kernel)
234
+
235
+ # Verify result exists and has same shape as input
236
+ assert result is not None
237
+ assert result.data is not None
238
+ assert result.data.shape == image.data.shape
239
+
240
+
241
+ if __name__ == "__main__":
242
+ pytest.main([__file__, "-v"])
@@ -0,0 +1,73 @@
1
+ # Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file.
2
+
3
+ """
4
+ ROI basic unit tests
5
+ """
6
+
7
+ # pylint: disable=invalid-name # Allows short reference names like x, y, ...
8
+ # guitest: show
9
+
10
+ from __future__ import annotations
11
+
12
+ import sigima.objects
13
+ from sigima.tests.data import (
14
+ create_multigaussian_image,
15
+ create_paracetamol_signal,
16
+ create_test_image_rois,
17
+ create_test_signal_rois,
18
+ )
19
+ from sigima.tests.env import execenv
20
+
21
+
22
+ def __conversion_methods(
23
+ roi: sigima.objects.SignalROI | sigima.objects.ImageROI,
24
+ obj: sigima.objects.SignalObj | sigima.objects.ImageObj,
25
+ ) -> None:
26
+ """Test conversion methods for single ROI objects"""
27
+ execenv.print(" test `to_dict` and `from_dict` methods")
28
+ roi_dict = roi.to_dict()
29
+ roi_new = obj.get_roi_class().from_dict(roi_dict)
30
+ assert roi.get_single_roi(0) == roi_new.get_single_roi(0)
31
+
32
+
33
+ def test_signal_roi_creation() -> None:
34
+ """Test signal ROI creation and conversion methods"""
35
+ obj = create_paracetamol_signal()
36
+ for roi in create_test_signal_rois(obj):
37
+ __conversion_methods(roi, obj)
38
+
39
+
40
+ def test_image_roi_creation() -> None:
41
+ """Test image ROI creation and conversion methods"""
42
+ obj = create_multigaussian_image()
43
+ # Update to use new coordinate API instead of setting dx/dy directly
44
+ if obj.data is not None:
45
+ obj.set_uniform_coords(0.035, 0.035)
46
+ for roi in create_test_image_rois(obj):
47
+ __conversion_methods(roi, obj)
48
+
49
+
50
+ def test_image_roi_modification() -> None:
51
+ """Test image ROI modification methods"""
52
+ obj = create_multigaussian_image()
53
+ roi = list(create_test_image_rois(obj))[0]
54
+
55
+ # Set image's ROI
56
+ obj.roi = roi
57
+ assert obj.roi == roi
58
+ nb_single_rois = len(roi.single_rois)
59
+
60
+ # Modify the ROI directly from the image's ROI attribute
61
+ # (for example, we try to remove a single ROI)
62
+ old_single_roi = obj.roi.single_rois.pop(0)
63
+ assert len(obj.roi.single_rois) == nb_single_rois - 1
64
+
65
+ # Add it back
66
+ obj.roi.add_roi(old_single_roi)
67
+ assert len(obj.roi.single_rois) == nb_single_rois
68
+
69
+
70
+ if __name__ == "__main__":
71
+ test_signal_roi_creation()
72
+ test_image_roi_creation()
73
+ test_image_roi_modification()