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.
- sigima/__init__.py +142 -2
- sigima/client/__init__.py +105 -0
- sigima/client/base.py +780 -0
- sigima/client/remote.py +469 -0
- sigima/client/stub.py +814 -0
- sigima/client/utils.py +90 -0
- sigima/config.py +444 -0
- sigima/data/logo/Sigima.svg +135 -0
- sigima/data/tests/annotations.json +798 -0
- sigima/data/tests/curve_fitting/exponential_fit.txt +511 -0
- sigima/data/tests/curve_fitting/gaussian_fit.txt +100 -0
- sigima/data/tests/curve_fitting/piecewiseexponential_fit.txt +1022 -0
- sigima/data/tests/curve_fitting/polynomial_fit.txt +100 -0
- sigima/data/tests/curve_fitting/twohalfgaussian_fit.txt +1000 -0
- sigima/data/tests/curve_formats/bandwidth.txt +201 -0
- sigima/data/tests/curve_formats/boxcar.npy +0 -0
- sigima/data/tests/curve_formats/datetime.txt +1001 -0
- sigima/data/tests/curve_formats/dynamic_parameters.txt +4000 -0
- sigima/data/tests/curve_formats/fw1e2.txt +301 -0
- sigima/data/tests/curve_formats/fwhm.txt +319 -0
- sigima/data/tests/curve_formats/multiple_curves.csv +29 -0
- sigima/data/tests/curve_formats/noised_saw.mat +0 -0
- sigima/data/tests/curve_formats/oscilloscope.csv +111 -0
- sigima/data/tests/curve_formats/other/other2/recursive2.txt +5 -0
- sigima/data/tests/curve_formats/other/recursive1.txt +5 -0
- sigima/data/tests/curve_formats/paracetamol.npy +0 -0
- sigima/data/tests/curve_formats/paracetamol.txt +1010 -0
- sigima/data/tests/curve_formats/paracetamol_dx_dy.csv +1000 -0
- sigima/data/tests/curve_formats/paracetamol_dy.csv +1001 -0
- sigima/data/tests/curve_formats/pulse1.npy +0 -0
- sigima/data/tests/curve_formats/pulse2.npy +0 -0
- sigima/data/tests/curve_formats/simple.txt +5 -0
- sigima/data/tests/curve_formats/spectrum.mca +2139 -0
- sigima/data/tests/curve_formats/square2.npy +0 -0
- sigima/data/tests/curve_formats/step.npy +0 -0
- sigima/data/tests/fabry-perot1.jpg +0 -0
- sigima/data/tests/fabry-perot2.jpg +0 -0
- sigima/data/tests/flower.npy +0 -0
- sigima/data/tests/image_formats/NF 180338201.scor-data +11003 -0
- sigima/data/tests/image_formats/binary_image.npy +0 -0
- sigima/data/tests/image_formats/binary_image.png +0 -0
- sigima/data/tests/image_formats/centroid_test.npy +0 -0
- sigima/data/tests/image_formats/coordinated_text/complex_image.txt +10011 -0
- sigima/data/tests/image_formats/coordinated_text/complex_ref_image.txt +10010 -0
- sigima/data/tests/image_formats/coordinated_text/image.txt +15 -0
- sigima/data/tests/image_formats/coordinated_text/image2.txt +14 -0
- sigima/data/tests/image_formats/coordinated_text/image_no_unit_no_label.txt +14 -0
- sigima/data/tests/image_formats/coordinated_text/image_with_nan.txt +15 -0
- sigima/data/tests/image_formats/coordinated_text/image_with_unit.txt +14 -0
- sigima/data/tests/image_formats/fiber.csv +480 -0
- sigima/data/tests/image_formats/fiber.jpg +0 -0
- sigima/data/tests/image_formats/fiber.png +0 -0
- sigima/data/tests/image_formats/fiber.txt +480 -0
- sigima/data/tests/image_formats/gaussian_spot_with_noise.npy +0 -0
- sigima/data/tests/image_formats/mr-brain.dcm +0 -0
- sigima/data/tests/image_formats/noised_gaussian.mat +0 -0
- sigima/data/tests/image_formats/sif_reader/nd_lum_image_no_glue.sif +0 -0
- sigima/data/tests/image_formats/sif_reader/raman1.sif +0 -0
- sigima/data/tests/image_formats/tiling.txt +10 -0
- sigima/data/tests/image_formats/uint16.tiff +0 -0
- sigima/data/tests/image_formats/uint8.tiff +0 -0
- sigima/data/tests/laser_beam/TEM00_z_13.jpg +0 -0
- sigima/data/tests/laser_beam/TEM00_z_18.jpg +0 -0
- sigima/data/tests/laser_beam/TEM00_z_23.jpg +0 -0
- sigima/data/tests/laser_beam/TEM00_z_30.jpg +0 -0
- sigima/data/tests/laser_beam/TEM00_z_35.jpg +0 -0
- sigima/data/tests/laser_beam/TEM00_z_40.jpg +0 -0
- sigima/data/tests/laser_beam/TEM00_z_45.jpg +0 -0
- sigima/data/tests/laser_beam/TEM00_z_50.jpg +0 -0
- sigima/data/tests/laser_beam/TEM00_z_55.jpg +0 -0
- sigima/data/tests/laser_beam/TEM00_z_60.jpg +0 -0
- sigima/data/tests/laser_beam/TEM00_z_65.jpg +0 -0
- sigima/data/tests/laser_beam/TEM00_z_70.jpg +0 -0
- sigima/data/tests/laser_beam/TEM00_z_75.jpg +0 -0
- sigima/data/tests/laser_beam/TEM00_z_80.jpg +0 -0
- sigima/enums.py +195 -0
- sigima/io/__init__.py +123 -0
- sigima/io/base.py +311 -0
- sigima/io/common/__init__.py +5 -0
- sigima/io/common/basename.py +164 -0
- sigima/io/common/converters.py +189 -0
- sigima/io/common/objmeta.py +181 -0
- sigima/io/common/textreader.py +58 -0
- sigima/io/convenience.py +157 -0
- sigima/io/enums.py +17 -0
- sigima/io/ftlab.py +395 -0
- sigima/io/image/__init__.py +9 -0
- sigima/io/image/base.py +177 -0
- sigima/io/image/formats.py +1016 -0
- sigima/io/image/funcs.py +414 -0
- sigima/io/signal/__init__.py +9 -0
- sigima/io/signal/base.py +129 -0
- sigima/io/signal/formats.py +290 -0
- sigima/io/signal/funcs.py +723 -0
- sigima/objects/__init__.py +260 -0
- sigima/objects/base.py +937 -0
- sigima/objects/image/__init__.py +88 -0
- sigima/objects/image/creation.py +556 -0
- sigima/objects/image/object.py +524 -0
- sigima/objects/image/roi.py +904 -0
- sigima/objects/scalar/__init__.py +57 -0
- sigima/objects/scalar/common.py +215 -0
- sigima/objects/scalar/geometry.py +502 -0
- sigima/objects/scalar/table.py +784 -0
- sigima/objects/shape.py +290 -0
- sigima/objects/signal/__init__.py +133 -0
- sigima/objects/signal/constants.py +27 -0
- sigima/objects/signal/creation.py +1428 -0
- sigima/objects/signal/object.py +444 -0
- sigima/objects/signal/roi.py +274 -0
- sigima/params.py +405 -0
- sigima/proc/__init__.py +96 -0
- sigima/proc/base.py +381 -0
- sigima/proc/decorator.py +330 -0
- sigima/proc/image/__init__.py +513 -0
- sigima/proc/image/arithmetic.py +335 -0
- sigima/proc/image/base.py +260 -0
- sigima/proc/image/detection.py +519 -0
- sigima/proc/image/edges.py +329 -0
- sigima/proc/image/exposure.py +406 -0
- sigima/proc/image/extraction.py +458 -0
- sigima/proc/image/filtering.py +219 -0
- sigima/proc/image/fourier.py +147 -0
- sigima/proc/image/geometry.py +661 -0
- sigima/proc/image/mathops.py +340 -0
- sigima/proc/image/measurement.py +195 -0
- sigima/proc/image/morphology.py +155 -0
- sigima/proc/image/noise.py +107 -0
- sigima/proc/image/preprocessing.py +182 -0
- sigima/proc/image/restoration.py +235 -0
- sigima/proc/image/threshold.py +217 -0
- sigima/proc/image/transformations.py +393 -0
- sigima/proc/signal/__init__.py +376 -0
- sigima/proc/signal/analysis.py +206 -0
- sigima/proc/signal/arithmetic.py +551 -0
- sigima/proc/signal/base.py +262 -0
- sigima/proc/signal/extraction.py +60 -0
- sigima/proc/signal/features.py +310 -0
- sigima/proc/signal/filtering.py +484 -0
- sigima/proc/signal/fitting.py +276 -0
- sigima/proc/signal/fourier.py +259 -0
- sigima/proc/signal/mathops.py +420 -0
- sigima/proc/signal/processing.py +580 -0
- sigima/proc/signal/stability.py +175 -0
- sigima/proc/title_formatting.py +227 -0
- sigima/proc/validation.py +272 -0
- sigima/tests/__init__.py +7 -0
- sigima/tests/common/__init__.py +0 -0
- sigima/tests/common/arithmeticparam_unit_test.py +26 -0
- sigima/tests/common/basename_unit_test.py +126 -0
- sigima/tests/common/client_unit_test.py +412 -0
- sigima/tests/common/converters_unit_test.py +77 -0
- sigima/tests/common/decorator_unit_test.py +176 -0
- sigima/tests/common/examples_unit_test.py +104 -0
- sigima/tests/common/kernel_normalization_unit_test.py +242 -0
- sigima/tests/common/roi_basic_unit_test.py +73 -0
- sigima/tests/common/roi_geometry_unit_test.py +171 -0
- sigima/tests/common/scalar_builder_unit_test.py +142 -0
- sigima/tests/common/scalar_unit_test.py +991 -0
- sigima/tests/common/shape_unit_test.py +183 -0
- sigima/tests/common/stat_unit_test.py +138 -0
- sigima/tests/common/title_formatting_unit_test.py +338 -0
- sigima/tests/common/tools_coordinates_unit_test.py +60 -0
- sigima/tests/common/transformations_unit_test.py +178 -0
- sigima/tests/common/validation_unit_test.py +205 -0
- sigima/tests/conftest.py +129 -0
- sigima/tests/data.py +998 -0
- sigima/tests/env.py +280 -0
- sigima/tests/guiutils.py +163 -0
- sigima/tests/helpers.py +532 -0
- sigima/tests/image/__init__.py +28 -0
- sigima/tests/image/binning_unit_test.py +128 -0
- sigima/tests/image/blob_detection_unit_test.py +312 -0
- sigima/tests/image/centroid_unit_test.py +170 -0
- sigima/tests/image/check_2d_array_unit_test.py +63 -0
- sigima/tests/image/contour_unit_test.py +172 -0
- sigima/tests/image/convolution_unit_test.py +178 -0
- sigima/tests/image/datatype_unit_test.py +67 -0
- sigima/tests/image/edges_unit_test.py +155 -0
- sigima/tests/image/enclosingcircle_unit_test.py +88 -0
- sigima/tests/image/exposure_unit_test.py +223 -0
- sigima/tests/image/fft2d_unit_test.py +189 -0
- sigima/tests/image/filtering_unit_test.py +166 -0
- sigima/tests/image/geometry_unit_test.py +654 -0
- sigima/tests/image/hough_circle_unit_test.py +147 -0
- sigima/tests/image/imageobj_unit_test.py +737 -0
- sigima/tests/image/morphology_unit_test.py +71 -0
- sigima/tests/image/noise_unit_test.py +57 -0
- sigima/tests/image/offset_correction_unit_test.py +72 -0
- sigima/tests/image/operation_unit_test.py +518 -0
- sigima/tests/image/peak2d_limits_unit_test.py +41 -0
- sigima/tests/image/peak2d_unit_test.py +133 -0
- sigima/tests/image/profile_unit_test.py +159 -0
- sigima/tests/image/projections_unit_test.py +121 -0
- sigima/tests/image/restoration_unit_test.py +141 -0
- sigima/tests/image/roi2dparam_unit_test.py +53 -0
- sigima/tests/image/roi_advanced_unit_test.py +588 -0
- sigima/tests/image/roi_grid_unit_test.py +279 -0
- sigima/tests/image/spectrum2d_unit_test.py +40 -0
- sigima/tests/image/threshold_unit_test.py +91 -0
- sigima/tests/io/__init__.py +0 -0
- sigima/tests/io/addnewformat_unit_test.py +125 -0
- sigima/tests/io/convenience_funcs_unit_test.py +470 -0
- sigima/tests/io/coordinated_text_format_unit_test.py +495 -0
- sigima/tests/io/datetime_csv_unit_test.py +198 -0
- sigima/tests/io/imageio_formats_test.py +41 -0
- sigima/tests/io/ioregistry_unit_test.py +69 -0
- sigima/tests/io/objmeta_unit_test.py +87 -0
- sigima/tests/io/readobj_unit_test.py +130 -0
- sigima/tests/io/readwriteobj_unit_test.py +67 -0
- sigima/tests/signal/__init__.py +0 -0
- sigima/tests/signal/analysis_unit_test.py +135 -0
- sigima/tests/signal/check_1d_arrays_unit_test.py +169 -0
- sigima/tests/signal/convolution_unit_test.py +404 -0
- sigima/tests/signal/datetime_unit_test.py +176 -0
- sigima/tests/signal/fft1d_unit_test.py +303 -0
- sigima/tests/signal/filters_unit_test.py +403 -0
- sigima/tests/signal/fitting_unit_test.py +929 -0
- sigima/tests/signal/fwhm_unit_test.py +111 -0
- sigima/tests/signal/noise_unit_test.py +128 -0
- sigima/tests/signal/offset_correction_unit_test.py +34 -0
- sigima/tests/signal/operation_unit_test.py +489 -0
- sigima/tests/signal/peakdetection_unit_test.py +145 -0
- sigima/tests/signal/processing_unit_test.py +657 -0
- sigima/tests/signal/pulse/__init__.py +112 -0
- sigima/tests/signal/pulse/crossing_times_unit_test.py +123 -0
- sigima/tests/signal/pulse/plateau_detection_unit_test.py +102 -0
- sigima/tests/signal/pulse/pulse_unit_test.py +1824 -0
- sigima/tests/signal/roi_advanced_unit_test.py +392 -0
- sigima/tests/signal/signalobj_unit_test.py +603 -0
- sigima/tests/signal/stability_unit_test.py +431 -0
- sigima/tests/signal/uncertainty_unit_test.py +611 -0
- sigima/tests/vistools.py +1030 -0
- sigima/tools/__init__.py +59 -0
- sigima/tools/checks.py +290 -0
- sigima/tools/coordinates.py +308 -0
- sigima/tools/datatypes.py +26 -0
- sigima/tools/image/__init__.py +97 -0
- sigima/tools/image/detection.py +451 -0
- sigima/tools/image/exposure.py +77 -0
- sigima/tools/image/extraction.py +48 -0
- sigima/tools/image/fourier.py +260 -0
- sigima/tools/image/geometry.py +190 -0
- sigima/tools/image/preprocessing.py +165 -0
- sigima/tools/signal/__init__.py +86 -0
- sigima/tools/signal/dynamic.py +254 -0
- sigima/tools/signal/features.py +135 -0
- sigima/tools/signal/filtering.py +171 -0
- sigima/tools/signal/fitting.py +1171 -0
- sigima/tools/signal/fourier.py +466 -0
- sigima/tools/signal/interpolation.py +70 -0
- sigima/tools/signal/peakdetection.py +126 -0
- sigima/tools/signal/pulse.py +1626 -0
- sigima/tools/signal/scaling.py +50 -0
- sigima/tools/signal/stability.py +258 -0
- sigima/tools/signal/windowing.py +90 -0
- sigima/worker.py +79 -0
- sigima-1.0.0.dist-info/METADATA +233 -0
- sigima-1.0.0.dist-info/RECORD +262 -0
- {sigima-0.0.1.dev0.dist-info → sigima-1.0.0.dist-info}/licenses/LICENSE +29 -29
- sigima-0.0.1.dev0.dist-info/METADATA +0 -60
- sigima-0.0.1.dev0.dist-info/RECORD +0 -6
- {sigima-0.0.1.dev0.dist-info → sigima-1.0.0.dist-info}/WHEEL +0 -0
- {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
|