--- a +++ b/mediaug/image_utils.py @@ -0,0 +1,260 @@ +from PIL import Image, ImageSequence, ImageDraw +import numpy as np +import cv2 + +from mediaug.utils import convert_array_to_poly +from mediaug.variables import COLOR_CYTO_MASK, COLOR_NUC_MASK + + +def np_to_pil(img): + """ Converts a PIL format image to numpy + Args: + new_img (np.array): converted image + Returns: + img (PIL.Image): input image + """ + return Image.fromarray(img) + + +def pil_to_np(img): + """ Converts a PIL format image to numpy + Args: + img (PIL.Image): input image + Returns: + new_img (np.array): converted image + """ + return np.array(img) + + +def read_tiff(path): + """ Reads an image with a .tff extension, used in the Unet example. + Args: + path (str): The path of the image + Returns: + ans (np.array): Numpy array of the image values + """ + return np.array([np.array(p) for p in ImageSequence.Iterator(Image.open(path))]) + + +def read_bmp(img_path): + """ Reads an image with a .bmp extension Returns np.array + Args: + path (str): The path of the image + Returns: + ans (np.array): Numpy array of the image values + """ + return cv2.imread(img_path) + + +def read_png(img_path): + """ Reads an image with a .png extension. Returns np.array + Args: + path (str): The path of the image + Returns: + ans (np.array): Numpy array of the image values + """ + return cv2.imread(img_path) + + +def read_dat_file(path): + """ Reads an .dat file with polygons, the data from SIPaKMeD. Returns np.array + Args: + path (str): The path of the .dat file + Returns: + ans (np.array): The [n,2] array of a polygon + """ + return np.loadtxt(path, delimiter=',') + + +def read_img(img_path): + """ Reads an image + Args: + path (str): The path of the image + Returns: + ans (np.array): Numpy array of the image values + """ + return cv2.imread(img_path) + + +def save_img(img, path): + """ Save an img to given path + Args: + img (np.array): The image numpy array + path (str): The path to save the image to + """ + cv2.imwrite(path, img) + return path + + +def rotate(image, angle): + """ Rotates an image by angle in degrees, increases + the dimension of the imageas necessary + Args: + image (np.array): Image array + angle (int): Degree of rotation clockwise in degrees + Returns: + rotated (np.array): The new rotated image array + """ + (h, w) = image.shape[:2] + (cX, cY) = (w // 2, h // 2) + + # grab the rotation matrix (applying the negative of the + # angle to rotate clockwise), then grab the sine and cosine + # (i.e., the rotation components of the matrix) + M = cv2.getRotationMatrix2D((cX, cY), -angle, 1.0) + cos = np.abs(M[0, 0]) + sin = np.abs(M[0, 1]) + + # compute the new bounding dimensions of the image + nW = int((h * sin) + (w * cos)) + nH = int((h * cos) + (w * sin)) + + # adjust the rotation matrix to take into account translation + M[0, 2] += (nW / 2) - cX + M[1, 2] += (nH / 2) - cY + return cv2.warpAffine(image, M, (nW, nH)) + + +def soften_mask(mask, amount=5): + """ Softens the edges of a mask by dialating it and then Gaussian blurring + Args: + mask (np.array): The mask array + amount (int): The number of times to apply the dilation, should be arround 5 + Returns: + ans (np.array): The transformed mask + """ + kernel = np.ones((5,5), np.uint8) + mask_dilation = cv2.dilate(mask, kernel, iterations=amount) + blur = cv2.GaussianBlur(mask_dilation, (5,5), 0) + return cv2.max(mask, blur) + + +def get_blank_mask(img, greyscale=False): + """ Return a blank mask the same size as the given image + Args: + img (np.array): The input image array + greyscale (Boolean): Make it a single channel mask + Returns: + mask (np.array): Black mask the size of img + """ + if greyscale: + return np.zeros(img.shape[:2], np.uint8) + return np.zeros(img.shape,np.uint8) + + +def image_on_image_alpha(bg, fg, fg_mask, center): + """ Place an image on an image with a backround mask + Blends them using the alpha mask. + Args: + bg (np.array): Background image array + fg (np.array): Foreground image array + fg_mask (np.array): Foreground alpha mask array + center (tuple[int,int]): The postion on bg to place fg + Returns: + ans (np.array): The merged image + """ + alpha = np.zeros(bg.shape[:2], dtype=np.uint8) + alpha = place_img_on_img(alpha, fg_mask, center) + cell_img = place_img_on_img(bg.copy(), fg, center) + + alpha = alpha.astype(float)/255 + alpha = np.repeat(alpha[:, :, np.newaxis], 3, axis=2) + + fg = cv2.multiply(alpha, cell_img.astype(float)) + bg = cv2.multiply(1.0 - alpha, bg.astype(float)) + + return cv2.add(fg, bg).astype(np.uint8) + + +def place_img_on_img(bg, fg, center): + """ Place an image on top of another image + Args: + bg (np.array): Backgourn image + fg (np.array): Foreground image + center (x, y): Where to put CENTER of fg image + Returns: + ans (np.array): The new image + """ + ch, cw = center + fg_h, fg_w = fg.shape[:2] + bg_h, bg_w = bg.shape[:2] + + # check offset in bg + if cw < 0 or cw > bg_w or ch < 0 or ch > bg_h: + raise ValueError('Center not in backgound bounds') + + # find top left corner of fg in respect to bg + left_w = cw - (fg_w // 2) + left_h = ch - (fg_h // 2) + end_w = left_w + fg_w + end_h = left_h + fg_h + + # for if goes over bg boundries + abs_left_w = max(left_w, 0) + abs_left_h = max(left_h, 0) + abs_end_w = min(end_w, bg_w) + abs_end_h = min(end_h, bg_h) + + # for fg boundries + fg_left_w = abs(min(left_w, 0)) + fg_left_h = abs(min(left_h, 0)) + diff_w = bg_w - end_w + diff_h = bg_h - end_h + if diff_w >= 0: + fg_end_w = fg_w + else: + fg_end_w = diff_w + if diff_h >= 0: + fg_end_h = fg_h + else: + fg_end_h = diff_h + + ans = np.copy(bg) + ans[abs_left_h:abs_end_h, abs_left_w:abs_end_w] = fg[fg_left_h:fg_end_h, fg_left_w:fg_end_w] + return ans + + +def generate_cell_mask(img, cyto, nuc): + """ Generate a mask for a labelled cell + Args: + img (np.array): The image array + cyto (np.array): A [n,2] numpy array representing the polygon for the cytoplasm of a cell + nuc (np.array): A [n,2] numpy array representing the polygon for the nucleus of a cell + Returns: + mask (np.array): The mask of parts of a cell + """ + w, h, _ = img.shape + mask = Image.new(mode="RGB", size=(h, w)) + ImageDraw.Draw(mask).polygon(convert_array_to_poly(cyto), outline=None, fill=COLOR_CYTO_MASK) + ImageDraw.Draw(mask).polygon(convert_array_to_poly(nuc), outline=None, fill=COLOR_NUC_MASK) + return np.array(mask) + + +def generate_cell_mask_list(img, cytos, nucs): + """ Generate a masks for a list of labelled cell in slide + Args: + img (np.array): The image array + cytos (list[np.array]): List of[n,2] numpy array representing the polygon for the cytoplasm of a cell + nucs (list[np.array]): List of [n,2] numpy array representing the polygon for the nucleus of a cell + Returns: + mask (np.array): The mask of cells in slide + """ + h, w, _ = img.shape + mask = Image.new(mode="RGB", size=(w, h)) + for poly in cytos: + ImageDraw.Draw(mask).polygon(convert_array_to_poly(poly), outline=None, fill=COLOR_CYTO_MASK) + for poly in nucs: + ImageDraw.Draw(mask).polygon(convert_array_to_poly(poly), outline=None, fill=COLOR_NUC_MASK) + return np.array(mask) + + +def is_greyscale(img): + """ Checks if an image array is greyscale + Args: + img (np.array): The image array + Returns: + is_greyscaled (Boolean): Is the image a greyscale image + """ + if len(img.shape) < 3: + return True + return False