{"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 torch\nimport os\nimport gc\nfrom PIL import Image\n\nimport torchvision\nfrom torch.utils.data import DataLoader\nimport torchvision.transforms as transforms\nimport pytorch_lightning as pl\nimport torch.nn.functional as F\n\nimport numpy as np\nimport json\nimport requests\nimport matplotlib.pyplot as plt\nimport warnings\nimport glob\nimport pandas as pd\nimport tqdm\nimport random\nwarnings.filterwarnings('ignore')\n%matplotlib inline\n\ndevice = torch.device(\"cuda\") if torch.cuda.is_available() else torch.device(\"cpu\")\nprint(f'Using {device} for inference')\n\nimport os\nimport sys\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\nimport matplotlib\nimport matplotlib.pyplot as plt\nimport PIL\nfrom IPython.display import Image, display\nimport openslide\n# import skimage.io\n# import tifffile\nfrom tqdm.notebook import tqdm\nimport zipfile\nimport cv2 as cv\n","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","execution":{"iopub.status.busy":"2023-05-14T21:09:53.985604Z","iopub.execute_input":"2023-05-14T21:09:53.985979Z","iopub.status.idle":"2023-05-14T21:09:53.996015Z","shell.execute_reply.started":"2023-05-14T21:09:53.98595Z","shell.execute_reply":"2023-05-14T21:09:53.994787Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### **Lets start with the tiling code!**","metadata":{}},{"cell_type":"code","source":"# Location of the files\ndata_dir = '/kaggle/input/prostate-cancer-grade-assessment/test_images'\nmask_dir = '/kaggle/input/prostate-cancer-grade-assessment/test_label_masks'\n\n# Location of training labels\ntrain_labels = pd.read_csv('/kaggle/input/prostate-cancer-grade-assessment/test.csv').set_index('image_id')\nfile_names = train_labels.index.tolist()","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# This function takes an openslide object and returns the top left coordinates of N tiles (of a given size) with the most tissue pixels. \n# Note: slide.level_dimensions[level] = (width,height).\n# Note: padding is done to the right and bottom, this is to keep it simple while having at most 1 tile in memory at a time.\ndef get_tile_locations_from_slide(slide, tile_size, N, level):\n    tiles = []\n    required_padding = False\n    xlocs, ylocs = np.arange(0, slide.level_dimensions[level][0], tile_size), np.arange(0, slide.level_dimensions[level][1], tile_size) # Get the coordinates of the top left corners of the tiles.\n    for x_i, xloc in enumerate(xlocs):\n        for y_i, yloc in enumerate(ylocs):\n            region = np.copy(slide.read_region((xloc*(4**level),yloc*(4**level)), level, (tile_size,tile_size))) # The position is wrt. level 0, so must convert to level 0 coordinates by multiplying by the downsampling factor.\n            region_arr = np.asarray(region)[:,:,:3] # Ignore the alpha channel\n            if xloc+tile_size > slide.level_dimensions[level][0] or yloc+tile_size > slide.level_dimensions[level][1]: # if the tile goes out of bounds\n                region_arr[region_arr==0] = 255\n                required_padding = True\n            pixel_sum = region_arr.sum()\n            tiles.append({'xloc': xloc, 'yloc': yloc, 'pixel_sum': pixel_sum, 'required_padding': required_padding}) # store top left corner location and the tile's pixel_sum\n            required_padding = False\n    sorted_tiles = sorted(tiles, key= lambda d: d['pixel_sum']) # Sort tiles based on their pixel_sum field\n    sorted_tiles = sorted_tiles[:N] # Get top N tiles\n    return sorted_tiles\n","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Plots the selected slices over the original slide.\ndef show_tile_locations(slide, tiles, tile_size, level, mask=None):\n    wsi = slide.read_region((0,0), level, slide.level_dimensions[level]) # Get whole slide image pixel values\n    wsi = np.asarray(wsi)[:,:,:3] # Convert to array (optional) and ignore alpha channel\n    fig, ax = plt.subplots()\n    ax.imshow(wsi)\n    if mask is not None:\n        mask_data = mask.read_region((0,0), level, slide.level_dimensions[level]) # Get whole mask\n        mask_data = np.asarray(mask_data)[:,:,0] # Convert to array and get only the Red channel (contains the mask data)\n        ax.imshow(wsi)\n        \n    for tile in tiles: # Draw the tiles\n        rect = matplotlib.patches.Rectangle((tile['xloc'],tile['yloc']),tile_size,tile_size, linewidth=1, edgecolor='r', facecolor='none')\n        ax.add_patch(rect)\n    plt.show()","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Creates a single image (array) from the selected tiles\ndef create_tiled_image(slide, tiles, tile_size, N_tiles, level):\n    N_side = int(np.sqrt(N_tiles)) # How many tiles is the image wide/tall\n    tiled_image = np.ones((N_side*tile_size,N_side*tile_size,3), dtype=np.uint8)*255\n    for i, tile in enumerate(tiles):\n        region = np.copy(np.asarray(slide.read_region((tile['xloc']*(4**level),tile['yloc']*(4**level)), level, (tile_size,tile_size)))) # The position is wrt. level 0, so must convert to level 0 coordinates by multiplying by the downsampling factor.\n        if tile['required_padding']:\n            region[region==0] = 255\n        tiled_image[tile_size*(i//(N_side)):tile_size*(i//(N_side))+tile_size, tile_size*(i%(N_side)) : tile_size*(i%(N_side))+tile_size, :] = region[:,:,:3]\n    return tiled_image","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def save_img(img, filename, folder):\n    if not os.path.exists(output_folder):\n        os.makedirs(folder)\n    img = PIL.Image.fromarray(img)\n    img.save(os.path.join(folder, filename+\"_tiled.png\"))\n    \ndef save_img_to_zip(img, filename, zip_file):\n    encoded_img = cv.imencode('.png', img)[1]\n    zip_file.writestr(filename+'_tiled.png', encoded_img)","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Creates a single image (array) from the selected tiles\ndef save_tiles(slide, tiles, tile_size, N_tiles, out_path, level=1):\n    for n, tile in enumerate(tiles):\n        region = np.asarray(slide.read_region((tile['xloc']*(4**level),tile['yloc']*(4**level)), level, (tile_size,tile_size))) # The position is wrt. level 0, so must convert to level 0 coordinates by multiplying by the downsampling factor.\n        if tile['required_padding']:\n            region[region==0] = 255\n    img = PIL.Image.fromarray(img)\n    img.save(os.path.join(folder, filename+\"_tiled.png\"))\n    return tiled_image","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# THE PARAMETERS 🔥\nN_tiles = 6**2 # Number of tiles per image, should have a whole square root\ntile_size = 2**8 # Width/height of tile, 2**8 = 256\nlevel = 1 # 0 is highest resolution, 2 is lowest resolution, good compromise is level 1\nout_dir = '/kaggle/Group3_PANDA_tiles/'\nos.makedirs(out_dir, exist_ok=True)\nplot_output = False\n\nfor file_name in tqdm(file_names[:10], desc='WSIs', leave=False, position=0):\n    save_path = out_dir+file_name\n    os.makedirs(save_path, exist_ok=True)\n\n    slide = openslide.OpenSlide(os.path.join(data_dir, file_name+'.tiff'))\n    tiles = get_tile_locations_from_slide(slide, tile_size, N_tiles, level) # Get tile coordinates of top N tiles\n\n#     tiled_image = create_tiled_image(slide, tiles, tile_size, N_tiles, level) # Convert the tiles information into a tiled image\n\n    if plot_output:\n        show_tile_locations(slide, tiles, tile_size, level) # Plot the tiles over the original slide\n        plt.imshow(tiled_image)\n        plt.show()\n\n    slide.close()","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### **Now we can load the model and write the csv file!**","metadata":{}},{"cell_type":"code","source":"########## THIS IF FOR WHOLE IMAGES ################\n#Defining the tile Data Module  TODO: use the imghash to make sure patients arent in test and train set\n\nMAX_EPOCHS = 1000\nclass PANDADataset(torch.utils.data.Dataset):\n    def __init__(self, dataset: str= 'train'):\n#         assert dataset in ['train', 'test'], \"dataset should one of \\\"train\\\" or \\\"test\\\"\"\n        \n        super().__init__()\n        self.df = pd.read_csv(f'/kaggle/input/prostate-cancer-grade-assessment/test.csv')\n#         self.df.drop(self.df[self.df['image_id'] == 'ffe9bcababc858e04840669e788065a1'].index, inplace = True)\n        self.imgdir = '/kaggle/input/prostate-cancer-grade-assessment/test_images'\n        assert len(self.df) == len(self.df['image_id'].unique())\n        self.num_classes = 6\n\n    def convert_to_ordinal(self, n: int, nclasses: int):\n        ordinal = torch.zeros(nclasses)\n        ordinal[0:n+1] = 1\n        return ordinal\n        \n    def load_tiles(self, samplepath):\n        tiles = glob.glob(os.path.join(samplepath, 'tile_*.png'))\n        tiles = [torchvision.io.read_image(tile)/255 for tile in tiles]\n        tiles = torch.stack(tiles)\n        return tiles\n\n    def __getitem__(self, idx):\n        row = self.df.iloc[idx]\n        return torchvision.io.read_image(os.path.join(folder, row['image_id']+\"_tiled.png\")/255\n    \n    def __len__(self):\n        return len(self.df)\n    \nprint(f\"{torch.cuda.memory_allocated()*1e-9:.4f}, GiB\")\ntest_data = PANDADataset()\nprint(f\"{torch.cuda.memory_allocated()*1e-9:.4f}, GiB\")\n\nBATCH_SIZE = 2\ntest_dataloader = DataLoader(test_data, batch_size=BATCH_SIZE, shuffle=True)\nprint(f\"{torch.cuda.memory_allocated()*1e-9:.4f}, GiB\")\n      \nindex = random.randint(0, len(test_data))\nprint(test.__getitem__(index))\nprint(training_data.__getitem__(index)[0].size(), training_data.__getitem__(index)[1].size())","metadata":{"execution":{"iopub.status.busy":"2023-05-14T21:53:37.753371Z","iopub.execute_input":"2023-05-14T21:53:37.754224Z","iopub.status.idle":"2023-05-14T21:53:37.836272Z","shell.execute_reply.started":"2023-05-14T21:53:37.754189Z","shell.execute_reply":"2023-05-14T21:53:37.834897Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"class efficientnetModule(pl.LightningModule):\n    def __init__(self):\n        super().__init__()\n        self.best_acc = torch.tensor(float('-inf'))\n        \n        self.val_losses_epoch = torch.empty(len(val_dataloader), len(training_data.__getitem__(index)[1])) #len(dataloader) == #batches\n        self.val_losses_epochs = torch.empty(MAX_EPOCHS)\n        \n        self.train_losses_epoch = torch.empty(len(train_dataloader), len(training_data.__getitem__(index)[1]))\n        self.train_losses_epochs = torch.empty(MAX_EPOCHS)\n        \n        self.val_accs_epoch = torch.empty(len(val_dataloader), len(training_data.__getitem__(index)[1]))\n        self.val_accs_epochs = torch.empty(MAX_EPOCHS)\n \n        self.model = torch.hub.load('NVIDIA/DeepLearningExamples:torchhub', 'nvidia_efficientnet_b0', pretrained=True) #todo: change model to ISUP output\n#         self.utils = torch.hub.load('NVIDIA/DeepLearningExamples:torchhub', 'nvidia_convnets_processing_utils')\n        \n        #change last later into correct # of classes\n        self.model.classifier.fc = torch.nn.Linear(in_features=1280, out_features=training_data.num_classes, bias=True)\n    \n    def get_prediction(self, output):\n        #this changes the prediction  of format (0.01, 0.9, 0.8) to (1., 1., 0.)\n        for i, prediction in enumerate(output):\n            maxi = torch.argmax(prediction)\n            prediction[0:maxi+1] = 1\n            prediction[maxi+1:] = 0\n            output[i] = prediction\n        return output\n    \n    def forward(self, x):\n        output = self.model(x)\n        return output\n\n    def training_step(self, batch, batch_idx):\n        x, y = batch\n        x = x.float()\n        y_hat = self.model(x)\n        loss = F.cross_entropy(y_hat, y)\n        self.train_losses_epoch[batch_idx] = loss\n        \n        #clear memory\n#         del x\n#         del y\n#         gc.collect()\n#         torch.cuda.empty_cache()\n\n        return loss\n    \n    def on_training_epoch_end(self):\n        loss = torch.mean(self.train_losses_epoch)\n        self.log(\"train_loss\", loss)\n        self.train_losses_epochs[self.current_epoch] = loss\n        self.train_losses_epoch = torch.empty(self.train_losses_epoch.size())\n\n    \n    def validation_step(self, batch, batch_idx):\n        with torch.no_grad(): #just to be sure, cudaMM error during training\n            x, y = batch\n            x = x.float()\n            y_hat = self.model(x)\n            loss = F.cross_entropy(y_hat, y)\n            self.val_losses_epoch[batch_idx] = loss\n\n            #calculate validation accuracy of batch\n            predictions = self.get_prediction(y_hat)\n            assert y_hat.size() == y.size()\n            sim = [(y[i] == y_hat[i]).all() for i in range(y_hat.size(0))]\n            val_acc = sum(sim)/y.size(0)\n            self.val_accs_epoch[batch_idx] = val_acc\n\n            #clear memory\n#             del x\n#             del y\n#             del predictions\n#             del sim\n    #         gc.collect()\n#             torch.cuda.empty_cache()\n        \n    def on_validation_epoch_end(self):\n        #validation loss\n        loss = torch.mean(self.val_losses_epoch)\n        self.log(\"val_loss\", loss)\n        self.val_losses_epochs[self.current_epoch] = loss\n        self.val_losses_epoch = torch.empty(self.val_losses_epoch.size())\n\n        #validation accuracy\n        acc = torch.mean(self.val_accs_epoch)\n        self.log(\"val_acc\", acc)\n        self.val_accs_epochs[self.current_epoch] = acc\n        self.val_accs_epoch = torch.empty(self.val_accs_epoch.size())\n        \n        #save best model\n        if acc > self.best_acc:\n            trainer.save_checkpoint(f\"best_model.ckpt\") #todo fixe validation acc calculation(just on batch is dumb)\n            self.best_acc = acc\n        \n    def on_test_epoch_end(self):\n        #validation loss\n        loss = torch.mean(self.val_losses_epoch)\n        self.log(\"test_loss\", loss)\n        self.test_losses_epochs[self.current_epoch] = loss\n        self.test_losses_epoch = torch.empty(self.test_losses_epoch.size())\n\n        #validation accuracy\n        acc = torch.mean(self.test_accs_epoch)\n        self.log(\"test_acc\", acc)\n        self.test_accs_epochs[self.current_epoch] = acc\n        self.test_accs_epoch = torch.empty(self.test_accs_epoch.size())\n        \n    \n    def configure_optimizers(self):\n        return torch.optim.Adam(self.model.classifier.fc.parameters(), lr=0.02)\n    \nprint(f\"{torch.cuda.memory_allocated()*1e-9:.4f}, GiB\")\nmodule = efficientnetModule()\nmodule.to(device)\nprint(f\"{torch.cuda.memory_allocated()*1e-9:.4f}, GiB\")\n# module.model.eval().to(device)\n# print(module.model['classifier'])","metadata":{"execution":{"iopub.status.busy":"2023-05-14T22:28:06.174707Z","iopub.execute_input":"2023-05-14T22:28:06.175136Z","iopub.status.idle":"2023-05-14T22:28:06.599947Z","shell.execute_reply.started":"2023-05-14T22:28:06.175093Z","shell.execute_reply":"2023-05-14T22:28:06.598716Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"trainer = pl.Trainer()\nchk_path = \"/kaggle/input/modello/best_model(1).ckpt\"\nmodel2 = efficientnetModule.load_from_checkpoint(chk_path)\n# results = trainer.test(model=model2, datamodule=efficientnetModule, verbose=True)\n# results","metadata":{"execution":{"iopub.status.busy":"2023-05-14T21:10:20.686275Z","iopub.execute_input":"2023-05-14T21:10:20.686647Z","iopub.status.idle":"2023-05-14T21:10:21.730059Z","shell.execute_reply.started":"2023-05-14T21:10:20.686617Z","shell.execute_reply":"2023-05-14T21:10:21.728701Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"with open('/kaggle/working/submission.csv', 'w') as submis:\n    submis.write('image_id,isup_grade')\n    with torch.no_grad():\n        for test_img in test_data:\n            result = model2.model(test_img)\n            submis.write(f'{testimg},{int(torch.sum(self.get_prediction(y_hat)))}')","metadata":{"execution":{"iopub.status.busy":"2023-05-14T21:57:06.076458Z","iopub.execute_input":"2023-05-14T21:57:06.076882Z","iopub.status.idle":"2023-05-14T21:57:06.089656Z","shell.execute_reply.started":"2023-05-14T21:57:06.076834Z","shell.execute_reply":"2023-05-14T21:57:06.088714Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"with open('twmp.txt', 'w') as f:\n    f.write(' ')\n","metadata":{"execution":{"iopub.status.busy":"2023-05-14T22:49:47.878615Z","iopub.execute_input":"2023-05-14T22:49:47.879201Z","iopub.status.idle":"2023-05-14T22:49:47.889659Z","shell.execute_reply.started":"2023-05-14T22:49:47.879151Z","shell.execute_reply":"2023-05-14T22:49:47.887103Z"},"trusted":true},"execution_count":null,"outputs":[]}]}