{"cells":[{"metadata":{"_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","trusted":true},"cell_type":"code","source":"import warnings; warnings.filterwarnings('ignore')\nimport pandas as pd, numpy as np\nimport matplotlib.pyplot as plt\n\nimport os, zipfile, random, csv\nimport seaborn as sns\nimport pydicom as dcm\nfrom glob import glob\nimport cv2","execution_count":null,"outputs":[]},{"metadata":{"_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","trusted":true},"cell_type":"code","source":"# # Install packages\n#!pip install -q pydicom","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"\nprint(f'Current working directory: {os.getcwd()}')\nprint('Folder and Files in current directory: {}'.format(os.listdir()))\nPATH = '/kaggle/'\nDATA_FOLDER = os.path.join(PATH, 'input/rsna-pneumonia-detection-challenge/')\nFolder_DCM_IMAGES = os.path.join(DATA_FOLDER,'stage_2_train_images/')\n\nWORKING_FOLDER = os.path.join(PATH,'working/')\nSAVE_PATH = os.path.join(WORKING_FOLDER,'Saved_Data/')\n\nif not os.path.exists(SAVE_PATH):\n    os.makedirs(SAVE_PATH)\n    \n\nprint(\"DATA_FOLDER: \", DATA_FOLDER)  \nprint(\"Folder_DCM_IMAGES: \", Folder_DCM_IMAGES) \nprint(\"SAVE_PATH: \", SAVE_PATH) \nprint(\"WORKING_FOLDER: \", WORKING_FOLDER) \n\nos.chdir(WORKING_FOLDER)\nprint(f'Current working directory: {os.getcwd()}')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"train_labels = pd.read_csv(os.path.join(DATA_FOLDER,'stage_2_train_labels.csv'))\nclass_info = pd.read_csv(os.path.join(DATA_FOLDER,'stage_2_detailed_class_info.csv'))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"print(f'Train Labels dataframe has {train_labels.shape[0]} rows and {train_labels.shape[1]} columns')\nprint(f'Class info dataframe has {class_info.shape[0]} rows and {class_info.shape[1]} columns')\nprint('Number of duplicates in patientID in train labels dataframe: {}'.format(len(train_labels) - (train_labels['patientId'].nunique())))\nprint('Number of duplicates in patientID in class info dataframe: {}'.format(len(class_info) - (class_info['patientId'].nunique())))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"import os\nimport csv\nimport random\n!pip install pydicom\nimport pydicom\nimport numpy as np\nimport pandas as pd\nfrom skimage import io\nfrom skimage import measure\nfrom skimage.transform import resize\n\nimport tensorflow as tf\nfrom tensorflow import keras\n\nfrom matplotlib import pyplot as plt\nimport matplotlib.patches as patches","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"TRAIN_PATH = os.path.join(DATA_FOLDER +'stage_2_train_images/')\n\nprint(TRAIN_PATH)\nfilenames = {}\n\nread_directory = True # False\nif(read_directory):\n  filenames = os.listdir(TRAIN_PATH)\n  pd.DataFrame(filenames).to_csv(SAVE_PATH+'train_path_listdir.csv')\nelse:\n  filenames=pd.read_csv(SAVE_PATH+'train_path_listdir.csv', usecols=[1],header=0).values.tolist()\n  filenames = [val for sublist in filenames for val in sublist]\n","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"\n# Use part of the data for training earlier and then run for 100% of the data\npercentage_data_used = 100\nfile_count = int(len(filenames)*percentage_data_used/100)\nprint(\"Total files available:\",file_count)\n\nrandom.shuffle(filenames)\n\n# split into train and validation filenames\nn_valid_samples = int(file_count * 0.3)\n\ntrain_filenames = filenames[n_valid_samples:file_count]\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\n\nimage_dimension = 224\nprint('Image Dimension to use:',image_dimension)\nprint('sample file:',filenames[0])","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Check dist of selected files based on the csv that was provided. \n# Did not try stratified sampling since the distribution is not impacted much.\ndef check_distribution(dataframe_to_check):\n  filename_check = pd.DataFrame(columns=['patientId','class'])\n  #get filename \n  for filename in dataframe_to_check:\n    filename_check = filename_check.append(class_info[class_info['patientId'] == filename.split('.')[0]])\n    \n  print('Rows',len(filename_check))\n  print('unique',len(filename_check['patientId'].unique()))\n  print(filename_check['class'].value_counts(normalize = True))\n\ncheck_distribution(train_filenames)\ncheck_distribution(valid_filenames)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# empty dictionary\npneumonia_locations = {}\n# load table\nwith open(os.path.join(DATA_FOLDER,'stage_2_train_labels.csv'), mode='r') as infile:\n    # open reader\n    reader = csv.reader(infile)\n    # skip header\n    next(reader, None)\n    # loop through rows\n    for rows in reader:\n        # retrieve information\n        filename = rows[0]\n        location = rows[1:5]\n        pneumonia = rows[5]\n        # if row contains pneumonia add label to dictionary\n        # which contains a list of pneumonia locations per filename\n        if pneumonia == '1':\n            # convert string to float to int\n            location = [int(float(i)) for i in location]\n            # save pneumonia location in dictionary\n            if filename in pneumonia_locations:\n                pneumonia_locations[filename].append(location)\n            else:\n                pneumonia_locations[filename] = [location]","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"import keras\n\n# The dataset is too large to fit into memory, so we need to create a generator that loads data on the fly.\n# Generator class to handle:\n# Image load from folder during train and predict modes, shuffle on epoc end, \n# resize loaded images, augment if needed, add trailing channel dimension\nclass generator(keras.utils.Sequence):\n    \n    def __init__(self, folder, filenames, pneumonia_locations=None, batch_size=8, image_size=image_dimension, shuffle=True, augment=False, predict=False):\n        self.folder = folder\n        self.filenames = filenames\n        self.pneumonia_locations = pneumonia_locations\n        self.batch_size = batch_size\n        self.image_size = image_size\n        self.shuffle = shuffle\n        self.augment = augment\n        self.predict = predict\n        self.on_epoch_end()\n        \n    # Loads the file from folder, resizes and augments the data with horizontal flip    \n    def __load__(self, filename):\n        # load dicom file as numpy array\n        img = pydicom.dcmread(os.path.join(self.folder, filename), force=True).pixel_array\n        \n        \n        # create empty mask\n        msk = np.zeros(img.shape)\n        img = np.stack((img,)*3, axis=-1)#binu code\n        # get filename without extension\n        filename = filename.split('.')[0]\n        # if image contains pneumonia\n        if filename in self.pneumonia_locations:\n            # loop through pneumonia\n            for location in self.pneumonia_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') > 0.5\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        # add trailing channel dimension\n        return img, msk\n    \n    # Loads images during prediction cycles\n    def __loadpredict__(self, filename):\n        # load dicom file as numpy array\n        img = pydicom.dcmread(os.path.join(self.folder, filename), force=True).pixel_array\n        img = np.stack((img,)*3, axis=-1)#binu code\n        \n        # resize image\n        img = resize(img, (self.image_size, self.image_size), mode='reflect')\n        # add trailing channel dimension\n        return img\n        \n    # Generator must implement this getter function    \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            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            return imgs, msks\n\n    # Shuffle data before start of next epoc    \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":{},"cell_type":"markdown","source":"## Layers and Model Architecture"},{"metadata":{"trusted":true},"cell_type":"code","source":"from tensorflow.keras.applications.mobilenet import MobileNet\nfrom tensorflow.keras.layers import Concatenate, UpSampling2D, Conv2D, Reshape, BatchNormalization\nfrom tensorflow.keras.models import Model\n\n\n# Model creator\ndef create_model(input_size, trainable=False):\n    model = MobileNet(input_shape=(input_size, input_size, 3), include_top=False, alpha=1.0, weights = \"imagenet\", dropout=0.4)\n\n    block1 = model.get_layer(\"conv_pw_1_relu\").output\n    block2 = model.get_layer(\"conv_pw_3_relu\").output\n    block3 = model.get_layer(\"conv_pw_5_relu\").output\n    block6 = model.get_layer(\"conv_pw_11_relu\").output\n    block7 = model.get_layer(\"conv_pw_13_relu\").output\n\n    x = Concatenate()([UpSampling2D()(block7), block6])\n    x = BatchNormalization()(x)\n    x = Concatenate()([UpSampling2D()(x), block3])\n    x = BatchNormalization()(x)\n    x = Concatenate()([UpSampling2D()(x), block2])\n    x = BatchNormalization()(x)\n    x = Concatenate()([UpSampling2D()(x), block1])\n    x = BatchNormalization()(x)\n    x =  UpSampling2D()(x)\n    x = Conv2D(1, kernel_size=1, activation=\"sigmoid\")(x)\n    x = Reshape((input_size, input_size))(x)\n\n    return Model(inputs=model.input, outputs=x)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Learning Functions"},{"metadata":{"trusted":true},"cell_type":"code","source":"from tensorflow.keras.callbacks import ModelCheckpoint, EarlyStopping, ReduceLROnPlateau\nfrom keras.optimizers import Adam\n# model = create_model(False)\n\n\noptimizer = Adam(lr=1e-4, beta_1=0.9, beta_2=0.999, epsilon=None, decay=0.0, amsgrad=False)\n\n# model.summary()\nPATIENCE = 10\n\ncheckpoint = ModelCheckpoint(SAVE_PATH + \"model-{val_loss:.2f}.h5\", monitor=\"val_loss\", verbose=1, save_best_only=True,\n                             save_weights_only=True, mode=\"auto\", period=1)\nstop = EarlyStopping(monitor=\"val_loss\", patience=PATIENCE, mode=\"auto\")\nreduce_lr = ReduceLROnPlateau(monitor=\"val_loss\", factor=0.2, patience=2, min_lr=1e-12, verbose=1, mode=\"auto\")","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# define iou or jaccard loss function\ndef 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(float(y_true) * float(y_pred))\n    score = (intersection + 1.) / (tf.reduce_sum(float(y_true)) + tf.reduce_sum(float(y_pred)) - intersection + 1.)\n    return 1 - score\n\n# combine bce loss and iou loss\ndef iou_bce_loss(y_true, y_pred):\n    return 0.5 * keras.losses.binary_crossentropy(y_true, y_pred) + 0.5 * iou_loss(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(float(y_true) * float(y_pred), axis=[1])\n    union = tf.reduce_sum(float(y_true),axis=[1]) + tf.reduce_sum(float(y_pred),axis=[1])\n    smooth = tf.ones(tf.shape(intersect))\n    return tf.reduce_mean((intersect + smooth) / (union - intersect + smooth))\n\n\n# create network and compiler\nmodel = create_model(input_size=image_dimension)\n\nmodel.compile(loss= iou_loss, optimizer=optimizer, metrics=['accuracy', mean_iou]) \n\nmodel.summary()\n\n# cosine learning rate annealing\n# changes learning rate based on the number of epocs passed\ndef cosine_annealing(x):\n    lr = 1e-4\n    epochs = 5\n    return lr* (np.cos(np.pi*x/epochs)+1.) /2\nlearning_rate = tf.keras.callbacks.LearningRateScheduler(cosine_annealing)\n\n# keeps logging the epoc output simultaneously while training\ncsv_logger = tf.keras.callbacks.CSVLogger(SAVE_PATH + 'logs_cnn_segment.csv', append = True)\n\n# Creating checkpoint of the best model to avoid save errors later on.\n# Saves training time once the best model is achieved.\ncp = tf.keras.callbacks.ModelCheckpoint(filepath = SAVE_PATH + 'model_checkpoint.h5', verbose = 1, save_best_only = True)\n\n# Keep monitoring val_loss to see if there is any improvement. \n# Mostly the model kept loss in a range so keeping patience as 4 to avoid bloating training time. \n# Any improvement of 0.5% in val_loss would get captured\nearly_stopping = tf.keras.callbacks.EarlyStopping(monitor='val_loss', min_delta=0.005, patience=2, restore_best_weights=True, verbose=1, mode='auto')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# create train and validation generators\nTRAIN_PATH = os.path.join(DATA_FOLDER +'stage_2_train_images/')\n\ntrain_gen = generator(TRAIN_PATH, train_filenames, pneumonia_locations, batch_size=4, image_size=image_dimension, shuffle=True, augment=True, predict=False)\nvalid_gen = generator(TRAIN_PATH, valid_filenames, pneumonia_locations, batch_size=4, image_size=image_dimension, shuffle=False, predict=False)\n\nhistory = model.fit_generator(generator=train_gen,\n                    epochs=5,\n                    validation_data=valid_gen,\n                    callbacks=[checkpoint, reduce_lr, stop],\n                    workers=4,\n                    use_multiprocessing=True,\n                    shuffle=True)\n\n# Save Model and history\ntraining_mode = True\nif(training_mode):\n  export_file_path = SAVE_PATH+'pneumonia_model.h5'\n\n  # Save the model\n  model.save(export_file_path)\n  print('model saved')\n\n  # Save history file\n  print(history.history.keys())\n  # convert the history.history dict to a pandas DataFrame:     \n  hist_df = pd.DataFrame(history.history) \n\n  # or save to csv: \n  hist_csv_file = SAVE_PATH+'pneumonia_model_history.csv'\n  with open(hist_csv_file, mode='w') as f:\n      hist_df.to_csv(f)\n  print('history saved')\n\n  \nelse:\n  model.load_weights(SAVE_PATH+'pneumonia_model.h5')\n  history = pd.read_csv(SAVE_PATH+'pneumonia_model_history.csv')\n  history","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"plt.figure(figsize=(25,6))\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[\"accuracy\"], label=\"Train accuracy\")\nplt.plot(history.epoch, history.history[\"val_accuracy\"], 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()\n","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Red is predicted mask, Blue is actual Mask\nfor imgs, msks in valid_gen:\n    # predict batch of images\n    preds = model.predict(imgs)\n    # create figure\n    f, axarr = plt.subplots(4, 8, figsize=(20,15))\n    \n    # Flatten the array\n    axarr = axarr.ravel()\n    axidx = 0\n    #print(msk)\n    # loop through batch\n    for img, msk, pred in zip(imgs, msks, preds):\n        # plot image\n        axarr[axidx].imshow(img[:, :, 0],cmap='bone')\n        \n        # threshold true mask\n        comp = msk[:, :] > 0.5\n        # apply connected components\n        comp = measure.label(comp)\n        # apply bounding boxes\n        predictionString = ''\n        for region in measure.regionprops(comp):\n            # retrieve x, y, height and width\n            y, x, y2, x2 = region.bbox\n            height = y2 - y\n            width = x2 - x\n            axarr[axidx].add_patch(patches.Rectangle((x,y),width,height,linewidth=2,edgecolor='b',facecolor='none'))\n        \n        # threshold predicted mask\n        comp = pred[:, :] > 0.5\n        # apply connected components\n        comp = measure.label(comp)\n        # apply bounding boxes\n        predictionString = ''\n        for region in measure.regionprops(comp):\n            # retrieve x, y, height and width\n            y, x, y2, x2 = region.bbox\n            height = y2 - y\n            width = x2 - x\n            axarr[axidx].add_patch(patches.Rectangle((x,y),width,height,linewidth=2,edgecolor='r',facecolor='none'))\n        axidx += 1\n    plt.show()\n    # only plot one batch\n    break\n\n    # There was considerable IOU for the images where prediction was correct. ","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}