Source code for wormpose.dataset.image_processing.image_utils
"""
Image processing example implementation for segmenting the worm in an image
"""
from typing import Callable
import cv2
import numpy as np
# how much to crop the image to find the biggest blob
# (this is to avoid picking a biggest blob that is not the worm)
_CROP_PERCENT = 0.15
[docs]class ConstantThreshold:
"""
Threshold function that always returns the same threshold
"""
def __init__(self, threshold_value):
self.threshold_value = threshold_value
def __call__(self, frame: np.ndarray) -> int:
return self.threshold_value
[docs]class OtsuThreshold(object):
"""
Calculates automatic Otsu threshold on the blurred frame
"""
def __init__(self, blur_kernel):
"""
Creates an Otsu threshold operation with a preprocessing gaussian blur
:param blur_kernel: Gaussian Kernel Size for the blur operation before the Otsu threshold method
to split background and foreground. [height width]. height and width should be odd and can have different values.
"""
self.blur_kernel = blur_kernel
def __call__(self, frame: np.ndarray) -> int:
blurred_frame = cv2.GaussianBlur(frame, self.blur_kernel, 0)
blurred_frame[frame == 0] = 0
background_threshold, _ = cv2.threshold(
blurred_frame[blurred_frame > 0],
0,
255,
cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU,
)
return background_threshold
[docs]def segment_foreground(
frame: np.ndarray,
foreground_close_struct_element,
foreground_dilate_struct_element,
threshold_fn: Callable[[np.ndarray], int],
is_foreground_lighter_than_background: bool,
):
"""
Processes a frame to isolate the object of interest (worm) from the background
:param frame: image to process
:param foreground_close_struct_element: morphological element to close holes in the foreground mask
:param foreground_dilate_struct_element: morphological element to expand the foreground mask
:param threshold_fn: function that will return the threshold to separate foreground from background in a frame
:param is_foreground_lighter_than_background: set to True if the foreground object of interest is lighter
(pixel values are on average higher) than the background
:return: segmentation mask with values of 1 for the worm object and 0 for the background,
and average value of the background pixels
"""
# find the threshold to separate foreground from background
background_threshold = threshold_fn(frame)
# use the threshold to deduce background and foreground masks, fill in holes
foreground_mask = (frame > 0).astype(np.uint8) * (frame < background_threshold).astype(np.uint8)
foreground_mask = cv2.morphologyEx(foreground_mask, cv2.MORPH_CLOSE, foreground_close_struct_element)
background_mask = (((frame > 0).astype(np.uint8) - foreground_mask) > 0).astype(np.uint8)
# invert foreground and background masks if the foreground is lighter than the background
if is_foreground_lighter_than_background:
foreground_mask, background_mask = background_mask, foreground_mask
# calculate the average background color
background_values = frame[background_mask.astype(bool)]
background_color = int(np.mean(background_values)) if len(background_values) > 0 else 0
background_color = frame.dtype.type(background_color)
# process the foreground mask to eliminate non worm objects
# use connected components to find blobs, but focus on the center of the image to find the biggest
# modify foreground_mask to only show the worm object
nb_labels, labels, stats, _ = cv2.connectedComponentsWithStats(foreground_mask)
labels_crop_size = int(_CROP_PERCENT * max(foreground_mask.shape))
labels_cropped = labels[
labels_crop_size : foreground_mask.shape[0] - labels_crop_size,
labels_crop_size : foreground_mask.shape[1] - labels_crop_size,
]
if nb_labels == 1:
foreground_mask.fill(0)
foreground_objects_sizes = [len(np.where(labels_cropped == l)[0]) for l in range(1, nb_labels)]
if len(foreground_objects_sizes) > 0:
biggest_blob_label = np.argmax(foreground_objects_sizes) + 1
foreground_mask[labels != biggest_blob_label] = 0
# add a little padding to the foreground mask
foreground_mask = cv2.dilate(foreground_mask, foreground_dilate_struct_element)
return foreground_mask, background_color