{"cells": [{"metadata": {"_cell_guid": "33d00425-f595-41fa-b822-f50882d56476", "_uuid": "98189f36fd3e16592fc072ff705a5d3073fd06cb"}, "source": ["# Overview\n", "The kernel goes through\n", "1. the preprocessing steps to load the data\n", "1. a quick visualization of the color-space\n", "1. training a simple CNN\n", "1. applying the model to the test data\n", "1. creating the RLE test data"], "cell_type": "markdown"}, {"metadata": {"_cell_guid": "01ef30eb-1363-4e51-9d5d-42f7011348e1", "collapsed": true, "_uuid": "bd906a2f847afc7d10764bfc7a91a3e4b2be6996"}, "source": ["import numpy as np # linear algebra\n", "import pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n", "from glob import glob\n", "import os\n", "from skimage.io import imread\n", "import matplotlib.pyplot as plt\n", "import seaborn as sns\n", "%matplotlib inline\n", "dsb_data_dir = os.path.join('..', 'input')\n", "stage_label = 'stage1'"], "cell_type": "code", "execution_count": null, "outputs": []}, {"metadata": {"_cell_guid": "4700c733-6359-4727-91dd-fada2f4e397e", "_uuid": "eb73ed385cad7f50245eb3e35190d402ed5cdece"}, "source": ["# Read in the labels\n", "Load the RLE-encoded output for the training set"], "cell_type": "markdown"}, {"metadata": {"_cell_guid": "766bc1d7-c066-4429-9fe7-5eb61d3c38e4", "_uuid": "9b9b10ba7c40e3092dfccb86cf41298a64de62c4"}, "source": ["train_labels = pd.read_csv(os.path.join(dsb_data_dir,'{}_train_labels.csv'.format(stage_label)))\n", "train_labels['EncodedPixels'] = train_labels['EncodedPixels'].map(lambda ep: [int(x) for x in ep.split(' ')])\n", "train_labels.sample(3)"], "cell_type": "code", "execution_count": null, "outputs": []}, {"metadata": {"_cell_guid": "493040fb-d048-4e65-9d91-cc9c022df15f", "_uuid": "835cbe19871b155ea14d3aa6a238567c50dccd21"}, "source": ["# Load in all Images\n", "Here we load in the images and process the paths so we have the appropriate information for each image"], "cell_type": "markdown"}, {"metadata": {"_cell_guid": "4ba1187a-33e4-40b7-aa17-b49d0df11dd5", "_uuid": "d5bf5405f63a39c9e3a6da0a7d71eb0ff03cfb90"}, "source": ["all_images = glob(os.path.join(dsb_data_dir, 'stage1_*', '*', '*', '*'))\n", "img_df = pd.DataFrame({'path': all_images})\n", "img_id = lambda in_path: in_path.split('/')[-3]\n", "img_type = lambda in_path: in_path.split('/')[-2]\n", "img_group = lambda in_path: in_path.split('/')[-4].split('_')[1]\n", "img_stage = lambda in_path: in_path.split('/')[-4].split('_')[0]\n", "img_df['ImageId'] = img_df['path'].map(img_id)\n", "img_df['ImageType'] = img_df['path'].map(img_type)\n", "img_df['TrainingSplit'] = img_df['path'].map(img_group)\n", "img_df['Stage'] = img_df['path'].map(img_stage)\n", "img_df.sample(2)"], "cell_type": "code", "execution_count": null, "outputs": []}, {"metadata": {"_cell_guid": "363ff396-e102-4cc4-b31b-156d994fadfb", "_uuid": "243582157d1c6f41c7822da75e1ddc2c2bf65159"}, "source": ["# Create Training Data\n", "Here we make training data and load all the images into the dataframe. We take a simplification here of grouping all the regions together (rather than keeping them distinct)."], "cell_type": "markdown"}, {"metadata": {"_cell_guid": "4c3ec4b5-bc8d-4ab9-a54c-9971da341845", "_uuid": "47c96aec86be1cd5292554483310a06ee9d45da2"}, "source": ["%%time\n", "train_df = img_df.query('TrainingSplit==\"train\"')\n", "train_rows = []\n", "group_cols = ['Stage', 'ImageId']\n", "for n_group, n_rows in train_df.groupby(group_cols):\n", "    c_row = {col_name: col_value for col_name, col_value in zip(group_cols, n_group)}\n", "    c_row['masks'] = n_rows.query('ImageType == \"masks\"')['path'].values.tolist()\n", "    c_row['images'] = n_rows.query('ImageType == \"images\"')['path'].values.tolist()\n", "    train_rows += [c_row]\n", "train_img_df = pd.DataFrame(train_rows)    \n", "IMG_CHANNELS = 3\n", "def read_and_stack(in_img_list):\n", "    return np.sum(np.stack([imread(c_img) for c_img in in_img_list], 0), 0)/255.0\n", "train_img_df['images'] = train_img_df['images'].map(read_and_stack).map(lambda x: x[:,:,:IMG_CHANNELS])\n", "train_img_df['masks'] = train_img_df['masks'].map(read_and_stack).map(lambda x: x.astype(int))\n", "train_img_df.sample(1)"], "cell_type": "code", "execution_count": null, "outputs": []}, {"metadata": {"_cell_guid": "2b85d62b-92f6-465d-8277-516e3b6b46bf", "_uuid": "895ed6676984a257cd1e5ba0cd51a5dd31e64584"}, "source": ["# Show a few images\n", "Here we show a few images of the cells where we see there is a mixture of brightfield and fluorescence which will probably make using a single segmentation algorithm difficult"], "cell_type": "markdown"}, {"metadata": {"_cell_guid": "877319ab-2cb1-4da8-b67b-12d3d8c8d6dc", "_uuid": "48836bbf0dd9f45245b9a7edeca6a817d53e0426"}, "source": ["n_img = 6\n", "fig, m_axs = plt.subplots(2, n_img, figsize = (12, 4))\n", "for (_, c_row), (c_im, c_lab) in zip(train_img_df.sample(n_img).iterrows(), \n", "                                     m_axs.T):\n", "    c_im.imshow(c_row['images'])\n", "    c_im.axis('off')\n", "    c_im.set_title('Microscope')\n", "    \n", "    c_lab.imshow(c_row['masks'])\n", "    c_lab.axis('off')\n", "    c_lab.set_title('Labeled')"], "cell_type": "code", "execution_count": null, "outputs": []}, {"metadata": {"_cell_guid": "198ce366-56f2-43d9-96a6-830b9239effc", "_uuid": "5fb9320cd737a2ee6d5a64024331ae09ebfe4b4b"}, "source": ["# Look at the intensity distribution\n", "Here we look briefly at the distribution of intensity and see a few groups forming, they should probably be handled separately. "], "cell_type": "markdown"}, {"metadata": {"_cell_guid": "067f8ae5-cac2-4ee6-9275-ee4ce8fc1e5a", "_uuid": "31a22cd0a9f7a8a7339781688407f2c793e7f0e6"}, "source": ["train_img_df['Red'] = train_img_df['images'].map(lambda x: np.mean(x[:,:,0]))\n", "train_img_df['Green'] = train_img_df['images'].map(lambda x: np.mean(x[:,:,1]))\n", "train_img_df['Blue'] = train_img_df['images'].map(lambda x: np.mean(x[:,:,2]))\n", "train_img_df['Gray'] = train_img_df['images'].map(lambda x: np.mean(x))\n", "train_img_df['Red-Blue'] = train_img_df['images'].map(lambda x: np.mean(x[:,:,0]-x[:,:,2]))\n", "sns.pairplot(train_img_df[['Gray', 'Red', 'Green', 'Blue', 'Red-Blue']])"], "cell_type": "code", "execution_count": null, "outputs": []}, {"metadata": {"_cell_guid": "8c0aea8a-67ba-4b29-8218-132651a16ef4", "_uuid": "30deb6f5f1a0596dd5803b312eef5b94bb4c83cc"}, "source": ["# Check Dimensions \n", "Here we show the dimensions of the data to see the variety in the input images"], "cell_type": "markdown"}, {"metadata": {"_cell_guid": "63d036a9-7cf5-4c53-b11d-d25abb436c29", "_uuid": "fb77a503aed7cf9725f3d7b6f79217ad2704facc"}, "source": ["train_img_df['images'].map(lambda x: x.shape).value_counts()"], "cell_type": "code", "execution_count": null, "outputs": []}, {"metadata": {"_cell_guid": "bdd7f061-b4a8-401f-ad0e-060dfe985abc", "_uuid": "a8f7221aa69a07ec7f5ce8b55220c7b0884d8935"}, "source": ["## Making a simple CNN\n", "Here we make a very simple CNN just to get a quick idea of how well it works. For this we use a batch normalization to normalize the inputs. We cheat a bit with the padding to keep problems simple."], "cell_type": "markdown"}, {"metadata": {"_cell_guid": "7b874c1d-ecb8-4fd6-b031-300e8d27b056", "_uuid": "f48b15ce268eb0973606feece814ed6f25b6ca6c"}, "source": ["from keras.models import Sequential\n", "from keras.layers import BatchNormalization, Conv2D, UpSampling2D, Lambda\n", "simple_cnn = Sequential()\n", "simple_cnn.add(BatchNormalization(input_shape = (None, None, IMG_CHANNELS), \n", "                                  name = 'NormalizeInput'))\n", "simple_cnn.add(Conv2D(8, kernel_size = (3,3), padding = 'same'))\n", "simple_cnn.add(Conv2D(8, kernel_size = (3,3), padding = 'same'))\n", "# use dilations to get a slightly larger field of view\n", "simple_cnn.add(Conv2D(16, kernel_size = (3,3), dilation_rate = 2, padding = 'same'))\n", "simple_cnn.add(Conv2D(16, kernel_size = (3,3), dilation_rate = 2, padding = 'same'))\n", "simple_cnn.add(Conv2D(32, kernel_size = (3,3), dilation_rate = 3, padding = 'same'))\n", "\n", "# the final processing\n", "simple_cnn.add(Conv2D(16, kernel_size = (1,1), padding = 'same'))\n", "simple_cnn.add(Conv2D(1, kernel_size = (1,1), padding = 'same', activation = 'sigmoid'))\n", "simple_cnn.summary()"], "cell_type": "code", "execution_count": null, "outputs": []}, {"metadata": {"_cell_guid": "50e75f24-8a9e-4ee9-bae7-748accd3416d", "_uuid": "437e0cd78132c5716ceb8d18ae990aeca6f790b4"}, "source": ["# Loss\n", "Since we are being evaulated with intersection over union we can use the inverse of the DICE score as the loss function to optimize"], "cell_type": "markdown"}, {"metadata": {"_cell_guid": "2b143d3a-61ff-4b67-ba5d-28f1271fae0d", "collapsed": true, "_uuid": "b23a8972f9f8f6d3b72e939243565c1bcb600e8d"}, "source": ["from keras import backend as K\n", "smooth = 1.\n", "def dice_coef(y_true, y_pred):\n", "    y_true_f = K.flatten(y_true)\n", "    y_pred_f = K.flatten(y_pred)\n", "    intersection = K.sum(y_true_f * y_pred_f)\n", "    return (2. * intersection + smooth) / (K.sum(y_true_f) + K.sum(y_pred_f) + smooth)\n", "def dice_coef_loss(y_true, y_pred):\n", "    return -dice_coef(y_true, y_pred)\n", "simple_cnn.compile(optimizer = 'adam', \n", "                   loss = dice_coef_loss, \n", "                   metrics = [dice_coef, 'acc', 'mse'])"], "cell_type": "code", "execution_count": null, "outputs": []}, {"metadata": {"_cell_guid": "7af51570-8670-4b43-8ecf-fe8f2f4124a1", "_uuid": "7b84ef9770c5a7ed96acdf2efa2ff576f167e79d"}, "source": ["# Simple Training\n", "Here we run a simple training, with each image being it's own batch (not a very good idea), but it keeps the code simple"], "cell_type": "markdown"}, {"metadata": {"_cell_guid": "ac26f410-4661-44e0-a6e8-15219d25a8b8", "_uuid": "1085d9cd838c788bc075c76fe89766a6e53309be"}, "source": ["def simple_gen():\n", "    while True:\n", "        for _, c_row in train_img_df.iterrows():\n", "            yield np.expand_dims(c_row['images'],0), np.expand_dims(np.expand_dims(c_row['masks'],-1),0)\n", "\n", "simple_cnn.fit_generator(simple_gen(), \n", "                         steps_per_epoch=train_img_df.shape[0],\n", "                        epochs = 3)"], "cell_type": "code", "execution_count": null, "outputs": []}, {"metadata": {"_cell_guid": "5c7c329a-3185-4a6b-a963-99248deedb15", "_uuid": "1cc1bc84b76c1b41a3fa5b057b72feed880b3068"}, "source": ["# Apply Model to Test\n", "Here we apply the model to the test data"], "cell_type": "markdown"}, {"metadata": {"_cell_guid": "5afdbc12-09b7-456c-9a72-dbeb10a289a7", "_uuid": "058a5059e44f710a31cd19e1e9b8a72180befc49"}, "source": ["%%time\n", "test_df = img_df.query('TrainingSplit==\"test\"')\n", "test_rows = []\n", "group_cols = ['Stage', 'ImageId']\n", "for n_group, n_rows in test_df.groupby(group_cols):\n", "    c_row = {col_name: col_value for col_name, col_value in zip(group_cols, n_group)}\n", "    c_row['images'] = n_rows.query('ImageType == \"images\"')['path'].values.tolist()\n", "    test_rows += [c_row]\n", "test_img_df = pd.DataFrame(test_rows)    \n", "\n", "test_img_df['images'] = test_img_df['images'].map(read_and_stack).map(lambda x: x[:,:,:IMG_CHANNELS])\n", "print(test_img_df.shape[0], 'images to process')\n", "test_img_df.sample(1)"], "cell_type": "code", "execution_count": null, "outputs": []}, {"metadata": {"_cell_guid": "9d784556-bcf6-4dfe-90b2-aa44dc54fe0c", "_uuid": "e0db0bd3e6af0e2d79f7aa2fa81e3371ab11621a"}, "source": ["%%time\n", "test_img_df['masks'] = test_img_df['images'].map(lambda x: simple_cnn.predict(np.expand_dims(x, 0))[0, :, :, 0])"], "cell_type": "code", "execution_count": null, "outputs": []}, {"metadata": {"_cell_guid": "6fb7b659-e779-4cce-a729-5fe2e72021aa", "_uuid": "c5c545456e4a10e33d4df4dcbbab254155bea42f"}, "source": ["## Show a few predictions"], "cell_type": "markdown"}, {"metadata": {"_cell_guid": "31f9f734-b193-48db-9342-9ceb49faab39", "_uuid": "d7e88a1c070da81a0255a3792eea2a3c0b0ec3e3"}, "source": ["n_img = 3\n", "from skimage.morphology import closing, opening, disk\n", "def clean_img(x):\n", "    return opening(closing(x, disk(1)), disk(3))\n", "fig, m_axs = plt.subplots(3, n_img, figsize = (12, 6))\n", "for (_, d_row), (c_im, c_lab, c_clean) in zip(test_img_df.sample(n_img).iterrows(), \n", "                                     m_axs):\n", "    c_im.imshow(d_row['images'])\n", "    c_im.axis('off')\n", "    c_im.set_title('Microscope')\n", "    \n", "    c_lab.imshow(d_row['masks'])\n", "    c_lab.axis('off')\n", "    c_lab.set_title('Predicted')\n", "    \n", "    c_clean.imshow(clean_img(d_row['masks']))\n", "    c_clean.axis('off')\n", "    c_clean.set_title('Clean')"], "cell_type": "code", "execution_count": null, "outputs": []}, {"metadata": {"_cell_guid": "52734b1b-617a-4075-b6c1-4eaf019594cb", "collapsed": true, "_uuid": "fdabcaf0484232ba704aeaa1b9bfeba1a539271f"}, "source": ["# Check RLE\n", "Check that our approach for RLE encoding (stolen from [here](https://www.kaggle.com/rakhlin/fast-run-length-encoding-python)) works"], "cell_type": "markdown"}, {"metadata": {"_cell_guid": "1d207c46-f3a8-42ec-8381-d4e6362cac9c", "collapsed": true, "_uuid": "8fa0c109c0f035833d60a94e93867f0228c2e6dd"}, "source": ["from skimage.morphology import label # label regions\n", "def rle_encoding(x):\n", "    '''\n", "    x: numpy array of shape (height, width), 1 - mask, 0 - background\n", "    Returns run length as list\n", "    '''\n", "    dots = np.where(x.T.flatten()==1)[0] # .T sets Fortran order down-then-right\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", "\n", "def prob_to_rles(x, cut_off = 0.5):\n", "    lab_img = label(x>cut_off)\n", "    if lab_img.max()<1:\n", "        lab_img[0,0] = 1 # ensure at least one prediction per image\n", "    for i in range(1, lab_img.max()+1):\n", "        yield rle_encoding(lab_img==i)"], "cell_type": "code", "execution_count": null, "outputs": []}, {"metadata": {"_cell_guid": "3731ae6b-b38c-4bf7-afa7-4338126fc0a7", "_uuid": "ccf9ce4c65200467574c93a8a4adb8d4b4cb4815"}, "source": ["## Calculate the RLEs for a Train Image"], "cell_type": "markdown"}, {"metadata": {"_cell_guid": "6a247714-aa63-482e-a8a1-b0f11a3ac31f", "collapsed": true, "_uuid": "5f2bb3e72ec8ed8186b55212f141d67beee101c7"}, "source": ["_, train_rle_row = next(train_img_df.tail(5).iterrows()) \n", "train_row_rles = list(prob_to_rles(train_rle_row['masks']))"], "cell_type": "code", "execution_count": null, "outputs": []}, {"metadata": {"_cell_guid": "817ae2a7-e0b3-469f-b41e-691512e582cc", "_uuid": "d311881b690401d4032da7bbe2aa5f7f97b207e5"}, "source": ["## Take the RLEs from the CSV"], "cell_type": "markdown"}, {"metadata": {"_cell_guid": "c74939d8-f78d-4856-b2ae-a69c2ca0b83c", "collapsed": true, "_uuid": "984845ff5072fc3ebb1b106831e4992170d5902f"}, "source": ["tl_rles = train_labels.query('ImageId==\"{ImageId}\"'.format(**train_rle_row))['EncodedPixels']"], "cell_type": "code", "execution_count": null, "outputs": []}, {"metadata": {"_cell_guid": "7af84b83-644c-44f7-a902-4797a0da9487", "_uuid": "80f40ffd0afe3710f3646d843abfb0ed0491630e"}, "source": ["## Check\n", "Since we made some simplifications, we don't expect everything to be perfect, but pretty close"], "cell_type": "markdown"}, {"metadata": {"_cell_guid": "72dd2317-89c7-4408-b42e-56c843fd54f6", "_uuid": "4841b68b07de08b6d3bb7e054e2d5830962148d8"}, "source": ["match, mismatch = 0, 0\n", "for img_rle, train_rle in zip(sorted(train_row_rles, key = lambda x: x[0]), \n", "                             sorted(tl_rles, key = lambda x: x[0])):\n", "    for i_x, i_y in zip(img_rle, train_rle):\n", "        if i_x == i_y:\n", "            match += 1\n", "        else:\n", "            mismatch += 1\n", "print('Matches: %d, Mismatches: %d, Accuracy: %2.1f%%' % (match, mismatch, 100.0*match/(match+mismatch)))"], "cell_type": "code", "execution_count": null, "outputs": []}, {"metadata": {"_cell_guid": "d4801c91-c922-452f-a835-a4e93ae1f356", "collapsed": true, "_uuid": "19c34be3cc8c907e4d36bfcb98b6781948ec21df"}, "source": ["# Calculate RLE for all the masks\n", "Here we generate the RLE for all the masks and output the the results to a table. We use a few morphological operations to clean up the images before submission since they can be very messy (remove single pixels, connect nearby regions, etc)"], "cell_type": "markdown"}, {"metadata": {"_cell_guid": "2e9375d2-e6b8-462d-95b7-0fda696fbfc4", "_uuid": "26d2c83004921a8b985084d8d168a54f4b728b61"}, "source": ["test_img_df['rles'] = test_img_df['masks'].map(clean_img).map(lambda x: list(prob_to_rles(x)))"], "cell_type": "code", "execution_count": null, "outputs": []}, {"metadata": {"_cell_guid": "65be8e2b-b46a-452f-a1d6-fe97b1932e68", "_uuid": "93f011b814251537f4c574908876e6157566c0b3"}, "source": ["out_pred_list = []\n", "for _, c_row in test_img_df.iterrows():\n", "    for c_rle in c_row['rles']:\n", "        out_pred_list+=[dict(ImageId=c_row['ImageId'], \n", "                             EncodedPixels = ' '.join(np.array(c_rle).astype(str)))]\n", "out_pred_df = pd.DataFrame(out_pred_list)\n", "print(out_pred_df.shape[0], 'regions found for', test_img_df.shape[0], 'images')\n", "out_pred_df.sample(3)"], "cell_type": "code", "execution_count": null, "outputs": []}, {"metadata": {"_cell_guid": "7f5f0c3a-222f-4470-88a7-d2f77bd54542", "collapsed": true, "_uuid": "4524606e4ae416dcf364a5170f82cf4ceea6d2af"}, "source": ["out_pred_df[['ImageId', 'EncodedPixels']].to_csv('predictions.csv', index = False)"], "cell_type": "code", "execution_count": null, "outputs": []}, {"metadata": {"_cell_guid": "576a277d-3815-4d59-a5b8-1924c1bafcee", "collapsed": true, "_uuid": "e118dc861c34fcf23e1954166b7a4e9607987955"}, "source": [], "cell_type": "code", "execution_count": null, "outputs": []}], "metadata": {"kernelspec": {"name": "python3", "language": "python", "display_name": "Python 3"}, "language_info": {"name": "python", "nbconvert_exporter": "python", "version": "3.6.4", "pygments_lexer": "ipython3", "mimetype": "text/x-python", "codemirror_mode": {"name": "ipython", "version": 3}, "file_extension": ".py"}}, "nbformat_minor": 1, "nbformat": 4}