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
sigima/tests/data.py ADDED
@@ -0,0 +1,998 @@
1
+ # Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file.
2
+
3
+ """
4
+ Test data functions
5
+
6
+ Functions creating test data: curves, images, ...
7
+ """
8
+
9
+ # pylint: disable=invalid-name # Allows short reference names like x, y, ...
10
+ # guitest: skip
11
+
12
+ from __future__ import annotations
13
+
14
+ from typing import Any, Callable, Generator
15
+
16
+ import guidata.dataset as gds
17
+ import numpy as np
18
+
19
+ from sigima.config import _
20
+ from sigima.io import read_image, read_signal
21
+ from sigima.objects import (
22
+ GaussParam,
23
+ GeometryResult,
24
+ ImageDatatypes,
25
+ ImageObj,
26
+ ImageROI,
27
+ ImageTypes,
28
+ NewImageParam,
29
+ NewSignalParam,
30
+ NormalDistribution1DParam,
31
+ NormalDistribution2DParam,
32
+ SignalObj,
33
+ SignalROI,
34
+ SignalTypes,
35
+ TableResult,
36
+ create_image,
37
+ create_image_from_param,
38
+ create_image_roi,
39
+ create_signal_from_param,
40
+ create_signal_parameters,
41
+ create_signal_roi,
42
+ )
43
+ from sigima.objects.image import UniformDistribution2DParam, create_image_parameters
44
+ from sigima.objects.scalar import KindShape
45
+ from sigima.tests.env import execenv
46
+ from sigima.tests.helpers import get_test_fnames
47
+
48
+
49
+ def get_test_signal(filename: str) -> SignalObj:
50
+ """Return test signal
51
+
52
+ Args:
53
+ filename: Filename
54
+
55
+ Returns:
56
+ Signal object
57
+ """
58
+ return read_signal(get_test_fnames(filename)[0])
59
+
60
+
61
+ def get_test_image(filename: str) -> ImageObj:
62
+ """Return test image
63
+
64
+ Args:
65
+ filename: Filename
66
+
67
+ Returns:
68
+ Image object
69
+ """
70
+ return read_image(get_test_fnames(filename)[0])
71
+
72
+
73
+ def iterate_signal_creation(
74
+ size: int = 500,
75
+ non_zero: bool = False,
76
+ verbose: bool = True,
77
+ preproc: Callable[[NewSignalParam], None] | None = None,
78
+ postproc: Callable[[SignalObj], None] | None = None,
79
+ ) -> Generator[SignalObj, None, None]:
80
+ """Iterate over all possible signals created from parameters
81
+
82
+ Args:
83
+ size: Size of the data. Defaults to 500.
84
+ non_zero: If True, skip zero signals. Defaults to False.
85
+ verbose: If True, print the signal types being created. Defaults to True.
86
+ preproc: Callback function to preprocess the signal parameters set before
87
+ creation. Defaults to None.
88
+ postproc: Callback function to postprocess the signal object after creation.
89
+ Defaults to None.
90
+
91
+ Yields:
92
+ Signal object created from parameters.
93
+ """
94
+ if verbose:
95
+ execenv.print(
96
+ f" Iterating over {len(SignalTypes)} signal types "
97
+ f"(size={size}, non_zero={non_zero}):"
98
+ )
99
+ for stype in SignalTypes:
100
+ if non_zero and stype in (SignalTypes.ZERO,):
101
+ continue
102
+ if verbose:
103
+ execenv.print(f" {stype.value}")
104
+ param = create_signal_parameters(stype, size=size)
105
+ if preproc is not None:
106
+ preproc(param)
107
+ signal = create_signal_from_param(param)
108
+ if postproc is not None:
109
+ postproc(signal, stype)
110
+ yield signal
111
+
112
+
113
+ def create_paracetamol_signal(
114
+ size: int | None = None, title: str | None = None
115
+ ) -> SignalObj:
116
+ """Create test signal (Paracetamol molecule spectrum)
117
+
118
+ Args:
119
+ size: Size of the data. Defaults to None.
120
+ title: Title of the signal. Defaults to None.
121
+
122
+ Returns:
123
+ Signal object
124
+ """
125
+ obj = read_signal(get_test_fnames("paracetamol.txt")[0])
126
+ if title is not None:
127
+ obj.title = title
128
+ if size is not None:
129
+ x0, y0 = obj.xydata
130
+ x1 = np.linspace(x0[0], x0[-1], size)
131
+ y1 = np.interp(x1, x0, y0)
132
+ obj.set_xydata(x1, y1)
133
+ return obj
134
+
135
+
136
+ def add_gaussian_noise_to_signal(
137
+ signal: SignalObj, p: NormalDistribution1DParam | None = None
138
+ ) -> None:
139
+ """Add Gaussian (Normal-law) random noise to data
140
+
141
+ Args:
142
+ signal: Signal object
143
+ p: Gaussian noise parameters.
144
+ """
145
+ if p is None:
146
+ p = NormalDistribution1DParam()
147
+ rng = np.random.default_rng(p.seed)
148
+ signal.data += rng.normal(p.mu, p.sigma, size=signal.data.shape)
149
+ signal.title = f"GaussNoise({signal.title}, µ={p.mu}, σ={p.sigma})"
150
+
151
+
152
+ def create_noisy_signal(
153
+ noiseparam: NormalDistribution1DParam | None = None,
154
+ param: NewSignalParam | None = None,
155
+ title: str | None = None,
156
+ noised: bool | None = None,
157
+ ) -> SignalObj:
158
+ """Create curve data, optionally noised
159
+
160
+ Args:
161
+ noiseparam: Noise parameters. Default: None: No noise
162
+ newparam: New signal parameters.
163
+ Default: Gaussian, size=500, xmin=-10, xmax=10,
164
+ a=1.0, sigma=1.0, mu=0.0, ymin=0.0
165
+ title: Title of the signal. Default: None
166
+ If not None, overrides the title in newparam
167
+ noised: If True, add noise to the signal.
168
+ Default: None (use noiseparam)
169
+ If True, eventually creates a new noiseparam if None
170
+
171
+ Returns:
172
+ Signal object
173
+ """
174
+ if param is None:
175
+ param = GaussParam()
176
+ if title is not None:
177
+ param.title = title
178
+ param.title = "Test signal (noisy)" if param.title is None else param.title
179
+ if noised is not None and noised and noiseparam is None:
180
+ noiseparam = NormalDistribution1DParam()
181
+ noiseparam.sigma = 5.0
182
+ sig = create_signal_from_param(param)
183
+ if noiseparam is not None:
184
+ add_gaussian_noise_to_signal(sig, noiseparam)
185
+ return sig
186
+
187
+
188
+ def create_periodic_signal(
189
+ stype: SignalTypes,
190
+ freq: float = 50.0,
191
+ size: int = 10000,
192
+ xmin: float = -10.0,
193
+ xmax: float = 10.0,
194
+ a: float = 1.0,
195
+ ) -> SignalObj:
196
+ """Create a periodic signal
197
+
198
+ Args:
199
+ stype: Type of the signal (shape of the periodic signal).
200
+ freq: Frequency of the signal. Defaults to 50.0.
201
+ size: Size of the signal. Defaults to 10000.
202
+ xmin: Minimum value of the signal. Defaults to None.
203
+ xmax: Maximum value of the signal. Defaults to None.
204
+ a: Amplitude of the signal. Defaults to 1.0.
205
+
206
+ Returns:
207
+ Signal object
208
+ """
209
+ p = create_signal_parameters(stype, size=size, xmin=xmin, xmax=xmax, freq=freq, a=a)
210
+ return create_signal_from_param(p)
211
+
212
+
213
+ def create_2d_steps_data(size: int, width: int, dtype: np.dtype) -> np.ndarray:
214
+ """Creating 2D steps data for testing purpose
215
+
216
+ Args:
217
+ size: Size of the data
218
+ width: Width of the steps
219
+ dtype: Data type
220
+
221
+ Returns:
222
+ 2D data
223
+ """
224
+ data = np.zeros((size, size), dtype=dtype)
225
+ value = 1
226
+ for col in range(0, size - width + 1, width):
227
+ data[:, col : col + width] = np.array(value).astype(dtype)
228
+ value *= 10
229
+ data2 = np.zeros_like(data)
230
+ value = 1
231
+ for row in range(0, size - width + 1, width):
232
+ data2[row : row + width, :] = np.array(value).astype(dtype)
233
+ value *= 10
234
+ data += data2
235
+ return data
236
+
237
+
238
+ def create_2d_random(
239
+ size: int, dtype: np.dtype, level: float = 0.1, seed: int = 1
240
+ ) -> np.ndarray:
241
+ """Creating 2D Uniform-law random image
242
+
243
+ Args:
244
+ size: Size of the data
245
+ dtype: Data type
246
+ level: Level of the random noise. Defaults to 0.1.
247
+ seed: Seed for random number generator. Defaults to 1.
248
+
249
+ Returns:
250
+ 2D data
251
+ """
252
+ rng = np.random.default_rng(seed)
253
+ amp = (np.iinfo(dtype).max if np.issubdtype(dtype, np.integer) else 1.0) * level
254
+ return np.array(rng.random((size, size)) * amp, dtype=dtype)
255
+
256
+
257
+ def create_2d_gaussian(
258
+ size: int,
259
+ dtype: np.dtype,
260
+ x0: float = 0,
261
+ y0: float = 0,
262
+ mu: float = 0.0,
263
+ sigma: float = 2.0,
264
+ amp: float | None = None,
265
+ ) -> np.ndarray:
266
+ """Creating 2D Gaussian (-10 <= x <= 10 and -10 <= y <= 10)
267
+
268
+ Args:
269
+ size: Size of the data
270
+ dtype: Data type
271
+ x0: x0. Defaults to 0.
272
+ y0: y0. Defaults to 0.
273
+ mu: mu. Defaults to 0.0.
274
+ sigma: sigma. Defaults to 2.0.
275
+ amp: Amplitude. Defaults to None.
276
+
277
+ Returns:
278
+ 2D data
279
+ """
280
+ xydata = np.linspace(-10, 10, size)
281
+ x, y = np.meshgrid(xydata, xydata)
282
+ if amp is None:
283
+ try:
284
+ amp = np.iinfo(dtype).max * 0.5
285
+ except ValueError:
286
+ # dtype is not integer
287
+ amp = 1.0
288
+ return np.array(
289
+ amp
290
+ * np.exp(
291
+ -((np.sqrt((x - x0) ** 2 + (y - y0) ** 2) - mu) ** 2) / (2.0 * sigma**2)
292
+ ),
293
+ dtype=dtype,
294
+ )
295
+
296
+
297
+ def get_laser_spot_data() -> list[np.ndarray]:
298
+ """Return a list of NumPy arrays containing images which are relevant for
299
+ testing laser spot image processing features
300
+
301
+ Returns:
302
+ List of NumPy arrays
303
+ """
304
+ znoise = create_2d_random(2000, np.uint16)
305
+ zgauss = create_2d_gaussian(2000, np.uint16, x0=2.0, y0=-3.0)
306
+ return [zgauss + znoise] + [
307
+ read_image(fname).data for fname in get_test_fnames("*.scor-data")
308
+ ]
309
+
310
+
311
+ class PeakDataParam(gds.DataSet):
312
+ """Peak data test image parameters"""
313
+
314
+ size = gds.IntItem(_("Size"), default=2000, min=1)
315
+ n_points = gds.IntItem(_("Number"), default=4, min=1, help=_("Number of points"))
316
+ sigma_gauss2d = gds.FloatItem(
317
+ "σ<sub>Gauss2D</sub>", default=0.06, help=_("Sigma of the 2D Gaussian")
318
+ )
319
+ amp_gauss2d = gds.IntItem(
320
+ "A<sub>Gauss2D</sub>", default=1900, help=_("Amplitude of the 2D Gaussian")
321
+ )
322
+ mu_noise = gds.IntItem(
323
+ "μ<sub>noise</sub>", default=845, help=_("Mean of the Gaussian distribution")
324
+ )
325
+ sigma_noise = gds.IntItem(
326
+ "σ<sub>noise</sub>",
327
+ default=25,
328
+ help=_("Standard deviation of the Gaussian distribution"),
329
+ )
330
+ dx0 = gds.FloatItem("dx0", default=0.0)
331
+ dy0 = gds.FloatItem("dy0", default=0.0)
332
+ att = gds.FloatItem(_("Attenuation"), default=1.0)
333
+
334
+
335
+ def get_peak2d_data(
336
+ p: PeakDataParam | None = None, seed: int | None = None, multi: bool = False
337
+ ) -> tuple[np.ndarray, np.ndarray]:
338
+ """Return a list of NumPy arrays containing images which are relevant for
339
+ testing 2D peak detection or similar image processing features
340
+
341
+ Args:
342
+ p: Peak data test image parameters. Defaults to None.
343
+ seed: Seed for random number generator. Defaults to None.
344
+ multi: If True, multiple peaks are generated. Defaults to False.
345
+
346
+ Returns:
347
+ A tuple containing the image data and coordinates of the peaks.
348
+ """
349
+ if p is None:
350
+ p = PeakDataParam()
351
+ delta = 0.1
352
+ rng = np.random.default_rng(seed)
353
+ coords_phys = (rng.random((p.n_points, 2)) - 0.5) * 10 * (1 - delta)
354
+ data = rng.normal(p.mu_noise, p.sigma_noise, size=(p.size, p.size))
355
+ multi_nb = 2 if multi else 1
356
+ for x0, y0 in coords_phys:
357
+ for idx in range(multi_nb):
358
+ if idx != 0:
359
+ p.dx0 = 0.08 + rng.random() * 0.08
360
+ p.dy0 = 0.08 + rng.random() * 0.08
361
+ p.att = 0.2 + rng.random() * 0.8
362
+ data += create_2d_gaussian(
363
+ p.size,
364
+ np.uint16,
365
+ x0=x0 + p.dx0,
366
+ y0=y0 + p.dy0,
367
+ sigma=p.sigma_gauss2d,
368
+ amp=p.amp_gauss2d / multi_nb * p.att,
369
+ )
370
+ # Convert coordinates to indices
371
+ coords = []
372
+ for x0, y0 in coords_phys:
373
+ x = (x0 + 10) / 20 * p.size
374
+ y = (y0 + 10) / 20 * p.size
375
+ if 0 <= x < p.size and 0 <= y < p.size:
376
+ coords.append((x, y))
377
+ return data, np.array(coords)
378
+
379
+
380
+ CLASS_NAME = "class_name"
381
+
382
+
383
+ def create_test_signal_rois(
384
+ obj: SignalObj,
385
+ ) -> Generator[SignalROI, None, None]:
386
+ """Create test signal ROIs (sigima.objects.SignalROI test object)
387
+
388
+ Yields:
389
+ SignalROI object
390
+ """
391
+ # ROI coordinates: for each ROI type, the coordinates are given for indices=True
392
+ # and indices=False (physical coordinates)
393
+ roi_coords = {
394
+ "segment": {
395
+ CLASS_NAME: "SegmentROI",
396
+ True: [50, 100], # indices [x0, dx]
397
+ False: [7.5, 10.0], # physical
398
+ },
399
+ }
400
+ for indices in (True, False):
401
+ execenv.print("indices:", indices)
402
+
403
+ for geometry, coords in roi_coords.items():
404
+ execenv.print(" geometry:", geometry)
405
+
406
+ roi = create_signal_roi(coords[indices], indices=indices)
407
+
408
+ sroi = roi.get_single_roi(0)
409
+ assert sroi.__class__.__name__ == coords[CLASS_NAME]
410
+
411
+ cds_ind = [int(val) for val in sroi.get_indices_coords(obj)]
412
+ assert cds_ind == coords[True]
413
+
414
+ cds_phys = [float(val) for val in sroi.get_physical_coords(obj)]
415
+ assert cds_phys == coords[False]
416
+
417
+ execenv.print(" get_physical_coords:", cds_phys)
418
+ execenv.print(" get_indices_coords: ", cds_ind)
419
+
420
+ yield roi
421
+
422
+
423
+ def __idx_to_phys(obj: ImageObj, idx_coords: list[int]) -> list[float]:
424
+ """Convert index coordinates to physical coordinates.
425
+
426
+ Args:
427
+ obj: Image object
428
+ idx_coords: List of index coordinates [x0, y0, dx, dy].
429
+
430
+ Returns:
431
+ List of physical coordinates [x0, y0, dx, dy].
432
+ """
433
+ coords_array = np.array(idx_coords, dtype=float)
434
+ coords_array[::2] = coords_array[::2] * obj.dx + obj.x0
435
+ coords_array[1::2] = coords_array[1::2] * obj.dy + obj.y0
436
+ return coords_array.tolist()
437
+
438
+
439
+ def create_test_image_rois(obj: ImageObj) -> Generator[ImageROI, None, None]:
440
+ """Create test image ROIs (sigima.objects.ImageROI test object)
441
+
442
+ Yields:
443
+ ImageROI object
444
+ """
445
+ # ROI coordinates: for each ROI type, the coordinates are given for indices=True
446
+ # and indices=False (physical coordinates)
447
+ rect_idx = [500, 750, 1000, 1250] # [x0, y0, dx, dy]
448
+ circ_idx = [1500, 1500, 500] # [x0, y0, radius]
449
+ poly_idx = [450, 150, 1300, 350, 1250, 950, 400, 1350] # [x0, y0, ...]
450
+ roi_coords = {
451
+ "rectangle": {
452
+ CLASS_NAME: "RectangularROI",
453
+ True: rect_idx, # indices [x0, y0, dx, dy]
454
+ False: __idx_to_phys(obj, rect_idx), # physical
455
+ },
456
+ "circle": {
457
+ CLASS_NAME: "CircularROI",
458
+ True: circ_idx, # indices [x0, y0, radius]
459
+ False: __idx_to_phys(obj, circ_idx), # physical
460
+ },
461
+ "polygon": {
462
+ CLASS_NAME: "PolygonalROI",
463
+ True: poly_idx, # indices [x0, y0, ...]
464
+ False: __idx_to_phys(obj, poly_idx), # physical
465
+ },
466
+ }
467
+ for indices in (True, False):
468
+ execenv.print("indices:", indices)
469
+
470
+ for geometry, coords in roi_coords.items():
471
+ execenv.print(" geometry:", geometry)
472
+
473
+ roi = create_image_roi(geometry, coords[indices], indices=indices)
474
+
475
+ sroi = roi.get_single_roi(0)
476
+ assert sroi.__class__.__name__ == coords[CLASS_NAME]
477
+
478
+ bbox_phys = [float(val) for val in sroi.get_bounding_box(obj)]
479
+ if geometry in ("rectangle", "circle"):
480
+ # pylint: disable=unbalanced-tuple-unpacking
481
+ x0, y0, x1, y1 = obj.physical_to_indices(bbox_phys)
482
+ if geometry == "rectangle":
483
+ coords_from_bbox = [int(xy) for xy in [x0, y0, x1 - x0, y1 - y0]]
484
+ else:
485
+ coords_from_bbox = [
486
+ int(xy) for xy in [(x0 + x1) / 2, (y0 + y1) / 2, (x1 - x0) / 2]
487
+ ]
488
+ assert coords_from_bbox == coords[True]
489
+
490
+ cds_phys = np.array(sroi.get_physical_coords(obj), float)
491
+ assert all(np.isclose(cds_phys, coords[False]))
492
+ cds_ind = np.rint(sroi.get_indices_coords(obj))
493
+ assert all(np.isclose(cds_ind, coords[True]))
494
+
495
+ execenv.print(" get_bounding_box: ", bbox_phys)
496
+ execenv.print(" get_physical_coords:", cds_phys)
497
+ execenv.print(" get_indices_coords: ", cds_ind)
498
+
499
+ yield roi
500
+
501
+
502
+ def __iterate_image_datatypes(
503
+ itype: ImageTypes,
504
+ data_size: int,
505
+ verbose: bool,
506
+ preproc: Callable[[NewImageParam], None] | None = None,
507
+ postproc: Callable[[ImageObj, ImageTypes], None] | None = None,
508
+ ) -> Generator[ImageObj | None, None, None]:
509
+ """Iterate over all datatypes for a given image type
510
+
511
+ Args:
512
+ itype: Image type
513
+ data_size: Size of the data
514
+ verbose: If True, print the image types being created
515
+ preproc: Callback function to preprocess the image parameters set before
516
+ creation. Defaults to None.
517
+ postproc: Callback function to postprocess the image object after creation.
518
+ Defaults to None.
519
+
520
+ Yields:
521
+ Image object created from parameters
522
+ """
523
+ for idtype in ImageDatatypes:
524
+ if verbose:
525
+ execenv.print(f" {idtype.value}")
526
+ param = create_image_parameters(
527
+ itype, idtype=idtype, width=data_size, height=data_size
528
+ )
529
+ if itype == ImageTypes.RAMP and idtype != ImageDatatypes.FLOAT64:
530
+ continue # Testing only float64 for ramp
531
+ if itype == ImageTypes.UNIFORM_DISTRIBUTION:
532
+ assert isinstance(param, UniformDistribution2DParam)
533
+ param.set_from_datatype(idtype.value)
534
+ elif itype == ImageTypes.NORMAL_DISTRIBUTION:
535
+ assert isinstance(param, NormalDistribution2DParam)
536
+ param.set_from_datatype(idtype.value)
537
+ if preproc is not None:
538
+ preproc(param)
539
+ image = create_image_from_param(param)
540
+ if postproc is not None:
541
+ postproc(image, itype)
542
+ yield image
543
+
544
+
545
+ def iterate_image_creation(
546
+ size: int = 500,
547
+ non_zero: bool = False,
548
+ verbose: bool = True,
549
+ preproc: Callable[[NewImageParam], None] | None = None,
550
+ postproc: Callable[[ImageObj, ImageTypes], None] | None = None,
551
+ ) -> Generator[ImageObj, None, None]:
552
+ """Iterate over all possible images created from parameters
553
+
554
+ Args:
555
+ size: Size of the data. Defaults to 500.
556
+ non_zero: If True, skip empty and zero images. Defaults to False.
557
+ verbose: If True, print the image types being created. Defaults to True.
558
+ preproc: Callback function to preprocess the image parameters set before
559
+ creation. Defaults to None.
560
+ postproc: Callback function to postprocess the image object after creation.
561
+
562
+ Yields:
563
+ Image object created from parameters.
564
+ """
565
+ if verbose:
566
+ execenv.print(
567
+ f" Iterating over {len(ImageTypes)} image types "
568
+ f"(size={size}, non_zero={non_zero}):"
569
+ )
570
+ for itype in ImageTypes:
571
+ if non_zero and itype == ImageTypes.ZEROS:
572
+ continue
573
+ if verbose:
574
+ execenv.print(f" {itype.value}")
575
+ yield from __iterate_image_datatypes(itype, size, verbose, preproc, postproc)
576
+
577
+
578
+ def __set_default_size_dtype(
579
+ p: NewImageParam | None = None,
580
+ ) -> NewImageParam:
581
+ """Set default shape and dtype
582
+
583
+ Args:
584
+ p: Image parameters. Defaults to None. If None, a new object is created.
585
+
586
+ Returns:
587
+ Image parameters
588
+ """
589
+ if p is None:
590
+ p = NewImageParam()
591
+ p.height = 2000 if p.height is None else p.height
592
+ p.width = 2000 if p.width is None else p.width
593
+ p.dtype = ImageDatatypes.UINT16 if p.dtype is None else p.dtype
594
+ return p
595
+
596
+
597
+ def create_checkerboard(p: NewImageParam | None = None, num_checkers=8) -> ImageObj:
598
+ """Generate a checkerboard pattern
599
+
600
+ Args:
601
+ p: Image parameters. Defaults to None.
602
+ num_checkers: Number of checkers. Defaults to 8.
603
+ """
604
+ p = __set_default_size_dtype(p)
605
+ p.title = "Test image (checkerboard)" if p.title is None else p.title
606
+ obj = create_image_from_param(p)
607
+ re = np.r_[num_checkers * [0, 1]] # one row of the checkerboard
608
+ board = np.vstack(num_checkers * (re, re ^ 1)) # build the checkerboard
609
+ board = np.kron(
610
+ board, np.ones((p.height // num_checkers, p.height // num_checkers))
611
+ ) # scale up the board
612
+ obj.data = board
613
+ return obj
614
+
615
+
616
+ def create_2dstep_image(p: NewImageParam | None = None) -> ImageObj:
617
+ """Creating 2D step image
618
+
619
+ Args:
620
+ p: Image parameters. Defaults to None.
621
+
622
+ Returns:
623
+ Image object
624
+ """
625
+ p = __set_default_size_dtype(p)
626
+ p.title = "Test image (2D step)" if p.title is None else p.title
627
+ obj = create_image_from_param(p)
628
+ obj.data = create_2d_steps_data(p.height, p.height // 10, p.dtype.to_numpy_dtype())
629
+ return obj
630
+
631
+
632
+ class RingParam(gds.DataSet):
633
+ """Parameters for creating a ring image"""
634
+
635
+ image_size = gds.IntItem(_("Size"), default=1000)
636
+ xc = gds.IntItem(_("X<sub>center</sub>"), default=500)
637
+ yc = gds.IntItem(_("Y<sub>center</sub>"), default=500)
638
+ thickness = gds.IntItem(_("Thickness"), default=10)
639
+ radius = gds.IntItem(_("Radius"), default=250)
640
+ intensity = gds.IntItem(_("Intensity"), default=1000)
641
+
642
+
643
+ def create_ring_data(
644
+ image_size: int, xc: int, yc: int, thickness: int, radius: int, intensity: int
645
+ ) -> np.ndarray:
646
+ """Create 2D ring data
647
+
648
+ Args:
649
+ image_size: Size of the image
650
+ xc: Center x coordinate
651
+ yc: Center y coordinate
652
+ thickness: Thickness of the ring
653
+ radius: Radius of the ring
654
+ intensity: Intensity of the ring
655
+
656
+ Returns:
657
+ 2D data
658
+ """
659
+ data = np.zeros((image_size, image_size), dtype=np.uint16)
660
+ for x in range(data.shape[0]):
661
+ for y in range(data.shape[1]):
662
+ if (x - xc) ** 2 + (y - yc) ** 2 >= (radius - thickness) ** 2 and (
663
+ x - xc
664
+ ) ** 2 + (y - yc) ** 2 <= (radius + thickness) ** 2:
665
+ data[x, y] = intensity
666
+ return data
667
+
668
+
669
+ def create_ring_image(p: RingParam | None = None) -> ImageObj:
670
+ """Creating 2D ring image
671
+
672
+ Args:
673
+ p: Ring image parameters. Defaults to None.
674
+
675
+ Returns:
676
+ Image object
677
+ """
678
+ if p is None:
679
+ p = RingParam()
680
+ obj = create_image(
681
+ f"Ring(size={p.image_size},xc={p.xc},yc={p.yc},thickness={p.thickness},"
682
+ f"radius={p.radius},intensity={p.intensity})"
683
+ )
684
+ obj.data = create_ring_data(
685
+ p.image_size,
686
+ p.xc,
687
+ p.yc,
688
+ p.thickness,
689
+ p.radius,
690
+ p.intensity,
691
+ )
692
+ return obj
693
+
694
+
695
+ def create_peak_image(p: NewImageParam | None = None) -> ImageObj:
696
+ """Creating image with bright peaks
697
+
698
+ Args:
699
+ p: Image parameters. Defaults to None
700
+
701
+ Returns:
702
+ Image object
703
+ """
704
+ p = __set_default_size_dtype(p)
705
+ p.title = "Test image (2D peaks)" if p.title is None else p.title
706
+ obj = create_image_from_param(p)
707
+ param = PeakDataParam()
708
+ if p.height is not None and p.width is not None:
709
+ param.size = max(p.height, p.width)
710
+ obj.data, coords = get_peak2d_data(param)
711
+ obj.metadata["peak_coords"] = coords
712
+ return obj
713
+
714
+
715
+ def create_sincos_image(p: NewImageParam | None = None) -> ImageObj:
716
+ """Creating test image (sin(x)+cos(y))
717
+
718
+ Args:
719
+ p: Image parameters. Defaults to None
720
+
721
+ Returns:
722
+ Image object
723
+ """
724
+ p = __set_default_size_dtype(p)
725
+ p.title = "Test image (sin(x)+cos(y))" if p.title is None else p.title
726
+ x, y = np.meshgrid(np.linspace(0, 10, p.width), np.linspace(0, 10, p.height))
727
+ raw_data = 0.5 * (np.sin(x) + np.cos(y)) + 0.5
728
+ obj = create_image_from_param(p)
729
+ if np.issubdtype(p.dtype.to_numpy_dtype(), np.floating):
730
+ obj.data = raw_data
731
+ return obj
732
+ dmin = np.iinfo(p.dtype.to_numpy_dtype()).min * 0.95
733
+ dmax = np.iinfo(p.dtype.to_numpy_dtype()).max * 0.95
734
+ obj.data = np.array(raw_data * (dmax - dmin) + dmin, dtype=p.dtype.to_numpy_dtype())
735
+ return obj
736
+
737
+
738
+ def add_annotations_from_file(obj: SignalObj | ImageObj, filename: str) -> None:
739
+ """Add annotations from a file to a Signal or Image object
740
+
741
+ Args:
742
+ obj: Signal or Image object to which annotations will be added
743
+ filename: Filename containing annotations
744
+ """
745
+ with open(filename, "r", encoding="utf-8") as file:
746
+ json_str = file.read()
747
+ if obj.annotations:
748
+ json_str = obj.annotations[:-1] + "," + json_str[1:]
749
+ obj.annotations = json_str
750
+
751
+
752
+ def create_noisy_gaussian_image(
753
+ p: NewImageParam | None = None,
754
+ center: tuple[float, float] | None = None,
755
+ level: float = 0.1,
756
+ add_annotations: bool = False,
757
+ ) -> ImageObj:
758
+ """Create test image (2D noisy gaussian)
759
+
760
+ Args:
761
+ p: Image parameters. Defaults to None.
762
+ center: Center of the gaussian. Defaults to None.
763
+ level: Level of the random noise. Defaults to 0.1.
764
+ add_annotations: If True, add annotations. Defaults to False.
765
+
766
+ Returns:
767
+ Image object
768
+ """
769
+ p = __set_default_size_dtype(p)
770
+ p.title = "Test image (noisy 2D Gaussian)" if p.title is None else p.title
771
+ obj = create_image_from_param(p)
772
+ if center is None:
773
+ # Default center
774
+ x0, y0 = 2.0, 3.0
775
+ else:
776
+ x0, y0 = center
777
+ obj.data = create_2d_gaussian(p.width, dtype=p.dtype.to_numpy_dtype(), x0=x0, y0=y0)
778
+ if level:
779
+ obj.data += create_2d_random(p.width, p.dtype.to_numpy_dtype(), level)
780
+ if add_annotations:
781
+ add_annotations_from_file(obj, get_test_fnames("annotations.json")[0])
782
+ return obj
783
+
784
+
785
+ def iterate_noisy_images(size: int = 128) -> Generator[ImageObj, None, None]:
786
+ """Iterate over all possible noisy Gaussian images in different datatypes.
787
+
788
+ Args:
789
+ size: Size of the image. Defaults to 128.
790
+ """
791
+ for dtype in ImageDatatypes:
792
+ param = NewImageParam.create(dtype=dtype, height=size, width=size)
793
+ yield create_noisy_gaussian_image(param, level=0.0)
794
+
795
+
796
+ def iterate_noisy_image_couples(
797
+ size: int = 128,
798
+ ) -> Generator[tuple[ImageObj, ImageObj], None, None]:
799
+ """Iterate over all possible pairs of noisy Gaussian images in different datatypes.
800
+
801
+ Args:
802
+ size: Size of the images. Defaults to 128.
803
+ """
804
+ for dtype1 in ImageDatatypes:
805
+ param1 = NewImageParam.create(dtype=dtype1, height=size, width=size)
806
+ ima1 = create_noisy_gaussian_image(param1, level=0.0)
807
+ for dtype2 in ImageDatatypes:
808
+ param2 = NewImageParam.create(dtype=dtype2, height=size, width=size)
809
+ ima2 = create_noisy_gaussian_image(param2, level=0.0)
810
+ yield ima1, ima2
811
+
812
+
813
+ def create_n_images(n: int = 100) -> list[ImageObj]:
814
+ """Create a list of N different images for testing."""
815
+ images = []
816
+ for i in range(n):
817
+ param = NewImageParam.create(
818
+ dtype=ImageDatatypes.FLOAT32,
819
+ height=128,
820
+ width=128,
821
+ )
822
+ img = create_noisy_gaussian_image(param, level=(i + 1) * 0.1)
823
+ images.append(img)
824
+ return images
825
+
826
+
827
+ class GridOfGaussianImages(gds.DataSet):
828
+ """Grid of Gaussian images"""
829
+
830
+ nrows = gds.IntItem(_("Number of rows"), default=3, min=1)
831
+ ncols = gds.IntItem(_("Number of columns"), default=3, min=1)
832
+
833
+
834
+ def create_grid_of_gaussian_images(p: GridOfGaussianImages | None = None) -> ImageObj:
835
+ """Create a grid image with multiple noisy Gaussian images.
836
+
837
+ Args:
838
+ p: Grid of Gaussian images parameters. Defaults to None.
839
+
840
+ Returns:
841
+ Image object containing the grid of images.
842
+ """
843
+ p = p or GridOfGaussianImages()
844
+ size = 512
845
+ grid_data = np.zeros((size, size), dtype=np.float32)
846
+ xmin, xmax = -10.0, 10.0
847
+ ymin, ymax = -10.0, 10.0
848
+ xstep = (xmax - xmin) / p.ncols
849
+ ystep = (ymax - ymin) / p.nrows
850
+ sigma = 0.1
851
+ amp = 1.0
852
+ for j in range(p.ncols):
853
+ for i in range(p.nrows):
854
+ grid_data += create_2d_gaussian(
855
+ size,
856
+ dtype=float,
857
+ x0=(i + 0.5) * xstep + xmin,
858
+ y0=(j + 0.5) * ystep + ymin,
859
+ sigma=sigma,
860
+ amp=amp,
861
+ )
862
+ sigma += 0.05
863
+ amp *= 1.1
864
+ return create_image("Grid Image", grid_data)
865
+
866
+
867
+ def create_multigaussian_image(p: NewImageParam | None = None) -> ImageObj:
868
+ """Create test image (multiple 2D-gaussian peaks)
869
+
870
+ Args:
871
+ p: Image parameters. Defaults to None.
872
+
873
+ Returns:
874
+ Image object
875
+ """
876
+ p = __set_default_size_dtype(p)
877
+ p.title = "Test image (multi-2D-gaussian)" if p.title is None else p.title
878
+ obj = create_image_from_param(p)
879
+ obj.data = (
880
+ create_2d_gaussian(p.width, p.dtype.to_numpy_dtype(), x0=0.5, y0=3.0)
881
+ + create_2d_gaussian(
882
+ p.width, p.dtype.to_numpy_dtype(), x0=-1.0, y0=-1.0, sigma=1.0
883
+ )
884
+ + create_2d_gaussian(p.width, p.dtype.to_numpy_dtype(), x0=7.0, y0=8.0)
885
+ )
886
+ return obj
887
+
888
+
889
+ def create_annotated_image(title: str | None = None) -> ImageObj:
890
+ """Create test image with annotations
891
+
892
+ Returns:
893
+ Image object
894
+ """
895
+ data = create_2d_gaussian(600, np.uint16, x0=2.0, y0=3.0)
896
+ title = "Test image (with metadata)" if title is None else title
897
+ image = create_image(title, data)
898
+ add_annotations_from_file(image, get_test_fnames("annotations.json")[0])
899
+ return image
900
+
901
+
902
+ def create_test_metadata() -> dict[str, Any]:
903
+ """Create test metadata for signals or images.
904
+
905
+ Returns:
906
+ Metadata dictionary
907
+ """
908
+ metadata = {}
909
+ metadata["tata"] = {
910
+ "lkl": 2,
911
+ "tototo": 3,
912
+ "arrdata": np.array([0, 1, 2, 3, 4, 5]),
913
+ "zzzz": "lklk",
914
+ "bool": True,
915
+ "float": 1.234,
916
+ "list": [1, 2.5, 3, "str", False, 5],
917
+ "d": {
918
+ "lkl": 2,
919
+ "tototo": 3,
920
+ "zzzz": "lklk",
921
+ "bool": True,
922
+ "float": 1.234,
923
+ "list": [
924
+ 1,
925
+ 2.5,
926
+ 3,
927
+ "str",
928
+ False,
929
+ 5,
930
+ {"lkl": 2, "l": [1, 2, 3]},
931
+ ],
932
+ },
933
+ }
934
+ metadata["toto"] = [
935
+ np.array([[1, 2], [-3, 0]]),
936
+ np.array([[1, 2], [-3, 0], [99, 241]]),
937
+ ]
938
+ metadata["array"] = np.array([-5, -4, -3, -2, -1])
939
+ return metadata
940
+
941
+
942
+ def create_test_signal_with_metadata() -> SignalObj:
943
+ """Create a test signal with complex metadata for serialization testing.
944
+
945
+ Returns:
946
+ Signal object with metadata containing various data types.
947
+ """
948
+ signal = create_paracetamol_signal()
949
+ signal.metadata = create_test_metadata()
950
+ return signal
951
+
952
+
953
+ def create_test_image_with_metadata() -> ImageObj:
954
+ """Create a test image with complex metadata for serialization testing.
955
+
956
+ Returns:
957
+ Image object with metadata containing various data types.
958
+ """
959
+ data = get_test_image("flower.npy").data
960
+ image = create_image("Test image with peaks", data)
961
+ image.metadata = create_test_metadata()
962
+ return image
963
+
964
+
965
+ def generate_geometry_results() -> Generator[GeometryResult, None, None]:
966
+ """Create test geometry results.
967
+
968
+ Yields:
969
+ GeometryResult object
970
+ """
971
+ for index, (shape, coords) in enumerate(
972
+ (
973
+ (KindShape.CIRCLE, [[250, 250, 200]]),
974
+ (KindShape.RECTANGLE, [[300, 200, 150, 250]]),
975
+ (KindShape.SEGMENT, [[50, 250, 400, 400]]),
976
+ (KindShape.POINT, [[500, 500]]),
977
+ (
978
+ KindShape.POLYGON,
979
+ [[100, 100, 150, 100, 150, 150, 200, 100, 250, 50]],
980
+ ),
981
+ )
982
+ ):
983
+ yield GeometryResult(f"GeomResult{index}", shape, coords=np.asarray(coords))
984
+
985
+
986
+ def generate_table_results() -> Generator[TableResult, None, None]:
987
+ """Create test table results.
988
+
989
+ Yields:
990
+ TableResult object
991
+ """
992
+ for index, (names, data) in enumerate(
993
+ (
994
+ (["A", "B", "C", "D"], [["banana", 2.5, -30909, 1.0]]),
995
+ (["P1", "P2", "P3", "P4"], [["apple", 1.232325, -9, 0]]),
996
+ )
997
+ ):
998
+ yield TableResult(f"TestProperties{index}", "test", names, data=data)