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,172 @@
|
|
|
1
|
+
# Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file.
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Contour finding test
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
# pylint: disable=invalid-name # Allows short reference names like x, y, ...
|
|
8
|
+
# pylint: disable=duplicate-code
|
|
9
|
+
|
|
10
|
+
import sys
|
|
11
|
+
import time
|
|
12
|
+
|
|
13
|
+
import numpy as np
|
|
14
|
+
import pytest
|
|
15
|
+
|
|
16
|
+
from sigima.enums import ContourShape
|
|
17
|
+
from sigima.tests import guiutils
|
|
18
|
+
from sigima.tests.data import get_peak2d_data
|
|
19
|
+
from sigima.tests.env import execenv
|
|
20
|
+
from sigima.tests.helpers import check_scalar_result
|
|
21
|
+
from sigima.tools import coordinates
|
|
22
|
+
from sigima.tools.image import get_2d_peaks_coords, get_contour_shapes
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def create_contour_shape_items(data, shape):
|
|
26
|
+
"""Create plotpy items for a specific contour shape.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
data: Input data array
|
|
30
|
+
shape: ContourShape enum value
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
List of plotpy items representing the detected contours
|
|
34
|
+
"""
|
|
35
|
+
# pylint: disable=import-outside-toplevel
|
|
36
|
+
from plotpy.builder import make
|
|
37
|
+
|
|
38
|
+
items = []
|
|
39
|
+
coords = get_contour_shapes(data, shape=shape)
|
|
40
|
+
execenv.print(f"Coordinates ({shape}s): {coords}")
|
|
41
|
+
for shapeargs in coords:
|
|
42
|
+
if shape == ContourShape.CIRCLE:
|
|
43
|
+
xc, yc, r = shapeargs
|
|
44
|
+
x0, y0, x1, y1 = coordinates.circle_to_diameter(xc, yc, r)
|
|
45
|
+
item = make.circle(x0, y0, x1, y1)
|
|
46
|
+
elif shape == ContourShape.ELLIPSE:
|
|
47
|
+
xc, yc, a, b, theta = shapeargs
|
|
48
|
+
coords_ellipse = coordinates.ellipse_to_diameters(xc, yc, a, b, theta)
|
|
49
|
+
x0, y0, x1, y1, x2, y2, x3, y3 = coords_ellipse
|
|
50
|
+
item = make.ellipse(x0, y0, x1, y1, x2, y2, x3, y3)
|
|
51
|
+
else: # ContourShape.POLYGON
|
|
52
|
+
# `shapeargs` is a flattened array of x, y coordinates
|
|
53
|
+
x, y = shapeargs[::2], shapeargs[1::2]
|
|
54
|
+
item = make.polygon(x, y, closed=False)
|
|
55
|
+
items.append(item)
|
|
56
|
+
return items
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@pytest.mark.gui
|
|
60
|
+
def test_contour_interactive():
|
|
61
|
+
"""2D peak detection test"""
|
|
62
|
+
data, _coords = get_peak2d_data()
|
|
63
|
+
with guiutils.lazy_qt_app_context(force=True):
|
|
64
|
+
# pylint: disable=import-outside-toplevel
|
|
65
|
+
from plotpy.builder import make
|
|
66
|
+
|
|
67
|
+
from sigima.tests import vistools
|
|
68
|
+
|
|
69
|
+
items = [make.image(data, interpolation="linear", colormap="hsv")]
|
|
70
|
+
t0 = time.time()
|
|
71
|
+
peak_coords = get_2d_peaks_coords(data)
|
|
72
|
+
dt = time.time() - t0
|
|
73
|
+
for x, y in peak_coords:
|
|
74
|
+
items.append(make.marker((x, y)))
|
|
75
|
+
execenv.print(f"Calculation time: {int(dt * 1e3):d} ms\n", file=sys.stderr)
|
|
76
|
+
execenv.print(f"Peak coordinates: {peak_coords}")
|
|
77
|
+
|
|
78
|
+
# Add contour shapes for all shape types
|
|
79
|
+
for shape in ContourShape:
|
|
80
|
+
items.extend(create_contour_shape_items(data, shape))
|
|
81
|
+
|
|
82
|
+
vistools.view_image_items(items)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@pytest.mark.validation
|
|
86
|
+
def test_contour_shape() -> None:
|
|
87
|
+
"""Test contour shape computation function"""
|
|
88
|
+
# Create test data with known shapes
|
|
89
|
+
data, _expected_coords = get_peak2d_data()
|
|
90
|
+
|
|
91
|
+
# Test each contour shape type
|
|
92
|
+
for shape in ContourShape:
|
|
93
|
+
execenv.print(f"Testing contour shape: {shape}")
|
|
94
|
+
|
|
95
|
+
# Get contour shapes from the function
|
|
96
|
+
detected_shapes = get_contour_shapes(data, shape=shape)
|
|
97
|
+
execenv.print(f"Detected {len(detected_shapes)} {shape}(s)")
|
|
98
|
+
|
|
99
|
+
# Basic validation checks
|
|
100
|
+
assert isinstance(detected_shapes, np.ndarray), (
|
|
101
|
+
f"get_contour_shapes should return numpy array for {shape}"
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
if len(detected_shapes) > 0:
|
|
105
|
+
# Check that we detected at least some shapes
|
|
106
|
+
execenv.print(f"Successfully detected contours for {shape}")
|
|
107
|
+
|
|
108
|
+
# Validate shape-specific properties
|
|
109
|
+
if shape == ContourShape.CIRCLE:
|
|
110
|
+
# For circles: [xc, yc, r]
|
|
111
|
+
assert detected_shapes.shape[1] == 3, (
|
|
112
|
+
"Circle contours should have 3 parameters (xc, yc, r)"
|
|
113
|
+
)
|
|
114
|
+
# Check that radius values are positive
|
|
115
|
+
radii = detected_shapes[:, 2]
|
|
116
|
+
assert np.all(radii > 0), "All circle radii should be positive"
|
|
117
|
+
check_scalar_result(
|
|
118
|
+
"Circle radius range",
|
|
119
|
+
np.mean(radii),
|
|
120
|
+
np.mean(radii), # Just check it's finite
|
|
121
|
+
rtol=1.0,
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
elif shape == ContourShape.ELLIPSE:
|
|
125
|
+
# For ellipses: [xc, yc, a, b, theta]
|
|
126
|
+
assert detected_shapes.shape[1] == 5, (
|
|
127
|
+
"Ellipse contours should have 5 parameters (xc, yc, a, b, theta)"
|
|
128
|
+
)
|
|
129
|
+
# Check that semi-axes are positive
|
|
130
|
+
a_values = detected_shapes[:, 2]
|
|
131
|
+
b_values = detected_shapes[:, 3]
|
|
132
|
+
assert np.all(a_values > 0), (
|
|
133
|
+
"All ellipse semi-axes 'a' should be positive"
|
|
134
|
+
)
|
|
135
|
+
assert np.all(b_values > 0), (
|
|
136
|
+
"All ellipse semi-axes 'b' should be positive"
|
|
137
|
+
)
|
|
138
|
+
check_scalar_result(
|
|
139
|
+
"Ellipse semi-axis 'a' range",
|
|
140
|
+
np.mean(a_values),
|
|
141
|
+
np.mean(a_values), # Just check it's finite
|
|
142
|
+
rtol=1.0,
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
elif shape == ContourShape.POLYGON:
|
|
146
|
+
# For polygons: flattened x,y coordinates
|
|
147
|
+
# Shape should be (n_contours, max_points) where max_points is even
|
|
148
|
+
assert detected_shapes.shape[1] % 2 == 0, (
|
|
149
|
+
"Polygon contours should have even number of coordinates "
|
|
150
|
+
"(x,y pairs)"
|
|
151
|
+
)
|
|
152
|
+
# Check that we have valid coordinates (not all NaN)
|
|
153
|
+
valid_coords = ~np.isnan(detected_shapes)
|
|
154
|
+
assert np.any(valid_coords), (
|
|
155
|
+
"Polygon should have some valid coordinates"
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
# Check that the function handles different threshold levels
|
|
159
|
+
for level in [0.3, 0.5, 0.7]:
|
|
160
|
+
shapes_at_level = get_contour_shapes(data, shape=shape, level=level)
|
|
161
|
+
assert isinstance(shapes_at_level, np.ndarray), (
|
|
162
|
+
f"get_contour_shapes should return numpy array for {shape} "
|
|
163
|
+
f"at level {level}"
|
|
164
|
+
)
|
|
165
|
+
execenv.print(f" At level {level}: detected {len(shapes_at_level)} shapes")
|
|
166
|
+
|
|
167
|
+
execenv.print("All contour shape tests passed!")
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
if __name__ == "__main__":
|
|
171
|
+
test_contour_interactive()
|
|
172
|
+
test_contour_shape()
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file.
|
|
2
|
+
|
|
3
|
+
"""Unit tests for image convolution/deconvolution features."""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from contextlib import contextmanager
|
|
8
|
+
|
|
9
|
+
import numpy as np
|
|
10
|
+
import pytest
|
|
11
|
+
import scipy.signal as sps
|
|
12
|
+
|
|
13
|
+
from sigima.config import options as sigima_options
|
|
14
|
+
from sigima.objects import create_image, create_image_from_param
|
|
15
|
+
from sigima.objects.image import Gauss2DParam, ImageObj, Zero2DParam
|
|
16
|
+
from sigima.proc.image.mathops import convolution, deconvolution
|
|
17
|
+
from sigima.tests import guiutils
|
|
18
|
+
from sigima.tests.helpers import check_array_result
|
|
19
|
+
from sigima.tools.image import deconvolve
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@contextmanager
|
|
23
|
+
def disable_kernel_normalization():
|
|
24
|
+
"""Context manager to temporarily disable kernel normalization."""
|
|
25
|
+
# Save current values
|
|
26
|
+
original_auto_normalize = sigima_options.auto_normalize_kernel.get()
|
|
27
|
+
|
|
28
|
+
# Disable for test
|
|
29
|
+
sigima_options.auto_normalize_kernel.set(False)
|
|
30
|
+
|
|
31
|
+
try:
|
|
32
|
+
yield
|
|
33
|
+
finally:
|
|
34
|
+
# Restore original values
|
|
35
|
+
sigima_options.auto_normalize_kernel.set(original_auto_normalize)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _generate_rectangle_image(title: str = "Rectangle", size: int = 32) -> ImageObj:
|
|
39
|
+
"""Generate a test square image with a rectangle in the center."""
|
|
40
|
+
data = np.zeros((size, size), dtype=np.float64)
|
|
41
|
+
data[size // 5 : 2 * size // 5, size // 7 : 5 * size // 7] = 1.0
|
|
42
|
+
img = create_image(title, data)
|
|
43
|
+
return img
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _generate_image(size: int = 32) -> ImageObj:
|
|
47
|
+
"""Generate a test square image.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
size: The dimension of the square image to generate.
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
An image object.
|
|
54
|
+
"""
|
|
55
|
+
# Gaussian image.
|
|
56
|
+
gauss_img = create_image_from_param(Gauss2DParam.create(height=size, width=size))
|
|
57
|
+
return gauss_img
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _generate_gaussian_kernel(size: int = 32, sigma: float = 1.0) -> ImageObj:
|
|
61
|
+
"""Generate a Gaussian kernel image.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
size: The dimension of the square kernel to generate.
|
|
65
|
+
sigma: The standard deviation of the Gaussian.
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
An image object.
|
|
69
|
+
"""
|
|
70
|
+
kernel = create_image_from_param(
|
|
71
|
+
Gauss2DParam.create(height=size, width=size, sigma=sigma)
|
|
72
|
+
)
|
|
73
|
+
return kernel
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _generate_identity_kernel(size: int = 7) -> ImageObj:
|
|
77
|
+
"""Generate an identity kernel image.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
size: The dimension of the square kernel to generate (must be odd).
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
An image object.
|
|
84
|
+
"""
|
|
85
|
+
assert size % 2 == 1, "Identity kernel size must be odd."
|
|
86
|
+
kernel = create_image_from_param(Zero2DParam.create(height=size, width=size))
|
|
87
|
+
assert kernel.data is not None
|
|
88
|
+
kernel.data[size // 2, size // 2] = 1.0
|
|
89
|
+
return kernel
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def __convolve_image(kernel: ImageObj, size: int = 32) -> tuple[ImageObj, ImageObj]:
|
|
93
|
+
"""Generate a test image and its convolution with a Gaussian kernel.
|
|
94
|
+
|
|
95
|
+
Returns:
|
|
96
|
+
A tuple (source image, kernel, convolved image).
|
|
97
|
+
"""
|
|
98
|
+
original = _generate_rectangle_image(size=size)
|
|
99
|
+
assert original.data is not None
|
|
100
|
+
convolved = convolution(original, kernel)
|
|
101
|
+
assert convolved.data is not None
|
|
102
|
+
return original, convolved
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
@pytest.mark.validation
|
|
106
|
+
def test_image_convolution(size: int = 32) -> None:
|
|
107
|
+
"""Validation test for the image convolution processing.
|
|
108
|
+
|
|
109
|
+
Note: This test disables kernel normalization to compare against raw scipy results.
|
|
110
|
+
"""
|
|
111
|
+
with disable_kernel_normalization():
|
|
112
|
+
# Test with a Gaussian kernel.
|
|
113
|
+
kernel = _generate_gaussian_kernel(size=size, sigma=1.0)
|
|
114
|
+
original, convolved = __convolve_image(kernel, size=size)
|
|
115
|
+
exp = sps.convolve(original.data, kernel.data, mode="same", method="auto")
|
|
116
|
+
check_array_result("Convolution", convolved.data, exp)
|
|
117
|
+
guiutils.view_images_side_by_side_if_gui(
|
|
118
|
+
[original, kernel, convolved],
|
|
119
|
+
["Original", "Kernel", "Convolved"],
|
|
120
|
+
title="Image Convolution Test: Gaussian Kernel",
|
|
121
|
+
)
|
|
122
|
+
# Test with an identity kernel.
|
|
123
|
+
kernel = _generate_identity_kernel(size=7)
|
|
124
|
+
original, convolved = __convolve_image(kernel, size=size)
|
|
125
|
+
# check_array_result("Convolution identity", convolved.data, original.data)
|
|
126
|
+
guiutils.view_images_side_by_side_if_gui(
|
|
127
|
+
[original, kernel, convolved],
|
|
128
|
+
["Original", "Kernel", "Convolved"],
|
|
129
|
+
title="Image Convolution Test: Identity Kernel",
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
@pytest.mark.validation
|
|
134
|
+
def test_image_deconvolution(size: int = 32) -> None:
|
|
135
|
+
"""Validation test for image deconvolution.
|
|
136
|
+
|
|
137
|
+
Note: This test disables kernel normalization to compare against expected results.
|
|
138
|
+
"""
|
|
139
|
+
with disable_kernel_normalization():
|
|
140
|
+
# Test with an identity kernel.
|
|
141
|
+
kernel = _generate_identity_kernel(size=31)
|
|
142
|
+
original, convolved = __convolve_image(kernel, size=size)
|
|
143
|
+
deconvolved = deconvolution(convolved, kernel)
|
|
144
|
+
guiutils.view_images_side_by_side_if_gui(
|
|
145
|
+
[original, kernel, convolved, deconvolved],
|
|
146
|
+
["Original", "Kernel", "Convolved", "Deconvolved"],
|
|
147
|
+
title="Image Deconvolution Test: Identity Kernel",
|
|
148
|
+
)
|
|
149
|
+
check_array_result("Deconvolution identity", deconvolved.data, original.data)
|
|
150
|
+
|
|
151
|
+
# # Test with a Gaussian kernel.
|
|
152
|
+
kernel = _generate_gaussian_kernel(size=31, sigma=1.0)
|
|
153
|
+
original, convolved = __convolve_image(kernel, size=64)
|
|
154
|
+
deconvolved = deconvolution(convolved, kernel)
|
|
155
|
+
# check_array_result("Deconvolution Gaussian", deconvolved.data, original.data)
|
|
156
|
+
guiutils.view_images_side_by_side_if_gui(
|
|
157
|
+
[original, kernel, convolved, deconvolved],
|
|
158
|
+
["Original", "Kernel", "Convolved", "Deconvolved"],
|
|
159
|
+
title="Image Deconvolution Test: Gaussian Kernel",
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def test_tools_image_deconvolve_null_kernel() -> None:
|
|
164
|
+
"""Test deconvolution with a null kernel."""
|
|
165
|
+
size = 32
|
|
166
|
+
src = _generate_image(size)
|
|
167
|
+
assert src.data is not None
|
|
168
|
+
kernel = create_image_from_param(Zero2DParam.create(height=size, width=size))
|
|
169
|
+
assert kernel.data is not None
|
|
170
|
+
with pytest.raises(ValueError, match="Deconvolution kernel cannot be null."):
|
|
171
|
+
deconvolve(src.data, kernel.data)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
if __name__ == "__main__":
|
|
175
|
+
guiutils.enable_gui()
|
|
176
|
+
test_image_convolution()
|
|
177
|
+
test_image_deconvolution()
|
|
178
|
+
test_tools_image_deconvolve_null_kernel()
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file.
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Unit tests for image data types
|
|
5
|
+
-------------------------------
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
# pylint: disable=invalid-name # Allows short reference names like x, y, ...
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import numpy as np
|
|
14
|
+
|
|
15
|
+
from sigima.objects import ImageDatatypes
|
|
16
|
+
from sigima.tests.env import execenv
|
|
17
|
+
from sigima.tools.datatypes import clip_astype
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def get_integer_datatypes() -> list[ImageDatatypes]:
|
|
21
|
+
"""Return all integer data types."""
|
|
22
|
+
return [dtype for dtype in ImageDatatypes if "int" in dtype.name.lower()]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def test_clip_astype():
|
|
26
|
+
"""Test `clip_astype` algorithm"""
|
|
27
|
+
# Test that function do nothing for certain data types
|
|
28
|
+
for dtype1 in ImageDatatypes:
|
|
29
|
+
for dtype2 in ImageDatatypes:
|
|
30
|
+
if not np.issubdtype(dtype2.value, np.integer):
|
|
31
|
+
continue
|
|
32
|
+
if np.issubdtype(dtype1.value, np.integer):
|
|
33
|
+
info1 = np.iinfo(dtype1.value)
|
|
34
|
+
else:
|
|
35
|
+
info1 = np.finfo(dtype1.value)
|
|
36
|
+
info2 = np.iinfo(dtype2.value)
|
|
37
|
+
if info2.min < info1.min or info2.max > info1.max:
|
|
38
|
+
continue
|
|
39
|
+
data = np.array([0, 1, 2, 3, 4, 5], dtype=dtype1.value)
|
|
40
|
+
txt = f"No change: {dtype1.value} -> {dtype2.value}"
|
|
41
|
+
execenv.print(txt, end="... ")
|
|
42
|
+
assert np.array_equal(clip_astype(data, dtype2.value), data), txt
|
|
43
|
+
execenv.print("OK")
|
|
44
|
+
|
|
45
|
+
# Test that function handles overflow for integer data types
|
|
46
|
+
for dtype in get_integer_datatypes():
|
|
47
|
+
maxval = np.iinfo(dtype.value).max
|
|
48
|
+
data1 = np.array([maxval], dtype=dtype.value)
|
|
49
|
+
data2 = clip_astype(data1.astype(float) + 1, dtype.value)
|
|
50
|
+
txt = f"Overflow: {dtype.value}"
|
|
51
|
+
execenv.print(txt, end="... ")
|
|
52
|
+
assert data2[0] == maxval, txt
|
|
53
|
+
execenv.print("OK")
|
|
54
|
+
|
|
55
|
+
# Test that function handles underflow for integer data types
|
|
56
|
+
for dtype in get_integer_datatypes():
|
|
57
|
+
minval = np.iinfo(dtype.value).min
|
|
58
|
+
data1 = np.array([minval], dtype=dtype.value)
|
|
59
|
+
data2 = clip_astype(data1.astype(float) - 1, dtype.value)
|
|
60
|
+
txt = f"Underflow: {dtype.value}"
|
|
61
|
+
execenv.print(txt, end="... ")
|
|
62
|
+
assert data2[0] == minval, txt
|
|
63
|
+
execenv.print("OK")
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
if __name__ == "__main__":
|
|
67
|
+
test_clip_astype()
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file.
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Unit tests for edge detection computation functions.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import pytest
|
|
10
|
+
from skimage import feature, filters, util
|
|
11
|
+
|
|
12
|
+
import sigima.objects
|
|
13
|
+
import sigima.params
|
|
14
|
+
import sigima.proc.image
|
|
15
|
+
from sigima.tests.data import get_test_image
|
|
16
|
+
from sigima.tests.helpers import check_array_result
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@pytest.mark.validation
|
|
20
|
+
def test_canny() -> None:
|
|
21
|
+
"""Validation test for the image Canny edge detection processing."""
|
|
22
|
+
# See [1] in sigima\tests\image\__init__.py for more details about the validation.
|
|
23
|
+
src = get_test_image("flower.npy")
|
|
24
|
+
p = sigima.params.CannyParam.create(
|
|
25
|
+
sigma=1.0, low_threshold=0.1, high_threshold=0.2
|
|
26
|
+
)
|
|
27
|
+
dst = sigima.proc.image.canny(src, p)
|
|
28
|
+
exp = util.img_as_ubyte(
|
|
29
|
+
feature.canny(
|
|
30
|
+
src.data,
|
|
31
|
+
sigma=p.sigma,
|
|
32
|
+
low_threshold=p.low_threshold,
|
|
33
|
+
high_threshold=p.high_threshold,
|
|
34
|
+
use_quantiles=p.use_quantiles,
|
|
35
|
+
mode=p.mode,
|
|
36
|
+
cval=p.cval,
|
|
37
|
+
)
|
|
38
|
+
)
|
|
39
|
+
check_array_result(
|
|
40
|
+
f"Canny[sigma={p.sigma},low_threshold={p.low_threshold},"
|
|
41
|
+
f"high_threshold={p.high_threshold}]",
|
|
42
|
+
dst.data,
|
|
43
|
+
exp,
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def __generic_edge_validation(method: str) -> None:
|
|
48
|
+
"""Generic test for edge detection methods."""
|
|
49
|
+
# See [1] in sigima\tests\image\__init__.py for more details about the validation.
|
|
50
|
+
src = get_test_image("flower.npy")
|
|
51
|
+
dst: sigima.objects.ImageObj = getattr(sigima.proc.image, method)(src)
|
|
52
|
+
exp = getattr(filters, method)(src.data)
|
|
53
|
+
check_array_result(f"{method.capitalize()}", dst.data, exp)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@pytest.mark.validation
|
|
57
|
+
def test_roberts() -> None:
|
|
58
|
+
"""Validation test for the image Roberts edge detection processing."""
|
|
59
|
+
__generic_edge_validation("roberts")
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@pytest.mark.validation
|
|
63
|
+
def test_prewitt() -> None:
|
|
64
|
+
"""Validation test for the image Prewitt edge detection processing."""
|
|
65
|
+
__generic_edge_validation("prewitt")
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@pytest.mark.validation
|
|
69
|
+
def test_prewitt_h() -> None:
|
|
70
|
+
"""Validation test for the image horizontal Prewitt edge detection processing."""
|
|
71
|
+
__generic_edge_validation("prewitt_h")
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@pytest.mark.validation
|
|
75
|
+
def test_prewitt_v() -> None:
|
|
76
|
+
"""Validation test for the image vertical Prewitt edge detection processing."""
|
|
77
|
+
__generic_edge_validation("prewitt_v")
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
@pytest.mark.validation
|
|
81
|
+
def test_sobel() -> None:
|
|
82
|
+
"""Validation test for the image Sobel edge detection processing."""
|
|
83
|
+
__generic_edge_validation("sobel")
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@pytest.mark.validation
|
|
87
|
+
def test_sobel_h() -> None:
|
|
88
|
+
"""Validation test for the image horizontal Sobel edge detection processing."""
|
|
89
|
+
__generic_edge_validation("sobel_h")
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@pytest.mark.validation
|
|
93
|
+
def test_sobel_v() -> None:
|
|
94
|
+
"""Validation test for the image vertical Sobel edge detection processing."""
|
|
95
|
+
__generic_edge_validation("sobel_v")
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
@pytest.mark.validation
|
|
99
|
+
def test_scharr() -> None:
|
|
100
|
+
"""Validation test for the image Scharr edge detection processing."""
|
|
101
|
+
__generic_edge_validation("scharr")
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
@pytest.mark.validation
|
|
105
|
+
def test_scharr_h() -> None:
|
|
106
|
+
"""Validation test for the image horizontal Scharr edge detection processing."""
|
|
107
|
+
__generic_edge_validation("scharr_h")
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
@pytest.mark.validation
|
|
111
|
+
def test_scharr_v() -> None:
|
|
112
|
+
"""Validation test for the image vertical Scharr edge detection processing."""
|
|
113
|
+
__generic_edge_validation("scharr_v")
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
@pytest.mark.validation
|
|
117
|
+
def test_farid() -> None:
|
|
118
|
+
"""Validation test for the image Farid edge detection processing."""
|
|
119
|
+
__generic_edge_validation("farid")
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
@pytest.mark.validation
|
|
123
|
+
def test_farid_h() -> None:
|
|
124
|
+
"""Validation test for the image horizontal Farid edge detection processing."""
|
|
125
|
+
__generic_edge_validation("farid_h")
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
@pytest.mark.validation
|
|
129
|
+
def test_farid_v() -> None:
|
|
130
|
+
"""Validation test for the image vertical Farid edge detection processing."""
|
|
131
|
+
__generic_edge_validation("farid_v")
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
@pytest.mark.validation
|
|
135
|
+
def test_laplace() -> None:
|
|
136
|
+
"""Validation test for the image Laplace edge detection processing."""
|
|
137
|
+
__generic_edge_validation("laplace")
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
if __name__ == "__main__":
|
|
141
|
+
test_canny()
|
|
142
|
+
test_roberts()
|
|
143
|
+
test_prewitt()
|
|
144
|
+
test_prewitt_h()
|
|
145
|
+
test_prewitt_v()
|
|
146
|
+
test_sobel()
|
|
147
|
+
test_sobel_h()
|
|
148
|
+
test_sobel_v()
|
|
149
|
+
test_scharr()
|
|
150
|
+
test_scharr_h()
|
|
151
|
+
test_scharr_v()
|
|
152
|
+
test_farid()
|
|
153
|
+
test_farid_h()
|
|
154
|
+
test_farid_v()
|
|
155
|
+
test_laplace()
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file.
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Enclosing circle test
|
|
5
|
+
|
|
6
|
+
Testing enclsoing circle function on various test images.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
# pylint: disable=invalid-name # Allows short reference names like x, y, ...
|
|
10
|
+
# pylint: disable=duplicate-code
|
|
11
|
+
|
|
12
|
+
import pytest
|
|
13
|
+
|
|
14
|
+
import sigima.tools.image
|
|
15
|
+
from sigima.config import _
|
|
16
|
+
from sigima.proc.image import enclosing_circle
|
|
17
|
+
from sigima.tests import guiutils
|
|
18
|
+
from sigima.tests.data import RingParam, create_ring_image, get_laser_spot_data
|
|
19
|
+
from sigima.tests.env import execenv
|
|
20
|
+
from sigima.tests.helpers import check_scalar_result
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def __enclosingcircle_test(data):
|
|
24
|
+
"""Enclosing circle test function"""
|
|
25
|
+
# pylint: disable=import-outside-toplevel
|
|
26
|
+
from plotpy.builder import make
|
|
27
|
+
|
|
28
|
+
from sigima.tests import vistools
|
|
29
|
+
|
|
30
|
+
items = []
|
|
31
|
+
items += [make.image(data, interpolation="nearest", eliminate_outliers=1.0)]
|
|
32
|
+
|
|
33
|
+
# Computing centroid coordinates
|
|
34
|
+
row, col = sigima.tools.image.get_centroid_auto(data)
|
|
35
|
+
label = _("Centroid") + " (%d, %d)"
|
|
36
|
+
execenv.print(label % (row, col))
|
|
37
|
+
cursor = make.xcursor(col, row, label=label)
|
|
38
|
+
cursor.set_resizable(False)
|
|
39
|
+
cursor.set_movable(False)
|
|
40
|
+
items.append(cursor)
|
|
41
|
+
|
|
42
|
+
x, y, radius = sigima.tools.image.get_enclosing_circle(data)
|
|
43
|
+
circle = make.circle(x - radius, y - radius, x + radius, y + radius)
|
|
44
|
+
circle.set_readonly(True)
|
|
45
|
+
circle.set_resizable(False)
|
|
46
|
+
circle.set_movable(False)
|
|
47
|
+
items.append(circle)
|
|
48
|
+
execenv.print(x, y, radius)
|
|
49
|
+
execenv.print("")
|
|
50
|
+
|
|
51
|
+
vistools.view_image_items(items)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@pytest.mark.gui
|
|
55
|
+
def test_enclosing_circle_interactive():
|
|
56
|
+
"""Interactive test for enclosing circle computation."""
|
|
57
|
+
with guiutils.lazy_qt_app_context(force=True):
|
|
58
|
+
for data in get_laser_spot_data():
|
|
59
|
+
__enclosingcircle_test(data)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@pytest.mark.validation
|
|
63
|
+
def test_image_enclosing_circle():
|
|
64
|
+
"""Test enclosing circle on a ring image."""
|
|
65
|
+
p = RingParam.create(
|
|
66
|
+
image_size=200,
|
|
67
|
+
xc=100,
|
|
68
|
+
yc=100,
|
|
69
|
+
radius=30,
|
|
70
|
+
thickness=5,
|
|
71
|
+
)
|
|
72
|
+
# Create a ring image, so that the outer circle radius is radius + thickness:
|
|
73
|
+
obj = create_ring_image(p)
|
|
74
|
+
execenv.print("Testing enclosing circle on a ring image...")
|
|
75
|
+
ex, ey, er = sigima.tools.image.get_enclosing_circle(obj.data)
|
|
76
|
+
geometry = enclosing_circle(obj)
|
|
77
|
+
x, y, r = geometry.coords[0]
|
|
78
|
+
execenv.print(geometry)
|
|
79
|
+
assert ex == x and ey == y and er == r, (
|
|
80
|
+
f"Enclosing circle test failed: expected ({ex}, {ey}, {er}), "
|
|
81
|
+
f"got ({x}, {y}, {r})"
|
|
82
|
+
)
|
|
83
|
+
check_scalar_result("Enclosing circle", er, p.radius + p.thickness, rtol=0.002)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
if __name__ == "__main__":
|
|
87
|
+
test_enclosing_circle_interactive()
|
|
88
|
+
test_image_enclosing_circle()
|