{"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_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"Here is my best, almost working notebook, for the Vesuvius Challenge in early 2023. I was having some memory issue, but was able to get through a couple iterations and was finally working on implementing the Albumentations library, trying some new architectures and pre-trained models, and test-time augmentations to improve my score.\n\nThis competition was definitely the coolest problem I worked on, and between figuring out batch and patch sizes, buffers, loss functions, and the whole 3D thing, I learned a ton more about CNNs, pytorch, image processing, and segmentation problems.\n\nA lot of this notebook is bits and pieces of code from others, but I really wanted to experiment with model architectures and transformations on my own, hence the memory difficulties.","metadata":{}},{"cell_type":"code","source":"# from example\nimport os\nimport gc\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nimport numpy as np\nimport glob\nimport PIL.Image as Image\nimport torch.utils.data as data\nimport matplotlib.pyplot as plt\nimport matplotlib.patches as patches\nfrom tqdm import tqdm\nfrom ipywidgets import interact, fixed\nfrom sklearn.metrics import fbeta_score\nfrom torchvision.transforms import functional as TF\nfrom torchvision.transforms import RandomCrop, ToPILImage, Pad, GaussianBlur\nimport torch.nn.functional as F\nfrom torch.nn.parameter import Parameter\nfrom torch.nn.modules import activation\nimport albumentations as A\nimport random\nimport cv2\nimport torchvision.transforms as T\n\n\n# try smaller buffer -- 28 taregt slice\nBUFFER = 0  # Buffer size in x and y direction, how big should the sub-images be? BUFFER * 2 + PATH_SIZE\nZ_START = 22 # First slice in the z direction to use\nZ_DIM = 8   # Number of slices in the z direction, only 8 works now\nZ_SKIP = 2\n\nTRAINING_STEPS = 10000 # number of patches to generate\nLEARNING_RATE = 0.05\nBATCH_SIZE = 8 # \nPATCH_SIZE = 64 # n xn prediction\n\n# use gpu if available\nDEVICE = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Other Modules","metadata":{}},{"cell_type":"code","source":"# !pip install git+https://github.com/qubvel/segmentation_models.pytorch\n\nimport sys\n\nsys.path.append('/kaggle/input/segmentation-models-pytorch')\nsys.path.append('/kaggle/input/pretrainedmodels')\nsys.path.append('/kaggle/input/efficientnet')\nsys.path","metadata":{"execution":{"iopub.status.busy":"2023-05-18T19:23:49.223989Z","iopub.execute_input":"2023-05-18T19:23:49.224339Z","iopub.status.idle":"2023-05-18T19:23:49.235033Z","shell.execute_reply.started":"2023-05-18T19:23:49.224308Z","shell.execute_reply":"2023-05-18T19:23:49.234075Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import segmentation_models_pytorch as smp\nimport efficientnet_pytorch as efficientnet","metadata":{"execution":{"iopub.status.busy":"2023-05-18T19:23:49.236558Z","iopub.execute_input":"2023-05-18T19:23:49.237155Z","iopub.status.idle":"2023-05-18T19:23:51.770545Z","shell.execute_reply.started":"2023-05-18T19:23:49.237124Z","shell.execute_reply":"2023-05-18T19:23:51.769627Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# dataset class\n# BUFFER\nclass TrainMPDataset(data.Dataset):\n    \n    def __init__(self, # which fragments to use\n                 patch_size, # 16 x 16\n                 val_rect, # bounds of validation rectangle\n                 val_frag_num,\n                 transform = None,\n                 fragments = (1, 2, 3)): # fragment number used\n\n        self.fragments = fragments\n        # load stuff in a tuple? nooo, create in fun\n        self.patch_size = patch_size\n        \n        self.rect = val_rect\n        \n        self.val_frag_num = val_frag_num\n        \n        self.transform = transform\n        # load in all images\n        print(\"Loading images\")\n        self.img_1, self.img_2, self.img_3 = self.__get_images__(1), self.__get_images__(2), self.__get_images__(3)\n        \n        # load in all masks\n        print(\"Loading masks\")\n        self.mask_1, self.mask_2, self.mask_3 = self.__get_mask__(1), self.__get_mask__(2), self.__get_mask__(3)\n        \n        # load in all labels\n        print(\"Loading labels\")\n        self.label_1, self.label_2, self.label_3 = self.__get_label__(1), self.__get_label__(2), self.__get_label__(3)\n\n    def __len__(self):\n        \n        return TRAINING_STEPS * BATCH_SIZE\n        \n    def __getitem__(self, index):\n        \n        # random fragement choice\n        f = random.choice(self.fragments)\n        \n        img, mask, label = self.__select_fragment__(f)\n        \n        # random subvolumes\n        img, label = self.random_subvolumes(img, mask, label)\n            \n        # optional transform\n        if self.transform is not None:\n            img = self.transform(img)\n            \n        # reformat and return\n        return img.view(1, Z_DIM, self.patch_size + BUFFER*2, self.patch_size + BUFFER*2), label.view(1, self.patch_size, self.patch_size)\n    \n#     def rotate_image_label(self, img, label, discrete = True):\n        \n#         # select angle from distribution\n#         if discrete == False:\n#             rotation_angle = random.choice(range(360))\n#         else:\n#             rotation_angle = random.choice([0, 90, 180, 270])\n#                  # THIS MAY BE WRONGGGG\n#         img, label = TF.rotate(img.squeeze(1), rotation_angle), TF.rotate(label.unsqueeze(0), rotation_angle)\n                \n#         return img.unsqueeze(1), label\n        \n    def random_subvolumes(self, img, mask, label, in_mask_cutoff = 0.1):\n        \n        # For testing, we won't show the model any patches outisde of the mask\n        # so its better if we omit those from training as well\n        # plus, itll speed up training and prediction\n        \n        # Most patches are gonna be completely in mask or completely out\n        \n        m = 0.0\n        \n        # with at least cutoff% in mask\n        while m < in_mask_cutoff:\n            \n            top, left, height, width = RandomCrop.get_params(mask, output_size=(self.patch_size, self.patch_size))\n            \n            m = torch.mean(TF.crop(mask, top, left, height, width).float())\n        \n        # add buffer to img crop\n        subvolume, label = TF.crop(img, top - BUFFER, left - BUFFER, height + BUFFER*2, width + BUFFER*2), TF.crop(label, top, left, height, width) \n        \n        return subvolume, label\n\n    def __get_mask__(self, fragnum):\n        \n        prefix = '/kaggle/input/vesuvius-challenge-ink-detection/train/' + str(fragnum) + '/'\n\n        # applying a 'dither'conversion to the mask?\n        mask = np.array(Image.open(prefix+\"mask.png\").convert('1'))\n        \n        # change mask of validation data\n        if fragnum == self.val_frag_num:\n            \n            mask[self.rect[1]:self.rect[1]+self.rect[3]+1, self.rect[0]:self.rect[0]+self.rect[2]+1] = False\n        \n        return torch.Tensor(mask)\n\n    def __get_label__(self, fragnum):\n        \n        prefix = '/kaggle/input/vesuvius-challenge-ink-detection/train/' + str(fragnum) + '/'\n        \n        # pixel-perfect label, return if > 0\n        label = torch.from_numpy(np.array(Image.open(prefix+\"inklabels.png\"))).gt(0).float().to(DEVICE)\n        \n        return label\n\n    def __get_images__(self, fragnum):\n        \n        prefix = '/kaggle/input/vesuvius-challenge-ink-detection/train/' + str(fragnum) + '/'\n        # list of np arrays\n        images = [np.array(Image.open(filename), dtype=np.float32)/65535.0 for filename in tqdm(sorted(glob.glob(prefix+\"surface_volume/*.tif\"))[Z_START:Z_START + (Z_DIM * Z_SKIP):Z_SKIP])]\n\n        # build tensor stack on 0 dimension\n        image_stack = torch.stack([torch.from_numpy(image) for image in images], dim=0).to(DEVICE)\n    \n        return image_stack\n    \n    def __select_fragment__(self, f):\n        \n        # simple switcher\n        if f == 1:\n            return self.img_1, self.mask_1, self.label_1\n        elif f == 2:\n            return self.img_2, self.mask_2, self.label_2\n        elif f == 3:\n            return self.img_3, self.mask_3, self.label_3","metadata":{"execution":{"iopub.status.busy":"2023-05-18T19:23:51.773357Z","iopub.execute_input":"2023-05-18T19:23:51.774740Z","iopub.status.idle":"2023-05-18T19:23:51.811484Z","shell.execute_reply.started":"2023-05-18T19:23:51.774714Z","shell.execute_reply":"2023-05-18T19:23:51.810468Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# dataset store on cpu, use albumentations\n# BUFFER\nclass TrainMPDatasetV2(data.Dataset):\n    \n    def __init__(self, # which fragments to use\n                 patch_size, # 16 x 16\n                 val_rect, # bounds of validation rectangle\n                 val_frag_num,\n                 transform = None,\n                 fragments = (1, 2, 3)): # fragment number used\n\n        self.fragments = fragments\n        # load stuff in a tuple? nooo, create in fun\n        self.patch_size = patch_size\n        \n        self.rect = val_rect\n        \n        self.val_frag_num = val_frag_num\n        \n        self.transform = transform\n        # load in all images\n        print(\"Loading images\")\n        \n        if 1 in fragments:\n            self.img_1, self.mask_1, self.label_1 = self.__get_images__(1), self.__get_mask__(1), self.__get_label__(1)\n            gc.collect()\n        \n        print(\"Loading fragment 2\")\n        \n        if 2 in fragments:\n            self.img_2, self.mask_2, self.label_2 = self.__get_images__(2), self.__get_mask__(2), self.__get_label__(2)\n\n            print(\"Loading fragment 3\")\n        if 3 in fragments:\n            self.img_3, self.mask_3, self.label_3 = self.__get_images__(3), self.__get_mask__(3), self.__get_label__(3)\n\n            \n    def __len__(self):\n        \n        return TRAINING_STEPS * BATCH_SIZE\n    \n    def __getitem__(self, index):\n        \n        # random fragement choice\n        f = random.choice(self.fragments)\n        \n        img, mask, label = self.__select_fragment__(f)\n        \n        # random subvolumes\n        img, label = self.random_subvolumes(img, mask, label)\n        \n        # add in transforms\n        if self.transform is not None:\n            transformed = A.Compose(self.transform)(image=img, mask=label) # 'label' is mask in this case\n            img = transformed[\"image\"]\n            label = transformed[\"mask\"]\n            \n        # reformat and return\n        return img.view(1, Z_DIM, self.patch_size + BUFFER*2, self.patch_size + BUFFER*2), label.view(1, self.patch_size, self.patch_size)\n        # return img, label\n    \n#     def rotate_image_label(self, img, label, discrete = True):\n        \n#         # select angle from distribution\n#         if discrete == False:\n#             rotation_angle = random.choice(range(360))\n#         else:\n#             rotation_angle = random.choice([0, 90, 180, 270])\n#                  # THIS MAY BE WRONGGGG\n#         img, label = TF.rotate(img.squeeze(1), rotation_angle), TF.rotate(label.unsqueeze(0), rotation_angle)\n                \n#         return img.unsqueeze(1), label\n        \n    def random_subvolumes(self, img, mask, label, in_mask_cutoff = 0.1):\n        \n        # For testing, we won't show the model any patches outisde of the mask\n        # so its better if we omit those from training as well\n        # plus, itll speed up training and prediction\n        \n        # Most patches are gonna be completely in mask or completely out\n        \n        m = 0.0\n        \n        # with at least cutoff% in mask\n        while m < in_mask_cutoff:\n            \n            # simple randn for coords\n            y, x = A.RandomCrop(height=PATCH_SIZE, width=PATCH_SIZE).get_params().values()\n            \n            # get crop params, offset buy PATCH_SIZE\n            top, left, height, width = int(y * (mask.shape[0] - PATCH_SIZE)), int(x * (mask.shape[1] - PATCH_SIZE)), PATCH_SIZE, PATCH_SIZE\n            \n            c = A.Crop(x_min = left, y_min = top, x_max = left + width, y_max = top + height)(image = mask)[\"image\"]\n            \n            m = np.mean(c)\n        \n        # add buffer to img crop\n        subvolume = A.Crop(x_min = left, y_min = top, x_max = left + width + (BUFFER * 2), y_max = top + height + (BUFFER * 2))(image = img)[\"image\"]\n        \n        label = A.Crop(x_min = left, y_min = top, x_max = left + width, y_max = top + height)(image = label)[\"image\"]\n        \n        return subvolume, label\n        \n    def __get_mask__(self, fragnum):\n        \n        prefix = '/kaggle/input/vesuvius-challenge-ink-detection/train/' + str(fragnum) + '/'\n\n        # applying a 'dither'conversion to the mask?\n        mask = np.array(Image.open(prefix+\"mask.png\").convert('1'))\n        \n        # change mask of validation data\n        if fragnum == self.val_frag_num:\n            \n            mask[self.rect[1]:self.rect[1]+self.rect[3]+1, self.rect[0]:self.rect[0]+self.rect[2]+1] = False\n        \n        return mask\n\n    def __get_label__(self, fragnum):\n        \n        prefix = '/kaggle/input/vesuvius-challenge-ink-detection/train/' + str(fragnum) + '/'\n        \n        # pixel-perfect label, return if > 0\n        label = np.array(Image.open(prefix+\"inklabels.png\"), dtype=np.float32)\n        \n        return label\n\n    def __get_images__(self, fragnum):\n        \n        prefix = '/kaggle/input/vesuvius-challenge-ink-detection/train/' + str(fragnum) + '/'\n        # list of np arrays\n        images = [np.pad(np.asarray(Image.open(filename), dtype=np.float32), (BUFFER, BUFFER))/65535 for filename in tqdm(sorted(glob.glob(prefix+\"surface_volume/*.tif\"))[Z_START:Z_START + (Z_DIM * Z_SKIP):Z_SKIP])]\n        \n        # build tensor stack on 0 dimension\n        image = None\n        \n        image_stack = np.stack([image for image in images], axis=0)\n        \n        # to weird numpy dimension order\n        return np.transpose(image_stack, (1, 2, 0))\n    \n    def __select_fragment__(self, f):\n        \n        # simple switcher\n        if f == 1:\n            return self.img_1, self.mask_1, self.label_1\n        elif f == 2:\n            return self.img_2, self.mask_2, self.label_2\n        elif f == 3:\n            return self.img_3, self.mask_3, self.label_3","metadata":{"execution":{"iopub.status.busy":"2023-05-18T19:23:51.812991Z","iopub.execute_input":"2023-05-18T19:23:51.813627Z","iopub.status.idle":"2023-05-18T19:23:51.853748Z","shell.execute_reply.started":"2023-05-18T19:23:51.813594Z","shell.execute_reply":"2023-05-18T19:23:51.852765Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# dataset store on gpu, use albumentations\nclass TrainMPDatasetV3(data.Dataset):\n    \n    def __init__(self, # which fragments to use\n                 patch_size, # 16 x 16\n                 val_rect, # bounds of validation rectangle\n                 val_frag_num,\n                 transform = None,\n                 fragments = (1, 2, 3)): # fragment number used\n\n        self.fragments = fragments\n        # load stuff in a tuple? nooo, create in fun\n        self.patch_size = patch_size\n        \n        self.rect = val_rect\n        \n        self.val_frag_num = val_frag_num\n        \n        self.transform = transform\n        # load in all images\n        print(\"Loading images\")\n        \n        if 1 in fragments:\n            self.img_1, self.mask_1, self.label_1 = self.__get_images__(1), self.__get_mask__(1), self.__get_label__(1)\n        \n        print(\"Loading fragment 2\")\n        \n        if 2 in fragments:\n            self.img_2, self.mask_2, self.label_2 = self.__get_images__(2), self.__get_mask__(2), self.__get_label__(2)\n\n            print(\"Loading fragment 3\")\n        if 3 in fragments:\n            self.img_3, self.mask_3, self.label_3 = self.__get_images__(3), self.__get_mask__(3), self.__get_label__(3)\n\n    def __len__(self):\n        \n        return TRAINING_STEPS * BATCH_SIZE\n        \n    def __getitem__(self, index):\n        \n        # random fragement choice\n        f = random.choice(self.fragments)\n        \n        img, mask, label = self.select_fragment(f)\n        \n        # random subvolumes\n        img, label = self.random_subvolumes(img, mask, label)\n            \n        # optional transform\n        if self.transform is not None:\n            \n            # to cpu and numpy\n            img, label = np.asarray(img.cpu()), np.asarray(label.cpu())\n            \n            # reorder channels for album\n            img = np.transpose(img, (1, 2, 0))\n            \n            t = self.transform(image = img, mask = label)\n            img = t[\"image\"]\n            label = t[\"mask\"]\n            \n        # reformat and return\n        return img.view(1, Z_DIM, self.patch_size + BUFFER*2, self.patch_size + BUFFER*2), label.view(1, self.patch_size, self.patch_size)\n    \n    def random_subvolumes(self, img, mask, label, in_mask_cutoff = 0.1):\n        \n        # For testing, we won't show the model any patches outisde of the mask\n        # so its better if we omit those from training as well\n        # plus, itll speed up training and prediction\n        \n        # Most patches are gonna be completely in mask or completely out\n        \n        m = 0.0\n        \n        # with at least cutoff% in mask\n        while m < in_mask_cutoff:\n            \n            top, left, height, width = RandomCrop.get_params(mask, output_size=(self.patch_size, self.patch_size))\n            \n            m = torch.mean(TF.crop(mask, top, left, height, width).float())\n        \n        # add buffer to img crop\n        subvolume, label = TF.crop(img, top - BUFFER, left - BUFFER, height + BUFFER*2, width + BUFFER*2), TF.crop(label, top, left, height, width) \n        \n        return subvolume, label\n\n    def __get_mask__(self, fragnum):\n        \n        prefix = '/kaggle/input/vesuvius-challenge-ink-detection/train/' + str(fragnum) + '/'\n\n        # applying a 'dither'conversion to the mask?\n        mask = np.array(Image.open(prefix+\"mask.png\").convert('1'))\n        \n        # change mask of validation data\n        if fragnum == self.val_frag_num:\n            \n            mask[self.rect[1]:self.rect[1]+self.rect[3]+1, self.rect[0]:self.rect[0]+self.rect[2]+1] = False\n        \n        return torch.Tensor(mask)\n\n    def __get_label__(self, fragnum):\n        \n        prefix = '/kaggle/input/vesuvius-challenge-ink-detection/train/' + str(fragnum) + '/'\n        \n        # pixel-perfect label, return if > 0\n        label = torch.from_numpy(np.array(Image.open(prefix+\"inklabels.png\"))).gt(0).float().to(DEVICE)\n        \n        return label\n\n    def __get_images__(self, fragnum):\n        \n        prefix = '/kaggle/input/vesuvius-challenge-ink-detection/train/' + str(fragnum) + '/'\n        # list of np arrays\n        images = [np.array(Image.open(filename), dtype=np.float32)/65535.0 for filename in tqdm(sorted(glob.glob(prefix+\"surface_volume/*.tif\"))[Z_START:Z_START + (Z_DIM * Z_SKIP):Z_SKIP])]\n\n        # build tensor stack on 0 dimension\n        image_stack = torch.stack([torch.from_numpy(image) for image in images], dim=0).to(DEVICE)\n    \n        return image_stack\n    \n    def select_fragment(self, f):\n        \n        # simple switcher\n        if f == 1:\n            return self.img_1, self.mask_1, self.label_1\n        elif f == 2:\n            return self.img_2, self.mask_2, self.label_2\n        elif f == 3:\n            return self.img_3, self.mask_3, self.label_3","metadata":{"execution":{"iopub.status.busy":"2023-05-18T19:23:51.857013Z","iopub.execute_input":"2023-05-18T19:23:51.859615Z","iopub.status.idle":"2023-05-18T19:23:51.896097Z","shell.execute_reply.started":"2023-05-18T19:23:51.859581Z","shell.execute_reply":"2023-05-18T19:23:51.895293Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# dataset class\nclass TestMPDataset(data.Dataset):\n    \n    def __init__(self, frag_prefix, \n                 patch_size, \n                 val = False, \n                 val_rect = None): # no pixels\n        \n        self.frag_prefix = frag_prefix\n        \n        # pad to make divisible by patch_size\n        self.patch_size = patch_size\n        \n        self.val = val\n        \n        self.rect = val_rect\n        \n        # pad images to patch_size, and include buffer on the edge\n        self.image_stack = self.__get_images__()\n        self.image_stack_padding = self.__fragment_padding__(self.image_stack, include_buffer = True) # with buffer for images\n        self.image_stack = Pad(self.image_stack_padding)(self.image_stack)\n        \n        # pad mask\n        self.mask = self.__get_mask__()\n        self.mask_padding = self.__fragment_padding__(self.mask)\n        self.mask = Pad(self.mask_padding)(self.mask)\n        \n        # pad label for eval set\n        # return zeroes if testing\n        if val == True:\n            self.label = self.__get_label__()\n            self.label_padding = self.__fragment_padding__(self.label)\n            self.label = Pad(self.label_padding)(self.label)\n        else: \n            self.label = torch.zeros_like(self.mask).to(DEVICE)\n            \n        # list of coordinates to valid patches\n        self.patch_coords = self.__get_patch_coords__()\n        \n    def __len__(self):\n        \n        return len(self.patch_coords)\n        \n    def __getitem__(self, index):\n        \n        ypatch, xpatch = self.patch_coords[index]\n        \n        # random rotate\n        img, label = self.__split_subvolumes__(ypatch, xpatch)\n        \n        img, label = img.view(1, Z_DIM, self.patch_size + BUFFER*2, self.patch_size + BUFFER*2), label.view(1, self.patch_size, self.patch_size)\n        \n        return img, label, (ypatch, xpatch) \n        \n    def __split_subvolumes__(self, ypatch, xpatch):\n        \n        labely, labelx = ypatch * self.patch_size, xpatch * self.patch_size\n        \n        label = self.label[labely:labely + self.patch_size, labelx:labelx + self.patch_size]\n        \n        # since we add padding, need to correct and add BUFFER back to coordinates\n        subvolume = self.image_stack[:, labely: labely + self.patch_size + BUFFER * 2, labelx: labelx + self.patch_size + BUFFER * 2]\n        \n        return subvolume, label\n        \n    # pads last two dimensions to make it suitable for feeding into model\n    def __fragment_padding__(self, x, include_buffer = False):\n        \n        if type(x) == np.ndarray:\n            x = torch.Tensor(x * 1)\n            \n        # list of padding dimensions\n        padding = [0, 0, 0, 0]\n        \n        # top and bottom padding\n        p1 = self.patch_size - (x.shape[-2] % self.patch_size)\n        \n        if x.shape[-2] % 2 == False:\n            padding[1], padding[3] = p1 // 2, p1 // 2\n        else:\n            padding[1], padding[3] = p1 // 2, (p1 // 2) + 1\n        \n        # left and right padding\n        p2 = self.patch_size - (x.shape[-1] % self.patch_size)\n        \n        if x.shape[-1] % 2 == False:\n            padding[0], padding[2] = p2 // 2, p2 // 2\n        else:\n            padding[0], padding[2] = p2 // 2, (p2 // 2) + 1\n            \n        #  add buffer to image stack padding so we don't go out of bounds\n        if include_buffer == True:\n            padding = [p + BUFFER for p in padding]\n            \n        return padding\n        \n    # get indexes of patches with at least one mask pixel\n    def __get_patch_coords__(self):\n        \n        mask = self.mask\n        \n        idx_list = []\n        \n        # unfold tensor to get even patches of 16 after padding\n        unfolded = mask.unfold(0, self.patch_size, self.patch_size).unfold(1, self.patch_size, self.patch_size)\n        \n        # for all possible patch coords in mask\n        for y, x in [(y, x) for y in range(unfolded.shape[0]) for x in range(unfolded.shape[1])]:\n            \n            # only keep if has one positive pixel\n            if torch.sum(unfolded[y, x, :, :]) > 0:\n                \n                idx_list.append((y, x))\n                \n        return idx_list\n    \n    def __get_mask__(self):\n        \n        # applying a 'dither'conversion to the mask?\n        mask = np.array(Image.open(self.frag_prefix+\"mask.png\").convert('1'))\n            \n        # if validation set, trim mask to rect size\n        if self.val:\n            mask = np.zeros(mask.shape, dtype=bool)\n            mask[self.rect[1]:self.rect[1]+self.rect[3]+1, self.rect[0]:self.rect[0]+self.rect[2]+1] = True\n        \n        return torch.Tensor(mask)\n\n    def __get_label__(self):\n                \n        # pixel-perfect label\n        label = torch.from_numpy(np.array(Image.open(self.frag_prefix+\"inklabels.png\"))).gt(0).float().to(DEVICE)\n        \n        return label\n\n    def __get_images__(self):\n        \n        # list of np arrays\n        images = [np.array(Image.open(filename), dtype=np.float32)/65535.0 for filename in tqdm(sorted(glob.glob(self.frag_prefix+\"surface_volume/*.tif\"))[Z_START:Z_START + (Z_DIM * Z_SKIP):Z_SKIP])]\n\n        # build tensor stack on 0 dimension\n        image_stack = torch.stack([torch.from_numpy(image) for image in images], dim=0).to(DEVICE)\n    \n        return image_stack\n    \n    def view_patch(idx):\n        output = np.asarray(self.__getitem__(idx).to(\"cpu\"), dtype = bool)","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2023-05-18T19:23:51.897594Z","iopub.execute_input":"2023-05-18T19:23:51.898211Z","iopub.status.idle":"2023-05-18T19:23:51.939263Z","shell.execute_reply.started":"2023-05-18T19:23:51.898159Z","shell.execute_reply":"2023-05-18T19:23:51.938333Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"Looking at the E\n# change the exact coords to have it line up with our patch grid\nrect = (3300//PATCH_SIZE  * PATCH_SIZE, 1600 // PATCH_SIZE * PATCH_SIZE, 900 // PATCH_SIZE * PATCH_SIZE, 800 // PATCH_SIZE * PATCH_SIZE)\nfig, ax = plt.subplots()\n\nax.imshow(Image.open('/kaggle/input/vesuvius-challenge-ink-detection/train/1/'+\"ir.png\"), cmap=\"gray\")\npatch = patches.Rectangle((rect[0], rect[1]), rect[2], rect[3], linewidth=2, edgecolor='r', facecolor='none')\nax.add_patch(patch)\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2023-05-18T19:23:51.940910Z","iopub.execute_input":"2023-05-18T19:23:51.941556Z","iopub.status.idle":"2023-05-18T19:23:54.379725Z","shell.execute_reply.started":"2023-05-18T19:23:51.941525Z","shell.execute_reply":"2023-05-18T19:23:54.378865Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Transforms","metadata":{}},{"cell_type":"code","source":"from albumentations.pytorch import ToTensorV2\n\n# train_aug_list = [\n#         # A.RandomResizedCrop(\n#         #     size, size, scale=(0.85, 1.0)),\n#         # A.Resize(size, size), already getting crop\n#         A.HorizontalFlip(p=0.3),\n#         A.VerticalFlip(p=0.3),\n#         A.RandomBrightnessContrast(p=0.6),\n#         A.ShiftScaleRotate(p=0.75),\n#         A.OneOf([\n#                 A.GaussNoise(var_limit=[10, 50]),\n#                 A.GaussianBlur(),\n#                 A.MotionBlur(),\n#                 ], p=0.4),\n#         A.GridDistortion(num_steps=5, distort_limit=0.3, p=0.5),\n#         A.CoarseDropout(max_holes=1, max_width=int(PATCH_SIZE * 0.3), max_height=int(PATCH_SIZE * 0.3), \n#                         mask_fill_value=0, p=0.5),\n#         # A.Cutout(max_h_size=int(size * 0.6),\n#         #          max_w_size=int(size * 0.6), num_holes=1, p=1.0),\n# #         A.Normalize(\n# #             mean= [0] * Z_DIM,\n# #             std= [1] * Z_DIM),\n#         ToTensorV2(transpose_mask=True),\n#     ]\n\n# valid_aug_list = [\n#     # A.Resize(size, size),\n#     A.Normalize(\n#         mean= [0] * Z_DIM,\n#         std= [1] * Z_DIM\n#     ),\n#     ToTensorV2(transpose_mask=True),\n# ]","metadata":{"execution":{"iopub.status.busy":"2023-05-18T19:23:54.380929Z","iopub.execute_input":"2023-05-18T19:23:54.381266Z","iopub.status.idle":"2023-05-18T19:23:54.389694Z","shell.execute_reply.started":"2023-05-18T19:23:54.381237Z","shell.execute_reply":"2023-05-18T19:23:54.388694Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"album_transform = A.Compose([\n    # A.RandomCrop(width=PATCH_SIZE, height=PATCH_SIZE),\n    A.HorizontalFlip(p=0.15),\n    A.VerticalFlip(p=0.15),\n    A.RandomBrightnessContrast(p=0.5, brightness_limit=0.10, contrast_limit=0.10),\n    # A.ShiftScaleRotate(p=0.75),\n    A.OneOf([\n                A.GaussNoise(var_limit=[10, 50]),\n                A.GaussianBlur()\n                #A.MotionBlur(),\n                ], p=0.4),\n    \n    # A.GridDistortion(num_steps=5, distort_limit=0.3, p=0.5),\n    A.CoarseDropout(max_holes=1, max_width=int(0.1 * PATCH_SIZE), max_height=int(0.1 * PATCH_SIZE), \n                mask_fill_value=0, p=0.5),\n    \n    ToTensorV2(transpose_mask=True)\n])","metadata":{"execution":{"iopub.status.busy":"2023-05-18T19:23:54.394043Z","iopub.execute_input":"2023-05-18T19:23:54.394579Z","iopub.status.idle":"2023-05-18T19:23:54.401768Z","shell.execute_reply.started":"2023-05-18T19:23:54.394546Z","shell.execute_reply":"2023-05-18T19:23:54.400932Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"tviz_transform = torch.nn.Sequential(\n#     T.RandomHorizontalFlip(p=0.5),\n#     T.RandomVerticalFlip(p=0.5),\n    T.RandomAutocontrast(p=0.5),\n    T.GaussianBlur(kernel_size = 5, sigma=(0.0001, 0.5)),\n    T.Normalize((0.485, 0.456, 0.406), (0.229, 0.224, 0.225)),\n)","metadata":{"execution":{"iopub.status.busy":"2023-05-18T19:23:54.404002Z","iopub.execute_input":"2023-05-18T19:23:54.404566Z","iopub.status.idle":"2023-05-18T19:23:54.411504Z","shell.execute_reply.started":"2023-05-18T19:23:54.404534Z","shell.execute_reply":"2023-05-18T19:23:54.410817Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"To numpy shape HWC","metadata":{}},{"cell_type":"markdown","source":"How do i do all these mutations on cuda?","metadata":{}},{"cell_type":"markdown","source":"## Define Datasets","metadata":{}},{"cell_type":"code","source":"train_dataset = TrainMPDatasetV3(patch_size = PATCH_SIZE, \n                                 val_rect = rect, \n                                 val_frag_num = 1, \n                                 transform = album_transform, \n                                 fragments = (1, 2, 3))\n\neval_dataset = TestMPDataset('/kaggle/input/vesuvius-challenge-ink-detection/train/1/',\n                            patch_size = PATCH_SIZE,\n                            val = True,\n                            val_rect = rect)\n# test_dataset = TestMPDataset('/kaggle/input/vesuvius-challenge-ink-det')","metadata":{"execution":{"iopub.status.busy":"2023-05-18T19:23:54.412655Z","iopub.execute_input":"2023-05-18T19:23:54.413224Z","iopub.status.idle":"2023-05-18T19:25:29.462340Z","shell.execute_reply.started":"2023-05-18T19:23:54.413172Z","shell.execute_reply":"2023-05-18T19:25:29.461408Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train_loader = data.DataLoader(train_dataset, batch_size = BATCH_SIZE, shuffle=False) # no need to shuffle","metadata":{"execution":{"iopub.status.busy":"2023-05-18T19:25:29.463609Z","iopub.execute_input":"2023-05-18T19:25:29.463958Z","iopub.status.idle":"2023-05-18T19:25:29.469689Z","shell.execute_reply.started":"2023-05-18T19:25:29.463927Z","shell.execute_reply":"2023-05-18T19:25:29.468780Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"gc.collect()\ntorch.cuda.empty_cache()","metadata":{"execution":{"iopub.status.busy":"2023-05-18T19:25:29.470949Z","iopub.execute_input":"2023-05-18T19:25:29.471653Z","iopub.status.idle":"2023-05-18T19:25:29.680871Z","shell.execute_reply.started":"2023-05-18T19:25:29.471621Z","shell.execute_reply":"2023-05-18T19:25:29.679797Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"eval_loader = data.DataLoader(eval_dataset, batch_size = BATCH_SIZE, shuffle=False)","metadata":{"execution":{"iopub.status.busy":"2023-05-18T19:25:29.682421Z","iopub.execute_input":"2023-05-18T19:25:29.682809Z","iopub.status.idle":"2023-05-18T19:25:29.693057Z","shell.execute_reply.started":"2023-05-18T19:25:29.682776Z","shell.execute_reply":"2023-05-18T19:25:29.692098Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# check speed\nimport time\nst = time.time()\nits = 0\nfor i in range(100):\n    st = time.time()\n    next(iter(train_loader))\n    its += 1/(time.time() - st)\n\nprint(\"Average it/s from loader: \" + str(its/100))","metadata":{"execution":{"iopub.status.busy":"2023-05-18T19:25:29.694265Z","iopub.execute_input":"2023-05-18T19:25:29.694600Z","iopub.status.idle":"2023-05-18T19:25:30.705203Z","shell.execute_reply.started":"2023-05-18T19:25:29.694569Z","shell.execute_reply":"2023-05-18T19:25:30.704243Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Model Architecture","metadata":{}},{"cell_type":"code","source":"class AUNET(nn.Module):\n    def __init__(self):\n        super(AUNET, self).__init__()\n\n        # Contracting Path\n        self.conv1 = nn.Conv3d(1, 16, \n                               kernel_size=3, \n                               padding = 1) # 8 x 76 x 76 ->  8 x 76 x 76\n        \n        self.bn1 = nn.BatchNorm3d(16) # sure I guess? doesn't change shape\n        \n        self.relu = nn.ReLU()\n        \n        self.pool1 = nn.MaxPool3d(kernel_size=2, stride = 2) # size / 2 so -> 4 x 38 x 38\n\n        # convert to 1 channel\n        self.conv2 = nn.Conv3d(16, 32, \n                               kernel_size=(4, 3, 3)) #4 x 38 x 38 -> 1 x 36 x 36\n        \n        self.bn2 = nn.BatchNorm3d(32)\n        \n        # sqeeze step\n        # to 2d\n        self.pool2 = nn.MaxPool2d(kernel_size = 2, stride = 2)\n        self.conv3 = nn.Conv2d(32, 64, kernel_size = 3)\n        # no batchnorm\n        \n        # one step below target dim\n        self.pool3 = nn.MaxPool2d(kernel_size = 2, stride = 2)\n        \n        self.conv4 = nn.Conv2d(64, 128, kernel_size = 3)\n        # no batchnorm\n        \n        # Expanding Path\n        self.upconv1 = nn.ConvTranspose2d(128, 64, kernel_size=3)\n        \n        self.conv5 = nn.Conv2d(128, 64, kernel_size=3, padding = 1)\n        self.bn5 = nn.BatchNorm2d(64)\n\n        self.upconv2 = nn.ConvTranspose2d(64, 1, kernel_size=2, stride=2)\n        # self.conv6 = nn.Conv3d(17, 1, kernel_size=3, padding=1)\n\n    def forward(self, x):\n        # Contracting Path\n        x = self.conv1(x)\n        x = self.bn1(x)\n        x1 = self.relu(x)\n        x = self.pool1(x1)\n\n        x = self.conv2(x)\n        x = self.bn2(x)\n        x = self.relu(x)\n        \n        # to 2d\n        x2 = torch.squeeze(x, 2)\n        \n        x = self.pool2(x2)\n\n        x = self.conv3(x)\n        x = self.relu(x) # target h x w shape\n        x3 = self.pool3(x)\n        \n        x = self.conv4(x3)\n        x4 = self.relu(x)\n        \n        # Expanding Path, realy only 1 u-net thing going on\n        \n        x = self.upconv1(x4)\n        x5 = torch.cat([x3, x], dim = 1)\n        \n        x = self.conv5(x5)\n        x = self.bn5(x)\n        x = self.relu(x)\n        x = self.upconv2(x)\n        \n        m = nn.Sigmoid()\n        \n        return m(x)","metadata":{"execution":{"iopub.status.busy":"2023-05-18T19:25:30.706618Z","iopub.execute_input":"2023-05-18T19:25:30.706939Z","iopub.status.idle":"2023-05-18T19:25:30.721366Z","shell.execute_reply.started":"2023-05-18T19:25:30.706909Z","shell.execute_reply":"2023-05-18T19:25:30.719789Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# import sys\n\n# sys.path.append('/kaggle/input/pretrainedmodels/pretrainedmodels-0.7.4')\n# sys.path.append('/kaggle/input/efficientnet-pytorch/EfficientNet-PyTorch-master')\n# sys.path.append('/kaggle/input/timm-pytorch-image-models/pytorch-image-models-master')\n# sys.path.append('/kaggle/input/segmentation-models-pytorch/segmentation_models.pytorch-master')\n\n# # import segmentation_models_pytorch as smp","metadata":{"execution":{"iopub.status.busy":"2023-05-18T19:25:30.722605Z","iopub.execute_input":"2023-05-18T19:25:30.723041Z","iopub.status.idle":"2023-05-18T19:25:30.734361Z","shell.execute_reply.started":"2023-05-18T19:25:30.723009Z","shell.execute_reply":"2023-05-18T19:25:30.733378Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# # Premade encoder/decoder from pytorch\n# class CustomModel(nn.Module):\n#     def __init__(self, cfg, weight=None):\n#         super().__init__()\n#         self.cfg = cfg\n\n#         self.encoder = smp.Unet(\n#             encoder_name=cfg.backbone, \n#             encoder_weights=weight,\n#             in_channels=cfg.in_chans,\n#             classes=cfg.target_size,\n#             activation=None,\n#         )\n\n#     def forward(self, image):\n#         output = self.encoder(image)\n#         output = output.squeeze(-1)\n#         return output\n\n# def build_model(cfg, weight=\"imagenet\"):\n#     print('model_name', cfg.model_name)\n#     print('backbone', cfg.backbone)\n\n#     model = CustomModel(cfg, weight)\n#     return model\n\ndef get_UnetPlusPlus():\n    model =  smp.UnetPlusPlus(\n                 encoder_name='efficientnet-b3',\n                 encoder_weights='imagenet',\n                 in_channels=Z_DIM,\n                 classes=1)\n    return model","metadata":{"execution":{"iopub.status.busy":"2023-05-18T19:25:30.735584Z","iopub.execute_input":"2023-05-18T19:25:30.736037Z","iopub.status.idle":"2023-05-18T19:25:30.744060Z","shell.execute_reply.started":"2023-05-18T19:25:30.736004Z","shell.execute_reply":"2023-05-18T19:25:30.743219Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"class CustomModel(nn.Module):\n    def __init__(self, cfg, weight=None):\n        super().__init__()\n        self.cfg = cfg\n\n        self.encoder = smp.Unet(\n            encoder_name='efficientnet-b3', \n            encoder_weights=weight,\n            in_channels=cfg.in_chans,\n            classes=cfg.target_size,\n            activation=None,\n        )\n\n    def forward(self, image):\n        output = self.encoder(image)\n        output = output.squeeze(-1)\n        return output","metadata":{"execution":{"iopub.status.busy":"2023-05-18T19:25:30.745385Z","iopub.execute_input":"2023-05-18T19:25:30.745713Z","iopub.status.idle":"2023-05-18T19:25:30.756659Z","shell.execute_reply.started":"2023-05-18T19:25:30.745682Z","shell.execute_reply":"2023-05-18T19:25:30.755806Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"model = get_UnetPlusPlus().to(DEVICE)","metadata":{"execution":{"iopub.status.busy":"2023-05-18T19:25:30.757592Z","iopub.execute_input":"2023-05-18T19:25:30.758086Z","iopub.status.idle":"2023-05-18T19:25:33.655237Z","shell.execute_reply.started":"2023-05-18T19:25:30.758055Z","shell.execute_reply":"2023-05-18T19:25:33.653989Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"class BinaryDiceLoss(nn.Module):\n    \"\"\"Dice loss of binary class\n    Args:\n        smooth: A float number to smooth loss, and avoid NaN error, default: 1\n        p: Denominator value: \\sum{x^p} + \\sum{y^p}, default: 2\n        predict: A tensor of shape [N, *]\n        target: A tensor of shape same with predict\n        reduction: Reduction method to apply, return mean over batch if 'mean',\n            return sum if 'sum', return a tensor of shape [N,] if 'none'\n    Returns:\n        Loss tensor according to arg reduction\n    Raise:\n        Exception if unexpected reduction\n    \"\"\"\n    def __init__(self, smooth=1, p=2, reduction='sum'):\n        super(BinaryDiceLoss, self).__init__()\n        self.smooth = smooth\n        self.p = p\n        self.reduction = reduction\n\n    def forward(self, predict, target):\n        assert predict.shape[0] == target.shape[0], \"predict & target batch size don't match\"\n        predict = predict.contiguous().view(predict.shape[0], -1)\n        target = target.contiguous().view(target.shape[0], -1)\n\n        num = torch.sum(torch.mul(predict, target), dim=1) + self.smooth\n        den = torch.sum(predict.pow(self.p) + target.pow(self.p), dim=1) + self.smooth\n\n        loss = 1 - num / den\n\n        if self.reduction == 'mean':\n            return loss.mean()\n        elif self.reduction == 'sum':\n            return loss.sum()\n        elif self.reduction == 'none':\n            return loss\n        else:\n            raise Exception('Unexpected reduction {}'.format(self.reduction))\n            \nDice = BinaryDiceLoss()\n\n# from https://github.com/hubutui/DiceLoss-PyTorch/blob/master/loss.py","metadata":{"execution":{"iopub.status.busy":"2023-05-18T19:25:33.660116Z","iopub.execute_input":"2023-05-18T19:25:33.660507Z","iopub.status.idle":"2023-05-18T19:25:33.676988Z","shell.execute_reply.started":"2023-05-18T19:25:33.660472Z","shell.execute_reply":"2023-05-18T19:25:33.675551Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Get a view of things, is it a man or a model?","metadata":{}},{"cell_type":"code","source":"next(iter(train_loader))[0][0]","metadata":{"execution":{"iopub.status.busy":"2023-05-18T19:25:33.681781Z","iopub.execute_input":"2023-05-18T19:25:33.682097Z","iopub.status.idle":"2023-05-18T19:25:33.742399Z","shell.execute_reply.started":"2023-05-18T19:25:33.682069Z","shell.execute_reply":"2023-05-18T19:25:33.741572Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"next(iter(train_loader))[0].squeeze().shape","metadata":{"execution":{"iopub.status.busy":"2023-05-18T19:28:08.286478Z","iopub.execute_input":"2023-05-18T19:28:08.286829Z","iopub.status.idle":"2023-05-18T19:28:08.305605Z","shell.execute_reply.started":"2023-05-18T19:28:08.286799Z","shell.execute_reply":"2023-05-18T19:28:08.304487Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Train","metadata":{}},{"cell_type":"code","source":"# set model as training\nmodel.train()\n\n# loss fun\n# criterion = nn.BCELoss()\ncriterion = nn.BCELoss()\n\n# SGD\noptimizer = optim.Adam(model.parameters(), lr=LEARNING_RATE)\n\n# using a scheduler!\nscheduler = torch.optim.lr_scheduler.OneCycleLR(optimizer, max_lr=LEARNING_RATE, total_steps=TRAINING_STEPS)\n\nrunning_loss = 0.0\n\nfor i, (subvolumes, labels) in tqdm(enumerate(train_loader), total = TRAINING_STEPS, position = 0, leave = True):\n\n    subvolumes, labels = subvolumes.to(DEVICE), labels.squeeze().to(DEVICE)\n    \n    if i >= TRAINING_STEPS:\n        break\n        \n    optimizer.zero_grad()\n    \n    outputs = model(subvolumes)\n    \n    #loss = criterion(outputs, labels)\n    loss =  nn.BCELoss()(outputs.squeeze(), labels)\n    # different loss fun\n    #loss = (nn.BCELoss()(outputs, labels)+Dice(outputs, labels))/2\n    \n    loss.backward()\n    \n    optimizer.step()\n    \n    scheduler.step()\n    \n    running_loss += loss.item()\n    \n    if i % 100 == 100-1:\n        \n        # avg loss, for n batches\n        print(\"Loss:\", running_loss / (100))\n        running_loss = 0.0","metadata":{"execution":{"iopub.status.busy":"2023-05-18T19:29:00.588935Z","iopub.execute_input":"2023-05-18T19:29:00.590165Z","iopub.status.idle":"2023-05-18T19:29:00.951341Z","shell.execute_reply.started":"2023-05-18T19:29:00.590125Z","shell.execute_reply":"2023-05-18T19:29:00.949942Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"With rotate and blur, its half as fast--30 min to train on 100,000 samples ~ 60it/s","metadata":{}},{"cell_type":"markdown","source":"# Eval","metadata":{}},{"cell_type":"code","source":"# empty output, put data in liek coordinates\noutput = torch.zeros_like(eval_dataset.label).float()\n\ncorrect = torch.zeros_like(eval_dataset.label)\n# set to eval mode, no backprop\nmodel.eval()\n\nwith torch.no_grad():\n    # batch num and payload (X, y, ycoord, xcoord)\n    for i, payload in enumerate(tqdm(eval_loader)):\n\n        # item num and value for model output from X tensor\n        for j, value in enumerate(model(payload[0].to(DEVICE))):\n            \n            # get coords for output\n            y1 = payload[2][0][j].item() * PATCH_SIZE\n            y2 = y1 + PATCH_SIZE\n            x1 = payload[2][1][j].item() * PATCH_SIZE\n            x2 = x1 + PATCH_SIZE\n            \n            output[y1:y2, x1:x2] = value\n            correct[y1:y2, x1:x2] = payload[1][j]","metadata":{"execution":{"iopub.status.busy":"2023-05-18T15:20:47.748960Z","iopub.execute_input":"2023-05-18T15:20:47.749319Z","iopub.status.idle":"2023-05-18T15:20:48.361454Z","shell.execute_reply.started":"2023-05-18T15:20:47.749289Z","shell.execute_reply":"2023-05-18T15:20:48.360572Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def compare_eval(prediction, actual, rect):\n    p, a = Image.fromarray(prediction), Image.fromarray(actual)\n    p, a = TF.crop(p, rect[1], rect[0], rect[3], rect[2]), TF.crop(a, rect[1], rect[0], rect[3], rect[2])\n    p, a = p.resize((p.size[0]//10, p.size[1]//10)), a.resize((a.size[0]//10, a.size[1]//10))\n    # display(a, p)\n    return display(a, p)","metadata":{"execution":{"iopub.status.busy":"2023-05-18T15:20:49.641211Z","iopub.execute_input":"2023-05-18T15:20:49.642219Z","iopub.status.idle":"2023-05-18T15:20:49.649536Z","shell.execute_reply.started":"2023-05-18T15:20:49.642168Z","shell.execute_reply":"2023-05-18T15:20:49.648322Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"c = np.asarray(correct.to(\"cpu\"), dtype = bool)\nbout = np.asarray(output.to(\"cpu\")) > 0.15\n\ncompare_eval(bout, c, rect)","metadata":{"execution":{"iopub.status.busy":"2023-05-18T15:20:55.957959Z","iopub.execute_input":"2023-05-18T15:20:55.958318Z","iopub.status.idle":"2023-05-18T15:20:56.482057Z","shell.execute_reply.started":"2023-05-18T15:20:55.958289Z","shell.execute_reply":"2023-05-18T15:20:56.481039Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Score","metadata":{}},{"cell_type":"code","source":"print(fbeta_score(c.flatten(), bout.flatten(), beta = 0.5))","metadata":{"execution":{"iopub.status.busy":"2023-05-18T15:21:40.198598Z","iopub.execute_input":"2023-05-18T15:21:40.199285Z","iopub.status.idle":"2023-05-18T15:21:56.272793Z","shell.execute_reply.started":"2023-05-18T15:21:40.199250Z","shell.execute_reply":"2023-05-18T15:21:56.271690Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Test","metadata":{}},{"cell_type":"code","source":"del train_dataset, train_loader, eval_dataset, eval_loader\ngc.collect()\ntorch.cuda.empty_cache()","metadata":{"execution":{"iopub.status.busy":"2023-05-16T14:31:52.623056Z","iopub.status.idle":"2023-05-16T14:31:52.624200Z","shell.execute_reply.started":"2023-05-16T14:31:52.623892Z","shell.execute_reply":"2023-05-16T14:31:52.623922Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def UnPad(x, padding):\n    \n    # left, top, right, bottom in\n    # top, left, heigh, width out\n    top = padding[1]\n    left = padding[0]\n    height = x.shape[-2] - top - padding[3]\n    width = x.shape[-1] - left - padding[2]\n    \n    return TF.crop(x, top, left, height, width)\n\ndef rle(output, threshold  =  0.40):\n    pixels = np.where(output.flatten().cpu() > threshold, 1, 0).astype(np.uint8)\n    pixels[0] = 0\n    pixels[-1] = 0\n    runs = np.where(pixels[1:] != pixels[:-1])[0] + 2\n    runs[1::2] = runs[1::2] - runs[:-1:2]\n    return ' '.join(str(x) for x in runs)","metadata":{"execution":{"iopub.status.busy":"2023-05-16T14:31:52.625693Z","iopub.status.idle":"2023-05-16T14:31:52.626684Z","shell.execute_reply.started":"2023-05-16T14:31:52.626403Z","shell.execute_reply":"2023-05-16T14:31:52.626431Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Predict A","metadata":{}},{"cell_type":"code","source":"a_prefix = '/kaggle/input/vesuvius-challenge-ink-detection/test/a/'\n\nif not os.path.exists(a_prefix):\n    a_prefix = '/kaggle/input/vesuvius-challenge/test/a/'\n    \na_dataset = TestMPDataset(a_prefix,\n                          patch_size = PATCH_SIZE,\n                          val = False)\n\na_loader = data.DataLoader(a_dataset, batch_size = BATCH_SIZE, shuffle=False)","metadata":{"execution":{"iopub.status.busy":"2023-05-16T14:31:52.628088Z","iopub.status.idle":"2023-05-16T14:31:52.629134Z","shell.execute_reply.started":"2023-05-16T14:31:52.628853Z","shell.execute_reply":"2023-05-16T14:31:52.628880Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# empty output, put data in liek coordinates\noutput = torch.zeros_like(a_dataset.mask).float()\n\n# set to eval mode, no backprop\nmodel.eval()\n\nwith torch.no_grad():\n    # batch num and payload (X, y, ycoord, xcoord)\n    for i, payload in enumerate(tqdm(a_loader)):\n\n        # item num and value for model output from X tensor\n        for j, value in enumerate(model(payload[0].to(DEVICE))):\n            \n            # get coords for output\n            y1 = payload[2][0][j].item() * PATCH_SIZE\n            y2 = y1 + PATCH_SIZE\n            x1 = payload[2][1][j].item() * PATCH_SIZE\n            x2 = x1 + PATCH_SIZE\n            \n            output[y1:y2, x1:x2] = value\n\n#  unpad/crop output left, top, right, bottom\noutput = UnPad(output, a_dataset.mask_padding)","metadata":{"execution":{"iopub.status.busy":"2023-05-16T14:31:52.630492Z","iopub.status.idle":"2023-05-16T14:31:52.631583Z","shell.execute_reply.started":"2023-05-16T14:31:52.631291Z","shell.execute_reply":"2023-05-16T14:31:52.631319Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"a_rle = rle(output)\n\ndel a_dataset, a_loader\ngc.collect()\ntorch.cuda.empty_cache()","metadata":{"execution":{"iopub.status.busy":"2023-05-16T14:31:52.632930Z","iopub.status.idle":"2023-05-16T14:31:52.633342Z","shell.execute_reply.started":"2023-05-16T14:31:52.633143Z","shell.execute_reply":"2023-05-16T14:31:52.633175Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Predict B","metadata":{}},{"cell_type":"code","source":"b_prefix = '/kaggle/input/vesuvius-challenge-ink-detection/test/b/'\n\nif not os.path.exists(b_prefix):\n    b_prefix = '/kaggle/input/vesuvius-challenge/test/b/'\n    \nb_dataset = TestMPDataset(b_prefix,\n                            patch_size = PATCH_SIZE,\n                            val = False)\n\nb_loader = data.DataLoader(b_dataset, batch_size = BATCH_SIZE, shuffle=False)","metadata":{"execution":{"iopub.status.busy":"2023-05-16T14:31:52.634517Z","iopub.status.idle":"2023-05-16T14:31:52.634928Z","shell.execute_reply.started":"2023-05-16T14:31:52.634741Z","shell.execute_reply":"2023-05-16T14:31:52.634761Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# empty output, put data in liek coordinates\noutput = torch.zeros_like(b_dataset.mask).float()\n\n# set to eval mode, no backprop\nmodel.eval()\n\nwith torch.no_grad():\n    # batch num and payload (X, y, ycoord, xcoord)\n    for i, payload in enumerate(tqdm(b_loader)):\n\n        # item num and value for model output from X tensor\n        for j, value in enumerate(model(payload[0].to(DEVICE))):\n            \n            # get coords for output\n            y1 = payload[2][0][j].item() * PATCH_SIZE\n            y2 = y1 + PATCH_SIZE\n            x1 = payload[2][1][j].item() * PATCH_SIZE\n            x2 = x1 + PATCH_SIZE\n            \n            output[y1:y2, x1:x2] = value\n\noutput = UnPad(output, b_dataset.mask_padding)","metadata":{"execution":{"iopub.status.busy":"2023-05-16T14:31:52.636785Z","iopub.status.idle":"2023-05-16T14:31:52.637404Z","shell.execute_reply.started":"2023-05-16T14:31:52.637201Z","shell.execute_reply":"2023-05-16T14:31:52.637221Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"b_rle = rle(output)","metadata":{"execution":{"iopub.status.busy":"2023-05-16T14:31:52.638753Z","iopub.status.idle":"2023-05-16T14:31:52.639114Z","shell.execute_reply.started":"2023-05-16T14:31:52.638942Z","shell.execute_reply":"2023-05-16T14:31:52.638959Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Post-Processing","metadata":{}},{"cell_type":"code","source":"# from skimage import morphology as m\n\n# a = m.remove_small_holes(\n#         m.remove_small_objects(\n#             bout, min_size=1200, connectivity=1, out=None),\n#     area_threshold=64, connectivity=1, out=None)\n# Image.fromarray(a)","metadata":{"execution":{"iopub.status.busy":"2023-05-16T14:31:52.640424Z","iopub.status.idle":"2023-05-16T14:31:52.641254Z","shell.execute_reply.started":"2023-05-16T14:31:52.641048Z","shell.execute_reply":"2023-05-16T14:31:52.641068Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Submit","metadata":{}},{"cell_type":"code","source":"print(\"Id,Predicted\\na,\" + a_rle + \"\\nb,\" + b_rle, file=open('submission.csv', 'w'))","metadata":{"execution":{"iopub.status.busy":"2023-05-16T14:31:52.642835Z","iopub.status.idle":"2023-05-16T14:31:52.643823Z","shell.execute_reply.started":"2023-05-16T14:31:52.643537Z","shell.execute_reply":"2023-05-16T14:31:52.643566Z"},"trusted":true},"execution_count":null,"outputs":[]}]}