{"cells":[{"metadata":{},"cell_type":"markdown","source":"## The Goal of this notebook is to build a simple Unet baseline model using Keras ","execution_count":null},{"metadata":{},"cell_type":"markdown","source":"Cf. https://arxiv.org/pdf/1505.04597.pdf","execution_count":null},{"metadata":{},"cell_type":"markdown","source":"Inspired from https://github.com/jocicmarko/ultrasound-nerve-segmentation","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"import os\nimport sys\nimport random\nimport warnings\n\nimport numpy as np\nimport pandas as pd\n\nimport matplotlib.pyplot as plt\n\nfrom itertools import chain\nfrom skimage.io import imread, imshow, imread_collection, concatenate_images\nfrom skimage.transform import resize\nfrom skimage.measure import label, regionprops\nfrom PIL import Image, ImageDraw\nfrom ast import literal_eval\nfrom tqdm.notebook import tqdm\n\nimport tensorflow as tf\n\nfrom keras.models import Model, load_model\nfrom keras.layers import Input\nfrom keras.layers.core import Dropout, Lambda\nfrom keras.layers.convolutional import Conv2D, Conv2DTranspose\nfrom keras.layers.pooling import MaxPooling2D\nfrom keras.layers.merge import concatenate\nfrom keras.callbacks import EarlyStopping\nfrom keras import backend as K","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Set some parameters\nIMG_WIDTH = 512\nIMG_HEIGHT = 512\n\nTRAIN_PATH = '/kaggle/input/global-wheat-detection/train/'\nTEST_PATH = '/kaggle/input/global-wheat-detection/test/'\nSC_FACTOR = int(1024 / IMG_WIDTH)\n\nwarnings.filterwarnings('ignore')\nseed = 42\nrandom.seed = seed\nnp.random.seed = seed","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"PATH = \"../input/global-wheat-detection/\"\ntrain_folder = os.path.join(PATH, \"train\")\ntest_folder = os.path.join(PATH, \"test\")\n\ntrain_csv_path = os.path.join(PATH, \"train.csv\")\ndf = pd.read_csv(train_csv_path)\nsample_sub = pd.read_csv(PATH + \"sample_submission.csv\")\n\ndf.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Get train and test IDs and paths\ntrain_ids = os.listdir(TRAIN_PATH)\ntest_ids = os.listdir(TEST_PATH)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### Convert multiple bounding boxes to masks","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"def make_polygon(coords):\n    xm, ym, w, h = coords\n    xm, ym, w, h = xm / SC_FACTOR, ym / SC_FACTOR, w / SC_FACTOR, h / SC_FACTOR   # scale values if image was downsized\n    return [(xm, ym), (xm, ym + h), (xm + w, ym + h), (xm + w, ym)]\n\nmasks = dict() # dictionnary containing all masks\n\nfor img_id, gp in tqdm(df.groupby(\"image_id\")):\n    gp['polygons'] = gp['bbox'].apply(eval).apply(lambda x: make_polygon(x))\n\n    img = Image.new('L', (IMG_WIDTH, IMG_HEIGHT), 0)\n    for pol in gp['polygons'].values:\n        ImageDraw.Draw(img).polygon(pol, outline=1, fill=1)\n\n    mask = np.array(img, dtype=np.uint8)\n    masks[img_id] = mask","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### Let's plot a quick example of the mask of the first image : ","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"im = Image.fromarray(masks[list(masks.keys())[7]])\nplt.imshow(im)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### Let's load the dataset","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"# Get and resize train images and masks\nX_train = np.zeros((len(train_ids), IMG_HEIGHT, IMG_WIDTH, 3), dtype=np.uint8)\nY_train = np.zeros((len(train_ids), IMG_HEIGHT, IMG_WIDTH, 1), dtype=np.bool)\nprint('Getting and resizing train images and masks... ')\nsys.stdout.flush()\n\nfor n, id_ in tqdm(enumerate(train_ids[:]), total=len(train_ids)):\n    path = TRAIN_PATH + id_\n    img = imread(path)\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    \n    id_clean = id_.split('.')[0]\n    if id_clean in masks.keys():\n        Y_train[n] = masks[id_clean][:, :, np.newaxis]\n\n# Get and resize test images\nX_test = np.zeros((len(test_ids), IMG_HEIGHT, IMG_WIDTH, 3), dtype=np.uint8)\nsizes_test = list()\nprint('Getting and resizing test images...')\nsys.stdout.flush()\n\nfor n, id_ in tqdm(enumerate(test_ids), total=len(test_ids)):\n    path = TEST_PATH + id_\n    img = imread(path)\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\nprint('Done!')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"X_train.shape, Y_train.shape","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Let's quickly look at what the images and mask look like (credits : https://www.kaggle.com/devvindan/wheat-detection-eda)","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"def show_images(images, num=2):\n    \n    images_to_show = np.random.choice(images, num)\n\n    for image_id in images_to_show:\n\n        image_path = os.path.join(train_folder, image_id + \".jpg\")\n        image = Image.open(image_path)\n\n        # get all bboxes for given image in [xmin, ymin, width, height]\n        bboxes = [literal_eval(box) for box in df[df['image_id'] == image_id]['bbox']]\n\n        # visualize them\n        draw = ImageDraw.Draw(image)\n        for bbox in bboxes:    \n            draw.rectangle([bbox[0], bbox[1], bbox[0] + bbox[2], bbox[1] + bbox[3]], width=3)\n\n        plt.figure(figsize = (15,15))\n        plt.imshow(image)\n        plt.show()\n\n\nunique_images = df['image_id'].unique()\nshow_images(unique_images)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#Credits to : https://www.kaggle.com/c/tgs-salt-identification-challenge/discussion/63044\n\ndef castF(x):\n    return K.cast(x, K.floatx())\n\ndef castB(x):\n    return K.cast(x, bool)\n\ndef iou_loss_core(true,pred):  #this can be used as a loss if you make it negative\n    intersection = true * pred\n    notTrue = 1 - true\n    union = true + (notTrue * pred)\n\n    return (K.sum(intersection, axis=-1) + K.epsilon()) / (K.sum(union, axis=-1) + K.epsilon())\n\ndef competitionMetric2(true, pred):\n\n    tresholds = [0.5 + (i * 0.05)  for i in range(5)]\n\n    #flattened images (batch, pixels)\n    true = K.batch_flatten(true)\n    pred = K.batch_flatten(pred)\n    pred = castF(K.greater(pred, 0.5))\n\n    #total white pixels - (batch,)\n    trueSum = K.sum(true, axis=-1)\n    predSum = K.sum(pred, axis=-1)\n\n    #has mask or not per image - (batch,)\n    true1 = castF(K.greater(trueSum, 1))    \n    pred1 = castF(K.greater(predSum, 1))\n\n    #to get images that have mask in both true and pred\n    truePositiveMask = castB(true1 * pred1)\n\n    #separating only the possible true positives to check iou\n    testTrue = tf.boolean_mask(true, truePositiveMask)\n    testPred = tf.boolean_mask(pred, truePositiveMask)\n\n    #getting iou and threshold comparisons\n    iou = iou_loss_core(testTrue,testPred) \n    truePositives = [castF(K.greater(iou, tres)) for tres in tresholds]\n\n    #mean of thressholds for true positives and total sum\n    truePositives = K.mean(K.stack(truePositives, axis=-1), axis=-1)\n    truePositives = K.sum(truePositives)\n\n    #to get images that don't have mask in both true and pred\n    trueNegatives = (1-true1) * (1 - pred1) # = 1 -true1 - pred1 + true1*pred1\n    trueNegatives = K.sum(trueNegatives) \n\n    return (truePositives + trueNegatives) / castF(K.shape(true)[0])","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Build U-Net model\ninputs = Input((IMG_HEIGHT, IMG_WIDTH, 3))\ns = Lambda(lambda x: x / 255) (inputs)  # rescale inputs\n\nc1 = Conv2D(16, (3, 3), activation='elu', kernel_initializer='he_normal', padding='same') (s)\nc1 = Dropout(0.1) (c1)\nc1 = Conv2D(16, (3, 3), activation='elu', kernel_initializer='he_normal', padding='same') (c1)\np1 = MaxPooling2D((2, 2)) (c1)\n\nc2 = Conv2D(32, (3, 3), activation='elu', kernel_initializer='he_normal', padding='same') (p1)\nc2 = Dropout(0.1) (c2)\nc2 = Conv2D(32, (3, 3), activation='elu', kernel_initializer='he_normal', padding='same') (c2)\np2 = MaxPooling2D((2, 2)) (c2)\n\nc3 = Conv2D(64, (3, 3), activation='elu', kernel_initializer='he_normal', padding='same') (p2)\nc3 = Dropout(0.2) (c3)\nc3 = Conv2D(64, (3, 3), activation='elu', kernel_initializer='he_normal', padding='same') (c3)\np3 = MaxPooling2D((2, 2)) (c3)\n\nc4 = Conv2D(128, (3, 3), activation='elu', kernel_initializer='he_normal', padding='same') (p3)\nc4 = Dropout(0.2) (c4)\nc4 = Conv2D(128, (3, 3), activation='elu', kernel_initializer='he_normal', padding='same') (c4)\np4 = MaxPooling2D(pool_size=(2, 2)) (c4)\n\nc5 = Conv2D(256, (3, 3), activation='elu', kernel_initializer='he_normal', padding='same') (p4)\nc5 = Dropout(0.3) (c5)\nc5 = Conv2D(256, (3, 3), activation='elu', kernel_initializer='he_normal', padding='same') (c5)\n\nu6 = Conv2DTranspose(128, (2, 2), strides=(2, 2), padding='same') (c5)\nu6 = concatenate([u6, c4])\nc6 = Conv2D(128, (3, 3), activation='elu', kernel_initializer='he_normal', padding='same') (u6)\nc6 = Dropout(0.2) (c6)\nc6 = Conv2D(128, (3, 3), activation='elu', kernel_initializer='he_normal', padding='same') (c6)\n\nu7 = Conv2DTranspose(64, (2, 2), strides=(2, 2), padding='same') (c6)\nu7 = concatenate([u7, c3])\nc7 = Conv2D(64, (3, 3), activation='elu', kernel_initializer='he_normal', padding='same') (u7)\nc7 = Dropout(0.2) (c7)\nc7 = Conv2D(64, (3, 3), activation='elu', kernel_initializer='he_normal', padding='same') (c7)\n\nu8 = Conv2DTranspose(32, (2, 2), strides=(2, 2), padding='same') (c7)\nu8 = concatenate([u8, c2])\nc8 = Conv2D(32, (3, 3), activation='elu', kernel_initializer='he_normal', padding='same') (u8)\nc8 = Dropout(0.1) (c8)\nc8 = Conv2D(32, (3, 3), activation='elu', kernel_initializer='he_normal', padding='same') (c8)\n\nu9 = Conv2DTranspose(16, (2, 2), strides=(2, 2), padding='same') (c8)\nu9 = concatenate([u9, c1], axis=3)\nc9 = Conv2D(16, (3, 3), activation='elu', kernel_initializer='he_normal', padding='same') (u9)\nc9 = Dropout(0.1) (c9)\nc9 = Conv2D(16, (3, 3), activation='elu', kernel_initializer='he_normal', padding='same') (c9)\n\noutputs = Conv2D(1, (1, 1), activation='sigmoid') (c9)\n\nmodel = Model(inputs=[inputs], outputs=[outputs])\nmodel.compile(optimizer='adam', loss='binary_crossentropy', metrics=[competitionMetric2])\nmodel.summary()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"from keras.utils import plot_model\nplot_model(model, show_shapes=True)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Fit model\nearlystop = EarlyStopping(patience=10, verbose=1, restore_best_weights=True)\n\nmodel.fit(X_train, \n         Y_train,\n         validation_split=0.1,\n         batch_size=32, \n         epochs=40, \n         callbacks=[earlystop],\n        )","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Prediction","execution_count":null},{"metadata":{},"cell_type":"markdown","source":"As `X_test` is already formatted, we can directly make a prediction","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"THRESH = 0.65\n\npreds = model.predict(X_test)[:, :, :, 0]\nmasked_preds = preds > THRESH","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Here we want to binarize the prediction to get a mask (as the input y_train was also a mask)","execution_count":null},{"metadata":{},"cell_type":"markdown","source":"* Let's visualise predictions ! ","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"n_rows = 3\nf, ax = plt.subplots(n_rows, 3, figsize=(14, 10))\n\nfor j, idx in enumerate([4,5,6]):\n    for k, kind in enumerate(['original', 'pred', 'masked_pred']):\n        if kind == 'original':\n            img = X_test[idx]\n        elif kind == 'pred':\n            img = preds[idx]\n        elif kind == 'masked_pred':\n            masked_pred = preds[idx] > THRESH\n            img = masked_pred\n        ax[j, k].imshow(img)\n\nplt.tight_layout()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Seems not too bad ! ","execution_count":null},{"metadata":{},"cell_type":"markdown","source":"### Now the next step is to translate our masked predictions into several bouding boxes","execution_count":null},{"metadata":{},"cell_type":"markdown","source":"For the moment, I'll assign 1.0 confidence for every box","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"def get_params_from_bbox(coords, scaling_factor=1):\n    xmin, ymin = coords[1] * scaling_factor, coords[0] * scaling_factor\n    w = (coords[3] - coords[1]) * scaling_factor\n    h = (coords[2] - coords[0]) * scaling_factor\n    \n    return xmin, ymin, w, h","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Allows to extract bounding boxes from binary masks\nbboxes = list()\n\nfor j in range(masked_preds.shape[0]):\n    label_j = label(masked_preds[j, :, :]) \n    props = regionprops(label_j)   # that's were the job is done\n    bboxes.append(props)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Here we format the bboxes into the required format\noutput = dict()\n\nfor i in range(masked_preds.shape[0]):\n    bboxes_processed = [get_params_from_bbox(bb.bbox, scaling_factor=SC_FACTOR) for bb in bboxes[i]]\n    formated_boxes = ['1.0 ' + ' '.join(map(str, bb_m)) for bb_m in bboxes_processed]\n    \n    output[sample_sub[\"image_id\"][i]] = \" \".join(formated_boxes)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"sample_sub[\"PredictionString\"] = output.values()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"sample_sub","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"sample_sub.to_csv('submission.csv', index=False)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"","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}