{"cells":[{"metadata":{},"cell_type":"markdown","source":"# Nuclie Semantic Segmentation - UNet using Tensorflow 2"},{"metadata":{"_cell_guid":"e61ef2d8-f315-4f7f-b07e-1de0f4e8441a","_uuid":"1677fddbb95f7545b6540e9201f3339a0fdbfc5d"},"cell_type":"markdown","source":"# Intro\n- Dataset used is from Kaggle's Data Science Bowl 2018 - Nuclei Segmentation\n- The architecture used is [U-Net](https://arxiv.org/abs/1505.04597), which is very common for image segmentation problems such as this.\n- This notebook is inspired from the great kernel [Keras U-net starter - LB 0.277](https://www.kaggle.com/keegil/keras-u-net-starter-lb-0-277) by Kjetil Åmdal-Sævik."},{"metadata":{"trusted":true},"cell_type":"code","source":"!pip install tensorflow-gpu==2.0.0-alpha0","execution_count":null,"outputs":[]},{"metadata":{"_cell_guid":"c332549b-8d23-4bb5-8497-e7a8eb8b21d2","_uuid":"5c38504af3a84bee68c66d3cde74443c58df422f","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\nfrom PIL import Image\nfrom tqdm import tqdm\nfrom itertools import chain\nfrom skimage.io import imread, imshow, imread_collection, concatenate_images\nfrom skimage.transform import resize\nfrom skimage.morphology import label\nfrom PIL import ImageFile\n\nfrom tensorflow.keras.models import Model, load_model\nfrom tensorflow.keras.layers import Input\nfrom tensorflow.keras.layers import Dropout, Lambda\nfrom tensorflow.keras.layers import Conv2D, Conv2DTranspose, BatchNormalization\nfrom tensorflow.keras.layers import MaxPooling2D\nfrom tensorflow.keras.layers import concatenate\nfrom tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint\nfrom tensorflow.keras import backend as K\n\nimport tensorflow as tf\n\n# Set some parameters\nIMG_WIDTH = 128\nIMG_HEIGHT = 128\nIMG_CHANNELS = 3\nTRAIN_PATH = '../input/stage1_train/'\nTEST_PATH = '../input/stage1_test/'\nFINAL_TEST_PATH = '../input/stage2_test_final/'\n\n#TRAIN_PATH = 'data/stage1_train/'\n#TEST_PATH = 'data/stage1_test/'\n#FINAL_TEST_PATH = 'data/stage2_test_final/'\n\ndir_path = ''\n\nwarnings.filterwarnings('ignore', category=UserWarning, module='skimage')\nseed = 42\nrandom.seed = seed\nnp.random.seed = seed","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"os.getcwd()","execution_count":null,"outputs":[]},{"metadata":{"_cell_guid":"ffa0caf0-2d1b-40f2-865b-8e6db88526b6","_uuid":"3fb9d6530fbbd0e22e41fc4fd9fd9fc0bff027ac","trusted":true},"cell_type":"code","source":"# Get train and test IDs\ntrain_ids = next(os.walk(TRAIN_PATH))[1]\ntest_ids = next(os.walk(TEST_PATH))[1]\nfinal_test_ids = next(os.walk(FINAL_TEST_PATH))[1]","execution_count":null,"outputs":[]},{"metadata":{"_cell_guid":"59c4a25d-645f-4b74-9c53-145ac78cc481","_uuid":"875af74f980236825de3a650825b46e25632422c"},"cell_type":"markdown","source":"# Get the data\n- Downsample both the training and test images to reduce computations\n- Retain record of the original sizes of the test images to upsample predicted masks and create correct run-length encodings "},{"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, IMG_CHANNELS), 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 ... ')","execution_count":null,"outputs":[]},{"metadata":{"_cell_guid":"ca0cc34b-c26f-41ee-88d7-975aebdb634e","_uuid":"9e389ba8bdb5b6fc03b231b6a6c84a8bde634053","scrolled":true,"trusted":true},"cell_type":"code","source":"\nsys.stdout.flush()\nfor n, id_ in tqdm(enumerate(train_ids), total=len(train_ids)):\n    \n    #Read image files iteratively\n    path = TRAIN_PATH + id_\n    img = imread(dir_path + path + '/images/' + id_ + '.png')[:,:,:IMG_CHANNELS]\n    img = resize(img, (IMG_HEIGHT, IMG_WIDTH), mode='constant', preserve_range=True)\n    \n    #Append image to numpy array for train dataset\n    X_train[n] = img\n    \n    #Read corresponding mask files iteratively\n    mask = np.zeros((IMG_HEIGHT, IMG_WIDTH, 1), dtype=np.bool)\n    \n    #Looping through masks\n    for mask_file in next(os.walk(path + '/masks/'))[2]:\n        \n        #Read individual masks\n        mask_ = imread(dir_path + path + '/masks/' + mask_file)\n        \n        #Expand individual mask dimensions\n        mask_ = np.expand_dims(resize(mask_, (IMG_HEIGHT, IMG_WIDTH), mode='constant', \n                                      preserve_range=True), axis=-1)\n        \n        #Overlay individual masks to create a final mask for corresponding image\n        mask = np.maximum(mask, mask_)\n    \n    #Append mask to numpy array for train dataset\n    Y_train[n] = mask\n\n# Get and resize test images\nX_test = np.zeros((len(test_ids), IMG_HEIGHT, IMG_WIDTH, IMG_CHANNELS), dtype=np.uint8)\nsizes_test = []\nprint('Getting and resizing test images ... ')\nsys.stdout.flush()\nfor n, id_ in tqdm(enumerate(test_ids), total=len(test_ids)):\n    path = TEST_PATH + id_\n    \n    #Read images iteratively\n    img = imread(dir_path + path + '/images/' + id_ + '.png')[:,:,:IMG_CHANNELS]\n    \n    #Get test size\n    sizes_test.append([img.shape[0], img.shape[1]])\n    \n    #Resize image to match training data\n    img = resize(img, (IMG_HEIGHT, IMG_WIDTH), mode='constant', preserve_range=True)\n    \n    #Append image to numpy array for test dataset\n    X_test[n] = img\n\nprint('Done!')\n\n","execution_count":null,"outputs":[]},{"metadata":{"_cell_guid":"c0523b03-1fc5-4505-a1b8-eb35ee617c8a","_uuid":"d4f8327802a1ec6139ce0585953986272ba62ce1"},"cell_type":"markdown","source":"## Visualize imported data"},{"metadata":{"_cell_guid":"88829b53-50ce-45d9-9540-77dd7384ad4c","_uuid":"283af26f0860b7069bdfd133c746e5d20971542c","trusted":true},"cell_type":"code","source":"# Check if training data looks all right\nix = random.randint(0, len(train_ids))\nimshow(X_train[ix])\nplt.show()\nimshow(np.squeeze(Y_train[ix]))\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{"_cell_guid":"c3b9f148-1dba-4b6a-981b-6cdbf394fc3c","_uuid":"986488a4c5223576be370e224426a30431911eb2"},"cell_type":"markdown","source":"# Build and train our neural network\nNext we build our U-Net model, loosely based on [U-Net: Convolutional Networks for Biomedical Image Segmentation](https://arxiv.org/pdf/1505.04597.pdf) and very similar to [this repo](https://github.com/jocicmarko/ultrasound-nerve-segmentation) from the Kaggle Ultrasound Nerve Segmentation competition.\n\n![](https://lmb.informatik.uni-freiburg.de/people/ronneber/u-net/u-net-architecture.png)"},{"metadata":{"_cell_guid":"c1dbc57c-b497-4ccb-b077-2053203ab7ed","_uuid":"0aa97d66c29f45dfac9b0f45fcf74ba0e778ba5d","trusted":true},"cell_type":"code","source":"# Build U-Net model\ninputs = Input((IMG_HEIGHT, IMG_WIDTH, IMG_CHANNELS))\ns = Lambda(lambda x: x / 255) (inputs)\n\nc1 = Conv2D(32, (3, 3), activation='relu', kernel_initializer='he_normal', padding='same') (s)\nc1 = BatchNormalization()(c1)\nc1 = Dropout(0.1) (c1)\nc1 = Conv2D(32, (3, 3), activation='relu', kernel_initializer='he_normal', padding='same') (c1)\nc1 = BatchNormalization()(c1)\np1 = MaxPooling2D((2, 2)) (c1)\n\nc2 = Conv2D(64, (3, 3), activation='relu', kernel_initializer='he_normal', padding='same') (p1)\nc2 = BatchNormalization()(c2)\nc2 = Dropout(0.1) (c2)\nc2 = Conv2D(64, (3, 3), activation='relu', kernel_initializer='he_normal', padding='same') (c2)\nc2 = BatchNormalization()(c2)\np2 = MaxPooling2D((2, 2)) (c2)\n\nc3 = Conv2D(128, (3, 3), activation='relu', kernel_initializer='he_normal', padding='same') (p2)\nc3 = BatchNormalization()(c3)\nc3 = Dropout(0.2) (c3)\nc3 = Conv2D(128, (3, 3), activation='relu', kernel_initializer='he_normal', padding='same') (c3)\nc3 = BatchNormalization()(c3)\np3 = MaxPooling2D((2, 2)) (c3)\n\nc4 = Conv2D(256, (3, 3), activation='relu', kernel_initializer='he_normal', padding='same') (p3)\nc4 = BatchNormalization()(c4)\nc4 = Dropout(0.2) (c4)\nc4 = Conv2D(256, (3, 3), activation='relu', kernel_initializer='he_normal', padding='same') (c4)\nc4 = BatchNormalization()(c4)\np4 = MaxPooling2D(pool_size=(2, 2)) (c4)\n\nc5 = Conv2D(512, (3, 3), activation='relu', kernel_initializer='he_normal', padding='same') (p4)\nc5 = BatchNormalization()(c5)\nc5 = Dropout(0.3) (c5)\nc5 = Conv2D(512, (3, 3), activation='relu', kernel_initializer='he_normal', padding='same') (c5)\nc5 = BatchNormalization()(c5)\n\nu6 = Conv2DTranspose(128, (2, 2), strides=(2, 2), padding='same') (c5)\nu6 = concatenate([u6, c4])\nc6 = Conv2D(256, (3, 3), activation='relu', kernel_initializer='he_normal', padding='same') (u6)\nc6 = BatchNormalization()(c6)\nc6 = Dropout(0.2) (c6)\nc6 = Conv2D(256, (3, 3), activation='relu', kernel_initializer='he_normal', padding='same') (c6)\nc6 = BatchNormalization()(c6)\n\nu7 = Conv2DTranspose(64, (2, 2), strides=(2, 2), padding='same') (c6)\nu7 = concatenate([u7, c3])\nc7 = Conv2D(128, (3, 3), activation='relu', kernel_initializer='he_normal', padding='same') (u7)\nc7 = BatchNormalization()(c7)\nc7 = Dropout(0.2) (c7)\nc7 = Conv2D(128, (3, 3), activation='relu', kernel_initializer='he_normal', padding='same') (c7)\nc7 = BatchNormalization()(c7)\n\nu8 = Conv2DTranspose(32, (2, 2), strides=(2, 2), padding='same') (c7)\nu8 = concatenate([u8, c2])\nc8 = Conv2D(64, (3, 3), activation='relu', kernel_initializer='he_normal', padding='same') (u8)\nc8 = BatchNormalization()(c8)\nc8 = Dropout(0.1) (c8)\nc8 = Conv2D(64, (3, 3), activation='relu', kernel_initializer='he_normal', padding='same') (c8)\nc8 = BatchNormalization()(c8)\n\nu9 = Conv2DTranspose(16, (2, 2), strides=(2, 2), padding='same') (c8)\nu9 = concatenate([u9, c1], axis=3)\nc9 = Conv2D(32, (3, 3), activation='relu', kernel_initializer='he_normal', padding='same') (u9)\nc9 = BatchNormalization()(c9)\nc9 = Dropout(0.1) (c9)\nc9 = Conv2D(32, (3, 3), activation='relu', kernel_initializer='he_normal', padding='same') (c9)\nc9 = BatchNormalization()(c9)\n\noutputs = Conv2D(1, (1, 1), activation='sigmoid') (c9)\n","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"model = Model(inputs=[inputs], outputs=[outputs])\nmodel.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])\nmodel.summary()","execution_count":null,"outputs":[]},{"metadata":{"_cell_guid":"9415b1c4-aa69-41b9-a1e3-d6053dbd4f64","_uuid":"c060db22daa2abf12b28240cd81bbcbf1ce1bf87","trusted":true},"cell_type":"code","source":"# Fit model\nearlystopper = EarlyStopping(patience=15, verbose=1)\ncheckpointer = ModelCheckpoint('model_unet_checkpoint.h5', verbose=1, save_best_only=True)\nresults = model.fit(X_train, Y_train, validation_split=0.1, batch_size=16, epochs=100, \n                    callbacks=[earlystopper, checkpointer])","execution_count":null,"outputs":[]},{"metadata":{"_cell_guid":"1f381f5b-1b71-4daa-a417-e02f4894540b","_uuid":"bb15226ea617cf91ed8f43179fccb5a15809e5a0"},"cell_type":"markdown","source":"\n# Make predictions"},{"metadata":{"_cell_guid":"2daa48d5-ac98-4e18-af3f-a582baaa44f0","_uuid":"f841760b4abca1a25cb750822f88268bd79bf2ce","trusted":true},"cell_type":"code","source":"# Predict on train, val and test\nmodel = load_model('model_unet_checkpoint.h5')\npreds_train = model.predict(X_train[:int(X_train.shape[0]*0.9)], verbose=1)\npreds_val = model.predict(X_train[int(X_train.shape[0]*0.9):], verbose=1)\npreds_test = model.predict(X_test, verbose=1)\n\n# Threshold predictions\npreds_train_t = (preds_train > 0.5).astype(np.uint8)\npreds_val_t = (preds_val > 0.5).astype(np.uint8)\npreds_test_t = (preds_test > 0.5).astype(np.uint8)\n\n# Create list of upsampled test masks\npreds_test_upsampled = []\nfor i in range(len(preds_test_t)):\n    preds_test_upsampled.append(resize(np.squeeze(preds_test_t[i]), \n                                       (sizes_test[i][0], sizes_test[i][1]), \n                                       mode='constant', preserve_range=True))","execution_count":null,"outputs":[]},{"metadata":{"_cell_guid":"649248cd-a1fb-4da6-ade2-4bebad44bcab","_uuid":"7e06242a50870e07a080064a4912b761775990fa","trusted":true},"cell_type":"code","source":"# Perform a sanity check on some random training samples\nix = random.randint(0, len(preds_test_t))\nimshow(X_train[ix])\nplt.show()\nimshow(np.squeeze(Y_train[ix]))\nplt.show()\nimshow(np.squeeze(preds_train_t[ix]))\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{"_cell_guid":"4f66b75c-c694-41a1-8c91-34bb6595837b","_uuid":"d4ccbb559375bc2777ffb692a20adc313159f2cc","trusted":true},"cell_type":"code","source":"# Perform a sanity check on some random validation samples\nix = random.randint(0, len(preds_val_t))\nimshow(X_train[int(X_train.shape[0]*0.9):][ix])\nplt.show()\nimshow(np.squeeze(Y_train[int(Y_train.shape[0]*0.9):][ix]))\nplt.show()\nimshow(np.squeeze(preds_val_t[ix]))\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{"_cell_guid":"a6690535-b2e4-49ac-98d9-7191bfabfb6f","_uuid":"6a34c98de7c6ae473f676a34fe7e099b46764eca"},"cell_type":"markdown","source":"# Encode and submit our results\n\n- [Link](https://www.kaggle.com/rakhlin/fast-run-length-encoding-python) has an excellent implementation of run-length encoding."},{"metadata":{"_cell_guid":"59a0af60-a7d7-41ef-a6fe-9e3c72defa07","_uuid":"4f99c1bf852e82b60bd4f982ca0df293f712cdf0","trusted":true},"cell_type":"code","source":"# Run-length encoding stolen from https://www.kaggle.com/rakhlin/fast-run-length-encoding-python\ndef rle_encoding(x):\n    dots = np.where(x.T.flatten() == 1)[0]\n    run_lengths = []\n    prev = -2\n    for b in dots:\n        if (b>prev+1): run_lengths.extend((b + 1, 0))\n        run_lengths[-1] += 1\n        prev = b\n    return run_lengths\n\ndef prob_to_rles(x, cutoff=0.5):\n    lab_img = label(x > cutoff)\n    for i in range(1, lab_img.max() + 1):\n        yield rle_encoding(lab_img == i)","execution_count":null,"outputs":[]},{"metadata":{"_cell_guid":"31133f8c-3f40-4dff-8e1d-898d56672332","_uuid":"2e07f6afc4787b068ba714428145dcb3951d718f"},"cell_type":"markdown","source":"- Iterate over the test IDs and generate run-length encodings for each seperate mask identified by skimage ..."},{"metadata":{"_cell_guid":"22fe24a1-7659-4cc9-9d23-211f38e5b99f","_uuid":"089587843ed6a3955fdcb9b23a6ec3bf5d703688","trusted":true},"cell_type":"code","source":"new_test_ids = []\nrles = []\nfor n, id_ in enumerate(test_ids):\n    rle = list(prob_to_rles(preds_test_upsampled[n]))\n    rles.extend(rle)\n    new_test_ids.extend([id_] * len(rle))","execution_count":null,"outputs":[]},{"metadata":{"_cell_guid":"20b6b627-0fd6-425d-888f-da7f39efb124","_uuid":"849184a40a2c9c21506d8b8eb10ad9155fa229e8"},"cell_type":"markdown","source":"- Create submission"},{"metadata":{"_cell_guid":"1ba0ee3a-cca0-4349-83f6-09a1ac6fcb44","_uuid":"ba589f56f5be1e6886bc88f5bf9e7d0a408e4048","trusted":true},"cell_type":"code","source":"# Create submission DataFrame\nsub = pd.DataFrame()\nsub['ImageId'] = new_test_ids\nsub['EncodedPixels'] = pd.Series(rles).apply(lambda x: ' '.join(str(y) for y in x))\nsub.to_csv('sub-dsbowl2018-1.csv', index=False)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"","execution_count":null,"outputs":[]}],"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"codemirror_mode":{"name":"ipython","version":3},"file_extension":".py","mimetype":"text/x-python","name":"python","nbconvert_exporter":"python","pygments_lexer":"ipython3","version":"3.6.7"}},"nbformat":4,"nbformat_minor":1}