{"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":"# Setup","metadata":{}},{"cell_type":"code","source":"import numpy as np\nimport pandas as pd\nfrom PIL import Image\nimport matplotlib.pyplot as plt\nfrom skimage import io\nimport glob, os, json, cv2, gc, shutil\nimport tensorflow as tf\nfrom tensorflow import keras\nfrom tensorflow.keras import layers\nfrom tensorflow.keras import backend as K\n\nIMG_SIZE = 1024\nINPUT_PATH = \"../input/hubmap-kidney-segmentation\"\nMODEL_PATH = \"../input/hubmap-model-v2/model.h5\"\ncommit = False\n\ndef create_folder(folder):\n    if not os.path.exists(folder):\n        os.makedirs(folder)\n        \ncreate_folder(\"./test\")","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","execution":{"iopub.status.busy":"2021-06-13T10:34:26.426423Z","iopub.execute_input":"2021-06-13T10:34:26.426996Z","iopub.status.idle":"2021-06-13T10:34:32.931058Z","shell.execute_reply.started":"2021-06-13T10:34:26.426873Z","shell.execute_reply":"2021-06-13T10:34:32.930031Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Image Functions","metadata":{}},{"cell_type":"code","source":"def read_tiff(image_path):\n    image = io.imread(image_path)\n    image = np.squeeze(image) # some images have unnecessary axes with shape 1 --> remove\n    if image.shape[0] == 3: # some images have color as first axis -> swap axes\n        image = image.swapaxes(0,1)\n        image = image.swapaxes(1,2)\n    return image\n    \ndef slice_images(image_id, image, mask=[], folder=\"\"):\n    print('Slicing Image ' + image_id + ' ...')\n\n    possible_slices_x = image.shape[0] // IMG_SIZE\n    possible_slices_y = image.shape[1] // IMG_SIZE\n\n    for x in range(possible_slices_x):\n        for y in range(possible_slices_y):\n            image_slice = image[x * IMG_SIZE : (x+1) * IMG_SIZE, y * IMG_SIZE : (y+1) * IMG_SIZE]\n            \n            if np.any(image_slice): # only process non-black --> no background images\n\n                if not len(mask) == 0:\n                    mask_slice = mask[x * IMG_SIZE : (x+1) * IMG_SIZE, y * IMG_SIZE : (y+1) * IMG_SIZE] * 255\n                    if 255 in mask_slice:\n                        cv2.imwrite(f\"./{folder}/{image_id}-imgslice.{x}.{y}.jpg\", image_slice)\n                        cv2.imwrite(f\"./{folder}/{image_id}-maskslice.{x}.{y}.png\", mask_slice.astype(int))\n                else:\n                    cv2.imwrite(f\"./{folder}/{image_id}-imgslice.{x}.{y}.jpg\", image_slice)\n\n## ref.: https://www.kaggle.com/bguberfain/memory-aware-rle-encoding\ndef rle_encode_less_memory(img):\n    pixels = img.T.flatten()\n    \n    # This simplified method requires first and last pixel to be zero\n    pixels[0] = 0\n    pixels[-1] = 0\n    runs = np.where(pixels[1:] != pixels[:-1])[0] + 2\n    runs[1::2] -= runs[::2]\n    \n    return ' '.join(str(x) for x in runs)\n\ndef slice_test_images(test_image_shapes):\n    df_train = pd.read_csv(os.path.join(INPUT_PATH, 'train.csv')) # use one train image as validation image (model was not trained with this)\n    df_train = df_train.iloc[0:1, :]\n    test_image_paths = sorted(glob.glob(os.path.join(INPUT_PATH, 'test/*.tiff')))\n    \n    for index, train_sample in df_train.iterrows():\n        image_id = train_sample['id']\n        encoded_mask = train_sample['encoding']\n\n        image_path = os.path.join(INPUT_PATH, f\"train/{image_id}.tiff\")\n        image = read_tiff(image_path)\n        test_image_shapes.append((image_id, image.shape))\n        \n        create_folder(f\"./test/{image_id}\")\n        slice_images(image_id, image, [], f\"test/{image_id}\")","metadata":{"execution":{"iopub.status.busy":"2021-06-13T10:34:32.932768Z","iopub.execute_input":"2021-06-13T10:34:32.933190Z","iopub.status.idle":"2021-06-13T10:34:32.948839Z","shell.execute_reply.started":"2021-06-13T10:34:32.933148Z","shell.execute_reply":"2021-06-13T10:34:32.947568Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Image to Tensor Mapping","metadata":{}},{"cell_type":"code","source":"def image_to_tensor(image_path, is_png):\n    image = tf.io.read_file(image_path)\n    if is_png:\n        image = tf.image.decode_png(image)\n    else:\n        image = tf.image.decode_jpeg(image)\n        \n    image = tf.image.convert_image_dtype(image, tf.float32)\n    \n    return image\n\n\ndef prepare_images_for_unet(image_path, mask_path):\n    image = image_to_tensor(image_path, is_png=False)\n    mask = image_to_tensor(mask_path, is_png=True)\n\n    return image, mask\n\n\ndef prepare_image_for_unet(image_path):\n    image = image_to_tensor(image_path, is_png=False)\n\n    return image","metadata":{"execution":{"iopub.status.busy":"2021-06-13T10:34:32.951247Z","iopub.execute_input":"2021-06-13T10:34:32.951662Z","iopub.status.idle":"2021-06-13T10:34:32.962679Z","shell.execute_reply.started":"2021-06-13T10:34:32.951622Z","shell.execute_reply":"2021-06-13T10:34:32.961708Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Loss Functions & Metrics","metadata":{}},{"cell_type":"code","source":"# ref.: https://gist.github.com/CarloSegat/1a2816676c48607dac9dda38afe4f3d9\ndef weighted_binary_crossentropy(y_true, y_pred, weight1=5, weight0=1):\n    y_true = K.clip(y_true, K.epsilon(), 1-K.epsilon())\n    y_pred = K.clip(y_pred, K.epsilon(), 1-K.epsilon())\n    logloss = -(y_true * K.log(y_pred) * weight1 + (1 - y_true) * K.log(1 - y_pred) * weight0 )\n    return K.mean( logloss, axis=-1)\n\n# ref.: https://github.com/keras-team/keras/issues/3611\ndef dice_coef(y_true, y_pred, smooth=1):\n    intersection = K.sum(y_true * y_pred, axis=[1,2,3])\n    union = K.sum(y_true, axis=[1,2,3]) + K.sum(y_pred, axis=[1,2,3])\n    return K.mean( (2. * intersection + smooth) / (union + smooth), axis=0)","metadata":{"execution":{"iopub.status.busy":"2021-06-13T10:34:32.965612Z","iopub.execute_input":"2021-06-13T10:34:32.965904Z","iopub.status.idle":"2021-06-13T10:34:32.974724Z","shell.execute_reply.started":"2021-06-13T10:34:32.965877Z","shell.execute_reply":"2021-06-13T10:34:32.973679Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Slice Test Images","metadata":{}},{"cell_type":"code","source":"test_image_shapes = []\nslice_test_images(test_image_shapes)","metadata":{"execution":{"iopub.status.busy":"2021-06-13T10:34:32.976057Z","iopub.execute_input":"2021-06-13T10:34:32.976466Z","iopub.status.idle":"2021-06-13T10:34:53.623948Z","shell.execute_reply.started":"2021-06-13T10:34:32.976425Z","shell.execute_reply":"2021-06-13T10:34:53.623022Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Predictions","metadata":{}},{"cell_type":"code","source":"model = keras.models.load_model(MODEL_PATH, custom_objects={'dice_coef':dice_coef, 'weighted_binary_crossentropy':weighted_binary_crossentropy})\nmodel.summary()\n","metadata":{"execution":{"iopub.status.busy":"2021-06-13T10:34:53.625342Z","iopub.execute_input":"2021-06-13T10:34:53.625682Z","iopub.status.idle":"2021-06-13T10:34:58.157254Z","shell.execute_reply.started":"2021-06-13T10:34:53.625644Z","shell.execute_reply":"2021-06-13T10:34:58.156340Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#submission_path = './submission.csv'\n#with open(submission_path, 'w') as f:\n#    f.write(\"id,predicted\\n\")\ndf_submission = pd.read_csv(\"../input/hubmap-kidney-segmentation/test/sample_submission.csv\")","metadata":{"execution":{"iopub.status.busy":"2021-06-13T10:34:58.160454Z","iopub.execute_input":"2021-06-13T10:34:58.160736Z","iopub.status.idle":"2021-06-13T10:34:58.172098Z","shell.execute_reply.started":"2021-06-13T10:34:58.160707Z","shell.execute_reply":"2021-06-13T10:34:58.171242Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Image Functions","metadata":{}},{"cell_type":"code","source":"def plot_masked_image(image, mask, name):\n    plt.imshow(image, interpolation='none')\n    plt.imshow(mask, cmap='jet', alpha=0.3, interpolation='none')\n    \n    plt.savefig(f\"./{name}.png\", dpi = 1000)\n    plt.show()\n    \ndef read_tiff(image_path):\n    image = io.imread(image_path)\n    image = np.squeeze(image) # some images have unnecessary axes with shape 1 --> remove\n    if image.shape[0] == 3: # some images have color as first axis -> swap axes\n        image = image.swapaxes(0,1)\n        image = image.swapaxes(1,2)\n    return image\n\ndef read_mask(image, encoded_mask):\n    mask = rle_decode(encoded_mask, (image.shape[1], image.shape[0])) # with inverted axes\n    mask = mask.swapaxes(0,1) # swap back axes\n    mask = np.expand_dims(mask, -1) # add one axis to have same shape as images\n    return mask\n\n# ref.: https://www.kaggle.com/stainsby/fast-tested-rle\ndef rle_decode(mask_rle, shape):\n    '''\n    mask_rle: run-length as string formated (start length)\n    shape: (height,width) of array to return \n    Returns numpy array, 1 - mask, 0 - background\n\n    '''\n    s = mask_rle.split()\n    starts, lengths = [np.asarray(x, dtype=int) for x in (s[0:][::2], s[1:][::2])]\n    starts -= 1\n    ends = starts + lengths\n    img = np.zeros(shape[0]*shape[1], dtype=np.uint8)\n    for lo, hi in zip(starts, ends):\n        img[lo:hi] = 1\n    return img.reshape(shape)","metadata":{"execution":{"iopub.status.busy":"2021-06-13T10:34:58.173390Z","iopub.execute_input":"2021-06-13T10:34:58.173751Z","iopub.status.idle":"2021-06-13T10:34:58.187477Z","shell.execute_reply.started":"2021-06-13T10:34:58.173710Z","shell.execute_reply":"2021-06-13T10:34:58.186433Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Predictions","metadata":{}},{"cell_type":"code","source":"def dice(im1, im2):\n    im1 = np.asarray(im1).astype(np.bool)\n    im2 = np.asarray(im2).astype(np.bool)\n    \n    intersection = np.logical_and(im1, im2)\n\n    return 2. * intersection.sum() / (im1.sum() + im2.sum())","metadata":{"execution":{"iopub.status.busy":"2021-06-13T10:34:58.190147Z","iopub.execute_input":"2021-06-13T10:34:58.190505Z","iopub.status.idle":"2021-06-13T10:34:58.198838Z","shell.execute_reply.started":"2021-06-13T10:34:58.190470Z","shell.execute_reply":"2021-06-13T10:34:58.197826Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"test_image_folders = sorted(glob.glob('../working/test/*/'))\ndf_train = pd.read_csv(os.path.join(INPUT_PATH, 'train.csv')) # use one train image as validation image (model was not trained with this)\nfor folder in test_image_folders:\n    print(f\"{folder}\")\n    image_id = folder.split('/')[-2]\n    test_image_slice_paths = np.array(glob.glob(f\"{folder}/*.jpg\")).astype(\"str\")\n    \n    original_shape = [item for item in test_image_shapes if item[0] == image_id][0][1]\n    \n    max_x = original_shape[0] // IMG_SIZE\n    max_y = original_shape[1] // IMG_SIZE\n    \n    img = np.zeros((max_x*IMG_SIZE, max_y*IMG_SIZE, 1)).astype(\"uint8\")\n    \n    for x in range(max_x):\n        print(x)\n        for y in range(max_y):\n            img_path = f\"{folder}{image_id}-imgslice.{x}.{y}.jpg\"\n            if img_path in test_image_slice_paths:\n                image = prepare_image_for_unet(img_path)\n                test_dataset = tf.data.Dataset.from_tensors((image)).batch(1)\n                with tf.device('/GPU:0'):\n                    mask = model.predict(test_dataset, verbose=0)[0]\n                    mask = mask > 0.9\n                    mask = mask.astype(\"uint8\")\n                    img[x*IMG_SIZE : (x+1)*IMG_SIZE, y*IMG_SIZE : (y+1)*IMG_SIZE] = mask\n                    del mask\n                del test_dataset\n                del image\n                gc.collect()\n\n    df_submission.loc[df_submission.id == image_id, 'predicted'] = rle_encode_less_memory(img)\n    \ndf_submission.to_csv('submission.csv', index=False)\nprint(df_submission)\n\n#if commit:\nplt.figure(figsize=(20,20))\nplt.imshow(img);","metadata":{"execution":{"iopub.status.busy":"2021-06-13T10:34:58.200519Z","iopub.execute_input":"2021-06-13T10:34:58.200942Z","iopub.status.idle":"2021-06-13T10:58:31.694495Z","shell.execute_reply.started":"2021-06-13T10:34:58.200902Z","shell.execute_reply":"2021-06-13T10:58:31.693583Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Cleanup","metadata":{}},{"cell_type":"code","source":"shutil.rmtree('./test')","metadata":{"execution":{"iopub.status.busy":"2021-06-13T10:58:31.696018Z","iopub.execute_input":"2021-06-13T10:58:31.696555Z","iopub.status.idle":"2021-06-13T10:58:31.746588Z","shell.execute_reply.started":"2021-06-13T10:58:31.696512Z","shell.execute_reply":"2021-06-13T10:58:31.745712Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Plot Ground Truth Mask Over Test Image","metadata":{}},{"cell_type":"code","source":"image_path = os.path.join(INPUT_PATH, f\"train/{image_id}.tiff\")\nimage = read_tiff(image_path)\nimage_to_plot = cv2.resize(image, (0, 0), fx=0.25, fy=0.25)","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"encoded_mask = df_train[df_train.id == image_id].iloc[0]['encoding']\ntrue_mask = read_mask(image, encoded_mask)\ntrue_mask_to_plot = cv2.resize((true_mask * 255).astype('float32'), (0, 0), fx=0.25, fy=0.25)\n\nplot_masked_image(image_to_plot, true_mask_to_plot, image_id)\ntrue_mask = np.squeeze(true_mask)\nsegmentation_mask = np.squeeze(img)\ntrue_mask = true_mask[:segmentation_mask.shape[0], :segmentation_mask.shape[1]]","metadata":{"execution":{"iopub.status.busy":"2021-06-13T10:58:48.161532Z","iopub.execute_input":"2021-06-13T10:58:48.161924Z","iopub.status.idle":"2021-06-13T10:59:13.674193Z","shell.execute_reply.started":"2021-06-13T10:58:48.161884Z","shell.execute_reply":"2021-06-13T10:59:13.673151Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Plot Predicted Mask Over Test Image","metadata":{}},{"cell_type":"code","source":"mask_to_plot = cv2.resize((img * 255).astype('float32'), (0, 0), fx=0.25, fy=0.25)\n\nplot_masked_image(image_to_plot, mask_to_plot, image_id)","metadata":{"execution":{"iopub.status.busy":"2021-06-13T10:58:31.747897Z","iopub.execute_input":"2021-06-13T10:58:31.748301Z","iopub.status.idle":"2021-06-13T10:58:48.160000Z","shell.execute_reply.started":"2021-06-13T10:58:31.748268Z","shell.execute_reply":"2021-06-13T10:58:48.159011Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Calculate Dice for Test Image","metadata":{}},{"cell_type":"code","source":"k=1\ncur_dice_coef = dice(segmentation_mask, true_mask)\nprint(f\"Dice: {cur_dice_coef}\")","metadata":{},"execution_count":null,"outputs":[]}]}