1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149
|
# Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file.
"""
Contour finding test
"""
# pylint: disable=invalid-name # Allows short reference names like x, y, ...
# pylint: disable=duplicate-code
import sys
import time
import numpy as np
import pytest
import sigima.objects
import sigima.params
import sigima.proc.image
from sigima.enums import ContourShape
from sigima.tests import guiutils
from sigima.tests.data import get_peak2d_data
from sigima.tests.env import execenv
from sigima.tests.helpers import (
check_array_result,
check_scalar_result,
)
from sigima.tools.image import get_2d_peaks_coords, get_contour_shapes
@pytest.mark.gui
def test_contour_interactive():
"""2D peak detection test"""
data, _coords = get_peak2d_data()
with guiutils.lazy_qt_app_context(force=True):
# pylint: disable=import-outside-toplevel
from sigima import viz
items = [viz.create_image(data, colormap="hsv")]
t0 = time.time()
peak_coords = get_2d_peaks_coords(data)
dt = time.time() - t0
for x, y in peak_coords:
items.append(viz.create_marker(x, y))
execenv.print(f"Calculation time: {int(dt * 1e3):d} ms\n", file=sys.stderr)
execenv.print(f"Peak coordinates: {peak_coords}")
# Add contour shapes for all shape types
for shape in ContourShape:
coords = get_contour_shapes(data, shape=shape)
items.extend(viz.create_contour_shapes(coords, shape))
viz.view_image_items(items)
@pytest.mark.validation
def test_contour_shape() -> None:
"""Test contour shape computation function"""
# Create test data with known shapes
data, _expected_coords = get_peak2d_data()
# Test each contour shape type with ROI creation
for shape in ContourShape:
execenv.print(f"Testing contour shape: {shape}")
# Get contour shapes from the function
detected_shapes = get_contour_shapes(data, shape=shape)
execenv.print(f"Detected {len(detected_shapes)} {shape}(s)")
image = sigima.objects.create_image("Contour Test Image", data=data)
param = sigima.params.ContourShapeParam.create(shape=shape)
results = sigima.proc.image.contour_shape(image, param)
sigima.proc.image.apply_detection_rois(image, results)
check_array_result(f"Contour shapes ({shape})", detected_shapes, results.coords)
# Basic validation checks
assert isinstance(detected_shapes, np.ndarray), (
f"get_contour_shapes should return numpy array for {shape}"
)
if len(detected_shapes) > 0:
# Check that we detected at least some shapes
execenv.print(f"Successfully detected contours for {shape}")
# Validate shape-specific properties
if shape == ContourShape.CIRCLE:
# For circles: [xc, yc, r]
assert detected_shapes.shape[1] == 3, (
"Circle contours should have 3 parameters (xc, yc, r)"
)
# Check that radius values are positive
radii = detected_shapes[:, 2]
assert np.all(radii > 0), "All circle radii should be positive"
check_scalar_result(
"Circle radius range",
np.mean(radii),
np.mean(radii), # Just check it's finite
rtol=1.0,
)
elif shape == ContourShape.ELLIPSE:
# For ellipses: [xc, yc, a, b, theta]
assert detected_shapes.shape[1] == 5, (
"Ellipse contours should have 5 parameters (xc, yc, a, b, theta)"
)
# Check that semi-axes are positive
a_values = detected_shapes[:, 2]
b_values = detected_shapes[:, 3]
assert np.all(a_values > 0), (
"All ellipse semi-axes 'a' should be positive"
)
assert np.all(b_values > 0), (
"All ellipse semi-axes 'b' should be positive"
)
check_scalar_result(
"Ellipse semi-axis 'a' range",
np.mean(a_values),
np.mean(a_values), # Just check it's finite
rtol=1.0,
)
elif shape == ContourShape.POLYGON:
# For polygons: flattened x,y coordinates
# Shape should be (n_contours, max_points) where max_points is even
assert detected_shapes.shape[1] % 2 == 0, (
"Polygon contours should have even number of coordinates "
"(x,y pairs)"
)
# Check that we have valid coordinates (not all NaN)
valid_coords = ~np.isnan(detected_shapes)
assert np.any(valid_coords), (
"Polygon should have some valid coordinates"
)
# Check that the function handles different threshold levels
for level in [0.3, 0.5, 0.7]:
shapes_at_level = get_contour_shapes(data, shape=shape, level=level)
assert isinstance(shapes_at_level, np.ndarray), (
f"get_contour_shapes should return numpy array for {shape} "
f"at level {level}"
)
execenv.print(f" At level {level}: detected {len(shapes_at_level)} shapes")
execenv.print("All contour shape tests passed!")
if __name__ == "__main__":
test_contour_interactive()
test_contour_shape()
|