{"cells":[{"metadata":{},"cell_type":"markdown","source":"I have taken few code lines and insipiration from Peter Chang's awsome [Exploratory Data Analysis](https://www.kaggle.com/peterchang77/exploratory-data-analysis) and Understanding from [What are Lung Opacities](https://www.kaggle.com/zahaviguy/what-are-lung-opacities#Chest-Radiographs-Basics) from Dr Guy Zahavi \n\nSince lot of things have been explained by Dr Guy Zhavi on Lung Opacity its very Nice article people who are attempting on this should read about it. "},{"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\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\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":{"trusted":true},"cell_type":"code","source":"import glob, pylab, pandas as pd\nimport pydicom, numpy as np\nimport matplotlib.pyplot as plt","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","trusted":true},"cell_type":"code","source":"!ls /kaggle/input/rsna-pneumonia-detection-challenge","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Lets check the label files \ni.e. stage_2_train_labels.csv"},{"metadata":{"trusted":true},"cell_type":"code","source":"import sys\nprint(sys.version)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# **Exploratory Data Analysis**"},{"metadata":{"trusted":true},"cell_type":"code","source":"pr_root='/kaggle/input/rsna-pneumonia-detection-challenge'\n\ndetail_info_df = pd.read_csv(pr_root+'/stage_2_detailed_class_info.csv')\n","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"detail_info_df.info()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"detail_info_df.info()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"detail_info_df.head(3)\n\n","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"detail_info_df['class'].unique()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"detail_info_df.isnull().apply(pd.value_counts)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"**- There are no null values in the stage_2_detailed_class_info.csv**"},{"metadata":{"trusted":true},"cell_type":"code","source":"# Read stage_2_train_labels.csv: Sample file is in proper format.\ntrain_df = pd.read_csv(pr_root+'/stage_2_train_labels.csv')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"train_df.info()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"print(\"\\nShape of train labels dataset:\", train_df.shape)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"train_df.head(3)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"train_df.nunique(dropna=True)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"- Train labels needs to be cleansed. There are Null values in the dataset.\n- Train lables has 6 columns. This dataset contains the bounding box information and Target class catagory. It contains 30227 records\n- Detailed class info dataset has 2 columns and 30227 records. It contains the patient class.\n- There are only 26884 unique records only\n"},{"metadata":{"trusted":true},"cell_type":"code","source":"train_df.isnull().apply(pd.value_counts)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Analyse patientIds and their corresponding bounding boxes. \nbounding_box_by_pat_grp = train_df.groupby(['patientId']).size().to_frame('total_boxes').reset_index()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"bounding_box_by_pat_grp.groupby(['total_boxes']).size().to_frame('NumberOfPatients').reset_index()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Concate the train_lbl_df and cls_info_df and create a single dataframe for processing, as each patientId is associated with unique class label\n#detailed_trained_lbl_df = pd.merge(train_lbl_df, detailed_cls_info_df, how='inner')\ncombined_df = pd.concat([train_df, detail_info_df['class']],  join='inner', verify_integrity=True, axis = 1)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"unique_values, count = np.unique(combined_df['Target'], return_counts=True)\nprint(\"unique_values: \",unique_values)\nlbls = {1: 'Pneumonia symptoms present', 0: 'Normal'}\n\n# Visualize the propertion of Pneumonia vs normal patients\nplt.pie(count, labels = ['Pneumonia symptoms present', 'Normal'], autopct='%1.1f%%', startangle=90)\nplt.tight_layout()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"unique_values, count = np.unique(combined_df['Target'], return_counts=True)\nprint(\"unique_values: \",unique_values)\nlbls = {1: 'Pneumonia symptoms present', 0: 'Normal'}\n\n# Visualize the propertion of Pneumonia vs normal patients\nplt.pie(count, labels = ['Pneumonia symptoms present', 'Normal'], autopct='%1.1f%%', startangle=90)\nplt.tight_layout()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"combined_df.head(3)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#add dicom file column to dataframe\ncombined_df['dicom'] = combined_df.apply(lambda x: (pr_root+'/stage_2_train_images/%s.dcm' % x['patientId']),axis=1)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"import glob, pandas as pd\nimport matplotlib.pyplot as plt\nimport pydicom, numpy as np\n\ndef parse_data(df):\n    \"\"\"\n    Method to read a CSV file (Pandas dataframe) and parse the \n    data into the following nested dictionary:\n\n      parsed = {\n        \n        'patientId-00': {\n            'dicom': path/to/dicom/file,\n            'label': either 0 or 1 for normal or pnuemonia, \n            'boxes': list of box(es)\n        },\n        'patientId-01': {\n            'dicom': path/to/dicom/file,\n            'label': either 0 or 1 for normal or pnuemonia, \n            'boxes': list of box(es)\n        }, ...\n\n      }\n\n    \"\"\"\n    # --- Define lambda to extract coords in list [y, x, height, width]\n    extract_box = lambda row: [row['y'], row['x'], row['height'], row['width']]\n\n    parsed = {}\n    for n, row in df.iterrows():\n        # --- Initialize patient entry into parsed \n        pid = row['patientId']\n        if pid not in parsed:\n            parsed[pid] = {\n                'dicom': pr_root+'/stage_2_train_images/%s.dcm' % pid,\n                'label': row['Target'],\n                'class': row['class'],\n                'boxes': []}\n\n        # --- Add box if opacity is present\n        if parsed[pid]['label'] == 1:\n            parsed[pid]['boxes'].append(extract_box(row))\n\n    return parsed\n\n\ndef draw(data):\n    \"\"\"\n    Method to draw single patient with bounding box(es) if present \n\n    \"\"\"\n    # --- Open DICOM file\n    d = pydicom.read_file(data['dicom'])\n    im = d.pixel_array\n\n    # --- Convert from single-channel grayscale to 3-channel RGB\n    im = np.stack([im] * 3, axis=2)\n\n    # --- Add boxes with random color if present\n    for box in data['boxes']:\n        rgb = np.floor(np.random.rand(3) * 256).astype('int')\n        im = overlay_box(im=im, box=box, rgb=rgb, stroke=6)\n\n    plt.imshow(im, cmap=plt.cm.gist_gray)\n    plt.axis('off')\n\ndef overlay_box(im, box, rgb, stroke=1):\n    \"\"\"\n    Method to overlay single box on image\n\n    \"\"\"\n    # --- Convert coordinates to integers\n    box = [int(b) for b in box]\n    \n    # --- Extract coordinates\n    y1, x1, height, width = box\n    y2 = y1 + height\n    x2 = x1 + width\n\n    im[y1:y1 + stroke, x1:x2] = rgb\n    im[y2:y2 + stroke, x1:x2] = rgb\n    im[y1:y2, x1:x1 + stroke] = rgb\n    im[y1:y2, x2:x2 + stroke] = rgb\n\n    return im","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"combined_df.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"\nparsed = parse_data(combined_df)\n\npatientId = combined_df['patientId'][4]\nprint('Just a checking that everything is working fine...')\nprint(parsed[patientId])\n\n\n","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"draw(parsed[patientId])","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"unique_values, count = np.unique(combined_df['class'], return_counts=True)\n\n# Visualize the distribution of class info\nplt.pie(count, labels = ['Lung Opacity', 'No Lung Opacity / Not Normal', 'Normal'], autopct='%1.1f%%', startangle=90)\nplt.tight_layout()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"import seaborn as sns\nsns.countplot(combined_df['class'],  hue=combined_df['Target'], palette='Greens')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Analyse patientIds and their corresponding bounding boxes. \nbounding_box_by_pat_grp = combined_df.groupby(['patientId']).size().to_frame('total_boxes').reset_index()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"bounding_box_by_pat_grp.groupby(['total_boxes']).size().to_frame('NumberOfPatients').reset_index()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Initial Observations:\n- There are 20672 records which doesn't have values for bounding box (x,y, width, height)\n- Patient Id, Target and class doesn't have any missing values\n- Each patient can have one than one record (duplicate patientIds) indicating that multiple lung opacity region\n  - There are 26684 unique patientIds.\n  - There are 3543 duplicate patientIds. \n  - 23286 patients i.e. 87% of patients have one bounding box, around 12% have 2 bounding boxes.\n  - 132 patients have more than 2 bounding boxes\n- There is significant class imbalance in the Target variable. We should use downsampling techniques before using the data into our model.\n- From the distribution of Target variable and class info.. \n  - Lung opacity class has target=1 Hence all pneumonia cases are with lung opacity and target=1. This class represents around 32% of origional data.\n  - Other two classes (Normal & No Lung Opacity / Not Normal) have target=0 indidating that they are normal patients without any pneumonia traces. This class represents around 68% of origional data.\n"},{"metadata":{},"cell_type":"markdown","source":"# U-Net Model Building "},{"metadata":{},"cell_type":"markdown","source":"# Overview\nHere we use the [montgomery dataset](https://www.kaggle.com/kmader/pulmonary-chest-xray-abnormalities#:~:text=Montgomery%20County%20X%2Dray%20Set&text=This%20set%20contains%20138%20posterior,including%20effusions%20and%20miliary%20patterns.) for Tuberculosis (not very healthy lungs) since it includes lung segmentations as a basis for learning how to segment lungs in the pneumonia dataset. We then generate masks for all of the images which can be used in future steps for detecting pneumonia better\n\n1. Organize the Training Data for Segmentation for Montegomery dataset\n1. Build Augmentation Pipeline and Generators for Montegomery dataset \n1. Build the U-Net Model\n1. Train the Model on Montegomery dataset\n1. Adapt model for full images\n1. Apply to RSNA Data \n"},{"metadata":{"trusted":true},"cell_type":"code","source":"import skimage","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"print (skimage.__version__)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"import numpy as np # linear algebra\nimport tensorflow as tf # for tensorflow based registration\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\nfrom skimage.util import montage\nimport os\nfrom cv2 import imread, createCLAHE # read and equalize images\nimport cv2\nfrom glob import glob\n%matplotlib inline\nimport matplotlib.pyplot as plt","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"xray_paths = glob(os.path.join('..', 'input', 'pulmonary-chest-xray-abnormalities',\n                              'Montgomery', 'MontgomerySet', '*', '*.png'))\nxray_images = [(c_path, \n               [os.path.join('/'.join(c_path.split('/')[:-2]),'ManualMask','leftMask', os.path.basename(c_path)),\n               os.path.join('/'.join(c_path.split('/')[:-2]),'ManualMask','rightMask', os.path.basename(c_path))]\n              ) for c_path in xray_paths]\nprint('xray Images', len(xray_paths))\nprint(xray_images[0])","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"!ls -l ../input/pulmonary-chest-xray-abnormalities/Montgomery/MontgomerySet/CXR_png/*","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"!ls -l /kaggle/input/pulmonary-chest-xray-abnormalities/Montgomery/MontgomerySet/ManualMask/leftMask |wc -l","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Read all the Images from Montegomery Set"},{"metadata":{"trusted":true},"cell_type":"code","source":"from skimage.io import imread as imread_raw\nfrom skimage.transform import resize\nimport warnings\nfrom tqdm import tqdm\nwarnings.filterwarnings('ignore', category=UserWarning, module='skimage') # skimage is really annoying\nOUT_DIM = (512, 512)\ndef imread(in_path, apply_clahe = False):\n    img_data = imread_raw(in_path)\n    n_img = (255*resize(img_data, OUT_DIM, mode = 'constant')).clip(0,255).astype(np.uint8)\n    if apply_clahe:\n        clahe_tool = createCLAHE(clipLimit=2.0, tileGridSize=(16,16))\n        n_img = clahe_tool.apply(n_img)\n    return np.expand_dims(n_img, -1)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#Create Numpy Array iof Imaages and Masks","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"img_vol, seg_vol = [], []\nfor img_path, s_paths in tqdm(xray_images):\n    img_vol += [imread(img_path)]    \n    seg_vol += [np.max(np.stack([imread(s_path, apply_clahe = False) for s_path in s_paths],0),0)]\nimg_vol = np.stack(img_vol,0)\nseg_vol = np.stack(seg_vol,0)\nprint('Images', img_vol.shape, 'Segmentations', seg_vol.shape)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"**Print One X-RAY and its Mask**"},{"metadata":{"trusted":true},"cell_type":"code","source":"np.random.seed(2018)\nt_img, m_img = img_vol[0], seg_vol[0]\n\nfig, (ax_img, ax_mask) = plt.subplots(1,2, figsize = (12, 6))\nax_img.imshow(np.clip(255*t_img, 0, 255).astype(np.uint8) if t_img.shape[2]==3 else t_img[:,:,0],\n              interpolation = 'none', cmap = 'bone')\nax_mask.imshow(m_img[:,:,0], cmap = 'bone')","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Make a Simple Model For the Montegomery Dataset\nHere we make a simple U-Net to create the lung segmentations"},{"metadata":{"trusted":true},"cell_type":"code","source":"from keras.layers import Conv2D, Activation, Input, UpSampling2D, concatenate, BatchNormalization\nfrom keras.layers import LeakyReLU\nfrom keras.initializers import RandomNormal\ndef c2(x_in, nf, strides=1):\n    x_out = Conv2D(nf, kernel_size=3, padding='same',\n                   kernel_initializer='he_normal', strides=strides)(x_in)\n    x_out = LeakyReLU(0.2)(x_out)\n    return x_out\ndef unet_enc(vol_size, enc_nf, pre_filter = 8):\n    src = Input(shape=vol_size + (1,), name = 'EncoderInput')\n    # down-sample path.\n    x_in = BatchNormalization(name = 'NormalizeInput')(src)\n    x_in = c2(x_in, pre_filter, 1)\n    x0 = c2(x_in, enc_nf[0], 2)  \n    x1 = c2(x0, enc_nf[1], 2)  \n    x2 = c2(x1, enc_nf[2], 2)  \n    x3 = c2(x2, enc_nf[3], 2) \n    return Model(inputs = [src], \n                outputs = [x_in, x0, x1, x2, x3],\n                name = 'UnetEncoder')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"from keras.models import Model\nfrom keras import layers\ndef unet(vol_size, enc_nf, dec_nf, full_size=True, edge_crop=48):\n    \"\"\"\n    unet network for voxelmorph \n    Args:\n        vol_size: volume size. e.g. (256, 256, 256)\n        enc_nf: encoder filters. right now it needs to be to 1x4.\n            e.g. [16,32,32,32]\n            TODO: make this flexible.\n        dec_nf: encoder filters. right now it's forced to be 1x7.\n            e.g. [32,32,32,32,8,8,3]\n            TODO: make this flexible.\n        full_size\n    \"\"\"\n\n    # inputs\n    raw_src = Input(shape=vol_size + (1,), name = 'ImageInput')\n    src = layers.GaussianNoise(0.25)(raw_src)\n    enc_model = unet_enc(vol_size, enc_nf)\n    # run the same encoder on the source and the target and concatenate the output at each level\n    x_in, x0, x1, x2, x3 = [s_enc for s_enc in enc_model(src)]\n\n    x = c2(x3, dec_nf[0])\n    x = UpSampling2D()(x)\n    x = concatenate([x, x2])\n    x = c2(x, dec_nf[1])\n    x = UpSampling2D()(x)\n    x = concatenate([x, x1])\n    x = c2(x, dec_nf[2])\n    x = UpSampling2D()(x)\n    x = concatenate([x, x0])\n    x = c2(x, dec_nf[3])\n    x = c2(x, dec_nf[4])\n    x = UpSampling2D()(x)\n    x = concatenate([x, x_in])\n    x = c2(x, dec_nf[5])\n\n    # transform the results into a flow.\n    y_seg = Conv2D(1, kernel_size=3, padding='same', name='lungs', activation='sigmoid')(x)\n    y_seg = layers.Cropping2D((edge_crop, edge_crop))(y_seg)\n    y_seg = layers.ZeroPadding2D((edge_crop, edge_crop))(y_seg)\n    # prepare model\n    model = Model(inputs=[raw_src], outputs=[y_seg])\n    return model","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# use the predefined depths\nnf_enc=[16,32,32,32]\nnf_dec=[32,32,32,32,32,16,16,2]\nnet = unet(OUT_DIM, nf_enc, nf_dec)\n# ensure the model roughly works\na= net.predict([np.zeros((1,)+OUT_DIM+(1,))])\nprint(a.shape)\nnet.summary()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"from keras.optimizers import Adam\nimport keras.backend as K\nfrom keras.optimizers import Adam\nfrom keras.losses import binary_crossentropy\n\nreg_param = 1.0\nlr = 2e-4\ndice_bce_param = 0.0\nuse_dice = True\n\ndef dice_coef(y_true, y_pred, smooth=1):\n    intersection = K.sum(y_true * y_pred, axis=[1,2,3])\n    union = K.sum(y_true, axis=[1,2,3]) + K.sum(y_pred, axis=[1,2,3])\n    return K.mean( (2. * intersection + smooth) / (union + smooth), axis=0)\ndef dice_p_bce(in_gt, in_pred):\n    return dice_bce_param*binary_crossentropy(in_gt, in_pred) - dice_coef(in_gt, in_pred)\ndef true_positive_rate(y_true, y_pred):\n    return K.sum(K.flatten(y_true)*K.flatten(K.round(y_pred)))/K.sum(y_true)\n\nnet.compile(optimizer=Adam(lr=lr), \n              loss=[dice_p_bce], \n           metrics = [true_positive_rate, 'binary_accuracy'])","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Create Training Data Generator\nHere we make a tool to generate training data from the X-ray scans"},{"metadata":{"trusted":true},"cell_type":"code","source":"img_arr = np.array(img_vol)\nseg_arr = np.array(seg_vol)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"from sklearn.model_selection import train_test_split\ntrain_vol, test_vol, train_seg, test_seg = train_test_split((img_arr-127.0)/127.0, \n                                                            (seg_arr>127).astype(np.float32), \n                                                            test_size = 0.2, \n                                                            random_state = 2018)\nprint('Train', train_vol.shape, 'Test', test_vol.shape, test_vol.mean(), test_vol.max())\nprint('Seg', train_seg.shape, train_seg.max(), np.unique(train_seg.ravel()))\nfig, (ax1, ax1hist, ax2, ax2hist) = plt.subplots(1, 4, figsize = (20, 4))\nax1.imshow(test_vol[0, :, :, 0])\nax1hist.hist(test_vol.ravel())\nax2.imshow(test_seg[0, :, :, 0]>0.5)\nax2hist.hist(train_seg.ravel());","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Adding Augmentation\nHere we use augmentation to get more data into the model"},{"metadata":{"trusted":true},"cell_type":"code","source":"from keras.preprocessing.image import ImageDataGenerator\ndg_args = dict(featurewise_center = False, \n                  samplewise_center = False,\n                  rotation_range = 5, \n                  width_shift_range = 0.05, \n                  height_shift_range = 0.05, \n                  shear_range = 0.01,\n                  zoom_range = [0.8, 1.2],  \n               # anatomically it doesnt make sense, but many images are flipped\n                  horizontal_flip = True,  \n                  vertical_flip = False,\n                  fill_mode = 'nearest',\n               data_format = 'channels_last')\n\nimage_gen = ImageDataGenerator(**dg_args)\n\ndef gen_augmented_pairs(in_vol, in_seg, batch_size = 16):\n    while True:\n        seed = np.random.choice(range(9999))\n        # keep the seeds syncronized otherwise the augmentation to the images is different from the masks\n        g_vol = image_gen.flow(in_vol, batch_size = batch_size, seed = seed)\n        g_seg = image_gen.flow(in_seg, batch_size = batch_size, seed = seed)\n        for i_vol, i_seg in zip(g_vol, g_seg):\n            yield i_vol, i_seg","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"train_gen = gen_augmented_pairs(train_vol, train_seg, batch_size = 16)\ntest_gen = gen_augmented_pairs(test_vol, test_seg, batch_size = 16)\ntrain_X, train_Y = next(train_gen)\ntest_X, test_Y = next(test_gen)\nprint(train_X.shape, train_Y.shape)\nprint(test_X.shape, test_Y.shape)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"test_X.mean()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### Training Data"},{"metadata":{"trusted":true},"cell_type":"code","source":"fig, (ax1, ax2) = plt.subplots(1, 2, figsize = (20, 10))\nax1.imshow(montage(train_X[:, :, :, 0]), cmap = 'bone')\nax1.set_title('CXR Image')\nax2.imshow(montage(train_Y[:, :, :, 0]), cmap = 'bone')\nax2.set_title('Seg Image')","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### Validation Data"},{"metadata":{"trusted":true},"cell_type":"code","source":"fig, (ax1, ax2) = plt.subplots(1, 2, figsize = (20, 10))\nax1.imshow(montage(test_X[:, :, :, 0]), cmap = 'bone')\nax1.set_title('CXR Image')\nax2.imshow(montage(test_Y[:, :, :, 0]), cmap = 'bone')\nax2.set_title('Seg Image')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"from keras.callbacks import ModelCheckpoint, LearningRateScheduler, EarlyStopping, ReduceLROnPlateau\nweight_path=\"{}_weights.best.hdf5\".format('cxr_reg')\n\ncheckpoint = ModelCheckpoint(weight_path, monitor='val_loss', verbose=1, \n                             save_best_only=True, mode='min', save_weights_only = True)\n\nreduceLROnPlat = ReduceLROnPlateau(monitor='val_loss', factor=0.5, \n                                   patience=3, \n                                   verbose=1, mode='min', epsilon=0.0001, cooldown=2, min_lr=1e-6)\nearly = EarlyStopping(monitor=\"val_loss\", \n                      mode=\"min\", \n                      patience=15) # probably needs to be more patient, but kaggle time is limited\ncallbacks_list = [checkpoint, early, reduceLROnPlat]","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"from IPython.display import clear_output\nloss_history = net.fit_generator(train_gen, \n                  steps_per_epoch=len(train_vol)//train_X.shape[0],\n                  epochs = 25,\n                  validation_data = (test_vol, test_seg),\n                  callbacks=callbacks_list\n                 )\n#clear_output()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Save the Model "},{"metadata":{"trusted":true},"cell_type":"code","source":"net.load_weights(weight_path)\nnet.save('full_model.h5')","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Accuracy of the Model"},{"metadata":{"trusted":true},"cell_type":"code","source":"import numpy as np\nfig, (ax1, ax2) = plt.subplots(1, 2, figsize = (10, 5))\nax1.plot(loss_history.history['loss'], '-', label = 'Loss')\nax1.plot(loss_history.history['val_loss'], '-', label = 'Validation Loss')\nax1.legend()\n\nax2.plot(100*np.array(loss_history.history['binary_accuracy']), '-', \n         label = 'Accuracy')\nax2.plot(100*np.array(loss_history.history['val_binary_accuracy']), '-',\n         label = 'Validation Accuracy')\nax2.legend()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Appying Trained Model to RSNA "},{"metadata":{},"cell_type":"markdown","source":"\nHere we load the RSNA data and apply the model to all of the images"},{"metadata":{"trusted":true},"cell_type":"code","source":"import pydicom\nfrom glob import glob\nbase_rsna_dir = os.path.join('..', 'input', 'rsna-pneumonia-detection-challenge')\ntest_mean, test_std = test_X.mean(), test_X.std()\nprint(\"{} {} \".format(test_mean, test_std))\ndef read_dicom_as_float(in_path):\n    out_mat = pydicom.read_file(in_path).pixel_array\n    norm_mat = (out_mat-1.0*np.mean(out_mat))/np.std(out_mat)\n    # make the RSNA distribution look like the training distribution\n    norm_mat = norm_mat*test_std+test_mean\n    return np.expand_dims(norm_mat, -1).astype(np.float32)\nall_rsna_df = pd.DataFrame({'path': glob(os.path.join(base_rsna_dir, \n                                                      'stage_*_images', '*.dcm'))})\nall_rsna_df.sample(3)\nn_shape = read_dicom_as_float(combined_df['dicom'].iloc[0]).shape\nn_shape","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"pneumonia_locations = {}\n# load table\nwith open(os.path.join('../input/rsna-pneumonia-detection-challenge/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":"class generator_single_channel(keras.utils.Sequence):\n    \n    def __init__(self, folder, filenames, pneumonia_locations=None, batch_size=100,\n                 image_size=256, shuffle=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.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.pneumonia_locations:\n            # loop through pneumonia\n            is_pneumonia = int(1)\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')\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":"from keras import layers\nin_shape = (1024,1024,1 )\nin_img = layers.Input(in_shape, name='DICOMInput')\nscale_factor = (2,2)\nds_dicom = layers.AvgPool2D(scale_factor)(in_img)\nunet_out = net(ds_dicom)\nus_out = layers.UpSampling2D(scale_factor)(unet_out)\nunet_big = Model(inputs=[in_img], outputs=[us_out])\nunet_big.save('big_model.h5')\nunet_big.summary()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"from skimage.segmentation import mark_boundaries\nfrom skimage.color import label2rgb\nfrom skimage.util import montage\ndef add_boundary(in_img, in_seg, cmap = 'bone', norm = True, add_labels = True):\n    if norm:\n        n_img = (1.0*in_img-in_img.min())/(1.1*(in_img.max()-in_img.min()))\n    else:\n        n_img = in_img\n    rgb_img = plt.cm.get_cmap(cmap)(n_img)[:, :, :3]\n    if add_labels:\n        return label2rgb(image = rgb_img, label = in_seg.astype(int), bg_label = 0)\n    else:\n        return mark_boundaries(image = rgb_img, label_img = in_seg.astype(int), color = (0, 1, 0), mode = 'thick')\ndef show_full_st(in_img, in_seg, gt_seg):\n    fig, (ax1, ax2, ax3) = plt.subplots(1, 3, figsize = (30, 10))\n    out_mtg = add_boundary(montage(in_img[:, :, :, 0]), \n                           montage(gt_seg[:, :, :, 0]>0.5))\n    ax1.imshow(out_mtg)\n    ax1.set_title('Ground Truth')\n    out_mtg = add_boundary(montage(in_img[:, :, :, 0]), \n                           montage(in_seg[:, :, :, 0]>0.5))\n    ax2.imshow(out_mtg)\n    ax2.set_title('Prediction')\n    out_mtg = montage(in_seg[:, :, :, 0]-gt_seg[:, :, :, 0])\n    ax3.imshow(out_mtg, cmap='RdBu', vmin=-1, vmax=1)\n    ax3.set_title('Difference')\ndef show_examples(n=1, with_roi = True):\n    roi_func = lambda x: x[:, \n                               OUT_DIM[0]//2-32:OUT_DIM[0]//2+32,\n                               OUT_DIM[1]//2-64:OUT_DIM[1]//2,\n                               :\n                              ]\n    for (test_X, test_Y), _ in zip(test_gen, range(n)):\n        seg_Y = net.predict(test_X)\n        show_full_st(test_X, seg_Y, test_Y)\n        show_full_st(roi_func(test_X), roi_func(seg_Y), roi_func(test_Y))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"opacity_sample_df = combined_df[combined_df.Target==1].sample(8)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"opacity_sample_df.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"parsed_opacity = parse_data(opacity_sample_df)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Print Some Chest X-Ray Lung Opacity Segmentation from RSNA"},{"metadata":{"trusted":true},"cell_type":"code","source":"import numpy as np\nfig, ax = plt.subplots(4, 3, figsize = (20, 15))\ncol=0\nrow=0\n\nfor index,c_row in opacity_sample_df.sample(8).iterrows():\n    if row==4:\n        break\n    col=0\n    c_img = read_dicom_as_float(c_row['dicom'])\n    c_seg = unet_big.predict(np.expand_dims(c_img, 0))[0]\n    ax[row,col].imshow(c_img[:, :, 0],cmap='bone')\n    col = col +1 \n    ax[row,col].imshow(add_boundary(c_img[:, :, 0], c_seg[:, :, 0]>0.5,add_labels = True ))\n    pid=c_row['patientId']\n    col =col+1\n    fig.add_subplot(ax[row,col]);\n    draw(parsed_opacity[pid])\n    #print('looped')\n    if col==2:\n        row=row+1\n    #print(row)    \n\n","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Make Prediction and Save them in ZIP file alongwith Mask (Taken from)  [K Scott's Notebook](https://www.kaggle.com/kmader/training-u-net-on-tb-images-to-segment-lungs)"},{"metadata":{"trusted":true},"cell_type":"code","source":"combined_df","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"\"\"\"\nimport zipfile as zf\nfrom io import BytesIO\nfrom PIL import Image\nbatch_size = 12\nwith zf.ZipFile('masks.zip', 'w') as f:\n    for i, c_rows in tqdm(all_rsna_df.groupby(lambda x: x//batch_size)):\n        cur_x = np.stack(c_rows['path'].map(read_dicom_as_float), 0)\n        cur_pred = unet_big.predict(cur_x)>0.5\n        for out_img, (_, c_row) in zip(cur_pred[:, :, :, 0], c_rows.iterrows()):\n            arc_name = os.path.relpath(c_row['path'], base_rsna_dir)\n            arc_name, _ = os.path.splitext(arc_name)\n            out_pil_obj = Image.fromarray((255*out_img).astype(np.uint8))\n            out_obj = BytesIO()\n            out_pil_obj.save(out_obj, format='png')\n            out_obj.seek(0)\n            f.writestr('{}.png'.format(arc_name), out_obj.read(), zf.ZIP_STORED)\n\n\"\"\"","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"parsed_opacity\n","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"import keras\nimport tensorflow as tf\nfrom keras.models import Model\nfrom skimage.transform import resize\nimport os\nimport random\nimport csv","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"from 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","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"checkpoint = 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 = '../input/rsna-pneumonia-detection-challenge/stage_2_train_images'\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":"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_unet = get_unet(input_img, n_filters=16, dropout=0.05, batchnorm=True)\nmodel_unet.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_unet.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":"train_gen_simple = generator_single_channel(folder, train_filenames, pneumonia_locations, batch_size=64, image_size=224, shuffle=False, predict=False)\nvalid_gen_simple = generator_single_channel(folder, valid_filenames, pneumonia_locations, batch_size=64, image_size=224, shuffle=False, predict=False)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"history = model_unet.fit_generator(train_gen_simple, validation_data=valid_gen_simple, callbacks=[checkpoint,es], epochs=5)\n#confusion matrix, f1 score\n#10% ","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"import matplotlib.pyplot as plt\n\nplt.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":"model_unet.load_weights('../input/capstone-rsna-pneumonia-detection-using-u-net/pnuemonia-detection-unet_0.6716.h5')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"valid_gen_pred = generator_single_channel(folder, valid_filenames, pneumonia_locations,\n                                            batch_size=64, image_size=224, shuffle=False, predict=True)","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}