{"cells":[{"metadata":{},"cell_type":"markdown","source":"# **Background**\n\nThis competition has been a learning curve for me. Starting of with an implementation of FasterRCNN Resnet50 FPN model:\n\nhttps://www.kaggle.com/shebinscaria/pytorch-resnet50fpn-gwd\n","execution_count":null},{"metadata":{},"cell_type":"markdown","source":"# **What's new in this notebook ?**\nThis notebook is to experiment with different augmentations extended by albumentations package.\nSo what is new in the notebook which you have already not seen -\n1. forumulate a framework for interactive implementation of augmentations\n2. implementation of cut-out augmentation* with num_holes as one of the parameters\n3. implementation of mosaic augmentation\n\nThe interactive framework is still experimental with some delays and constraints because I am still familiarizing myself with the ipython widget\n\n*this was inspired by one of the discussion threads: https://www.kaggle.com/c/global-wheat-detection/discussion/151800\nand more specifically by a comment from Alex Shoenkov","execution_count":null},{"metadata":{},"cell_type":"markdown","source":"**Some samples/screenshots are placed below to show it looks like when you run the code**","execution_count":null},{"metadata":{},"cell_type":"markdown","source":"=> Hue-Saturation-Value","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"import matplotlib.pyplot as plt\nimport cv2\nscreenshot_dir=\"/kaggle/input/gwd-screenshots/\"\nimg=cv2.imread(screenshot_dir+\"image1.PNG\")\nimg = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\nfig,ax=plt.subplots(1,1,figsize=(16,8))\nax.set_axis_off()\nax.imshow(img)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"=> Guassian Blur","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"img=cv2.imread(screenshot_dir+\"image2.PNG\")\nimg = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\nfig,ax=plt.subplots(1,1,figsize=(16,8))\nax.set_axis_off()\nax.imshow(img)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"=>Cutout (Albumentation v/s Cutout)","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"img=cv2.imread(screenshot_dir+\"image3_1.PNG\")\nimg = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\nfig,ax=plt.subplots(1,1,figsize=(16,8))\nax.set_axis_off()\nax.imshow(img)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"img=cv2.imread(screenshot_dir+\"image3_2.PNG\")\nimg = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\nfig,ax=plt.subplots(1,1,figsize=(16,8))\nax.set_axis_off()\nax.imshow(img)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"=>Custom Cutmix/Mosaic","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"img=cv2.imread(screenshot_dir+\"image4.PNG\")\nimg = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\nfig,ax=plt.subplots(1,1,figsize=(16,8))\nax.set_axis_off()\nax.imshow(img)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Post this, I hope to improve my score by incorporating it my above notebook. Thus, any feedback on this especially on the cut-out augmentations would be much appreciated","execution_count":null},{"metadata":{},"cell_type":"markdown","source":"# **References**\n\nBelow helped in a great manner:\n\n1. https://www.kaggle.com/nvnnghia/awesome-augmentation\n2. https://www.kaggle.com/c/global-wheat-detection/discussion/151800\n3. http://www.andrewjanowczyk.com/employing-the-albumentation-library-in-pytorch-workflows-bonus-helper-for-selecting-appropriate-values/\n4. https://www.kaggle.com/kaushal2896/gwd-data-augmentation-tutorial-5-fold-split\n5. https://www.kaggle.com/reighns/augmentations-data-cleaning-and-bounding-boxes","execution_count":null},{"metadata":{"_uuid":"5cbeb61a-cc1f-4b48-8e8f-5711a3abd06f","_cell_guid":"d61ecc08-ed93-4f18-bcd9-5c4d91e29459","trusted":true},"cell_type":"markdown","source":"## Importing Packages","execution_count":null},{"metadata":{"_uuid":"e7134710-7dbd-4b1f-8fcd-29cfddafeb9a","_cell_guid":"928c232e-6769-4162-89b4-67d5db43f0c3","trusted":true},"cell_type":"code","source":"import numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\nimport os\nimport matplotlib.pyplot as plt\nimport PIL\nfrom PIL import Image\n\nimport cv2\n\nimport torch\nimport torchvision\nfrom torchvision import transforms\n\nimport torch.utils.data\nfrom torch.utils.data import Dataset, DataLoader\nimport random\n\nimport albumentations\nfrom albumentations.pytorch.transforms import ToTensorV2","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"import ipywidgets as widgets\nfrom ipywidgets import interact, interactive\nfrom IPython.display import display\nimport plotly.graph_objects as go","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"0e3fb9ba-32ac-4842-a795-07819fc80325","_cell_guid":"31621c88-b652-443b-b686-18ae911c7dd0","trusted":true},"cell_type":"code","source":"os.listdir(\"/kaggle/input/global-wheat-detection\")\n\ntrain_dir = \"/kaggle/input/global-wheat-detection/train\"\ntest_dir = \"/kaggle/input/global-wheat-detection/test\"\n\ndf_train=pd.read_csv(\"/kaggle/input/global-wheat-detection/train.csv\")","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"2fd93bf5-fa60-4031-ae8b-cad7c9198b6b","_cell_guid":"904c7cd7-4014-4067-9644-7fe07dcaf519","trusted":true},"cell_type":"markdown","source":"We know the structure df_train from the given data description:\n1. image_id - the unique image ID\n2. width, height - the width and height of the images\n3. bbox - a bounding box, formatted as a Python-style list of [xmin, ymin, width, height]\n\nbbox is something of high interest to us. We need to structure bbox in a way that can be used further","execution_count":null},{"metadata":{"_uuid":"d807a667-bbcb-41c5-a0ad-4e510ed02ac0","_cell_guid":"82be4a7e-cf65-4dfe-8dea-4a9987232487","trusted":true},"cell_type":"code","source":"df_train['x0'] = df_train['bbox'].map(lambda x: x[1:-1].split(\",\")[0]).astype(float)\ndf_train['y0'] = df_train['bbox'].map(lambda x: x[1:-1].split(\",\")[1]).astype(float)\ndf_train['w'] = df_train['bbox'].map(lambda x: x[1:-1].split(\",\")[2]).astype(float)\ndf_train['h'] = df_train['bbox'].map(lambda x: x[1:-1].split(\",\")[3]).astype(float)\ndf_train['x1'] = df_train['x0'] + df_train['w']\ndf_train['y1'] = df_train['y0'] + df_train['h']","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"484821d6-daeb-4e4b-aa17-d5d4d0b133db","_cell_guid":"05e5ccba-682b-473e-87f4-84f13e993c8e","trusted":true},"cell_type":"code","source":"list_image_ids = list(df_train['image_id'].unique())\ndict_bbox = {}\ndict_labels = {}\nfor img_id in list_image_ids:\n    dict_bbox[img_id] = df_train.loc[df_train['image_id']==img_id,['x0','y0','x1','y1']].astype(np.int32).values\n    dict_labels[img_id] = np.ones((len(dict_bbox[img_id]),1),dtype=np.int32)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Incorporating some helper functions","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"def show_image(disp_image):\n    fig,ax = plt.subplots(1,1,figsize=(16,8))\n    ax.set_axis_off()\n    ax.imshow(disp_image)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def load_image(image_id,img_dir=train_dir):\n    ret_img = cv2.imread(img_dir+\"//\"+image_id+\".jpg\")\n    ret_img = cv2.cvtColor(ret_img, cv2.COLOR_BGR2RGB)\n    return ret_img","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def load_image_bbox(disp_image,bboxes):\n    ret_image = disp_image.copy()\n    for box in bboxes:\n        x0,y0,x1,y1 = box[0],box[1],box[2],box[3]\n        cv2.rectangle(ret_image,(x0,y0),(x1,y1),(255,0,0),3)\n    return ret_image","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Disclaimer: There are delays sometimes when rendering the output image. This could be due to the execution load triggered by albumentations library, as it is primarily used for a batch of data. I am working on reducing these delays. If you have any ideas on the same. Please let me know.\n\nThis are the reasons why I have configured below parameters in a specific manner:\n1. \"continous_update\" set to False.\n2. introduced \"interact_manual\"","execution_count":null},{"metadata":{},"cell_type":"markdown","source":"**1. HSV Augmentation**","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"imageid_dropdown = widgets.Dropdown(description=\"ImageID\",value=list_image_ids[0],options=list_image_ids)\nx_widget = widgets.IntSlider(min=-180, max=180, step=1, value=0, continuous_update=False, description='Hue')\ny_widget = widgets.IntSlider(min=0, max=100, step=1, value=0, continuous_update=False, description='Saturation')\nz_widget = widgets.IntSlider(min=0, max=100, step=1, value=0, continuous_update=False, description='Value')\n\ndef interactive_hsv(img_id, x,y,z):\n    sel_image_id = img_id\n    sel_image = load_image(sel_image_id)\n    pascal_voc_bbox = dict_bbox[sel_image_id]\n    labels = dict_labels[sel_image_id]\n    alb_transformations = [albumentations.HueSaturationValue(always_apply=True, hue_shift_limit=x, sat_shift_limit=y, val_shift_limit=z)]\n    alb_bbox_params = albumentations.BboxParams(format='pascal_voc',label_fields=['labels'])\n    aug = albumentations.Compose(alb_transformations, bbox_params = alb_bbox_params)\n    augmented_image = aug(image = sel_image, bboxes=pascal_voc_bbox, labels=labels)\n    #show_image(augmented_image['image'])\n    show_image(load_image_bbox(augmented_image['image'],augmented_image['bboxes']))\n\n#widgets.interact_manual(interactive_hsv,img_id=imageid_dropdown, x=x_widget, y=y_widget, z=z_widget)\nwidgets.interact_manual(interactive_hsv,img_id=imageid_dropdown, x=x_widget, y=y_widget, z=z_widget)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"**2. Gaussian Blur**","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"imageid_dropdown = widgets.Dropdown(description=\"ImageID\",value=list_image_ids[0],options=list_image_ids)\ngblurlimit_widget = widgets.IntSlider(min=3, max=101, step=2, value=3, continuous_update=False, description='GBlurSize')\n\ndef interactive_gaussianblur(img_id, gblurlimit):\n    sel_image_id = img_id\n    sel_image = load_image(sel_image_id)\n    pascal_voc_bbox = dict_bbox[sel_image_id]\n    labels = dict_labels[sel_image_id]\n    alb_transformations = [albumentations.GaussianBlur(blur_limit=gblurlimit,always_apply=False,p=1.0)]\n    alb_bbox_params = albumentations.BboxParams(format='pascal_voc',label_fields=['labels'])\n    aug = albumentations.Compose(alb_transformations, bbox_params = alb_bbox_params)\n    augmented_image = aug(image = sel_image, bboxes=pascal_voc_bbox, labels=labels)\n    #show_image(augmented_image['image'])\n    show_image(load_image_bbox(augmented_image['image'],augmented_image['bboxes']))\n\nwidgets.interact_manual(interactive_gaussianblur,img_id=imageid_dropdown, gblurlimit=gblurlimit_widget)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"**3. Cutout**\n\nHere lets compare the output of a.) Cutout from albumentations b.) Custom Cutout","execution_count":null},{"metadata":{},"cell_type":"markdown","source":"a.)Cutout from albumentations","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"imageid_dropdown = widgets.Dropdown(description=\"ImageID\",value=list_image_ids[0],options=list_image_ids)\nnumholes_widget = widgets.IntSlider(min=1, max=16, step=1, value=8, continuous_update=False, description='num_holes')\nmaxhsize_widget = widgets.IntSlider(min=10, max=1000, step=10, value=8, continuous_update=False, description='max_h_size')\nmaxwsize_widget = widgets.IntSlider(min=10, max=1000, step=10, value=8, continuous_update=False, description='max_h_size')\n\ndef interactive_cutout(img_id, numholes, maxhsize, maxwsize):\n    sel_image_id = img_id\n    sel_image = load_image(sel_image_id)\n    pascal_voc_bbox = dict_bbox[sel_image_id]\n    labels = dict_labels[sel_image_id]\n    alb_transformations = [albumentations.Cutout(num_holes=numholes,max_h_size=maxhsize, max_w_size=maxwsize, fill_value=0, always_apply=False,p=1.0)]\n    alb_bbox_params = albumentations.BboxParams(format='pascal_voc',label_fields=['labels'])\n    aug = albumentations.Compose(alb_transformations, bbox_params = alb_bbox_params)\n    augmented_image = aug(image = sel_image, bboxes=pascal_voc_bbox, labels=labels)\n    #show_image(augmented_image['image'])\n    show_image(load_image_bbox(augmented_image['image'],augmented_image['bboxes']))\n\nwidgets.interact_manual(interactive_cutout,img_id=imageid_dropdown,numholes=numholes_widget, maxhsize=maxhsize_widget, maxwsize=maxwsize_widget)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Notice how the bouding boxes returned are overalapping the blacked out portions of the image","execution_count":null},{"metadata":{},"cell_type":"markdown","source":"b.) Custom cutout","execution_count":null},{"metadata":{},"cell_type":"markdown","source":"Lets start with a helper function","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"def generate_custom_cutout(new_image,boxes,n_holes=20,max_h_size=20,max_w_size=20,height=1024,width=1024):\n    \n    box_coord = []\n    #box_coord=list(boxes)\n    for box in boxes:\n        x0,y0,x1,y1 = box[0],box[1],box[2],box[3]\n        box_coord.append([x0,y0,x1,y1])\n\n    \n    temp_image = new_image.copy()\n    final_list_coord = box_coord.copy()\n    i=0\n    for hole_ in range(n_holes):\n        x = random.randint(0, 1024)\n        y = random.randint(0, 1024)\n        # Generating diagonally opposite co-ordinates\n        y1 = np.clip(y-max_h_size // 2, 0, height)\n        x1 = np.clip(x-max_w_size // 2, 0, width)\n        y2 = np.clip(y1 + max_h_size, 0, height)\n        x2 = np.clip(x1 + max_w_size, 0, width)\n        mask = np.ones((1024,1024,3),np.int32)\n        mask[y1:y2,x1:x2,:]=0\n        invert_mask = 1- mask\n        temp_image = temp_image * mask\n        j=0\n        for box in box_coord:\n            j=j+1\n\n            x0,y0,x1,y1 = box[0], box[1], box[2], box[3]\n            # finding intersection\n            img1 = invert_mask\n\n            img2 = np.zeros((1024,1024,3))\n            img2[y0:y1,x0:x1,:]=1\n\n\n            intersection = np.logical_and(img1, img2)\n            instersection_area = np.sum(intersection)\n\n            if instersection_area>0:\n                if box in final_list_coord:\n                    final_list_coord.remove(box)\n\n    ret_image={}\n    ret_image['image']=temp_image\n    ret_image['bboxes']=final_list_coord\n    \n    return ret_image","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"imageid_dropdown = widgets.Dropdown(description=\"ImageID\",value=list_image_ids[0],options=list_image_ids)\nnumholes_widget = widgets.IntSlider(min=1, max=16, step=1, value=8, continuous_update=False, description='num_holes')\nmaxhsize_widget = widgets.IntSlider(min=10, max=1000, step=10, value=8, continuous_update=False, description='max_h_size')\nmaxwsize_widget = widgets.IntSlider(min=10, max=1000, step=10, value=8, continuous_update=False, description='max_h_size')\n\ndef interactive_custom_cutout(img_id, numholes, maxhsize, maxwsize):\n    sel_image_id = img_id\n    sel_image = load_image(sel_image_id)\n    pascal_voc_bbox = dict_bbox[sel_image_id]\n    labels = dict_labels[sel_image_id]\n    \n    augmented_image = generate_custom_cutout(sel_image,pascal_voc_bbox,n_holes=numholes,max_h_size=maxhsize,max_w_size=maxwsize,height=1024,width=1024)\n    show_image(load_image_bbox(augmented_image['image'],augmented_image['bboxes']))\n\nwidgets.interact_manual(interactive_custom_cutout,img_id=imageid_dropdown,numholes=numholes_widget, maxhsize=maxhsize_widget, maxwsize=maxwsize_widget)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"**4. CutoutMix/Mosaic**\n\nThis is not available in the albumentation library. Lets look at a custom implementation of the same","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"class mosaic:\n    def __init__(self, image_dir,dict_boundingbox):\n        self.dir = image_dir\n        self.dict_bb = dict_boundingbox\n    \n    def load_input_image(self,image_id):\n        ret_img = cv2.imread(self.dir+\"//\"+image_id+\".jpg\")\n        ret_img = cv2.cvtColor(ret_img, cv2.COLOR_BGR2RGB)\n        return ret_img\n    \n    \n    def select_bboxes(self,img_x0,img_y0,img_x1,img_y1,h,w,list_bbox):\n        bboxes =[]\n        for box in list_bbox:\n            bboxes.append([box[0],box[1],box[2],box[3]])\n        \n        mask_disp = np.zeros((h,w))\n        mask_img = np.zeros((h,w))\n        mask_disp[img_y0:img_y1,img_x0:img_x1]=1\n        mask_img[img_y0:img_y1,img_x0:img_x1]=1\n        \n        res_bbox = [] \n        for box in bboxes:\n            x0,y0,x1,y1 = int(box[0]),int(box[1]),int(box[2]),int(box[3])\n            area_box = (y1-y0)*(x1-x0)\n\n            mask_box = np.zeros((h,w))\n            mask_box[y0:y1,x0:x1]=1\n\n            intersection = np.logical_and(mask_img, mask_box)\n            intersection_area = np.sum(intersection)\n            if(int(intersection_area) == int(area_box)):\n                res_bbox.append(box)\n        return res_bbox\n    \n    def generate_mosaic(self,img_id1,img_id2,img_id3,img_id4):\n        # for mosaic\n        padding = 100\n        blank_img = np.zeros((1024,1024,3))\n        w_set,h_set = 1024,1024\n        \n        img_1,bbox_1 = self.load_input_image(img_id1),self.dict_bb[img_id1]\n        \n        img_2,bbox_2 = self.load_input_image(img_id2),self.dict_bb[img_id2]\n        \n        img_3,bbox_3 = self.load_input_image(img_id3),self.dict_bb[img_id3]\n        \n        img_4,bbox_4 = self.load_input_image(img_id4),self.dict_bb[img_id4]\n\n        #Generating center coordinates\n        xc = random.randint(0+padding, 1024-padding)\n        yc = random.randint(0+padding, 1024-padding)\n        \n\n        # first block (top-left)\n        blank_img[0:yc,0:xc,:] = img_1[0:yc,0:xc,:]\n        bbox_sel_1 = self.select_bboxes(0,0,xc,yc,h_set,w_set,bbox_1)\n\n        # second block (bottom-left)\n        blank_img[yc:h_set,0:xc,:] = img_2[yc:h_set,0:xc,:]\n        bbox_sel_2 = self.select_bboxes(0,yc,xc,h_set,h_set,w_set,bbox_2)\n\n        # third corner (bottom-right)\n        blank_img[yc:h_set,xc:w_set,:] = img_3[yc:h_set,xc:w_set,:]\n        bbox_sel_3 = self.select_bboxes(xc,yc,w_set,h_set,h_set,w_set,bbox_3)\n\n        # fourth corner (top-right)\n        blank_img[0:yc,xc:w_set,:] = img_4[0:yc,xc:w_set,:]\n        bbox_sel_4 = self.select_bboxes(xc,0,w_set,yc,h_set,w_set,bbox_4)\n        ret_image = {}\n        ret_image['bboxes']=bbox_sel_1+bbox_sel_2+bbox_sel_3+bbox_sel_4\n        for box in ret_image['bboxes']:\n            cv2.rectangle(blank_img,(box[0],box[1]),(box[2],box[3]),(255,0,0),3)\n        \n        ret_image['image']=blank_img.astype(np.int32)\n        \n        return ret_image","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Please Note:Initially select a set of images or click on all 4 drop downs and select images and click on \"Run Interact\". If you simply click on \"Run Interact\" it will not show the mosaic","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"imageid1_dropdown = widgets.Dropdown(description=\"ImageID1\",value=list_image_ids[0],options=list_image_ids)\nimageid2_dropdown = widgets.Dropdown(description=\"ImageID2\",value=list_image_ids[0],options=list_image_ids)\nimageid3_dropdown = widgets.Dropdown(description=\"ImageID3\",value=list_image_ids[0],options=list_image_ids)\nimageid4_dropdown = widgets.Dropdown(description=\"ImageID4\",value=list_image_ids[0],options=list_image_ids)\n\n\ndef interactive_custom_cutout(img_id1, img_id2, img_id3, img_id4):\n    \n    directory_of_images = \"/kaggle/input/global-wheat-detection/train\"\n    aug_obj = mosaic(directory_of_images,dict_bbox)\n    augmented_image = aug_obj.generate_mosaic(img_id1,img_id2,img_id3,img_id4)\n    #augmented_image = generate_custom_cutout(sel_image,pascal_voc_bbox,n_holes=numholes,max_h_size=maxhsize,max_w_size=maxwsize,height=1024,width=1024)\n    show_image(load_image_bbox(augmented_image['image'],augmented_image['bboxes']))\n\nwidgets.interact_manual(interactive_custom_cutout,img_id1=imageid1_dropdown, img_id2=imageid2_dropdown, img_id3=imageid3_dropdown, img_id4=imageid4_dropdown)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# **Conclusion**\n\nHope this gives an idea of how augmentations can be made in an interactive form and how tuning can be made more intuitive by this. I really hope this helps someone in one way or another.\n\nI have not gone to the extend of incorporating all possible augmentations present in the albumentations library, as it may seem repititive. \n\nAgain, just repeating what I mentioned earler, this interactive framework is still experimental with some delays and constraints because I am still familiarizing myself with the ipython widget. \n\nAlso I am planning to incorporate these in my code to tune my current model below:\nhttps://www.kaggle.com/shebinscaria/pytorch-resnet50fpn-gwd\n\n\nThus, any feedback on this, especially on the cut-out augmentations would be much appreciated","execution_count":null}],"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}