{"cells": [{"source": ["# What ?\n", "Another implementation for IoU metric used in this competition.\n", "\n", "# Why ?\n", "I've based my work on these great notebooks [Keras U-Net starter - LB 0.277](https://www.kaggle.com/keegil/keras-u-net-starter-lb-0-277) and [Example Metric Implementation](https://www.kaggle.com/wcukierski/example-metric-implementation). Event tought @keegil provides one implementation of the `mean_iou` metric, the value diverges from the example implementation from @wcukierski. So I start to work on a Keras compatible implementation for the @wcukierski metric.\n", "\n", "# Whow ?\n", "Using `tf.py_func` we can use a python function inside Tensorflow environment.\n"], "cell_type": "markdown", "metadata": {}}, {"outputs": [], "source": ["# This Python 3 environment comes with many helpful analytics libraries installed\n", "# It is defined by the kaggle/python docker image: https://github.com/kaggle/docker-python\n", "# For example, here's several helpful packages to load in \n", "\n", "import numpy as np # linear algebra\n", "import pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n", "\n", "# Input data files are available in the \"../input/\" directory.\n", "# For example, running this (by clicking run or pressing Shift+Enter) will list the files in the input directory\n", "\n", "from subprocess import check_output\n", "print(check_output([\"ls\", \"../input\"]).decode(\"utf8\"))\n", "\n", "# Any results you write to the current directory are saved as output."], "cell_type": "code", "execution_count": null, "metadata": {"_cell_guid": "0acf5985-168c-4fef-ade5-9b1aa6193a63", "_uuid": "eba720a72c8feb2be58b2d398cf8468d57190212"}}, {"outputs": [], "source": ["import os\n", "import sys\n", "import random\n", "import warnings\n", "\n", "import numpy as np\n", "import pandas as pd\n", "\n", "import matplotlib.pyplot as plt\n", "\n", "from tqdm import tqdm\n", "from itertools import chain\n", "from skimage.io import imread, imshow, imread_collection, concatenate_images\n", "from skimage.transform import resize\n", "from skimage.morphology import label\n", "\n", "from keras.models import Model, load_model\n", "from keras.layers import Input\n", "from keras.layers.core import Lambda\n", "from keras.layers.convolutional import Conv2D, Conv2DTranspose\n", "from keras.layers.pooling import MaxPooling2D\n", "from keras.layers.merge import concatenate\n", "from keras.callbacks import EarlyStopping, ModelCheckpoint\n", "from keras import backend as K\n", "\n", "import tensorflow as tf\n", "\n", "# Set some parameters\n", "IMG_WIDTH = 128\n", "IMG_HEIGHT = 128\n", "IMG_CHANNELS = 3\n", "TRAIN_PATH = '../input/stage1_train/'\n", "TEST_PATH = '../input/stage1_test/'\n", "\n", "warnings.filterwarnings('ignore', category=UserWarning, module='skimage')\n", "seed = 42\n", "random.seed = seed\n", "np.random.seed = seed"], "cell_type": "code", "execution_count": null, "metadata": {}}, {"outputs": [], "source": ["# Get train and test IDs\n", "train_ids = next(os.walk(TRAIN_PATH))[1]\n", "test_ids = next(os.walk(TEST_PATH))[1]"], "cell_type": "code", "execution_count": null, "metadata": {}}, {"outputs": [], "source": ["# Get and resize train images and masks\n", "X_train = np.zeros((len(train_ids), IMG_HEIGHT, IMG_WIDTH, IMG_CHANNELS), dtype=np.uint8)\n", "Y_train = np.zeros((len(train_ids), IMG_HEIGHT, IMG_WIDTH, 1), dtype=np.bool)\n", "print('Getting and resizing train images and masks ... ')\n", "sys.stdout.flush()\n", "for n, id_ in tqdm(enumerate(train_ids), total=len(train_ids)):\n", "    path = TRAIN_PATH + id_\n", "    img = imread(path + '/images/' + id_ + '.png')[:,:,:IMG_CHANNELS]\n", "    img = resize(img, (IMG_HEIGHT, IMG_WIDTH), mode='constant', preserve_range=True)\n", "    X_train[n] = img\n", "    mask = np.zeros((IMG_HEIGHT, IMG_WIDTH, 1), dtype=np.bool)\n", "    for mask_file in next(os.walk(path + '/masks/'))[2]:\n", "        mask_ = imread(path + '/masks/' + mask_file)\n", "        mask_ = np.expand_dims(resize(mask_, (IMG_HEIGHT, IMG_WIDTH), mode='constant', \n", "                                      preserve_range=True), axis=-1)\n", "        mask = np.maximum(mask, mask_)\n", "    Y_train[n] = mask\n", "\n", "# Get and resize test images\n", "X_test = np.zeros((len(test_ids), IMG_HEIGHT, IMG_WIDTH, IMG_CHANNELS), dtype=np.uint8)\n", "sizes_test = []\n", "print('Getting and resizing test images ... ')\n", "sys.stdout.flush()\n", "for n, id_ in tqdm(enumerate(test_ids), total=len(test_ids)):\n", "    path = TEST_PATH + id_\n", "    img = imread(path + '/images/' + id_ + '.png')[:,:,:IMG_CHANNELS]\n", "    sizes_test.append([img.shape[0], img.shape[1]])\n", "    img = resize(img, (IMG_HEIGHT, IMG_WIDTH), mode='constant', preserve_range=True)\n", "    X_test[n] = img\n", "\n", "print('Done!')"], "cell_type": "code", "execution_count": null, "metadata": {}}, {"outputs": [], "source": ["def iou_metric(y_true_in, y_pred_in, print_table=False):\n", "    labels = label(y_true_in > 0.5)\n", "    y_pred = label(y_pred_in > 0.5)\n", "    \n", "    true_objects = len(np.unique(labels))\n", "    pred_objects = len(np.unique(y_pred))\n", "\n", "    intersection = np.histogram2d(labels.flatten(), y_pred.flatten(), bins=(true_objects, pred_objects))[0]\n", "\n", "    # Compute areas (needed for finding the union between all objects)\n", "    area_true = np.histogram(labels, bins = true_objects)[0]\n", "    area_pred = np.histogram(y_pred, bins = pred_objects)[0]\n", "    area_true = np.expand_dims(area_true, -1)\n", "    area_pred = np.expand_dims(area_pred, 0)\n", "\n", "    # Compute union\n", "    union = area_true + area_pred - intersection\n", "\n", "    # Exclude background from the analysis\n", "    intersection = intersection[1:,1:]\n", "    union = union[1:,1:]\n", "    union[union == 0] = 1e-9\n", "\n", "    # Compute the intersection over union\n", "    iou = intersection / union\n", "\n", "    # Precision helper function\n", "    def precision_at(threshold, iou):\n", "        matches = iou > threshold\n", "        true_positives = np.sum(matches, axis=1) == 1   # Correct objects\n", "        false_positives = np.sum(matches, axis=0) == 0  # Missed objects\n", "        false_negatives = np.sum(matches, axis=1) == 0  # Extra objects\n", "        tp, fp, fn = np.sum(true_positives), np.sum(false_positives), np.sum(false_negatives)\n", "        return tp, fp, fn\n", "\n", "    # Loop over IoU thresholds\n", "    prec = []\n", "    if print_table:\n", "        print(\"Thresh\\tTP\\tFP\\tFN\\tPrec.\")\n", "    for t in np.arange(0.5, 1.0, 0.05):\n", "        tp, fp, fn = precision_at(t, iou)\n", "        if (tp + fp + fn) > 0:\n", "            p = tp / (tp + fp + fn)\n", "        else:\n", "            p = 0\n", "        if print_table:\n", "            print(\"{:1.3f}\\t{}\\t{}\\t{}\\t{:1.3f}\".format(t, tp, fp, fn, p))\n", "        prec.append(p)\n", "    \n", "    if print_table:\n", "        print(\"AP\\t-\\t-\\t-\\t{:1.3f}\".format(np.mean(prec)))\n", "    return np.mean(prec)\n", "\n", "def iou_metric_batch(y_true_in, y_pred_in):\n", "    batch_size = y_true_in.shape[0]\n", "    metric = []\n", "    for batch in range(batch_size):\n", "        value = iou_metric(y_true_in[batch], y_pred_in[batch])\n", "        metric.append(value)\n", "    return np.array(np.mean(metric), dtype=np.float32)\n", "\n", "def my_iou_metric(label, pred):\n", "    metric_value = tf.py_func(iou_metric_batch, [label, pred], tf.float32)\n", "    return metric_value"], "cell_type": "code", "execution_count": null, "metadata": {"collapsed": true}}, {"outputs": [], "source": ["from keras import backend as K\n", "K.clear_session()\n", "\n", "# Build U-Net model\n", "inputs = Input((IMG_HEIGHT, IMG_WIDTH, IMG_CHANNELS))\n", "s = Lambda(lambda x: x ) (inputs)\n", "\n", "c1 = Conv2D(8, (3, 3), activation='relu', padding='same') (s)\n", "c1 = Conv2D(8, (3, 3), activation='relu', padding='same') (c1)\n", "p1 = MaxPooling2D((2, 2)) (c1)\n", "\n", "c2 = Conv2D(16, (3, 3), activation='relu', padding='same') (p1)\n", "c2 = Conv2D(16, (3, 3), activation='relu', padding='same') (c2)\n", "p2 = MaxPooling2D((2, 2)) (c2)\n", "\n", "c3 = Conv2D(32, (3, 3), activation='relu', padding='same') (p2)\n", "c3 = Conv2D(32, (3, 3), activation='relu', padding='same') (c3)\n", "p3 = MaxPooling2D((2, 2)) (c3)\n", "\n", "c4 = Conv2D(64, (3, 3), activation='relu', padding='same') (p3)\n", "c4 = Conv2D(64, (3, 3), activation='relu', padding='same') (c4)\n", "p4 = MaxPooling2D(pool_size=(2, 2)) (c4)\n", "\n", "c5 = Conv2D(128, (3, 3), activation='relu', padding='same') (p4)\n", "c5 = Conv2D(128, (3, 3), activation='relu', padding='same') (c5)\n", "\n", "u6 = Conv2DTranspose(64, (2, 2), strides=(2, 2), padding='same') (c5)\n", "u6 = concatenate([u6, c4])\n", "c6 = Conv2D(64, (3, 3), activation='relu', padding='same') (u6)\n", "c6 = Conv2D(64, (3, 3), activation='relu', padding='same') (c6)\n", "\n", "u7 = Conv2DTranspose(32, (2, 2), strides=(2, 2), padding='same') (c6)\n", "u7 = concatenate([u7, c3])\n", "c7 = Conv2D(32, (3, 3), activation='relu', padding='same') (u7)\n", "c7 = Conv2D(32, (3, 3), activation='relu', padding='same') (c7)\n", "\n", "u8 = Conv2DTranspose(16, (2, 2), strides=(2, 2), padding='same') (c7)\n", "u8 = concatenate([u8, c2])\n", "c8 = Conv2D(16, (3, 3), activation='relu', padding='same') (u8)\n", "c8 = Conv2D(16, (3, 3), activation='relu', padding='same') (c8)\n", "\n", "u9 = Conv2DTranspose(8, (2, 2), strides=(2, 2), padding='same') (c8)\n", "u9 = concatenate([u9, c1], axis=3)\n", "c9 = Conv2D(8, (3, 3), activation='relu', padding='same') (u9)\n", "c9 = Conv2D(8, (3, 3), activation='relu', padding='same') (c9)\n", "\n", "outputs = Conv2D(1, (1, 1), activation='sigmoid') (c9)\n", "\n", "model = Model(inputs=[inputs], outputs=[outputs])\n", "model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=[my_iou_metric])\n", "model.summary()"], "cell_type": "code", "execution_count": null, "metadata": {}}, {"outputs": [], "source": ["results = model.fit(X_train, Y_train, validation_split=0.1, batch_size=8, epochs=5)"], "cell_type": "code", "execution_count": null, "metadata": {}}, {"outputs": [], "source": ["# Predict on train, val and test\n", "preds_train = model.predict(X_train[:int(X_train.shape[0]*0.9)], verbose=1)\n", "preds_val = model.predict(X_train[int(X_train.shape[0]*0.9):], verbose=1)\n", "preds_test = model.predict(X_test, verbose=1)\n", "\n", "# Threshold predictions\n", "preds_train_t = (preds_train > 0.5).astype(np.uint8)\n", "preds_val_t = (preds_val > 0.5).astype(np.uint8)\n", "preds_test_t = (preds_test > 0.5).astype(np.uint8)\n", "\n", "# Create list of upsampled test masks\n", "preds_test_upsampled = []\n", "for i in range(len(preds_test)):\n", "    preds_test_upsampled.append(resize(np.squeeze(preds_test[i]), \n", "                                       (sizes_test[i][0], sizes_test[i][1]), \n", "                                       mode='constant', preserve_range=True))"], "cell_type": "code", "execution_count": null, "metadata": {}}, {"outputs": [], "source": ["ix = 10\n", "plt.figure(figsize=(20,20))\n", "plt.subplot(131)\n", "imshow(X_train[ix])\n", "plt.title(\"Image\")\n", "plt.subplot(132)\n", "imshow(np.squeeze(Y_train[ix]))\n", "plt.title(\"Mask\")\n", "plt.subplot(133)\n", "imshow(np.squeeze(preds_train_t[ix] > 0.5))\n", "plt.title(\"Predictions\")\n", "plt.show()"], "cell_type": "code", "execution_count": null, "metadata": {}}, {"outputs": [], "source": ["iou_metric(np.squeeze(Y_train[ix]), np.squeeze(preds_train_t[ix]), print_table=True)"], "cell_type": "code", "execution_count": null, "metadata": {}}], "nbformat": 4, "metadata": {"kernelspec": {"name": "python3", "display_name": "Python 3", "language": "python"}, "language_info": {"codemirror_mode": {"name": "ipython", "version": 3}, "name": "python", "file_extension": ".py", "version": "3.6.4", "mimetype": "text/x-python", "pygments_lexer": "ipython3", "nbconvert_exporter": "python"}}, "nbformat_minor": 1}