{"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":"code","source":"import gc\nimport math\nimport glob\nimport os\nimport torch\nimport numpy as np\nimport torch.nn as nn\nimport torch.nn.functional as F\nimport torchvision.transforms.functional as TF\nimport torchvision.utils\nimport pytorch_lightning as pl\nimport torchmetrics as tm\nimport albumentations as A\nfrom albumentations.pytorch import ToTensorV2\nfrom typing import List\nfrom PIL import Image","metadata":{"execution":{"iopub.status.busy":"2022-07-09T20:17:11.954675Z","iopub.execute_input":"2022-07-09T20:17:11.955128Z","iopub.status.idle":"2022-07-09T20:17:11.961882Z","shell.execute_reply.started":"2022-07-09T20:17:11.955095Z","shell.execute_reply":"2022-07-09T20:17:11.961041Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def set_seed(seed = 42):\n    '''Sets the seed of the entire notebook so results are the same every time we run.\n    This is for REPRODUCIBILITY.'''\n    np.random.seed(seed)\n    torch.manual_seed(seed)\n    torch.cuda.manual_seed(seed)\n    # When running on the CuDNN backend, two further options must be set\n    torch.backends.cudnn.deterministic = True\n    torch.backends.cudnn.benchmark = False\n    # Set a fixed value for the hash seed\n    os.environ['PYTHONHASHSEED'] = str(seed)\n    print('> SEEDING DONE')\n    \nset_seed()","metadata":{"execution":{"iopub.status.busy":"2022-07-09T20:17:11.986040Z","iopub.execute_input":"2022-07-09T20:17:11.986585Z","iopub.status.idle":"2022-07-09T20:17:11.996057Z","shell.execute_reply.started":"2022-07-09T20:17:11.986555Z","shell.execute_reply":"2022-07-09T20:17:11.995272Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"folder = '/kaggle/working/'\nfor filename in os.listdir(folder):\n    file_path = os.path.join(folder, filename)\n    try:\n        if os.path.isfile(file_path) or os.path.islink(file_path):\n            os.unlink(file_path)\n        elif os.path.isdir(file_path):\n            shutil.rmtree(file_path)\n    except Exception as e:\n        print('Failed to delete %s. Reason: %s' % (file_path, e))","metadata":{"execution":{"iopub.status.busy":"2022-07-09T20:17:12.019386Z","iopub.execute_input":"2022-07-09T20:17:12.019718Z","iopub.status.idle":"2022-07-09T20:17:12.029203Z","shell.execute_reply.started":"2022-07-09T20:17:12.019690Z","shell.execute_reply":"2022-07-09T20:17:12.028321Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Check for GPU or setup TPU\n# if not torch.cuda.is_available():\n#     !curl https://raw.githubusercontent.com/pytorch/xla/master/contrib/scripts/env-setup.py -o pytorch-xla-env-setup.py\n#     !python pytorch-xla-env-setup.py --version 1.7 --apt-packages libomp5 libopenblas-dev\n#     !pip install pytorch-lightning==1.1.5","metadata":{"_kg_hide-input":true,"_kg_hide-output":true,"execution":{"iopub.status.busy":"2022-07-09T20:17:12.052333Z","iopub.execute_input":"2022-07-09T20:17:12.052719Z","iopub.status.idle":"2022-07-09T20:17:12.056561Z","shell.execute_reply.started":"2022-07-09T20:17:12.052690Z","shell.execute_reply":"2022-07-09T20:17:12.055745Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Model Architecture\n\nThe following image depicts the architecture that will be used for segmentation. We will first define a torch module as the building block for the model, then use a pytorch lightning module to define the final model.\n\n![](https://raw.githubusercontent.com/aladdinpersson/Machine-Learning-Collection/master/ML/Pytorch/image_segmentation/semantic_segmentation_unet/UNET_architecture.png)","metadata":{}},{"cell_type":"code","source":"!git clone https://github.com/wemoveon2/SegLoss.git\n!cp -r SegLoss/losses_pytorch/* .\nfrom dice_loss import GDiceLossV2, FocalTversky_loss\nfrom hausdorff import HausdorffERLoss","metadata":{"execution":{"iopub.status.busy":"2022-07-09T20:17:12.086215Z","iopub.execute_input":"2022-07-09T20:17:12.086653Z","iopub.status.idle":"2022-07-09T20:17:13.637858Z","shell.execute_reply.started":"2022-07-09T20:17:12.086610Z","shell.execute_reply":"2022-07-09T20:17:13.636706Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"ft_loss_args = {\n    'apply_nonlin': F.sigmoid, \n    'batch_dice': True,\n}\nhausdorff_er_loss = HausdorffERLoss()\ngeneralized_dice_loss_v2 = GDiceLossV2(apply_nonlin=F.sigmoid)\nft_loss = FocalTversky_loss(ft_loss_args)\n\ndef loss_fn(pred, gt):\n    return 0.5*hausdorff_er_loss(pred, gt) + 0.25*generalized_dice_loss_v2(pred, gt) + 0.25*ft_loss(pred, gt)","metadata":{"execution":{"iopub.status.busy":"2022-07-09T20:17:13.642831Z","iopub.execute_input":"2022-07-09T20:17:13.643435Z","iopub.status.idle":"2022-07-09T20:17:13.653175Z","shell.execute_reply.started":"2022-07-09T20:17:13.643388Z","shell.execute_reply":"2022-07-09T20:17:13.652371Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"!pip install segmentation_models_pytorch\nimport segmentation_models_pytorch as smp\n\nBCELoss     = smp.losses.SoftBCEWithLogitsLoss()\nTverskyLoss = smp.losses.TverskyLoss(mode='multilabel', log_loss=False)\n\n@torch.no_grad()\ndef dice_coef(y_pred, y_true, thr=0.5, dim=(2,3), epsilon=0.001):\n    y_t = y_true.to(torch.float32)\n    y_p = (y_pred>thr).to(torch.float32)\n    inter = (y_t*y_p).sum(dim=dim)\n    den = y_t.sum(dim=dim) + y_p.sum(dim=dim)\n    del y_t, y_p\n    dice = ((2*inter+epsilon)/(den+epsilon)).mean(dim=(1,0))\n    del inter, den\n    return dice\n@torch.no_grad()\ndef iou_coef(y_pred, y_true, thr=0.5, dim=(2,3), epsilon=0.001):\n    y_t = y_true.to(torch.float32)\n    y_p = (y_pred>thr).to(torch.float32)\n    inter = (y_t*y_p).sum(dim=dim)\n    union = (y_t + y_p - y_t*y_p).sum(dim=dim)\n    del y_t, y_p\n    iou = ((inter+epsilon)/(union+epsilon)).mean(dim=(1,0))\n    del inter, union\n    return iou\n\ndef loss_fn(y_pred, y_true):\n    return 0.5*BCELoss(y_pred, y_true) + 0.5*TverskyLoss(y_pred, y_true)","metadata":{"execution":{"iopub.status.busy":"2022-07-09T20:17:26.432463Z","iopub.execute_input":"2022-07-09T20:17:26.432909Z","iopub.status.idle":"2022-07-09T20:17:43.961836Z","shell.execute_reply.started":"2022-07-09T20:17:26.432870Z","shell.execute_reply":"2022-07-09T20:17:43.960805Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"class DropBlock2D(nn.Module):\n    r\"\"\"Randomly zeroes 2D spatial blocks of the input tensor.\n    As described in the paper\n    `DropBlock: A regularization method for convolutional networks`_ ,\n    dropping whole blocks of feature map allows to remove semantic\n    information as compared to regular dropout.\n    Args:\n        drop_prob (float): probability of an element to be dropped.\n        block_size (int): size of the block to drop\n    Shape:\n        - Input: `(N, C, H, W)`\n        - Output: `(N, C, H, W)`\n    .. _DropBlock: A regularization method for convolutional networks:\n       https://arxiv.org/abs/1810.12890\n    \"\"\"\n\n    def __init__(self, drop_prob, block_size):\n        super(DropBlock2D, self).__init__()\n        self.drop_prob = drop_prob\n        self.block_size = block_size\n\n    def forward(self, x):\n        # shape: (bsize, channels, height, width)\n        assert x.dim() == 4, \\\n            \"Expected input with 4 dimensions (bsize, channels, height, width)\"\n        if not self.training or self.drop_prob == 0.:\n            return x\n        else:\n            # get gamma value\n            gamma = self._compute_gamma(x)\n            # sample mask\n            mask = (torch.rand(x.shape[0], *x.shape[2:]) < gamma).float()\n            # place mask on input device\n            mask = mask.to(x.device)\n            # compute block mask\n            block_mask = self._compute_block_mask(mask)\n            # apply block mask\n            out = x * block_mask[:, None, :, :]\n            # scale output\n            out = out * block_mask.numel() / block_mask.sum()\n            return out\n\n    def _compute_block_mask(self, mask):\n        block_mask = F.max_pool2d(input=mask[:, None, :, :],\n                                  kernel_size=(self.block_size, self.block_size),\n                                  stride=(1, 1),\n                                  padding=self.block_size // 2)\n        if self.block_size % 2 == 0:\n            block_mask = block_mask[:, :, :-1, :-1]\n        block_mask = 1 - block_mask.squeeze(1)\n        return block_mask\n\n    def _compute_gamma(self, x):\n        return self.drop_prob / (self.block_size ** 2)\n\nclass Block(pl.LightningModule):\n    def __init__(self, in_channels: int, out_channels: int, kernel: int = 3, \n                 stride: int = 1, padding: int = 1, bias: bool = False, shortcut: bool = True):\n        super().__init__()\n        self.conv = nn.Sequential(\n            nn.Conv2d(in_channels, out_channels, kernel, stride, padding, bias=bias),\n            nn.BatchNorm2d(out_channels),\n            nn.ReLU(),\n            nn.Conv2d(out_channels, out_channels, kernel, stride, padding, bias=bias),\n            nn.BatchNorm2d(out_channels),\n            DropBlock2D(0.8, 3)\n        )\n        if shortcut:\n            self.shortcut = nn.Sequential(\n                nn.Conv2d(in_channels, out_channels, kernel, stride, padding, bias=bias),\n                nn.BatchNorm2d(out_channels))\n        else:\n            self.shortcut = None\n    def forward(self, x):\n        identity = x\n        out = self.conv(x)\n        if self.expand_dims and self.shortcut is not None:\n            identity = self.shortcut(identity)\n        out += identity\n        out = F.relu(out)\n        return out\n    \n    def expand_dims(self):\n        return self.in_channels != self.out_channels\n\nclass UNET(pl.LightningModule):\n    def __init__(self, loss_fn, in_channels: int = 3, n_classes: int = 1, \n                 features: List[int] = [64,128,256,512,1024], learning_rate: float = 1.5e-3):\n        super().__init__()\n        self.learning_rate = learning_rate \n        self.down = nn.ModuleList()\n        self.up = nn.ModuleList()\n        self.pool = nn.MaxPool2d(2, 2)\n        for feature in features:\n            self.down.append(Block(in_channels, feature))\n            in_channels=feature\n        for feature in reversed(features):\n            self.up.append(\n                nn.ConvTranspose2d(feature*2, feature, 2, 2)\n            )\n            self.up.append(\n                Block(feature*2, feature) # x gets concat to 2xchannel\n            )\n        self.bottleneck = Block(features[-1], features[-1]*2)\n        self.final_conv = nn.Conv2d(features[0], n_classes, 1)\n        self.loss_fn = loss_fn\n        \n        self.val_num_correct = 0\n        self.val_num_pixels = 0\n        self.val_dice_score = 0\n        self.num_correct = 0\n        self.num_pixels = 0\n        self.dice_score = 0\n    def forward(self, x):\n        skip_connections = []\n        for down in self.down:\n            x = down(x)\n            skip_connections.append(x)\n            x = self.pool(x)\n        x = self.bottleneck(x)\n        skip_connections = skip_connections[::-1]\n        for idx in range(0, len(self.up), 2):\n            x = self.up[idx](x)\n            skip_connection = skip_connections[idx//2]\n            if x.shape != skip_connection.shape:\n                x = TF.resize(x, size=skip_connection.shape[2:])\n            concat_skip = torch.cat((skip_connection, x), dim=1) # Concat along channels (b, c, h, w)\n            x = self.up[idx+1](concat_skip)\n        return self.final_conv(x)\n    \n    def training_step(self, batch, batch_idx):\n        x, y = batch\n        pred = self(x)\n        loss = self.loss_fn(pred, y)\n        pred = torch.sigmoid(pred)\n        pred = (pred > 0.5).float()\n        self.num_correct += (pred == y).sum()\n        self.num_pixels += torch.numel(pred)\n        self.dice_score += (2 * (pred * y).sum()) / (\n            (pred + y).sum() + 1e-8\n        )\n        self.log('train_loss', loss, prog_bar = True, logger = True)\n        return {'loss': loss}\n    \n    def training_epoch_end(self, output):\n        train_acc = float(f'{(self.num_correct/self.num_pixels)*100:.2f}')\n        self.log('train_acc', train_acc, prog_bar = True, logger = True)\n        dice_score = self.dice_score/len(output)\n        self.log('train_dice_score', dice_score, prog_bar = True, logger = True)\n        self.num_correct, self.num_pixels, self.dice_score = 0,0,0\n    \n    def validation_step(self, batch, batch_idx):\n        x, y = batch\n        pred = self(x)\n        loss = self.loss_fn(pred, y)\n        pred = torch.sigmoid(pred)\n        pred = (pred > 0.5).float()\n        self.val_num_correct += (pred == y).sum()\n        self.val_num_pixels += torch.numel(pred)\n        self.val_dice_score += (2 * (pred * y).sum()) / (\n            (pred + y).sum() + 1e-8\n        )\n        self.log('val_loss', loss, prog_bar = True, logger = True)\n        return {'loss': loss}\n    \n    def validation_epoch_end(self, output):\n        val_acc = float(f'{(self.val_num_correct/self.val_num_pixels)*100:.2f}')\n        self.log('val_acc', val_acc, prog_bar = True, logger = True)\n        dice_score = self.val_dice_score/len(output)\n        self.log('val_dice_score', dice_score, prog_bar = True, logger = True)\n        self.val_num_correct, self.val_num_pixels, self.val_dice_score = 0,0,0\n        \n    def configure_optimizers(self):\n        optimizer = torch.optim.AdamW(params = self.parameters(), lr = self.learning_rate, weight_decay=0.3)\n        return optimizer","metadata":{"_kg_hide-output":true,"execution":{"iopub.status.busy":"2022-07-09T20:17:59.691791Z","iopub.execute_input":"2022-07-09T20:17:59.692210Z","iopub.status.idle":"2022-07-09T20:17:59.732804Z","shell.execute_reply.started":"2022-07-09T20:17:59.692174Z","shell.execute_reply":"2022-07-09T20:17:59.732016Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"We can see how many parameters we have in our model using the following command. Note that the order shown does not reflect how a training example flows through the architecture. ","metadata":{}},{"cell_type":"code","source":"# pl.utilities.model_summary.summarize(UNET(),-1)","metadata":{"execution":{"iopub.status.busy":"2022-07-09T20:17:59.734546Z","iopub.execute_input":"2022-07-09T20:17:59.735120Z","iopub.status.idle":"2022-07-09T20:17:59.750608Z","shell.execute_reply.started":"2022-07-09T20:17:59.735085Z","shell.execute_reply":"2022-07-09T20:17:59.749846Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Data\nNow that we have our model, we define a torch Dataset to retrieve and apply our transformations to our data. We define the three necessary methods and move on to defining our LightningDataModule, which will split our Dataset into train/val splits and prepare the appropriate Dataloader when called by the Trainer object later on when we start training.","metadata":{}},{"cell_type":"code","source":"class SegmentationDataset(torch.utils.data.Dataset):\n        \n    def __init__(self, image_path, mask_path, transforms):\n        self.images = glob.glob(os.path.join(image_path, '*.jpg'))\n        self.image_path = image_path\n        self.mask_path = mask_path\n        self.transforms = transforms\n\n    def __len__(self):\n        return len(self.images)\n\n    def __getitem__(self, idx):\n        img = np.array(Image.open(self.images[idx]).convert('RGB'))\n        mask = np.array(Image.open(os.path.join(self.mask_path, os.path.basename(self.images[idx]).replace('.jpg', '.png')))) \n        mask[mask == 255.0] = 1.0  \n        augmentations = self.transforms(image=img, mask=mask)\n        image = augmentations[\"image\"]\n        mask = augmentations[\"mask\"]\n        mask = torch.unsqueeze(mask, 0)\n        mask = mask.type(torch.float32)\n        return image.float(), mask","metadata":{"execution":{"iopub.status.busy":"2022-07-09T20:22:14.852760Z","iopub.execute_input":"2022-07-09T20:22:14.855900Z","iopub.status.idle":"2022-07-09T20:22:14.873864Z","shell.execute_reply.started":"2022-07-09T20:22:14.855831Z","shell.execute_reply":"2022-07-09T20:22:14.872495Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import cv2\n\ndata_transforms = {\n    \"valid\": A.Compose([\n        A.Resize(*[255,255], interpolation=cv2.INTER_NEAREST),\n        A.pytorch.ToTensorV2()]),\n    \"train\": A.Compose([\n        A.Resize(*[255,255], interpolation=cv2.INTER_NEAREST),\n        A.HorizontalFlip(p=0.5),\n        A.VerticalFlip(p=0.5),\n        A.ShiftScaleRotate(shift_limit=0.0625, scale_limit=0.05, rotate_limit=10, p=0.5),\n        A.OneOf([\n            A.GridDistortion(num_steps=5, distort_limit=0.05, p=1.0),\n            A.OpticalDistortion(distort_limit=0.05, shift_limit=0.05, p=0.3),\n            A.ElasticTransform(alpha=1, sigma=50, alpha_affine=50, p=1.0)\n        ], p=0.40),\n        A.pytorch.ToTensorV2()])\n}","metadata":{"execution":{"iopub.status.busy":"2022-07-09T20:20:34.297783Z","iopub.execute_input":"2022-07-09T20:20:34.298198Z","iopub.status.idle":"2022-07-09T20:20:34.306538Z","shell.execute_reply.started":"2022-07-09T20:20:34.298157Z","shell.execute_reply":"2022-07-09T20:20:34.305553Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"class SegmentationDataModule(pl.LightningDataModule):\n    \n    def __init__(self, image_path: str, mask_path:str , transform, \n                 train_size: float = 0.90, batch_size: int = 5):\n        super().__init__()\n        self.image_path = image_path\n        self.mask_path = mask_path\n        self.batch_size = batch_size\n        self.transform = transform\n        self.train_size = train_size\n        \n    def setup(self, stage = None):\n        if stage in (None, 'fit'):\n            ds = SegmentationDataset(self.image_path, self.mask_path, self.transform)\n            train_size = math.floor(len(ds)*self.train_size)\n            val_size = len(ds)-train_size\n            train_ds, val_ds = torch.utils.data.random_split(ds, [train_size, val_size])\n            self.train_dataset = train_ds\n            self.val_dataset = val_ds\n    \n    def train_dataloader(self):\n        return torch.utils.data.DataLoader(self.train_dataset, self.batch_size, num_workers=2, shuffle = True, persistent_workers=True)\n\n    def val_dataloader(self):\n        return torch.utils.data.DataLoader(self.val_dataset, self.batch_size, num_workers=2, persistent_workers=True)","metadata":{"execution":{"iopub.status.busy":"2022-07-09T20:20:36.356379Z","iopub.execute_input":"2022-07-09T20:20:36.356717Z","iopub.status.idle":"2022-07-09T20:20:36.366782Z","shell.execute_reply.started":"2022-07-09T20:20:36.356689Z","shell.execute_reply":"2022-07-09T20:20:36.365653Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Training\n\nOnce our model, optimizer+loss, and LightningDataModule is defined, we can use Trainer to run our training and validation loops. We can also use callbacks provided by lightning (check out [Bolts](https://lightning-bolts.readthedocs.io/en/latest/) for advanced callbacks, such as for sparsification) to create checkpoints or for early stopping. Trainer also takes care of multi-GPU and TPU training. \n\nWe also use albumentations to apply the **same** data augmentations on the image **and** mask. I don't believe the torchvision transforms allows you to do the same, but correct me if I'm wrong. ","metadata":{}},{"cell_type":"code","source":"torch.cuda.empty_cache()\n# transform = A.Compose(\n#     [\n#         A.Resize(height=255, width=255),\n#         A.Rotate(limit=45, p=0.7),\n#         A.HorizontalFlip(p=0.5),\n#         A.VerticalFlip(p=0.3),\n#         A.Normalize(\n#             mean=[0.0, 0.0, 0.0],\n#             std=[1.0, 1.0, 1.0],\n#             max_pixel_value=255.0,\n#         ),\n#         A.pytorch.ToTensorV2(),\n#     ]\n# )\nargs = {\n    'in_channels': 3,\n    'n_classes': 1,\n    'features': [64,128,256,512,1024],\n    'loss_fn': loss_fn\n}\nds = SegmentationDataModule('../input/carvana-image-masking-png/train_images', '../input/carvana-image-masking-png/train_masks', transform=data_transforms['train'])\nmodel = UNET(**args)\nif not os.path.isdir('./unet_model'): os.mkdir('./unet_model')\ncheckpointCallback = pl.callbacks.ModelCheckpoint(dirpath=\"./unet_model\", \n                                                  save_top_k=1, \n                                                  monitor=\"val_loss\",\n                                                 filename='{epoch}-{val_loss:.5f}',\n                                                 mode='min')\nif torch.cuda.is_available():\n    trainer = pl.Trainer(max_epochs=8, accelerator='gpu', gpus=1, \n                         callbacks=[checkpointCallback], profiler='simple',\n                         auto_lr_find=True, accumulate_grad_batches=20)\nelse:\n    trainer = pl.Trainer(max_epochs=7, accelerator='tpu',  \n                         callbacks=[checkpointCallback], auto_lr_find=True)\n\ntrainer.fit(model, datamodule=ds)","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","execution":{"iopub.status.busy":"2022-07-09T20:22:17.461905Z","iopub.execute_input":"2022-07-09T20:22:17.463062Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Utility Functions\n\nHere we define three utility functions:\n  1. **save_images**(model, loader, folder, device)\n      - This is used to save predictions and their corresponding masks from a specified model across a specified dataloader.\n      - Device can be set to cpu if not running on gpu\n  2. **get_concat_v**(im1, im2)\n      - This is used to later concatenate the prediction and mask images we create using save_image.\n      - im1 & im2 are PIL.Image objects\n  3. **merge_photos**(src_folder, dst_folder, remove_single)\n      - This is used to read and concatenate the prediction and mask images using the previously defined get_concat_v().\n      - remove_single can be set to False to save the unmerged images","metadata":{}},{"cell_type":"code","source":"def save_images(model, loader, folder='val_img', device='cuda'):\n    model.eval()\n    if not os.path.isdir(folder):\n        os.mkdir(folder)\n    model.to(device=device)\n    for idx, (x, y) in enumerate(loader):\n        x = x.to(device=device)\n        y = y.to(device=device)\n        with torch.no_grad():\n            preds = torch.sigmoid(model(x).cuda())\n            preds = (preds > 0.5).float()\n        torchvision.utils.save_image(\n            preds, f\"{folder}/pred_{idx}.png\"\n        )\n        torchvision.utils.save_image(y, f\"{folder}/mask_{idx}.png\")       \n\ndef get_concat_v(im1, im2):\n    dst = Image.new('RGB', (im1.width, im1.height + im2.height))\n    dst.paste(im1, (0, 0))\n    dst.paste(im2, (0, im1.height))\n    return dst\n\ndef merge_photos(src_folder: str='./val_img', dst_folder: str='./merged_val_img', remove_single: bool=True):\n    files = glob.glob(src_folder+'/*.png')\n    if not os.path.isdir(dst_folder):\n        os.mkdir(dst_folder)\n    for i in range(int(len(files)/2)):\n        pred_img = Image.open(f'{src_folder}/pred_{i}.png')\n        mask_img = Image.open(f'{src_folder}/mask_{i}.png')\n        get_concat_v(pred_img, mask_img).save(f'{dst_folder}/merged_pred_mask_{i}.png')\n        if remove_single:\n            os.remove(f'./val_img/pred_{i}.png')\n            os.remove(f'./val_img/mask_{i}.png')","metadata":{"execution":{"iopub.status.busy":"2022-07-09T20:18:03.195720Z","iopub.status.idle":"2022-07-09T20:18:03.198092Z","shell.execute_reply.started":"2022-07-09T20:18:03.197875Z","shell.execute_reply":"2022-07-09T20:18:03.197896Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"ds = SegmentationDataModule('../input/carvana-image-masking-png/train_images', '../input/carvana-image-masking-png/train_masks', transform=data_transforms['valid'])\nds.setup()\nsave_images(model, ds.val_dataloader())\nmerge_photos()\n# TODO: Implement feature for sampling random img/label pairs for model prediction/eval\n#         - Should show output and mask as images, pref side by side or across batches\n#         - Conduct additional testing using the competition or data from the internet","metadata":{"execution":{"iopub.status.busy":"2022-07-09T20:18:03.199450Z","iopub.status.idle":"2022-07-09T20:18:03.199933Z","shell.execute_reply.started":"2022-07-09T20:18:03.199689Z","shell.execute_reply":"2022-07-09T20:18:03.199711Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"!zip -r lightning_logs.zip ./lightning_logs\n!zip -r merged_val_img.zip ./merged_val_img\n!zip -r unet.zip ./unet_model","metadata":{"_kg_hide-input":true,"_kg_hide-output":true,"execution":{"iopub.status.busy":"2022-07-09T20:18:03.202759Z","iopub.status.idle":"2022-07-09T20:18:03.207630Z","shell.execute_reply.started":"2022-07-09T20:18:03.206471Z","shell.execute_reply":"2022-07-09T20:18:03.206518Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Since Kaggle doesn't work with Tensorboard, download and load the logs in Google Collab to analyze the training and validation metrics\n\n<button><a href='./lightning_logs.zip'>Download Logs</a></button>\n<button><a href='./merged_val_img.zip'>Download Images</a></button>\n<button><a href='./unet.zip'>Download Model</a></button>","metadata":{}}]}