{"cells":[{"metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"cell_type":"code","source":"# This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load in \n\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\n# Input data files are available in the \"../input/\" directory.\n# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\n\nimport os\n# for dirname, _, filenames in os.walk('/kaggle/input'):\n#     for filename in filenames:\n#         print(os.path.join(dirname, filename))\n\n# Any results you write to the current directory are saved as output.","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","trusted":true},"cell_type":"code","source":"# Imports\nimport openslide\nimport PIL\nimport skimage.io\nimport cv2\nfrom tqdm.notebook import tqdm\nimport time\nimport torch\nimport sys\n!pip install pretrainedmodels\nimport pretrainedmodels\nfrom sklearn import model_selection\nimport torch.nn as nn\nimport albumentations","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# PATHS\nIMG_PATH = '../input/prostate-cancer-grade-assessment/train_images/'\nMSK_PATH = '../input/prostate-cancer-grade-assessment/train_label_masks/'\nSAVE_PATH = '/kaggle/Resized/'\nTEST_PATH = '../input/prostate-cancer-grade-assessment/test_images/'\n\nDIMS = (512, 512)\nTRAIN_BAT_SIZE = 32\nVALID_BAT_SIZE = 16\nDEVICE = 'cuda'\nEPOCHS = 40\nFOLDS = 4\nTRAIN_FOLDS = [4,3,1,2] # FOLDS must always remain in train folds for StratifiedShuffleSplit\nVAL_FOLDS = [0]\nMODEL_MEAN=(0.485, 0.456, 0.406)\nMODEL_STD=(0.229, 0.224, 0.225)\n\n!ls ../input/prostate-cancer-grade-assessment","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"train_csv = pd.read_csv('../input/prostate-cancer-grade-assessment/train.csv')\n# train_csv = train_csv.sample(frac = 1).reset_index(drop=True)\n\ntrain_csv['gleason_score'] = train_csv['gleason_score'].replace('negative', '0+0')\ntrain_csv['g_score1'] = train_csv['gleason_score'].apply(lambda x: int(x.split('+')[0]))\ntrain_csv['g_score2'] = train_csv['gleason_score'].apply(lambda x: int(x.split('+')[1]))\n\ntrain_csv['kfolds'] = FOLDS\n# kf = model_selection.StratifiedKFold(n_splits = FOLDS, shuffle = False, random_state = 10)\nsss = model_selection.StratifiedShuffleSplit(n_splits=FOLDS, test_size=0.05, random_state = 10)\nfor fold, (train_idx, val_idx) in enumerate(sss.split(X = train_csv, y=train_csv.isup_grade.values)):\n    print(len(train_idx), len(val_idx))\n    train_csv.loc[val_idx, 'kfolds'] = fold\n# train_csv.head()\ntrain_csv.to_csv('/kaggle/train_folds.csv', index = False)\ntrain_csv = pd.read_csv('/kaggle/train_folds.csv')\ntrain_csv['has_mask'] = 1\n# train_csv[train_csv.kfolds == 0]\n# train_csv.kfolds.unique()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def print_slide_details(slide, show_thumbnail=True, max_size=(600,400)):\n    \"\"\"Print some basic information about a slide\"\"\"\n    # Generate a small image thumbnail\n    if show_thumbnail:\n        display(slide.get_thumbnail(size=max_size))\n\n    # Here we compute the \"pixel spacing\": the physical size of a pixel in the image.\n    # OpenSlide gives the resolution in centimeters so we convert this to microns.\n    spacing = 1 / (float(slide.properties['tiff.XResolution']) / 10000)\n    \n    print(f\"File id: {slide}\")\n    print(f\"Dimensions: {slide.dimensions}\")\n    print(f\"Microns per pixel / pixel spacing: {spacing:.3f}\")\n    print(f\"Number of levels in the image: {slide.level_count}\")\n    print(f\"Downsample factor per level: {slide.level_downsamples}\")\n    print(f\"Dimensions of levels: {slide.level_dimensions}\")","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# example_slides = [\n#     '005e66f06bce9c2e49142536caf2f6ee',\n#     '00928370e2dfeb8a507667ef1d4efcbb',\n#     '007433133235efc27a39f11df6940829',\n#     '024ed1244a6d817358cedaea3783bbde',\n# ]\n\n# for case_id in example_slides:\n#     biopsy = openslide.OpenSlide(os.path.join(IMG_PATH, f'{case_id}.tiff'))\n#     print_slide_details(biopsy)\n#     biopsy.close()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Start Here"},{"metadata":{"trusted":true},"cell_type":"markdown","source":"## Loading the images and resizing them\nWe'll load and resize images using the fastest methods as demonstrated by xhlulu in kernel: https://www.kaggle.com/xhlulu/panda-resize-and-save-train-data"},{"metadata":{"trusted":true},"cell_type":"code","source":"os.makedirs(SAVE_PATH, exist_ok = True)\n!ls","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def resize_and_save(img_id, dim, level):\n    img = skimage.io.MultiImage(IMG_PATH+img_id+'.tiff')\n    out = cv2.resize(img[level], dim)\n    cv2.imwrite(SAVE_PATH+f'{img_id}.png',out)\n    \ndef mask_resize_and_save(img_id, dim, level):\n    try:\n        img = skimage.io.MultiImage(MSK_PATH+img_id+'_mask.tiff')\n        out = cv2.resize(img[level], dim)\n        cv2.imwrite(SAVE_PATH+f'{img_id}_mask.png',out)\n    except:\n        print('hello')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# for img_id in tqdm(train_csv.image_id[:10]):\n#     resize_and_save(img_id, DIMS, -1)\n#     mask_resize_and_save(img_id, DIMS, -1)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# !ls /kaggle/Resized/\n!ls /kaggle/Resized -1 | wc -l","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### Overlays\nOverlaying the mask onto the images and resizing them into desired dimensions without changing the aspect ratio."},{"metadata":{"trusted":true},"cell_type":"code","source":"def overlay_mask_on_slide(img_id, center='radboud', level = 2, alpha=0.8, max_size=(1024, 1024), view = False, mask = False):\n    \"\"\"Outputs Mask overlayed on a slide to a image of desired dimensions without changing the aspect ratio.\n        Edited from https://www.kaggle.com/wouterbulten/getting-started-with-the-panda-dataset\"\"\"\n    if not mask:\n        slide = openslide.OpenSlide(os.path.join(IMG_PATH, f'{img_id}.tiff'))\n        slide_data = slide.read_region((0,0), level, slide.level_dimensions[level])\n        #slidPIL.Image.fromarray(slide_data).convert(mode = 'RGBA')\n        background = PIL.Image.new('RGBA', max_size, (255, 255, 255, 255))\n        #paste the image on max_size background\n        background.paste(slide_data, (0, 0), slide_data)\n        background.thumbnail(size=max_size, resample=0)\n        background.save(SAVE_PATH+f'{img_id}_overlayed.png')\n        \n    if center not in ['radboud', 'karolinska']:\n        raise Exception(\"Unsupported palette, should be one of [radboud, karolinska].\")\n\n    try:\n        mask = openslide.OpenSlide(os.path.join(MSK_PATH, f'{img_id}_mask.tiff'))\n    except:\n        # Skip over the image if the mask of image is not available\n        train_csv.loc[train_csv.image_id == img_id,'has_mask'] = 0\n        return\n    slide = openslide.OpenSlide(os.path.join(IMG_PATH, f'{img_id}.tiff'))\n\n    # Load data from the desired level\n    slide_data = slide.read_region((0,0), level, slide.level_dimensions[level])\n    mask_data = mask.read_region((0,0), level, mask.level_dimensions[level])\n\n    # Mask data is present in the R channel\n    mask_data = mask_data.split()[0]\n\n    # Create alpha mask\n    alpha_int = int(round(255*alpha))\n    if center == 'radboud':\n        alpha_content = np.less(mask_data.split()[0], 2).astype('uint8') * alpha_int + (255 - alpha_int)\n    elif center == 'karolinska':\n        alpha_content = np.less(mask_data.split()[0], 1).astype('uint8') * alpha_int + (255 - alpha_int)\n\n    alpha_content = PIL.Image.fromarray(alpha_content)\n    preview_palette = np.zeros(shape=768, dtype=int)\n\n    if center == 'radboud':\n        # Mapping: {0: background, 1: stroma, 2: benign epithelium, 3: Gleason 3, 4: Gleason 4, 5: Gleason 5}\n        preview_palette[0:18] = (np.array([0, 0, 0, 0.5, 0.5, 0.5, 0, 1, 0, 1, 1, 0.7, 1, 0.5, 0, 1, 0, 0]) * 255).astype(int)\n    elif center == 'karolinska':\n        # Mapping: {0: background, 1: benign, 2: cancer}\n        preview_palette[0:9] = (np.array([0, 0, 0, 0, 1, 0, 1, 0, 0]) * 255).astype(int)\n\n    mask_data.putpalette(data=preview_palette.tolist())\n    mask_rgb = mask_data.convert(mode='RGB')\n\n    overlayed_image = PIL.Image.composite(image1=slide_data, image2=mask_rgb, mask=alpha_content).convert('RGBA')\n\n    # reduce the size of image to max_size\n    overlayed_image.thumbnail(size=max_size, resample=0)\n\n    # create a white background of desired dimensions\n    background = PIL.Image.new('RGBA', max_size, (255, 255, 255, 255))\n    #paste the image on max_size background\n    background.paste(overlayed_image, (0, 0), overlayed_image)\n    background.thumbnail(size=max_size, resample=0)\n    background.save(SAVE_PATH+f'{img_id}_overlayed.png')\n    # To see the output images\n    if view:\n        display(background)\n    slide.close()\n    mask.close()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"for i, row in tqdm(train_csv.iterrows(), total = len(train_csv)):\n    overlay_mask_on_slide(row['image_id'], row['data_provider'], max_size = DIMS, mask = False)\n#     print(row)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Remove the data of images which have no mask\nprint(len(train_csv))\ntrain_csv = train_csv[train_csv['has_mask'] == 1]\ntrain_csv.to_csv('/kaggle/train_folds.csv', index = False)\nlen(train_csv)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Dataset\nclass PANDADataset:\n    def __init__(self, folds, mean, std): #, img_ht, img_wd,\n        df = pd.read_csv('/kaggle/train_folds.csv')\n        df = df[['image_id', 'g_score1', 'g_score2']]\n#         print('ds')\n#         df = df[df.kfold.isin(folds)].reset_index(drop=True)\n        self.image_ids = df.image_id.values\n        self.g_score1 = df.g_score1.values\n        self.g_score2 = df.g_score2.values\n#         self.isup = df.isup.values\n        \n        if len(folds) == 1:\n            self.aug = albumentations.Compose([\n#                 albumentations.Resize(img_ht, img_wd, always_apply = True), #  already resized\n                albumentations.Normalize(mean, std, always_apply = True)\n            ])\n        else:\n            self.aug = albumentations.Compose([\n#                 albumentations.Resize(img_ht, img_wd, always_apply = True),\n                albumentations.ShiftScaleRotate(shift_limit = -0.0625, \n                                                scale_limit = 0.1, \n                                                rotate_limit = 5,\n                                                p = 0.9),\n                albumentations.Normalize(mean, std, always_apply = True)\n            ])\n\n    def __len__(self):\n        return len(self.image_ids)\n\n    def __getitem__(self, item):\n#         print('pdb')\n#         import pdb; pdb.set_trace()\n        image = PIL.Image.open(SAVE_PATH+self.image_ids[item]+'_overlayed.png').convert('RGB')\n#         image = image.reshape(137, 236).astype(float)\n#         image = Image.fromarray(image).convert('RGB')\n        image = self.aug(image = np.array(image))['image']\n        # pdb.set_trace()\n        image = np.transpose(image, (2, 0, 1)).astype(np.float32)\n        return {\n            'image': torch.tensor(image, dtype = torch.float),\n            'g_score1': torch.tensor(self.g_score1[item], dtype = torch.long),\n            'g_score2': torch.tensor(self.g_score2[item], dtype = torch.long),\n#             'isup': torch.tensor(self.isup[item], dtype = torch.long),\n        }","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Model 1'Resnet34'\n\nclass ResNet34(nn.Module):\n    def __init__(self, pretrained, freeze = True):\n        super(ResNet34, self).__init__()\n        if pretrained:\n            self.model = pretrainedmodels.__dict__['resnet34'](pretrained = 'imagenet')\n        else:\n            self.model = pretrainedmodels.__dict__['resnet34'](pretrained = None)\n        if freeze:\n            for param in self.model.parameters():\n                param.requires_grad = False\n        layers = []\n        layers.append(nn.Linear(512, 256))\n        layers.append(nn.ReLU())\n        layers.append(nn.Linear(256, 6))\n        layers.append(nn.ReLU())\n        layers.append(nn.Linear(6, 128))\n        layers.append(nn.ReLU())\n        layers.append(nn.Linear(128, 256))\n        layers.append(nn.ReLU())\n        layers.append(nn.Linear(256, 6))\n        layers.append(nn.Sigmoid())\n\n        self.l0 = nn.Sequential(*layers)\n        self.l1 = nn.Sequential(*layers)\n\n    def forward(self, X):\n        bs, _, _, _ = X.shape\n        X = self.model.features(X)\n        X = nn.functional.adaptive_avg_pool2d(X, 1).reshape(bs, -1)\n        l0 = self.l0(X)\n        l1 = self.l1(X)\n        return l0, l1","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Training and Eval code\n\ndef train(dataset, data_loader, model, optimizer):\n    model.train()\n    for bi, d in tqdm(enumerate(data_loader), total = int(len(dataset) / data_loader.batch_size)):\n#         print('d')\n        image = d['image']\n        g_score1 = d['g_score1']\n        g_score2 = d['g_score2']\n        \n        image = image.to(DEVICE, dtype = torch.float)\n        g_score1 = g_score1.to(DEVICE, dtype = torch.long)\n        g_score2 = g_score2.to(DEVICE, dtype = torch.long)\n        \n        # resets gradients to zero for at the start of every mini-batch, so as not mix up the gradients.\n        optimizer.zero_grad()\n        \n        outputs = model(image)\n        targets = (g_score1, g_score2)\n        \n        loss = loss_function(outputs, targets)\n        \n        ''' Calling .backward() mutiple times accumulates the gradient (by addition) for each parameter.\n            This is why you should call optimizer.zero_grad() after each .step() call. \n            Note that following the first .backward call,\n            a second call is only possible after you have performed another forward pass\n        '''\n        loss.backward()\n\n        '''optimizer.step is performs a parameter update based on the current gradient \n           (stored in .grad attribute of a parameter) and the update rule\n        '''\n        optimizer.step()\n\ndef eval(dataset, data_loader, model):\n    model.eval()\n    final_loss = 0\n    counter = 0\n    for bi, d in tqdm(enumerate(data_loader), total = int(len(dataset) / data_loader.batch_size)):\n        counter += 1\n        image = d['image']\n        g_score1 = d['g_score1']\n        g_score2 = d['g_score2']\n        \n        image = image.to(DEVICE, dtype = torch.float)\n        g_score1 = g_score1.to(DEVICE, dtype = torch.long)\n        g_score2 = g_score2.to(DEVICE, dtype = torch.long)\n        \n        outputs = model(image)\n        targets = (g_score1, g_score2)\n        loss = loss_function(outputs, targets)\n        final_loss += loss\n    return final_loss/counter\n\ndef loss_function(outputs, targets):\n    o1, o2 = outputs\n    t1, t2 = targets\n    \n    l1 = nn.CrossEntropyLoss()(o1, t1)\n    l2 = nn.CrossEntropyLoss()(o2, t2)\n    \n    return (l1 + l2) / 2","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Driver Code\ndef main():\n    model = ResNet34(pretrained = True, freeze = False)\n    model.to(DEVICE)\n    \n    train_dataset = PANDADataset(\n        folds = TRAIN_FOLDS,\n#         img_ht = DIMS[0],\n#         img_wd = DIMS[1],\n        mean = MODEL_MEAN,\n        std = MODEL_STD\n    )\n    \n    train_loader = torch.utils.data.DataLoader(\n        dataset = train_dataset,\n        batch_size = TRAIN_BAT_SIZE,\n        shuffle = True,\n        num_workers = 4\n    )\n    \n    valid_dataset = PANDADataset(\n        folds = VAL_FOLDS,\n#         img_ht = DIMS[0],\n#         img_wd = DIMS[1],\n        mean = MODEL_MEAN,\n        std = MODEL_STD\n    )\n    \n    valid_loader = torch.utils.data.DataLoader(\n        dataset = valid_dataset,\n        batch_size = VALID_BAT_SIZE,\n        shuffle = True,\n        num_workers = 4\n    )\n    \n    optimizer = torch.optim.Adam(model.parameters(), lr = 1e-4)\n    scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(optimizer, mode = 'min',\n                                                          patience = 3, factor = 0.3, verbose = True)\n#     print('a')\n    \n    for epoch in tqdm(range(EPOCHS)):\n#         print('b')\n        train(train_dataset, train_loader, model, optimizer)\n#         print('c')\n        with torch.no_grad():\n            val_score = eval(valid_dataset, valid_loader, model)\n        scheduler.step(val_score)\n        if epoch % 10 == 9:\n            torch.save(model.state_dict(), f'model_{VAL_FOLDS[0]}_{epoch}.bin')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"main()","execution_count":null,"outputs":[]}],"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"}},"nbformat":4,"nbformat_minor":4}