{"cells":[{"metadata":{},"cell_type":"markdown","source":"Hi! I found  [Iafoss kernel](https://www.kaggle.com/iafoss/panda-16x128x128-tiles) super useful for my experiments, but when we using it, tiles will have a random position in the final image, I found it weird. So I rewrite it and now, the final image most likely will consist of tiles in the original position, I mean that if tiles were together in the original image, they will be together in the final. The algorithm isn't ideal, but I tried.","execution_count":null},{"metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"cell_type":"code","source":"import os\nimport cv2\nimport skimage.io\nfrom tqdm.notebook import tqdm\nimport numpy as np\nfrom albumentations.core.transforms_interface import ImageOnlyTransform\nimport matplotlib.pyplot as plt\n","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"TRAIN = '../input/prostate-cancer-grade-assessment/train_images/'\nMASKS = '../input/prostate-cancer-grade-assessment/train_label_masks/'\nOUT_TRAIN = 'train.zip'\nOUT_MASKS = 'masks.zip'","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"names = [name[:-10] for name in os.listdir(MASKS)]\nimgs = []\nfor name in tqdm(names[:10]):\n    img = skimage.io.MultiImage(os.path.join(TRAIN,name+'.tiff'))[-1]\n    imgs.append(img)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"\nclass SplitAndConcatTilesMine(ImageOnlyTransform):\n    def __init__(\n        self, always_apply=False, p=1.0, tile_size=128, pad_value=255, tiles_in_final_image_size=(5, 5)\n    ):\n        super().__init__(always_apply, p)\n        self.tile_size = tile_size\n        self.pad_value = pad_value\n        self.tiles_in_final_image_size = tiles_in_final_image_size\n        self.resulted_tiles = int(self.tiles_in_final_image_size[0] * self.tiles_in_final_image_size[1])\n\n        self.need_reverse = True if self.pad_value == 0 else False\n\n    def pad_image(self, image):\n        height, width, channels = image.shape\n        pad_h, pad_w = self.tile_size - height % self.tile_size, self.tile_size - width % self.tile_size\n        res_size = (\n            self.tiles_in_final_image_size[0] * self.tile_size,\n            self.tiles_in_final_image_size[1] * self.tile_size,\n        )\n        if res_size[0] > height + pad_h:\n            pad_h = res_size[0] - height\n        if res_size[1] > width + pad_w:\n            pad_w = res_size[1] - width\n\n        padded_img = np.pad(image, [(pad_h, 0), (pad_w, 0), (0, 0)], \"constant\", constant_values=self.pad_value)\n        height_padded, width_padded, channels_padded = padded_img.shape\n\n        assert height_padded >= height\n        assert width_padded >= width\n        assert channels_padded >= channels\n        assert height_padded % self.tile_size == 0\n        assert width_padded % self.tile_size == 0\n        return padded_img, height_padded, width_padded\n\n    def cut_tiles(self, padded_img, height_padded, width_padded):\n        w_len, h_len = width_padded // self.tile_size, height_padded // self.tile_size\n        h_w_tile_storage = [[None for w in range(w_len)] for h in range(h_len)]\n        tiles = []\n        for h in range(h_len):\n            for w in range(w_len):\n                tile = padded_img[\n                    self.tile_size * h : self.tile_size * (h + 1), self.tile_size * w : self.tile_size * (w + 1)\n                ]\n                tile_intensivity = tile.sum()\n                h_w_tile_storage[h][w] = tile\n                tiles.append([tile, h, w, tile_intensivity])\n        sorted_tiles = sorted(tiles, key=lambda x: x[3], reverse=self.need_reverse)\n        return h_w_tile_storage, sorted_tiles\n\n    def constract_bin_matrix(self, sorted_tiles, height_padded, width_padded):\n        # fill bin_mask [intence_block_bool, height, width]\n        bin_mask = np.zeros((height_padded // self.tile_size, width_padded // self.tile_size, 3), dtype=int)\n        for i in range(self.resulted_tiles):\n            _, h, w, _ = sorted_tiles[i]\n            bin_mask[h][w][0] = 1\n            bin_mask[h][w][1] = h\n            bin_mask[h][w][2] = w\n        return bin_mask\n\n    def apply(self, image, **params):\n\n        padded_img, height_padded, width_padded = self.pad_image(image)\n\n        h_w_tile_storage, sorted_tiles = self.cut_tiles(padded_img, height_padded, width_padded)\n\n        bin_mask = self.constract_bin_matrix(sorted_tiles, height_padded, width_padded)\n\n        resulted_img = [\n            [None for _ in range(self.tiles_in_final_image_size[1])] for _ in range(self.tiles_in_final_image_size[0])\n        ]\n        region_of_interest = np.ones(self.tiles_in_final_image_size, dtype=bool)\n\n        most_intencivity = 1 # crunch for while loop\n        while most_intencivity > 0:\n            bin_mask, region_of_interest, resulted_img, most_intencivity = self.process_region(\n                bin_mask, region_of_interest, resulted_img\n            )\n\n        # deal with leftovers\n        bin_mask, resulted_img\n        bin_h, bin_w, _ = bin_mask.shape\n        for h in range(bin_h):\n            for w in range(bin_w):\n                if bin_mask[h][w][0] == 1:\n                    resulted_img = self.insert_value_in_res_im_array(resulted_img, bin_mask[h][w][1:].tolist())\n                    bin_mask[h][w][0] = 0\n\n        tiles_arr = [\n            [None for _ in range(self.tiles_in_final_image_size[1])] for _ in range(self.tiles_in_final_image_size[0])\n        ]\n        for h in range(self.tiles_in_final_image_size[0]):\n            for w in range(self.tiles_in_final_image_size[1]):\n                target_h, target_w = resulted_img[h][w]\n                tiles_arr[h][w] = h_w_tile_storage[target_h][target_w]\n\n        return np.hstack(np.hstack(np.array(tiles_arr)))\n\n    def get_transform_init_args_names(self):\n        return (\"tile_size\", \"pad_value\", \"tiles_in_final_image_size\",)\n\n    def insert_value_in_res_im_array(self, resulted_img, value):\n        for h in range(len(resulted_img)):\n            for w in range(len(resulted_img[0])):\n                if resulted_img[h][w] is None:\n                    resulted_img[h][w] = value\n                    return resulted_img\n\n    def process_region(self, bin_mask, region_of_interest, resulted_img):\n        # select_region\n        most_intensivity = 0\n        most_intensive_region = None\n\n        bin_mask_h, bin_mask_w, _ = bin_mask.shape\n        for h in range(bin_mask_h - self.tiles_in_final_image_size[0]):\n            for w in range(bin_mask_w - self.tiles_in_final_image_size[1]):\n                h_slice = slice(h, h + self.tiles_in_final_image_size[0])\n                w_slice = slice(w, w + self.tiles_in_final_image_size[1])\n                bin_tile = bin_mask[h_slice, w_slice]\n                intense = bin_tile[region_of_interest, 0].sum()\n                if intense > most_intensivity:\n                    most_intensivity = intense\n                    most_intensive_region = bin_tile\n                    most_intensive_region_slices = (h_slice, w_slice)\n        if most_intensivity > 0:\n            # fill resulted arr\n            new_region_of_interest = np.zeros(self.tiles_in_final_image_size, dtype=bool)\n            for h in range(self.tiles_in_final_image_size[0]):\n                for w in range(self.tiles_in_final_image_size[1]):\n                    interest = region_of_interest[h, w]\n                    if interest:\n                        is_filled_tile = most_intensive_region[h][w][0]\n                        if is_filled_tile:\n                            resulted_img[h][w] = most_intensive_region[h][w][1:].tolist()  # tolist important\n                        else:\n                            new_region_of_interest[h][w] = 1\n\n            # clean selected\n            bin_mask[most_intensive_region_slices][region_of_interest] = 0\n\n            return bin_mask, new_region_of_interest, resulted_img, most_intensivity\n        else:\n            return bin_mask, region_of_interest, resulted_img, most_intensivity\n","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"class SplitAndConcatTilesIafoss(ImageOnlyTransform):\n    def __init__(self, fake=None, always_apply=False, p=1.0, N=16):\n        super().__init__(always_apply, p)\n        self.fake = fake\n        self.N = N\n        self.buffer_size = np.round(self.N ** 0.5).astype(int)\n\n    def tile(self, img, N=16, sz=128):\n        shape = img.shape\n        pad0, pad1 = (sz - shape[0] % sz) % sz, (sz - shape[1] % sz) % sz\n        img = np.pad(img, [[pad0 // 2, pad0 - pad0 // 2], [pad1 // 2, pad1 - pad1 // 2], [0, 0]], constant_values=0)\n\n        img = img.reshape(img.shape[0] // sz, sz, img.shape[1] // sz, sz, 3)\n        img = img.transpose(0, 2, 1, 3, 4).reshape(-1, sz, sz, 3)\n        if len(img) < N:\n            img = np.pad(img, [[0, N - len(img)], [0, 0], [0, 0], [0, 0]], constant_values=0)\n        idxs = np.argsort(img.reshape(img.shape[0], -1).sum(-1))[-N:]\n        img = img[idxs]\n\n        return img\n\n    def apply(self, image: np.array, **params) -> np.array:\n        tiles = self.tile(image, N=self.N)\n        horizons = []\n        buffer = []\n        for t in tiles:\n            buffer.append(t)\n            if len(buffer) == self.buffer_size:\n                horizons.append(np.vstack(buffer))\n                buffer = []\n\n        res = np.hstack(horizons)\n        return res\n\n    def get_transform_init_args_names(self):\n        return (\"fake\",)\n\ndef display_images(images):\n    '''\n    This function takes in input a list of images. It then iterates through the image making openslide objects , on which different functions\n    for getting out information can be called later\n    source: https://www.kaggle.com/tanulsingh077/prostate-cancer-in-depth-understanding-eda-model\n    '''\n    mine_tiler = SplitAndConcatTilesMine()\n    iafoss_tiles = SplitAndConcatTilesIafoss()\n    f, ax = plt.subplots(len(images), 3, figsize=(18, 22))\n    for i, image in enumerate(images):\n        ax[i, 0].imshow(image) #Displaying Image\n        \n        tiled_image_iafoss = iafoss_tiles(image=255-image)[\"image\"] # notice, my implementation works with invertd images\n        ax[i, 1].imshow(255-tiled_image_iafoss)\n        \n        tiled_image_mine = mine_tiler(image=image)[\"image\"]\n        ax[i, 2].imshow(tiled_image_mine) #Displaying Image\n    \n    ax[0, 0].set_title(f\"Default\")\n    ax[0, 1].set_title(f\"Iafoss\")\n    ax[0, 2].set_title(f\"Mine\")\n\n    plt.show() ","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"display_images(imgs)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"P.S. Algorithm is kinda complicated, so I didn't apply code optimization for keeping code as simple as possible, on my hardware this method didn't slow down my pipeline, if you have speed problems, I think the easiest way, is applying numba.jit to process_region cause it's for loops over NumPy arrays.","execution_count":null}],"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}