{"cells":[{"metadata":{},"cell_type":"markdown","source":"In this kernel used github repos [efficientdet-pytorch](https://github.com/rwightman/efficientdet-pytorch) and [pytorch-image-models](https://github.com/rwightman/pytorch-image-models) by [@rwightman](https://www.kaggle.com/rwightman). Don't forget add stars ;)","execution_count":null},{"metadata":{"_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","trusted":true},"cell_type":"code","source":"!pip install --no-deps '../input/timm-package/timm-0.1.26-py3-none-any.whl' > /dev/null\n!pip install --no-deps '../input/pycocotools/pycocotools-2.0-cp37-cp37m-linux_x86_64.whl' > /dev/null","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"import sys\nsys.path.insert(0, \"../input/timm-efficientdet-pytorch\")\nsys.path.insert(0, \"../input/omegaconf\")\nsys.path.insert(0, \"../input/weightedboxesfusion\")\n\nfrom ensemble_boxes import *\nimport torch\nimport numpy as np\nimport pandas as pd\nfrom glob import glob\nfrom torch.utils.data import Dataset,DataLoader\nimport albumentations as A\nfrom albumentations.pytorch.transforms import ToTensorV2\nimport cv2\nimport gc\nfrom matplotlib import pyplot as plt\nfrom effdet import get_efficientdet_config, EfficientDet, DetBenchEval\nfrom effdet.efficientdet import HeadNet\nimport torchvision\n\nfrom torchvision.models.detection.faster_rcnn import FastRCNNPredictor\nfrom torchvision.models.detection import FasterRCNN\nfrom torchvision.models.detection.rpn import AnchorGenerator","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def get_valid_transforms():\n    return A.Compose([\n            A.Resize(height=512, width=512, p=1.0),\n            ToTensorV2(p=1.0),\n        ], p=1.0)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"DATA_ROOT_PATH = '../input/global-wheat-detection/test'\n\nclass DatasetRetriever(Dataset):\n\n    def __init__(self, image_ids, transforms=None):\n        super().__init__()\n        self.image_ids = image_ids\n        self.transforms = transforms\n\n    def __getitem__(self, index: int):\n        image_id = self.image_ids[index]\n        image = cv2.imread(f'{DATA_ROOT_PATH}/{image_id}.jpg', cv2.IMREAD_COLOR)\n        image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB).astype(np.float32)\n        image /= 255.0\n        if self.transforms:\n            sample = {'image': image}\n            sample = self.transforms(**sample)\n            image = sample['image']#/255.0\n        \n        return image, image_id\n\n    def __len__(self) -> int:\n        return self.image_ids.shape[0]","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"dataset = DatasetRetriever(\n    image_ids=np.array([path.split('/')[-1][:-4] for path in glob(f'{DATA_ROOT_PATH}/*.jpg')]),\n    transforms=get_valid_transforms()\n)\n\ndef collate_fn(batch):\n    return tuple(zip(*batch))\n\ndata_loader = DataLoader(\n    dataset,\n    batch_size=2,\n    shuffle=False,\n    num_workers=2,\n    drop_last=False,\n    collate_fn=collate_fn\n)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"from collections import OrderedDict\ndef load_net(checkpoint_path, version='d5'):\n    if version == 'd5':\n        config_name = 'tf_efficientdet_d5'\n    elif version == 'd7':\n        config_name = 'tf_efficientdet_d7'\n    else:\n        raise NotImplemented\n        \n    config = get_efficientdet_config(config_name)\n    net = EfficientDet(config, pretrained_backbone=False)\n\n    config.num_classes = 1\n    config.image_size=512\n    net.class_net = HeadNet(config, num_outputs=config.num_classes, norm_kwargs=dict(eps=.001, momentum=.01))\n\n    checkpoint = torch.load(checkpoint_path)['model_state_dict']\n# #     print(checkpoint.keys())\n#     checkpoint2 = OrderedDict()\n    \n#     for v in checkpoint:\n#         if 'anchor_labeler.anchors.boxes' in v:\n#             checkpoint2['anchors.boxes'] = checkpoint[v]\n#             continue\n#         checkpoint2[v[6:]] = checkpoint[v]\n# #     checkpoint['anchors.boxes'] = checkpoint.pop('anchor_labeler.anchors.boxes')\n#     net.load_state_dict(checkpoint2)\n# #     net.load_state_dict(checkpoint['model_state_dict'])\n\n#     del checkpoint\n#     gc.collect()\n\n    net = DetBenchEval(net, config)\n    checkpoint['anchors.boxes'] = checkpoint.pop('anchor_labeler.anchors.boxes')\n    net.load_state_dict(checkpoint)\n    net.eval();\n    return net.cuda()\n\n\n# \n# net = load_net('../input/wheat-effdet5-fold0-best-checkpoint/fold0-best-all-states.bin')\n# net = load_net('../input/efficient-det-fold4/efficient_det_fold4.pth')\n# net = load_net('../input/gwd-efficient-det-fold0-30e/efficient_det_fold0.pth')\n\nUSE_YXYX_TO_XYXY = False\n\nmodels = [\n#     load_net('../input/gwd-efficient-det7-fold0-30e/efficient_det7_fold0_30e.pth', 'd7'),\n#     load_net('../input/gwd-efficient-det-fold0-30e/efficient_det_fold0.pth'),\n#     load_net('../input/gwd-efficient-det-fold1-17e/efficient_det_fold1.pth'),\n#     load_net('../input/gwd-efficient-det-fold2-19e/EfficientDetModel_fold2_19e.pth'),\n#     load_net('../input/gwd-efficient-det-fold3-25e/efficient_det_fold3_25e.pth'),\n#     load_net('../input/gwd-efficient-det-fold4-30e/efficient_det_fold4.pth'),\n#     load_net('../input/gwdefficientnetfold013e/EfficientDetModel_fold0_13e.pth')\n#     load_net('../input/gwdeffnetfold210ev5/EfficientDetModel_fold2_010e.pth')\n#     load_net('../input/gwdeffnetfold041ev4/EfficientDetModel_fold0_41e.pth'),\n#     load_net('../input/gwdeffnetfold110ev5/EfficientDetModel_fold1_10e.pth'),\n#     load_net('../input/gwdeffnetfold210ev5/EfficientDetModel_fold2_010e.pth'),\n#     load_net('../input/gwdeffnetfold310ev5/EfficientDetModel_fold3_010e.pth'),\n#     load_net('../input/gwdeffnetd7fold024ev5/EfficientDetModel_fold0_35e.pth', 'd7'),\n#     load_net('../input/gwd-efficient-det-fold0-30e/efficient_det_fold0.pth'),\n#     load_net('../input/gwdeffnetfold041ev4/EfficientDetModel_fold0_41e.pth'),\n    \n#     load_net('../input/gwd-efficient-det-fold3-25e/efficient_det_fold3_25e.pth'),\n#     load_net('../input/gwdeffnet-fold422ev5/EfficientDetModel_fold4_034e.pth'),\n#     load_net('../input/gwdeffnet-fold422ev5/EfficientDetModel_fold4_034e.pth'),\n#     load_net('../input/gwdeffnetfold110ev5/EfficientDetModel_fold1_10e.pth')\n#     load_net('../input/gwdeffnetfold013ev3/EfficientDetModel_fold0_13e.pth')\n#     load_net('../input/gwdeffnetfold210ev5/EfficientDetModel_fold2_015e.pth')\n#     load_net('../input/gwd-efficient-det-fold0-30e/EfficientDetModel_fold0_038e.pth'),\n#     load_net('../input/gwdeffnetfold210ev5/EfficientDetModel_fold2_52e.pth')\n    \n    \n    load_net('../input/gwd-efficient-det-fold0-30e/EfficientDetModel_fold0_035e.pth'),\n    load_net('../input/gwdeffnetfold110ev5/EfficientDetModel_fold1_030e.pth'),\n    load_net('../input/gwdeffnetfold210ev5/EfficientDetModel_fold2_058e.pth'),\n    load_net('../input/gwd-efficient-det-fold3-25e/EfficientDetModel_fold3_037e.pth'),\n    load_net('../input/gwdeffnet-fold422ev5/EfficientDetModel_fold4_034e.pth'),\n    \n    \n#     load_net('../input/gwdeffnet1024fold08e/EfficientDetModel_fold0_019e.pth'),\n    \n    \n    \n]\n","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"class BaseWheatTTA:\n    \"\"\" author: @shonenkov \"\"\"\n    image_size = 512\n\n    def augment(self, image):\n        raise NotImplementedError\n    \n    def batch_augment(self, images):\n        raise NotImplementedError\n    \n    def deaugment_boxes(self, boxes):\n        raise NotImplementedError\n\nclass TTAHorizontalFlip(BaseWheatTTA):\n    \"\"\" author: @shonenkov \"\"\"\n\n    def augment(self, image):\n        return image.flip(1)\n    \n    def batch_augment(self, images):\n        return images.flip(2)\n    \n    def deaugment_boxes(self, boxes):\n        boxes[:, [1,3]] = self.image_size - boxes[:, [3,1]]\n        return boxes\n\nclass TTAVerticalFlip(BaseWheatTTA):\n    \"\"\" author: @shonenkov \"\"\"\n    \n    def augment(self, image):\n        return image.flip(2)\n    \n    def batch_augment(self, images):\n        return images.flip(3)\n    \n    def deaugment_boxes(self, boxes):\n        boxes[:, [0,2]] = self.image_size - boxes[:, [2,0]]\n        return boxes\n    \nclass TTARotate90(BaseWheatTTA):\n    \"\"\" author: @shonenkov \"\"\"\n    \n    def augment(self, image):\n        return torch.rot90(image, 1, (1, 2))\n\n    def batch_augment(self, images):\n        return torch.rot90(images, 1, (2, 3))\n    \n    def deaugment_boxes(self, boxes):\n        res_boxes = boxes.copy()\n        res_boxes[:, [0,2]] = self.image_size - boxes[:, [1,3]]\n        res_boxes[:, [1,3]] = boxes[:, [2,0]]\n        return res_boxes\n\nclass TTACompose(BaseWheatTTA):\n    \"\"\" author: @shonenkov \"\"\"\n    def __init__(self, transforms):\n        self.transforms = transforms\n        \n    def augment(self, image):\n        for transform in self.transforms:\n            image = transform.augment(image)\n        return image\n    \n    def batch_augment(self, images):\n        for transform in self.transforms:\n            images = transform.batch_augment(images)\n        return images\n    \n    def prepare_boxes(self, boxes):\n        result_boxes = boxes.copy()\n        result_boxes[:,0] = np.min(boxes[:, [0,2]], axis=1)\n        result_boxes[:,2] = np.max(boxes[:, [0,2]], axis=1)\n        result_boxes[:,1] = np.min(boxes[:, [1,3]], axis=1)\n        result_boxes[:,3] = np.max(boxes[:, [1,3]], axis=1)\n        return result_boxes\n    \n    def deaugment_boxes(self, boxes):\n        for transform in self.transforms[::-1]:\n            boxes = transform.deaugment_boxes(boxes)\n        return self.prepare_boxes(boxes)\n    \nclass TTAChannelShuffle(BaseWheatTTA):\n    def augment(self, image):\n        return image[[2,1,0]]\n\n    def batch_augment(self, images):\n        return images[:,[2,1,0]]\n    \n    def deaugment_boxes(self, boxes):\n        return boxes\n    \nclass TTAGrayScale(BaseWheatTTA):\n    def augment(self, image):\n        pil_image = torchvision.transforms.ToPILImage()(image)\n        new_image = torchvision.transforms.functional.to_grayscale(pil_image, 3)\n        return  torchvision.transforms.ToTensor()(new_image)\n\n    def batch_augment(self, images):\n        new_images = []\n        for image in images:\n            pil_image = torchvision.transforms.ToPILImage()(image.cpu())\n            new_image = torchvision.transforms.functional.to_grayscale(pil_image, 3)\n            new_images.append(torchvision.transforms.ToTensor()(new_image).cuda())\n        return torch.stack(new_images)\n    \n    def deaugment_boxes(self, boxes):\n        return boxes\n    \nclass TTACrop(BaseWheatTTA):\n    def __init__(self, x_min=None, x_max=None, y_min=None, y_max=None):\n        self.x_min = x_min\n        self.x_max = x_max\n        self.y_min = y_min\n        self.y_max = y_max\n        \n    def augment(self, image):\n        crop_image = image[:, self.y_min:self.y_max, self.x_min:self.x_max] \n        new_image = torch.nn.functional.interpolate(crop_image.unsqueeze(0), size=(512,512), mode=\"nearest\")[0]\n        return new_image\n\n    def batch_augment(self, images):\n        crop_images = images[:, :, self.y_min:self.y_max, self.x_min:self.x_max] \n        new_images = torch.nn.functional.interpolate(crop_images, size=(512,512), mode=\"nearest\")\n        return new_images\n    \n    def deaugment_boxes(self, boxes):\n        boxes = (boxes/2).astype(np.int32).clip(min=0, max=511)\n        \n        if self.y_min == 0:\n            boxes[:, 0] +=  self.x_min\n            boxes[:, 2] +=  self.x_min\n        elif self.x_min == 0:\n            boxes[:, 1] +=  self.y_min\n            boxes[:, 3] +=  self.y_min\n        else:\n            boxes[:, 0] +=  self.y_min\n            boxes[:, 1] +=  self.y_min\n            boxes[:, 2] +=  self.y_min\n            boxes[:, 3] +=  self.y_min\n            \n        return boxes","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# you can try own combinations:\ntransform = TTACompose([\n#     TTARotate90(),\n#     TTAVerticalFlip(),\n#     TTAChannelShuffle()\n    TTACrop(0,256,0,256),\n#     TTACrop(256,512,0,256),\n#     TTACrop(0,256,256,512),\n#     TTACrop(256,512,256,512),\n])\n\nfig, ax = plt.subplots(1, 3, figsize=(16, 6))\n\nimage, image_id = dataset[5]\n\nnumpy_image = image.permute(1,2,0).cpu().numpy().copy()\n\nax[0].imshow(numpy_image);\nax[0].set_title('original')\n\ntta_image = transform.augment(image)\ntta_image_numpy = tta_image.permute(1,2,0).cpu().numpy().copy()\n\nnet = models[0]\ndet = net(tta_image.unsqueeze(0).float().cuda(), torch.tensor([1]).float().cuda())\n# boxes, scores = det[0,:,:4].int().detach().cpu().numpy(), det[0,:,4].detach().cpu().numpy() #process_det(0, det)\nboxes = det[0].int().detach().cpu().numpy()[:,:4]    \nscores = det[0].detach().cpu().numpy()[:,4]\nindexes = np.where(scores > 0.35)[0]\nboxes = boxes[indexes]\n\nboxes[:, 2] = boxes[:, 2] + boxes[:, 0]\nboxes[:, 3] = boxes[:, 3] + boxes[:, 1]\nif USE_YXYX_TO_XYXY:\n    boxes[:,[0,1,2,3]] = boxes[:,[1,0,3,2]]\n    \nfor box in boxes:\n    cv2.rectangle(tta_image_numpy, (box[0], box[1]), (box[2],  box[3]), (0, 1, 0), 2)\n\nax[1].imshow(tta_image_numpy);\nax[1].set_title('tta')\n    \nboxes = transform.deaugment_boxes(boxes)\n\nfor box in boxes:\n    cv2.rectangle(numpy_image, (box[0], box[1]), (box[2],  box[3]), (0, 1, 0), 2)\n    \nax[2].imshow(numpy_image);\nax[2].set_title('deaugment predictions');","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"from itertools import product\n\ntta_transforms = []\n\ntransform_list = product([TTAHorizontalFlip(), None], \n                            [TTAVerticalFlip(), None],\n                            [TTARotate90(), None],\n                            [TTAChannelShuffle(), None])\nfor tta_combination in transform_list:\n    tta_transforms.append(TTACompose([tta_transform for tta_transform in tta_combination if tta_transform]))\n    ","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def make_predictions(images, score_threshold=0.35):\n    images = torch.stack(images).float().cuda()\n    predictions = []\n    \n    for net in models:\n        with torch.no_grad():\n            det = net(images, torch.tensor([1]*images.shape[0]).float().cuda())\n            for i in range(images.shape[0]):\n                boxes = det[i].detach().cpu().numpy()[:,:4]    \n                scores = det[i].detach().cpu().numpy()[:,4]\n                indexes = np.where(scores > score_threshold)[0]\n                boxes = boxes[indexes]\n                boxes[:, 2] = boxes[:, 2] + boxes[:, 0]\n                boxes[:, 3] = boxes[:, 3] + boxes[:, 1]\n                if USE_YXYX_TO_XYXY:\n                    boxes[:,[0,1,2,3]] = boxes[:,[1,0,3,2]]\n                predictions.append({\n                    'boxes': boxes,\n                    'scores': scores[indexes],\n                })\n    return [predictions]\n\ndef make_tta_predictions(images, score_threshold=0.35):\n    images = torch.stack(images).float().cuda()\n    predictions = []\n    for net in models:\n        with torch.no_grad():                      \n            for tta_transform in tta_transforms:\n                result = []\n                det = net(tta_transform.batch_augment(images.clone()), torch.tensor([1]*images.shape[0]).float().cuda())\n\n                for i in range(images.shape[0]):\n                    boxes = det[i].detach().cpu().numpy()[:,:4]    \n                    scores = det[i].detach().cpu().numpy()[:,4]\n                    indexes = np.where(scores > score_threshold)[0]\n                    boxes = boxes[indexes]\n                    boxes[:, 2] = boxes[:, 2] + boxes[:, 0]\n                    boxes[:, 3] = boxes[:, 3] + boxes[:, 1]\n                    if USE_YXYX_TO_XYXY:\n                      boxes[:,[0,1,2,3]] = boxes[:,[1,0,3,2]]\n                    \n                    boxes = tta_transform.deaugment_boxes(boxes.copy())\n                    result.append({\n                        'boxes': boxes,\n                        'scores': scores[indexes],\n                    })\n                predictions.append(result)\n    return predictions\n\n\n# fold0 fold3\n# -------------WBF--------------\n# [Best Iou Thr]: 0.473\n# [Best Skip Box Thr]: 0.408\n# [Best Score]: 0.7386\n# ------------------------------\n\n#  fold 0 3 4 \n# -------------WBF--------------\n# [Best Iou Thr]: 0.472\n# [Best Skip Box Thr]: 0.448\n# [Best Score]: 0.7369\n# ------------------------------\n\n# v89\n# -------------WBF--------------\n# [Best Iou Thr]: 0.479\n# [Best Skip Box Thr]: 0.430\n# [Best Score]: 0.7347\n# ------------------------------\ndef run_wbf(predictions, image_index, image_size=512, iou_thr=0.479, skip_box_thr=0.430, weights=None):\n    boxes = [(prediction[image_index]['boxes']/(image_size-1)).tolist()  for prediction in predictions]\n    scores = [prediction[image_index]['scores'].tolist()  for prediction in predictions]\n    labels = [np.ones(prediction[image_index]['scores'].shape[0]).tolist() for prediction in predictions]\n    boxes, scores, labels = weighted_boxes_fusion(boxes, scores, labels, weights=None, iou_thr=iou_thr, skip_box_thr=skip_box_thr)\n    boxes = boxes*(image_size-1)\n    return boxes, scores, labels","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"import matplotlib.pyplot as plt\n\nfor j, (images, image_ids) in enumerate(data_loader):\n    break\n\npredictions = make_tta_predictions(images)\n\ni = 0\nsample = images[i].permute(1,2,0).cpu().numpy()\n\nboxes, scores, labels = run_wbf(predictions, image_index=i)\n\nif images[i].size()[1] == 512:\n    boxes = (boxes*2).astype(np.int32).clip(min=0, max=1023)\nelse:\n    boxes = boxes.astype(np.int32)\n\n# boxes = det[i].detach().cpu().numpy()[:,:4]    \n# scores = det[i].detach().cpu().numpy()[:,4]\n# indexes = np.where(scores > 0.35)[0]\n# boxes = boxes[indexes]\n\nfig, ax = plt.subplots(1, 1, figsize=(16, 8))\n\nfor box in boxes:\n    cv2.rectangle(sample, (box[0], box[1]), (box[2], box[3]), (1, 0, 0), 1)\n    \nax.set_axis_off()\nax.imshow(sample);","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def format_prediction_string(boxes, scores):\n    pred_strings = []\n    for j in zip(scores, boxes):\n        pred_strings.append(\"{0:.4f} {1} {2} {3} {4}\".format(j[0], j[1][0], j[1][1], j[1][2], j[1][3]))\n    return \" \".join(pred_strings)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"results = []\nboxes_ = {}\nscores_ = {}\n\nfor images, image_ids in data_loader:\n    predictions = make_tta_predictions(images)\n    for i, image in enumerate(images):\n        boxes, scores, labels = run_wbf(predictions, image_index=i)\n        \n        if image.size()[1] == 512:\n            boxes = (boxes*2).astype(np.int32).clip(min=0, max=1023)\n        else:\n            boxes = boxes.astype(np.int32)\n            \n        image_id = image_ids[i]\n        \n        boxes_[image_id] = boxes.copy()\n        scores_[image_id] = scores.copy()\n        \n#         indexes = np.where(scores > 0.35)[0]\n#         boxes = boxes[indexes]\n        boxes[:, 2] = boxes[:, 2] - boxes[:, 0]\n        boxes[:, 3] = boxes[:, 3] - boxes[:, 1]\n        \n        boxes = boxes[scores >= 0.05].astype(np.int32)\n        scores = scores[scores >=float(0.05)]\n\n        result = {\n            'image_id': image_id,\n            'PredictionString': format_prediction_string(boxes, scores)\n        }\n        results.append(result)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"test_df = pd.DataFrame(results, columns=['image_id', 'PredictionString'])\ntest_df.to_csv('submission.csv', index=False)\ntest_df.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# images, targets, ids = next(iter(data_loader))\n\nitr = 0\nitr2=0\nimg_count = len(images)\nfig, ax = plt.subplots(5, 2, figsize=(30, 70))\n\nfor j, (images, image_ids) in enumerate(data_loader):\n   \n    for i,image in enumerate(images):  \n        boxes = boxes_[image_ids[i]].copy() #targets[i]['boxes']\n#         print(boxes)\n        if image.size()[1] == 512:\n            boxes = (boxes/2).astype(np.int32)\n        else:\n            boxes = boxes.astype(np.int32)\n        sample = image.permute(1,2,0).cpu().numpy()\n\n        for box,score in zip(boxes, scores_[image_ids[i]]):\n            cv2.rectangle(sample,\n                      (int(box[0]), int(box[1])),\n                      (int(box[2]), int(box[3])),\n                      (220, 0, 0), 2)\n            cv2.putText(sample, '%.2f'%(score), (box[2], box[3]), cv2.FONT_HERSHEY_SIMPLEX , 0.5, (255,255,255), 2, cv2.LINE_AA)\n        ax[j][i].set_title(f\"{image_ids[i]}\")  \n        ax[j][i].imshow(sample)#.astype(np.float32))\n        itr+=1\n    if itr == 9:    \n        break\n        \nplt.show()","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}