Source code for aigarmic.process_plate_image

# Filename: 	process_plate_image.py
# Author: 	Alessandro Gerada
# Date: 	2023-01-27
# Copyright: 	Alessandro Gerada 2023
# Email: 	alessandro.gerada@liverpool.ac.uk

"""Pre-processing of agar plate images to extract individual colonies"""

import cv2  # pylint: disable=import-error
from imutils import contours
from typing import Optional
from numpy import ndarray, empty


[docs] def find_threshold_value(image: ndarray, look_for: int, start: int = 20, end: int = 100, by: int = 1, area_lower_bound: int = 1000) -> Optional[tuple[list, int]]: """ Find threshold value that correctly splits an agar plate image into colony sub-images. Assumes that a black grid overlays the image. :param image: Image file loaded using cv2.imread :param look_for: target sub-images :param start: starting threshold value :param end: ending threshold value :param by: threshold increment value :param area_lower_bound: minimum area for a contour to be considered :return: tuple of contours and threshold value """ for i in range(start, end, by): _, thresh = cv2.threshold(image, i, 255, cv2.THRESH_BINARY_INV) # pylint: disable=no-member # Find contours and filter using area _contours = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) # pylint: disable=no-member _contours = _contours[0] if len(_contours) == 2 else _contours[1] grid_contours = [] for c in _contours: area = cv2.contourArea(c) # pylint: disable=no-member if area > area_lower_bound: grid_contours.append(c) # sort contours and remove biggest (outer) grid square grid_contours = sorted(grid_contours, key=cv2.contourArea) # pylint: disable=no-member grid_contours = grid_contours[:-1] # If we find the target boxes, return contours and threshold if len(grid_contours) == look_for: return grid_contours, i return None
[docs] def split_by_grid(image: ndarray, n_rows: int, n_cols: int, visualise_contours: bool = False, plate_name: Optional[str] = None) -> list[list[ndarray]]: """ Split an agar plate image into individual colony sub-images using a grid overlay. :param image: image file loaded using cv2.imread :param n_rows: number of rows in the grid :param n_cols: number of columns in the grid :param visualise_contours: if True, display the contours found (useful for validation) :param plate_name: name of plate to display in visualisation (useful for validation) :return: matrix of sub-images """ if visualise_contours and not plate_name: raise ValueError("Pass plate name to split_by_grid if using visualise_contours") blur = cv2.GaussianBlur(image, (25, 25), 0) # pylint: disable=no-member gray = cv2.cvtColor(blur, cv2.COLOR_BGR2GRAY) # pylint: disable=no-member grid_contours, _ = find_threshold_value(gray, look_for=n_rows * n_cols) if visualise_contours: _image = image cv2.drawContours(_image, grid_contours, -1, (0, 255, 0), 10) # pylint: disable=no-member cv2.imshow(plate_name, _image) # pylint: disable=no-member cv2.waitKey() # pylint: disable=no-member if not grid_contours: raise ValueError("Unable to find contours threshold that returns correct number of colony images") # Sort contours, starting left to right (grid_contours, _) = contours.sort_contours(grid_contours, method="left-to-right") sorted_grid = [] col = [] # temporary list to hold columns while sorting for (i, c) in enumerate(grid_contours, 1): col.append(c) if i % n_rows == 0: # found column - sort top to bottom and add to output (c_tmp, _) = contours.sort_contours(col, method="top-to-bottom") sorted_grid.append(c_tmp) col = [] out_matrix = [[empty(shape=1) for _ in range(len(sorted_grid))] for _ in range(n_rows)] # Iterate through each box for j, col in enumerate(sorted_grid): for i, c in enumerate(col): x, y, w, h = cv2.boundingRect(c) # pylint: disable=no-member cropped_image = image[y:y + h, x:x + w] out_matrix[i][j] = cropped_image return out_matrix