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,59 @@
1
+ """
2
+ Tools (:mod:`sigima.tools`)
3
+ ---------------------------
4
+
5
+ This package contains functions operating on NumPy arrays that are intended to be
6
+ used in Sigima computation functions. These functions are complementary to the
7
+ algorithms provided by external libraries such as SciPy, NumPy, and scikit-image.
8
+
9
+ Even though these functions are primarily designed to be used in the Sigima pipeline,
10
+ they can also be used independently. They provide a wide range of features but are
11
+ not exhaustive due to the vast number of algorithms already available in the
12
+ scientific Python ecosystem.
13
+
14
+ .. seealso::
15
+
16
+ The :mod:`sigima.proc` module contains the Sigima computation functions that
17
+ operate on signal and image objects (i.e. :class:`sigima.objects.SignalObj` and
18
+ :class:`sigima.objects.ImageObj`, defined in the :mod:`sigima.objects` package).
19
+
20
+ These tools are organized in subpackages according to their purpose. The following
21
+ subpackages are available:
22
+
23
+ - :mod:`sigima.tools.checks`: Input data checks for all tools
24
+ - :mod:`sigima.tools.signal`: Signal processing tools
25
+ - :mod:`sigima.tools.image`: Image processing tools
26
+ - :mod:`sigima.tools.datatypes`: Data type conversion tools
27
+ - :mod:`sigima.tools.coordinates`: Coordinate conversion tools
28
+
29
+ Check functions
30
+ ^^^^^^^^^^^^^^^
31
+
32
+ .. automodule:: sigima.tools.checks
33
+ :members:
34
+
35
+ Signal Processing Tools
36
+ ^^^^^^^^^^^^^^^^^^^^^^^
37
+
38
+ .. automodule:: sigima.tools.signal
39
+ :members:
40
+
41
+ Image Processing Tools
42
+ ^^^^^^^^^^^^^^^^^^^^^^
43
+
44
+ .. automodule:: sigima.tools.image
45
+ :members:
46
+
47
+ Data Type Conversion Tools
48
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^
49
+
50
+ .. automodule:: sigima.tools.datatypes
51
+ :members:
52
+
53
+ Coordinate Conversion Tools
54
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^
55
+
56
+ .. automodule:: sigima.tools.coordinates
57
+ :members:
58
+
59
+ """
sigima/tools/checks.py ADDED
@@ -0,0 +1,290 @@
1
+ # Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file.
2
+
3
+ """
4
+ .. Checks for 1D and 2D NumPy arrays used in tools (:mod:`sigima.tools.checks`).
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from dataclasses import dataclass
10
+ from functools import wraps
11
+ from typing import Any, Callable
12
+
13
+ import numpy as np
14
+
15
+
16
+ @dataclass(frozen=True)
17
+ class ArrayValidationRules:
18
+ """Hold 1-D array validation rules."""
19
+
20
+ #: Label used in error messages (e.g., "x" or "y")
21
+ label: str
22
+ #: Whether to enforce 1-D.
23
+ require_1d: bool = True
24
+ #: Check minimum size
25
+ min_size: int | None = None
26
+ #: Expected dtype (np.issubdtype). Use None to skip.
27
+ dtype: type | None = None
28
+ #: Whether to enforce finite values only.
29
+ finite_only: bool = False
30
+ #: Whether to enforce non-decreasing order.
31
+ sorted_: bool = False
32
+ #: Whether to enforce constant spacing (within rtol).
33
+ evenly_spaced: bool = False
34
+ #: Relative tolerance for regular spacing.
35
+ rtol: float = 1e-5
36
+
37
+
38
+ def _validate_array_1d(arr: np.ndarray, *, rules: ArrayValidationRules) -> None:
39
+ """Validate a single 1D NumPy array according to the provided rules.
40
+
41
+ Args:
42
+ arr: Array to validate.
43
+ rules: Validation rules to apply.
44
+
45
+ Raises:
46
+ ValueError: If shape constraint is violated.
47
+ ValueError: If size constraint is violated.
48
+ ValueError: If finite constraint is violated.
49
+ ValueError: If order constraint is violated.
50
+ ValueError: If spacing constraint is violated.
51
+ TypeError: If dtype does not match.
52
+ """
53
+ if rules.require_1d and arr.ndim != 1:
54
+ raise ValueError(f"{rules.label} must be 1-D.")
55
+ if rules.min_size is not None and arr.size < rules.min_size:
56
+ raise ValueError(f"{rules.label} must have at least {rules.min_size} elements.")
57
+ if rules.dtype is not None and not np.issubdtype(arr.dtype, rules.dtype):
58
+ raise TypeError(
59
+ f"{rules.label} must be of type {rules.dtype}, but got {arr.dtype}."
60
+ )
61
+ if rules.finite_only and not np.all(np.isfinite(arr)):
62
+ raise ValueError(f"{rules.label} must contain only finite values.")
63
+ if rules.sorted_ and arr.size > 1 and not np.all(np.diff(arr) > 0.0):
64
+ raise ValueError(f"{rules.label} must be sorted in ascending order.")
65
+ if rules.evenly_spaced and arr.size > 1:
66
+ dx = np.diff(arr)
67
+ if not np.allclose(dx, np.mean(dx), rtol=rules.rtol):
68
+ raise ValueError(f"{rules.label} must be evenly spaced.")
69
+
70
+
71
+ def check_1d_array(
72
+ func: Callable[..., Any] | None = None,
73
+ *,
74
+ require_1d: bool = True,
75
+ min_size: int | None = None,
76
+ dtype: type | None = np.inexact,
77
+ finite_only: bool = False,
78
+ sorted_: bool = False,
79
+ evenly_spaced: bool = False,
80
+ rtol: float = 1e-5,
81
+ label: str = "array",
82
+ ) -> Callable:
83
+ """Decorator to check a single 1D NumPy array.
84
+
85
+ Can be used with or without parentheses.
86
+
87
+ Args:
88
+ require_1d: Whether to check if the array is 1-D.
89
+ min_size: Minimum size of the array.
90
+ dtype: Expected dtype of the array (np.issubdtype). Use None to skip.
91
+ finite_only: Whether to check if the array contains only finite values.
92
+ sorted_: Whether to check if the array is sorted in ascending order.
93
+ evenly_spaced: Whether to check if the array is evenly spaced.
94
+ rtol: Relative tolerance for regular spacing.
95
+ label: Label for error messages (e.g., "x", "y").
96
+
97
+ Returns:
98
+ Decorated function with pre-checks on the single array.
99
+ """
100
+
101
+ def decorator(inner_func: Callable[..., Any]) -> Callable[..., Any]:
102
+ @wraps(inner_func)
103
+ def wrapper(arr: np.ndarray, *args: Any, **kwargs: Any) -> Any:
104
+ _validate_array_1d(
105
+ arr,
106
+ rules=ArrayValidationRules(
107
+ label=label,
108
+ require_1d=require_1d,
109
+ min_size=min_size,
110
+ dtype=dtype,
111
+ finite_only=finite_only,
112
+ sorted_=sorted_,
113
+ evenly_spaced=evenly_spaced,
114
+ rtol=rtol,
115
+ ),
116
+ )
117
+ return inner_func(arr, *args, **kwargs)
118
+
119
+ return wrapper
120
+
121
+ if func is not None:
122
+ return decorator(func)
123
+ return decorator
124
+
125
+
126
+ def check_1d_arrays(
127
+ func: Callable[..., Any] | None = None,
128
+ *,
129
+ x_require_1d: bool = True,
130
+ x_min_size: int | None = None,
131
+ x_dtype: type | None = np.floating,
132
+ x_finite_only: bool = False,
133
+ x_sorted: bool = False,
134
+ x_evenly_spaced: bool = False,
135
+ y_require_1d: bool = True,
136
+ y_min_size: int | None = None,
137
+ y_dtype: type | None = np.inexact,
138
+ y_finite_only: bool = False,
139
+ same_size: bool = True,
140
+ rtol: float = 1e-5,
141
+ ) -> Callable:
142
+ """Decorator to check paired 1D NumPy arrays (x, y).
143
+
144
+ Can be used with or without parentheses.
145
+
146
+ Args:
147
+ func: Function to decorate.
148
+ x_require_1d: Whether to check if x is 1-D.
149
+ x_min_size: Minimum size of x.
150
+ x_dtype: Expected dtype of x (np.issubdtype). Use None to skip.
151
+ x_finite_only: Whether to check if x contains only finite values.
152
+ x_sorted: Whether to check if x is sorted in ascending order.
153
+ x_evenly_spaced: Whether to check if x is evenly spaced.
154
+ y_require_1d: Whether to check if y is 1-D.
155
+ y_min_size: Minimum size of y.
156
+ y_dtype: Expected dtype of y (np.issubdtype). Use None to skip.
157
+ y_finite_only: Whether to check if y contains only finite values.
158
+ same_size: Whether to check that x and y have the same size.
159
+ rtol: Relative tolerance for regular spacing (used for x).
160
+
161
+ Returns:
162
+ Decorated function with pre-checks on x/y.
163
+ """
164
+
165
+ def decorator(inner_func: Callable[..., Any]) -> Callable[..., Any]:
166
+ @wraps(inner_func)
167
+ def wrapper(x: np.ndarray, y: np.ndarray, *args: Any, **kwargs: Any) -> Any:
168
+ _validate_array_1d(
169
+ x,
170
+ rules=ArrayValidationRules(
171
+ label="x",
172
+ require_1d=x_require_1d,
173
+ min_size=x_min_size,
174
+ dtype=x_dtype,
175
+ finite_only=x_finite_only,
176
+ sorted_=x_sorted,
177
+ evenly_spaced=x_evenly_spaced,
178
+ rtol=rtol,
179
+ ),
180
+ )
181
+ _validate_array_1d(
182
+ y,
183
+ rules=ArrayValidationRules(
184
+ label="y",
185
+ require_1d=y_require_1d,
186
+ min_size=y_min_size,
187
+ dtype=y_dtype,
188
+ finite_only=y_finite_only,
189
+ sorted_=False,
190
+ evenly_spaced=False,
191
+ rtol=rtol,
192
+ ),
193
+ )
194
+ if same_size and x.size != y.size:
195
+ raise ValueError("x and y must have the same size.")
196
+ return inner_func(x, y, *args, **kwargs)
197
+
198
+ return wrapper
199
+
200
+ if func is not None:
201
+ return decorator(func)
202
+ return decorator
203
+
204
+
205
+ def check_2d_array(
206
+ func: Callable[..., Any] | None = None,
207
+ *,
208
+ ndim: int = 2,
209
+ dtype: type | None = None,
210
+ non_constant: bool = False,
211
+ finite_only: bool = False,
212
+ ) -> Callable:
213
+ """
214
+ Decorator to check input for functions operating on 2D NumPy arrays (e.g. images).
215
+
216
+ Can be used with parentheses:
217
+
218
+ .. code-block:: python
219
+
220
+ @check_2d_array(ndim=3, dtype=np.uint8)
221
+ def process_image(image: np.ndarray) -> np.ndarray:
222
+ # Process the image
223
+ return image
224
+
225
+ Or without parentheses (default arguments):
226
+
227
+ .. code-block:: python
228
+
229
+ @check_2d_array
230
+ def process_image(image: np.ndarray) -> np.ndarray:
231
+ # Process the image
232
+ return image
233
+
234
+ Args:
235
+ ndim: Expected number of dimensions.
236
+ dtype: Expected dtype.
237
+ non_constant: Whether to check that the array has dynamic range.
238
+ finite_only: Whether to check that all values are finite.
239
+
240
+ Returns:
241
+ Decorated function with pre-checks on data.
242
+ """
243
+
244
+ def decorator(inner_func: Callable[..., Any]) -> Callable[..., Any]:
245
+ @wraps(inner_func)
246
+ def wrapper(data: np.ndarray, *args: Any, **kwargs: Any) -> Any:
247
+ # === Check input array
248
+ if data.ndim != ndim:
249
+ raise ValueError(f"Input array must be {ndim}D, got {data.ndim}D.")
250
+ if dtype is not None and not np.issubdtype(data.dtype, dtype):
251
+ raise TypeError(
252
+ f"Input array must be of type {dtype}, got {data.dtype}."
253
+ )
254
+ if non_constant:
255
+ dmin, dmax = np.nanmin(data), np.nanmax(data)
256
+ if dmin == dmax:
257
+ raise ValueError("Input array has no dynamic range.")
258
+ if finite_only and not np.all(np.isfinite(data)):
259
+ raise ValueError("Input array contains non-finite values.")
260
+ # === Call the original function
261
+ return inner_func(data, *args, **kwargs)
262
+
263
+ return wrapper
264
+
265
+ if func is not None:
266
+ # Usage: `@check_2d_array`
267
+ return decorator(func)
268
+ # Usage: `@check_2d_array(...)`
269
+ return decorator
270
+
271
+
272
+ def normalize_kernel(kernel: np.ndarray) -> np.ndarray:
273
+ """Normalize a convolution/deconvolution kernel if needed.
274
+
275
+ This utility function can normalize the kernel to sum to 1.0.
276
+
277
+ Args:
278
+ kernel: The kernel array to normalize.
279
+
280
+ Returns:
281
+ The normalized kernel if it's not already normalized and if its sum is not
282
+ zero, otherwise the original kernel.
283
+
284
+ Note:
285
+ A kernel is considered normalized if ``np.isclose(sum(kernel), 1.0)``.
286
+ """
287
+ kernel_sum = np.sum(kernel)
288
+ if not np.isclose(kernel_sum, 1.0) and kernel_sum != 0.0:
289
+ return kernel / kernel_sum
290
+ return kernel
@@ -0,0 +1,308 @@
1
+ # Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file.
2
+
3
+ """
4
+ .. Coordinates Algorithms (see parent package :mod:`sigima.tools`)
5
+ """
6
+
7
+ # pylint: disable=invalid-name # Allows short reference names like x, y, ...
8
+
9
+ from __future__ import annotations
10
+
11
+ from typing import Literal
12
+
13
+ import numpy as np
14
+
15
+ from sigima.tools.checks import check_1d_arrays
16
+
17
+
18
+ def circle_to_diameter(
19
+ xc: float, yc: float, r: float
20
+ ) -> tuple[float, float, float, float]:
21
+ """Convert circle center and radius to X diameter coordinates
22
+
23
+ Args:
24
+ xc: Circle center X coordinate
25
+ yc: Circle center Y coordinate
26
+ r: Circle radius
27
+
28
+ Returns:
29
+ tuple: Circle X diameter coordinates
30
+ """
31
+ return xc - r, yc, xc + r, yc
32
+
33
+
34
+ def array_circle_to_diameter(data: np.ndarray) -> np.ndarray:
35
+ """Convert circle center and radius to X diameter coordinates (array version)
36
+
37
+ Args:
38
+ data: Circle center and radius, in the form of a 2D array (N, 3)
39
+
40
+ Returns:
41
+ Circle X diameter coordinates, in the form of a 2D array (N, 4)
42
+ """
43
+ xc, yc, r = data[:, 0], data[:, 1], data[:, 2]
44
+ x_start = xc - r
45
+ x_end = xc + r
46
+ result = np.column_stack((x_start, yc, x_end, yc)).astype(float)
47
+ return result
48
+
49
+
50
+ def circle_to_center_radius(
51
+ x0: float, y0: float, x1: float, y1: float
52
+ ) -> tuple[float, float, float]:
53
+ """Convert circle X diameter coordinates to center and radius
54
+
55
+ Args:
56
+ x0: Diameter start X coordinate
57
+ y0: Diameter start Y coordinate
58
+ x1: Diameter end X coordinate
59
+ y1: Diameter end Y coordinate
60
+
61
+ Returns:
62
+ tuple: Circle center and radius
63
+ """
64
+ xc, yc = (x0 + x1) / 2, (y0 + y1) / 2
65
+ r = np.sqrt((x1 - x0) ** 2 + (y1 - y0) ** 2) / 2
66
+ return xc, yc, r
67
+
68
+
69
+ def array_circle_to_center_radius(data: np.ndarray) -> np.ndarray:
70
+ """Convert circle X diameter coordinates to center and radius (array version)
71
+
72
+ Args:
73
+ data: Circle X diameter coordinates, in the form of a 2D array (N, 4)
74
+
75
+ Returns:
76
+ Circle center and radius, in the form of a 2D array (N, 3)
77
+ """
78
+ x0, y0, x1, y1 = data[:, 0], data[:, 1], data[:, 2], data[:, 3]
79
+ xc, yc = (x0 + x1) / 2, (y0 + y1) / 2
80
+ r = np.sqrt((x1 - x0) ** 2 + (y1 - y0) ** 2) / 2
81
+ result = np.column_stack((xc, yc, r)).astype(float)
82
+ return result
83
+
84
+
85
+ def ellipse_to_diameters(
86
+ xc: float, yc: float, a: float, b: float, theta: float
87
+ ) -> tuple[float, float, float, float, float, float, float, float]:
88
+ """Convert ellipse center, axes and angle to X/Y diameters coordinates
89
+
90
+ Args:
91
+ xc: Ellipse center X coordinate
92
+ yc: Ellipse center Y coordinate
93
+ a: Ellipse half larger axis
94
+ b: Ellipse half smaller axis
95
+ theta: Ellipse angle
96
+
97
+ Returns:
98
+ Ellipse X/Y diameters (major/minor axes) coordinates
99
+ """
100
+ dxa, dya = a * np.cos(theta), a * np.sin(theta)
101
+ dxb, dyb = b * np.sin(theta), b * np.cos(theta)
102
+ x0, y0, x1, y1 = xc - dxa, yc - dya, xc + dxa, yc + dya
103
+ x2, y2, x3, y3 = xc - dxb, yc - dyb, xc + dxb, yc + dyb
104
+ return x0, y0, x1, y1, x2, y2, x3, y3
105
+
106
+
107
+ def array_ellipse_to_diameters(data: np.ndarray) -> np.ndarray:
108
+ """Convert ellipse center, axes and angle to X/Y diameters coordinates
109
+ (array version)
110
+
111
+ Args:
112
+ data: Ellipse center, axes and angle, in the form of a 2D array (N, 5)
113
+
114
+ Returns:
115
+ Ellipse X/Y diameters (major/minor axes) coordinates,
116
+ in the form of a 2D array (N, 8)
117
+ """
118
+ xc, yc, a, b, theta = data[:, 0], data[:, 1], data[:, 2], data[:, 3], data[:, 4]
119
+ dxa, dya = a * np.cos(theta), a * np.sin(theta)
120
+ dxb, dyb = b * np.sin(theta), b * np.cos(theta)
121
+ x0, y0, x1, y1 = xc - dxa, yc - dya, xc + dxa, yc + dya
122
+ x2, y2, x3, y3 = xc - dxb, yc - dyb, xc + dxb, yc + dyb
123
+ result = np.column_stack((x0, y0, x1, y1, x2, y2, x3, y3)).astype(float)
124
+ return result
125
+
126
+
127
+ def ellipse_to_center_axes_angle(
128
+ x0: float,
129
+ y0: float,
130
+ x1: float,
131
+ y1: float,
132
+ x2: float,
133
+ y2: float,
134
+ x3: float,
135
+ y3: float,
136
+ ) -> tuple[float, float, float, float, float]:
137
+ """Convert ellipse X/Y diameters coordinates to center, axes and angle
138
+
139
+ Args:
140
+ x0: major axis start X coordinate
141
+ y0: major axis start Y coordinate
142
+ x1: major axis end X coordinate
143
+ y1: major axis end Y coordinate
144
+ x2: minor axis start X coordinate
145
+ y2: minor axis start Y coordinate
146
+ x3: minor axis end X coordinate
147
+ y3: minor axis end Y coordinate
148
+
149
+ Returns:
150
+ Ellipse center, axes and angle
151
+ """
152
+ xc, yc = (x0 + x1) / 2, (y0 + y1) / 2
153
+ a = np.sqrt((x1 - x0) ** 2 + (y1 - y0) ** 2) / 2
154
+ b = np.sqrt((x3 - x2) ** 2 + (y3 - y2) ** 2) / 2
155
+ theta = np.arctan2(y1 - y0, x1 - x0)
156
+ return xc, yc, a, b, theta
157
+
158
+
159
+ def array_ellipse_to_center_axes_angle(data: np.ndarray) -> np.ndarray:
160
+ """Convert ellipse X/Y diameters coordinates to center, axes and angle
161
+ (array version)
162
+
163
+ Args:
164
+ data: Ellipse X/Y diameters coordinates, in the form of a 2D array (N, 8)
165
+
166
+ Returns:
167
+ Ellipse center, axes and angle, in the form of a 2D array (N, 5)
168
+ """
169
+ x0, y0, x1, y1, x2, y2, x3, y3 = (
170
+ data[:, 0],
171
+ data[:, 1],
172
+ data[:, 2],
173
+ data[:, 3],
174
+ data[:, 4],
175
+ data[:, 5],
176
+ data[:, 6],
177
+ data[:, 7],
178
+ )
179
+ xc, yc = (x0 + x1) / 2, (y0 + y1) / 2
180
+ a = np.sqrt((x1 - x0) ** 2 + (y1 - y0) ** 2) / 2
181
+ b = np.sqrt((x3 - x2) ** 2 + (y3 - y2) ** 2) / 2
182
+ theta = np.arctan2(y1 - y0, x1 - x0)
183
+ result = np.column_stack((xc, yc, a, b, theta)).astype(float)
184
+ return result
185
+
186
+
187
+ @check_1d_arrays
188
+ def to_polar(
189
+ x: np.ndarray, y: np.ndarray, unit: Literal["°", "rad"] = "rad"
190
+ ) -> tuple[np.ndarray, np.ndarray]:
191
+ """Convert Cartesian coordinates to polar coordinates.
192
+
193
+ Args:
194
+ x: Cartesian x-coordinate.
195
+ y: Cartesian y-coordinate.
196
+ unit: Unit of the angle ("°" or "rad").
197
+
198
+ Returns:
199
+ Polar coordinates (r, theta) where r is the radius and theta is the angle.
200
+
201
+ Raises:
202
+ ValueError: If the unit is not "°" or "rad".
203
+ """
204
+ if unit not in ["rad", "°"]:
205
+ raise ValueError(f"Unit must be radian ('rad') or degree ('°'), got {unit}.")
206
+ r = np.sqrt(x**2 + y**2)
207
+ theta = np.arctan2(y, x)
208
+ if unit == "°":
209
+ theta = np.rad2deg(theta)
210
+ return r, theta
211
+
212
+
213
+ @check_1d_arrays
214
+ def to_cartesian(
215
+ r: np.ndarray, theta: np.ndarray, unit: Literal["°", "rad"] = "rad"
216
+ ) -> tuple[np.ndarray, np.ndarray]:
217
+ """Convert polar coordinates to Cartesian coordinates.
218
+
219
+ Args:
220
+ r: Polar radius.
221
+ theta: Polar angle.
222
+ unit: Unit of the angle ("°" or "rad").
223
+
224
+ Returns:
225
+ Cartesian coordinates (x, y) where x is the x-coordinate and y is the
226
+ y-coordinate.
227
+
228
+ Raises:
229
+ ValueError: If the unit is not "°" or "rad".
230
+ ValueError: If any value of the radius is negative.
231
+ """
232
+ if unit not in ["rad", "°"]:
233
+ raise ValueError(f"Unit must be radian ('rad') or degree ('°'), got {unit}.")
234
+ if np.any(r < 0.0):
235
+ raise ValueError("Negative radius values are not allowed.")
236
+ if unit == "°":
237
+ theta = np.deg2rad(theta)
238
+ x = r * np.cos(theta)
239
+ y = r * np.sin(theta)
240
+ return x, y
241
+
242
+
243
+ def rotate(angle: float) -> np.ndarray:
244
+ """Return rotation matrix
245
+
246
+ Args:
247
+ angle: Rotation angle (in radians)
248
+
249
+ Returns:
250
+ Rotation matrix
251
+ """
252
+ cos_a = np.cos(angle)
253
+ sin_a = np.sin(angle)
254
+ return np.array([[cos_a, -sin_a, 0], [sin_a, cos_a, 0], [0, 0, 1]], dtype=float)
255
+
256
+
257
+ def colvector(x: float, y: float) -> np.ndarray:
258
+ """Return vector from coordinates
259
+
260
+ Args:
261
+ x: x-coordinate
262
+ y: y-coordinate
263
+
264
+ Returns:
265
+ Vector
266
+ """
267
+ return np.array([x, y, 1]).T
268
+
269
+
270
+ def vector_rotation(theta: float, dx: float, dy: float) -> tuple[float, float]:
271
+ """Compute theta-rotation on vector
272
+
273
+ Args:
274
+ theta: Rotation angle
275
+ dx: x-coordinate of vector
276
+ dy: y-coordinate of vector
277
+
278
+ Returns:
279
+ Tuple of (x, y) coordinates of rotated vector
280
+ """
281
+ return (rotate(theta) @ colvector(dx, dy)).ravel()[:2]
282
+
283
+
284
+ @check_1d_arrays(x_require_1d=False, y_require_1d=False)
285
+ def polar_to_complex(
286
+ r: np.ndarray, theta: np.ndarray, unit: Literal["°", "rad"] = "rad"
287
+ ) -> np.ndarray:
288
+ """Convert polar coordinates to complex number.
289
+
290
+ Args:
291
+ r: Polar radius.
292
+ theta: Polar angle.
293
+ unit: Unit of the angle ("°" or "rad").
294
+
295
+ Returns:
296
+ Complex numbers corresponding to the polar coordinates.
297
+
298
+ Raises:
299
+ ValueError: If the unit is not "°" or "rad".
300
+ ValueError: If any value of the radius is negative.
301
+ """
302
+ if unit not in ["rad", "°"]:
303
+ raise ValueError(f"Unit must be radian ('rad') or degree ('°'), got {unit}.")
304
+ if np.any(r < 0.0):
305
+ raise ValueError("Negative radius values are not allowed.")
306
+ if unit == "°":
307
+ theta = np.deg2rad(theta)
308
+ return r * np.exp(1j * theta)
@@ -0,0 +1,26 @@
1
+ # Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file.
2
+
3
+ """
4
+ .. Data Type Conversion Algorithms (see parent package :mod:`sigima.algorithms`)
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import numpy as np
10
+
11
+
12
+ def clip_astype(data: np.ndarray, dtype: np.dtype) -> np.ndarray:
13
+ """Convert array to a new data type, after having clipped values to the new
14
+ data type's range if it is an integer type.
15
+ If data type is not integer, this is equivalent to ``data.astype(dtype)``.
16
+
17
+ Args:
18
+ data: Array to convert
19
+ dtype: Data type to convert to
20
+
21
+ Returns:
22
+ Array converted to new data type
23
+ """
24
+ if np.issubdtype(dtype, np.integer):
25
+ return np.clip(data, np.iinfo(dtype).min, np.iinfo(dtype).max).astype(dtype)
26
+ return data.astype(dtype)