{"cells":[{"metadata":{},"cell_type":"markdown","source":"# Prostate cANcer graDe Assessment (PANDA) \n\n### Tiles, Augmentation using Albumentation, TF-Records\n\n > This notebook demonstrates the following:\n - Images to 36x256x256x3 Tiles to 1536x1536x3 Single Image.\n - Removal of White Background.\n - Augmentation Pipeline using Albumentations.\n - Converting Images to TF-Records\n - Save as a dataset for training on TPU's using TensorFlow","execution_count":null},{"metadata":{},"cell_type":"markdown","source":"## References\n\nConverting Images to Tiles - [PANDA Inference w/ 36 tiles_256](https://www.kaggle.com/haqishen/train-efficientnet-b0-w-36-tiles-256-lb0-87) - Qishen Ha","execution_count":null},{"metadata":{},"cell_type":"markdown","source":"#### Importing the Dependencies","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"import time\nstart_time = time.time()\nimport os; import gc; import math\ngc.enable()\nimport skimage.io\nimport numpy as np\nimport pandas as pd\nimport cv2\nimport PIL.Image\nimport tensorflow as tf\nimport albumentations\nimport matplotlib.pyplot as plt\n%matplotlib inline\n%config InlineBackend.figure_format = 'retina'\nfrom tqdm import tqdm_notebook as tqdm\nprint(\"Libraries Imported.! Time step {:.2f}\".format(time.time()-start_time))","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"#### Dataset Configuration","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"start_time = time.time()\ndata_dir = '../input/prostate-cancer-grade-assessment'\ndf_train = pd.read_csv(os.path.join(data_dir, 'train.csv'))\nimage_folder = os.path.join(data_dir, 'train_images')\n\ntile_size = 256\nimage_size = 256\nn_tiles = 36\nnum_workers = 4\nprint(\"Configuration Done.! Time step {:.2f}\".format(time.time()-start_time))","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"#### Augmentation Pipeline","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"# Simple Augmentation\ntransforms_train = albumentations.Compose([\n    albumentations.Transpose(p=0.5),\n    albumentations.VerticalFlip(p=0.5),\n    albumentations.HorizontalFlip(p=0.5),\n])\ntransforms_val = albumentations.Compose([])\n\n# Heavy Augmentation\n# transforms_train = albumentations.Compose([\n#     albumentations.OneOf([\n#         albumentations.ShiftScaleRotate(shift_limit=0.05, scale_limit=0.1,\n#                                         rotate_limit=15,\n#                                         border_mode=cv2.BORDER_CONSTANT, value=0),\n#         albumentations.OpticalDistortion(distort_limit=0.11, shift_limit=0.15,\n#                                          border_mode=cv2.BORDER_CONSTANT,\n#                                          value=0),\n#         albumentations.NoOp(),\n#     ]),\n#     albumentations.OneOf([\n#         albumentations.RandomGamma(gamma_limit=(50, 150)),\n#         albumentations.NoOp()\n#     ]),\n#     albumentations.OneOf([\n#         albumentations.Blur(),\n#         albumentations.Transpose(),\n#         albumentations.ElasticTransform(),\n#         albumentations.GridDistortion(),\n#         albumentations.CoarseDropout(),\n#         albumentations.NoOp()\n#     ]),\n#     albumentations.OneOf([\n#         albumentations.HorizontalFlip(),\n#         albumentations.VerticalFlip(),\n#         albumentations.NoOp()\n#     ])     \n# ])","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"#### Conversion of Images to Tiles\n","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"def get_tiles(img, mode=0):\n    result = []\n    h, w, c = img.shape\n    pad_h = (tile_size - h % tile_size) % tile_size + ((tile_size * mode) // 2)\n    pad_w = (tile_size - w % tile_size) % tile_size + ((tile_size * mode) // 2)\n\n    img2 = np.pad(img,[[pad_h // 2, pad_h - pad_h // 2], [pad_w // 2,pad_w - pad_w//2], [0,0]], constant_values=255)\n    img3 = img2.reshape(\n        img2.shape[0] // tile_size,\n        tile_size,\n        img2.shape[1] // tile_size,\n        tile_size,\n        3\n    )\n    img3 = img3.transpose(0,2,1,3,4).reshape(-1, tile_size, tile_size,3)\n    n_tiles_with_info = (img3.reshape(img3.shape[0],-1).sum(1) < tile_size ** 2 * 3 * 255).sum()\n    if len(img) < n_tiles:\n        img3 = np.pad(img3,[[0,N-len(img3)],[0,0],[0,0],[0,0]], constant_values=255)\n    idxs = np.argsort(img3.reshape(img3.shape[0],-1).sum(-1))[:n_tiles]\n    img3 = img3[idxs]\n    for i in range(len(img3)):\n        result.append({'img':img3[i], 'idx':i})\n    return result, n_tiles_with_info >= n_tiles","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"#### Note:\n##### The n_records variables decides the number of train_images to read and convert it into tiles. Better set this parameter as Kaggle allows on 4.9GB HDD and saving 1536x1536x3 sized images will use up all the space hence will throw an ERROR. \n\n##### What I will suggest is un-comment this `images = cv2.resize(images, (512, 512))` code below and set your image size small so that all the data fits in memory or run multiple instances of conversion. \n\n##### I will upload the complete dataset later and attach the link on this kernel.","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"start_1, end_1 = 0, 500\nstart_2, end_2 = 500, 1000\nshard_1, shard_2 = 0, 1","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"start_time = time.time()\nsave_dir = \"kaggle/train_images/\"\nos.makedirs(save_dir, exist_ok=True)\n\ndef covert_tiles(start_records, end_records):\n    # select the number of data samples here\n\n    for i in tqdm(range(start_records, end_records)):\n\n        row = df_train.iloc[i]\n        img_id = row.image_id\n\n        save_path = save_dir + img_id + '.png'\n\n        tiff_file = os.path.join(image_folder, f'{img_id}.tiff')\n        image = skimage.io.MultiImage(tiff_file)[1]\n\n        tiles, OK = get_tiles(image)\n\n        idxes = list(range(n_tiles))\n        n_row_tiles = int(np.sqrt(n_tiles))\n        images = np.zeros((image_size * n_row_tiles, image_size * n_row_tiles, 3))\n\n        for h in range(n_row_tiles):\n            for w in range(n_row_tiles):\n                i = h * n_row_tiles + w\n                if len(tiles) > idxes[i]:\n                    this_img = tiles[idxes[i]]['img']\n                else:\n                    this_img = np.ones((image_size, image_size, 3)).astype(np.uint8) * 255\n                this_img = 255 - this_img\n                if transforms_train is not None:\n                    # apply augmentation\n                    this_img = transforms_train(image=this_img)['image']\n                h1 = h * image_size\n                w1 = w * image_size\n                images[h1:h1+image_size, w1:w1+image_size] = this_img\n        if transforms_train is not None:\n            images = transforms_train(image=images)['image']\n        images = images.astype(np.float32)\n\n        #images = cv2.resize(images, (512, 512))\n\n        cv2.imwrite(save_path, images)\n    print(\"Coversion of Image to Tiles Complete.! Time step {:.2f}\".format(time.time()-start_time))","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"#### Convert to TF-Records","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"data_root = \"kaggle/train_images/\"\ntf_record_dir = \"kaggle/tfrecord_data/\"\n\ndef get_paths_and_labels(first_index=0, last_index=1000):\n    # utility function to return image and label\n    first_index=first_index\n    last_index=last_index\n    return [(os.path.join(save_dir, df_train.iloc[i].image_id+\".png\"), df_train.iloc[i].isup_grade)  for i in range(len(df_train.iloc[first_index:last_index]))]","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def write_to_tfrecords(num, start, end):\n    start_time = time.time()\n    \n    record_dir = tf_record_dir\n\n    if os.path.exists(record_dir):\n        return\n    os.makedirs(record_dir, exist_ok=True)\n\n    print(\"Converting images to TFRecords...\")\n    \n    # number of records per shard\n    records_per_shard = 250\n    shard_number = num\n    # start index\n    start_index = start\n    # end index\n    end_index = end\n    path_template = os.path.join(record_dir, \"shard_{0:04d}.tfrecords\")\n    writer = tf.io.TFRecordWriter(path_template.format(shard_number))\n    \n    for i, (image_path, label) in enumerate(get_paths_and_labels(first_index=start_index, last_index=end_index)):\n        if i and not (i % records_per_shard):\n            shard_number += 1\n            writer.close()\n            writer = tf.io.TFRecordWriter(path_template.format(shard_number))\n\n        with open(image_path, \"rb\") as f:\n            image_bytes = f.read()\n\n        record_bytes = tf.train.Example(features=tf.train.Features(feature={\n                            \"image\": tf.train.Feature(bytes_list=tf.train.BytesList(value=[image_bytes])),\n                            \"label\": tf.train.Feature(int64_list=tf.train.Int64List(value=[label]))\n                        })).SerializeToString()\n\n        writer.write(record_bytes)\n\n    writer.close()\n    print(\"TFRecord conversion complete.\")\n    print('Conversion to TF-Records is Complete.! Time step {:.2f}'.format(time.time()-start_time))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"covert_tiles(start_1, end_1)\nwrite_to_tfrecords(shard_1, start_1, end_1)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"#### Free up memory by deleting Tile Images","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"import os, shutil\nfolder = 'kaggle/train_images/'\nfor filename in os.listdir(folder):\n    file_path = os.path.join(folder, filename)\n    try:\n        if os.path.isfile(file_path) or os.path.islink(file_path):\n            os.unlink(file_path)\n        elif os.path.isdir(file_path):\n            shutil.rmtree(file_path)\n    except Exception as e:\n        print('Failed to delete %s. Reason: %s' % (file_path, e))\nos.removedirs('kaggle/train_images/')","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"#### Read TF-Records to Check Proper Conversion","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"IMAGE_SIZE = [1536, 1536]\ndef decode_image(image_data):\n    image = tf.image.decode_png(image_data, channels=3)\n    image = tf.cast(image, tf.float32)  \n    return image\n\ndef read_labeled_tfrecord(record):\n    record = tf.io.parse_single_example(record, RECORD_SCHEMA)\n    image = decode_image(record['image'])\n    label = tf.cast(record['label'], tf.int32)\n    return image, label \n\nRECORD_PATTERN = os.path.join('kaggle/tfrecord_data/', \"*.tfrecords\")\nRECORD_SCHEMA = {\n    \"image\": tf.io.FixedLenFeature([], dtype=tf.string),\n    \"label\": tf.io.FixedLenFeature([1], dtype=tf.int64)\n}","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"dataset = tf.data.Dataset.list_files(RECORD_PATTERN)\ndataset = dataset.interleave(tf.data.TFRecordDataset, num_parallel_calls=tf.data.experimental.AUTOTUNE)\ndataset = dataset.shuffle(100)\ndataset = dataset.map(read_labeled_tfrecord, num_parallel_calls=tf.data.experimental.AUTOTUNE)\ndataset = dataset.batch(500, drop_remainder=True)\ndataset = dataset.prefetch(tf.data.experimental.AUTOTUNE)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Please Upvote if you liked this Kernel.","execution_count":null}],"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}