library.helpers

  1from pathlib import Path
  2
  3import cv2
  4
  5def ensure_grayscale(im):
  6    """
  7    Convert an image to grayscale if it is not already.
  8
  9    Parameters
 10    ----------
 11    im : numpy.ndarray
 12        Input image array. Can be either a 2D grayscale image or a 3D color 
 13        image with 3 channels (BGR/BGRA format).
 14
 15    Returns
 16    -------
 17    numpy.ndarray
 18        Grayscale image as a 2D array.
 19
 20    Raises
 21    ------
 22    ValueError
 23        If the input image has an unexpected shape (neither 2D nor 3D with 3 channels).
 24
 25    Examples
 26    --------
 27    >>> gray_img = ensure_grayscale(color_image)
 28    >>> gray_img = ensure_grayscale(already_gray_image)  # Returns unchanged
 29    """
 30    if len(im.shape) == 3 and im.shape[2] == 3:
 31        gray = cv2.cvtColor(im, cv2.COLOR_BGRA2GRAY)
 32    elif len(im.shape) == 2:
 33        gray = im
 34    else:
 35        raise ValueError(f"Unexpected image shape: {image.shape}")
 36
 37    return gray
 38
 39def crop_and_save(bbox, image_path, output_path, show=False, dpi=100):
 40    """Crop and save a square region from an image.
 41
 42    Parameters
 43    ----------
 44    bbox : tuple
 45        ((x1, y1), (x2, y2)) Coordinates of the top-left and bottom-right
 46        corners of the crop rectangle.
 47    image_path : str or pathlib.Path
 48        Path to the source image.
 49    output_path : str or pathlib.Path
 50        Path where the cropped image will be saved.
 51    show : bool, optional
 52        If True, display the cropped image.
 53    dpi : int, optional
 54        DPI to use when displaying the image.
 55
 56    Returns
 57    -------
 58    pathlib.Path
 59        Path to the saved cropped image.
 60
 61    Raises
 62    ------
 63    ValueError
 64        If bbox is not a valid rectangle (e.g. zero or negative area).
 65    """
 66
 67    im = cv2.imread(str(image_path))
 68
 69    (x1, y1), (x2, y2) = bbox
 70
 71    cropped = im[y1:y2, x1:x2]
 72
 73    output_path = Path(output_path)
 74    output_path.parent.mkdir(exist_ok=True)
 75    cv2.imwrite(str(output_path), cropped)
 76
 77    if show:
 78        plt.figure(dpi=dpi)
 79        plt.imshow(cropped)
 80        plt.title(str(output_path))
 81        plt.show()
 82    return output_path
 83
 84
 85def get_mask(img_path, thresh=128, maxval=255, show=False, dpi=300):
 86    """Return a binary mask for an image using Otsu's thresholding.
 87
 88    Parameters
 89    ----------
 90    img_path : str or pathlib.Path
 91        Path to the input image.
 92    thresh : int, optional
 93        Initial threshold value (ignored when using Otsu's method).
 94    maxval : int, optional
 95        Value to assign to pixels greater than the threshold.
 96    show : bool, optional
 97        If True, display the resulting mask.
 98    dpi : int, optional
 99        DPI to use when displaying the image.
100
101    Returns
102    -------
103    ret : float
104        Computed threshold value (from Otsu's method when requested).
105    binary_mask : numpy.ndarray
106        Binary mask image (dtype uint8) with values in {0, maxval}.
107    """
108
109    im = cv2.imread(img_path)
110    im = ensure_grayscale(im)
111    ret, binary_mask = cv2.threshold(im, thresh=thresh, maxval=maxval, type=cv2.THRESH_BINARY + cv2.THRESH_OTSU)
112    if show:
113        plt.figure(dpi=dpi)
114        plt.imshow(binary_mask, cmap='gray')
115        plt.show()
116    return ret, binary_mask
117
118
119# +
120def get_bounding_box(binary_mask):
121    """Compute a square bounding box around the largest contour in a binary mask.
122
123    Parameters
124    ----------
125    binary_mask : numpy.ndarray
126        2D binary image (dtype uint8) where foreground pixels are non-zero (e.g. 255).
127
128    Returns
129    -------
130    tuple
131        A pair of 2-tuples ((x1, y1), (x2, y2)) giving the top-left and
132        bottom-right coordinates of a square bounding box that encloses the
133        largest contour. Coordinates are clamped to the image boundaries (the
134        function uses the module-level `im` array for image shape).
135
136    Raises
137    ------
138    ValueError
139        If no contours are found in the provided mask.
140    """
141    contours, hiearchy = cv2.findContours(binary_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
142
143    # get the biggest contour
144    max_contour = max(contours, key=cv2.contourArea)
145
146    # get the x, y, width, height of the contour
147    x, y, w, h = cv2.boundingRect(max_contour)
148
149    # get the longest side
150    size = max(w, h)
151
152    # find the center of each side
153    center_x = x + w // 2
154    center_y = y + h // 2
155
156    # calculate the new x, y based on the longest side
157    new_x = center_x - size // 2
158    new_y = center_y - size // 2
159
160    # make sure it is inside the borders of the image
161    new_x = min(new_x, binary_mask.shape[1] - size)
162    new_y = min(new_y, binary_mask.shape[0] - size)
163
164    return((new_x, new_y), (new_x + size, new_y + size))
def ensure_grayscale(im):
 6def ensure_grayscale(im):
 7    """
 8    Convert an image to grayscale if it is not already.
 9
10    Parameters
11    ----------
12    im : numpy.ndarray
13        Input image array. Can be either a 2D grayscale image or a 3D color 
14        image with 3 channels (BGR/BGRA format).
15
16    Returns
17    -------
18    numpy.ndarray
19        Grayscale image as a 2D array.
20
21    Raises
22    ------
23    ValueError
24        If the input image has an unexpected shape (neither 2D nor 3D with 3 channels).
25
26    Examples
27    --------
28    >>> gray_img = ensure_grayscale(color_image)
29    >>> gray_img = ensure_grayscale(already_gray_image)  # Returns unchanged
30    """
31    if len(im.shape) == 3 and im.shape[2] == 3:
32        gray = cv2.cvtColor(im, cv2.COLOR_BGRA2GRAY)
33    elif len(im.shape) == 2:
34        gray = im
35    else:
36        raise ValueError(f"Unexpected image shape: {image.shape}")
37
38    return gray

Convert an image to grayscale if it is not already.

Parameters

im : numpy.ndarray Input image array. Can be either a 2D grayscale image or a 3D color image with 3 channels (BGR/BGRA format).

Returns

numpy.ndarray Grayscale image as a 2D array.

Raises

ValueError If the input image has an unexpected shape (neither 2D nor 3D with 3 channels).

Examples

>>> gray_img = ensure_grayscale(color_image)
>>> gray_img = ensure_grayscale(already_gray_image)  # Returns unchanged
def crop_and_save(bbox, image_path, output_path, show=False, dpi=100):
40def crop_and_save(bbox, image_path, output_path, show=False, dpi=100):
41    """Crop and save a square region from an image.
42
43    Parameters
44    ----------
45    bbox : tuple
46        ((x1, y1), (x2, y2)) Coordinates of the top-left and bottom-right
47        corners of the crop rectangle.
48    image_path : str or pathlib.Path
49        Path to the source image.
50    output_path : str or pathlib.Path
51        Path where the cropped image will be saved.
52    show : bool, optional
53        If True, display the cropped image.
54    dpi : int, optional
55        DPI to use when displaying the image.
56
57    Returns
58    -------
59    pathlib.Path
60        Path to the saved cropped image.
61
62    Raises
63    ------
64    ValueError
65        If bbox is not a valid rectangle (e.g. zero or negative area).
66    """
67
68    im = cv2.imread(str(image_path))
69
70    (x1, y1), (x2, y2) = bbox
71
72    cropped = im[y1:y2, x1:x2]
73
74    output_path = Path(output_path)
75    output_path.parent.mkdir(exist_ok=True)
76    cv2.imwrite(str(output_path), cropped)
77
78    if show:
79        plt.figure(dpi=dpi)
80        plt.imshow(cropped)
81        plt.title(str(output_path))
82        plt.show()
83    return output_path

Crop and save a square region from an image.

Parameters

bbox : tuple ((x1, y1), (x2, y2)) Coordinates of the top-left and bottom-right corners of the crop rectangle. image_path : str or pathlib.Path Path to the source image. output_path : str or pathlib.Path Path where the cropped image will be saved. show : bool, optional If True, display the cropped image. dpi : int, optional DPI to use when displaying the image.

Returns

pathlib.Path Path to the saved cropped image.

Raises

ValueError If bbox is not a valid rectangle (e.g. zero or negative area).

def get_mask(img_path, thresh=128, maxval=255, show=False, dpi=300):
 86def get_mask(img_path, thresh=128, maxval=255, show=False, dpi=300):
 87    """Return a binary mask for an image using Otsu's thresholding.
 88
 89    Parameters
 90    ----------
 91    img_path : str or pathlib.Path
 92        Path to the input image.
 93    thresh : int, optional
 94        Initial threshold value (ignored when using Otsu's method).
 95    maxval : int, optional
 96        Value to assign to pixels greater than the threshold.
 97    show : bool, optional
 98        If True, display the resulting mask.
 99    dpi : int, optional
100        DPI to use when displaying the image.
101
102    Returns
103    -------
104    ret : float
105        Computed threshold value (from Otsu's method when requested).
106    binary_mask : numpy.ndarray
107        Binary mask image (dtype uint8) with values in {0, maxval}.
108    """
109
110    im = cv2.imread(img_path)
111    im = ensure_grayscale(im)
112    ret, binary_mask = cv2.threshold(im, thresh=thresh, maxval=maxval, type=cv2.THRESH_BINARY + cv2.THRESH_OTSU)
113    if show:
114        plt.figure(dpi=dpi)
115        plt.imshow(binary_mask, cmap='gray')
116        plt.show()
117    return ret, binary_mask

Return a binary mask for an image using Otsu's thresholding.

Parameters

img_path : str or pathlib.Path Path to the input image. thresh : int, optional Initial threshold value (ignored when using Otsu's method). maxval : int, optional Value to assign to pixels greater than the threshold. show : bool, optional If True, display the resulting mask. dpi : int, optional DPI to use when displaying the image.

Returns

ret : float Computed threshold value (from Otsu's method when requested). binary_mask : numpy.ndarray Binary mask image (dtype uint8) with values in {0, maxval}.

def get_bounding_box(binary_mask):
121def get_bounding_box(binary_mask):
122    """Compute a square bounding box around the largest contour in a binary mask.
123
124    Parameters
125    ----------
126    binary_mask : numpy.ndarray
127        2D binary image (dtype uint8) where foreground pixels are non-zero (e.g. 255).
128
129    Returns
130    -------
131    tuple
132        A pair of 2-tuples ((x1, y1), (x2, y2)) giving the top-left and
133        bottom-right coordinates of a square bounding box that encloses the
134        largest contour. Coordinates are clamped to the image boundaries (the
135        function uses the module-level `im` array for image shape).
136
137    Raises
138    ------
139    ValueError
140        If no contours are found in the provided mask.
141    """
142    contours, hiearchy = cv2.findContours(binary_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
143
144    # get the biggest contour
145    max_contour = max(contours, key=cv2.contourArea)
146
147    # get the x, y, width, height of the contour
148    x, y, w, h = cv2.boundingRect(max_contour)
149
150    # get the longest side
151    size = max(w, h)
152
153    # find the center of each side
154    center_x = x + w // 2
155    center_y = y + h // 2
156
157    # calculate the new x, y based on the longest side
158    new_x = center_x - size // 2
159    new_y = center_y - size // 2
160
161    # make sure it is inside the borders of the image
162    new_x = min(new_x, binary_mask.shape[1] - size)
163    new_y = min(new_y, binary_mask.shape[0] - size)
164
165    return((new_x, new_y), (new_x + size, new_y + size))

Compute a square bounding box around the largest contour in a binary mask.

Parameters

binary_mask : numpy.ndarray 2D binary image (dtype uint8) where foreground pixels are non-zero (e.g. 255).

Returns

tuple A pair of 2-tuples ((x1, y1), (x2, y2)) giving the top-left and bottom-right coordinates of a square bounding box that encloses the largest contour. Coordinates are clamped to the image boundaries (the function uses the module-level im array for image shape).

Raises

ValueError If no contours are found in the provided mask.