{"cells":[{"metadata":{},"cell_type":"markdown","source":"# Pneumonia Detection Challenge"},{"metadata":{},"cell_type":"markdown","source":"**What is Pneumonia?**\n>Pneumonia is an infection in one or both lungs. Bacteria, viruses, and fungi cause it. The infection causes inflammation in the air sacs in your lungs, which are called alveoli.\n<br/>\n>In this project we will be building CNN model to detect Pneumonia and locating the lung opacities in CXR (Chest Radiographs) images, which usually used by highly trained specialists in medical science to detect Pneumonia in a person."},{"metadata":{},"cell_type":"markdown","source":"**Data Directory**"},{"metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"cell_type":"code","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\n\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\nimport random\n\n# Input data files are available in the read-only \"../input/\" directory\n# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\nimport matplotlib.pyplot as plt\nimport matplotlib.patches as patches\nimport pydicom\n\nimport os\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        print(os.path.join(dirname, filename))\n\n# You can write up to 5GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using \"Save & Run All\" \n# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","trusted":true},"cell_type":"code","source":"dirname = '/kaggle/input/rsna-pneumonia-detection-challenge/'\ntrain_img_dir = dirname + 'stage_2_train_images'\ntest_img_dir = dirname + 'stage_2_test_images' \n\nos.getcwd()\n\ntotal_Train_img_dir = len([name for name in os.listdir(train_img_dir) if os.path.isfile(os.path.join(train_img_dir, name)) ])\ntotal_test_img_dir = len([name for name in os.listdir(test_img_dir) if os.path.isfile(os.path.join(test_img_dir, name)) ])\n\nprint('Number of training samples : ', total_Train_img_dir  )\nprint('Number of test samples     : ', total_test_img_dir )\n","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"import pandas as pd\n\ntrain_data = pd.read_csv(dirname+\"stage_2_train_labels.csv\")\ntrain_data.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"pbox_locations = {}\ntrain_images = []\ntarget = []\nfor i in range(len(train_data)):\n    n = str(dirname +'stage_2_train_images/' + train_data['patientId'][i] + '.dcm')\n    \n    if n not in train_images:\n        train_images.append(n)\n        target.append(train_data['Target'][i])\n    if train_data['Target'][i]==1:\n        loc = [int(train_data['x'][i]), int(train_data['y'][i]),\n               int(train_data['width'][i]), int(train_data['height'][i])]\n        if n in pbox_locations:\n            pbox_locations[n].append(loc)\n        else:\n            pbox_locations[n] = [loc]\ntrain_images = pd.DataFrame(train_images)\ntrain_images.columns = ['filepath']\ntrain_images['Target'] = target","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"train_images.head()\n","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"train_images.Target.value_counts()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"len(pbox_locations)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"import seaborn as sns\n\n\nsns.countplot(train_images.Target)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"im = (pydicom.dcmread(train_images['filepath'][10]).pixel_array)\nplt.imshow(im)\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"f, axarr = plt.subplots(2, 5, figsize=(20, 15))\naxarr = axarr.ravel()\naxidx = 0\ndf1 = train_images[train_images['Target']==0].sample(5)\ndf2 = train_images[train_images['Target']==1].sample(5)\ndf = np.concatenate((np.array(df1['filepath']),np.array(df2['filepath'])),axis=0)\ndf = list(df)\nfor i in range(len(df)):\n    axarr[axidx].imshow(pydicom.dcmread(df[i]).pixel_array)\n    if df[i] in pbox_locations:\n        l = pbox_locations[df[i]]\n        for j in l:\n            axarr[axidx].add_patch(patches.Rectangle((j[0], j[1]), j[2], j[3], linewidth=2, edgecolor='b', facecolor='none'))\n    axidx+=1\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"import keras\nimport tensorflow as tf\nfrom keras.models import Model\nfrom keras.layers import Input, BatchNormalization, Activation, Dense, Dropout\nfrom keras.layers.convolutional import Conv2D, Conv2DTranspose\nfrom keras.layers.pooling import MaxPooling2D, GlobalMaxPool2D\nfrom keras.layers.merge import concatenate, add\nfrom keras.callbacks import EarlyStopping, ModelCheckpoint\nfrom keras.optimizers import Adam\nimport cv2\nfrom skimage.transform import resize","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"class generator_single_channel(keras.utils.Sequence):\n    \n    def __init__(self, folder, filenames, pbox_locations=None, batch_size=32,\n                 image_size=256, shuffle=True, predict=False):\n        self.folder = folder\n        self.filenames = filenames\n        self.pbox_locations = pbox_locations\n        self.batch_size = batch_size\n        self.image_size = image_size\n        self.shuffle = shuffle\n        self.predict = predict\n        self.on_epoch_end()\n        \n    def __load__(self, filename):\n        # load dicom file as numpy array\n        img = pydicom.dcmread(os.path.join(self.folder, filename)).pixel_array\n        # create empty mask\n        msk = np.zeros(img.shape)\n        # get filename without extension\n        filename = filename.split('.')[0]\n        # if image contains pneumonia\n        is_pneumonia = int(0)\n        if filename in self.pbox_locations:\n            # loop through pneumonia\n            is_pneumonia = int(1)\n            for location in self.pbox_locations[filename]:\n                # add 1's at the location of the pneumonia\n                x, y, w, h = location\n                msk[y:y+h, x:x+w] = 1\n        # resize both image and mask\n        img = resize(img, (self.image_size, self.image_size), mode='reflect')\n        msk = resize(msk, (self.image_size, self.image_size), mode='reflect')\n        # if augment then horizontal flip half the time\n        # if self.augment and random.random() > 0.5:\n        #     img = np.fliplr(img)\n        #     msk = np.fliplr(msk)\n\n        # add trailing channel dimension\n        img = np.expand_dims(img, axis=-1)\n        msk = np.expand_dims(msk, axis=-1)\n        is_pneumonia = np.array(is_pneumonia)\n\n        return img, msk\n    \n    def __loadpredict__(self, filename):\n        # load dicom file as numpy array\n        img = pydicom.dcmread(os.path.join(self.folder, filename)).pixel_array\n        # resize image\n        img = resize(img, (self.image_size, self.image_size), mode='reflect')\n        # add trailing channel dimension\n        img = np.expand_dims(img, axis=-1)\n        \n        return img\n        \n    def __getitem__(self, index):\n        # select batch\n        filenames = self.filenames[index*self.batch_size:(index+1)*self.batch_size]\n        # predict mode: return images and filenames\n        if self.predict:\n            # load files\n            imgs = [self.__loadpredict__(filename) for filename in filenames]\n            # create numpy batch\n            imgs = np.array(imgs)\n            \n            return imgs,filenames\n        # train mode: return images and masks\n        else:\n            # load files\n            items = [self.__load__(filename) for filename in filenames]\n            # unzip images and masks\n            imgs, msks = zip(*items)\n            # create numpy batch\n            imgs = np.array(imgs)\n            msks = np.array(msks)\n\n            return imgs,msks\n        \n    def on_epoch_end(self):\n        if self.shuffle:\n            random.shuffle(self.filenames)\n        \n    def __len__(self):\n        if self.predict:\n            # return everything\n            return int(np.ceil(len(self.filenames) / self.batch_size))\n        else:\n            # return full batches only\n            return int(len(self.filenames) / self.batch_size)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def conv2d_block(input_tensor, n_filters, kernel_size=3, batchnorm=True):\n    # first layer\n    x = Conv2D(filters=n_filters, kernel_size=(kernel_size, kernel_size), kernel_initializer=\"he_normal\",\n               padding=\"same\")(input_tensor)\n    if batchnorm:\n        x = BatchNormalization()(x)\n    x = Activation(\"relu\")(x)\n    # second layer\n    x = Conv2D(filters=n_filters, kernel_size=(kernel_size, kernel_size), kernel_initializer=\"he_normal\",\n               padding=\"same\")(x)\n    if batchnorm:\n        x = BatchNormalization()(x)\n    x = Activation(\"relu\")(x)\n    return x","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def get_unet(input_img, n_filters=16, dropout=0.5, batchnorm=True):\n    # contracting path\n    c1 = conv2d_block(input_img, n_filters=n_filters*1, kernel_size=3, batchnorm=batchnorm)\n    p1 = MaxPooling2D((2, 2)) (c1)\n    p1 = Dropout(dropout*0.5)(p1)\n\n    c2 = conv2d_block(p1, n_filters=n_filters*2, kernel_size=3, batchnorm=batchnorm)\n    p2 = MaxPooling2D((2, 2)) (c2)\n    p2 = Dropout(dropout)(p2)\n\n    c3 = conv2d_block(p2, n_filters=n_filters*4, kernel_size=3, batchnorm=batchnorm)\n    p3 = MaxPooling2D((2, 2)) (c3)\n    p3 = Dropout(dropout)(p3)\n\n    c4 = conv2d_block(p3, n_filters=n_filters*8, kernel_size=3, batchnorm=batchnorm)\n    p4 = MaxPooling2D(pool_size=(2, 2)) (c4)\n    p4 = Dropout(dropout)(p4)\n    \n    c5 = conv2d_block(p4, n_filters=n_filters*16, kernel_size=3, batchnorm=batchnorm)\n    \n    # expansive path\n    u6 = Conv2DTranspose(n_filters*8, (3, 3), strides=(2, 2), padding='same') (c5)\n    u6 = concatenate([u6, c4])\n    u6 = Dropout(dropout)(u6)\n    c6 = conv2d_block(u6, n_filters=n_filters*8, kernel_size=3, batchnorm=batchnorm)\n\n    u7 = Conv2DTranspose(n_filters*4, (3, 3), strides=(2, 2), padding='same') (c6)\n    u7 = concatenate([u7, c3])\n    u7 = Dropout(dropout)(u7)\n    c7 = conv2d_block(u7, n_filters=n_filters*4, kernel_size=3, batchnorm=batchnorm)\n\n    u8 = Conv2DTranspose(n_filters*2, (3, 3), strides=(2, 2), padding='same') (c7)\n    u8 = concatenate([u8, c2])\n    u8 = Dropout(dropout)(u8)\n    c8 = conv2d_block(u8, n_filters=n_filters*2, kernel_size=3, batchnorm=batchnorm)\n\n    u9 = Conv2DTranspose(n_filters*1, (3, 3), strides=(2, 2), padding='same') (c8)\n    u9 = concatenate([u9, c1], axis=3)\n    u9 = Dropout(dropout)(u9)\n    c9 = conv2d_block(u9, n_filters=n_filters*1, kernel_size=3, batchnorm=batchnorm)\n    \n    outputs = Conv2D(1, (1, 1), activation='sigmoid') (c9)\n    model = Model(inputs=[input_img], outputs=[outputs])\n    return model","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"input_img = Input((224, 224, 1), name='img')\nmodel = get_unet(input_img, n_filters=16, dropout=0.05, batchnorm=True)\nmodel.summary()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def iou_loss(y_true, y_pred):\n    y_true = tf.reshape(y_true, [-1])\n    y_pred = tf.reshape(y_pred, [-1])\n    intersection = tf.reduce_sum(y_true * y_pred)\n    score = (intersection + 1.) / (tf.reduce_sum(y_true) + tf.reduce_sum(y_pred) - intersection + 1.)\n    return 1 - score\n\n# def bce_loss(y_true,y_pred):\n#   return keras.losses.binary_crossentropy(y_true,y_pred)\n\n# mean iou as a metric\ndef mean_iou(y_true, y_pred):\n    y_pred = tf.round(y_pred)\n    intersect = tf.reduce_sum(y_true * y_pred)\n    union = tf.reduce_sum(y_true) + tf.reduce_sum(y_pred)\n    smooth = tf.ones(tf.shape(intersect))\n    return tf.reduce_mean((intersect + smooth) / (union - intersect + smooth))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"model.compile(optimizer='adam',\n                     loss=iou_loss,\n                     metrics=[mean_iou,'accuracy'])\n\ncheckpoint = keras.callbacks.ModelCheckpoint(\"pnuemonia-detection-unet_{val_loss:.4f}.h5\",monitor='val_loss',\n                             verbose=1, save_best_only=False,save_weights_only=True, mode=\"auto\")\n\nes = keras.callbacks.EarlyStopping(monitor='val_loss', patience=4)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"folder = train_img_dir\nfilenames = os.listdir(folder)\nrandom.shuffle(filenames)\n# split into train and validation filenames\nn_valid_samples = 6000\ntrain_filenames = filenames[n_valid_samples:]\nvalid_filenames = filenames[:n_valid_samples]\nprint('n train samples', len(train_filenames))\nprint('n valid samples', len(valid_filenames))\nn_train_samples = len(filenames) - n_valid_samples","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# create train and validation generators\ntrain_gen = generator_single_channel(folder, train_filenames, pbox_locations, batch_size=64, image_size=224, shuffle=True, predict=False)\nvalid_gen = generator_single_channel(folder, valid_filenames, pbox_locations, batch_size=64, image_size=224, shuffle=False, predict=False)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"history = model.fit_generator(train_gen, validation_data=valid_gen, callbacks=[checkpoint,es], epochs=5)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"**Model Training Metrics**"},{"metadata":{"trusted":true},"cell_type":"code","source":"from skimage.segmentation import mark_boundaries\nfrom skimage.measure import label, regionprops\nimport matplotlib.pyplot as plt\nimport matplotlib as mpl\nimport cv2","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"plt.figure(figsize=(12,4))\nplt.subplot(131)\nplt.plot(history.epoch, history.history[\"loss\"], label=\"Train loss\")\nplt.plot(history.epoch, history.history[\"val_loss\"], label=\"Valid loss\")\nplt.legend()\nplt.subplot(132)\nplt.plot(history.epoch, history.history[\"acc\"], label=\"Train accuracy\")\nplt.plot(history.epoch, history.history[\"val_acc\"], label=\"Valid accuracy\")\nplt.legend()\nplt.subplot(133)\nplt.plot(history.epoch, history.history[\"mean_iou\"], label=\"Train iou\")\nplt.plot(history.epoch, history.history[\"val_mean_iou\"], label=\"Valid iou\")\nplt.legend()\nplt.show()","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}