{"cells":[{"metadata":{},"cell_type":"markdown","source":"# Step 1. See how the data is\n\n1. let's see how the data is formed\n2. and then make a function to see more easily"},{"metadata":{},"cell_type":"markdown","source":"import some modules"},{"metadata":{"trusted":true},"cell_type":"code","source":"%config Completer.use_jedi = False\nimport os\nimport numpy as np # linear algebra\nimport pandas as pd # data processing\nimport seaborn as sns\nimport matplotlib.pyplot as plt\n%matplotlib inline\nfrom PIL import Image, ImageOps\nfrom matplotlib.pyplot import imread\nimport tensorflow as tf","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"setting some constants"},{"metadata":{"trusted":true},"cell_type":"code","source":"D_ = r'/kaggle/input/hpa-single-cell-image-classification'\nD_TRAIN = os.path.join(D_, 'train')\nD_TEST = os.path.join(D_, 'test')\nD_TRAIN_CSV = os.path.join(D_, 'train.csv')\nD_SUBMI_CSV = os.path.join(D_, 'sample_submission.csv')","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"What does the train set look like?"},{"metadata":{"trusted":true},"cell_type":"code","source":"df_train = pd.read_csv(D_TRAIN_CSV)\ndf_train.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"print(f'Train Sample Numbers - {df_train.shape[0]}')","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"**Test Samples**"},{"metadata":{"trusted":true},"cell_type":"code","source":"df_test = pd.read_csv(D_SUBMI_CSV)\ndf_test.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"print(f'Test Sample Numbers - {df_test.shape[0]}')","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## **Labels**"},{"metadata":{"trusted":true},"cell_type":"code","source":"DICT_LABEL_NAME = {\n    0: 'Nucleoplasm',\n    1: 'Nuclear membrane',\n    2: 'Nucleoli',\n    3: 'Nucleoli fibrillar center',\n    4: 'Nuclear speckles',\n    5: 'Nuclear bodies',\n    6: 'Endoplasmic reticulum',\n    7: 'Golgi apparatus',\n    8: 'Intermediate filaments',\n    9: 'Actin filaments',\n    10: 'Microtubules',\n    11: 'Mitotic spindle',\n    12: 'Centrosome',\n    13: 'Plasma membrane',\n    14: 'Mitochondria',\n    15: 'Aggresome',\n    16: 'Cytosol',\n    17: 'Vesicles and punctate cytosolic patterns',\n    18: 'Negative'\n}\nDICT_NAME_LABEL = dict((v, k) for k, v in DICT_LABEL_NAME.items())\n\ndef fill_targets(row):\n    row['split_Label'] = np.array(row.Label.split('|')).astype(np.int)\n    for num in row.split_Label:\n        name = DICT_LABEL_NAME[int(num)]\n        row.loc[name] = 1\n    return row\n\nfill_targets(df_train.iloc[0])","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"**Reformat Train Targets**"},{"metadata":{"trusted":true},"cell_type":"code","source":"for name in DICT_NAME_LABEL.keys():\n    df_train[name] = 0 \ndf_train = df_train.apply(lambda row: fill_targets(row), axis = 1)\ndf_train.head()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"**Reformat Test Targets**"},{"metadata":{},"cell_type":"markdown","source":"## 1. Distribution of the data"},{"metadata":{"trusted":true},"cell_type":"code","source":"df_train_targets = df_train.drop(['ID', 'Label', 'split_Label'], axis=1)\ndf_train_value_counts = df_train_targets.sum().sort_values(ascending=False)\ndf_train_value_counts.head()\nplt.figure(figsize=(10, 10))\nsns.barplot(y=df_train_value_counts.index, x=df_train_value_counts.values)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## 2. Target numbers distribution"},{"metadata":{"trusted":true},"cell_type":"code","source":"df_numTarget_counts = df_train_targets.sum(axis='columns').value_counts()\nplt.figure(figsize=(10, 4))\nsns.barplot(x=df_numTarget_counts.index, y=df_numTarget_counts.values, palette='Reds')\nplt.xlabel('Number of targets per image')\nplt.ylabel('Number of images')","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## 3. Relations"},{"metadata":{"trusted":true},"cell_type":"code","source":"plt.figure(figsize = (15, 15))\nsns.heatmap(df_train_targets.corr(), cmap = 'YlGnBu', vmin =-1, vmax=1)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## 3. Looks of the image"},{"metadata":{},"cell_type":"markdown","source":"**check input quality**"},{"metadata":{"trusted":true},"cell_type":"code","source":"from os import listdir\nlist_file = listdir(D_TRAIN)\nlist_color = ['red', 'yellow', 'green', 'blue']\ndf_train = df_train.set_index('ID')\nfor color in list_color:\n    df_train[color] = 0 \nfor file in list_file:\n    ID = file.split('_')[0]\n    color = file.split('_')[1].split('.png')[0]\n    df_train.loc[ID, color] += 1","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"df_color = df_train[list_color]\ndf_color.value_counts()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"check test quality"},{"metadata":{"trusted":true},"cell_type":"code","source":"df_test = df_test.set_index('ID')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"list_file = listdir(D_TEST)\nlen(list_file)\nfor i in range(3):\n    print(list_file[i])","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"for color in list_color:\n    df_test[color] = 0 \nfor file in list_file:\n    ID = file.split('_')[0]\n    color = file.split('_')[1].split('.png')[0]\n    df_test.loc[ID, color] += 1\ndf_color = df_test[list_color]\ndf_color.value_counts()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"**Let's see the image**"},{"metadata":{"trusted":true},"cell_type":"code","source":"def load_image(image_id, basepath = D_TRAIN):\n    red_image = imread(os.path.join(basepath, image_id + '_red.png'))\n    blue_image = imread(os.path.join(basepath, image_id + '_blue.png'))\n    green_image = imread(os.path.join(basepath, image_id + '_green.png'))\n    #yellow_image = imread(os.path.join(basepath, image_id + '_yellow.png'))\n    #image = np.dstack((red_image, blue_image, green_image, yellow_image))\n    image = np.dstack((red_image, green_image, blue_image))\n    if image.max() > 255:\n        image = (image/255).astype('uint8')\n    return image\n\nsample_image = load_image(df_train.index[1])\nplt.imshow(sample_image)\nplt.axis('off')\nplt.show()\n","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"**separate to individual cells**"},{"metadata":{},"cell_type":"markdown","source":"I found HPACellSeg \n\n* https://www.kaggle.com/lnhtrang/hpa-public-data-download-and-hpacellseg\n* https://github.com/CellProfiling/HPA-Cell-Segmentation\n\nI'm tried to use this mask, but it was too slow. \n* https://www.kaggle.com/yushinjung/human-protein-atlas-single-cell-classification\n\nI coinceidentally found the masks already processed.\nhttps://www.kaggle.com/its7171/hpa-mask"},{"metadata":{},"cell_type":"markdown","source":"Let's check whether mask works."},{"metadata":{"trusted":true},"cell_type":"code","source":"D_CELL_MASK = '/kaggle/input/hpa-mask/hpa_cell_mask'\nD_NUCLEI_MASK = '/kaggle/input/hpa-mask/hpa_nuclei_mask'\n\nflag_demo_mask = False\nif flag_demo_mask:\n    ID_sample = '000a6c98-bb9b-11e8-b2b9-ac1f6b6435d0'\n    d_nucl = os.path.join(D_CELL_MASK, ID_sample)\n    d_cell = os.path.join(D_NUCLEI_MASK, ID_sample)\n\n    cell_mask = np.load(d_cell + '.npz')['arr_0']\n    nucl_mask = np.load(d_nucl + '.npz')['arr_0']\n\n    image_sample = load_image(ID_sample, D_TRAIN)\n    fig, ax = plt.subplots(1, 3, figsize=(30, 10))\n    ax[0].imshow(image_sample)\n    ax[1].imshow(cell_mask)\n    ax[2].imshow(nucl_mask)\n\n    for _ in range(3):\n        ax[_].axis('off')","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"**Important Thing**\n\nwe have to get unique image only to use for training"},{"metadata":{"trusted":true},"cell_type":"code","source":"flag_saveUsingIndex = True # to save using index list\n\nimport random \ndf_train_targets = df_train.drop(['Label', 'split_Label'] + list_color, axis = 1)\ndf_train_unique_targets = df_train_targets[df_train_targets.sum(axis = 'columns') == 1]\nn_max_image_per_label = 10000\nlist_using_index = []\n\nd_saveUsingIndex = os.path.join('/kaggle/working', 'save_using_Index')\nif not flag_saveUsingIndex:\n    list_using_index = np.load(d_saveUsingIndex + '.npz')['arr_0']\nelse:\n    for name in DICT_NAME_LABEL.keys():\n        num_unique_image = (df_train_unique_targets[name] == 1).sum()\n        num__ = len(list_using_index)\n        if num_unique_image < n_max_image_per_label:\n            list_using_index += list(df_train_unique_targets[df_train_unique_targets[name] == 1].index)\n        else:\n            # num_random = random.sample(range(0, num_unique_image), n_max_image_per_label)\n            # for ease let's make it simple \n            num_random = [i for i in range(100)]\n            list_using_index += list(df_train_unique_targets[df_train_unique_targets[name] == 1].iloc[num_random].index)\n        print(f'{name} - unique image number is {len(list_using_index) - num__}')\n        np.savez(d_saveUsingIndex, list_using_index)    # save for future work","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Images of cell by cell"},{"metadata":{"trusted":true},"cell_type":"code","source":"flag_seeMasks = False\nimport cv2\ndef get_single_cellImage(contours, original_image, cell_mask):\n    list_singleImage = []\n    for contour in contours:\n        x, y, width, height = cv2.boundingRect(contour)\n        instance_contour = np.zeros(cell_mask.shape)\n        cv2.drawContours(instance_contour,[contour], 0, 255, thickness=cv2.FILLED)\n\n        isolated_cell_image = np.zeros(cell_mask.shape)\n        isolated_cell_image = cv2.bitwise_and(image,image, mask = instance_contour.astype(\"uint8\"))\n        list_singleImage.append(isolated_cell_image[y:y+height,x:x+width,:3])\n    return list_singleImage","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"for every unique image, single cell image will be saved to a folder"},{"metadata":{"trusted":true},"cell_type":"code","source":"d_saveFolderSingleCell = os.path.join('/kaggle/working/', 'SingleCellTrain')\nif not os.path.isdir(d_saveFolderSingleCell):\n    os.mkdir(d_saveFolderSingleCell)\n\nlist_existingfile = os.listdir(d_saveFolderSingleCell)\nlabel = 0 \ntotal_index = len(list_using_index)\nfor num, ID in enumerate(list_using_index):\n    if num % 100 == 0 :\n        print('{:.2f} % is finished'.format(num/total_index * 100))\n    if list_existingfile.count(f'{ID}_0_0.npz') > 0:\n        continue\n    image = load_image(ID)\n    d_cell = os.path.join(D_NUCLEI_MASK, ID) \n    cell_mask = np.load(d_cell + '.npz')['arr_0']\n#     if flag_seeMasks:\n#         fig, ax = plt.subplots(1, 3, figsize = (24, 8))\n#         ax[0].imshow(image)\n#         ax[1].imshow(cell_mask)\n#         ax[2].imshow(nucl_mask)\n#         for _ in range(3):\n#             ax[_].axis('off')\n    contours, hierarchy = cv2.findContours(cell_mask.astype(np.uint8), mode=cv2.RETR_EXTERNAL, method=cv2.CHAIN_APPROX_SIMPLE)\n#     for contour in contours:    \n#         plt.figure(figsize = (5, 5))\n#         x, y, width, height = cv2.boundingRect(contour)\n#         instance_contour = np.zeros(cell_mask.shape)\n#         cv2.drawContours(instance_contour,[contour], 0, 255, thickness=cv2.FILLED)\n\n#         isolated_cell_image = np.zeros(cell_mask.shape)\n#         isolated_cell_image = cv2.bitwise_and(image,image, mask = instance_contour.astype(\"uint8\"))\n#         plt.axis('off')\n#         plt.title(f'{x}, {y}, {width}, {height}')\n#         plt.imshow(isolated_cell_image[y:y+height,x:x+width,:3])\n    list_delIndex = []\n    for i, contour in enumerate(contours):\n        if cv2.contourArea(contour) < 50: # check whether there is too little contour\n            list_delIndex.append(i)\n    if len(list_delIndex) != 0 :\n        list_delIndex.sort(reverse=True)\n        for i in list_delIndex:\n            del contours[i]\n    if len(contours) == 0 : # if there is no cell found go next\n        continue\n    list_singleCellImage = get_single_cellImage(contours, image, cell_mask)\n    for i, sc_image in enumerate(list_singleCellImage):\n        d_saveFile = os.path.join(d_saveFolderSingleCell, f'{ID}_{i}_{label}')\n        np.savez_compressed(d_saveFile, sc_image)\n#     plt.imshow(image)\n#     fig, ax = plt.subplots(1, len(list_singleCellImage), figsize=(len(list_singleCellImage*10), 10) )\n#     for i, sc_image in enumerate(list_singleCellImage):\n#         ax[i].imshow(sc_image)\n#         ax[i].axis('off')\n#         ax[i].set_title(f'{i}', fontdict={'fontsize': 50})\n    ","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"**Let's segment the images before making predictions**"},{"metadata":{"trusted":true},"cell_type":"code","source":"# functions to get path for required image\ndef get_imagepath_of_RYB(image_ID, basepath):\n    r_path = os.path.join(basepath, f'{image_ID}_red.png')\n    y_path = os.path.join(basepath, f'{image_ID}_yellow.png')\n    b_path = os.path.join(basepath, f'{image_ID}_blue.png')\n    return r_path, y_path, b_path","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"**encode binary mask**\n\nhttps://www.kaggle.com/thedrcat/hpa-baseline-cell-segmentation#kln-39"},{"metadata":{"trusted":true},"cell_type":"code","source":"!pip install -q \"/kaggle/input/pycocotools/pycocotools-2.0-cp37-cp37m-linux_x86_64.whl\"","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"import base64\nfrom pycocotools import _mask as coco_mask\n\nfrom pycocotools import _mask \nimport zlib\ndef encode_binary_mask(mask):\n    # check input mask --\n    if mask.dtype != np.bool:\n#         raise ValueError(\n#             \"encode_binary_mask expects a binary mask, received dtype == %s\" %\n#             mask.dtype)\n        mask = mask.astype(np.bool)\n    mask = np.squeeze(mask)\n    if len(mask.shape) != 2:\n        raise ValueError(\n            \"encode_binary_mask expects a 2d mask, received shape == %s\" %\n            mask.shape)\n    # convert input mask to expected COCO API input --\n    mask_to_encode = mask.reshape(mask.shape[0], mask.shape[1], 1)\n    mask_to_encode = mask_to_encode.astype(np.uint8)\n    mask_to_encode = np.asfortranarray(mask_to_encode)\n\n    # RLE encode mask --\n    encoded_mask = _mask.encode(mask_to_encode)[0][\"counts\"]\n\n    # compress and base64 encoding --\n    binary_str = zlib.compress(encoded_mask, zlib.Z_BEST_COMPRESSION)\n    base64_str = base64.b64encode(binary_str)\n    return base64_str.decode('ascii')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"from itertools import groupby\n\n# encode\ndef coco_rle_encode(mask):\n    rle = {'counts': [], 'size': list(mask.shape)}\n    counts = rle.get('counts')\n    for i, (value, elements) in enumerate(groupby(mask.ravel(order='F'))):\n        if i == 0 and value == 1:\n            counts.append(0)\n        counts.append(len(list(elements)))\n    return rle","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"**Before machine learning we have to get all the single cell image from test image**\n\nby using segmentator above, we will save test images single cell"},{"metadata":{},"cell_type":"markdown","source":"# Step 2. Building a baseline model"},{"metadata":{"trusted":true},"cell_type":"code","source":"from sklearn.model_selection import train_test_split\nseries_ID = df_train.index\nseries_trainID, series_validID = train_test_split(series_ID, test_size=0.25, random_state=0)\nseries_testID = df_test.index\n\nlabels = df_train[DICT_NAME_LABEL.keys()]\nprint(f'train ID, length-{len(series_trainID)}\\n{series_trainID[:2]}')\nprint(f'valid ID, length-{len(series_validID)}\\n{series_validID[:2]}')\nprint(f'labels, length-{labels.shape}\\n{labels.iloc[:2, :2]}')","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## **Shared Parameters**"},{"metadata":{"trusted":true},"cell_type":"code","source":"class ModelParameter:\n    def __init__(self, \n                 basepath=D_TRAIN, \n                 num_classes=len(DICT_NAME_LABEL.keys()),\n                 image_row_dim=32, \n                 image_col_dim=32, \n                 shuffle=False, \n                 batch_size=200, \n                 n_epochs=1, \n                 n_channels=4):\n        self.basepath = basepath\n        self.num_classes = num_classes\n        self.image_row_dim = image_row_dim\n        self.image_col_dim = image_col_dim\n        self.shuffle = shuffle\n        self.batch_size = batch_size\n        self.n_epochs = n_epochs\n        self.n_channels = n_channels\nparameter_base = ModelParameter(basepath=D_TRAIN)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## **Image Preprocessor**"},{"metadata":{"trusted":true},"cell_type":"code","source":"from skimage.transform import resize\n\nclass ImagePreprocessor:\n    def __init__(self, parameter):\n        self.parameter = parameter\n        self.basepath = parameter.basepath\n        self.image_row_dim = parameter.image_row_dim\n        self.image_col_dim = parameter.image_col_dim\n    \n    def resize(self,image):\n        image = resize(image, (self.image_row_dim, self.image_col_dim))\n        return image\n    \n    def load_image(self, image_id):\n        return load_image(image_id, basepath = self.basepath)\n    \n    def preprocess(self, image):\n        image = self.resize(image)\n        return image\n    \npreprocessor = ImagePreprocessor(parameter_base)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"if False:\n    for i, ID in enumerate(series_trainID):\n        print(i)\n        fig, ax = plt.subplots(1,2, figsize=(10, 5))\n        image = preprocessor.load_image(ID)\n        ax[0].imshow(image)    \n        print(image.shape)\n        image = preprocessor.preprocess(image)\n        ax[1].imshow(image)\n        print(image.shape)\n        if i == 3:\n            break   ","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"import keras\nclass DataGenerator(keras.utils.Sequence):\n    def __init__(self, list_IDs, labels, parameter, preprocessor):\n        self.current_epoch = 0 \n        self.params = parameter\n        self.labels = labels\n        self.list_IDs = list_IDs\n        self.preprocessor = preprocessor\n        \n        self.dim = (parameter.image_row_dim, parameter.image_col_dim)\n        self.batch_size = parameter.batch_size\n        self.num_classes = parameter.num_classes\n        self.shuffle = parameter.shuffle\n        self.n_channels = parameter.n_channels\n        \n        self.on_epoch_end()\n        \n    def on_epoch_end(self):\n        self.indexes = np.arange(len(self.list_IDs))\n        if self.shuffle == True:\n            np.random.shuffle(self.indexes, random_state=self.current_epoch)\n            self.current_epoch += 1\n        \n    def get_targets_per_image(self, identifier):\n        return self.labels.loc[identifier][DICT_NAME_LABEL.keys()]\n        \n    def __data_generation(self, list_IDs_temp):\n        # Initialize\n        data = np.empty((self.batch_size, *self.dim, self_n_channels))\n        label = np.empty((self.batch_size, self.num_classes), dtype = int)\n        # Generate Data\n        for i, identifier in enumerate(list_IDs_temp):\n            # store dataset\n            image = self.preprocessor.load_image(identifier)\n            image = self.preprocessor.preprocess(image)\n            data[i]\n            # store label\n            label[i] = self.get_targets_per_image(identifier)\n        return data, label\n    def __len__(self):\n        'Denotes the number of batches per epoch'\n        return int(np.floor(len(self.list_IDs) / self.batch_size))\n    def __getitem__(self, index):\n        'Generate one batch of data'\n        # Generate indexes of the batch\n        indexes = self.indexes[index * self.batch_size:(index+1) * self.batch_size]\n        # Find list of IDs\n        list_IDs_temp = [self.list_IDs[k] for k in indexes]\n        # Generate data\n        data, label = self.__data_generation(list_IDs_temp)\n        pass\n    \ntrainDataGenerator = DataGenerator(series_trainID, labels, parameter_base, preprocessor)\nvalidDataGenerator = DataGenerator(series_validID, labels, parameter_base, preprocessor)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"class PredictGenerator:\n    def __init__(self, predict_IDs, preprocessor, predict_path):\n        self.preprocessor = preprocessor\n        self.preprocessor.basepath = predict_path\n        self.identifiers = predict_IDs\n        \n    def predict(self, model):\n        y = np.empty(shape=(len(self.identifiers), self.preprocessor.parameter.num_classes))\n        for n in range(len(self.identifiers)):\n            image = self.preprocessor.load_image(self.identifiers[n])\n            image = self.preprocessor.preprocess(image)\n            y[n] = model.predict(image)      \n        return y\n\ntestDataGenerator = PredictGenerator(series_testID, preprocessor, D_TEST)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## CNN Baseline model using KERAS"},{"metadata":{"trusted":true},"cell_type":"code","source":"from keras.models import Sequential\nfrom keras.layers import Dense, Dropout, Flatten\nfrom keras.layers import Conv2D, MaxPooling2D\nfrom keras.losses import binary_crossentropy\nfrom keras.optimizers import Adadelta\nfrom keras.initializers import VarianceScaling\n\nclass BaseLineModel:\n    def __init__(self, parameter):\n        self.params = parameter\n        self.num_classes = parameter.num_classes\n        self.img_rows = parameter.image_row_dim\n        self.img_cols = parameter.image_col_dim\n        self.n_channels = parameter.n_channels\n        self.input_shape = (self.img_rows, self.img_cols, self.n_channels)\n        self.my_metrics = ['accuracy']\n        \n    def build_model(self):\n        self.model = Sequential()\n        \n        ","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}