--- a +++ b/utils/libs.py @@ -0,0 +1,135 @@ +import glob +import numpy as np +import cv2 +from PIL import Image +from utils import config + + +def write(filename, content, class_num=2, color_map=True): + """ + Save image array to a specified path. + The image will be automatically recolored via the class number. + :param filename: The specified path. + :param content: Numpy array containing the image. + :param class_num: Total class number. + :param color_map: Whether change the probability into gray grade. + """ + if class_num <= 1: + raise Exception('ERROR: Class number should be >= 2.') + color_stage = 255. / (class_num - 1) if color_map else 1.0 + new_image = Image.fromarray(np.uint8(content * color_stage)) + new_image.save(filename, "PNG") + + +def generate_effective_regions(size): + """ + This function is used to generate effective regions for inference according to the given slide size. + :param size: Given slide size, should be in the form of [w, h]. + """ + width = size[0] + height = size[1] + x_step = int(width / config.CENTER_SIZE) + y_step = int(height / config.CENTER_SIZE) + regions = [] + for x in range(0, x_step): + for y in range(0, y_step): + regions.append([x * config.CENTER_SIZE, y * config.CENTER_SIZE, 0, 0, + config.CENTER_SIZE - 1, config.CENTER_SIZE - 1]) + if not height % config.CENTER_SIZE == 0: + for x in range(0, x_step): + regions.append([x * config.CENTER_SIZE, height - config.CENTER_SIZE, + 0, (y_step + 1) * config.CENTER_SIZE - height, + config.CENTER_SIZE - 1, config.CENTER_SIZE - 1]) + if not width % config.CENTER_SIZE == 0: + for y in range(0, y_step): + regions.append([width - config.CENTER_SIZE, y * config.CENTER_SIZE, + (x_step + 1) * config.CENTER_SIZE - width, 0, + config.CENTER_SIZE - 1, config.CENTER_SIZE - 1]) + if not (height % config.CENTER_SIZE == 0 or width % config.CENTER_SIZE == 0): + regions.append([width - config.CENTER_SIZE, height - config.CENTER_SIZE, + (x_step + 1) * config.CENTER_SIZE - width, (y_step + 1) * config.CENTER_SIZE - height, + config.CENTER_SIZE - 1, config.CENTER_SIZE - 1]) + return regions + + +def generate_overlap_tile(region, dimensions): + """ + This function is used to process border patches. + """ + shifted_region_x = region[0] - config.BORDER_SIZE + shifted_region_y = region[1] - config.BORDER_SIZE + clip_region_x = config.BORDER_SIZE + clip_region_y = config.BORDER_SIZE + if region[0] == 0: + shifted_region_x = shifted_region_x + config.BORDER_SIZE + clip_region_x = 0 + if region[1] == 0: + shifted_region_y = shifted_region_y + config.BORDER_SIZE + clip_region_y = 0 + if region[0] == dimensions[0] - config.CENTER_SIZE: + shifted_region_x = shifted_region_x - config.BORDER_SIZE + clip_region_x = 2 * config.BORDER_SIZE + if region[1] == dimensions[1] - config.CENTER_SIZE: + shifted_region_y = shifted_region_y - config.BORDER_SIZE + clip_region_y = 2 * config.BORDER_SIZE + return [shifted_region_x, shifted_region_y], [clip_region_x, clip_region_y] + + +def image_to_array(input_image): + """ + Loads image into numpy array. + """ + im_array = np.array(input_image.getdata(), dtype=np.uint8) + im_array = im_array.reshape((input_image.size[0], input_image.size[1])) + return im_array + + +def post_processing(image_patch): + """ + Remove small noisy points. + """ + kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (config.FILTER_KERNEL, config.FILTER_KERNEL)) + open_patch = cv2.morphologyEx(image_patch, cv2.MORPH_OPEN, kernel) + close_patch = cv2.morphologyEx(open_patch, cv2.MORPH_CLOSE, kernel) + return close_patch + + +def concat_patches(temp_dir, image_name): + """ + Concatenate the predicted patches into a thumbnail result. + """ + prediction_list = glob.glob(temp_dir + image_name + '*_prediction.png') + patch_list = [] + for prediction_image in prediction_list: + name_parts = prediction_image.split('/')[-1].split('_') + pos_x, pos_y = int(name_parts[-3]), int(name_parts[-2]) + patch_list.append([pos_x, pos_y]) + image_patches = [] + patch_list.sort() + last_x = -1 + row_patch = [] + for position in patch_list: + pos_x = position[0] + pos_y = position[1] + image = Image.open(temp_dir + '_'.join([image_name, str(pos_x), str(pos_y), 'prediction']) + '.png') + original_width, original_height = image.size + if original_width < config.THUMBNAIL_RATIO or original_height < config.THUMBNAIL_RATIO: + continue + image = image.resize( + (int(original_width / config.THUMBNAIL_RATIO), + int(original_height / config.THUMBNAIL_RATIO)), Image.NEAREST) + image_patch = image_to_array(image) + if not pos_x == last_x: + last_x = pos_x + if len(row_patch) == 0: + row_patch = image_patch + else: + if not len(image_patches) == 0: + image_patches = np.column_stack((image_patches, row_patch)) + else: + image_patches = row_patch + row_patch = image_patch + else: + row_patch = np.row_stack((row_patch, image_patch)) + prediction = np.column_stack((image_patches, row_patch)) + return prediction