Diff of /mediaug/image_utils.py [000000] .. [05e710]

Switch to side-by-side view

--- 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