{"cells":[{"metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"cell_type":"markdown","source":"# About notebook\n\nThis notebook contains a code that removes empty space around biopsies and arranges them more compactly. You can see an example of work at the end of the laptop.\nThe presented code allows you to reduce the size of the dataset by more than 2 times.","execution_count":null},{"metadata":{},"cell_type":"markdown","source":"# Restore compressed image\n```python\nfor name in tqdm(names[:10]):\n    img_path = os.path.join(IMAGES, name + '.tiff')\n    img = tiff.imread(img_path)\n    \n    img2 = get_compact(img, compact_representation[name])\n```","execution_count":null},{"metadata":{},"cell_type":"markdown","source":"# Create compressed\nWhen you create annotation for compressed image do not use full resolution image, it is very slow!\n\n```python\nfor name in tqdm(names[:10]):\n    img_path = os.path.join(IMAGES, name)\n    img = MultiImage(img_path)[-1]\n    \n    compact_image, minimal_boxes = get_minimal_image(img)\n    compact_representation[name] = {\"original_size\": img.shape[:2], \"rectangles\": minimal_boxes}\n```","execution_count":null},{"metadata":{"_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","trusted":true},"cell_type":"code","source":"import cv2 as cv\nimport numpy as np\nimport os\nimport matplotlib.pyplot as plt\nimport json\n\nfrom tqdm.notebook import tqdm\n\nfrom skimage.io import MultiImage\n\nimport tifffile as tiff","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"IMAGES = \"../input/prostate-cancer-grade-assessment/train_images\"","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def rotate_image(mat, angle, rect=None):\n    \"\"\"\n    Rotates an image (angle in degrees) and expands image to avoid cropping\n    \"\"\"\n    height, width = mat.shape[:2] # image shape has 3 dimensions\n    image_center = (width / 2, height / 2) # getRotationMatrix2D needs coordinates in reverse order (width, height) compared to shape\n\n    rotation_mat = cv.getRotationMatrix2D(image_center, angle, 1.)\n\n    # rotation calculates the cos and sin, taking absolutes of those.\n    abs_cos = abs(rotation_mat[0,0]) \n    abs_sin = abs(rotation_mat[0,1])\n\n    # find the new width and height bounds\n    bound_w = int(height * abs_sin + width * abs_cos)\n    bound_h = int(height * abs_cos + width * abs_sin)\n\n    # subtract old image center (bringing image back to origo) and adding the new image center coordinates\n    rotation_mat[0, 2] += bound_w / 2 - image_center[0]\n    rotation_mat[1, 2] += bound_h / 2 - image_center[1]\n\n    # rotate image with the new bounds and translated rotation matrix\n    rotated_mat = cv.warpAffine(mat, rotation_mat, (bound_w, bound_h),\n                                borderMode=cv.BORDER_CONSTANT, borderValue=(255, 255, 255))\n    \n    if rect is not None:\n        (x, y), wh, a = rect\n        xy = np.array([x, y, 1]) @ rotation_mat.T\n        rect = tuple(xy), wh, 0\n        return rotated_mat, rect\n    \n    return rotated_mat","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def rect2points(rect, image_shape):\n    box = cv.boxPoints(rect)\n    box = np.int0(box)\n\n    tl = box.min(axis=0).clip(0)\n    br = box.max(axis=0).clip([0, 0], (image_shape[1], image_shape[0]))\n\n    return tl, br\n\n\ndef get_sub_image(image, rect):\n    (x1, y1), (x2, y2) = rect2points(rect, image.shape)\n    \n    sub_image = image[y1:y2, x1:x2]\n    if np.prod(sub_image.shape[:2]) < 10:\n        return None\n    \n    (x, y), wh, a = rect\n    if a == 0:\n        s = (sub_image != 255).sum()\n        total = np.prod(sub_image.shape[:2])\n        if s < 0.25 * total:\n            return None\n        \n        h, w = sub_image.shape[:2]\n        if h > w:\n            sub_image = np.rot90(sub_image)\n        return sub_image\n\n    xy = (x - x1), (y - y1)\n    rect = xy, wh, a\n    sub_image, rect = rotate_image(sub_image, a, rect)\n\n    return get_sub_image(sub_image, rect)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def get_minimal_image(image):\n    raw_image = image\n    if image.ndim > 2:\n        image = cv.cvtColor(image, cv.COLOR_RGB2GRAY)\n    image = (image != 255).astype(np.uint8)\n    image = cv.morphologyEx(image, cv.MORPH_CLOSE, cv.getStructuringElement(cv.MORPH_RECT, (10, 10)))\n    \n    countours, hierarchy = cv.findContours(image, cv.RETR_LIST, cv.CHAIN_APPROX_SIMPLE)\n    \n    angle = 0\n    prev_area = 0\n    min_rects = []\n    for c in countours:\n        rect = cv.minAreaRect(c)\n        cur_area = np.prod(rect[1])\n        if cur_area > prev_area:\n            prev_area = cur_area\n            angle = rect[-1]\n        min_rects.append(rect)\n    \n    height, width = image.shape[:2] # image shape has 3 dimensions\n    image_center = (width / 2, height / 2) # getRotationMatrix2D needs coordinates in reverse order (width, height) compared to shape\n\n    rotation_mat = cv.getRotationMatrix2D(image_center, angle, 1.)\n    \n    # rotation calculates the cos and sin, taking absolutes of those.\n    abs_cos = abs(rotation_mat[0,0]) \n    abs_sin = abs(rotation_mat[0,1])\n\n    # find the new width and height bounds\n    bound_w = int(height * abs_sin + width * abs_cos)\n    bound_h = int(height * abs_cos + width * abs_sin)\n    shift = np.array([bound_w, bound_h]) / 2\n    \n    # subtract old image center (bringing image back to origo) and adding the new image center coordinates\n    rotation_mat[0, 2] += bound_w / 2 - image_center[0]\n    rotation_mat[1, 2] += bound_h / 2 - image_center[1]\n    \n    result_shape = [0, 0]\n    \n    minimal_boxes = []\n    result_sub_images = []\n    \n    new_rectangles = []\n    for rect in min_rects:\n        (x, y), (w, h), a = rect\n        w += 5\n        h += 5\n        \n        sub_image = get_sub_image(raw_image, rect)\n        if sub_image is None:\n            continue\n        \n        minimal_boxes.append(rect)\n        result_shape[0] += sub_image.shape[0]\n        result_shape[1] = max(result_shape[1], sub_image.shape[1])\n        result_sub_images.append(sub_image)\n        \n        xy = np.array([x, y, 1]) @ rotation_mat.T\n        rect = ((xy[0], xy[1]), (w, h), a - angle)\n        box = cv.boxPoints(rect)\n        box = np.int0(box)\n        \n        tl = box.min(axis=0).clip(0)\n        br = box.max(axis=0).clip([0, 0], [bound_w, bound_h])\n        box = np.stack([tl, br])\n        \n        h = br[1] - tl[1]\n        w = br[0] - tl[0]\n        \n        new_rectangles.append(box)\n\n    offset = 0\n    result_img = np.full(result_shape + [3], 255, dtype=np.uint8)\n    for sub_img in result_sub_images:\n        h, w = sub_img.shape[:2]\n        result_img[offset:offset + h, :w] = sub_img\n        offset += h\n        \n    return result_img, minimal_boxes","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"names = [name for name in os.listdir(IMAGES)]\ncompact_representation = {}\n\nmean_ratio = 0\n\nfor name in tqdm(names):\n    img_path = os.path.join(IMAGES, name)\n\n    img = MultiImage(img_path)[-1]\n    \n    compact_image, minimal_boxes = get_minimal_image(img)\n    compact_representation[name] = {\"original_size\": img.shape[:2], \"rectangles\": minimal_boxes}\n    \n    mean_ratio += np.prod(compact_image.shape[:2]) / np.prod(img.shape[:2])\nprint(f\"Mean ratio: {mean_ratio / len(names)}\")","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"with open(\"compact_representation.json\", \"w\") as file:\n    json.dump(compact_representation, file)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def get_compact(image, compact_representation):\n    current_shape = image.shape[:2]\n    original_size = compact_representation[\"original_size\"]\n    \n    scale_h = current_shape[0] / original_size[0]\n    scale_w = current_shape[1] / original_size[1]\n    \n    boxes = compact_representation[\"rectangles\"]\n\n    result_shape = [0, 0]\n    for (x, y), (w, h), a in boxes:\n        w, h = (w * scale_w, h * scale_h)\n        if h > w:\n            w, h = h, w\n        shape = int(np.ceil(h)), int(np.ceil(w))\n        \n        result_shape[0] += int(shape[0])\n        result_shape[1] = max(result_shape[1], int(shape[1]))\n        \n    result_image = np.full(list(result_shape) + [3], 255, dtype=np.uint8)\n    \n    offset = 0\n    for box in boxes:\n        (x, y), (w, h), a = box\n        rect = (x * scale_w, y * scale_h), (w * scale_w, h * scale_h), a\n        sub_image = get_sub_image(image, rect)\n        result_image[offset:offset + sub_image.shape[0], :sub_image.shape[1]] = sub_image\n        offset += sub_image.shape[0]\n    \n    return result_image","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"names = sorted([name for name in os.listdir(IMAGES)])\n\nfor name in tqdm(names[:5]):\n    img_path = os.path.join(IMAGES, name)\n    img = tiff.imread(img_path)\n    \n    img2 = get_compact(img, compact_representation[name])\n    \n    h, w = img.shape[:2]\n    img = cv.resize(img, (w // 32, h // 32))\n    \n    h, w = img2.shape[:2]\n    img2 = cv.resize(img2, (w // 32, h // 32))\n    \n    print(img.shape, img2.shape)\n    \n    plt.figure()\n    \n    plt.subplot(121)\n    plt.imshow(img)\n    \n    plt.subplot(122)\n    plt.imshow(img2)\n    \n    plt.show()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"","execution_count":null,"outputs":[]}],"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"}},"nbformat":4,"nbformat_minor":4}