{"cells":[{"metadata":{},"cell_type":"markdown","source":"## Idea\nThe purpose of this notebook is to explain the procedure of dividing the given large image with any shape, into multiple 256x256 fixed sized images, which can be further used to train a model with fixed input size. \n\nAfter dividing the given image into multiple smaller images, there will be many images which has very small details or in many cases no details (fully white image), we'll discard those images as they are not useful for training models."},{"metadata":{"trusted":true},"cell_type":"code","source":"!pip install iterative-stratification","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"cell_type":"code","source":"import os\n\nimport math\nimport openslide\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\nfrom tqdm import tqdm\nfrom joblib import Parallel, delayed\nfrom matplotlib import pyplot as plt\nfrom PIL import Image, ImageChops\n\nfrom iterstrat.ml_stratifiers import MultilabelStratifiedKFold","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","trusted":true},"cell_type":"code","source":"BASE_DIR = '/kaggle/input/prostate-cancer-grade-assessment'\nDATA_DIR = os.path.join(BASE_DIR, 'train_images')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"%%time\ntrain_df = pd.read_csv(os.path.join(BASE_DIR, 'train.csv'))\ntest_df = pd.read_csv(os.path.join(BASE_DIR, 'test.csv'))\nsample_sub_df = pd.read_csv(os.path.join(BASE_DIR, 'sample_submission.csv'))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"train_df.head()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Let's first read and visualize the training image which we are going to use as an example in this notebook."},{"metadata":{"trusted":true},"cell_type":"code","source":"img = os.path.join(DATA_DIR, f'{train_df[\"image_id\"].iloc[5]}.tiff')\nimg = openslide.OpenSlide(img)\npatch = img.read_region((0, 0), 2, img.level_dimensions[-1])\nimg.close()\npatch","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Here are some constants' definition, which you can modify if you want higher dimensional images instead of 256x256 or you want to use higher resolution images."},{"metadata":{"trusted":true},"cell_type":"code","source":"crop_size = 256  # Size of resultant images\ncrop_level = 2  # The level of slide used to get the images (you can use 0 to get very high resolution images)\ndown_samples = [1, 4, 16]  # List of down samples available in any tiff image file","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"The below code will crop the given image and will store the result in an array"},{"metadata":{"trusted":true},"cell_type":"code","source":"def split_image(openslide_image):\n    \"\"\"\n    Splits the given image into multiple images if 256x256\n    \"\"\"\n    \n    # Get the size of the given image\n    width, height = openslide_image.level_dimensions[crop_level]\n\n    # Get the dimensions of level 0 resolution, as it's required in \"read_region()\" function\n    base_height = down_samples[crop_level] * height  # height of level 0\n    base_width = down_samples[crop_level] * width  # width of level 0\n\n    # Get the number of smaller images \n    h_crops = math.ceil(width / crop_size)\n    v_crops = math.ceil(height / crop_size)\n\n    splits = []\n    for v in range(v_crops):\n        for h in range(h_crops): \n            x_location = h*crop_size*down_samples[crop_level]\n            y_location = v*crop_size*down_samples[crop_level]\n\n            patch = openslide_image.read_region((x_location, y_location), crop_level, (crop_size, crop_size))\n\n            splits.append(patch)\n    return splits, h_crops, v_crops","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"img = os.path.join(DATA_DIR, f'{train_df[\"image_id\"].iloc[5]}.tiff')\nimg = openslide.OpenSlide(img)\ncrops, h_crops, v_crops = split_image(img)\nimg.close()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Now as we have all the smaller images available. Let's plot all of them and verify out result by comparing it to original image."},{"metadata":{"trusted":true},"cell_type":"code","source":"fig, ax = plt.subplots(nrows=v_crops, ncols=h_crops, figsize=(12, 12))\ncount=0\nfor row in ax:\n    for col in row:\n        patch = crops[count]\n        col.grid(False)\n        col.set_xticks([])\n        col.set_yticks([])\n        col.imshow(patch)\n        count += 1\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"This looks promising. The only thing remaining is to discard the 'white' images. Let's do it."},{"metadata":{"trusted":true},"cell_type":"code","source":"def get_emptiness(arr):\n    total_ele = arr.size\n    white_ele = np.count_nonzero(arr == 255) + np.count_nonzero(arr == 0)\n    return white_ele / total_ele","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"ignore_threshold = 0.95  # If the image is more than 95% empty, consider it as white and ignore","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def filter_white_images(images):\n    non_empty_crops = []\n    for image in images:\n        image_arr = np.array(image)[...,:3]  # Discard the alpha channel\n        emptiness = get_emptiness(image_arr)\n        if emptiness < ignore_threshold:\n            non_empty_crops.append(image)\n    return non_empty_crops","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"non_empty_crops = filter_white_images(crops)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"len(non_empty_crops)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Let's plot all the crops separately and verify that all the crops have meaningful details and are not empty."},{"metadata":{"trusted":true},"cell_type":"code","source":"for f in non_empty_crops:\n    display(f)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Cheers! We divided a large image into multiple images of size 256x256. Repeat this procedure on each of the training image to get the fixed sized images without lossing any data. "},{"metadata":{},"cell_type":"markdown","source":"Now let's do the above procedure for entire training dataset and save the resultant dataset so that it can be used further in training the models."},{"metadata":{"trusted":true},"cell_type":"code","source":"# train_df = train_df.loc[:20]","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"!mkdir train_images","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"dataset = []\ndef create_dataset(count):\n    img = os.path.join(DATA_DIR, f'{train_df[\"image_id\"].iloc[count]}.tiff')\n    img = openslide.OpenSlide(img)\n    crops, _, _ = split_image(img)\n    img.close()\n\n    non_empty_crops = filter_white_images(crops)\n    image_id = train_df['image_id'].iloc[count]\n\n    for index, img in enumerate(non_empty_crops):\n        img_metadata = {}\n        img = img.convert('RGB')\n\n        img_metadata['image_id'] = f'{image_id}_{index}'\n        img_metadata['data_provider'] = train_df['data_provider'].iloc[count]\n        img_metadata['isup_grade'] = train_df['isup_grade'].iloc[count]\n        img_metadata['gleason_score'] = train_df['gleason_score'].iloc[count]\n\n        img.save(f'train_images/{image_id}_{index}.jpg', 'JPEG', quality=100, optimize=True, progressive=True)\n        dataset.append(img_metadata)\n    return dataset","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"dataset = Parallel(n_jobs=8)(delayed(create_dataset)(count) for count in tqdm(range(len(train_df))))\ndataset = [item for sublist in dataset for item in sublist]\n\ndataset = pd.DataFrame(dataset)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Creating k-folds"},{"metadata":{"trusted":true},"cell_type":"code","source":"dataset.loc[:, 'kfold'] = -1\n\n# Randomly shuffle the dataset\ndataset = dataset.sample(frac=1).reset_index(drop=True)\n\nX = dataset[['image_id', 'data_provider']].values\ny = dataset[['isup_grade', 'gleason_score']].values\n\nmskf = MultilabelStratifiedKFold(n_splits=5)\n\nfor fold, (train_idx, val_idx) in enumerate(mskf.split(X, y)):\n    dataset.loc[val_idx, 'kfold'] = fold\n\nprint(dataset.kfold.value_counts())","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"dataset","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"dataset.to_csv('train.csv', index=False)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"import zipfile\ndef zipdir(path, ziph):\n    # ziph is zipfile handle\n    for root, dirs, files in os.walk(path):\n        for file in files:\n            ziph.write(os.path.join(root, file))\n\nzipf = zipfile.ZipFile('train.zip', 'w', zipfile.ZIP_DEFLATED)\nzipdir('/kaggle/working/train_images', zipf)\nzipf.close()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"!mv train.zip train","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"!rm -rf /kaggle/working/train_images","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"!ls","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}