624 lines
26 KiB
Python
624 lines
26 KiB
Python
|
import math
|
||
|
import unittest
|
||
|
|
||
|
import numpy as np
|
||
|
from numpy.testing import assert_equal
|
||
|
from pytest import raises, warns
|
||
|
|
||
|
from skimage.morphology import extrema
|
||
|
|
||
|
eps = 1e-12
|
||
|
|
||
|
|
||
|
def diff(a, b):
|
||
|
a = np.asarray(a, dtype=np.float64)
|
||
|
b = np.asarray(b, dtype=np.float64)
|
||
|
t = ((a - b) ** 2).sum()
|
||
|
return math.sqrt(t)
|
||
|
|
||
|
|
||
|
class TestExtrema(unittest.TestCase):
|
||
|
|
||
|
def test_saturated_arithmetic(self):
|
||
|
"""Adding/subtracting a constant and clipping"""
|
||
|
# Test for unsigned integer
|
||
|
data = np.array([[250, 251, 5, 5],
|
||
|
[100, 200, 253, 252],
|
||
|
[4, 10, 1, 3]],
|
||
|
dtype=np.uint8)
|
||
|
# adding the constant
|
||
|
img_constant_added = extrema._add_constant_clip(data, 4)
|
||
|
expected = np.array([[254, 255, 9, 9],
|
||
|
[104, 204, 255, 255],
|
||
|
[8, 14, 5, 7]],
|
||
|
dtype=np.uint8)
|
||
|
error = diff(img_constant_added, expected)
|
||
|
assert error < eps
|
||
|
img_constant_subtracted = extrema._subtract_constant_clip(data, 4)
|
||
|
expected = np.array([[246, 247, 1, 1],
|
||
|
[96, 196, 249, 248],
|
||
|
[0, 6, 0, 0]],
|
||
|
dtype=np.uint8)
|
||
|
error = diff(img_constant_subtracted, expected)
|
||
|
assert error < eps
|
||
|
|
||
|
# Test for signed integer
|
||
|
data = np.array([[32767, 32766],
|
||
|
[-32768, -32767]],
|
||
|
dtype=np.int16)
|
||
|
img_constant_added = extrema._add_constant_clip(data, 1)
|
||
|
expected = np.array([[32767, 32767],
|
||
|
[-32767, -32766]],
|
||
|
dtype=np.int16)
|
||
|
error = diff(img_constant_added, expected)
|
||
|
assert error < eps
|
||
|
img_constant_subtracted = extrema._subtract_constant_clip(data, 1)
|
||
|
expected = np.array([[32766, 32765],
|
||
|
[-32768, -32768]],
|
||
|
dtype=np.int16)
|
||
|
error = diff(img_constant_subtracted, expected)
|
||
|
assert error < eps
|
||
|
|
||
|
def test_h_maxima(self):
|
||
|
"""h-maxima for various data types"""
|
||
|
|
||
|
data = np.array([[10, 11, 13, 14, 14, 15, 14, 14, 13, 11],
|
||
|
[11, 13, 15, 16, 16, 16, 16, 16, 15, 13],
|
||
|
[13, 15, 40, 40, 18, 18, 18, 60, 60, 15],
|
||
|
[14, 16, 40, 40, 19, 19, 19, 60, 60, 16],
|
||
|
[14, 16, 18, 19, 19, 19, 19, 19, 18, 16],
|
||
|
[15, 16, 18, 19, 19, 20, 19, 19, 18, 16],
|
||
|
[14, 16, 18, 19, 19, 19, 19, 19, 18, 16],
|
||
|
[14, 16, 80, 80, 19, 19, 19, 100, 100, 16],
|
||
|
[13, 15, 80, 80, 18, 18, 18, 100, 100, 15],
|
||
|
[11, 13, 15, 16, 16, 16, 16, 16, 15, 13]],
|
||
|
dtype=np.uint8)
|
||
|
|
||
|
expected_result = np.array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||
|
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||
|
[0, 0, 0, 0, 0, 0, 0, 1, 1, 0],
|
||
|
[0, 0, 0, 0, 0, 0, 0, 1, 1, 0],
|
||
|
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||
|
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||
|
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||
|
[0, 0, 1, 1, 0, 0, 0, 1, 1, 0],
|
||
|
[0, 0, 1, 1, 0, 0, 0, 1, 1, 0],
|
||
|
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]],
|
||
|
dtype=np.uint8)
|
||
|
for dtype in [np.uint8, np.uint64, np.int8, np.int64]:
|
||
|
data = data.astype(dtype)
|
||
|
out = extrema.h_maxima(data, 40)
|
||
|
|
||
|
error = diff(expected_result, out)
|
||
|
assert error < eps
|
||
|
|
||
|
def test_h_minima(self):
|
||
|
"""h-minima for various data types"""
|
||
|
|
||
|
data = np.array([[10, 11, 13, 14, 14, 15, 14, 14, 13, 11],
|
||
|
[11, 13, 15, 16, 16, 16, 16, 16, 15, 13],
|
||
|
[13, 15, 40, 40, 18, 18, 18, 60, 60, 15],
|
||
|
[14, 16, 40, 40, 19, 19, 19, 60, 60, 16],
|
||
|
[14, 16, 18, 19, 19, 19, 19, 19, 18, 16],
|
||
|
[15, 16, 18, 19, 19, 20, 19, 19, 18, 16],
|
||
|
[14, 16, 18, 19, 19, 19, 19, 19, 18, 16],
|
||
|
[14, 16, 80, 80, 19, 19, 19, 100, 100, 16],
|
||
|
[13, 15, 80, 80, 18, 18, 18, 100, 100, 15],
|
||
|
[11, 13, 15, 16, 16, 16, 16, 16, 15, 13]],
|
||
|
dtype=np.uint8)
|
||
|
data = 100 - data
|
||
|
expected_result = np.array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||
|
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||
|
[0, 0, 0, 0, 0, 0, 0, 1, 1, 0],
|
||
|
[0, 0, 0, 0, 0, 0, 0, 1, 1, 0],
|
||
|
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||
|
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||
|
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||
|
[0, 0, 1, 1, 0, 0, 0, 1, 1, 0],
|
||
|
[0, 0, 1, 1, 0, 0, 0, 1, 1, 0],
|
||
|
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]],
|
||
|
dtype=np.uint8)
|
||
|
for dtype in [np.uint8, np.uint64, np.int8, np.int64]:
|
||
|
data = data.astype(dtype)
|
||
|
out = extrema.h_minima(data, 40)
|
||
|
|
||
|
error = diff(expected_result, out)
|
||
|
assert error < eps
|
||
|
assert out.dtype == expected_result.dtype
|
||
|
|
||
|
def test_extrema_float(self):
|
||
|
"""specific tests for float type"""
|
||
|
data = np.array([[0.10, 0.11, 0.13, 0.14, 0.14, 0.15, 0.14,
|
||
|
0.14, 0.13, 0.11],
|
||
|
[0.11, 0.13, 0.15, 0.16, 0.16, 0.16, 0.16,
|
||
|
0.16, 0.15, 0.13],
|
||
|
[0.13, 0.15, 0.40, 0.40, 0.18, 0.18, 0.18,
|
||
|
0.60, 0.60, 0.15],
|
||
|
[0.14, 0.16, 0.40, 0.40, 0.19, 0.19, 0.19,
|
||
|
0.60, 0.60, 0.16],
|
||
|
[0.14, 0.16, 0.18, 0.19, 0.19, 0.19, 0.19,
|
||
|
0.19, 0.18, 0.16],
|
||
|
[0.15, 0.182, 0.18, 0.19, 0.204, 0.20, 0.19,
|
||
|
0.19, 0.18, 0.16],
|
||
|
[0.14, 0.16, 0.18, 0.19, 0.19, 0.19, 0.19,
|
||
|
0.19, 0.18, 0.16],
|
||
|
[0.14, 0.16, 0.80, 0.80, 0.19, 0.19, 0.19,
|
||
|
1.0, 1.0, 0.16],
|
||
|
[0.13, 0.15, 0.80, 0.80, 0.18, 0.18, 0.18,
|
||
|
1.0, 1.0, 0.15],
|
||
|
[0.11, 0.13, 0.15, 0.16, 0.16, 0.16, 0.16,
|
||
|
0.16, 0.15, 0.13]],
|
||
|
dtype=np.float32)
|
||
|
inverted_data = 1.0 - data
|
||
|
|
||
|
out = extrema.h_maxima(data, 0.003)
|
||
|
expected_result = np.array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||
|
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||
|
[0, 0, 1, 1, 0, 0, 0, 1, 1, 0],
|
||
|
[0, 0, 1, 1, 0, 0, 0, 1, 1, 0],
|
||
|
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||
|
[0, 0, 0, 0, 1, 0, 0, 0, 0, 0],
|
||
|
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||
|
[0, 0, 1, 1, 0, 0, 0, 1, 1, 0],
|
||
|
[0, 0, 1, 1, 0, 0, 0, 1, 1, 0],
|
||
|
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]],
|
||
|
dtype=np.uint8)
|
||
|
|
||
|
error = diff(expected_result, out)
|
||
|
assert error < eps
|
||
|
|
||
|
out = extrema.h_minima(inverted_data, 0.003)
|
||
|
error = diff(expected_result, out)
|
||
|
assert error < eps
|
||
|
|
||
|
def test_h_maxima_float_image(self):
|
||
|
"""specific tests for h-maxima float image type"""
|
||
|
w = 10
|
||
|
x, y = np.mgrid[0:w, 0:w]
|
||
|
data = 20 - 0.2 * ((x - w / 2) ** 2 + (y - w / 2) ** 2)
|
||
|
data[2:4, 2:4] = 40
|
||
|
data[2:4, 7:9] = 60
|
||
|
data[7:9, 2:4] = 80
|
||
|
data[7:9, 7:9] = 100
|
||
|
data = data.astype(np.float32)
|
||
|
|
||
|
expected_result = np.zeros_like(data)
|
||
|
expected_result[(data > 19.9)] = 1.0
|
||
|
|
||
|
for h in [1.0e-12, 1.0e-6, 1.0e-3, 1.0e-2, 1.0e-1, 0.1]:
|
||
|
out = extrema.h_maxima(data, h)
|
||
|
error = diff(expected_result, out)
|
||
|
assert error < eps
|
||
|
|
||
|
def test_h_maxima_float_h(self):
|
||
|
"""specific tests for h-maxima float h parameter"""
|
||
|
data = np.array([[0, 0, 0, 0, 0],
|
||
|
[0, 3, 3, 3, 0],
|
||
|
[0, 3, 4, 3, 0],
|
||
|
[0, 3, 3, 3, 0],
|
||
|
[0, 0, 0, 0, 0]], dtype=np.uint8)
|
||
|
|
||
|
h_vals = np.linspace(1.0, 2.0, 100)
|
||
|
failures = 0
|
||
|
for i in range(h_vals.size):
|
||
|
maxima = extrema.h_maxima(data, h_vals[i])
|
||
|
|
||
|
if (maxima[2, 2] == 0):
|
||
|
failures += 1
|
||
|
|
||
|
assert (failures == 0)
|
||
|
|
||
|
def test_h_maxima_large_h(self):
|
||
|
"""test that h-maxima works correctly for large h"""
|
||
|
data = np.array([[10, 10, 10, 10, 10],
|
||
|
[10, 13, 13, 13, 10],
|
||
|
[10, 13, 14, 13, 10],
|
||
|
[10, 13, 13, 13, 10],
|
||
|
[10, 10, 10, 10, 10]], dtype=np.uint8)
|
||
|
|
||
|
maxima = extrema.h_maxima(data, 5)
|
||
|
assert (np.sum(maxima) == 0)
|
||
|
|
||
|
data = np.array([[10, 10, 10, 10, 10],
|
||
|
[10, 13, 13, 13, 10],
|
||
|
[10, 13, 14, 13, 10],
|
||
|
[10, 13, 13, 13, 10],
|
||
|
[10, 10, 10, 10, 10]], dtype=np.float32)
|
||
|
|
||
|
maxima = extrema.h_maxima(data, 5.0)
|
||
|
assert (np.sum(maxima) == 0)
|
||
|
|
||
|
def test_h_minima_float_image(self):
|
||
|
"""specific tests for h-minima float image type"""
|
||
|
w = 10
|
||
|
x, y = np.mgrid[0:w, 0:w]
|
||
|
data = 180 + 0.2 * ((x - w / 2) ** 2 + (y - w / 2) ** 2)
|
||
|
data[2:4, 2:4] = 160
|
||
|
data[2:4, 7:9] = 140
|
||
|
data[7:9, 2:4] = 120
|
||
|
data[7:9, 7:9] = 100
|
||
|
data = data.astype(np.float32)
|
||
|
|
||
|
expected_result = np.zeros_like(data)
|
||
|
expected_result[(data < 180.1)] = 1.0
|
||
|
|
||
|
for h in [1.0e-12, 1.0e-6, 1.0e-3, 1.0e-2, 1.0e-1, 0.1]:
|
||
|
out = extrema.h_minima(data, h)
|
||
|
error = diff(expected_result, out)
|
||
|
assert error < eps
|
||
|
|
||
|
def test_h_minima_float_h(self):
|
||
|
"""specific tests for h-minima float h parameter"""
|
||
|
data = np.array([[4, 4, 4, 4, 4],
|
||
|
[4, 1, 1, 1, 4],
|
||
|
[4, 1, 0, 1, 4],
|
||
|
[4, 1, 1, 1, 4],
|
||
|
[4, 4, 4, 4, 4]], dtype=np.uint8)
|
||
|
|
||
|
h_vals = np.linspace(1.0, 2.0, 100)
|
||
|
failures = 0
|
||
|
for i in range(h_vals.size):
|
||
|
minima = extrema.h_minima(data, h_vals[i])
|
||
|
|
||
|
if (minima[2, 2] == 0):
|
||
|
failures += 1
|
||
|
|
||
|
assert (failures == 0)
|
||
|
|
||
|
def test_h_minima_large_h(self):
|
||
|
"""test that h-minima works correctly for large h"""
|
||
|
data = np.array([[14, 14, 14, 14, 14],
|
||
|
[14, 11, 11, 11, 14],
|
||
|
[14, 11, 10, 11, 14],
|
||
|
[14, 11, 11, 11, 14],
|
||
|
[14, 14, 14, 14, 14]], dtype=np.uint8)
|
||
|
|
||
|
maxima = extrema.h_minima(data, 5)
|
||
|
assert (np.sum(maxima) == 0)
|
||
|
|
||
|
data = np.array([[14, 14, 14, 14, 14],
|
||
|
[14, 11, 11, 11, 14],
|
||
|
[14, 11, 10, 11, 14],
|
||
|
[14, 11, 11, 11, 14],
|
||
|
[14, 14, 14, 14, 14]], dtype=np.float32)
|
||
|
|
||
|
maxima = extrema.h_minima(data, 5.0)
|
||
|
assert (np.sum(maxima) == 0)
|
||
|
|
||
|
|
||
|
class TestLocalMaxima(unittest.TestCase):
|
||
|
"""Some tests for local_minima are included as well."""
|
||
|
|
||
|
supported_dtypes = [
|
||
|
np.uint8, np.uint16, np.uint32, np.uint64,
|
||
|
np.int8, np.int16, np.int32, np.int64,
|
||
|
np.float32, np.float64
|
||
|
]
|
||
|
image = np.array(
|
||
|
[[1, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0],
|
||
|
[1, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0],
|
||
|
[0, 0, 0, 2, 0, 0, 3, 3, 0, 0, 4, 0, 2, 0, 0],
|
||
|
[0, 1, 0, 0, 0, 0, 0, 0, 4, 4, 0, 3, 0, 0, 0],
|
||
|
[0, 2, 0, 1, 0, 2, 1, 0, 0, 0, 0, 3, 0, 0, 0],
|
||
|
[0, 0, 2, 0, 2, 0, 0, 0, 2, 1, 0, 0, 0, 0, 0]],
|
||
|
dtype=np.uint8
|
||
|
)
|
||
|
# Connectivity 2, maxima can touch border, returned with default values
|
||
|
expected_default = np.array(
|
||
|
[[1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||
|
[1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||
|
[0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0],
|
||
|
[0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0],
|
||
|
[0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||
|
[0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0]],
|
||
|
dtype=np.bool
|
||
|
)
|
||
|
# Connectivity 1 (cross), maxima can touch border
|
||
|
expected_cross = np.array(
|
||
|
[[1, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0],
|
||
|
[1, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0],
|
||
|
[0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0],
|
||
|
[0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0],
|
||
|
[0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0],
|
||
|
[0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0]],
|
||
|
dtype=np.bool
|
||
|
)
|
||
|
|
||
|
def test_empty(self):
|
||
|
"""Test result with empty image."""
|
||
|
result = extrema.local_maxima(np.array([[]]), indices=False)
|
||
|
assert result.size == 0
|
||
|
assert result.dtype == np.bool
|
||
|
assert result.shape == (1, 0)
|
||
|
|
||
|
result = extrema.local_maxima(np.array([]), indices=True)
|
||
|
assert isinstance(result, tuple)
|
||
|
assert len(result) == 1
|
||
|
assert result[0].size == 0
|
||
|
assert result[0].dtype == np.intp
|
||
|
|
||
|
result = extrema.local_maxima(np.array([[]]), indices=True)
|
||
|
assert isinstance(result, tuple)
|
||
|
assert len(result) == 2
|
||
|
assert result[0].size == 0
|
||
|
assert result[0].dtype == np.intp
|
||
|
assert result[1].size == 0
|
||
|
assert result[1].dtype == np.intp
|
||
|
|
||
|
def test_dtypes(self):
|
||
|
"""Test results with default configuration for all supported dtypes."""
|
||
|
for dtype in self.supported_dtypes:
|
||
|
result = extrema.local_maxima(self.image.astype(dtype))
|
||
|
assert result.dtype == np.bool
|
||
|
assert_equal(result, self.expected_default)
|
||
|
|
||
|
def test_dtypes_old(self):
|
||
|
"""
|
||
|
Test results with default configuration and data copied from old unit
|
||
|
tests for all supported dtypes.
|
||
|
"""
|
||
|
data = np.array(
|
||
|
[[10, 11, 13, 14, 14, 15, 14, 14, 13, 11],
|
||
|
[11, 13, 15, 16, 16, 16, 16, 16, 15, 13],
|
||
|
[13, 15, 40, 40, 18, 18, 18, 60, 60, 15],
|
||
|
[14, 16, 40, 40, 19, 19, 19, 60, 60, 16],
|
||
|
[14, 16, 18, 19, 19, 19, 19, 19, 18, 16],
|
||
|
[15, 16, 18, 19, 19, 20, 19, 19, 18, 16],
|
||
|
[14, 16, 18, 19, 19, 19, 19, 19, 18, 16],
|
||
|
[14, 16, 80, 80, 19, 19, 19, 100, 100, 16],
|
||
|
[13, 15, 80, 80, 18, 18, 18, 100, 100, 15],
|
||
|
[11, 13, 15, 16, 16, 16, 16, 16, 15, 13]],
|
||
|
dtype=np.uint8
|
||
|
)
|
||
|
expected = np.array(
|
||
|
[[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||
|
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||
|
[0, 0, 1, 1, 0, 0, 0, 1, 1, 0],
|
||
|
[0, 0, 1, 1, 0, 0, 0, 1, 1, 0],
|
||
|
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||
|
[0, 0, 0, 0, 0, 1, 0, 0, 0, 0],
|
||
|
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||
|
[0, 0, 1, 1, 0, 0, 0, 1, 1, 0],
|
||
|
[0, 0, 1, 1, 0, 0, 0, 1, 1, 0],
|
||
|
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]],
|
||
|
dtype=np.bool
|
||
|
)
|
||
|
for dtype in self.supported_dtypes:
|
||
|
image = data.astype(dtype)
|
||
|
result = extrema.local_maxima(image)
|
||
|
assert result.dtype == np.bool
|
||
|
assert_equal(result, expected)
|
||
|
|
||
|
def test_connectivity(self):
|
||
|
"""Test results if selem is a scalar."""
|
||
|
# Connectivity 1: generates cross shaped structuring element
|
||
|
result_conn1 = extrema.local_maxima(self.image, connectivity=1)
|
||
|
assert result_conn1.dtype == np.bool
|
||
|
assert_equal(result_conn1, self.expected_cross)
|
||
|
|
||
|
# Connectivity 2: generates square shaped structuring element
|
||
|
result_conn2 = extrema.local_maxima(self.image, connectivity=2)
|
||
|
assert result_conn2.dtype == np.bool
|
||
|
assert_equal(result_conn2, self.expected_default)
|
||
|
|
||
|
# Connectivity 3: generates square shaped structuring element
|
||
|
result_conn3 = extrema.local_maxima(self.image, connectivity=3)
|
||
|
assert result_conn3.dtype == np.bool
|
||
|
assert_equal(result_conn3, self.expected_default)
|
||
|
|
||
|
def test_selem(self):
|
||
|
"""Test results if selem is given."""
|
||
|
selem_cross = np.array(
|
||
|
[[0, 1, 0], [1, 1, 1], [0, 1, 0]], dtype=np.bool)
|
||
|
result_selem_cross = extrema.local_maxima(
|
||
|
self.image, selem=selem_cross)
|
||
|
assert result_selem_cross.dtype == np.bool
|
||
|
assert_equal(result_selem_cross, self.expected_cross)
|
||
|
|
||
|
for selem in [
|
||
|
((True,) * 3,) * 3,
|
||
|
np.ones((3, 3), dtype=np.float64),
|
||
|
np.ones((3, 3), dtype=np.uint8),
|
||
|
np.ones((3, 3), dtype=np.bool),
|
||
|
]:
|
||
|
# Test different dtypes for selem which expects a boolean array but
|
||
|
# will accept and convert other types if possible
|
||
|
result_selem_square = extrema.local_maxima(self.image, selem=selem)
|
||
|
assert result_selem_square.dtype == np.bool
|
||
|
assert_equal(result_selem_square, self.expected_default)
|
||
|
|
||
|
selem_x = np.array([[1, 0, 1], [0, 1, 0], [1, 0, 1]], dtype=np.bool)
|
||
|
expected_selem_x = np.array(
|
||
|
[[1, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0],
|
||
|
[1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||
|
[0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0],
|
||
|
[0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0],
|
||
|
[0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0],
|
||
|
[0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0]],
|
||
|
dtype=np.bool
|
||
|
)
|
||
|
result_selem_x = extrema.local_maxima(self.image, selem=selem_x)
|
||
|
assert result_selem_x.dtype == np.bool
|
||
|
assert_equal(result_selem_x, expected_selem_x)
|
||
|
|
||
|
def test_indices(self):
|
||
|
"""Test output if indices of peaks are desired."""
|
||
|
# Connectivity 1
|
||
|
expected_conn1 = np.nonzero(self.expected_cross)
|
||
|
result_conn1 = extrema.local_maxima(self.image, connectivity=1,
|
||
|
indices=True)
|
||
|
assert_equal(result_conn1, expected_conn1)
|
||
|
|
||
|
# Connectivity 2
|
||
|
expected_conn2 = np.nonzero(self.expected_default)
|
||
|
result_conn2 = extrema.local_maxima(self.image, connectivity=2,
|
||
|
indices=True)
|
||
|
assert_equal(result_conn2, expected_conn2)
|
||
|
|
||
|
def test_allow_borders(self):
|
||
|
"""Test maxima detection at the image border."""
|
||
|
# Use connectivity 1 to allow many maxima, only filtering at border is
|
||
|
# of interest
|
||
|
result_with_boder = extrema.local_maxima(
|
||
|
self.image, connectivity=1, allow_borders=True)
|
||
|
assert result_with_boder.dtype == np.bool
|
||
|
assert_equal(result_with_boder, self.expected_cross)
|
||
|
|
||
|
expected_without_border = np.array(
|
||
|
[[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||
|
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0],
|
||
|
[0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0],
|
||
|
[0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0],
|
||
|
[0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0],
|
||
|
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]],
|
||
|
dtype=np.bool
|
||
|
)
|
||
|
result_without_border = extrema.local_maxima(
|
||
|
self.image, connectivity=1, allow_borders=False)
|
||
|
assert result_with_boder.dtype == np.bool
|
||
|
assert_equal(result_without_border, expected_without_border)
|
||
|
|
||
|
def test_nd(self):
|
||
|
"""Test one- and three-dimensional case."""
|
||
|
# One-dimension
|
||
|
x_1d = np.array([1, 1, 0, 1, 2, 3, 0, 2, 1, 2, 0])
|
||
|
expected_1d = np.array([1, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0],
|
||
|
dtype=np.bool)
|
||
|
result_1d = extrema.local_maxima(x_1d)
|
||
|
assert result_1d.dtype == np.bool
|
||
|
assert_equal(result_1d, expected_1d)
|
||
|
|
||
|
# 3-dimensions (adapted from old unit test)
|
||
|
x_3d = np.zeros((8, 8, 8), dtype=np.uint8)
|
||
|
expected_3d = np.zeros((8, 8, 8), dtype=np.bool)
|
||
|
# first maximum: only one pixel
|
||
|
x_3d[1, 1:3, 1:3] = 100
|
||
|
x_3d[2, 2, 2] = 200
|
||
|
x_3d[3, 1:3, 1:3] = 100
|
||
|
expected_3d[2, 2, 2] = 1
|
||
|
# second maximum: three pixels in z-direction
|
||
|
x_3d[5:8, 1, 1] = 200
|
||
|
expected_3d[5:8, 1, 1] = 1
|
||
|
# third: two maxima in 0 and 3.
|
||
|
x_3d[0, 5:8, 5:8] = 200
|
||
|
x_3d[1, 6, 6] = 100
|
||
|
x_3d[2, 5:7, 5:7] = 200
|
||
|
x_3d[0:3, 5:8, 5:8] += 50
|
||
|
expected_3d[0, 5:8, 5:8] = 1
|
||
|
expected_3d[2, 5:7, 5:7] = 1
|
||
|
# four : one maximum in the corner of the square
|
||
|
x_3d[6:8, 6:8, 6:8] = 200
|
||
|
x_3d[7, 7, 7] = 255
|
||
|
expected_3d[7, 7, 7] = 1
|
||
|
result_3d = extrema.local_maxima(x_3d)
|
||
|
assert result_3d.dtype == np.bool
|
||
|
assert_equal(result_3d, expected_3d)
|
||
|
|
||
|
def test_constant(self):
|
||
|
"""Test behaviour for 'flat' images."""
|
||
|
const_image = np.full((7, 6), 42, dtype=np.uint8)
|
||
|
expected = np.zeros((7, 6), dtype=np.uint8)
|
||
|
for dtype in self.supported_dtypes:
|
||
|
const_image = const_image.astype(dtype)
|
||
|
# test for local maxima
|
||
|
result = extrema.local_maxima(const_image)
|
||
|
assert result.dtype == np.bool
|
||
|
assert_equal(result, expected)
|
||
|
# test for local minima
|
||
|
result = extrema.local_minima(const_image)
|
||
|
assert result.dtype == np.bool
|
||
|
assert_equal(result, expected)
|
||
|
|
||
|
def test_extrema_float(self):
|
||
|
"""Specific tests for float type."""
|
||
|
# Copied from old unit test for local_maxma
|
||
|
image = np.array(
|
||
|
[[0.10, 0.11, 0.13, 0.14, 0.14, 0.15, 0.14, 0.14, 0.13, 0.11],
|
||
|
[0.11, 0.13, 0.15, 0.16, 0.16, 0.16, 0.16, 0.16, 0.15, 0.13],
|
||
|
[0.13, 0.15, 0.40, 0.40, 0.18, 0.18, 0.18, 0.60, 0.60, 0.15],
|
||
|
[0.14, 0.16, 0.40, 0.40, 0.19, 0.19, 0.19, 0.60, 0.60, 0.16],
|
||
|
[0.14, 0.16, 0.18, 0.19, 0.19, 0.19, 0.19, 0.19, 0.18, 0.16],
|
||
|
[0.15, 0.182, 0.18, 0.19, 0.204, 0.20, 0.19, 0.19, 0.18, 0.16],
|
||
|
[0.14, 0.16, 0.18, 0.19, 0.19, 0.19, 0.19, 0.19, 0.18, 0.16],
|
||
|
[0.14, 0.16, 0.80, 0.80, 0.19, 0.19, 0.19, 1.0, 1.0, 0.16],
|
||
|
[0.13, 0.15, 0.80, 0.80, 0.18, 0.18, 0.18, 1.0, 1.0, 0.15],
|
||
|
[0.11, 0.13, 0.15, 0.16, 0.16, 0.16, 0.16, 0.16, 0.15, 0.13]],
|
||
|
dtype=np.float32
|
||
|
)
|
||
|
inverted_image = 1.0 - image
|
||
|
expected_result = np.array(
|
||
|
[[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||
|
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||
|
[0, 0, 1, 1, 0, 0, 0, 1, 1, 0],
|
||
|
[0, 0, 1, 1, 0, 0, 0, 1, 1, 0],
|
||
|
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||
|
[0, 1, 0, 0, 1, 0, 0, 0, 0, 0],
|
||
|
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||
|
[0, 0, 1, 1, 0, 0, 0, 1, 1, 0],
|
||
|
[0, 0, 1, 1, 0, 0, 0, 1, 1, 0],
|
||
|
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]],
|
||
|
dtype=np.bool
|
||
|
)
|
||
|
|
||
|
# Test for local maxima with automatic step calculation
|
||
|
result = extrema.local_maxima(image)
|
||
|
assert result.dtype == np.bool
|
||
|
assert_equal(result, expected_result)
|
||
|
|
||
|
# Test for local minima with automatic step calculation
|
||
|
result = extrema.local_minima(inverted_image)
|
||
|
assert result.dtype == np.bool
|
||
|
assert_equal(result, expected_result)
|
||
|
|
||
|
def test_exceptions(self):
|
||
|
"""Test if input validation triggers correct exceptions."""
|
||
|
# Mismatching number of dimensions
|
||
|
with raises(ValueError, match="number of dimensions"):
|
||
|
extrema.local_maxima(
|
||
|
self.image, selem=np.ones((3, 3, 3), dtype=np.bool))
|
||
|
with raises(ValueError, match="number of dimensions"):
|
||
|
extrema.local_maxima(
|
||
|
self.image, selem=np.ones((3,), dtype=np.bool))
|
||
|
|
||
|
# All dimensions in selem must be of size 3
|
||
|
with raises(ValueError, match="dimension size"):
|
||
|
extrema.local_maxima(
|
||
|
self.image, selem=np.ones((2, 3), dtype=np.bool))
|
||
|
with raises(ValueError, match="dimension size"):
|
||
|
extrema.local_maxima(
|
||
|
self.image, selem=np.ones((5, 5), dtype=np.bool))
|
||
|
|
||
|
with raises(TypeError, match="float16 which is not supported"):
|
||
|
extrema.local_maxima(np.empty(1, dtype=np.float16))
|
||
|
|
||
|
def test_small_array(self):
|
||
|
"""Test output for arrays with dimension smaller 3.
|
||
|
|
||
|
If any dimension of an array is smaller than 3 and `allow_borders` is
|
||
|
false a structuring element, which has at least 3 elements in each
|
||
|
dimension, can't be applied. This is an implementation detail so
|
||
|
`local_maxima` should still return valid output (see gh-3261).
|
||
|
|
||
|
If `allow_borders` is true the array is padded internally and there is
|
||
|
no problem.
|
||
|
"""
|
||
|
warning_msg = "maxima can't exist .* any dimension smaller 3 .*"
|
||
|
x = np.array([0, 1])
|
||
|
extrema.local_maxima(x, allow_borders=True) # no warning
|
||
|
with warns(UserWarning, match=warning_msg):
|
||
|
result = extrema.local_maxima(x, allow_borders=False)
|
||
|
assert_equal(result, [0, 0])
|
||
|
assert result.dtype == np.bool
|
||
|
|
||
|
x = np.array([[1, 2], [2, 2]])
|
||
|
extrema.local_maxima(x, allow_borders=True, indices=True) # no warning
|
||
|
with warns(UserWarning, match=warning_msg):
|
||
|
result = extrema.local_maxima(x, allow_borders=False, indices=True)
|
||
|
assert_equal(result, np.zeros((2, 0), dtype=np.intp))
|
||
|
assert result[0].dtype == np.intp
|
||
|
assert result[1].dtype == np.intp
|
||
|
|
||
|
|
||
|
if __name__ == "__main__":
|
||
|
np.testing.run_module_suite()
|