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,97 @@
1
+ # Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file.
2
+
3
+ """
4
+ Image Processing Tools (:mod:`sigima.tools.image`)
5
+ --------------------------------------------------
6
+
7
+ This package contains image processing tools, which are organized into subpackages
8
+ according to their purpose:
9
+
10
+ - :mod:`sigima.tools.image.detection`: Object detection algorithms
11
+ (blob detection, peak detection, contour fitting)
12
+ - :mod:`sigima.tools.image.exposure`: Exposure and level adjustment functions,
13
+ including flat-field correction
14
+ - :mod:`sigima.tools.image.extraction`: Data extraction and analysis functions
15
+ (radial profiles, feature extraction)
16
+ - :mod:`sigima.tools.image.fourier`: 2D Fourier transform operations and
17
+ spectral analysis
18
+ - :mod:`sigima.tools.image.geometry`: Geometric analysis and transformations
19
+ - :mod:`sigima.tools.image.preprocessing`: Data preprocessing and transformation
20
+ utilities (scaling, normalization, binning, padding)
21
+
22
+ All functions are re-exported at the subpackage level for backward
23
+ compatibility. Existing imports like ``from sigima.tools.image import fft2d``
24
+ will continue to work.
25
+ """
26
+
27
+ from __future__ import annotations
28
+
29
+ # Import all functions from submodules for backward compatibility
30
+ from sigima.tools.image.detection import (
31
+ find_blobs_dog,
32
+ find_blobs_doh,
33
+ find_blobs_log,
34
+ find_blobs_opencv,
35
+ get_2d_peaks_coords,
36
+ get_contour_shapes,
37
+ get_hough_circle_peaks,
38
+ remove_overlapping_disks,
39
+ )
40
+ from sigima.tools.image.exposure import flatfield, normalize
41
+ from sigima.tools.image.extraction import get_radial_profile
42
+ from sigima.tools.image.fourier import (
43
+ convolve,
44
+ deconvolve,
45
+ fft2d,
46
+ gaussian_freq_filter,
47
+ ifft2d,
48
+ magnitude_spectrum,
49
+ phase_spectrum,
50
+ psd,
51
+ )
52
+ from sigima.tools.image.geometry import (
53
+ get_centroid_auto,
54
+ get_centroid_fourier,
55
+ get_enclosing_circle,
56
+ get_projected_profile_centroid,
57
+ )
58
+ from sigima.tools.image.preprocessing import (
59
+ binning,
60
+ distance_matrix,
61
+ get_absolute_level,
62
+ scale_data_to_min_max,
63
+ zero_padding,
64
+ )
65
+
66
+ # Define __all__ to specify what gets imported with
67
+ # "from sigima.tools.image import *"
68
+ __all__ = [
69
+ "binning",
70
+ "convolve",
71
+ "deconvolve",
72
+ "distance_matrix",
73
+ "fft2d",
74
+ "find_blobs_dog",
75
+ "find_blobs_doh",
76
+ "find_blobs_log",
77
+ "find_blobs_opencv",
78
+ "flatfield",
79
+ "gaussian_freq_filter",
80
+ "get_2d_peaks_coords",
81
+ "get_absolute_level",
82
+ "get_centroid_auto",
83
+ "get_centroid_fourier",
84
+ "get_contour_shapes",
85
+ "get_enclosing_circle",
86
+ "get_hough_circle_peaks",
87
+ "get_projected_profile_centroid",
88
+ "get_radial_profile",
89
+ "ifft2d",
90
+ "magnitude_spectrum",
91
+ "normalize",
92
+ "phase_spectrum",
93
+ "psd",
94
+ "remove_overlapping_disks",
95
+ "scale_data_to_min_max",
96
+ "zero_padding",
97
+ ]
@@ -0,0 +1,451 @@
1
+ # Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file.
2
+
3
+ """
4
+ Detection algorithms module
5
+ ---------------------------
6
+
7
+ This module provides various object detection algorithms for image analysis.
8
+
9
+ Features include:
10
+
11
+ - Blob detection using multiple algorithms (DoG, DoH, LoG, OpenCV)
12
+ - Peak detection with configurable thresholds and neighborhood sizes
13
+ - Hough transform-based circle detection
14
+ - Contour shape fitting (circles, ellipses, polygons)
15
+ - Utility functions for coordinate processing
16
+
17
+ These tools support automated feature extraction and object identification
18
+ in images for scientific and technical applications.
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ import numpy as np
24
+ import scipy.ndimage as spi
25
+ from numpy import ma
26
+ from skimage import exposure, feature, measure, transform
27
+
28
+ from sigima.enums import ContourShape
29
+ from sigima.tools.checks import check_2d_array
30
+ from sigima.tools.image.preprocessing import distance_matrix, get_absolute_level
31
+
32
+
33
+ @check_2d_array(non_constant=True)
34
+ def get_2d_peaks_coords(
35
+ data: np.ndarray, size: int | None = None, level: float = 0.5
36
+ ) -> np.ndarray:
37
+ """Detect peaks in image data, return coordinates.
38
+
39
+ If neighborhoods size is None, default value is the highest value
40
+ between 50 pixels and the 1/40th of the smallest image dimension.
41
+
42
+ Detection threshold level is relative to difference
43
+ between data maximum and minimum values.
44
+
45
+ Args:
46
+ data: Input data
47
+ size: Neighborhood size (default: None)
48
+ level: Relative level (default: 0.5)
49
+
50
+ Returns:
51
+ Coordinates of peaks
52
+ """
53
+ if size is None:
54
+ size = max(min(data.shape) // 40, 50)
55
+ data_max = spi.maximum_filter(data, size)
56
+ data_min = spi.minimum_filter(data, size)
57
+ data_diff = data_max - data_min
58
+ diff = (data_max - data_min) > get_absolute_level(data_diff, level)
59
+ maxima = data == data_max
60
+ maxima[diff == 0] = 0
61
+ labeled, _num_objects = spi.label(maxima)
62
+ slices = spi.find_objects(labeled)
63
+ coords = []
64
+ for dy, dx in slices:
65
+ x_center = int(0.5 * (dx.start + dx.stop - 1))
66
+ y_center = int(0.5 * (dy.start + dy.stop - 1))
67
+ coords.append((x_center, y_center))
68
+ if len(coords) > 1:
69
+ # Eventually removing duplicates
70
+ dist = distance_matrix(coords)
71
+ for index in reversed(np.unique(np.where((dist < size) & (dist > 0))[1])):
72
+ coords.pop(index)
73
+ return np.array(coords)
74
+
75
+
76
+ def get_contour_shapes(
77
+ data: np.ndarray | ma.MaskedArray,
78
+ shape: ContourShape = ContourShape.ELLIPSE,
79
+ level: float = 0.5,
80
+ ) -> np.ndarray:
81
+ """Find iso-valued contours in a 2D array, above relative level (.5 means FWHM).
82
+
83
+ Args:
84
+ data: Input data
85
+ shape: Shape to fit. Default is ELLIPSE
86
+ level: Relative level (default: 0.5)
87
+
88
+ Returns:
89
+ Coordinates of shapes fitted to contours
90
+ """
91
+ # pylint: disable=too-many-locals
92
+ contours = measure.find_contours(data, level=get_absolute_level(data, level))
93
+ coords = []
94
+ for contour in contours:
95
+ # `contour` is a (N, 2) array (rows, cols): we need to check if all those
96
+ # coordinates are masked: if so, we skip this contour
97
+ if isinstance(data, ma.MaskedArray) and np.all(
98
+ data.mask[contour[:, 0].astype(int), contour[:, 1].astype(int)]
99
+ ):
100
+ continue
101
+ if shape == ContourShape.CIRCLE:
102
+ model = measure.CircleModel()
103
+ if model.estimate(contour):
104
+ yc, xc, r = model.params
105
+ if r <= 1.0:
106
+ continue
107
+ coords.append([xc, yc, r])
108
+ elif shape == ContourShape.ELLIPSE:
109
+ model = measure.EllipseModel()
110
+ if model.estimate(contour):
111
+ yc, xc, b, a, theta = model.params
112
+ if a <= 1.0 or b <= 1.0:
113
+ continue
114
+ coords.append([xc, yc, a, b, theta])
115
+ elif shape == ContourShape.POLYGON:
116
+ # `contour` is a (N, 2) array (rows, cols): we need to convert it
117
+ # to a list of x, y coordinates flattened in a single list
118
+ coords.append(contour[:, ::-1].flatten())
119
+ else:
120
+ raise NotImplementedError(f"Invalid contour model {model}")
121
+ if shape == ContourShape.POLYGON:
122
+ # `coords` is a list of arrays of shape (N, 2) where N is the number of points
123
+ # that can vary from one array to another, so we need to padd with NaNs each
124
+ # array to get a regular array:
125
+ max_len = max(coord.shape[0] for coord in coords)
126
+ arr = np.full((len(coords), max_len), np.nan)
127
+ for i_row, coord in enumerate(coords):
128
+ arr[i_row, : coord.shape[0]] = coord
129
+ return arr
130
+ return np.array(coords)
131
+
132
+
133
+ @check_2d_array(non_constant=True)
134
+ def get_hough_circle_peaks(
135
+ data: np.ndarray,
136
+ min_radius: float | None = None,
137
+ max_radius: float | None = None,
138
+ nb_radius: int | None = None,
139
+ min_distance: int = 1,
140
+ ) -> np.ndarray:
141
+ """Detect peaks in image from circle Hough transform, return circle coordinates.
142
+
143
+ Args:
144
+ data: Input data
145
+ min_radius: Minimum radius (default: None)
146
+ max_radius: Maximum radius (default: None)
147
+ nb_radius: Number of radii (default: None)
148
+ min_distance: Minimum distance between circles (default: 1)
149
+
150
+ Returns:
151
+ Coordinates of circles
152
+ """
153
+ assert min_radius is not None and max_radius is not None and max_radius > min_radius
154
+ if nb_radius is None:
155
+ nb_radius = max_radius - min_radius + 1
156
+ hough_radii = np.arange(
157
+ min_radius, max_radius + 1, (max_radius - min_radius + 1) // nb_radius
158
+ )
159
+ hough_res = transform.hough_circle(data, hough_radii)
160
+ _accums, cx, cy, radii = transform.hough_circle_peaks(
161
+ hough_res, hough_radii, min_xdistance=min_distance, min_ydistance=min_distance
162
+ )
163
+ return np.vstack([cx, cy, radii]).T
164
+
165
+
166
+ # MARK: Blob detection -----------------------------------------------------------------
167
+
168
+
169
+ def __blobs_to_coords(blobs: np.ndarray) -> np.ndarray:
170
+ """Convert blobs to coordinates
171
+
172
+ Args:
173
+ blobs: Blobs
174
+
175
+ Returns:
176
+ Coordinates
177
+ """
178
+ cy, cx, radii = blobs.T
179
+ coords = np.vstack([cx, cy, radii]).T
180
+ return coords
181
+
182
+
183
+ @check_2d_array(non_constant=True)
184
+ def find_blobs_dog(
185
+ data: np.ndarray,
186
+ min_sigma: float = 1,
187
+ max_sigma: float = 30,
188
+ overlap: float = 0.5,
189
+ threshold_rel: float = 0.2,
190
+ exclude_border: bool = True,
191
+ ) -> np.ndarray:
192
+ """
193
+ Finds blobs in the given grayscale image using the Difference of Gaussians
194
+ (DoG) method.
195
+
196
+ Args:
197
+ data: The grayscale input image.
198
+ min_sigma: The minimum blob radius in pixels.
199
+ max_sigma: The maximum blob radius in pixels.
200
+ overlap: The minimum overlap between two blobs in pixels. For instance, if two
201
+ blobs are detected with radii of 10 and 12 respectively, and the ``overlap``
202
+ is set to 0.5, then the area of the smaller blob will be ignored and only the
203
+ area of the larger blob will be returned.
204
+ threshold_rel: The absolute lower bound for scale space maxima. Local maxima
205
+ smaller than ``threshold_rel`` are ignored. Reduce this to detect blobs with
206
+ less intensities.
207
+ exclude_border: If ``True``, exclude blobs from detection if they are too
208
+ close to the border of the image. Border size is ``min_sigma``.
209
+
210
+ Returns:
211
+ Coordinates of blobs
212
+ """
213
+ # Use scikit-image's Difference of Gaussians (DoG) method
214
+ blobs = feature.blob_dog(
215
+ data,
216
+ min_sigma=min_sigma,
217
+ max_sigma=max_sigma,
218
+ overlap=overlap,
219
+ threshold_rel=threshold_rel,
220
+ exclude_border=exclude_border,
221
+ )
222
+ return __blobs_to_coords(blobs)
223
+
224
+
225
+ @check_2d_array(non_constant=True)
226
+ def find_blobs_doh(
227
+ data: np.ndarray,
228
+ min_sigma: float = 1,
229
+ max_sigma: float = 30,
230
+ overlap: float = 0.5,
231
+ log_scale: bool = False,
232
+ threshold_rel: float = 0.2,
233
+ ) -> np.ndarray:
234
+ """
235
+ Finds blobs in the given grayscale image using the Determinant of Hessian
236
+ (DoH) method.
237
+
238
+ Args:
239
+ data: The grayscale input image.
240
+ min_sigma: The minimum blob radius in pixels.
241
+ max_sigma: The maximum blob radius in pixels.
242
+ overlap: The minimum overlap between two blobs in pixels. For instance, if two
243
+ blobs are detected with radii of 10 and 12 respectively, and the ``overlap``
244
+ is set to 0.5, then the area of the smaller blob will be ignored and only the
245
+ area of the larger blob will be returned.
246
+ log_scale: If ``True``, the radius of each blob is returned as ``sqrt(sigma)``
247
+ for each detected blob.
248
+ threshold_rel: The absolute lower bound for scale space maxima. Local maxima
249
+ smaller than ``threshold_rel`` are ignored. Reduce this to detect blobs with
250
+ less intensities.
251
+
252
+ Returns:
253
+ Coordinates of blobs
254
+ """
255
+ # Use scikit-image's Determinant of Hessian (DoH) method to detect blobs
256
+ blobs = feature.blob_doh(
257
+ data,
258
+ min_sigma=min_sigma,
259
+ max_sigma=max_sigma,
260
+ num_sigma=int(max_sigma - min_sigma + 1),
261
+ threshold=None,
262
+ threshold_rel=threshold_rel,
263
+ overlap=overlap,
264
+ log_scale=log_scale,
265
+ )
266
+ return __blobs_to_coords(blobs)
267
+
268
+
269
+ @check_2d_array(non_constant=True)
270
+ def find_blobs_log(
271
+ data: np.ndarray,
272
+ min_sigma: float = 1,
273
+ max_sigma: float = 30,
274
+ overlap: float = 0.5,
275
+ log_scale: bool = False,
276
+ threshold_rel: float = 0.2,
277
+ exclude_border: bool = True,
278
+ ) -> np.ndarray:
279
+ """Finds blobs in the given grayscale image using the Laplacian of Gaussian
280
+ (LoG) method.
281
+
282
+ Args:
283
+ data: The grayscale input image.
284
+ min_sigma: The minimum blob radius in pixels.
285
+ max_sigma: The maximum blob radius in pixels.
286
+ overlap: The minimum overlap between two blobs in pixels. For instance, if
287
+ two blobs are detected with radii of 10 and 12 respectively, and the
288
+ ``overlap`` is set to 0.5, then the area of the smaller blob will be ignored
289
+ and only the area of the larger blob will be returned.
290
+ log_scale: If ``True``, the radius of each blob is returned as ``sqrt(sigma)``
291
+ for each detected blob.
292
+ threshold_rel: The absolute lower bound for scale space maxima. Local maxima
293
+ smaller than ``threshold_rel`` are ignored. Reduce this to detect blobs with
294
+ less intensities.
295
+ exclude_border: If ``True``, exclude blobs from detection if they are too
296
+ close to the border of the image. Border size is ``min_sigma``.
297
+
298
+ Returns:
299
+ Coordinates of blobs
300
+ """
301
+ # Use scikit-image's Laplacian of Gaussian (LoG) method to detect blobs
302
+ blobs = feature.blob_log(
303
+ data,
304
+ min_sigma=min_sigma,
305
+ max_sigma=max_sigma,
306
+ num_sigma=int(max_sigma - min_sigma + 1),
307
+ threshold=None,
308
+ threshold_rel=threshold_rel,
309
+ overlap=overlap,
310
+ log_scale=log_scale,
311
+ exclude_border=exclude_border,
312
+ )
313
+ return __blobs_to_coords(blobs)
314
+
315
+
316
+ def remove_overlapping_disks(coords: np.ndarray) -> np.ndarray:
317
+ """Remove overlapping disks among coordinates
318
+
319
+ Args:
320
+ coords: The coordinates of the disks
321
+
322
+ Returns:
323
+ The coordinates of the disks with overlapping disks removed
324
+ """
325
+ # Get the radii of each disk from the coordinates
326
+ radii = coords[:, 2]
327
+ # Calculate the distance between the center of each pair of disks
328
+ dist = np.sqrt(np.sum((coords[:, None, :2] - coords[:, :2]) ** 2, axis=-1))
329
+ # Create a boolean mask where the distance between the centers
330
+ # is less than the sum of the radii
331
+ mask = dist < (radii[:, None] + radii)
332
+ # Find the indices of overlapping disks
333
+ overlapping_indices = np.argwhere(mask)
334
+ # Remove the smaller disk from each overlapping pair
335
+ for i, j in overlapping_indices:
336
+ if i != j:
337
+ if radii[i] < radii[j]:
338
+ coords[i] = [np.nan, np.nan, np.nan]
339
+ else:
340
+ coords[j] = [np.nan, np.nan, np.nan]
341
+ # Remove rows with NaN values
342
+ coords = coords[~np.isnan(coords).any(axis=1)]
343
+ return coords
344
+
345
+
346
+ # pylint: disable=too-many-positional-arguments
347
+ @check_2d_array(non_constant=True)
348
+ def find_blobs_opencv(
349
+ data: np.ndarray,
350
+ min_threshold: float | None = None,
351
+ max_threshold: float | None = None,
352
+ min_repeatability: int | None = None,
353
+ min_dist_between_blobs: float | None = None,
354
+ filter_by_color: bool | None = None,
355
+ blob_color: int | None = None,
356
+ filter_by_area: bool | None = None,
357
+ min_area: float | None = None,
358
+ max_area: float | None = None,
359
+ filter_by_circularity: bool | None = None,
360
+ min_circularity: float | None = None,
361
+ max_circularity: float | None = None,
362
+ filter_by_inertia: bool | None = None,
363
+ min_inertia_ratio: float | None = None,
364
+ max_inertia_ratio: float | None = None,
365
+ filter_by_convexity: bool | None = None,
366
+ min_convexity: float | None = None,
367
+ max_convexity: float | None = None,
368
+ ) -> np.ndarray:
369
+ """
370
+ Finds blobs in the given grayscale image using OpenCV's SimpleBlobDetector.
371
+
372
+ Args:
373
+ data: The grayscale input image.
374
+ min_threshold: The minimum blob intensity.
375
+ max_threshold: The maximum blob intensity.
376
+ min_repeatability: The minimum number of times a blob is detected
377
+ before it is reported.
378
+ min_dist_between_blobs: The minimum distance between blobs.
379
+ filter_by_color: If ``True``, blobs are filtered by color.
380
+ blob_color: The color of the blobs to filter by.
381
+ filter_by_area: If ``True``, blobs are filtered by area.
382
+ min_area: The minimum blob area.
383
+ max_area: The maximum blob area.
384
+ filter_by_circularity: If ``True``, blobs are filtered by circularity.
385
+ min_circularity: The minimum blob circularity.
386
+ max_circularity: The maximum blob circularity.
387
+ filter_by_inertia: If ``True``, blobs are filtered by inertia.
388
+ min_inertia_ratio: The minimum blob inertia ratio.
389
+ max_inertia_ratio: The maximum blob inertia ratio.
390
+ filter_by_convexity: If ``True``, blobs are filtered by convexity.
391
+ min_convexity: The minimum blob convexity.
392
+ max_convexity: The maximum blob convexity.
393
+
394
+ Returns:
395
+ Coordinates of blobs
396
+ """
397
+ # Note:
398
+ # Importing OpenCV inside the function in order to eventually raise an ImportError
399
+ # when the function is called and OpenCV is not installed. This error will be
400
+ # handled by DataLab and the user will be informed that OpenCV is required to use
401
+ # this function.
402
+ import cv2 # pylint: disable=import-outside-toplevel
403
+
404
+ params = cv2.SimpleBlobDetector_Params()
405
+ if min_threshold is not None:
406
+ params.minThreshold = min_threshold
407
+ if max_threshold is not None:
408
+ params.maxThreshold = max_threshold
409
+ if min_repeatability is not None:
410
+ params.minRepeatability = min_repeatability
411
+ if min_dist_between_blobs is not None:
412
+ params.minDistBetweenBlobs = min_dist_between_blobs
413
+ if filter_by_color is not None:
414
+ params.filterByColor = filter_by_color
415
+ if blob_color is not None:
416
+ params.blobColor = blob_color
417
+ if filter_by_area is not None:
418
+ params.filterByArea = filter_by_area
419
+ if min_area is not None:
420
+ params.minArea = min_area
421
+ if max_area is not None:
422
+ params.maxArea = max_area
423
+ if filter_by_circularity is not None:
424
+ params.filterByCircularity = filter_by_circularity
425
+ if min_circularity is not None:
426
+ params.minCircularity = min_circularity
427
+ if max_circularity is not None:
428
+ params.maxCircularity = max_circularity
429
+ if filter_by_inertia is not None:
430
+ params.filterByInertia = filter_by_inertia
431
+ if min_inertia_ratio is not None:
432
+ params.minInertiaRatio = min_inertia_ratio
433
+ if max_inertia_ratio is not None:
434
+ params.maxInertiaRatio = max_inertia_ratio
435
+ if filter_by_convexity is not None:
436
+ params.filterByConvexity = filter_by_convexity
437
+ if min_convexity is not None:
438
+ params.minConvexity = min_convexity
439
+ if max_convexity is not None:
440
+ params.maxConvexity = max_convexity
441
+ detector = cv2.SimpleBlobDetector_create(params)
442
+ image = exposure.rescale_intensity(data, out_range=np.uint8)
443
+ keypoints = detector.detect(image)
444
+ if keypoints:
445
+ coords = cv2.KeyPoint_convert(keypoints)
446
+ radii = 0.5 * np.array([kp.size for kp in keypoints])
447
+ blobs = np.vstack([coords[:, 1], coords[:, 0], radii]).T
448
+ blobs = remove_overlapping_disks(blobs)
449
+ else:
450
+ blobs = np.array([]).reshape((0, 3))
451
+ return __blobs_to_coords(blobs)
@@ -0,0 +1,77 @@
1
+ # Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file.
2
+
3
+ """
4
+ Exposure and level adjustment module
5
+ ------------------------------------
6
+
7
+ This module provides functions for adjusting image exposure, contrast, and intensity
8
+ levels.
9
+
10
+ Features include:
11
+
12
+ - Dynamic range scaling and adjustment
13
+ - Various normalization methods (maximum, amplitude, area, energy, RMS)
14
+ - Data type preserving transformations
15
+
16
+ These tools support image enhancement and preprocessing operations that adjust
17
+ the intensity distribution of images while preserving their essential characteristics.
18
+ """
19
+
20
+ from __future__ import annotations
21
+
22
+ import numpy as np
23
+
24
+ from sigima.enums import NormalizationMethod
25
+ from sigima.tools.checks import check_2d_array
26
+ from sigima.tools.image.preprocessing import scale_data_to_min_max
27
+
28
+
29
+ @check_2d_array(non_constant=True)
30
+ def normalize(
31
+ data: np.ndarray,
32
+ parameter: NormalizationMethod = NormalizationMethod.MAXIMUM,
33
+ ) -> np.ndarray:
34
+ """Normalize input array to a given parameter.
35
+
36
+ Args:
37
+ data: Input data
38
+ parameter: Normalization parameter (default: MAXIMUM)
39
+
40
+ Returns:
41
+ Normalized array
42
+ """
43
+ if parameter == NormalizationMethod.MAXIMUM:
44
+ return scale_data_to_min_max(data, np.nanmin(data) / np.nanmax(data), 1.0)
45
+ if parameter == NormalizationMethod.AMPLITUDE:
46
+ return scale_data_to_min_max(data, 0.0, 1.0)
47
+ fdata = np.array(data, dtype=float)
48
+ if parameter == NormalizationMethod.AREA:
49
+ return fdata / np.nansum(fdata)
50
+ if parameter == NormalizationMethod.ENERGY:
51
+ return fdata / np.sqrt(np.nansum(fdata * fdata.conjugate()))
52
+ if parameter == NormalizationMethod.RMS:
53
+ return fdata / np.sqrt(np.nanmean(fdata * fdata.conjugate()))
54
+ raise ValueError(f"Unsupported parameter {parameter}")
55
+
56
+
57
+ @check_2d_array
58
+ def flatfield(
59
+ rawdata: np.ndarray, flatdata: np.ndarray, threshold: float | None = None
60
+ ) -> np.ndarray:
61
+ """Compute flat-field correction
62
+
63
+ Args:
64
+ rawdata: Raw data
65
+ flatdata: Flat-field data
66
+ threshold: Threshold for flat-field correction (default: None)
67
+
68
+ Returns:
69
+ Flat-field corrected data
70
+ """
71
+ dtemp = np.array(rawdata, dtype=float, copy=True) * np.nanmean(flatdata)
72
+ dunif = np.array(flatdata, dtype=float, copy=True)
73
+ dunif[dunif == 0] = 1.0
74
+ dcorr_all = np.array(dtemp / dunif, dtype=rawdata.dtype)
75
+ dcorr = np.array(rawdata, copy=True)
76
+ dcorr[rawdata > threshold] = dcorr_all[rawdata > threshold]
77
+ return dcorr
@@ -0,0 +1,48 @@
1
+ """
2
+ Signal/Image Data Extraction
3
+ -----------------------------
4
+
5
+ This module contains functions for extracting information and features from image data:
6
+
7
+ - Radial profile extraction
8
+ - Statistical analysis and feature extraction functions
9
+ - Data analysis utilities
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import numpy as np
15
+
16
+ from sigima.tools.checks import check_2d_array
17
+
18
+
19
+ @check_2d_array
20
+ def get_radial_profile(
21
+ data: np.ndarray, center: tuple[int, int]
22
+ ) -> tuple[np.ndarray, np.ndarray]:
23
+ """Return radial profile of image data
24
+
25
+ Args:
26
+ data: Input data (2D array)
27
+ center: Coordinates of the center of the profile (x, y)
28
+
29
+ Returns:
30
+ Radial profile (X, Y) where X is the distance from the center (1D array)
31
+ and Y is the average value of pixels at this distance (1D array)
32
+ """
33
+ y, x = np.indices((data.shape)) # Get the indices of pixels
34
+ x0, y0 = center
35
+ r = np.sqrt((x - x0) ** 2 + (y - y0) ** 2) # Calculate distance to the center
36
+ r = r.astype(int)
37
+
38
+ # Average over the same distance
39
+ tbin = np.bincount(r.ravel(), data.ravel()) # Sum of pixel values at each distance
40
+ nr = np.bincount(r.ravel()) # Number of pixels at each distance
41
+
42
+ yprofile = tbin / nr # this is the half radial profile
43
+ # Let's mirror it to get the full radial profile (the first element is the center)
44
+ yprofile = np.concatenate((yprofile[::-1], yprofile[1:]))
45
+ # The x axis is the distance from the center (0 is the center)
46
+ xprofile = np.arange(len(yprofile)) - len(yprofile) // 2
47
+
48
+ return xprofile, yprofile