{"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":"# Per WSI, create a square image of N tiles, based on number of tissue pixels (and their darkness).\nThis code is inspired by https://www.kaggle.com/code/iafoss/panda-16x128x128-tiles","metadata":{}},{"cell_type":"code","source":"import 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\nimport skimage.io\n# import tifffile\nfrom tqdm.notebook import tqdm\nimport zipfile\nimport cv2 as cv","metadata":{"execution":{"iopub.status.busy":"2023-05-17T18:56:43.647707Z","iopub.execute_input":"2023-05-17T18:56:43.649155Z","iopub.status.idle":"2023-05-17T18:56:44.527771Z","shell.execute_reply.started":"2023-05-17T18:56:43.649103Z","shell.execute_reply":"2023-05-17T18:56:44.526277Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Location of the files\ndata_dir = '/kaggle/input/prostate-cancer-grade-assessment/train_images'\nmask_dir = '/kaggle/input/prostate-cancer-grade-assessment/train_label_masks/'\nout_dir = '/kaggle/working/tiled_images/'\nif not os.path.exists(out_dir):\n    os.makedirs(out_dir)\n# Location of training labels\nsamples = pd.read_csv('/kaggle/input/prostate-cancer-grade-assessment/train.csv')","metadata":{"execution":{"iopub.status.busy":"2023-05-17T18:56:44.530234Z","iopub.execute_input":"2023-05-17T18:56:44.530655Z","iopub.status.idle":"2023-05-17T18:56:44.685368Z","shell.execute_reply.started":"2023-05-17T18:56:44.530614Z","shell.execute_reply":"2023-05-17T18:56:44.683855Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"## Takes an openslide object and returns the top left coordinates of N tiles (of a given size) with the most tissue pixels. \n\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","metadata":{"execution":{"iopub.status.busy":"2023-05-17T18:56:44.687025Z","iopub.execute_input":"2023-05-17T18:56:44.687436Z","iopub.status.idle":"2023-05-17T18:56:44.70312Z","shell.execute_reply.started":"2023-05-17T18:56:44.687399Z","shell.execute_reply":"2023-05-17T18:56:44.701428Z"},"trusted":true},"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":{"iopub.status.busy":"2023-05-17T18:56:44.706763Z","iopub.execute_input":"2023-05-17T18:56:44.707198Z","iopub.status.idle":"2023-05-17T18:56:44.721661Z","shell.execute_reply.started":"2023-05-17T18:56:44.70716Z","shell.execute_reply":"2023-05-17T18:56:44.720463Z"},"trusted":true},"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    encoded_img = cv.imencode('.png', img)[1]\n    print(f'Saving to: {os.path.join(folder, filename+\"_tiled.png\")}')\n    cv.imwrite(os.path.join(folder, filename+\"_tiled.png\"), img)\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":{"iopub.status.busy":"2023-05-17T18:56:44.723339Z","iopub.execute_input":"2023-05-17T18:56:44.723752Z","iopub.status.idle":"2023-05-17T18:56:44.741284Z","shell.execute_reply.started":"2023-05-17T18:56:44.723699Z","shell.execute_reply":"2023-05-17T18:56:44.739719Z"},"trusted":true},"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, is_mask=False, data_provider='radboud'):\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    if is_mask:\n        wsi = color_code_mask(wsi,data_provider, level)\n    fig, ax = plt.subplots()\n    ax.imshow(wsi)\n    for tile in tiles: # Draw the tiles\n        rect = matplotlib.patches.Rectangle((tile['xloc'],tile['yloc']),tile_size,tile_size, linewidth=1, edgecolor='b', facecolor='none')\n        ax.add_patch(rect)\n    plt.show()","metadata":{"execution":{"iopub.status.busy":"2023-05-17T18:56:44.742602Z","iopub.execute_input":"2023-05-17T18:56:44.743041Z","iopub.status.idle":"2023-05-17T18:56:44.756791Z","shell.execute_reply.started":"2023-05-17T18:56:44.743002Z","shell.execute_reply":"2023-05-17T18:56:44.75562Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def color_code_mask(mask, data_provider:str, level:int):\n    mask = np.copy(mask)\n    # Set background to white\n    mask[mask==0] = 255\n    mask_data = mask[:,:,0]\n    if data_provider == 'radboud':\n        # Stroma\n        mask[:,:,:][mask_data==1] = 200\n        # healthy/Benign\n        mask[:,:,:][mask_data==2] = 150\n        # Cancerous (Gleason 3)\n        mask[:,:,1:3][mask_data==3] = 75\n        mask[:,:,0][mask_data==3] = 170\n        # Cancerous (Gleason 4)\n        mask[:,:,1:3][mask_data==4] = 25\n        mask[:,:,0][mask_data==4] = 200\n        # Cancerous (Gleason 5)\n        mask[:,:,1:3][mask_data==5] = 0\n        mask[:,:,0][mask_data==5] = 255\n    elif data_provider == 'karolinska':\n        # Benign\n        mask[:,:,:][mask_data==1] = 200\n        # Cancerous\n        mask[:,:,1:3][mask_data==2] = 75\n        mask[:,:,0][mask_data==2] = 255\n    return mask","metadata":{"execution":{"iopub.status.busy":"2023-05-17T18:56:44.758563Z","iopub.execute_input":"2023-05-17T18:56:44.759102Z","iopub.status.idle":"2023-05-17T18:56:44.773722Z","shell.execute_reply.started":"2023-05-17T18:56:44.759051Z","shell.execute_reply":"2023-05-17T18:56:44.772504Z"},"trusted":true},"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, is_mask=False, data_provider='radboud'):\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    if is_mask:\n        wsi = color_code_mask(wsi,data_provider, level)\n    fig, ax = plt.subplots()\n    ax.imshow(wsi)\n    for tile in tiles: # Draw the tiles\n        rect = matplotlib.patches.Rectangle((tile['xloc'],tile['yloc']),tile_size,tile_size, linewidth=1, edgecolor='b', facecolor='none')\n        ax.add_patch(rect)\n    plt.show()","metadata":{"execution":{"iopub.status.busy":"2023-05-17T18:56:44.775786Z","iopub.execute_input":"2023-05-17T18:56:44.776206Z","iopub.status.idle":"2023-05-17T18:56:44.795562Z","shell.execute_reply.started":"2023-05-17T18:56:44.776168Z","shell.execute_reply":"2023-05-17T18:56:44.794399Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"## Plots the selected slices over the original slide.\ndef save_tile_locations_plots(slide, mask, tiled_image, tiles, tile_size, level, data_provider, isup_grade, file_name, show=False):\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    mask = mask.read_region((0,0), level, slide.level_dimensions[level])\n    mask = np.asarray(mask)[:,:,:3] # Convert to array (optional) and ignore alpha channel\n    mask = color_code_mask(mask,data_provider, level)\n    fig, (ax_slide, ax_mask, ax_tiled_image) = plt.subplots(1,3, figsize=(22,10))\n#     plt.suptitle(f\"ISUP grade: {isup_grade}\")\n    ax_slide.imshow(wsi)\n    ax_slide.set_title(\"WSI with tiles\")\n    for tile in tiles: # Draw the tiles\n        rect = matplotlib.patches.Rectangle((tile['xloc'],tile['yloc']),tile_size,tile_size, linewidth=1, edgecolor='b', facecolor='none')\n        ax_slide.add_patch(rect)\n    ax_mask.imshow(mask)\n    ax_mask.set_title(\"Mask with tiles\")\n    for tile in tiles: # Draw the tiles\n        rect = matplotlib.patches.Rectangle((tile['xloc'],tile['yloc']),tile_size,tile_size, linewidth=1, edgecolor='b', facecolor='none')\n        ax_mask.add_patch(rect)\n    ax_tiled_image.imshow(tiled_image)\n    ax_tiled_image.set_title(\"Extracted tiles\")\n    if show:\n        plt.show()\n    plt.savefig(file_name+\".png\", facecolor='white', transparent=False)\n    plt.close()","metadata":{"execution":{"iopub.status.busy":"2023-05-17T18:56:44.797104Z","iopub.execute_input":"2023-05-17T18:56:44.797947Z","iopub.status.idle":"2023-05-17T18:56:44.813282Z","shell.execute_reply.started":"2023-05-17T18:56:44.797907Z","shell.execute_reply":"2023-05-17T18:56:44.811707Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Get the tiling information of Y (25) random samples of each ISUP grade","metadata":{}},{"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\noutput_folder = os.path.join(out_dir, 'tiled_images')\nplot_output = False\nY = 25\nselected_indices = np.zeros(Y*6)\nfor i, isup_grade in enumerate(range(0,6)):\n    isup_indices = list(samples[samples['isup_grade']==isup_grade].index)\n    selected_indices[Y*i:Y*(i+1)]= np.random.choice(isup_indices, Y, replace=False)\n    \n\nfor indx in tqdm(selected_indices):\n    sample = samples.iloc[int(indx)]\n    file_name = sample.loc['image_id']\n    data_provider = sample.loc['data_provider']\n    slide = openslide.OpenSlide(os.path.join(data_dir, file_name+'.tiff'))\n    mask = openslide.OpenSlide(os.path.join(mask_dir, file_name+'_mask.tiff'))\n\n    tiles = get_tile_locations_from_slide(slide, tile_size, N_tiles, level) # Get tile coordinates of top N tiles\n    tiled_image = create_tiled_image(slide, tiles, tile_size, N_tiles, level) # Convert the tiles information into a tiled image\n\n    isup_grade = sample.loc['isup_grade']\n    save_path = os.path.join(out_dir, file_name+f\"_isup{isup_grade}\")\n    save_tile_locations_plots(slide, mask, tiled_image, tiles, tile_size, level, \n                              data_provider=data_provider, isup_grade=isup_grade, file_name=save_path ,show=plot_output);\n\n#     if plot_output:\n#         show_tile_locations(slide, tiles, tile_size, level) # Plot the tiles over the original slide\n#         show_tile_locations(mask, tiles, tile_size, level, is_mask=True, data_provider=data_provider) # Plot the tiles over the original mask\n\n    slide.close()","metadata":{"execution":{"iopub.status.busy":"2023-05-17T18:56:44.817591Z","iopub.execute_input":"2023-05-17T18:56:44.818416Z","iopub.status.idle":"2023-05-17T19:37:49.84721Z","shell.execute_reply.started":"2023-05-17T18:56:44.818357Z","shell.execute_reply":"2023-05-17T19:37:49.845834Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"!zip -r tile_placements.zip /kaggle/working/tiled_images/","metadata":{"execution":{"iopub.status.busy":"2023-05-17T19:37:49.849198Z","iopub.execute_input":"2023-05-17T19:37:49.849617Z","iopub.status.idle":"2023-05-17T19:37:55.499797Z","shell.execute_reply.started":"2023-05-17T19:37:49.849581Z","shell.execute_reply":"2023-05-17T19:37:55.498179Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"from IPython.display import FileLink \nos.chdir(r'/kaggle/working')\nFileLink(r'tile_placements.zip')","metadata":{"execution":{"iopub.status.busy":"2023-05-17T19:37:55.501705Z","iopub.execute_input":"2023-05-17T19:37:55.502136Z","iopub.status.idle":"2023-05-17T19:37:55.512297Z","shell.execute_reply.started":"2023-05-17T19:37:55.502091Z","shell.execute_reply":"2023-05-17T19:37:55.511048Z"},"trusted":true},"execution_count":null,"outputs":[]}]}