dito.processing
This submodule provides functionality for basic image processing.
1""" 2This submodule provides functionality for basic image processing. 3""" 4 5import itertools 6import math 7import operator 8 9import cv2 10import numpy as np 11 12import dito.core 13import dito.visual 14 15 16# 17# basic processing 18# 19 20 21def argmin(image): 22 """ 23 Compute the coordinates of the minimum value in the image. 24 25 Parameters 26 ---------- 27 image : numpy.ndarray 28 The input image. 29 30 Returns 31 ------- 32 tuple 33 The coordinates of the minimum value in the image. 34 35 Notes 36 ----- 37 The order of the indices is equivalent to the order of the image axes. 38 This may differ from common conventions that use (x, y) coordinates. 39 """ 40 return np.unravel_index(np.argmin(image), image.shape) 41 42 43def argmax(image): 44 """ 45 Compute the coordinates of the maximum value in the image. 46 47 Parameters 48 ---------- 49 image : numpy.ndarray 50 The input image. 51 52 Returns 53 ------- 54 tuple 55 The coordinates of the maximum value in the image. 56 57 Notes 58 ----- 59 The order of the indices is equivalent to the order of the image axes. 60 This may differ from common conventions that use (x, y) coordinates. 61 """ 62 return np.unravel_index(np.argmax(image), image.shape) 63 64 65def nms_iter(image, peak_radius): 66 """ 67 Iterate through peaks in the image using non-maximum suppression (NMS). 68 69 The function yields peaks by repeatedly finding the maximum value in the image, suppressing its 70 neighborhood within the given peak radius, and repeating the process. The iteration stops when no 71 more positive values remain in the image. 72 73 Parameters 74 ---------- 75 image : numpy.ndarray 76 The input grayscale image. 77 peak_radius : int 78 The radius around each peak to suppress in subsequent iterations. 79 80 Yields 81 ------ 82 dict 83 A dictionary containing the peak index, coordinates, and value: 84 - 'n_peak' : int, the index of the current peak. 85 - 'peak_xy' : tuple, the (x, y) coordinates of the peak. 86 - 'peak_value' : float, the value of the peak. 87 """ 88 89 # peak radius must be a non-negative int 90 if not (isinstance(peak_radius, int) and (peak_radius >= 0)): 91 raise ValueError(f"Argument 'peak_radius' must be a non-negative integer (but is: {peak_radius})") 92 93 # only allow images of shape (Y, X) or (Y, X, 1) 94 if not dito.is_gray(image): 95 raise ValueError(f"Image must be grayscale (shapes (Y, X) or (Y, X, 1)), but has shape {image.shape}") 96 97 # remove the last singleton dimension if necessary 98 image_work = image.copy() 99 if len(image_work.shape) == 3: 100 image_work = image_work[:, :, 0] 101 102 for n_peak in itertools.count(): 103 # extract max 104 (peak_y, peak_x) = argmax(image_work) 105 peak_value = image_work[peak_y, peak_x] 106 107 # stop if there are no positive values left (otherwise, we might hit an infinite loop) 108 if peak_value <= 0: 109 return 110 111 # suppress neighborhood 112 image_work[ 113 max(0, peak_y - peak_radius):min(image_work.shape[0], peak_y + peak_radius + 1), 114 max(0, peak_x - peak_radius):min(image_work.shape[1], peak_x + peak_radius + 1), 115 ] = 0 116 117 yield { 118 "n_peak": n_peak, 119 "peak_xy": (peak_x, peak_y), 120 "peak_value": peak_value, 121 } 122 123 124def nms(image, peak_radius, max_peak_count=1000, rel_max_value=0.1): 125 """ 126 Perform non-maximum suppression (NMS) to extract peaks from the image. 127 128 The function finds peaks in the image, suppressing neighboring values around each peak and iterating 129 until the specified maximum peak count or relative peak value threshold is reached. 130 131 Parameters 132 ---------- 133 image : numpy.ndarray 134 The input grayscale image. 135 peak_radius : int 136 The radius around each peak to suppress in subsequent iterations. 137 max_peak_count : int, optional 138 The maximum number of peaks to extract (default is 1000). 139 rel_max_value : float, optional 140 The relative peak value threshold. The extraction process stops when the peak value falls below 141 this proportion of the maximum peak value (default is 0.1). 142 143 Returns 144 ------- 145 list of dict 146 A list of dictionaries, where each dictionary contains information about a detected peak: 147 - 'n_peak' : int, the index of the peak. 148 - 'peak_xy' : tuple, the (x, y) coordinates of the peak. 149 - 'peak_value' : the value of the peak. 150 """ 151 152 # check argument 'max_peak_count' 153 if not (isinstance(max_peak_count, int) and (max_peak_count >= 1)): 154 raise ValueError(f"Argument 'max_peak_count' must be an integer >= 1, but is: {max_peak_count}") 155 156 # check argument 'rel_max_value' 157 if not (isinstance(rel_max_value, float) and (0.0 <= rel_max_value <= 1.0)): 158 raise ValueError(f"Argument 'rel_max_value' must be a float between 0.0 and 1.0 (both inclusive), but is: {rel_max_value}") 159 160 peaks = [] 161 max_value = None 162 for peak in nms_iter(image=image, peak_radius=peak_radius): 163 if (peak["n_peak"] + 1) >= max_peak_count: 164 # stop if max peak count was reached 165 break 166 if max_value is None: 167 # use first peak's value as reference for all other values (when comparing) 168 max_value = peak["peak_value"] 169 else: 170 # stop if peak value is too small 171 if (peak["peak_value"] / max_value) < rel_max_value: 172 break 173 peaks.append(peak) 174 175 return peaks 176 177 178def clipped_diff(image1, image2, scale=None, offset=None, apply_abs=False): 179 """ 180 Compute the clipped difference between two images. 181 182 The `image1` and `image2` inputs must have the same dtype. The function computes the element-wise difference 183 between `image1` and `image2`, and then applies an optional offset and scale factor to the difference values. 184 The resulting values are clipped to the original dtype range, to prevent overflow or underflow. 185 186 Parameters 187 ---------- 188 image1 : numpy.ndarray 189 The first input image (minuend). 190 image2 : numpy.ndarray 191 The second input image (subtrahend). 192 scale : float, optional 193 The scale factor to apply to the difference values. If specified, the difference values are multiplied by 194 `scale` before any offset is applied. The default value is `None`, which means no scaling is applied. 195 offset : float, optional 196 The offset value to add to the difference values. If specified, the difference values are increased by 197 `offset` after any scaling is applied. The default value is `None`, which means no offset is applied. 198 apply_abs : bool, optional 199 If `True`, the absolute value of the difference image is computed after a scaling and/or offset is applied. 200 The default value is `False`. 201 202 Returns 203 ------- 204 numpy.ndarray 205 The clipped difference image, with the same shape and dtype as the input images. 206 """ 207 208 # assert equal dtypes 209 if image1.dtype != image2.dtype: 210 raise ValueError("Both images must have the same dtypes (but have '{}' and '{}')".format(image1.dtype, image2.dtype)) 211 dtype = image1.dtype 212 dtype_range = dito.core.dtype_range(dtype=dtype) 213 214 # raw diff 215 diff = image1.astype(np.float32) - image2.astype(np.float32) 216 217 # apply offset, scale, and abs if specified 218 if scale is not None: 219 diff *= scale 220 if offset is not None: 221 diff += offset 222 if apply_abs: 223 diff = np.abs(diff) 224 225 # clip values outside of original range 226 diff = dito.clip(image=diff, lower=dtype_range[0], upper=dtype_range[1]) 227 228 return diff.astype(dtype) 229 230 231def abs_diff(image1, image2): 232 """ 233 Compute the absolute difference between two images. 234 235 The `image1` and `image2` inputs must have the same dtype. The function computes the element-wise absolute 236 difference between `image1` and `image2`, and then clips the resulting values to the original dtype range, 237 to prevent overflow or underflow (which might happen for signed integer dtypes). 238 239 Parameters 240 ---------- 241 image1 : numpy.ndarray 242 The first input image (minuend). 243 image2 : numpy.ndarray 244 The second input image (subtrahend). 245 246 Returns 247 ------- 248 numpy.ndarray 249 The absolute difference image, with the same shape and dtype as the input images. 250 """ 251 return clipped_diff(image1=image1, image2=image2, scale=None, offset=None, apply_abs=True) 252 253 254def shifted_diff(image1, image2): 255 """ 256 Compute the shifted difference between two images. 257 258 The `image1` and `image2` inputs must have the same dtype. The function computes the element-wise difference 259 between `image1` and `image2`, and then applies a scale and offset to the difference values to shift the result 260 back into the original dtype range such that there is no need for clipping 261 262 Parameters 263 ---------- 264 image1 : numpy.ndarray 265 The first input image (minuend). 266 image2 : numpy.ndarray 267 The second input image (subtrahend). 268 269 Returns 270 ------- 271 numpy.ndarray 272 The shifted difference image, with the same shape and dtype as the input images. 273 """ 274 dtype_range = dito.core.dtype_range(dtype=image1.dtype) 275 return clipped_diff(image1=image1, image2=image2, scale=0.5, offset=0.5 * (dtype_range[0] + dtype_range[1]), apply_abs=False) 276 277 278def gaussian_blur(image, sigma): 279 """ 280 Apply Gaussian blur to an image. 281 282 The filter kernel size is adapted to `sigma` automatically by OpenCV's 283 `cv2.GaussianBlur`. 284 285 Parameters 286 ---------- 287 image : numpy.ndarray 288 Input image to be blurred. 289 sigma : float 290 Standard deviation for Gaussian kernel. Must be greater than 0.0. 291 292 Returns 293 ------- 294 numpy.ndarray 295 Blurred image, with the same shape and dtype as the input image. 296 """ 297 if sigma <= 0.0: 298 return image 299 return cv2.GaussianBlur(src=image, ksize=None, sigmaX=sigma) 300 301 302def median_blur(image, kernel_size): 303 """ 304 Apply a median filter to an image. 305 306 Parameters 307 ---------- 308 image : numpy.ndarray 309 Input image to be filtered. 310 kernel_size : int 311 Size of the median filter kernel. Must be a positive odd integer. 312 313 Returns 314 ------- 315 numpy.ndarray 316 Filtered image, with the same shape and dtype as the input image. 317 """ 318 return cv2.medianBlur(src=image, ksize=kernel_size) 319 320 321def clahe(image, clip_limit=None, tile_grid_size=None): 322 """ 323 Apply Contrast Limited Adaptive Histogram Equalization (CLAHE) to an image. 324 325 Parameters 326 ---------- 327 image : numpy.ndarray 328 Input image to be equalized. 329 clip_limit : float, optional 330 Threshold for contrast limiting. If `None`, no clipping is performed. 331 Default is `None`. 332 tile_grid_size : tuple(int, int), optional 333 Number of rows and columns into which the image will be divided. 334 Default is `(8, 8)` (as for OpenCV). 335 336 Returns 337 ------- 338 numpy.ndarray 339 Equalized image, with the same shape and dtype as the input image. 340 """ 341 clahe_op = cv2.createCLAHE(clipLimit=clip_limit, tileGridSize=tile_grid_size) 342 return clahe_op.apply(image) 343 344 345# 346# thresholding 347# 348 349 350def otsu(image): 351 """ 352 Perform Otsu thresholding on a grayscale image. 353 354 This function computes the optimal threshold for the input grayscale image using the Otsu method, 355 and returns the thresholded image and the computed threshold. 356 357 Parameters 358 ---------- 359 image : numpy.ndarray 360 Input grayscale image to be thresholded. 361 362 Returns 363 ------- 364 tuple 365 A tuple containing the threshold value and the thresholded image, both as numpy.ndarray 366 with the same shape and dtype as the input image. 367 368 Raises 369 ------ 370 ValueError 371 If the input image is not grayscale. 372 """ 373 if dito.core.is_color(image=image): 374 raise ValueError("Expected gray image but got color image for Otsu thresholding") 375 (theta, image2) = cv2.threshold(src=image, thresh=-1, maxval=255, type=cv2.THRESH_BINARY | cv2.THRESH_OTSU) 376 return (theta, image2) 377 378 379def otsu_theta(image): 380 """ 381 Compute the Otsu threshold for a grayscale image. 382 383 This function computes the optimal threshold for the input grayscale image using the Otsu method, 384 and returns only the threshold value. 385 386 Parameters 387 ---------- 388 image : numpy.ndarray 389 Input grayscale image. 390 391 Returns 392 ------- 393 float 394 The computed threshold value. 395 """ 396 (theta, image2) = otsu(image=image) 397 return theta 398 399 400def otsu_image(image): 401 """ 402 Threshold a grayscale image using the Otsu method. 403 404 This function computes the optimal threshold for the input grayscale image using the Otsu method, 405 and returns the thresholded image. 406 407 Parameters 408 ---------- 409 image : numpy.ndarray 410 Input grayscale image to be thresholded. 411 412 Returns 413 ------- 414 numpy.ndarray 415 The thresholded image, as a numpy.ndarray with the same shape and dtype as the input image. 416 """ 417 (theta, image2) = otsu(image=image) 418 return image2 419 420 421# 422# morphological operations 423# 424 425 426def morpho_op_kernel(shape, size): 427 """ 428 Create a morphological operation kernel. 429 430 Parameters 431 ---------- 432 shape : int 433 Type of the kernel. The following values are supported: 434 - `cv2.MORPH_RECT`: rectangular kernel 435 - `cv2.MORPH_CROSS`: cross-shaped kernel 436 - `cv2.MORPH_ELLIPSE`: elliptical kernel 437 size : int or tuple of int 438 Size (width, height) of the kernel. If a single integer is provided, assume equal width and height. 439 440 Returns 441 ------- 442 numpy.ndarray 443 The morphological operation kernel. 444 """ 445 ksize = dito.utils.get_validated_tuple(x=size, type_=int, count=2) 446 kernel = cv2.getStructuringElement(shape=shape, ksize=ksize, anchor=(-1, -1)) 447 return kernel 448 449 450def morpho_op(image, operation, shape=cv2.MORPH_ELLIPSE, size=3, anchor=(-1, -1), iterations=1): 451 """ 452 Apply a morphological operation to an image. 453 454 Parameters 455 ---------- 456 image : numpy.ndarray 457 Input image to which the morphological operation is applied. 458 operation : int 459 Type of morphological operation. The following values are supported: 460 - `cv2.MORPH_ERODE`: erosion 461 - `cv2.MORPH_DILATE`: dilation 462 - `cv2.MORPH_OPEN`: opening 463 - `cv2.MORPH_CLOSE`: closing 464 - `cv2.MORPH_GRADIENT`: gradient 465 - `cv2.MORPH_TOPHAT`: top hat 466 - `cv2.MORPH_BLACKHAT`: black hat 467 shape : int 468 Type of the kernel. The following values are supported: 469 - `cv2.MORPH_RECT`: rectangular kernel 470 - `cv2.MORPH_CROSS`: cross-shaped kernel 471 - `cv2.MORPH_ELLIPSE`: elliptical kernel 472 The default value is `cv2.MORPH_ELLIPSE`. 473 size : int or tuple of int 474 Size (width, height) of the kernel. If a single integer is provided, assume equal width and height. 475 The default value is `3`. 476 anchor : tuple of int, optional 477 Anchor point of the structuring element used for the morphological operation. The anchor should lie within 478 the kernel. The default value is `(-1, -1)`, which means that the anchor is at the center of the kernel. 479 iterations : int, optional 480 Number of times the morphological operation is applied. The default value is `1`. 481 482 Returns 483 ------- 484 numpy.ndarray 485 Resulting image after applying the morphological operation, with the same shape and dtype as the input image. 486 """ 487 kernel = morpho_op_kernel(shape=shape, size=size) 488 return cv2.morphologyEx(src=image, op=operation, kernel=kernel, anchor=anchor, iterations=iterations) 489 490 491def dilate(image, **kwargs): 492 """ 493 Apply morphological dilation to an image. 494 495 Parameters 496 ---------- 497 image : numpy.ndarray 498 Input image to be dilated. 499 **kwargs 500 Optional arguments to be passed to `morpho_op`. 501 502 Returns 503 ------- 504 numpy.ndarray 505 Dilated image, with the same shape and dtype as the input image. 506 """ 507 return morpho_op(image=image, operation=cv2.MORPH_DILATE, **kwargs) 508 509 510def erode(image, **kwargs): 511 """ 512 Apply morphological erosion to an image. 513 514 Parameters 515 ---------- 516 image : numpy.ndarray 517 Input image to be eroded. 518 **kwargs 519 Optional arguments to be passed to `morpho_op`. 520 521 Returns 522 ------- 523 numpy.ndarray 524 Eroded image, with the same shape and dtype as the input image. 525 """ 526 return morpho_op(image=image, operation=cv2.MORPH_ERODE, **kwargs) 527 528 529def morpho_open(image, **kwargs): 530 """ 531 Apply morphological opening to an image. 532 533 Parameters 534 ---------- 535 image : numpy.ndarray 536 Input image to be opened. 537 **kwargs 538 Optional arguments to be passed to `morpho_op`. 539 540 Returns 541 ------- 542 numpy.ndarray 543 Image after the opening operation, with the same shape and dtype as the input image. 544 """ 545 return morpho_op(image=image, operation=cv2.MORPH_OPEN, **kwargs) 546 547 548def morpho_close(image, **kwargs): 549 """ 550 Apply morphological closing to an image. 551 552 Parameters 553 ---------- 554 image : numpy.ndarray 555 Input image to be closed. 556 **kwargs 557 Optional arguments to be passed to `morpho_op`. 558 559 Returns 560 ------- 561 numpy.ndarray 562 Image after the closing operation, with the same shape and dtype as the input image. 563 """ 564 return morpho_op(image=image, operation=cv2.MORPH_CLOSE, **kwargs) 565 566 567def blackhat(image, **kwargs): 568 """ 569 Apply the morphological blackhat operation to an image. 570 571 Parameters 572 ---------- 573 image : numpy.ndarray 574 Input image. 575 **kwargs 576 Optional arguments to be passed to `morpho_op`. 577 578 Returns 579 ------- 580 numpy.ndarray 581 Image after the blackhat operation, with the same shape and dtype as the input image. 582 """ 583 return morpho_op(image=image, operation=cv2.MORPH_BLACKHAT, **kwargs) 584 585 586def tophat(image, **kwargs): 587 """ 588 Apply the morphological tophat operation to an image. 589 590 Parameters 591 ---------- 592 image : numpy.ndarray 593 Input image. 594 **kwargs 595 Optional arguments to be passed to `morpho_op`. 596 597 Returns 598 ------- 599 numpy.ndarray 600 Image after the tophat operation, with the same shape and dtype as the input image. 601 """ 602 return morpho_op(image=image, operation=cv2.MORPH_TOPHAT, **kwargs) 603 604 605# 606# filters 607# 608 609 610def dog(image, sigma1, sigma2, return_raw=False, colormap=None): 611 """ 612 Apply the difference of Gaussians (DoG) operation to an image. 613 614 Parameters 615 ---------- 616 image : numpy.ndarray 617 Input image to which the DoG is applied. 618 sigma1 : float 619 Standard deviation of the first Gaussian filter. 620 sigma2 : float 621 Standard deviation of the second Gaussian filter. 622 return_raw : bool, optional 623 If True, return the raw difference image. Otherwise, the difference image is shifted and scaled to match the 624 original dtype range. The default value is False. 625 colormap : str, optional 626 Name of the colormap to use for colorizing the result. If None, no colorization is applied. The default value 627 is None. 628 629 Returns 630 ------- 631 numpy.ndarray 632 Resulting image after applying the DoG, with the same shape and dtype as the input image. 633 """ 634 blur1 = gaussian_blur(image=image, sigma=sigma1).astype(np.float32) 635 blur2 = gaussian_blur(image=image, sigma=sigma2).astype(np.float32) 636 diff = blur1 - blur2 637 if return_raw: 638 return diff 639 else: 640 diff_11 = diff / dito.core.dtype_range(dtype=image.dtype)[1] 641 diff_01 = (diff_11 + 1.0) * 0.5 642 result = dito.convert(image=diff_01, dtype=image.dtype) 643 if colormap is not None: 644 result = dito.visual.colorize(image=result, colormap=colormap) 645 return result 646 647 648def dog_interactive(image, colormap=None): 649 """ 650 Display an interactive window for exploring the difference of Gaussians (DoG) of an image. 651 652 The window displays the input image, two sliders for adjusting the standard deviations of the Gaussian filters used 653 in the DoG, and a visualization of the DoG result. 654 655 Parameters 656 ---------- 657 image : numpy.ndarray 658 Input image to which the DoG is applied. 659 colormap : str, optional 660 Name of the colormap to use for colorizing the result. If None, no colorization is applied. The default value 661 is None. 662 663 Returns 664 ------- 665 None 666 """ 667 window_name = "dito.dog_interactive" 668 sliders = [dito.highgui.FloatSlider(window_name=window_name, name="sigma{}".format(n_slider + 1), min_value=0.0, max_value=15.0, value_count=1001) for n_slider in range(2)] 669 sliders[0].set_value(0.5) 670 sliders[1].set_value(0.8) 671 672 image_show = None 673 while True: 674 if (image_show is None) or any(slider.changed for slider in sliders): 675 sigmas = [sliders[n_slider].get_value() for n_slider in range(2)] 676 images_blur = [gaussian_blur(image=image, sigma=sigmas[n_slider]) for n_slider in range(2)] 677 images_blur = [dito.visual.text(image=image_blur, message="sigma{} = {:.2f}".format(n_slider + 1, sigmas[n_slider])) for (n_slider, image_blur) in enumerate(images_blur)] 678 image_dog = dog(image, sigma1=sigmas[0], sigma2=sigmas[1], return_raw=False, colormap=colormap) 679 image_show = dito.stack([[image, image_dog], images_blur]) 680 key = dito.show(image=image_show, window_name=window_name, wait=10) 681 if key in dito.qkeys(): 682 return 683 684 685# 686# contours 687# 688 689 690class Contour(): 691 """ 692 A class to represent a contour. 693 694 Attributes 695 ---------- 696 points : numpy.ndarray 697 The points that make up the contour. A 2D numpy array of shape `(n, 2)`, where `n` is the number of points 698 in the contour. Each row contains the `(x, y)` coordinates of a point. 699 """ 700 701 def __init__(self, points): 702 """ 703 Parameters 704 ---------- 705 points : numpy.ndarray 706 The points defining the contour. A 2D numpy array of shape `(n, 2)`, where `n` is the number of points 707 in the contour. Each row contains the `(x, y)` coordinates of a point. 708 """ 709 self.points = points 710 711 def __len__(self): 712 """ 713 Return the number of points in the contour. 714 715 Returns 716 ------- 717 int 718 The number of points in the contour. 719 """ 720 return len(self.points) 721 722 def __eq__(self, other): 723 """ 724 Check if two contours are equal. 725 726 Parameters 727 ---------- 728 other : Contour 729 Another instance of the `Contour` class. 730 731 Returns 732 ------- 733 bool 734 True if the two contours are equal, False otherwise. 735 """ 736 if not isinstance(other, Contour): 737 raise TypeError("Argument 'other' must be a contour") 738 739 if len(self) != len(other): 740 return False 741 742 return np.array_equal(self.points, other.points) 743 744 def copy(self): 745 """ 746 Return a copy of the current instance of the `Contour` class. 747 748 Returns 749 ------- 750 Contour 751 A copy of the current instance of the `Contour` class. 752 """ 753 return Contour(points=self.points.copy()) 754 755 def get_center(self): 756 """ 757 Return the center point `(x, y)` of the contour. 758 759 Returns 760 ------- 761 numpy.ndarray 762 A 2D numpy array representing the `(x, y)` coordinates of the center point of the contour. 763 """ 764 return np.mean(self.points, axis=0) 765 766 def get_center_x(self): 767 """ 768 Return the x-coordinate of the center point of the contour. 769 770 Returns 771 ------- 772 float 773 The x-coordinate of the center point of the contour. 774 """ 775 return np.mean(self.points[:, 0]) 776 777 def get_center_y(self): 778 """ 779 Return the y-coordinate of the center point of the contour. 780 781 Returns 782 ------- 783 float 784 The y-coordinate of the center point of the contour. 785 """ 786 return np.mean(self.points[:, 1]) 787 788 def get_min_x(self): 789 """ 790 Return the minimum x-coordinate of the contour. 791 792 Returns 793 ------- 794 float 795 The minimum x-coordinate of the contour. 796 """ 797 return np.min(self.points[:, 0]) 798 799 def get_max_x(self): 800 """ 801 Return the maximum x-coordinate of the contour. 802 803 Returns 804 ------- 805 float 806 The maximum x-coordinate of the contour. 807 """ 808 return np.max(self.points[:, 0]) 809 810 def get_width(self): 811 """ 812 Return the width of the contour. 813 814 Returns 815 ------- 816 float 817 The width of the contour. 818 """ 819 return self.get_max_x() - self.get_min_x() 820 821 def get_min_y(self): 822 """ 823 Return the minimum y-coordinate of the contour. 824 825 Returns 826 ------- 827 float 828 The minimum y-coordinate of the contour. 829 """ 830 return np.min(self.points[:, 1]) 831 832 def get_max_y(self): 833 """ 834 Return the maximum y-coordinate of the contour. 835 836 Returns 837 ------- 838 float 839 The maximum y-coordinate of the contour. 840 """ 841 return np.max(self.points[:, 1]) 842 843 def get_height(self): 844 """ 845 Return the height of the contour. 846 847 Returns 848 ------- 849 float 850 The height of the contour. 851 """ 852 return self.get_max_y() - self.get_min_y() 853 854 def get_area(self, mode="draw"): 855 """ 856 Compute the area of the contour. 857 858 Parameters 859 ---------- 860 mode : {"draw", "calc"}, optional 861 The method to use for computing the area. If "draw", the area is computed by drawing the contour as a filled 862 white shape on a black image and counting the number of white pixels. If "calc", the area is computed using the 863 cv2.contourArea() function. Default is "draw". 864 865 Returns 866 ------- 867 float 868 The area of the contour. 869 """ 870 if mode == "draw": 871 image = self.draw_standalone(color=(1,), thickness=1, filled=True, antialias=False, border=2) 872 return np.sum(image) 873 874 elif mode == "calc": 875 return cv2.contourArea(contour=self.points) 876 877 else: 878 raise ValueError("Invalid value for argument 'mode': '{}'".format(mode)) 879 880 def get_perimeter(self): 881 """ 882 Compute the perimeter of the contour. 883 884 Returns 885 ------- 886 float 887 The perimeter of the contour. 888 """ 889 return cv2.arcLength(curve=self.points, closed=True) 890 891 def get_circularity(self): 892 """ 893 Compute the circularity of the contour. 894 895 Returns 896 ------- 897 float 898 The circularity of the contour. 899 """ 900 r_area = np.sqrt(self.get_area() / np.pi) 901 r_perimeter = self.get_perimeter() / (2.0 * np.pi) 902 return r_area / r_perimeter 903 904 def get_ellipse(self): 905 """ 906 Fit an ellipse to the contour. 907 908 Returns 909 ------- 910 tuple 911 A tuple `(center, size, angle)`, where `center` is a tuple `(x, y)` representing the center point 912 of the ellipse, `size` is a tuple `(width, height)` representing the size of the ellipse, and `angle` 913 is the angle (in degrees) between the major axis of the ellipse and the x-axis. 914 """ 915 return cv2.fitEllipse(points=self.points) 916 917 def get_eccentricity(self): 918 """ 919 Calculate the eccentricity of the contour. 920 921 Returns 922 ------- 923 float 924 The eccentricity of the contour. 925 """ 926 ellipse = self.get_ellipse() 927 (width, height) = ellipse[1] 928 semi_major_axis = max(width, height) * 0.5 929 semi_minor_axis = min(width, height) * 0.5 930 eccentricity = math.sqrt(1.0 - (semi_minor_axis / semi_major_axis)**2) 931 return eccentricity 932 933 def get_moments(self): 934 """ 935 Calculate the moments of the contour. 936 937 The moment values can be used to calculate various properties of the contour, such as its centroid, 938 orientation, and size. 939 940 Returns 941 ------- 942 dict 943 A dictionary of moment values for the contour. 944 """ 945 return cv2.moments(array=self.points, binaryImage=False) 946 947 def get_hu_moments(self, log=True): 948 """ 949 Calculate the seven Hu moments for the contour. 950 951 These moments are invariant shape descriptors that can be used to capture shape properties of a contour, and 952 the first six of them are invariant to translation, scale, and rotation. However, the seventh moment is not 953 invariant to reflection and changes its sign under reflection. 954 955 Parameters 956 ---------- 957 log : bool, optional 958 If True (default), the logarithm of the absolute value of the Hu moments will be returned. If False, the raw 959 Hu moments will be returned. 960 961 Returns 962 ------- 963 numpy.ndarray 964 A 1D numpy array containing the seven Hu moments for the contour. If `log=True`, the values will be the 965 logarithm of the absolute value of the Hu moments. 966 """ 967 hu_moments = cv2.HuMoments(m=self.get_moments()) 968 if log: 969 return np.sign(hu_moments) * np.log10(np.abs(hu_moments)) 970 else: 971 return hu_moments 972 973 def shift(self, offset_x=None, offset_y=None): 974 """ 975 Shift the contour by a given offset along the x and/or y axis. 976 977 Parameters 978 ---------- 979 offset_x : float, optional 980 The amount by which to shift the contour along the x-axis. If not specified, the contour is not shifted along the x-axis. 981 offset_y : float, optional 982 The amount by which to shift the contour along the y-axis. If not specified, the contour is not shifted along the y-axis. 983 984 Returns 985 ------- 986 None 987 """ 988 if offset_x is not None: 989 self.points[:, 0] += offset_x 990 if offset_y is not None: 991 self.points[:, 1] += offset_y 992 993 def draw(self, image, color, thickness=1, filled=True, antialias=False, offset=None): 994 """ 995 Draw the contour into an existing image. 996 997 The image is changed in-place. 998 999 Parameters 1000 ---------- 1001 image : numpy.ndarray 1002 The image into which the contour will be drawn. 1003 color : tuple 1004 The color of the contour. Its length must equal the channel count of the image. 1005 thickness : int, optional 1006 The thickness of the contour lines. Has no effect if `filled` is True. Default is 1. 1007 filled : bool, optional 1008 Whether the contour should be filled. If True, the interior of the contour is filled with the `color` value. Default is True. 1009 antialias : bool, optional 1010 Whether antialiasing should be applied when drawing the contour. Default is False. 1011 offset : tuple or list or numpy.ndarray, optional 1012 The `(x, y)` coordinates of the offset of the contour from the origin of the image. Default is None, which corresponds to no offset. 1013 1014 Returns 1015 ------- 1016 None 1017 """ 1018 cv2.drawContours(image=image, contours=[np.round(self.points).astype(np.int32)], contourIdx=0, color=color, thickness=cv2.FILLED if filled else thickness, lineType=cv2.LINE_AA if antialias else cv2.LINE_8, offset=offset) 1019 1020 def draw_standalone(self, color, thickness=1, filled=True, antialias=False, border=0): 1021 """ 1022 Draw the contour as a standalone image. 1023 1024 The image has the same size as the contour (which thus is centered in the image), 1025 but an additional border can be specified. 1026 1027 Parameters 1028 ---------- 1029 color : tuple or list or numpy.ndarray 1030 The color of the contour. Currently, only grayscale mode is supported. 1031 thickness : int, optional 1032 The thickness of the contour lines. Has no effect if `filled` is True. Default is 1. 1033 filled : bool, optional 1034 Whether the contour should be filled. If True, the interior of the contour is filled with the `color` value. Default is True. 1035 antialias : bool, optional 1036 Whether antialiasing should be applied when drawing the contour. Default is False. 1037 border : int, optional 1038 The size of the border around the contour. Default is 0. 1039 1040 Returns 1041 ------- 1042 numpy.ndarray 1043 A 2D numpy array representing the image of the contour. 1044 """ 1045 image = np.zeros(shape=(2 * border + self.get_height(), 2 * border + self.get_width()), dtype=np.uint8) 1046 self.draw(image=image, color=color, thickness=thickness, filled=filled, antialias=antialias, offset=(border - self.get_min_x(), border - self.get_min_y())) 1047 return image 1048 1049 1050class ContourList(): 1051 """ 1052 A class representing a list of contours. 1053 1054 It allows for easy filtering of contours by various properties and offers 1055 additional helper functions such as drawing. 1056 1057 Attributes 1058 ---------- 1059 contours : list of Contour objects 1060 The list of contours stored in the ContourList object. 1061 """ 1062 1063 def __init__(self, contours_): 1064 """ 1065 Parameters 1066 ---------- 1067 contours_ : list of Contour objects 1068 The list of contours to be stored. 1069 """ 1070 self.contours = contours_ 1071 1072 def __len__(self): 1073 """ 1074 Return the number of contours. 1075 1076 Returns 1077 ------- 1078 int 1079 The number of contours stored in the ContourList object. 1080 """ 1081 return len(self.contours) 1082 1083 def __eq__(self, other): 1084 """ 1085 Check if two ContourList objects are equal. 1086 1087 Parameters 1088 ---------- 1089 other : object 1090 The object to compare to. 1091 1092 Returns 1093 ------- 1094 bool 1095 True if the two objects are equal, False otherwise. 1096 1097 Raises 1098 ------ 1099 TypeError 1100 If the argument `other` is not a ContourList object. 1101 """ 1102 if not isinstance(other, ContourList): 1103 raise TypeError("Argument 'other' must be a contour list") 1104 1105 if len(self) != len(other): 1106 return False 1107 1108 for (contour_self, contour_other) in zip(self.contours, other.contours): 1109 if contour_self != contour_other: 1110 return False 1111 1112 return True 1113 1114 def __getitem__(self, key): 1115 """ 1116 Return the contour object at the specified index. 1117 1118 Parameters 1119 ---------- 1120 key : int 1121 The index of the contour object to be returned. 1122 1123 Returns 1124 ------- 1125 object 1126 The contour object at the specified index. 1127 """ 1128 return self.contours[key] 1129 1130 def copy(self): 1131 """ 1132 Return a copy of the ContourList object. 1133 1134 Returns 1135 ------- 1136 object 1137 A copy of the ContourList object. 1138 """ 1139 contours_copy = [contour.copy() for contour in self.contours] 1140 return ContourList(contours_=contours_copy) 1141 1142 def filter(self, func, min_value=None, max_value=None): 1143 """ 1144 Filter the contour list based on a given function and range of values. 1145 1146 Only contours whose function values fall within the specified range are retained. 1147 The contour list is modified in place. 1148 1149 Parameters 1150 ---------- 1151 func : function 1152 The function used to extract a value from each contour. It must return a value which can be compared against 1153 `min_value` and/or `max_value`. 1154 min_value : float or None, optional 1155 The minimum value of the extracted value for a contour to be kept. Contours with extracted values lower than 1156 this will be removed. If None, no minimum filter is applied. Default is None. 1157 max_value : float or None, optional 1158 The maximum value of the extracted value for a contour to be kept. Contours with extracted values higher than 1159 this will be removed. If None, no maximum filter is applied. Default is None. 1160 1161 Returns 1162 ------- 1163 None 1164 """ 1165 if (min_value is None) and (max_value is None): 1166 # nothing to do 1167 return 1168 1169 # filter 1170 contours_filtered = [] 1171 for contour in self.contours: 1172 value = func(contour) 1173 if (min_value is not None) and (value < min_value): 1174 continue 1175 if (max_value is not None) and (value > max_value): 1176 continue 1177 contours_filtered.append(contour) 1178 self.contours = contours_filtered 1179 1180 def filter_center_x(self, min_value=None, max_value=None): 1181 """ 1182 Filter the list of contours by the x-coordinate of their centers. 1183 1184 Only contours whose center x-coordinates fall within the specified range are retained. 1185 The contour list is modified in place. 1186 1187 Parameters 1188 ---------- 1189 min_value : float or None, optional 1190 The minimum allowed value of the x-coordinate. If a contour's center x-coordinate is less than this, it is 1191 discarded. If None, no lower bound is applied. Default is None. 1192 max_value : float or None, optional 1193 The maximum allowed value of the x-coordinate. If a contour's center x-coordinate is greater than this, it is 1194 discarded. If None, no upper bound is applied. Default is None. 1195 1196 Returns 1197 ------- 1198 None 1199 """ 1200 self.filter(func=operator.methodcaller("get_center_x"), min_value=min_value, max_value=max_value) 1201 1202 def filter_center_y(self, min_value=None, max_value=None): 1203 """ 1204 Filter the list of contours by the y-coordinate of their centers. 1205 1206 Only contours whose center y-coordinates fall within the specified range are retained. 1207 The contour list is modified in place. 1208 1209 Parameters 1210 ---------- 1211 min_value : float or None, optional 1212 The minimum allowed value of the y-coordinate. If a contour's center y-coordinate is less than this, it is 1213 discarded. If None, no lower bound is applied. Default is None. 1214 max_value : float or None, optional 1215 The maximum allowed value of the y-coordinate. If a contour's center y-coordinate is greater than this, it is 1216 discarded. If None, no upper bound is applied. Default is None. 1217 1218 Returns 1219 ------- 1220 None 1221 """ 1222 self.filter(func=operator.methodcaller("get_center_y"), min_value=min_value, max_value=max_value) 1223 1224 def filter_area(self, min_value=None, max_value=None, mode="draw"): 1225 """ 1226 Filter the list of contours by their area. 1227 1228 Only contours whose areas fall within the specified range are retained. 1229 The contour list is modified in place. 1230 1231 Parameters 1232 ---------- 1233 min_value : float or None, optional 1234 The minimum allowed area. If a contour's area is less than this, it is discarded. If None, no lower bound 1235 is applied. Default is None. 1236 max_value : float or None, optional 1237 The maximum allowed area. If a contour's area is greater than this, it is discarded. If None, no upper bound 1238 is applied. Default is None. 1239 mode : str, optional 1240 The mode to use for computing the area. See `get_area` for details. Default is 'draw'. 1241 1242 Returns 1243 ------- 1244 None 1245 """ 1246 self.filter(func=operator.methodcaller("get_area", mode=mode), min_value=min_value, max_value=max_value) 1247 1248 def filter_perimeter(self, min_value=None, max_value=None): 1249 """ 1250 Filter the list of contours by their perimeters. 1251 1252 Only contours whose perimeters fall within the specified range are retained. 1253 The contour list is modified in place. 1254 1255 Parameters 1256 ---------- 1257 min_value : float or None, optional 1258 The minimum allowed perimeter. If a contour's perimeter is less than this, it is discarded. 1259 If None, no lower bound is applied. Default is None. 1260 max_value : float or None, optional 1261 The maximum allowed perimeter. If a contour's perimeter is greater than this, it is discarded. 1262 If None, no upper bound is applied. Default is None. 1263 1264 Returns 1265 ------- 1266 None 1267 """ 1268 self.filter(func=operator.methodcaller("get_perimeter"), min_value=min_value, max_value=max_value) 1269 1270 def filter_circularity(self, min_value=None, max_value=None): 1271 """ 1272 Filter the list of contours by their circularity. 1273 1274 Only contours whose circularities fall within the specified range are retained. 1275 The contour list is modified in place. 1276 1277 Parameters 1278 ---------- 1279 min_value : float or None, optional 1280 The minimum allowed circularity. If a contour's circularity is less than this, it is discarded. 1281 If None, no lower bound is applied. Default is None. 1282 max_value : float or None, optional 1283 The maximum allowed circularity. If a contour's circularity is greater than this, it is discarded. 1284 If None, no upper bound is applied. Default is None. 1285 1286 Returns 1287 ------- 1288 None 1289 """ 1290 self.filter(func=operator.methodcaller("get_circularity"), min_value=min_value, max_value=max_value) 1291 1292 def find_largest(self, return_index=True): 1293 """ 1294 Return the contour (or the contour index) with the largest area. 1295 1296 Parameters 1297 ---------- 1298 return_index : bool, optional 1299 If `True`, return the index of the largest contour. Otherwise, return the contour object itself. 1300 Default is `True`. 1301 1302 Returns 1303 ------- 1304 int or Contour or None 1305 The index of the largest contour if `return_index` is `True`, or the largest contour object if 1306 `return_index` is `False`. If no contour is found, returns None. 1307 """ 1308 max_area = None 1309 argmax_area = None 1310 for (n_contour, contour) in enumerate(self.contours): 1311 area = contour.get_area() 1312 if (max_area is None) or (area > max_area): 1313 max_area = area 1314 argmax_area = n_contour 1315 1316 if argmax_area is None: 1317 return None 1318 else: 1319 if return_index: 1320 return argmax_area 1321 else: 1322 return self.contours[argmax_area] 1323 1324 def draw_all(self, image, colors=None, **kwargs): 1325 """ 1326 Draw all the contours into an image using a different color for each contour. 1327 1328 Parameters 1329 ---------- 1330 image : numpy.ndarray 1331 The image into which the contours will be drawn. 1332 colors : list of tuples, optional 1333 The colors to use for each contour. The length of the list must be equal to the number of contours. 1334 If not provided, random colors will be generated. Default is None. 1335 **kwargs 1336 Additional keyword arguments to pass to the `Contour.draw()` method. 1337 1338 Returns 1339 ------- 1340 None 1341 """ 1342 if colors is None: 1343 colors = tuple(dito.random_color() for _ in range(len(self))) 1344 1345 for (contour, color) in zip(self.contours, colors): 1346 contour.draw(image=image, color=color, **kwargs) 1347 1348 1349class ContourFinder(ContourList): 1350 """ 1351 Extension of `ContourList` with support to find contours in an image. 1352 1353 Between OpenCV 3.x and 4.x, the API of `cv2.findContours` changed. This 1354 class is basically a wrapper for `cv2.findContours` which works for both 1355 versions. 1356 1357 As a subclass of `ContourList`, it inherits all the contour list 1358 manipulation methods defined by its parent class. 1359 1360 Attributes 1361 ---------- 1362 image : numpy.ndarray 1363 The input image, stored as a copy of the input argument. 1364 contours : list of Contour 1365 The contours found in the input image. 1366 """ 1367 1368 def __init__(self, image): 1369 """ 1370 Initialize a `ContourFinder` instance and find contours in the given `image`. 1371 1372 Parameters 1373 ---------- 1374 image : numpy.ndarray 1375 The image from which to find contours. Will be converted to dtype `numpy.uint8` 1376 """ 1377 self.image = image.copy() 1378 if self.image.dtype == bool: 1379 self.image = dito.core.convert(image=self.image, dtype=np.uint8) 1380 contours_ = self.find_contours(image=self.image) 1381 super().__init__(contours_=contours_) 1382 1383 @staticmethod 1384 def find_contours(image): 1385 """ 1386 Find the contours in the given `image`. 1387 1388 Parameters 1389 ---------- 1390 image : numpy.ndarray 1391 The image from which to find contours. 1392 1393 Returns 1394 ------- 1395 list of Contour 1396 A list of instances of the `Contour` class, one for each contour found in the input image. 1397 """ 1398 1399 # find raw contours 1400 result = cv2.findContours(image=image, mode=cv2.RETR_LIST, method=cv2.CHAIN_APPROX_NONE) 1401 1402 # compatible with OpenCV 3.x and 4.x, see https://stackoverflow.com/a/53909713/1913780 1403 contours_raw = result[-2] 1404 1405 # return tuple of instances of class `Contour` 1406 return [Contour(points=contour_raw[:, 0, :]) for contour_raw in contours_raw] 1407 1408 1409def contours(image): 1410 """ 1411 Find and return all contours in the given image. 1412 1413 It is a convenience wrapper for `ContourFinder`. 1414 1415 Parameters 1416 ---------- 1417 image : numpy.ndarray 1418 The image in which to find the contours. 1419 1420 Returns 1421 ------- 1422 ContourList 1423 An instance of the `ContourList` class containing all the found contours. 1424 """ 1425 contour_finder = ContourFinder(image=image) 1426 return contour_finder.contours 1427 1428 1429class VoronoiPartition(ContourList): 1430 """ 1431 Extension of `ContourList` where the contours are derived as facets of the Voronoi partition of a set of given points. 1432 1433 As a subclass of `ContourList`, it inherits all the contour list 1434 manipulation methods defined by its parent class. 1435 1436 Attributes 1437 ---------- 1438 contours : list of Contour 1439 The list of Voronoi facets, each represented as a `Contour` object. 1440 """ 1441 1442 def __init__(self, image_size, points): 1443 """ 1444 Initialize a `VoronoiPartition` instance from a set of input points. 1445 1446 Parameters 1447 ---------- 1448 image_size : tuple of int 1449 The size of the image (width, height) in pixels. 1450 points : numpy.ndarray 1451 The array of input points for which to compute the Voronoi partition. Each row represents a point (x, y). 1452 """ 1453 contours_ = self.get_facets(image_size=image_size, points=points) 1454 super().__init__(contours_=contours_) 1455 1456 @staticmethod 1457 def get_facets(image_size, points): 1458 """ 1459 Calculate the Voronoi partition based on the given points. 1460 1461 Parameters 1462 ---------- 1463 image_size : tuple of int 1464 The size of the image (width, height) in pixels. 1465 points : numpy.ndarray 1466 The array of input points for which to compute the Voronoi partition. Each row represents a point (x, y). 1467 1468 Returns 1469 ------- 1470 list of Contour 1471 The list of Voronoi facets, each represented as a `Contour` object. 1472 """ 1473 subdiv = cv2.Subdiv2D((0, 0, image_size[0], image_size[1])) 1474 for point in points: 1475 subdiv.insert(pt=point) 1476 (voronoi_facets, voronoi_centers) = subdiv.getVoronoiFacetList(idx=[]) 1477 return [Contour(voronoi_facet) for voronoi_facet in voronoi_facets] 1478 1479 1480def voronoi(image_size, points): 1481 """ 1482 Compute the Voronoi partition of a set of input points. 1483 1484 It is a convenience wrapper for `VoronoiPartition`. 1485 1486 Parameters 1487 ---------- 1488 image_size : tuple of int 1489 The size of the image (width, height) in pixels. 1490 points : numpy.ndarray 1491 The array of input points. Each row represents a point (x, y). 1492 1493 Returns 1494 ------- 1495 list of Contour 1496 The list of Voronoi facets, each represented as a `Contour` object. 1497 """ 1498 voronoi_partition = VoronoiPartition(image_size=image_size, points=points) 1499 return voronoi_partition.contours
22def argmin(image): 23 """ 24 Compute the coordinates of the minimum value in the image. 25 26 Parameters 27 ---------- 28 image : numpy.ndarray 29 The input image. 30 31 Returns 32 ------- 33 tuple 34 The coordinates of the minimum value in the image. 35 36 Notes 37 ----- 38 The order of the indices is equivalent to the order of the image axes. 39 This may differ from common conventions that use (x, y) coordinates. 40 """ 41 return np.unravel_index(np.argmin(image), image.shape)
Compute the coordinates of the minimum value in the image.
Parameters
- image (numpy.ndarray): The input image.
Returns
- tuple: The coordinates of the minimum value in the image.
Notes
The order of the indices is equivalent to the order of the image axes. This may differ from common conventions that use (x, y) coordinates.
44def argmax(image): 45 """ 46 Compute the coordinates of the maximum value in the image. 47 48 Parameters 49 ---------- 50 image : numpy.ndarray 51 The input image. 52 53 Returns 54 ------- 55 tuple 56 The coordinates of the maximum value in the image. 57 58 Notes 59 ----- 60 The order of the indices is equivalent to the order of the image axes. 61 This may differ from common conventions that use (x, y) coordinates. 62 """ 63 return np.unravel_index(np.argmax(image), image.shape)
Compute the coordinates of the maximum value in the image.
Parameters
- image (numpy.ndarray): The input image.
Returns
- tuple: The coordinates of the maximum value in the image.
Notes
The order of the indices is equivalent to the order of the image axes. This may differ from common conventions that use (x, y) coordinates.
66def nms_iter(image, peak_radius): 67 """ 68 Iterate through peaks in the image using non-maximum suppression (NMS). 69 70 The function yields peaks by repeatedly finding the maximum value in the image, suppressing its 71 neighborhood within the given peak radius, and repeating the process. The iteration stops when no 72 more positive values remain in the image. 73 74 Parameters 75 ---------- 76 image : numpy.ndarray 77 The input grayscale image. 78 peak_radius : int 79 The radius around each peak to suppress in subsequent iterations. 80 81 Yields 82 ------ 83 dict 84 A dictionary containing the peak index, coordinates, and value: 85 - 'n_peak' : int, the index of the current peak. 86 - 'peak_xy' : tuple, the (x, y) coordinates of the peak. 87 - 'peak_value' : float, the value of the peak. 88 """ 89 90 # peak radius must be a non-negative int 91 if not (isinstance(peak_radius, int) and (peak_radius >= 0)): 92 raise ValueError(f"Argument 'peak_radius' must be a non-negative integer (but is: {peak_radius})") 93 94 # only allow images of shape (Y, X) or (Y, X, 1) 95 if not dito.is_gray(image): 96 raise ValueError(f"Image must be grayscale (shapes (Y, X) or (Y, X, 1)), but has shape {image.shape}") 97 98 # remove the last singleton dimension if necessary 99 image_work = image.copy() 100 if len(image_work.shape) == 3: 101 image_work = image_work[:, :, 0] 102 103 for n_peak in itertools.count(): 104 # extract max 105 (peak_y, peak_x) = argmax(image_work) 106 peak_value = image_work[peak_y, peak_x] 107 108 # stop if there are no positive values left (otherwise, we might hit an infinite loop) 109 if peak_value <= 0: 110 return 111 112 # suppress neighborhood 113 image_work[ 114 max(0, peak_y - peak_radius):min(image_work.shape[0], peak_y + peak_radius + 1), 115 max(0, peak_x - peak_radius):min(image_work.shape[1], peak_x + peak_radius + 1), 116 ] = 0 117 118 yield { 119 "n_peak": n_peak, 120 "peak_xy": (peak_x, peak_y), 121 "peak_value": peak_value, 122 }
Iterate through peaks in the image using non-maximum suppression (NMS).
The function yields peaks by repeatedly finding the maximum value in the image, suppressing its neighborhood within the given peak radius, and repeating the process. The iteration stops when no more positive values remain in the image.
Parameters
- image (numpy.ndarray): The input grayscale image.
- peak_radius (int): The radius around each peak to suppress in subsequent iterations.
Yields
- dict: A dictionary containing the peak index, coordinates, and value:
- 'n_peak' : int, the index of the current peak.
- 'peak_xy' : tuple, the (x, y) coordinates of the peak.
- 'peak_value' : float, the value of the peak.
125def nms(image, peak_radius, max_peak_count=1000, rel_max_value=0.1): 126 """ 127 Perform non-maximum suppression (NMS) to extract peaks from the image. 128 129 The function finds peaks in the image, suppressing neighboring values around each peak and iterating 130 until the specified maximum peak count or relative peak value threshold is reached. 131 132 Parameters 133 ---------- 134 image : numpy.ndarray 135 The input grayscale image. 136 peak_radius : int 137 The radius around each peak to suppress in subsequent iterations. 138 max_peak_count : int, optional 139 The maximum number of peaks to extract (default is 1000). 140 rel_max_value : float, optional 141 The relative peak value threshold. The extraction process stops when the peak value falls below 142 this proportion of the maximum peak value (default is 0.1). 143 144 Returns 145 ------- 146 list of dict 147 A list of dictionaries, where each dictionary contains information about a detected peak: 148 - 'n_peak' : int, the index of the peak. 149 - 'peak_xy' : tuple, the (x, y) coordinates of the peak. 150 - 'peak_value' : the value of the peak. 151 """ 152 153 # check argument 'max_peak_count' 154 if not (isinstance(max_peak_count, int) and (max_peak_count >= 1)): 155 raise ValueError(f"Argument 'max_peak_count' must be an integer >= 1, but is: {max_peak_count}") 156 157 # check argument 'rel_max_value' 158 if not (isinstance(rel_max_value, float) and (0.0 <= rel_max_value <= 1.0)): 159 raise ValueError(f"Argument 'rel_max_value' must be a float between 0.0 and 1.0 (both inclusive), but is: {rel_max_value}") 160 161 peaks = [] 162 max_value = None 163 for peak in nms_iter(image=image, peak_radius=peak_radius): 164 if (peak["n_peak"] + 1) >= max_peak_count: 165 # stop if max peak count was reached 166 break 167 if max_value is None: 168 # use first peak's value as reference for all other values (when comparing) 169 max_value = peak["peak_value"] 170 else: 171 # stop if peak value is too small 172 if (peak["peak_value"] / max_value) < rel_max_value: 173 break 174 peaks.append(peak) 175 176 return peaks
Perform non-maximum suppression (NMS) to extract peaks from the image.
The function finds peaks in the image, suppressing neighboring values around each peak and iterating until the specified maximum peak count or relative peak value threshold is reached.
Parameters
- image (numpy.ndarray): The input grayscale image.
- peak_radius (int): The radius around each peak to suppress in subsequent iterations.
- max_peak_count (int, optional): The maximum number of peaks to extract (default is 1000).
- rel_max_value (float, optional): The relative peak value threshold. The extraction process stops when the peak value falls below this proportion of the maximum peak value (default is 0.1).
Returns
- list of dict: A list of dictionaries, where each dictionary contains information about a detected peak:
- 'n_peak' : int, the index of the peak.
- 'peak_xy' : tuple, the (x, y) coordinates of the peak.
- 'peak_value' : the value of the peak.
179def clipped_diff(image1, image2, scale=None, offset=None, apply_abs=False): 180 """ 181 Compute the clipped difference between two images. 182 183 The `image1` and `image2` inputs must have the same dtype. The function computes the element-wise difference 184 between `image1` and `image2`, and then applies an optional offset and scale factor to the difference values. 185 The resulting values are clipped to the original dtype range, to prevent overflow or underflow. 186 187 Parameters 188 ---------- 189 image1 : numpy.ndarray 190 The first input image (minuend). 191 image2 : numpy.ndarray 192 The second input image (subtrahend). 193 scale : float, optional 194 The scale factor to apply to the difference values. If specified, the difference values are multiplied by 195 `scale` before any offset is applied. The default value is `None`, which means no scaling is applied. 196 offset : float, optional 197 The offset value to add to the difference values. If specified, the difference values are increased by 198 `offset` after any scaling is applied. The default value is `None`, which means no offset is applied. 199 apply_abs : bool, optional 200 If `True`, the absolute value of the difference image is computed after a scaling and/or offset is applied. 201 The default value is `False`. 202 203 Returns 204 ------- 205 numpy.ndarray 206 The clipped difference image, with the same shape and dtype as the input images. 207 """ 208 209 # assert equal dtypes 210 if image1.dtype != image2.dtype: 211 raise ValueError("Both images must have the same dtypes (but have '{}' and '{}')".format(image1.dtype, image2.dtype)) 212 dtype = image1.dtype 213 dtype_range = dito.core.dtype_range(dtype=dtype) 214 215 # raw diff 216 diff = image1.astype(np.float32) - image2.astype(np.float32) 217 218 # apply offset, scale, and abs if specified 219 if scale is not None: 220 diff *= scale 221 if offset is not None: 222 diff += offset 223 if apply_abs: 224 diff = np.abs(diff) 225 226 # clip values outside of original range 227 diff = dito.clip(image=diff, lower=dtype_range[0], upper=dtype_range[1]) 228 229 return diff.astype(dtype)
Compute the clipped difference between two images.
The image1 and image2 inputs must have the same dtype. The function computes the element-wise difference
between image1 and image2, and then applies an optional offset and scale factor to the difference values.
The resulting values are clipped to the original dtype range, to prevent overflow or underflow.
Parameters
- image1 (numpy.ndarray): The first input image (minuend).
- image2 (numpy.ndarray): The second input image (subtrahend).
- scale (float, optional):
The scale factor to apply to the difference values. If specified, the difference values are multiplied by
scalebefore any offset is applied. The default value isNone, which means no scaling is applied. - offset (float, optional):
The offset value to add to the difference values. If specified, the difference values are increased by
offsetafter any scaling is applied. The default value isNone, which means no offset is applied. - apply_abs (bool, optional):
If
True, the absolute value of the difference image is computed after a scaling and/or offset is applied. The default value isFalse.
Returns
- numpy.ndarray: The clipped difference image, with the same shape and dtype as the input images.
232def abs_diff(image1, image2): 233 """ 234 Compute the absolute difference between two images. 235 236 The `image1` and `image2` inputs must have the same dtype. The function computes the element-wise absolute 237 difference between `image1` and `image2`, and then clips the resulting values to the original dtype range, 238 to prevent overflow or underflow (which might happen for signed integer dtypes). 239 240 Parameters 241 ---------- 242 image1 : numpy.ndarray 243 The first input image (minuend). 244 image2 : numpy.ndarray 245 The second input image (subtrahend). 246 247 Returns 248 ------- 249 numpy.ndarray 250 The absolute difference image, with the same shape and dtype as the input images. 251 """ 252 return clipped_diff(image1=image1, image2=image2, scale=None, offset=None, apply_abs=True)
Compute the absolute difference between two images.
The image1 and image2 inputs must have the same dtype. The function computes the element-wise absolute
difference between image1 and image2, and then clips the resulting values to the original dtype range,
to prevent overflow or underflow (which might happen for signed integer dtypes).
Parameters
- image1 (numpy.ndarray): The first input image (minuend).
- image2 (numpy.ndarray): The second input image (subtrahend).
Returns
- numpy.ndarray: The absolute difference image, with the same shape and dtype as the input images.
255def shifted_diff(image1, image2): 256 """ 257 Compute the shifted difference between two images. 258 259 The `image1` and `image2` inputs must have the same dtype. The function computes the element-wise difference 260 between `image1` and `image2`, and then applies a scale and offset to the difference values to shift the result 261 back into the original dtype range such that there is no need for clipping 262 263 Parameters 264 ---------- 265 image1 : numpy.ndarray 266 The first input image (minuend). 267 image2 : numpy.ndarray 268 The second input image (subtrahend). 269 270 Returns 271 ------- 272 numpy.ndarray 273 The shifted difference image, with the same shape and dtype as the input images. 274 """ 275 dtype_range = dito.core.dtype_range(dtype=image1.dtype) 276 return clipped_diff(image1=image1, image2=image2, scale=0.5, offset=0.5 * (dtype_range[0] + dtype_range[1]), apply_abs=False)
Compute the shifted difference between two images.
The image1 and image2 inputs must have the same dtype. The function computes the element-wise difference
between image1 and image2, and then applies a scale and offset to the difference values to shift the result
back into the original dtype range such that there is no need for clipping
Parameters
- image1 (numpy.ndarray): The first input image (minuend).
- image2 (numpy.ndarray): The second input image (subtrahend).
Returns
- numpy.ndarray: The shifted difference image, with the same shape and dtype as the input images.
279def gaussian_blur(image, sigma): 280 """ 281 Apply Gaussian blur to an image. 282 283 The filter kernel size is adapted to `sigma` automatically by OpenCV's 284 `cv2.GaussianBlur`. 285 286 Parameters 287 ---------- 288 image : numpy.ndarray 289 Input image to be blurred. 290 sigma : float 291 Standard deviation for Gaussian kernel. Must be greater than 0.0. 292 293 Returns 294 ------- 295 numpy.ndarray 296 Blurred image, with the same shape and dtype as the input image. 297 """ 298 if sigma <= 0.0: 299 return image 300 return cv2.GaussianBlur(src=image, ksize=None, sigmaX=sigma)
Apply Gaussian blur to an image.
The filter kernel size is adapted to sigma automatically by OpenCV's
cv2.GaussianBlur.
Parameters
- image (numpy.ndarray): Input image to be blurred.
- sigma (float): Standard deviation for Gaussian kernel. Must be greater than 0.0.
Returns
- numpy.ndarray: Blurred image, with the same shape and dtype as the input image.
303def median_blur(image, kernel_size): 304 """ 305 Apply a median filter to an image. 306 307 Parameters 308 ---------- 309 image : numpy.ndarray 310 Input image to be filtered. 311 kernel_size : int 312 Size of the median filter kernel. Must be a positive odd integer. 313 314 Returns 315 ------- 316 numpy.ndarray 317 Filtered image, with the same shape and dtype as the input image. 318 """ 319 return cv2.medianBlur(src=image, ksize=kernel_size)
Apply a median filter to an image.
Parameters
- image (numpy.ndarray): Input image to be filtered.
- kernel_size (int): Size of the median filter kernel. Must be a positive odd integer.
Returns
- numpy.ndarray: Filtered image, with the same shape and dtype as the input image.
322def clahe(image, clip_limit=None, tile_grid_size=None): 323 """ 324 Apply Contrast Limited Adaptive Histogram Equalization (CLAHE) to an image. 325 326 Parameters 327 ---------- 328 image : numpy.ndarray 329 Input image to be equalized. 330 clip_limit : float, optional 331 Threshold for contrast limiting. If `None`, no clipping is performed. 332 Default is `None`. 333 tile_grid_size : tuple(int, int), optional 334 Number of rows and columns into which the image will be divided. 335 Default is `(8, 8)` (as for OpenCV). 336 337 Returns 338 ------- 339 numpy.ndarray 340 Equalized image, with the same shape and dtype as the input image. 341 """ 342 clahe_op = cv2.createCLAHE(clipLimit=clip_limit, tileGridSize=tile_grid_size) 343 return clahe_op.apply(image)
Apply Contrast Limited Adaptive Histogram Equalization (CLAHE) to an image.
Parameters
- image (numpy.ndarray): Input image to be equalized.
- clip_limit (float, optional):
Threshold for contrast limiting. If
None, no clipping is performed. Default isNone. - tile_grid_size (tuple(int, int), optional):
Number of rows and columns into which the image will be divided.
Default is
(8, 8)(as for OpenCV).
Returns
- numpy.ndarray: Equalized image, with the same shape and dtype as the input image.
351def otsu(image): 352 """ 353 Perform Otsu thresholding on a grayscale image. 354 355 This function computes the optimal threshold for the input grayscale image using the Otsu method, 356 and returns the thresholded image and the computed threshold. 357 358 Parameters 359 ---------- 360 image : numpy.ndarray 361 Input grayscale image to be thresholded. 362 363 Returns 364 ------- 365 tuple 366 A tuple containing the threshold value and the thresholded image, both as numpy.ndarray 367 with the same shape and dtype as the input image. 368 369 Raises 370 ------ 371 ValueError 372 If the input image is not grayscale. 373 """ 374 if dito.core.is_color(image=image): 375 raise ValueError("Expected gray image but got color image for Otsu thresholding") 376 (theta, image2) = cv2.threshold(src=image, thresh=-1, maxval=255, type=cv2.THRESH_BINARY | cv2.THRESH_OTSU) 377 return (theta, image2)
Perform Otsu thresholding on a grayscale image.
This function computes the optimal threshold for the input grayscale image using the Otsu method, and returns the thresholded image and the computed threshold.
Parameters
- image (numpy.ndarray): Input grayscale image to be thresholded.
Returns
- tuple: A tuple containing the threshold value and the thresholded image, both as numpy.ndarray with the same shape and dtype as the input image.
Raises
- ValueError: If the input image is not grayscale.
380def otsu_theta(image): 381 """ 382 Compute the Otsu threshold for a grayscale image. 383 384 This function computes the optimal threshold for the input grayscale image using the Otsu method, 385 and returns only the threshold value. 386 387 Parameters 388 ---------- 389 image : numpy.ndarray 390 Input grayscale image. 391 392 Returns 393 ------- 394 float 395 The computed threshold value. 396 """ 397 (theta, image2) = otsu(image=image) 398 return theta
Compute the Otsu threshold for a grayscale image.
This function computes the optimal threshold for the input grayscale image using the Otsu method, and returns only the threshold value.
Parameters
- image (numpy.ndarray): Input grayscale image.
Returns
- float: The computed threshold value.
401def otsu_image(image): 402 """ 403 Threshold a grayscale image using the Otsu method. 404 405 This function computes the optimal threshold for the input grayscale image using the Otsu method, 406 and returns the thresholded image. 407 408 Parameters 409 ---------- 410 image : numpy.ndarray 411 Input grayscale image to be thresholded. 412 413 Returns 414 ------- 415 numpy.ndarray 416 The thresholded image, as a numpy.ndarray with the same shape and dtype as the input image. 417 """ 418 (theta, image2) = otsu(image=image) 419 return image2
Threshold a grayscale image using the Otsu method.
This function computes the optimal threshold for the input grayscale image using the Otsu method, and returns the thresholded image.
Parameters
- image (numpy.ndarray): Input grayscale image to be thresholded.
Returns
- numpy.ndarray: The thresholded image, as a numpy.ndarray with the same shape and dtype as the input image.
427def morpho_op_kernel(shape, size): 428 """ 429 Create a morphological operation kernel. 430 431 Parameters 432 ---------- 433 shape : int 434 Type of the kernel. The following values are supported: 435 - `cv2.MORPH_RECT`: rectangular kernel 436 - `cv2.MORPH_CROSS`: cross-shaped kernel 437 - `cv2.MORPH_ELLIPSE`: elliptical kernel 438 size : int or tuple of int 439 Size (width, height) of the kernel. If a single integer is provided, assume equal width and height. 440 441 Returns 442 ------- 443 numpy.ndarray 444 The morphological operation kernel. 445 """ 446 ksize = dito.utils.get_validated_tuple(x=size, type_=int, count=2) 447 kernel = cv2.getStructuringElement(shape=shape, ksize=ksize, anchor=(-1, -1)) 448 return kernel
Create a morphological operation kernel.
Parameters
- shape (int):
Type of the kernel. The following values are supported:
cv2.MORPH_RECT: rectangular kernelcv2.MORPH_CROSS: cross-shaped kernelcv2.MORPH_ELLIPSE: elliptical kernel
- size (int or tuple of int): Size (width, height) of the kernel. If a single integer is provided, assume equal width and height.
Returns
- numpy.ndarray: The morphological operation kernel.
451def morpho_op(image, operation, shape=cv2.MORPH_ELLIPSE, size=3, anchor=(-1, -1), iterations=1): 452 """ 453 Apply a morphological operation to an image. 454 455 Parameters 456 ---------- 457 image : numpy.ndarray 458 Input image to which the morphological operation is applied. 459 operation : int 460 Type of morphological operation. The following values are supported: 461 - `cv2.MORPH_ERODE`: erosion 462 - `cv2.MORPH_DILATE`: dilation 463 - `cv2.MORPH_OPEN`: opening 464 - `cv2.MORPH_CLOSE`: closing 465 - `cv2.MORPH_GRADIENT`: gradient 466 - `cv2.MORPH_TOPHAT`: top hat 467 - `cv2.MORPH_BLACKHAT`: black hat 468 shape : int 469 Type of the kernel. The following values are supported: 470 - `cv2.MORPH_RECT`: rectangular kernel 471 - `cv2.MORPH_CROSS`: cross-shaped kernel 472 - `cv2.MORPH_ELLIPSE`: elliptical kernel 473 The default value is `cv2.MORPH_ELLIPSE`. 474 size : int or tuple of int 475 Size (width, height) of the kernel. If a single integer is provided, assume equal width and height. 476 The default value is `3`. 477 anchor : tuple of int, optional 478 Anchor point of the structuring element used for the morphological operation. The anchor should lie within 479 the kernel. The default value is `(-1, -1)`, which means that the anchor is at the center of the kernel. 480 iterations : int, optional 481 Number of times the morphological operation is applied. The default value is `1`. 482 483 Returns 484 ------- 485 numpy.ndarray 486 Resulting image after applying the morphological operation, with the same shape and dtype as the input image. 487 """ 488 kernel = morpho_op_kernel(shape=shape, size=size) 489 return cv2.morphologyEx(src=image, op=operation, kernel=kernel, anchor=anchor, iterations=iterations)
Apply a morphological operation to an image.
Parameters
- image (numpy.ndarray): Input image to which the morphological operation is applied.
- operation (int):
Type of morphological operation. The following values are supported:
cv2.MORPH_ERODE: erosioncv2.MORPH_DILATE: dilationcv2.MORPH_OPEN: openingcv2.MORPH_CLOSE: closingcv2.MORPH_GRADIENT: gradientcv2.MORPH_TOPHAT: top hatcv2.MORPH_BLACKHAT: black hat
- shape (int):
Type of the kernel. The following values are supported:
cv2.MORPH_RECT: rectangular kernelcv2.MORPH_CROSS: cross-shaped kernelcv2.MORPH_ELLIPSE: elliptical kernel The default value iscv2.MORPH_ELLIPSE.
- size (int or tuple of int):
Size (width, height) of the kernel. If a single integer is provided, assume equal width and height.
The default value is
3. - anchor (tuple of int, optional):
Anchor point of the structuring element used for the morphological operation. The anchor should lie within
the kernel. The default value is
(-1, -1), which means that the anchor is at the center of the kernel. - iterations (int, optional):
Number of times the morphological operation is applied. The default value is
1.
Returns
- numpy.ndarray: Resulting image after applying the morphological operation, with the same shape and dtype as the input image.
492def dilate(image, **kwargs): 493 """ 494 Apply morphological dilation to an image. 495 496 Parameters 497 ---------- 498 image : numpy.ndarray 499 Input image to be dilated. 500 **kwargs 501 Optional arguments to be passed to `morpho_op`. 502 503 Returns 504 ------- 505 numpy.ndarray 506 Dilated image, with the same shape and dtype as the input image. 507 """ 508 return morpho_op(image=image, operation=cv2.MORPH_DILATE, **kwargs)
Apply morphological dilation to an image.
Parameters
- image (numpy.ndarray): Input image to be dilated.
- **kwargs: Optional arguments to be passed to
morpho_op.
Returns
- numpy.ndarray: Dilated image, with the same shape and dtype as the input image.
511def erode(image, **kwargs): 512 """ 513 Apply morphological erosion to an image. 514 515 Parameters 516 ---------- 517 image : numpy.ndarray 518 Input image to be eroded. 519 **kwargs 520 Optional arguments to be passed to `morpho_op`. 521 522 Returns 523 ------- 524 numpy.ndarray 525 Eroded image, with the same shape and dtype as the input image. 526 """ 527 return morpho_op(image=image, operation=cv2.MORPH_ERODE, **kwargs)
Apply morphological erosion to an image.
Parameters
- image (numpy.ndarray): Input image to be eroded.
- **kwargs: Optional arguments to be passed to
morpho_op.
Returns
- numpy.ndarray: Eroded image, with the same shape and dtype as the input image.
530def morpho_open(image, **kwargs): 531 """ 532 Apply morphological opening to an image. 533 534 Parameters 535 ---------- 536 image : numpy.ndarray 537 Input image to be opened. 538 **kwargs 539 Optional arguments to be passed to `morpho_op`. 540 541 Returns 542 ------- 543 numpy.ndarray 544 Image after the opening operation, with the same shape and dtype as the input image. 545 """ 546 return morpho_op(image=image, operation=cv2.MORPH_OPEN, **kwargs)
Apply morphological opening to an image.
Parameters
- image (numpy.ndarray): Input image to be opened.
- **kwargs: Optional arguments to be passed to
morpho_op.
Returns
- numpy.ndarray: Image after the opening operation, with the same shape and dtype as the input image.
549def morpho_close(image, **kwargs): 550 """ 551 Apply morphological closing to an image. 552 553 Parameters 554 ---------- 555 image : numpy.ndarray 556 Input image to be closed. 557 **kwargs 558 Optional arguments to be passed to `morpho_op`. 559 560 Returns 561 ------- 562 numpy.ndarray 563 Image after the closing operation, with the same shape and dtype as the input image. 564 """ 565 return morpho_op(image=image, operation=cv2.MORPH_CLOSE, **kwargs)
Apply morphological closing to an image.
Parameters
- image (numpy.ndarray): Input image to be closed.
- **kwargs: Optional arguments to be passed to
morpho_op.
Returns
- numpy.ndarray: Image after the closing operation, with the same shape and dtype as the input image.
568def blackhat(image, **kwargs): 569 """ 570 Apply the morphological blackhat operation to an image. 571 572 Parameters 573 ---------- 574 image : numpy.ndarray 575 Input image. 576 **kwargs 577 Optional arguments to be passed to `morpho_op`. 578 579 Returns 580 ------- 581 numpy.ndarray 582 Image after the blackhat operation, with the same shape and dtype as the input image. 583 """ 584 return morpho_op(image=image, operation=cv2.MORPH_BLACKHAT, **kwargs)
Apply the morphological blackhat operation to an image.
Parameters
- image (numpy.ndarray): Input image.
- **kwargs: Optional arguments to be passed to
morpho_op.
Returns
- numpy.ndarray: Image after the blackhat operation, with the same shape and dtype as the input image.
587def tophat(image, **kwargs): 588 """ 589 Apply the morphological tophat operation to an image. 590 591 Parameters 592 ---------- 593 image : numpy.ndarray 594 Input image. 595 **kwargs 596 Optional arguments to be passed to `morpho_op`. 597 598 Returns 599 ------- 600 numpy.ndarray 601 Image after the tophat operation, with the same shape and dtype as the input image. 602 """ 603 return morpho_op(image=image, operation=cv2.MORPH_TOPHAT, **kwargs)
Apply the morphological tophat operation to an image.
Parameters
- image (numpy.ndarray): Input image.
- **kwargs: Optional arguments to be passed to
morpho_op.
Returns
- numpy.ndarray: Image after the tophat operation, with the same shape and dtype as the input image.
611def dog(image, sigma1, sigma2, return_raw=False, colormap=None): 612 """ 613 Apply the difference of Gaussians (DoG) operation to an image. 614 615 Parameters 616 ---------- 617 image : numpy.ndarray 618 Input image to which the DoG is applied. 619 sigma1 : float 620 Standard deviation of the first Gaussian filter. 621 sigma2 : float 622 Standard deviation of the second Gaussian filter. 623 return_raw : bool, optional 624 If True, return the raw difference image. Otherwise, the difference image is shifted and scaled to match the 625 original dtype range. The default value is False. 626 colormap : str, optional 627 Name of the colormap to use for colorizing the result. If None, no colorization is applied. The default value 628 is None. 629 630 Returns 631 ------- 632 numpy.ndarray 633 Resulting image after applying the DoG, with the same shape and dtype as the input image. 634 """ 635 blur1 = gaussian_blur(image=image, sigma=sigma1).astype(np.float32) 636 blur2 = gaussian_blur(image=image, sigma=sigma2).astype(np.float32) 637 diff = blur1 - blur2 638 if return_raw: 639 return diff 640 else: 641 diff_11 = diff / dito.core.dtype_range(dtype=image.dtype)[1] 642 diff_01 = (diff_11 + 1.0) * 0.5 643 result = dito.convert(image=diff_01, dtype=image.dtype) 644 if colormap is not None: 645 result = dito.visual.colorize(image=result, colormap=colormap) 646 return result
Apply the difference of Gaussians (DoG) operation to an image.
Parameters
- image (numpy.ndarray): Input image to which the DoG is applied.
- sigma1 (float): Standard deviation of the first Gaussian filter.
- sigma2 (float): Standard deviation of the second Gaussian filter.
- return_raw (bool, optional): If True, return the raw difference image. Otherwise, the difference image is shifted and scaled to match the original dtype range. The default value is False.
- colormap (str, optional): Name of the colormap to use for colorizing the result. If None, no colorization is applied. The default value is None.
Returns
- numpy.ndarray: Resulting image after applying the DoG, with the same shape and dtype as the input image.
649def dog_interactive(image, colormap=None): 650 """ 651 Display an interactive window for exploring the difference of Gaussians (DoG) of an image. 652 653 The window displays the input image, two sliders for adjusting the standard deviations of the Gaussian filters used 654 in the DoG, and a visualization of the DoG result. 655 656 Parameters 657 ---------- 658 image : numpy.ndarray 659 Input image to which the DoG is applied. 660 colormap : str, optional 661 Name of the colormap to use for colorizing the result. If None, no colorization is applied. The default value 662 is None. 663 664 Returns 665 ------- 666 None 667 """ 668 window_name = "dito.dog_interactive" 669 sliders = [dito.highgui.FloatSlider(window_name=window_name, name="sigma{}".format(n_slider + 1), min_value=0.0, max_value=15.0, value_count=1001) for n_slider in range(2)] 670 sliders[0].set_value(0.5) 671 sliders[1].set_value(0.8) 672 673 image_show = None 674 while True: 675 if (image_show is None) or any(slider.changed for slider in sliders): 676 sigmas = [sliders[n_slider].get_value() for n_slider in range(2)] 677 images_blur = [gaussian_blur(image=image, sigma=sigmas[n_slider]) for n_slider in range(2)] 678 images_blur = [dito.visual.text(image=image_blur, message="sigma{} = {:.2f}".format(n_slider + 1, sigmas[n_slider])) for (n_slider, image_blur) in enumerate(images_blur)] 679 image_dog = dog(image, sigma1=sigmas[0], sigma2=sigmas[1], return_raw=False, colormap=colormap) 680 image_show = dito.stack([[image, image_dog], images_blur]) 681 key = dito.show(image=image_show, window_name=window_name, wait=10) 682 if key in dito.qkeys(): 683 return
Display an interactive window for exploring the difference of Gaussians (DoG) of an image.
The window displays the input image, two sliders for adjusting the standard deviations of the Gaussian filters used in the DoG, and a visualization of the DoG result.
Parameters
- image (numpy.ndarray): Input image to which the DoG is applied.
- colormap (str, optional): Name of the colormap to use for colorizing the result. If None, no colorization is applied. The default value is None.
Returns
- None
691class Contour(): 692 """ 693 A class to represent a contour. 694 695 Attributes 696 ---------- 697 points : numpy.ndarray 698 The points that make up the contour. A 2D numpy array of shape `(n, 2)`, where `n` is the number of points 699 in the contour. Each row contains the `(x, y)` coordinates of a point. 700 """ 701 702 def __init__(self, points): 703 """ 704 Parameters 705 ---------- 706 points : numpy.ndarray 707 The points defining the contour. A 2D numpy array of shape `(n, 2)`, where `n` is the number of points 708 in the contour. Each row contains the `(x, y)` coordinates of a point. 709 """ 710 self.points = points 711 712 def __len__(self): 713 """ 714 Return the number of points in the contour. 715 716 Returns 717 ------- 718 int 719 The number of points in the contour. 720 """ 721 return len(self.points) 722 723 def __eq__(self, other): 724 """ 725 Check if two contours are equal. 726 727 Parameters 728 ---------- 729 other : Contour 730 Another instance of the `Contour` class. 731 732 Returns 733 ------- 734 bool 735 True if the two contours are equal, False otherwise. 736 """ 737 if not isinstance(other, Contour): 738 raise TypeError("Argument 'other' must be a contour") 739 740 if len(self) != len(other): 741 return False 742 743 return np.array_equal(self.points, other.points) 744 745 def copy(self): 746 """ 747 Return a copy of the current instance of the `Contour` class. 748 749 Returns 750 ------- 751 Contour 752 A copy of the current instance of the `Contour` class. 753 """ 754 return Contour(points=self.points.copy()) 755 756 def get_center(self): 757 """ 758 Return the center point `(x, y)` of the contour. 759 760 Returns 761 ------- 762 numpy.ndarray 763 A 2D numpy array representing the `(x, y)` coordinates of the center point of the contour. 764 """ 765 return np.mean(self.points, axis=0) 766 767 def get_center_x(self): 768 """ 769 Return the x-coordinate of the center point of the contour. 770 771 Returns 772 ------- 773 float 774 The x-coordinate of the center point of the contour. 775 """ 776 return np.mean(self.points[:, 0]) 777 778 def get_center_y(self): 779 """ 780 Return the y-coordinate of the center point of the contour. 781 782 Returns 783 ------- 784 float 785 The y-coordinate of the center point of the contour. 786 """ 787 return np.mean(self.points[:, 1]) 788 789 def get_min_x(self): 790 """ 791 Return the minimum x-coordinate of the contour. 792 793 Returns 794 ------- 795 float 796 The minimum x-coordinate of the contour. 797 """ 798 return np.min(self.points[:, 0]) 799 800 def get_max_x(self): 801 """ 802 Return the maximum x-coordinate of the contour. 803 804 Returns 805 ------- 806 float 807 The maximum x-coordinate of the contour. 808 """ 809 return np.max(self.points[:, 0]) 810 811 def get_width(self): 812 """ 813 Return the width of the contour. 814 815 Returns 816 ------- 817 float 818 The width of the contour. 819 """ 820 return self.get_max_x() - self.get_min_x() 821 822 def get_min_y(self): 823 """ 824 Return the minimum y-coordinate of the contour. 825 826 Returns 827 ------- 828 float 829 The minimum y-coordinate of the contour. 830 """ 831 return np.min(self.points[:, 1]) 832 833 def get_max_y(self): 834 """ 835 Return the maximum y-coordinate of the contour. 836 837 Returns 838 ------- 839 float 840 The maximum y-coordinate of the contour. 841 """ 842 return np.max(self.points[:, 1]) 843 844 def get_height(self): 845 """ 846 Return the height of the contour. 847 848 Returns 849 ------- 850 float 851 The height of the contour. 852 """ 853 return self.get_max_y() - self.get_min_y() 854 855 def get_area(self, mode="draw"): 856 """ 857 Compute the area of the contour. 858 859 Parameters 860 ---------- 861 mode : {"draw", "calc"}, optional 862 The method to use for computing the area. If "draw", the area is computed by drawing the contour as a filled 863 white shape on a black image and counting the number of white pixels. If "calc", the area is computed using the 864 cv2.contourArea() function. Default is "draw". 865 866 Returns 867 ------- 868 float 869 The area of the contour. 870 """ 871 if mode == "draw": 872 image = self.draw_standalone(color=(1,), thickness=1, filled=True, antialias=False, border=2) 873 return np.sum(image) 874 875 elif mode == "calc": 876 return cv2.contourArea(contour=self.points) 877 878 else: 879 raise ValueError("Invalid value for argument 'mode': '{}'".format(mode)) 880 881 def get_perimeter(self): 882 """ 883 Compute the perimeter of the contour. 884 885 Returns 886 ------- 887 float 888 The perimeter of the contour. 889 """ 890 return cv2.arcLength(curve=self.points, closed=True) 891 892 def get_circularity(self): 893 """ 894 Compute the circularity of the contour. 895 896 Returns 897 ------- 898 float 899 The circularity of the contour. 900 """ 901 r_area = np.sqrt(self.get_area() / np.pi) 902 r_perimeter = self.get_perimeter() / (2.0 * np.pi) 903 return r_area / r_perimeter 904 905 def get_ellipse(self): 906 """ 907 Fit an ellipse to the contour. 908 909 Returns 910 ------- 911 tuple 912 A tuple `(center, size, angle)`, where `center` is a tuple `(x, y)` representing the center point 913 of the ellipse, `size` is a tuple `(width, height)` representing the size of the ellipse, and `angle` 914 is the angle (in degrees) between the major axis of the ellipse and the x-axis. 915 """ 916 return cv2.fitEllipse(points=self.points) 917 918 def get_eccentricity(self): 919 """ 920 Calculate the eccentricity of the contour. 921 922 Returns 923 ------- 924 float 925 The eccentricity of the contour. 926 """ 927 ellipse = self.get_ellipse() 928 (width, height) = ellipse[1] 929 semi_major_axis = max(width, height) * 0.5 930 semi_minor_axis = min(width, height) * 0.5 931 eccentricity = math.sqrt(1.0 - (semi_minor_axis / semi_major_axis)**2) 932 return eccentricity 933 934 def get_moments(self): 935 """ 936 Calculate the moments of the contour. 937 938 The moment values can be used to calculate various properties of the contour, such as its centroid, 939 orientation, and size. 940 941 Returns 942 ------- 943 dict 944 A dictionary of moment values for the contour. 945 """ 946 return cv2.moments(array=self.points, binaryImage=False) 947 948 def get_hu_moments(self, log=True): 949 """ 950 Calculate the seven Hu moments for the contour. 951 952 These moments are invariant shape descriptors that can be used to capture shape properties of a contour, and 953 the first six of them are invariant to translation, scale, and rotation. However, the seventh moment is not 954 invariant to reflection and changes its sign under reflection. 955 956 Parameters 957 ---------- 958 log : bool, optional 959 If True (default), the logarithm of the absolute value of the Hu moments will be returned. If False, the raw 960 Hu moments will be returned. 961 962 Returns 963 ------- 964 numpy.ndarray 965 A 1D numpy array containing the seven Hu moments for the contour. If `log=True`, the values will be the 966 logarithm of the absolute value of the Hu moments. 967 """ 968 hu_moments = cv2.HuMoments(m=self.get_moments()) 969 if log: 970 return np.sign(hu_moments) * np.log10(np.abs(hu_moments)) 971 else: 972 return hu_moments 973 974 def shift(self, offset_x=None, offset_y=None): 975 """ 976 Shift the contour by a given offset along the x and/or y axis. 977 978 Parameters 979 ---------- 980 offset_x : float, optional 981 The amount by which to shift the contour along the x-axis. If not specified, the contour is not shifted along the x-axis. 982 offset_y : float, optional 983 The amount by which to shift the contour along the y-axis. If not specified, the contour is not shifted along the y-axis. 984 985 Returns 986 ------- 987 None 988 """ 989 if offset_x is not None: 990 self.points[:, 0] += offset_x 991 if offset_y is not None: 992 self.points[:, 1] += offset_y 993 994 def draw(self, image, color, thickness=1, filled=True, antialias=False, offset=None): 995 """ 996 Draw the contour into an existing image. 997 998 The image is changed in-place. 999 1000 Parameters 1001 ---------- 1002 image : numpy.ndarray 1003 The image into which the contour will be drawn. 1004 color : tuple 1005 The color of the contour. Its length must equal the channel count of the image. 1006 thickness : int, optional 1007 The thickness of the contour lines. Has no effect if `filled` is True. Default is 1. 1008 filled : bool, optional 1009 Whether the contour should be filled. If True, the interior of the contour is filled with the `color` value. Default is True. 1010 antialias : bool, optional 1011 Whether antialiasing should be applied when drawing the contour. Default is False. 1012 offset : tuple or list or numpy.ndarray, optional 1013 The `(x, y)` coordinates of the offset of the contour from the origin of the image. Default is None, which corresponds to no offset. 1014 1015 Returns 1016 ------- 1017 None 1018 """ 1019 cv2.drawContours(image=image, contours=[np.round(self.points).astype(np.int32)], contourIdx=0, color=color, thickness=cv2.FILLED if filled else thickness, lineType=cv2.LINE_AA if antialias else cv2.LINE_8, offset=offset) 1020 1021 def draw_standalone(self, color, thickness=1, filled=True, antialias=False, border=0): 1022 """ 1023 Draw the contour as a standalone image. 1024 1025 The image has the same size as the contour (which thus is centered in the image), 1026 but an additional border can be specified. 1027 1028 Parameters 1029 ---------- 1030 color : tuple or list or numpy.ndarray 1031 The color of the contour. Currently, only grayscale mode is supported. 1032 thickness : int, optional 1033 The thickness of the contour lines. Has no effect if `filled` is True. Default is 1. 1034 filled : bool, optional 1035 Whether the contour should be filled. If True, the interior of the contour is filled with the `color` value. Default is True. 1036 antialias : bool, optional 1037 Whether antialiasing should be applied when drawing the contour. Default is False. 1038 border : int, optional 1039 The size of the border around the contour. Default is 0. 1040 1041 Returns 1042 ------- 1043 numpy.ndarray 1044 A 2D numpy array representing the image of the contour. 1045 """ 1046 image = np.zeros(shape=(2 * border + self.get_height(), 2 * border + self.get_width()), dtype=np.uint8) 1047 self.draw(image=image, color=color, thickness=thickness, filled=filled, antialias=antialias, offset=(border - self.get_min_x(), border - self.get_min_y())) 1048 return image
A class to represent a contour.
Attributes
- points (numpy.ndarray):
The points that make up the contour. A 2D numpy array of shape
(n, 2), wherenis the number of points in the contour. Each row contains the(x, y)coordinates of a point.
702 def __init__(self, points): 703 """ 704 Parameters 705 ---------- 706 points : numpy.ndarray 707 The points defining the contour. A 2D numpy array of shape `(n, 2)`, where `n` is the number of points 708 in the contour. Each row contains the `(x, y)` coordinates of a point. 709 """ 710 self.points = points
Parameters
- points (numpy.ndarray):
The points defining the contour. A 2D numpy array of shape
(n, 2), wherenis the number of points in the contour. Each row contains the(x, y)coordinates of a point.
756 def get_center(self): 757 """ 758 Return the center point `(x, y)` of the contour. 759 760 Returns 761 ------- 762 numpy.ndarray 763 A 2D numpy array representing the `(x, y)` coordinates of the center point of the contour. 764 """ 765 return np.mean(self.points, axis=0)
Return the center point (x, y) of the contour.
Returns
- numpy.ndarray: A 2D numpy array representing the
(x, y)coordinates of the center point of the contour.
767 def get_center_x(self): 768 """ 769 Return the x-coordinate of the center point of the contour. 770 771 Returns 772 ------- 773 float 774 The x-coordinate of the center point of the contour. 775 """ 776 return np.mean(self.points[:, 0])
Return the x-coordinate of the center point of the contour.
Returns
- float: The x-coordinate of the center point of the contour.
778 def get_center_y(self): 779 """ 780 Return the y-coordinate of the center point of the contour. 781 782 Returns 783 ------- 784 float 785 The y-coordinate of the center point of the contour. 786 """ 787 return np.mean(self.points[:, 1])
Return the y-coordinate of the center point of the contour.
Returns
- float: The y-coordinate of the center point of the contour.
789 def get_min_x(self): 790 """ 791 Return the minimum x-coordinate of the contour. 792 793 Returns 794 ------- 795 float 796 The minimum x-coordinate of the contour. 797 """ 798 return np.min(self.points[:, 0])
Return the minimum x-coordinate of the contour.
Returns
- float: The minimum x-coordinate of the contour.
800 def get_max_x(self): 801 """ 802 Return the maximum x-coordinate of the contour. 803 804 Returns 805 ------- 806 float 807 The maximum x-coordinate of the contour. 808 """ 809 return np.max(self.points[:, 0])
Return the maximum x-coordinate of the contour.
Returns
- float: The maximum x-coordinate of the contour.
811 def get_width(self): 812 """ 813 Return the width of the contour. 814 815 Returns 816 ------- 817 float 818 The width of the contour. 819 """ 820 return self.get_max_x() - self.get_min_x()
Return the width of the contour.
Returns
- float: The width of the contour.
822 def get_min_y(self): 823 """ 824 Return the minimum y-coordinate of the contour. 825 826 Returns 827 ------- 828 float 829 The minimum y-coordinate of the contour. 830 """ 831 return np.min(self.points[:, 1])
Return the minimum y-coordinate of the contour.
Returns
- float: The minimum y-coordinate of the contour.
833 def get_max_y(self): 834 """ 835 Return the maximum y-coordinate of the contour. 836 837 Returns 838 ------- 839 float 840 The maximum y-coordinate of the contour. 841 """ 842 return np.max(self.points[:, 1])
Return the maximum y-coordinate of the contour.
Returns
- float: The maximum y-coordinate of the contour.
844 def get_height(self): 845 """ 846 Return the height of the contour. 847 848 Returns 849 ------- 850 float 851 The height of the contour. 852 """ 853 return self.get_max_y() - self.get_min_y()
Return the height of the contour.
Returns
- float: The height of the contour.
855 def get_area(self, mode="draw"): 856 """ 857 Compute the area of the contour. 858 859 Parameters 860 ---------- 861 mode : {"draw", "calc"}, optional 862 The method to use for computing the area. If "draw", the area is computed by drawing the contour as a filled 863 white shape on a black image and counting the number of white pixels. If "calc", the area is computed using the 864 cv2.contourArea() function. Default is "draw". 865 866 Returns 867 ------- 868 float 869 The area of the contour. 870 """ 871 if mode == "draw": 872 image = self.draw_standalone(color=(1,), thickness=1, filled=True, antialias=False, border=2) 873 return np.sum(image) 874 875 elif mode == "calc": 876 return cv2.contourArea(contour=self.points) 877 878 else: 879 raise ValueError("Invalid value for argument 'mode': '{}'".format(mode))
Compute the area of the contour.
Parameters
- mode ({"draw", "calc"}, optional): The method to use for computing the area. If "draw", the area is computed by drawing the contour as a filled white shape on a black image and counting the number of white pixels. If "calc", the area is computed using the cv2.contourArea() function. Default is "draw".
Returns
- float: The area of the contour.
881 def get_perimeter(self): 882 """ 883 Compute the perimeter of the contour. 884 885 Returns 886 ------- 887 float 888 The perimeter of the contour. 889 """ 890 return cv2.arcLength(curve=self.points, closed=True)
Compute the perimeter of the contour.
Returns
- float: The perimeter of the contour.
892 def get_circularity(self): 893 """ 894 Compute the circularity of the contour. 895 896 Returns 897 ------- 898 float 899 The circularity of the contour. 900 """ 901 r_area = np.sqrt(self.get_area() / np.pi) 902 r_perimeter = self.get_perimeter() / (2.0 * np.pi) 903 return r_area / r_perimeter
Compute the circularity of the contour.
Returns
- float: The circularity of the contour.
905 def get_ellipse(self): 906 """ 907 Fit an ellipse to the contour. 908 909 Returns 910 ------- 911 tuple 912 A tuple `(center, size, angle)`, where `center` is a tuple `(x, y)` representing the center point 913 of the ellipse, `size` is a tuple `(width, height)` representing the size of the ellipse, and `angle` 914 is the angle (in degrees) between the major axis of the ellipse and the x-axis. 915 """ 916 return cv2.fitEllipse(points=self.points)
Fit an ellipse to the contour.
Returns
- tuple: A tuple
(center, size, angle), wherecenteris a tuple(x, y)representing the center point of the ellipse,sizeis a tuple(width, height)representing the size of the ellipse, andangleis the angle (in degrees) between the major axis of the ellipse and the x-axis.
918 def get_eccentricity(self): 919 """ 920 Calculate the eccentricity of the contour. 921 922 Returns 923 ------- 924 float 925 The eccentricity of the contour. 926 """ 927 ellipse = self.get_ellipse() 928 (width, height) = ellipse[1] 929 semi_major_axis = max(width, height) * 0.5 930 semi_minor_axis = min(width, height) * 0.5 931 eccentricity = math.sqrt(1.0 - (semi_minor_axis / semi_major_axis)**2) 932 return eccentricity
Calculate the eccentricity of the contour.
Returns
- float: The eccentricity of the contour.
934 def get_moments(self): 935 """ 936 Calculate the moments of the contour. 937 938 The moment values can be used to calculate various properties of the contour, such as its centroid, 939 orientation, and size. 940 941 Returns 942 ------- 943 dict 944 A dictionary of moment values for the contour. 945 """ 946 return cv2.moments(array=self.points, binaryImage=False)
Calculate the moments of the contour.
The moment values can be used to calculate various properties of the contour, such as its centroid, orientation, and size.
Returns
- dict: A dictionary of moment values for the contour.
948 def get_hu_moments(self, log=True): 949 """ 950 Calculate the seven Hu moments for the contour. 951 952 These moments are invariant shape descriptors that can be used to capture shape properties of a contour, and 953 the first six of them are invariant to translation, scale, and rotation. However, the seventh moment is not 954 invariant to reflection and changes its sign under reflection. 955 956 Parameters 957 ---------- 958 log : bool, optional 959 If True (default), the logarithm of the absolute value of the Hu moments will be returned. If False, the raw 960 Hu moments will be returned. 961 962 Returns 963 ------- 964 numpy.ndarray 965 A 1D numpy array containing the seven Hu moments for the contour. If `log=True`, the values will be the 966 logarithm of the absolute value of the Hu moments. 967 """ 968 hu_moments = cv2.HuMoments(m=self.get_moments()) 969 if log: 970 return np.sign(hu_moments) * np.log10(np.abs(hu_moments)) 971 else: 972 return hu_moments
Calculate the seven Hu moments for the contour.
These moments are invariant shape descriptors that can be used to capture shape properties of a contour, and the first six of them are invariant to translation, scale, and rotation. However, the seventh moment is not invariant to reflection and changes its sign under reflection.
Parameters
- log (bool, optional): If True (default), the logarithm of the absolute value of the Hu moments will be returned. If False, the raw Hu moments will be returned.
Returns
- numpy.ndarray: A 1D numpy array containing the seven Hu moments for the contour. If
log=True, the values will be the logarithm of the absolute value of the Hu moments.
974 def shift(self, offset_x=None, offset_y=None): 975 """ 976 Shift the contour by a given offset along the x and/or y axis. 977 978 Parameters 979 ---------- 980 offset_x : float, optional 981 The amount by which to shift the contour along the x-axis. If not specified, the contour is not shifted along the x-axis. 982 offset_y : float, optional 983 The amount by which to shift the contour along the y-axis. If not specified, the contour is not shifted along the y-axis. 984 985 Returns 986 ------- 987 None 988 """ 989 if offset_x is not None: 990 self.points[:, 0] += offset_x 991 if offset_y is not None: 992 self.points[:, 1] += offset_y
Shift the contour by a given offset along the x and/or y axis.
Parameters
- offset_x (float, optional): The amount by which to shift the contour along the x-axis. If not specified, the contour is not shifted along the x-axis.
- offset_y (float, optional): The amount by which to shift the contour along the y-axis. If not specified, the contour is not shifted along the y-axis.
Returns
- None
994 def draw(self, image, color, thickness=1, filled=True, antialias=False, offset=None): 995 """ 996 Draw the contour into an existing image. 997 998 The image is changed in-place. 999 1000 Parameters 1001 ---------- 1002 image : numpy.ndarray 1003 The image into which the contour will be drawn. 1004 color : tuple 1005 The color of the contour. Its length must equal the channel count of the image. 1006 thickness : int, optional 1007 The thickness of the contour lines. Has no effect if `filled` is True. Default is 1. 1008 filled : bool, optional 1009 Whether the contour should be filled. If True, the interior of the contour is filled with the `color` value. Default is True. 1010 antialias : bool, optional 1011 Whether antialiasing should be applied when drawing the contour. Default is False. 1012 offset : tuple or list or numpy.ndarray, optional 1013 The `(x, y)` coordinates of the offset of the contour from the origin of the image. Default is None, which corresponds to no offset. 1014 1015 Returns 1016 ------- 1017 None 1018 """ 1019 cv2.drawContours(image=image, contours=[np.round(self.points).astype(np.int32)], contourIdx=0, color=color, thickness=cv2.FILLED if filled else thickness, lineType=cv2.LINE_AA if antialias else cv2.LINE_8, offset=offset)
Draw the contour into an existing image.
The image is changed in-place.
Parameters
- image (numpy.ndarray): The image into which the contour will be drawn.
- color (tuple): The color of the contour. Its length must equal the channel count of the image.
- thickness (int, optional):
The thickness of the contour lines. Has no effect if
filledis True. Default is 1. - filled (bool, optional):
Whether the contour should be filled. If True, the interior of the contour is filled with the
colorvalue. Default is True. - antialias (bool, optional): Whether antialiasing should be applied when drawing the contour. Default is False.
- offset (tuple or list or numpy.ndarray, optional):
The
(x, y)coordinates of the offset of the contour from the origin of the image. Default is None, which corresponds to no offset.
Returns
- None
1021 def draw_standalone(self, color, thickness=1, filled=True, antialias=False, border=0): 1022 """ 1023 Draw the contour as a standalone image. 1024 1025 The image has the same size as the contour (which thus is centered in the image), 1026 but an additional border can be specified. 1027 1028 Parameters 1029 ---------- 1030 color : tuple or list or numpy.ndarray 1031 The color of the contour. Currently, only grayscale mode is supported. 1032 thickness : int, optional 1033 The thickness of the contour lines. Has no effect if `filled` is True. Default is 1. 1034 filled : bool, optional 1035 Whether the contour should be filled. If True, the interior of the contour is filled with the `color` value. Default is True. 1036 antialias : bool, optional 1037 Whether antialiasing should be applied when drawing the contour. Default is False. 1038 border : int, optional 1039 The size of the border around the contour. Default is 0. 1040 1041 Returns 1042 ------- 1043 numpy.ndarray 1044 A 2D numpy array representing the image of the contour. 1045 """ 1046 image = np.zeros(shape=(2 * border + self.get_height(), 2 * border + self.get_width()), dtype=np.uint8) 1047 self.draw(image=image, color=color, thickness=thickness, filled=filled, antialias=antialias, offset=(border - self.get_min_x(), border - self.get_min_y())) 1048 return image
Draw the contour as a standalone image.
The image has the same size as the contour (which thus is centered in the image), but an additional border can be specified.
Parameters
- color (tuple or list or numpy.ndarray): The color of the contour. Currently, only grayscale mode is supported.
- thickness (int, optional):
The thickness of the contour lines. Has no effect if
filledis True. Default is 1. - filled (bool, optional):
Whether the contour should be filled. If True, the interior of the contour is filled with the
colorvalue. Default is True. - antialias (bool, optional): Whether antialiasing should be applied when drawing the contour. Default is False.
- border (int, optional): The size of the border around the contour. Default is 0.
Returns
- numpy.ndarray: A 2D numpy array representing the image of the contour.
1051class ContourList(): 1052 """ 1053 A class representing a list of contours. 1054 1055 It allows for easy filtering of contours by various properties and offers 1056 additional helper functions such as drawing. 1057 1058 Attributes 1059 ---------- 1060 contours : list of Contour objects 1061 The list of contours stored in the ContourList object. 1062 """ 1063 1064 def __init__(self, contours_): 1065 """ 1066 Parameters 1067 ---------- 1068 contours_ : list of Contour objects 1069 The list of contours to be stored. 1070 """ 1071 self.contours = contours_ 1072 1073 def __len__(self): 1074 """ 1075 Return the number of contours. 1076 1077 Returns 1078 ------- 1079 int 1080 The number of contours stored in the ContourList object. 1081 """ 1082 return len(self.contours) 1083 1084 def __eq__(self, other): 1085 """ 1086 Check if two ContourList objects are equal. 1087 1088 Parameters 1089 ---------- 1090 other : object 1091 The object to compare to. 1092 1093 Returns 1094 ------- 1095 bool 1096 True if the two objects are equal, False otherwise. 1097 1098 Raises 1099 ------ 1100 TypeError 1101 If the argument `other` is not a ContourList object. 1102 """ 1103 if not isinstance(other, ContourList): 1104 raise TypeError("Argument 'other' must be a contour list") 1105 1106 if len(self) != len(other): 1107 return False 1108 1109 for (contour_self, contour_other) in zip(self.contours, other.contours): 1110 if contour_self != contour_other: 1111 return False 1112 1113 return True 1114 1115 def __getitem__(self, key): 1116 """ 1117 Return the contour object at the specified index. 1118 1119 Parameters 1120 ---------- 1121 key : int 1122 The index of the contour object to be returned. 1123 1124 Returns 1125 ------- 1126 object 1127 The contour object at the specified index. 1128 """ 1129 return self.contours[key] 1130 1131 def copy(self): 1132 """ 1133 Return a copy of the ContourList object. 1134 1135 Returns 1136 ------- 1137 object 1138 A copy of the ContourList object. 1139 """ 1140 contours_copy = [contour.copy() for contour in self.contours] 1141 return ContourList(contours_=contours_copy) 1142 1143 def filter(self, func, min_value=None, max_value=None): 1144 """ 1145 Filter the contour list based on a given function and range of values. 1146 1147 Only contours whose function values fall within the specified range are retained. 1148 The contour list is modified in place. 1149 1150 Parameters 1151 ---------- 1152 func : function 1153 The function used to extract a value from each contour. It must return a value which can be compared against 1154 `min_value` and/or `max_value`. 1155 min_value : float or None, optional 1156 The minimum value of the extracted value for a contour to be kept. Contours with extracted values lower than 1157 this will be removed. If None, no minimum filter is applied. Default is None. 1158 max_value : float or None, optional 1159 The maximum value of the extracted value for a contour to be kept. Contours with extracted values higher than 1160 this will be removed. If None, no maximum filter is applied. Default is None. 1161 1162 Returns 1163 ------- 1164 None 1165 """ 1166 if (min_value is None) and (max_value is None): 1167 # nothing to do 1168 return 1169 1170 # filter 1171 contours_filtered = [] 1172 for contour in self.contours: 1173 value = func(contour) 1174 if (min_value is not None) and (value < min_value): 1175 continue 1176 if (max_value is not None) and (value > max_value): 1177 continue 1178 contours_filtered.append(contour) 1179 self.contours = contours_filtered 1180 1181 def filter_center_x(self, min_value=None, max_value=None): 1182 """ 1183 Filter the list of contours by the x-coordinate of their centers. 1184 1185 Only contours whose center x-coordinates fall within the specified range are retained. 1186 The contour list is modified in place. 1187 1188 Parameters 1189 ---------- 1190 min_value : float or None, optional 1191 The minimum allowed value of the x-coordinate. If a contour's center x-coordinate is less than this, it is 1192 discarded. If None, no lower bound is applied. Default is None. 1193 max_value : float or None, optional 1194 The maximum allowed value of the x-coordinate. If a contour's center x-coordinate is greater than this, it is 1195 discarded. If None, no upper bound is applied. Default is None. 1196 1197 Returns 1198 ------- 1199 None 1200 """ 1201 self.filter(func=operator.methodcaller("get_center_x"), min_value=min_value, max_value=max_value) 1202 1203 def filter_center_y(self, min_value=None, max_value=None): 1204 """ 1205 Filter the list of contours by the y-coordinate of their centers. 1206 1207 Only contours whose center y-coordinates fall within the specified range are retained. 1208 The contour list is modified in place. 1209 1210 Parameters 1211 ---------- 1212 min_value : float or None, optional 1213 The minimum allowed value of the y-coordinate. If a contour's center y-coordinate is less than this, it is 1214 discarded. If None, no lower bound is applied. Default is None. 1215 max_value : float or None, optional 1216 The maximum allowed value of the y-coordinate. If a contour's center y-coordinate is greater than this, it is 1217 discarded. If None, no upper bound is applied. Default is None. 1218 1219 Returns 1220 ------- 1221 None 1222 """ 1223 self.filter(func=operator.methodcaller("get_center_y"), min_value=min_value, max_value=max_value) 1224 1225 def filter_area(self, min_value=None, max_value=None, mode="draw"): 1226 """ 1227 Filter the list of contours by their area. 1228 1229 Only contours whose areas fall within the specified range are retained. 1230 The contour list is modified in place. 1231 1232 Parameters 1233 ---------- 1234 min_value : float or None, optional 1235 The minimum allowed area. If a contour's area is less than this, it is discarded. If None, no lower bound 1236 is applied. Default is None. 1237 max_value : float or None, optional 1238 The maximum allowed area. If a contour's area is greater than this, it is discarded. If None, no upper bound 1239 is applied. Default is None. 1240 mode : str, optional 1241 The mode to use for computing the area. See `get_area` for details. Default is 'draw'. 1242 1243 Returns 1244 ------- 1245 None 1246 """ 1247 self.filter(func=operator.methodcaller("get_area", mode=mode), min_value=min_value, max_value=max_value) 1248 1249 def filter_perimeter(self, min_value=None, max_value=None): 1250 """ 1251 Filter the list of contours by their perimeters. 1252 1253 Only contours whose perimeters fall within the specified range are retained. 1254 The contour list is modified in place. 1255 1256 Parameters 1257 ---------- 1258 min_value : float or None, optional 1259 The minimum allowed perimeter. If a contour's perimeter is less than this, it is discarded. 1260 If None, no lower bound is applied. Default is None. 1261 max_value : float or None, optional 1262 The maximum allowed perimeter. If a contour's perimeter is greater than this, it is discarded. 1263 If None, no upper bound is applied. Default is None. 1264 1265 Returns 1266 ------- 1267 None 1268 """ 1269 self.filter(func=operator.methodcaller("get_perimeter"), min_value=min_value, max_value=max_value) 1270 1271 def filter_circularity(self, min_value=None, max_value=None): 1272 """ 1273 Filter the list of contours by their circularity. 1274 1275 Only contours whose circularities fall within the specified range are retained. 1276 The contour list is modified in place. 1277 1278 Parameters 1279 ---------- 1280 min_value : float or None, optional 1281 The minimum allowed circularity. If a contour's circularity is less than this, it is discarded. 1282 If None, no lower bound is applied. Default is None. 1283 max_value : float or None, optional 1284 The maximum allowed circularity. If a contour's circularity is greater than this, it is discarded. 1285 If None, no upper bound is applied. Default is None. 1286 1287 Returns 1288 ------- 1289 None 1290 """ 1291 self.filter(func=operator.methodcaller("get_circularity"), min_value=min_value, max_value=max_value) 1292 1293 def find_largest(self, return_index=True): 1294 """ 1295 Return the contour (or the contour index) with the largest area. 1296 1297 Parameters 1298 ---------- 1299 return_index : bool, optional 1300 If `True`, return the index of the largest contour. Otherwise, return the contour object itself. 1301 Default is `True`. 1302 1303 Returns 1304 ------- 1305 int or Contour or None 1306 The index of the largest contour if `return_index` is `True`, or the largest contour object if 1307 `return_index` is `False`. If no contour is found, returns None. 1308 """ 1309 max_area = None 1310 argmax_area = None 1311 for (n_contour, contour) in enumerate(self.contours): 1312 area = contour.get_area() 1313 if (max_area is None) or (area > max_area): 1314 max_area = area 1315 argmax_area = n_contour 1316 1317 if argmax_area is None: 1318 return None 1319 else: 1320 if return_index: 1321 return argmax_area 1322 else: 1323 return self.contours[argmax_area] 1324 1325 def draw_all(self, image, colors=None, **kwargs): 1326 """ 1327 Draw all the contours into an image using a different color for each contour. 1328 1329 Parameters 1330 ---------- 1331 image : numpy.ndarray 1332 The image into which the contours will be drawn. 1333 colors : list of tuples, optional 1334 The colors to use for each contour. The length of the list must be equal to the number of contours. 1335 If not provided, random colors will be generated. Default is None. 1336 **kwargs 1337 Additional keyword arguments to pass to the `Contour.draw()` method. 1338 1339 Returns 1340 ------- 1341 None 1342 """ 1343 if colors is None: 1344 colors = tuple(dito.random_color() for _ in range(len(self))) 1345 1346 for (contour, color) in zip(self.contours, colors): 1347 contour.draw(image=image, color=color, **kwargs)
A class representing a list of contours.
It allows for easy filtering of contours by various properties and offers additional helper functions such as drawing.
Attributes
- contours (list of Contour objects): The list of contours stored in the ContourList object.
1064 def __init__(self, contours_): 1065 """ 1066 Parameters 1067 ---------- 1068 contours_ : list of Contour objects 1069 The list of contours to be stored. 1070 """ 1071 self.contours = contours_
Parameters
- contours_ (list of Contour objects): The list of contours to be stored.
1131 def copy(self): 1132 """ 1133 Return a copy of the ContourList object. 1134 1135 Returns 1136 ------- 1137 object 1138 A copy of the ContourList object. 1139 """ 1140 contours_copy = [contour.copy() for contour in self.contours] 1141 return ContourList(contours_=contours_copy)
Return a copy of the ContourList object.
Returns
- object: A copy of the ContourList object.
1143 def filter(self, func, min_value=None, max_value=None): 1144 """ 1145 Filter the contour list based on a given function and range of values. 1146 1147 Only contours whose function values fall within the specified range are retained. 1148 The contour list is modified in place. 1149 1150 Parameters 1151 ---------- 1152 func : function 1153 The function used to extract a value from each contour. It must return a value which can be compared against 1154 `min_value` and/or `max_value`. 1155 min_value : float or None, optional 1156 The minimum value of the extracted value for a contour to be kept. Contours with extracted values lower than 1157 this will be removed. If None, no minimum filter is applied. Default is None. 1158 max_value : float or None, optional 1159 The maximum value of the extracted value for a contour to be kept. Contours with extracted values higher than 1160 this will be removed. If None, no maximum filter is applied. Default is None. 1161 1162 Returns 1163 ------- 1164 None 1165 """ 1166 if (min_value is None) and (max_value is None): 1167 # nothing to do 1168 return 1169 1170 # filter 1171 contours_filtered = [] 1172 for contour in self.contours: 1173 value = func(contour) 1174 if (min_value is not None) and (value < min_value): 1175 continue 1176 if (max_value is not None) and (value > max_value): 1177 continue 1178 contours_filtered.append(contour) 1179 self.contours = contours_filtered
Filter the contour list based on a given function and range of values.
Only contours whose function values fall within the specified range are retained. The contour list is modified in place.
Parameters
- func (function):
The function used to extract a value from each contour. It must return a value which can be compared against
min_valueand/ormax_value. - min_value (float or None, optional): The minimum value of the extracted value for a contour to be kept. Contours with extracted values lower than this will be removed. If None, no minimum filter is applied. Default is None.
- max_value (float or None, optional): The maximum value of the extracted value for a contour to be kept. Contours with extracted values higher than this will be removed. If None, no maximum filter is applied. Default is None.
Returns
- None
1181 def filter_center_x(self, min_value=None, max_value=None): 1182 """ 1183 Filter the list of contours by the x-coordinate of their centers. 1184 1185 Only contours whose center x-coordinates fall within the specified range are retained. 1186 The contour list is modified in place. 1187 1188 Parameters 1189 ---------- 1190 min_value : float or None, optional 1191 The minimum allowed value of the x-coordinate. If a contour's center x-coordinate is less than this, it is 1192 discarded. If None, no lower bound is applied. Default is None. 1193 max_value : float or None, optional 1194 The maximum allowed value of the x-coordinate. If a contour's center x-coordinate is greater than this, it is 1195 discarded. If None, no upper bound is applied. Default is None. 1196 1197 Returns 1198 ------- 1199 None 1200 """ 1201 self.filter(func=operator.methodcaller("get_center_x"), min_value=min_value, max_value=max_value)
Filter the list of contours by the x-coordinate of their centers.
Only contours whose center x-coordinates fall within the specified range are retained. The contour list is modified in place.
Parameters
- min_value (float or None, optional): The minimum allowed value of the x-coordinate. If a contour's center x-coordinate is less than this, it is discarded. If None, no lower bound is applied. Default is None.
- max_value (float or None, optional): The maximum allowed value of the x-coordinate. If a contour's center x-coordinate is greater than this, it is discarded. If None, no upper bound is applied. Default is None.
Returns
- None
1203 def filter_center_y(self, min_value=None, max_value=None): 1204 """ 1205 Filter the list of contours by the y-coordinate of their centers. 1206 1207 Only contours whose center y-coordinates fall within the specified range are retained. 1208 The contour list is modified in place. 1209 1210 Parameters 1211 ---------- 1212 min_value : float or None, optional 1213 The minimum allowed value of the y-coordinate. If a contour's center y-coordinate is less than this, it is 1214 discarded. If None, no lower bound is applied. Default is None. 1215 max_value : float or None, optional 1216 The maximum allowed value of the y-coordinate. If a contour's center y-coordinate is greater than this, it is 1217 discarded. If None, no upper bound is applied. Default is None. 1218 1219 Returns 1220 ------- 1221 None 1222 """ 1223 self.filter(func=operator.methodcaller("get_center_y"), min_value=min_value, max_value=max_value)
Filter the list of contours by the y-coordinate of their centers.
Only contours whose center y-coordinates fall within the specified range are retained. The contour list is modified in place.
Parameters
- min_value (float or None, optional): The minimum allowed value of the y-coordinate. If a contour's center y-coordinate is less than this, it is discarded. If None, no lower bound is applied. Default is None.
- max_value (float or None, optional): The maximum allowed value of the y-coordinate. If a contour's center y-coordinate is greater than this, it is discarded. If None, no upper bound is applied. Default is None.
Returns
- None
1225 def filter_area(self, min_value=None, max_value=None, mode="draw"): 1226 """ 1227 Filter the list of contours by their area. 1228 1229 Only contours whose areas fall within the specified range are retained. 1230 The contour list is modified in place. 1231 1232 Parameters 1233 ---------- 1234 min_value : float or None, optional 1235 The minimum allowed area. If a contour's area is less than this, it is discarded. If None, no lower bound 1236 is applied. Default is None. 1237 max_value : float or None, optional 1238 The maximum allowed area. If a contour's area is greater than this, it is discarded. If None, no upper bound 1239 is applied. Default is None. 1240 mode : str, optional 1241 The mode to use for computing the area. See `get_area` for details. Default is 'draw'. 1242 1243 Returns 1244 ------- 1245 None 1246 """ 1247 self.filter(func=operator.methodcaller("get_area", mode=mode), min_value=min_value, max_value=max_value)
Filter the list of contours by their area.
Only contours whose areas fall within the specified range are retained. The contour list is modified in place.
Parameters
- min_value (float or None, optional): The minimum allowed area. If a contour's area is less than this, it is discarded. If None, no lower bound is applied. Default is None.
- max_value (float or None, optional): The maximum allowed area. If a contour's area is greater than this, it is discarded. If None, no upper bound is applied. Default is None.
- mode (str, optional):
The mode to use for computing the area. See
get_areafor details. Default is 'draw'.
Returns
- None
1249 def filter_perimeter(self, min_value=None, max_value=None): 1250 """ 1251 Filter the list of contours by their perimeters. 1252 1253 Only contours whose perimeters fall within the specified range are retained. 1254 The contour list is modified in place. 1255 1256 Parameters 1257 ---------- 1258 min_value : float or None, optional 1259 The minimum allowed perimeter. If a contour's perimeter is less than this, it is discarded. 1260 If None, no lower bound is applied. Default is None. 1261 max_value : float or None, optional 1262 The maximum allowed perimeter. If a contour's perimeter is greater than this, it is discarded. 1263 If None, no upper bound is applied. Default is None. 1264 1265 Returns 1266 ------- 1267 None 1268 """ 1269 self.filter(func=operator.methodcaller("get_perimeter"), min_value=min_value, max_value=max_value)
Filter the list of contours by their perimeters.
Only contours whose perimeters fall within the specified range are retained. The contour list is modified in place.
Parameters
- min_value (float or None, optional): The minimum allowed perimeter. If a contour's perimeter is less than this, it is discarded. If None, no lower bound is applied. Default is None.
- max_value (float or None, optional): The maximum allowed perimeter. If a contour's perimeter is greater than this, it is discarded. If None, no upper bound is applied. Default is None.
Returns
- None
1271 def filter_circularity(self, min_value=None, max_value=None): 1272 """ 1273 Filter the list of contours by their circularity. 1274 1275 Only contours whose circularities fall within the specified range are retained. 1276 The contour list is modified in place. 1277 1278 Parameters 1279 ---------- 1280 min_value : float or None, optional 1281 The minimum allowed circularity. If a contour's circularity is less than this, it is discarded. 1282 If None, no lower bound is applied. Default is None. 1283 max_value : float or None, optional 1284 The maximum allowed circularity. If a contour's circularity is greater than this, it is discarded. 1285 If None, no upper bound is applied. Default is None. 1286 1287 Returns 1288 ------- 1289 None 1290 """ 1291 self.filter(func=operator.methodcaller("get_circularity"), min_value=min_value, max_value=max_value)
Filter the list of contours by their circularity.
Only contours whose circularities fall within the specified range are retained. The contour list is modified in place.
Parameters
- min_value (float or None, optional): The minimum allowed circularity. If a contour's circularity is less than this, it is discarded. If None, no lower bound is applied. Default is None.
- max_value (float or None, optional): The maximum allowed circularity. If a contour's circularity is greater than this, it is discarded. If None, no upper bound is applied. Default is None.
Returns
- None
1293 def find_largest(self, return_index=True): 1294 """ 1295 Return the contour (or the contour index) with the largest area. 1296 1297 Parameters 1298 ---------- 1299 return_index : bool, optional 1300 If `True`, return the index of the largest contour. Otherwise, return the contour object itself. 1301 Default is `True`. 1302 1303 Returns 1304 ------- 1305 int or Contour or None 1306 The index of the largest contour if `return_index` is `True`, or the largest contour object if 1307 `return_index` is `False`. If no contour is found, returns None. 1308 """ 1309 max_area = None 1310 argmax_area = None 1311 for (n_contour, contour) in enumerate(self.contours): 1312 area = contour.get_area() 1313 if (max_area is None) or (area > max_area): 1314 max_area = area 1315 argmax_area = n_contour 1316 1317 if argmax_area is None: 1318 return None 1319 else: 1320 if return_index: 1321 return argmax_area 1322 else: 1323 return self.contours[argmax_area]
Return the contour (or the contour index) with the largest area.
Parameters
- return_index (bool, optional):
If
True, return the index of the largest contour. Otherwise, return the contour object itself. Default isTrue.
Returns
- int or Contour or None: The index of the largest contour if
return_indexisTrue, or the largest contour object ifreturn_indexisFalse. If no contour is found, returns None.
1325 def draw_all(self, image, colors=None, **kwargs): 1326 """ 1327 Draw all the contours into an image using a different color for each contour. 1328 1329 Parameters 1330 ---------- 1331 image : numpy.ndarray 1332 The image into which the contours will be drawn. 1333 colors : list of tuples, optional 1334 The colors to use for each contour. The length of the list must be equal to the number of contours. 1335 If not provided, random colors will be generated. Default is None. 1336 **kwargs 1337 Additional keyword arguments to pass to the `Contour.draw()` method. 1338 1339 Returns 1340 ------- 1341 None 1342 """ 1343 if colors is None: 1344 colors = tuple(dito.random_color() for _ in range(len(self))) 1345 1346 for (contour, color) in zip(self.contours, colors): 1347 contour.draw(image=image, color=color, **kwargs)
Draw all the contours into an image using a different color for each contour.
Parameters
- image (numpy.ndarray): The image into which the contours will be drawn.
- colors (list of tuples, optional): The colors to use for each contour. The length of the list must be equal to the number of contours. If not provided, random colors will be generated. Default is None.
- **kwargs: Additional keyword arguments to pass to the
Contour.draw()method.
Returns
- None
1350class ContourFinder(ContourList): 1351 """ 1352 Extension of `ContourList` with support to find contours in an image. 1353 1354 Between OpenCV 3.x and 4.x, the API of `cv2.findContours` changed. This 1355 class is basically a wrapper for `cv2.findContours` which works for both 1356 versions. 1357 1358 As a subclass of `ContourList`, it inherits all the contour list 1359 manipulation methods defined by its parent class. 1360 1361 Attributes 1362 ---------- 1363 image : numpy.ndarray 1364 The input image, stored as a copy of the input argument. 1365 contours : list of Contour 1366 The contours found in the input image. 1367 """ 1368 1369 def __init__(self, image): 1370 """ 1371 Initialize a `ContourFinder` instance and find contours in the given `image`. 1372 1373 Parameters 1374 ---------- 1375 image : numpy.ndarray 1376 The image from which to find contours. Will be converted to dtype `numpy.uint8` 1377 """ 1378 self.image = image.copy() 1379 if self.image.dtype == bool: 1380 self.image = dito.core.convert(image=self.image, dtype=np.uint8) 1381 contours_ = self.find_contours(image=self.image) 1382 super().__init__(contours_=contours_) 1383 1384 @staticmethod 1385 def find_contours(image): 1386 """ 1387 Find the contours in the given `image`. 1388 1389 Parameters 1390 ---------- 1391 image : numpy.ndarray 1392 The image from which to find contours. 1393 1394 Returns 1395 ------- 1396 list of Contour 1397 A list of instances of the `Contour` class, one for each contour found in the input image. 1398 """ 1399 1400 # find raw contours 1401 result = cv2.findContours(image=image, mode=cv2.RETR_LIST, method=cv2.CHAIN_APPROX_NONE) 1402 1403 # compatible with OpenCV 3.x and 4.x, see https://stackoverflow.com/a/53909713/1913780 1404 contours_raw = result[-2] 1405 1406 # return tuple of instances of class `Contour` 1407 return [Contour(points=contour_raw[:, 0, :]) for contour_raw in contours_raw]
Extension of ContourList with support to find contours in an image.
Between OpenCV 3.x and 4.x, the API of cv2.findContours changed. This
class is basically a wrapper for cv2.findContours which works for both
versions.
As a subclass of ContourList, it inherits all the contour list
manipulation methods defined by its parent class.
Attributes
- image (numpy.ndarray): The input image, stored as a copy of the input argument.
- contours (list of Contour): The contours found in the input image.
1369 def __init__(self, image): 1370 """ 1371 Initialize a `ContourFinder` instance and find contours in the given `image`. 1372 1373 Parameters 1374 ---------- 1375 image : numpy.ndarray 1376 The image from which to find contours. Will be converted to dtype `numpy.uint8` 1377 """ 1378 self.image = image.copy() 1379 if self.image.dtype == bool: 1380 self.image = dito.core.convert(image=self.image, dtype=np.uint8) 1381 contours_ = self.find_contours(image=self.image) 1382 super().__init__(contours_=contours_)
Initialize a ContourFinder instance and find contours in the given image.
Parameters
- image (numpy.ndarray):
The image from which to find contours. Will be converted to dtype
numpy.uint8
1384 @staticmethod 1385 def find_contours(image): 1386 """ 1387 Find the contours in the given `image`. 1388 1389 Parameters 1390 ---------- 1391 image : numpy.ndarray 1392 The image from which to find contours. 1393 1394 Returns 1395 ------- 1396 list of Contour 1397 A list of instances of the `Contour` class, one for each contour found in the input image. 1398 """ 1399 1400 # find raw contours 1401 result = cv2.findContours(image=image, mode=cv2.RETR_LIST, method=cv2.CHAIN_APPROX_NONE) 1402 1403 # compatible with OpenCV 3.x and 4.x, see https://stackoverflow.com/a/53909713/1913780 1404 contours_raw = result[-2] 1405 1406 # return tuple of instances of class `Contour` 1407 return [Contour(points=contour_raw[:, 0, :]) for contour_raw in contours_raw]
1410def contours(image): 1411 """ 1412 Find and return all contours in the given image. 1413 1414 It is a convenience wrapper for `ContourFinder`. 1415 1416 Parameters 1417 ---------- 1418 image : numpy.ndarray 1419 The image in which to find the contours. 1420 1421 Returns 1422 ------- 1423 ContourList 1424 An instance of the `ContourList` class containing all the found contours. 1425 """ 1426 contour_finder = ContourFinder(image=image) 1427 return contour_finder.contours
Find and return all contours in the given image.
It is a convenience wrapper for ContourFinder.
Parameters
- image (numpy.ndarray): The image in which to find the contours.
Returns
- ContourList: An instance of the
ContourListclass containing all the found contours.
1430class VoronoiPartition(ContourList): 1431 """ 1432 Extension of `ContourList` where the contours are derived as facets of the Voronoi partition of a set of given points. 1433 1434 As a subclass of `ContourList`, it inherits all the contour list 1435 manipulation methods defined by its parent class. 1436 1437 Attributes 1438 ---------- 1439 contours : list of Contour 1440 The list of Voronoi facets, each represented as a `Contour` object. 1441 """ 1442 1443 def __init__(self, image_size, points): 1444 """ 1445 Initialize a `VoronoiPartition` instance from a set of input points. 1446 1447 Parameters 1448 ---------- 1449 image_size : tuple of int 1450 The size of the image (width, height) in pixels. 1451 points : numpy.ndarray 1452 The array of input points for which to compute the Voronoi partition. Each row represents a point (x, y). 1453 """ 1454 contours_ = self.get_facets(image_size=image_size, points=points) 1455 super().__init__(contours_=contours_) 1456 1457 @staticmethod 1458 def get_facets(image_size, points): 1459 """ 1460 Calculate the Voronoi partition based on the given points. 1461 1462 Parameters 1463 ---------- 1464 image_size : tuple of int 1465 The size of the image (width, height) in pixels. 1466 points : numpy.ndarray 1467 The array of input points for which to compute the Voronoi partition. Each row represents a point (x, y). 1468 1469 Returns 1470 ------- 1471 list of Contour 1472 The list of Voronoi facets, each represented as a `Contour` object. 1473 """ 1474 subdiv = cv2.Subdiv2D((0, 0, image_size[0], image_size[1])) 1475 for point in points: 1476 subdiv.insert(pt=point) 1477 (voronoi_facets, voronoi_centers) = subdiv.getVoronoiFacetList(idx=[]) 1478 return [Contour(voronoi_facet) for voronoi_facet in voronoi_facets]
Extension of ContourList where the contours are derived as facets of the Voronoi partition of a set of given points.
As a subclass of ContourList, it inherits all the contour list
manipulation methods defined by its parent class.
Attributes
- contours (list of Contour):
The list of Voronoi facets, each represented as a
Contourobject.
1443 def __init__(self, image_size, points): 1444 """ 1445 Initialize a `VoronoiPartition` instance from a set of input points. 1446 1447 Parameters 1448 ---------- 1449 image_size : tuple of int 1450 The size of the image (width, height) in pixels. 1451 points : numpy.ndarray 1452 The array of input points for which to compute the Voronoi partition. Each row represents a point (x, y). 1453 """ 1454 contours_ = self.get_facets(image_size=image_size, points=points) 1455 super().__init__(contours_=contours_)
Initialize a VoronoiPartition instance from a set of input points.
Parameters
- image_size (tuple of int): The size of the image (width, height) in pixels.
- points (numpy.ndarray): The array of input points for which to compute the Voronoi partition. Each row represents a point (x, y).
1457 @staticmethod 1458 def get_facets(image_size, points): 1459 """ 1460 Calculate the Voronoi partition based on the given points. 1461 1462 Parameters 1463 ---------- 1464 image_size : tuple of int 1465 The size of the image (width, height) in pixels. 1466 points : numpy.ndarray 1467 The array of input points for which to compute the Voronoi partition. Each row represents a point (x, y). 1468 1469 Returns 1470 ------- 1471 list of Contour 1472 The list of Voronoi facets, each represented as a `Contour` object. 1473 """ 1474 subdiv = cv2.Subdiv2D((0, 0, image_size[0], image_size[1])) 1475 for point in points: 1476 subdiv.insert(pt=point) 1477 (voronoi_facets, voronoi_centers) = subdiv.getVoronoiFacetList(idx=[]) 1478 return [Contour(voronoi_facet) for voronoi_facet in voronoi_facets]
Calculate the Voronoi partition based on the given points.
Parameters
- image_size (tuple of int): The size of the image (width, height) in pixels.
- points (numpy.ndarray): The array of input points for which to compute the Voronoi partition. Each row represents a point (x, y).
Returns
- list of Contour: The list of Voronoi facets, each represented as a
Contourobject.
1481def voronoi(image_size, points): 1482 """ 1483 Compute the Voronoi partition of a set of input points. 1484 1485 It is a convenience wrapper for `VoronoiPartition`. 1486 1487 Parameters 1488 ---------- 1489 image_size : tuple of int 1490 The size of the image (width, height) in pixels. 1491 points : numpy.ndarray 1492 The array of input points. Each row represents a point (x, y). 1493 1494 Returns 1495 ------- 1496 list of Contour 1497 The list of Voronoi facets, each represented as a `Contour` object. 1498 """ 1499 voronoi_partition = VoronoiPartition(image_size=image_size, points=points) 1500 return voronoi_partition.contours
Compute the Voronoi partition of a set of input points.
It is a convenience wrapper for VoronoiPartition.
Parameters
- image_size (tuple of int): The size of the image (width, height) in pixels.
- points (numpy.ndarray): The array of input points. Each row represents a point (x, y).
Returns
- list of Contour: The list of Voronoi facets, each represented as a
Contourobject.