{"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":"# Begin","metadata":{}},{"cell_type":"code","source":"import numpy as np\nimport pandas as pd\nfrom PIL import Image\nfrom tqdm.notebook import tqdm\n\ntqdm.pandas()\n\nimport time\nimport os\nimport gc\n\ngc.enable()\nstart_time = time.time()\ntime_limit = 8*3600 #8 hours max","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","execution":{"iopub.status.busy":"2022-06-30T08:22:27.388329Z","iopub.execute_input":"2022-06-30T08:22:27.388932Z","iopub.status.idle":"2022-06-30T08:22:27.564338Z","shell.execute_reply.started":"2022-06-30T08:22:27.388809Z","shell.execute_reply":"2022-06-30T08:22:27.562747Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train_df = pd.read_csv(\"../input/hubmap-organ-segmentation/train.csv\")\ntrain_df","metadata":{"execution":{"iopub.status.busy":"2022-06-30T08:22:28.012701Z","iopub.execute_input":"2022-06-30T08:22:28.013234Z","iopub.status.idle":"2022-06-30T08:22:28.396717Z","shell.execute_reply.started":"2022-06-30T08:22:28.013180Z","shell.execute_reply":"2022-06-30T08:22:28.394979Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train_df.describe()","metadata":{"execution":{"iopub.status.busy":"2022-06-30T08:22:28.569248Z","iopub.execute_input":"2022-06-30T08:22:28.569886Z","iopub.status.idle":"2022-06-30T08:22:28.615278Z","shell.execute_reply.started":"2022-06-30T08:22:28.569840Z","shell.execute_reply":"2022-06-30T08:22:28.614149Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Helper Functions","metadata":{}},{"cell_type":"code","source":"#Credits to: ravishah1\n#Source: https://www.kaggle.com/competitions/hubmap-organ-segmentation/discussion/332838\n\ndef mask2rle(img, orig_dim=160):\n    '''\n    img: numpy array, 1 - mask, 0 - background\n    Returns run length as string formated\n    '''\n    #Rescale image to original size\n    n = Image.fromarray(img)\n    n = n.resize((orig_dim, orig_dim))\n    n = np.array(n).astype(float)\n    #Get pixels to flatten\n    pixels = n.T.flatten()\n    #Round the pixels using the half of the range of pixel value\n    pixels = (pixels-min(pixels) > ((max(pixels)-min(pixels))/2)).astype(int)\n    \n    pixels = np.concatenate([[0], pixels, [0]])\n    runs = np.where(pixels[1:] != pixels[:-1])[0] + 1\n    runs[1::2] -= runs[::2]\n    return ' '.join(str(x) for x in runs)","metadata":{"execution":{"iopub.status.busy":"2022-06-30T08:22:29.167294Z","iopub.execute_input":"2022-06-30T08:22:29.167982Z","iopub.status.idle":"2022-06-30T08:22:29.180565Z","shell.execute_reply.started":"2022-06-30T08:22:29.167935Z","shell.execute_reply":"2022-06-30T08:22:29.179191Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import matplotlib.pyplot as plt\nfrom skimage import color\n\ndef show_masked_img(img, mask, title=''):\n    mask = mask.reshape(img.shape[:2])\n    fig, ax = plt.subplots(1, 3, figsize=(9, 3))\n    fig.suptitle(title, fontsize=16)\n    img, mask = img.numpy(), mask.numpy()\n    \n    ax[0].imshow(mask); ax[0].set_title('Mask')\n    ax[1].imshow(img); ax[1].set_title('Image')\n    ax[2].imshow(color.label2rgb(mask.T, img,\n                               bg_label=0, bg_color=(1.,1.,1.), alpha=0.25))\n    ax[2].set_title('Masked Image')\n    plt.show()","metadata":{"execution":{"iopub.status.busy":"2022-06-30T08:22:29.770177Z","iopub.execute_input":"2022-06-30T08:22:29.771008Z","iopub.status.idle":"2022-06-30T08:22:29.910533Z","shell.execute_reply.started":"2022-06-30T08:22:29.770965Z","shell.execute_reply":"2022-06-30T08:22:29.908922Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def rle_to_2d_arr(rle, l):\n    rle = np.array(list(map(int, rle.split())))\n    label = np.zeros((l*l))\n    \n    for start, end in zip(rle[::2], rle[1::2]):\n        label[start:start+end] = 1\n        \n    #Convert label to image\n    label = Image.fromarray(label.reshape(l, l))\n    #Resize label\n    label = label.resize((t_size, t_size))\n    label = np.array(label).astype(float)\n    #rescale label\n    label = np.round((label - label.min())/(label.max() - label.min()))\n    \n    return label\n\ndef random_rotate(X, Y):\n    flipped_x, flipped_y = [], []\n    for x, y in zip(X, Y):\n        flip_v = np.random.random()>0.5\n        flip_h = np.random.random()>0.5\n            \n        y = y.T\n        #Flip the x or y on their axis\n        if flip_v:\n            x = x[::-1, :, :]\n            y = y[::-1, :]\n\n        if flip_h:\n            x = x[:, ::-1, :]\n            y = y[:, ::-1]\n            \n        flipped_x.append(x)\n        flipped_y.append(y.T)\n    return np.array(x), np.array(y.flatten())","metadata":{"execution":{"iopub.status.busy":"2022-06-30T08:22:30.188536Z","iopub.execute_input":"2022-06-30T08:22:30.188945Z","iopub.status.idle":"2022-06-30T08:22:30.204078Z","shell.execute_reply.started":"2022-06-30T08:22:30.188913Z","shell.execute_reply":"2022-06-30T08:22:30.202728Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Data Loader","metadata":{}},{"cell_type":"code","source":"import torch\nfrom torch.utils.data import Dataset, DataLoader\nimport torch.nn as nn\n\nDEVICE = \"cuda\" if torch.cuda.is_available() else \"cpu\"\nprint(\"Current device:\", DEVICE.upper())\n\nt_size = 160\n\nclass ImageDataLoader(Dataset):\n    def __init__(self, df, img_size=160, rotate=True, train=True):\n        self.df = df\n        self.img_size = img_size\n        self.is_train = train\n        self.rotate = rotate\n        loc = \"train\" if self.is_train else \"test\"\n        \n        self.paths = df[\"id\"].apply(\n            lambda x: f\"../input/hubmap-organ-segmentation/{loc}_images/{x}.tiff\"\n        )\n        \n    def __len__(self):\n        return self.df.shape[0]\n    \n    def __getitem__(self, idx):\n        '''Fetch image at index idx. If train, fetch the labels.'''\n        path = self.paths[idx]\n        #Read Image\n        image = Image.open(path)\n        image = image.resize((self.img_size, self.img_size))\n        image = np.array(image).astype(float)\n        image /= 255.\n        image = torch.Tensor(image)\n        \n        if self.is_train:\n            label = rle_to_2d_arr(self.df.rle[idx], self.df.img_width[idx])\n            label = torch.Tensor(label)\n            if self.rotate:\n                image, label = self.flip_image(image, label)\n            return torch.Tensor(image), label.flatten()\n        else:\n            if self.rotate:\n                image = self.flip_image(image)\n            return torch.Tensor(image)\n    \n    def flip_image(self, image, label=None):\n        flip_v = int(np.random.random()>0.5)\n        flip_h = int(np.random.random()>0.5)\n        dims = []\n        if flip_v: dims.append(0)\n        if flip_h: dims.append(1)\n        #Flip the image or label on their axis\n        image = torch.flip(image, dims)\n            \n        if self.is_train:\n            label = torch.flip(label.T, dims)\n            return image, label.T\n        else:\n            return image","metadata":{"execution":{"iopub.status.busy":"2022-06-30T08:22:30.659303Z","iopub.execute_input":"2022-06-30T08:22:30.659815Z","iopub.status.idle":"2022-06-30T08:22:32.663331Z","shell.execute_reply.started":"2022-06-30T08:22:30.659775Z","shell.execute_reply":"2022-06-30T08:22:32.662043Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"test = ImageDataLoader(train_df)\nprint(test[100][0].shape, test[100][1].shape)\nshow_masked_img(*test[1], 'Sample')","metadata":{"execution":{"iopub.status.busy":"2022-06-30T08:22:32.667301Z","iopub.execute_input":"2022-06-30T08:22:32.667898Z","iopub.status.idle":"2022-06-30T08:22:34.882715Z","shell.execute_reply.started":"2022-06-30T08:22:32.667863Z","shell.execute_reply":"2022-06-30T08:22:34.881757Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#Initialize train and validation data\nfrom sklearn.model_selection import train_test_split as tts\n\nBATCH_SIZE = 16\n\ndef data_collate(batch):\n    images, labels = [], []\n    for data in batch:\n        images.append(data[0])\n        labels.append(data[1])\n    #pad labels to match batch size\n        \n    return torch.stack(images), torch.stack(labels)\n\ndf_train, df_valid = tts(train_df, test_size = 0.1, shuffle=True)\ndf_train, df_valid = df_train.reset_index(drop=True), df_valid.reset_index(drop=True)\nprint(\"Train:\", df_train.shape)\nprint(\"Validation:\", df_valid.shape)\n\ntrain_data = ImageDataLoader(df_train)\nvalid_data = ImageDataLoader(df_valid, rotate=False)\n\ntrain_loader = DataLoader(train_data, batch_size=BATCH_SIZE, shuffle=True,\n                          drop_last=True, collate_fn=data_collate, num_workers=2,\n                          prefetch_factor=BATCH_SIZE//2)\n\nvalid_loader = DataLoader(valid_data, batch_size=BATCH_SIZE, shuffle=False,\n                          drop_last=False, collate_fn=data_collate, num_workers=2,\n                          prefetch_factor=BATCH_SIZE//2)","metadata":{"execution":{"iopub.status.busy":"2022-06-30T08:22:34.884036Z","iopub.execute_input":"2022-06-30T08:22:34.885152Z","iopub.status.idle":"2022-06-30T08:22:35.315504Z","shell.execute_reply.started":"2022-06-30T08:22:34.885112Z","shell.execute_reply":"2022-06-30T08:22:35.313704Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Model","metadata":{}},{"cell_type":"code","source":"import torchvision\n\nclass MaskerModel(nn.Module):\n    def __init__(self):\n        super().__init__()\n        self.resnet = torchvision.models.resnet18(pretrained=False)\n        num_ftrs = self.resnet.fc.in_features\n        self.resnet.fc = nn.Linear(num_ftrs, 2048)\n        self.relu = nn.ReLU()\n        self.dropout = nn.Dropout(0.25)\n        self.out_fc = nn.Linear(2048, t_size**2)\n        self.sigmoid = nn.Sigmoid()\n    \n    def forward(self, image):\n        i = image.reshape((-1, 3, t_size, t_size))\n        o = self.resnet(i)\n        return self.sigmoid(self.out_fc(self.dropout(self.relu(o))))","metadata":{"execution":{"iopub.status.busy":"2022-06-30T08:22:35.319152Z","iopub.execute_input":"2022-06-30T08:22:35.320299Z","iopub.status.idle":"2022-06-30T08:22:35.665886Z","shell.execute_reply.started":"2022-06-30T08:22:35.320252Z","shell.execute_reply":"2022-06-30T08:22:35.664305Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"m = MaskerModel()\nm","metadata":{"execution":{"iopub.status.busy":"2022-06-30T08:22:35.667432Z","iopub.execute_input":"2022-06-30T08:22:35.667792Z","iopub.status.idle":"2022-06-30T08:22:36.533441Z","shell.execute_reply.started":"2022-06-30T08:22:35.667761Z","shell.execute_reply":"2022-06-30T08:22:36.531695Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"for images, labels in valid_loader:\n    #print(images, labels)\n    preds = m(images)\n    print(preds.shape)\n    break\n\ncounts = 0\nfor c, (a, b) in zip(preds.detach(), zip(images, labels)):\n    print(\"#\"*100)\n    show_masked_img(a, b, f'Original #{counts}')\n    show_masked_img(a, c, f'Predicted #{counts}')\n    counts += 1\n    if counts > 4: print('#'*100);break","metadata":{"execution":{"iopub.status.busy":"2022-06-30T08:22:36.535269Z","iopub.execute_input":"2022-06-30T08:22:36.535712Z","iopub.status.idle":"2022-06-30T08:22:52.408204Z","shell.execute_reply.started":"2022-06-30T08:22:36.535672Z","shell.execute_reply":"2022-06-30T08:22:52.406752Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"del images, labels, preds, a, b, c, m\ngc.collect()","metadata":{"execution":{"iopub.status.busy":"2022-06-30T08:22:52.410330Z","iopub.execute_input":"2022-06-30T08:22:52.410792Z","iopub.status.idle":"2022-06-30T08:22:52.565419Z","shell.execute_reply.started":"2022-06-30T08:22:52.410751Z","shell.execute_reply":"2022-06-30T08:22:52.563318Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Train","metadata":{}},{"cell_type":"code","source":"model = MaskerModel().to(DEVICE)\n\noptim = torch.optim.Adam(model.parameters(), lr=1e-4, weight_decay=1e-6, amsgrad=False)\nscheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optim, T_max=4, eta_min=1e-6, last_epoch=-1)\ncriterion = nn.BCELoss()\n\nEPOCHS = 100\nno_improvement_limit = 20\nno_impr_count = 0\nbest_loss = np.inf\n\nfor epoch in range(EPOCHS):\n    print(f\"Epoch [{epoch+1}/{EPOCHS}]:\")\n\n    if time.time() - start_time > time_limit:\n        break #Avoid TimeLimitExceeded Error\n\n    model.train()\n    train_loss = 0\n    for images, labels in tqdm(train_loader, desc='train...'):\n        images = images.to(DEVICE)\n        labels = labels.to(DEVICE)\n        optim.zero_grad()\n        preds = model(images)\n        loss = criterion(preds, labels)\n        train_loss += loss.item()\n        loss.backward()\n        optim.step()    \n    train_loss /= len(train_loader)\n    \n    valid_loss = 0\n    for images, labels in tqdm(valid_loader, desc='validation...'):\n        images = images.to(DEVICE)\n        labels = labels.to(DEVICE)\n        with torch.no_grad():\n            preds = model(images)\n            loss = criterion(preds, labels)\n            valid_loss += loss.item()\n    valid_loss /= len(valid_loader)\n    \n    print(f\"[epoch {epoch+1}/{EPOCHS}] train_loss:{train_loss:.4f}, valid_loss:{valid_loss:.4f}\")\n    \n    if epoch%5 == 0:\n        show_masked_img(images.detach()[0], labels.detach()[0], f'Original epoch {epoch}')\n        show_masked_img(images.detach()[0], preds.detach()[0], f'Predicted epoch {epoch}')\n\n    if valid_loss < best_loss:\n        no_impr_count = 0\n        best_loss = valid_loss\n        torch.save(model.state_dict(), \"hubseg_model.pth\")\n        print(\"Saved...\")\n    else:\n        no_impr_count += 1\n        print(\"Loss did not improve...\")\n        if no_impr_count >= no_improvement_limit:\n            print(\"Early stopping!\")\n            break","metadata":{"execution":{"iopub.status.busy":"2022-06-30T08:22:52.566976Z","iopub.execute_input":"2022-06-30T08:22:52.567607Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Preview of train predictions","metadata":{}},{"cell_type":"code","source":"for images, labels in train_loader:\n    preds = model(images)\n    print(preds.shape)\n    break\ncounts = 0\nfor c, (a, b) in zip(preds.detach(), zip(images, labels)):\n    print(\"#\"*100)\n    show_masked_img(a, b, f'Original #{counts}')\n    show_masked_img(a, c, f'Predicted #{counts}')\n    counts += 1\n    if counts > 4: print('#'*100);break","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Preview of validation predictions","metadata":{}},{"cell_type":"code","source":"for images, labels in valid_loader:\n    preds = model(images)\n    print(preds.shape)\n    break\ncounts = 0\nfor c, (a, b) in zip(preds.detach(), zip(images, labels)):\n    print(\"#\"*100)\n    show_masked_img(a, b, f'Original #{counts}')\n    show_masked_img(a, c, f'Predicted #{counts}')\n    counts += 1\n    if counts > 4: print('#'*100);break","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"del train_loader, valid_loader, images, labels, preds, a, b, c\ngc.collect()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Prediction","metadata":{}},{"cell_type":"code","source":"test_df = pd.read_csv(\"../input/hubmap-organ-segmentation/test.csv\")\ntest_data = ImageDataLoader(test_df, rotate=False, train=False)\ntest_loader = DataLoader(test_data, batch_size=BATCH_SIZE, shuffle=False,\n                          drop_last=False, num_workers=2,\n                          prefetch_factor=BATCH_SIZE//2)\n\nmodel.load_state_dict(torch.load(\"hubseg_model.pth\", map_location=DEVICE))\n\npreds = []\n\nfor image in tqdm(test_loader, desc='Predicting...'):\n    with torch.no_grad():\n        pred = model(image)\n        preds.append(pred.detach().numpy())\n\npreds = np.concatenate(preds,axis=0)\norig_size = test_df.img_height.values\n\nsub = {'id':[], 'rle':[]}\nfor i, (p, s) in zip(test_df.id, zip(preds, orig_size)):\n    sub['id'].append(i)\n    sub['rle'].append(mask2rle(p, s))\n\nsub = pd.DataFrame(sub)\nsub.to_csv('submission.csv', index=False)\nsub","metadata":{"trusted":true},"execution_count":null,"outputs":[]}]}