1import math 2import unittest 3 4import numpy as np 5import pytest 6from numpy.testing import assert_equal 7from pytest import raises, warns 8 9from skimage._shared.testing import expected_warnings 10from skimage.morphology import extrema 11 12 13eps = 1e-12 14 15 16def diff(a, b): 17 a = np.asarray(a, dtype=np.float64) 18 b = np.asarray(b, dtype=np.float64) 19 t = ((a - b) ** 2).sum() 20 return math.sqrt(t) 21 22 23class TestExtrema(): 24 25 def test_saturated_arithmetic(self): 26 """Adding/subtracting a constant and clipping""" 27 # Test for unsigned integer 28 data = np.array([[250, 251, 5, 5], 29 [100, 200, 253, 252], 30 [4, 10, 1, 3]], 31 dtype=np.uint8) 32 # adding the constant 33 img_constant_added = extrema._add_constant_clip(data, 4) 34 expected = np.array([[254, 255, 9, 9], 35 [104, 204, 255, 255], 36 [8, 14, 5, 7]], 37 dtype=np.uint8) 38 error = diff(img_constant_added, expected) 39 assert error < eps 40 img_constant_subtracted = extrema._subtract_constant_clip(data, 4) 41 expected = np.array([[246, 247, 1, 1], 42 [96, 196, 249, 248], 43 [0, 6, 0, 0]], 44 dtype=np.uint8) 45 error = diff(img_constant_subtracted, expected) 46 assert error < eps 47 48 # Test for signed integer 49 data = np.array([[32767, 32766], 50 [-32768, -32767]], 51 dtype=np.int16) 52 img_constant_added = extrema._add_constant_clip(data, 1) 53 expected = np.array([[32767, 32767], 54 [-32767, -32766]], 55 dtype=np.int16) 56 error = diff(img_constant_added, expected) 57 assert error < eps 58 img_constant_subtracted = extrema._subtract_constant_clip(data, 1) 59 expected = np.array([[32766, 32765], 60 [-32768, -32768]], 61 dtype=np.int16) 62 error = diff(img_constant_subtracted, expected) 63 assert error < eps 64 65 def test_h_maxima(self): 66 """h-maxima for various data types""" 67 68 data = np.array([[10, 11, 13, 14, 14, 15, 14, 14, 13, 11], 69 [11, 13, 15, 16, 16, 16, 16, 16, 15, 13], 70 [13, 15, 40, 40, 18, 18, 18, 60, 60, 15], 71 [14, 16, 40, 40, 19, 19, 19, 60, 60, 16], 72 [14, 16, 18, 19, 19, 19, 19, 19, 18, 16], 73 [15, 16, 18, 19, 19, 20, 19, 19, 18, 16], 74 [14, 16, 18, 19, 19, 19, 19, 19, 18, 16], 75 [14, 16, 80, 80, 19, 19, 19, 100, 100, 16], 76 [13, 15, 80, 80, 18, 18, 18, 100, 100, 15], 77 [11, 13, 15, 16, 16, 16, 16, 16, 15, 13]], 78 dtype=np.uint8) 79 80 expected_result = np.array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 81 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 82 [0, 0, 0, 0, 0, 0, 0, 1, 1, 0], 83 [0, 0, 0, 0, 0, 0, 0, 1, 1, 0], 84 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 85 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 86 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 87 [0, 0, 1, 1, 0, 0, 0, 1, 1, 0], 88 [0, 0, 1, 1, 0, 0, 0, 1, 1, 0], 89 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], 90 dtype=np.uint8) 91 for dtype in [np.uint8, np.uint64, np.int8, np.int64]: 92 data = data.astype(dtype) 93 out = extrema.h_maxima(data, 40) 94 95 error = diff(expected_result, out) 96 assert error < eps 97 98 def test_h_minima(self): 99 """h-minima for various data types""" 100 101 data = np.array([[10, 11, 13, 14, 14, 15, 14, 14, 13, 11], 102 [11, 13, 15, 16, 16, 16, 16, 16, 15, 13], 103 [13, 15, 40, 40, 18, 18, 18, 60, 60, 15], 104 [14, 16, 40, 40, 19, 19, 19, 60, 60, 16], 105 [14, 16, 18, 19, 19, 19, 19, 19, 18, 16], 106 [15, 16, 18, 19, 19, 20, 19, 19, 18, 16], 107 [14, 16, 18, 19, 19, 19, 19, 19, 18, 16], 108 [14, 16, 80, 80, 19, 19, 19, 100, 100, 16], 109 [13, 15, 80, 80, 18, 18, 18, 100, 100, 15], 110 [11, 13, 15, 16, 16, 16, 16, 16, 15, 13]], 111 dtype=np.uint8) 112 data = 100 - data 113 expected_result = np.array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 114 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 115 [0, 0, 0, 0, 0, 0, 0, 1, 1, 0], 116 [0, 0, 0, 0, 0, 0, 0, 1, 1, 0], 117 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 118 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 119 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 120 [0, 0, 1, 1, 0, 0, 0, 1, 1, 0], 121 [0, 0, 1, 1, 0, 0, 0, 1, 1, 0], 122 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], 123 dtype=np.uint8) 124 for dtype in [np.uint8, np.uint64, np.int8, np.int64]: 125 data = data.astype(dtype) 126 out = extrema.h_minima(data, 40) 127 128 error = diff(expected_result, out) 129 assert error < eps 130 assert out.dtype == expected_result.dtype 131 132 def test_extrema_float(self): 133 """specific tests for float type""" 134 data = np.array([[0.10, 0.11, 0.13, 0.14, 0.14, 0.15, 0.14, 135 0.14, 0.13, 0.11], 136 [0.11, 0.13, 0.15, 0.16, 0.16, 0.16, 0.16, 137 0.16, 0.15, 0.13], 138 [0.13, 0.15, 0.40, 0.40, 0.18, 0.18, 0.18, 139 0.60, 0.60, 0.15], 140 [0.14, 0.16, 0.40, 0.40, 0.19, 0.19, 0.19, 141 0.60, 0.60, 0.16], 142 [0.14, 0.16, 0.18, 0.19, 0.19, 0.19, 0.19, 143 0.19, 0.18, 0.16], 144 [0.15, 0.182, 0.18, 0.19, 0.204, 0.20, 0.19, 145 0.19, 0.18, 0.16], 146 [0.14, 0.16, 0.18, 0.19, 0.19, 0.19, 0.19, 147 0.19, 0.18, 0.16], 148 [0.14, 0.16, 0.80, 0.80, 0.19, 0.19, 0.19, 149 1.0, 1.0, 0.16], 150 [0.13, 0.15, 0.80, 0.80, 0.18, 0.18, 0.18, 151 1.0, 1.0, 0.15], 152 [0.11, 0.13, 0.15, 0.16, 0.16, 0.16, 0.16, 153 0.16, 0.15, 0.13]], 154 dtype=np.float32) 155 inverted_data = 1.0 - data 156 157 out = extrema.h_maxima(data, 0.003) 158 expected_result = np.array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 159 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 160 [0, 0, 1, 1, 0, 0, 0, 1, 1, 0], 161 [0, 0, 1, 1, 0, 0, 0, 1, 1, 0], 162 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 163 [0, 0, 0, 0, 1, 0, 0, 0, 0, 0], 164 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 165 [0, 0, 1, 1, 0, 0, 0, 1, 1, 0], 166 [0, 0, 1, 1, 0, 0, 0, 1, 1, 0], 167 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], 168 dtype=np.uint8) 169 170 error = diff(expected_result, out) 171 assert error < eps 172 173 out = extrema.h_minima(inverted_data, 0.003) 174 error = diff(expected_result, out) 175 assert error < eps 176 177 def test_h_maxima_float_image(self): 178 """specific tests for h-maxima float image type""" 179 w = 10 180 x, y = np.mgrid[0:w, 0:w] 181 data = 20 - 0.2 * ((x - w / 2) ** 2 + (y - w / 2) ** 2) 182 data[2:4, 2:4] = 40 183 data[2:4, 7:9] = 60 184 data[7:9, 2:4] = 80 185 data[7:9, 7:9] = 100 186 data = data.astype(np.float32) 187 188 expected_result = np.zeros_like(data) 189 expected_result[(data > 19.9)] = 1.0 190 191 for h in [1.0e-12, 1.0e-6, 1.0e-3, 1.0e-2, 1.0e-1, 0.1]: 192 out = extrema.h_maxima(data, h) 193 error = diff(expected_result, out) 194 assert error < eps 195 196 def test_h_maxima_float_h(self): 197 """specific tests for h-maxima float h parameter""" 198 data = np.array([[0, 0, 0, 0, 0], 199 [0, 3, 3, 3, 0], 200 [0, 3, 4, 3, 0], 201 [0, 3, 3, 3, 0], 202 [0, 0, 0, 0, 0]], dtype=np.uint8) 203 204 h_vals = np.linspace(1.0, 2.0, 100) 205 failures = 0 206 207 for h in h_vals: 208 if h % 1 != 0: 209 msgs = ['possible precision loss converting image'] 210 else: 211 msgs = [] 212 213 with expected_warnings(msgs): 214 maxima = extrema.h_maxima(data, h) 215 216 if (maxima[2, 2] == 0): 217 failures += 1 218 219 assert (failures == 0) 220 221 def test_h_maxima_large_h(self): 222 """test that h-maxima works correctly for large h""" 223 data = np.array([[10, 10, 10, 10, 10], 224 [10, 13, 13, 13, 10], 225 [10, 13, 14, 13, 10], 226 [10, 13, 13, 13, 10], 227 [10, 10, 10, 10, 10]], dtype=np.uint8) 228 229 maxima = extrema.h_maxima(data, 5) 230 assert (np.sum(maxima) == 0) 231 232 data = np.array([[10, 10, 10, 10, 10], 233 [10, 13, 13, 13, 10], 234 [10, 13, 14, 13, 10], 235 [10, 13, 13, 13, 10], 236 [10, 10, 10, 10, 10]], dtype=np.float32) 237 238 maxima = extrema.h_maxima(data, 5.0) 239 assert (np.sum(maxima) == 0) 240 241 def test_h_minima_float_image(self): 242 """specific tests for h-minima float image type""" 243 w = 10 244 x, y = np.mgrid[0:w, 0:w] 245 data = 180 + 0.2 * ((x - w / 2) ** 2 + (y - w / 2) ** 2) 246 data[2:4, 2:4] = 160 247 data[2:4, 7:9] = 140 248 data[7:9, 2:4] = 120 249 data[7:9, 7:9] = 100 250 data = data.astype(np.float32) 251 252 expected_result = np.zeros_like(data) 253 expected_result[(data < 180.1)] = 1.0 254 255 for h in [1.0e-12, 1.0e-6, 1.0e-3, 1.0e-2, 1.0e-1, 0.1]: 256 out = extrema.h_minima(data, h) 257 error = diff(expected_result, out) 258 assert error < eps 259 260 def test_h_minima_float_h(self): 261 """specific tests for h-minima float h parameter""" 262 data = np.array([[4, 4, 4, 4, 4], 263 [4, 1, 1, 1, 4], 264 [4, 1, 0, 1, 4], 265 [4, 1, 1, 1, 4], 266 [4, 4, 4, 4, 4]], dtype=np.uint8) 267 268 h_vals = np.linspace(1.0, 2.0, 100) 269 failures = 0 270 for h in h_vals: 271 if h % 1 != 0: 272 msgs = ['possible precision loss converting image'] 273 else: 274 msgs = [] 275 276 with expected_warnings(msgs): 277 minima = extrema.h_minima(data, h) 278 279 if (minima[2, 2] == 0): 280 failures += 1 281 282 assert (failures == 0) 283 284 def test_h_minima_large_h(self): 285 """test that h-minima works correctly for large h""" 286 data = np.array([[14, 14, 14, 14, 14], 287 [14, 11, 11, 11, 14], 288 [14, 11, 10, 11, 14], 289 [14, 11, 11, 11, 14], 290 [14, 14, 14, 14, 14]], dtype=np.uint8) 291 292 maxima = extrema.h_minima(data, 5) 293 assert (np.sum(maxima) == 0) 294 295 data = np.array([[14, 14, 14, 14, 14], 296 [14, 11, 11, 11, 14], 297 [14, 11, 10, 11, 14], 298 [14, 11, 11, 11, 14], 299 [14, 14, 14, 14, 14]], dtype=np.float32) 300 301 maxima = extrema.h_minima(data, 5.0) 302 assert (np.sum(maxima) == 0) 303 304 305class TestLocalMaxima(unittest.TestCase): 306 """Some tests for local_minima are included as well.""" 307 308 supported_dtypes = [ 309 np.uint8, np.uint16, np.uint32, np.uint64, 310 np.int8, np.int16, np.int32, np.int64, 311 np.float32, np.float64 312 ] 313 image = np.array( 314 [[1, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0], 315 [1, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0], 316 [0, 0, 0, 2, 0, 0, 3, 3, 0, 0, 4, 0, 2, 0, 0], 317 [0, 1, 0, 0, 0, 0, 0, 0, 4, 4, 0, 3, 0, 0, 0], 318 [0, 2, 0, 1, 0, 2, 1, 0, 0, 0, 0, 3, 0, 0, 0], 319 [0, 0, 2, 0, 2, 0, 0, 0, 2, 1, 0, 0, 0, 0, 0]], 320 dtype=np.uint8 321 ) 322 # Connectivity 2, maxima can touch border, returned with default values 323 expected_default = np.array( 324 [[1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 325 [1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 326 [0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0], 327 [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0], 328 [0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0], 329 [0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0]], 330 dtype=bool 331 ) 332 # Connectivity 1 (cross), maxima can touch border 333 expected_cross = np.array( 334 [[1, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0], 335 [1, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0], 336 [0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0], 337 [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0], 338 [0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0], 339 [0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0]], 340 dtype=bool 341 ) 342 343 def test_empty(self): 344 """Test result with empty image.""" 345 result = extrema.local_maxima(np.array([[]]), indices=False) 346 assert result.size == 0 347 assert result.dtype == bool 348 assert result.shape == (1, 0) 349 350 result = extrema.local_maxima(np.array([]), indices=True) 351 assert isinstance(result, tuple) 352 assert len(result) == 1 353 assert result[0].size == 0 354 assert result[0].dtype == np.intp 355 356 result = extrema.local_maxima(np.array([[]]), indices=True) 357 assert isinstance(result, tuple) 358 assert len(result) == 2 359 assert result[0].size == 0 360 assert result[0].dtype == np.intp 361 assert result[1].size == 0 362 assert result[1].dtype == np.intp 363 364 def test_dtypes(self): 365 """Test results with default configuration for all supported dtypes.""" 366 for dtype in self.supported_dtypes: 367 result = extrema.local_maxima(self.image.astype(dtype)) 368 assert result.dtype == bool 369 assert_equal(result, self.expected_default) 370 371 def test_dtypes_old(self): 372 """ 373 Test results with default configuration and data copied from old unit 374 tests for all supported dtypes. 375 """ 376 data = np.array( 377 [[10, 11, 13, 14, 14, 15, 14, 14, 13, 11], 378 [11, 13, 15, 16, 16, 16, 16, 16, 15, 13], 379 [13, 15, 40, 40, 18, 18, 18, 60, 60, 15], 380 [14, 16, 40, 40, 19, 19, 19, 60, 60, 16], 381 [14, 16, 18, 19, 19, 19, 19, 19, 18, 16], 382 [15, 16, 18, 19, 19, 20, 19, 19, 18, 16], 383 [14, 16, 18, 19, 19, 19, 19, 19, 18, 16], 384 [14, 16, 80, 80, 19, 19, 19, 100, 100, 16], 385 [13, 15, 80, 80, 18, 18, 18, 100, 100, 15], 386 [11, 13, 15, 16, 16, 16, 16, 16, 15, 13]], 387 dtype=np.uint8 388 ) 389 expected = np.array( 390 [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 391 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 392 [0, 0, 1, 1, 0, 0, 0, 1, 1, 0], 393 [0, 0, 1, 1, 0, 0, 0, 1, 1, 0], 394 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 395 [0, 0, 0, 0, 0, 1, 0, 0, 0, 0], 396 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 397 [0, 0, 1, 1, 0, 0, 0, 1, 1, 0], 398 [0, 0, 1, 1, 0, 0, 0, 1, 1, 0], 399 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], 400 dtype=bool 401 ) 402 for dtype in self.supported_dtypes: 403 image = data.astype(dtype) 404 result = extrema.local_maxima(image) 405 assert result.dtype == bool 406 assert_equal(result, expected) 407 408 def test_connectivity(self): 409 """Test results if footprint is a scalar.""" 410 # Connectivity 1: generates cross shaped footprint 411 result_conn1 = extrema.local_maxima(self.image, connectivity=1) 412 assert result_conn1.dtype == bool 413 assert_equal(result_conn1, self.expected_cross) 414 415 # Connectivity 2: generates square shaped footprint 416 result_conn2 = extrema.local_maxima(self.image, connectivity=2) 417 assert result_conn2.dtype == bool 418 assert_equal(result_conn2, self.expected_default) 419 420 # Connectivity 3: generates square shaped footprint 421 result_conn3 = extrema.local_maxima(self.image, connectivity=3) 422 assert result_conn3.dtype == bool 423 assert_equal(result_conn3, self.expected_default) 424 425 def test_footprint(self): 426 """Test results if footprint is given.""" 427 footprint_cross = np.array( 428 [[0, 1, 0], [1, 1, 1], [0, 1, 0]], dtype=bool) 429 result_footprint_cross = extrema.local_maxima( 430 self.image, footprint=footprint_cross) 431 assert result_footprint_cross.dtype == bool 432 assert_equal(result_footprint_cross, self.expected_cross) 433 434 for footprint in [ 435 ((True,) * 3,) * 3, 436 np.ones((3, 3), dtype=np.float64), 437 np.ones((3, 3), dtype=np.uint8), 438 np.ones((3, 3), dtype=bool), 439 ]: 440 # Test different dtypes for footprint which expects a boolean array 441 # but will accept and convert other types if possible 442 result_footprint_square = extrema.local_maxima( 443 self.image, footprint=footprint 444 ) 445 assert result_footprint_square.dtype == bool 446 assert_equal(result_footprint_square, self.expected_default) 447 448 footprint_x = np.array([[1, 0, 1], [0, 1, 0], [1, 0, 1]], dtype=bool) 449 expected_footprint_x = np.array( 450 [[1, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0], 451 [1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 452 [0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0], 453 [0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0], 454 [0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0], 455 [0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0]], 456 dtype=bool 457 ) 458 result_footprint_x = extrema.local_maxima(self.image, 459 footprint=footprint_x) 460 assert result_footprint_x.dtype == bool 461 assert_equal(result_footprint_x, expected_footprint_x) 462 463 def test_indices(self): 464 """Test output if indices of peaks are desired.""" 465 # Connectivity 1 466 expected_conn1 = np.nonzero(self.expected_cross) 467 result_conn1 = extrema.local_maxima(self.image, connectivity=1, 468 indices=True) 469 assert_equal(result_conn1, expected_conn1) 470 471 # Connectivity 2 472 expected_conn2 = np.nonzero(self.expected_default) 473 result_conn2 = extrema.local_maxima(self.image, connectivity=2, 474 indices=True) 475 assert_equal(result_conn2, expected_conn2) 476 477 def test_allow_borders(self): 478 """Test maxima detection at the image border.""" 479 # Use connectivity 1 to allow many maxima, only filtering at border is 480 # of interest 481 result_with_boder = extrema.local_maxima( 482 self.image, connectivity=1, allow_borders=True) 483 assert result_with_boder.dtype == bool 484 assert_equal(result_with_boder, self.expected_cross) 485 486 expected_without_border = np.array( 487 [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 488 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0], 489 [0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0], 490 [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0], 491 [0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0], 492 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], 493 dtype=bool 494 ) 495 result_without_border = extrema.local_maxima( 496 self.image, connectivity=1, allow_borders=False) 497 assert result_with_boder.dtype == bool 498 assert_equal(result_without_border, expected_without_border) 499 500 def test_nd(self): 501 """Test one- and three-dimensional case.""" 502 # One-dimension 503 x_1d = np.array([1, 1, 0, 1, 2, 3, 0, 2, 1, 2, 0]) 504 expected_1d = np.array([1, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0], 505 dtype=bool) 506 result_1d = extrema.local_maxima(x_1d) 507 assert result_1d.dtype == bool 508 assert_equal(result_1d, expected_1d) 509 510 # 3-dimensions (adapted from old unit test) 511 x_3d = np.zeros((8, 8, 8), dtype=np.uint8) 512 expected_3d = np.zeros((8, 8, 8), dtype=bool) 513 # first maximum: only one pixel 514 x_3d[1, 1:3, 1:3] = 100 515 x_3d[2, 2, 2] = 200 516 x_3d[3, 1:3, 1:3] = 100 517 expected_3d[2, 2, 2] = 1 518 # second maximum: three pixels in z-direction 519 x_3d[5:8, 1, 1] = 200 520 expected_3d[5:8, 1, 1] = 1 521 # third: two maxima in 0 and 3. 522 x_3d[0, 5:8, 5:8] = 200 523 x_3d[1, 6, 6] = 100 524 x_3d[2, 5:7, 5:7] = 200 525 x_3d[0:3, 5:8, 5:8] += 50 526 expected_3d[0, 5:8, 5:8] = 1 527 expected_3d[2, 5:7, 5:7] = 1 528 # four : one maximum in the corner of the square 529 x_3d[6:8, 6:8, 6:8] = 200 530 x_3d[7, 7, 7] = 255 531 expected_3d[7, 7, 7] = 1 532 result_3d = extrema.local_maxima(x_3d) 533 assert result_3d.dtype == bool 534 assert_equal(result_3d, expected_3d) 535 536 def test_constant(self): 537 """Test behaviour for 'flat' images.""" 538 const_image = np.full((7, 6), 42, dtype=np.uint8) 539 expected = np.zeros((7, 6), dtype=np.uint8) 540 for dtype in self.supported_dtypes: 541 const_image = const_image.astype(dtype) 542 # test for local maxima 543 result = extrema.local_maxima(const_image) 544 assert result.dtype == bool 545 assert_equal(result, expected) 546 # test for local minima 547 result = extrema.local_minima(const_image) 548 assert result.dtype == bool 549 assert_equal(result, expected) 550 551 def test_extrema_float(self): 552 """Specific tests for float type.""" 553 # Copied from old unit test for local_maxma 554 image = np.array( 555 [[0.10, 0.11, 0.13, 0.14, 0.14, 0.15, 0.14, 0.14, 0.13, 0.11], 556 [0.11, 0.13, 0.15, 0.16, 0.16, 0.16, 0.16, 0.16, 0.15, 0.13], 557 [0.13, 0.15, 0.40, 0.40, 0.18, 0.18, 0.18, 0.60, 0.60, 0.15], 558 [0.14, 0.16, 0.40, 0.40, 0.19, 0.19, 0.19, 0.60, 0.60, 0.16], 559 [0.14, 0.16, 0.18, 0.19, 0.19, 0.19, 0.19, 0.19, 0.18, 0.16], 560 [0.15, 0.182, 0.18, 0.19, 0.204, 0.20, 0.19, 0.19, 0.18, 0.16], 561 [0.14, 0.16, 0.18, 0.19, 0.19, 0.19, 0.19, 0.19, 0.18, 0.16], 562 [0.14, 0.16, 0.80, 0.80, 0.19, 0.19, 0.19, 1.0, 1.0, 0.16], 563 [0.13, 0.15, 0.80, 0.80, 0.18, 0.18, 0.18, 1.0, 1.0, 0.15], 564 [0.11, 0.13, 0.15, 0.16, 0.16, 0.16, 0.16, 0.16, 0.15, 0.13]], 565 dtype=np.float32 566 ) 567 inverted_image = 1.0 - image 568 expected_result = np.array( 569 [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 570 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 571 [0, 0, 1, 1, 0, 0, 0, 1, 1, 0], 572 [0, 0, 1, 1, 0, 0, 0, 1, 1, 0], 573 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 574 [0, 1, 0, 0, 1, 0, 0, 0, 0, 0], 575 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 576 [0, 0, 1, 1, 0, 0, 0, 1, 1, 0], 577 [0, 0, 1, 1, 0, 0, 0, 1, 1, 0], 578 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], 579 dtype=bool 580 ) 581 582 # Test for local maxima with automatic step calculation 583 result = extrema.local_maxima(image) 584 assert result.dtype == bool 585 assert_equal(result, expected_result) 586 587 # Test for local minima with automatic step calculation 588 result = extrema.local_minima(inverted_image) 589 assert result.dtype == bool 590 assert_equal(result, expected_result) 591 592 def test_exceptions(self): 593 """Test if input validation triggers correct exceptions.""" 594 # Mismatching number of dimensions 595 with raises(ValueError, match="number of dimensions"): 596 extrema.local_maxima( 597 self.image, footprint=np.ones((3, 3, 3), dtype=bool)) 598 with raises(ValueError, match="number of dimensions"): 599 extrema.local_maxima( 600 self.image, footprint=np.ones((3,), dtype=bool)) 601 602 # All dimensions in footprint must be of size 3 603 with raises(ValueError, match="dimension size"): 604 extrema.local_maxima( 605 self.image, footprint=np.ones((2, 3), dtype=bool)) 606 with raises(ValueError, match="dimension size"): 607 extrema.local_maxima( 608 self.image, footprint=np.ones((5, 5), dtype=bool)) 609 610 with raises(TypeError, match="float16 which is not supported"): 611 extrema.local_maxima(np.empty(1, dtype=np.float16)) 612 613 def test_small_array(self): 614 """Test output for arrays with dimension smaller 3. 615 616 If any dimension of an array is smaller than 3 and `allow_borders` is 617 false a footprint, which has at least 3 elements in each 618 dimension, can't be applied. This is an implementation detail so 619 `local_maxima` should still return valid output (see gh-3261). 620 621 If `allow_borders` is true the array is padded internally and there is 622 no problem. 623 """ 624 warning_msg = "maxima can't exist .* any dimension smaller 3 .*" 625 x = np.array([0, 1]) 626 extrema.local_maxima(x, allow_borders=True) # no warning 627 with warns(UserWarning, match=warning_msg): 628 result = extrema.local_maxima(x, allow_borders=False) 629 assert_equal(result, [0, 0]) 630 assert result.dtype == bool 631 632 x = np.array([[1, 2], [2, 2]]) 633 extrema.local_maxima(x, allow_borders=True, indices=True) # no warning 634 with warns(UserWarning, match=warning_msg): 635 result = extrema.local_maxima(x, allow_borders=False, indices=True) 636 assert_equal(result, np.zeros((2, 0), dtype=np.intp)) 637 assert result[0].dtype == np.intp 638 assert result[1].dtype == np.intp 639 640 641@pytest.mark.parametrize( 642 'function', 643 ['local_maxima', 'local_minima', 'h_minima', 'h_maxima'] 644) 645def test_selem_kwarg_deprecation(function): 646 img = np.zeros((16, 16)) 647 args = (20,) if function.startswith('h_') else () 648 with expected_warnings(["`selem` is a deprecated argument name"]): 649 getattr(extrema, function)(img, *args, selem=np.ones((3, 3))) 650