{"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_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Superchanrging YOLOv7 with SAM\n\nCan we supercharge our YOLOv7 masks by using Meta's SAM to increase the quality of the segmentation masks? \nThis notebook uses initial masks created by YOLOv7 as prompts to prompt SAM into delivering masks. \n\nThis is the inference notebook for this YOLOv7 [training notebook](https://www.kaggle.com/code/fnands/a-quick-yolov7-baseline).  \n\nThe inference code in the main repo [doesn't seem to actually export segmentation masks](https://github.com/WongKinYiu/yolov7/issues/1483), so I modified the inference loop.   \n\n### TODO:\n* Filter out glomerulus","metadata":{}},{"cell_type":"code","source":"!cp -r /kaggle/input/pycocotools/ /kaggle/working/pycocotools\n!pip install /kaggle/working/pycocotools/pycocotools-2.0.6  --no-index --find-links=/kaggle/working/pycocotools/ ","metadata":{"_kg_hide-input":true,"_kg_hide-output":true,"execution":{"iopub.status.busy":"2023-06-22T06:46:18.396738Z","iopub.execute_input":"2023-06-22T06:46:18.397325Z","iopub.status.idle":"2023-06-22T06:46:48.490033Z","shell.execute_reply.started":"2023-06-22T06:46:18.397291Z","shell.execute_reply":"2023-06-22T06:46:48.488349Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import base64\nimport numpy as np\nfrom pycocotools import _mask as coco_mask\nfrom typing import Text\nimport zlib\nimport json","metadata":{"_kg_hide-input":true,"_kg_hide-output":true,"execution":{"iopub.status.busy":"2023-06-22T06:46:48.492511Z","iopub.execute_input":"2023-06-22T06:46:48.492879Z","iopub.status.idle":"2023-06-22T06:46:48.504716Z","shell.execute_reply.started":"2023-06-22T06:46:48.492846Z","shell.execute_reply":"2023-06-22T06:46:48.503775Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"from torchmetrics.detection.mean_ap import MeanAveragePrecision","metadata":{"_kg_hide-input":true,"_kg_hide-output":true,"execution":{"iopub.status.busy":"2023-06-22T06:46:48.505838Z","iopub.execute_input":"2023-06-22T06:46:48.506805Z","iopub.status.idle":"2023-06-22T06:47:04.079961Z","shell.execute_reply.started":"2023-06-22T06:46:48.506759Z","shell.execute_reply":"2023-06-22T06:47:04.078911Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"!pip install /kaggle/input/yolov7-weights-and-wheels/yolo_wheel/yolov7-0.0.1-py37.py38.py39-none-any.whl --no-index --find-links=/kaggle/input/yolov7-weights-and-wheels/yolo_wheel","metadata":{"_kg_hide-input":true,"_kg_hide-output":true,"execution":{"iopub.status.busy":"2023-06-22T06:47:04.081388Z","iopub.execute_input":"2023-06-22T06:47:04.081785Z","iopub.status.idle":"2023-06-22T06:47:38.950063Z","shell.execute_reply.started":"2023-06-22T06:47:04.081755Z","shell.execute_reply":"2023-06-22T06:47:38.948804Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"!cp -r /kaggle/input/yolov7-weights-and-wheels/yolov7 yolo","metadata":{"_kg_hide-input":true,"_kg_hide-output":true,"execution":{"iopub.status.busy":"2023-06-22T06:47:38.952939Z","iopub.execute_input":"2023-06-22T06:47:38.953262Z","iopub.status.idle":"2023-06-22T06:47:40.668573Z","shell.execute_reply.started":"2023-06-22T06:47:38.953233Z","shell.execute_reply":"2023-06-22T06:47:40.666889Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"from yolo.seg.segment import predict","metadata":{"_kg_hide-input":true,"_kg_hide-output":true,"execution":{"iopub.status.busy":"2023-06-22T06:47:40.670280Z","iopub.execute_input":"2023-06-22T06:47:40.670957Z","iopub.status.idle":"2023-06-22T06:47:41.221104Z","shell.execute_reply.started":"2023-06-22T06:47:40.670870Z","shell.execute_reply":"2023-06-22T06:47:41.219652Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Create a yaml file as expected by YOLOv7 (and others)\nyaml_text = \"\"\"\n# class names\nnames: \n  0: blood_vessel\n  1: glomerulus\n  2: unsure\n\"\"\"\nwith open('/kaggle/working/hubmap-coco.yaml', 'w') as text_file:\n    text_file.write(yaml_text)","metadata":{"execution":{"iopub.status.busy":"2023-06-22T06:47:41.222969Z","iopub.execute_input":"2023-06-22T06:47:41.223310Z","iopub.status.idle":"2023-06-22T06:47:41.228580Z","shell.execute_reply.started":"2023-06-22T06:47:41.223284Z","shell.execute_reply":"2023-06-22T06:47:41.227881Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import argparse\nimport os\nimport platform\nimport sys\nfrom pathlib import Path\n\nimport torch\nimport torch.backends.cudnn as cudnn\nfrom torchvision.transforms import Resize\nimport cv2\nfrom typing import Dict, List\n\nfrom models.common import DetectMultiBackend\nfrom utils.dataloaders import IMG_FORMATS, VID_FORMATS, LoadImages, LoadStreams, LoadImagesAndLabels\nfrom utils.general import (LOGGER, Profile, check_file, check_img_size, check_imshow, check_requirements, colorstr, cv2,\n                           increment_path, non_max_suppression, print_args, scale_coords, strip_optimizer, xyxy2xywh)\nfrom utils.plots import Annotator, colors, save_one_box\nfrom utils.segment.general import process_mask, scale_masks\nfrom utils.segment.plots import plot_masks\nfrom utils.torch_utils import select_device, smart_inference_mode","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2023-06-22T06:47:41.230138Z","iopub.execute_input":"2023-06-22T06:47:41.230390Z","iopub.status.idle":"2023-06-22T06:47:41.246395Z","shell.execute_reply.started":"2023-06-22T06:47:41.230369Z","shell.execute_reply":"2023-06-22T06:47:41.245041Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"from segment_anything import SamPredictor, sam_model_registry","metadata":{"execution":{"iopub.status.busy":"2023-06-22T06:47:41.248284Z","iopub.execute_input":"2023-06-22T06:47:41.248636Z","iopub.status.idle":"2023-06-22T06:47:41.275839Z","shell.execute_reply.started":"2023-06-22T06:47:41.248608Z","shell.execute_reply":"2023-06-22T06:47:41.274920Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"new_weights = torch.load('/kaggle/input/home-sam-weights/epoch72-step24601.ckpt', map_location=torch.device('cpu'))['state_dict']","metadata":{"execution":{"iopub.status.busy":"2023-06-22T06:47:41.276955Z","iopub.execute_input":"2023-06-22T06:47:41.277228Z","iopub.status.idle":"2023-06-22T06:47:44.100738Z","shell.execute_reply.started":"2023-06-22T06:47:41.277205Z","shell.execute_reply":"2023-06-22T06:47:44.099770Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"renamed_weights = {}\nfor key in new_weights.keys():\n    renamed_weights[key[10:]] = new_weights[key]","metadata":{"execution":{"iopub.status.busy":"2023-06-22T06:47:44.102513Z","iopub.execute_input":"2023-06-22T06:47:44.102905Z","iopub.status.idle":"2023-06-22T06:47:44.108643Z","shell.execute_reply.started":"2023-06-22T06:47:44.102871Z","shell.execute_reply":"2023-06-22T06:47:44.107279Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"torch.save(renamed_weights, 'renamed_weights.pth')","metadata":{"execution":{"iopub.status.busy":"2023-06-22T06:47:44.110365Z","iopub.execute_input":"2023-06-22T06:47:44.110683Z","iopub.status.idle":"2023-06-22T06:47:45.341615Z","shell.execute_reply.started":"2023-06-22T06:47:44.110658Z","shell.execute_reply":"2023-06-22T06:47:45.340151Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#sam = sam_model_registry[\"vit_b\"](checkpoint=\"/kaggle/input/segment-anything/pytorch/vit-b/1/model.pth\")#\"renamed_weights.pth\")\nsam = sam_model_registry[\"vit_b\"](checkpoint=\"renamed_weights.pth\")\n\ndevice = \"cpu\"\nsam.to(device=device)\npredictor = SamPredictor(sam)","metadata":{"execution":{"iopub.status.busy":"2023-06-22T06:47:45.342905Z","iopub.execute_input":"2023-06-22T06:47:45.343240Z","iopub.status.idle":"2023-06-22T06:47:46.552319Z","shell.execute_reply.started":"2023-06-22T06:47:45.343214Z","shell.execute_reply":"2023-06-22T06:47:46.551177Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def encode_binary_mask(mask: np.ndarray) -> Text:\n    \"\"\"Converts a binary mask into OID challenge encoding ascii text.\"\"\"\n\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\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\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 = coco_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","metadata":{"execution":{"iopub.status.busy":"2023-06-22T06:47:46.555320Z","iopub.execute_input":"2023-06-22T06:47:46.555641Z","iopub.status.idle":"2023-06-22T06:47:46.563398Z","shell.execute_reply.started":"2023-06-22T06:47:46.555609Z","shell.execute_reply":"2023-06-22T06:47:46.562180Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import matplotlib.pyplot as plt\n%matplotlib inline","metadata":{"execution":{"iopub.status.busy":"2023-06-22T06:47:46.564587Z","iopub.execute_input":"2023-06-22T06:47:46.565064Z","iopub.status.idle":"2023-06-22T06:47:46.577627Z","shell.execute_reply.started":"2023-06-22T06:47:46.565035Z","shell.execute_reply":"2023-06-22T06:47:46.576373Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"ANNOTATIONS_FILE = '/kaggle/input/de-duplicated-annotations-for-hubmap-hhv/cleaned_polygons.jsonl'\n\ndef jsonl_to_dict(annotations_jsonl: Path) -> Dict[str, List[Dict]]:\n    # Read .jsonl file and convert it to a list of dicts\n    # The dicts contain IDs, class names and segmentation masks\n    # from https://www.kaggle.com/code/leonidkulyk/eda-hubmap-hhv-interactive-annotations\n    with open(annotations_jsonl, 'r') as json_file:\n        json_list = list(json_file)\n\n    tiles_dicts = []\n    for json_str in json_list:\n        tiles_dicts.append(json.loads(json_str))\n\n    dict_of_tiles = {}\n    for tile in tiles_dicts:\n        dict_of_tiles[tile['id']] = tile['annotations']\n        \n    return dict_of_tiles","metadata":{"execution":{"iopub.status.busy":"2023-06-22T06:47:46.578923Z","iopub.execute_input":"2023-06-22T06:47:46.579588Z","iopub.status.idle":"2023-06-22T06:47:46.590645Z","shell.execute_reply.started":"2023-06-22T06:47:46.579551Z","shell.execute_reply":"2023-06-22T06:47:46.589320Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def zip_box(coordinates: List[List[List[int]]]):\n    x_coordinates, y_coordinates = zip(*coordinates[0])\n    x_max = max(x_coordinates)\n    x_min = min(x_coordinates)\n    y_max = max(y_coordinates)\n    y_min = min(y_coordinates)\n        \n    return np.array([x_min, y_min, x_max, y_max])","metadata":{"execution":{"iopub.status.busy":"2023-06-22T06:47:46.592296Z","iopub.execute_input":"2023-06-22T06:47:46.592648Z","iopub.status.idle":"2023-06-22T06:47:46.608354Z","shell.execute_reply.started":"2023-06-22T06:47:46.592620Z","shell.execute_reply":"2023-06-22T06:47:46.606900Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"label_dict = jsonl_to_dict(ANNOTATIONS_FILE)","metadata":{"execution":{"iopub.status.busy":"2023-06-22T06:47:46.610273Z","iopub.execute_input":"2023-06-22T06:47:46.611389Z","iopub.status.idle":"2023-06-22T06:47:50.789397Z","shell.execute_reply.started":"2023-06-22T06:47:46.611349Z","shell.execute_reply":"2023-06-22T06:47:50.788021Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def segment(\n        weights,\n        source,\n        data,\n        project,\n        validate = False,\n        imgsz=(640, 640),  # inference size (height, width)\n        conf_thres=0.25,  # confidence threshold\n        iou_thres=0.45,  # NMS IOU threshold\n        max_det=1000,  # maximum detections per image\n        device='',  # cuda device, i.e. 0 or 0,1,2,3 or cpu\n        view_img=False,  # show results\n        save_txt=False,  # save results to *.txt\n        save_conf=False,  # save confidences in --save-txt labels\n        save_crop=False,  # save cropped prediction boxes\n        nosave=False,  # do not save images/videos\n        classes=None,  # filter by class: --class 0, or --class 0 2 3\n        agnostic_nms=False,  # class-agnostic NMS\n        augment=False,  # augmented inference\n        visualize=False,  # visualize features\n        update=False,  # update all models\n        name='exp',  # save results to project/name\n        exist_ok=False,  # existing project/name ok, do not increment\n        line_thickness=3,  # bounding box thickness (pixels)\n        hide_labels=False,  # hide labels\n        hide_conf=False,  # hide confidences\n        half=False,  # use FP16 half-precision inference\n        dnn=False,  # use OpenCV DNN for ONNX inference\n    ):\n    with open('/kaggle/working/submission.csv', 'w') as sub_file:\n        # Write header\n        sub_file.write('id,height,width,prediction_string\\n')\n        \n        #if validate: \n        #    label_dict = jsonl_to_dict(ANNOTATIONS_FILE)\n        \n        mask_resizer = Resize((256, 256))\n        \n        # Directories\n        save_dir = increment_path(Path(project) / name, exist_ok=exist_ok)  # increment run\n        (save_dir / 'labels' if save_txt else save_dir).mkdir(parents=True, exist_ok=True)  # make dir\n\n        device = select_device(device)\n        model = DetectMultiBackend(weights, device=device, dnn=dnn, data=data, fp16=half)\n        stride, names, pt = model.stride, model.names, model.pt\n        imgsz = check_img_size(imgsz, s=stride)  # check image size\n\n        dataset = LoadImages(source, img_size=imgsz, stride=stride, auto=pt)\n        bs = 1  # batch_size\n\n        model.warmup(imgsz=(1 if pt else bs, 3, *imgsz))  # warmup\n        seen, windows, dt = 0, [], (Profile(), Profile(), Profile())\n        \n        preds = []\n        target = []\n        \n        for path, im, im0s, vid_cap, s in dataset: \n            # Write id and size\n            predictor.set_image(np.transpose(im, (1, 2, 0)))\n            \n            image_id = Path(path).stem\n            sub_file.write(f'{image_id},512,512,')\n            with dt[0]:\n                im = torch.from_numpy(im).to(device)\n                im = im.half() if model.fp16 else im.float()  # uint8 to fp16/32\n                im /= 255  # 0 - 255 to 0.0 - 1.0\n                if len(im.shape) == 3:\n                    im = im[None]  # expand for batch dim\n\n            # Inference\n            with dt[1]:\n                visualize = increment_path(save_dir / Path(path).stem, mkdir=True) if visualize else False\n                pred, out = model(im, augment=augment, visualize=visualize)\n                proto = out[1]\n\n            # NMS\n            with dt[2]:\n                pred = non_max_suppression(pred, conf_thres, iou_thres, classes, agnostic_nms, max_det=max_det, nm=32)\n\n            for i, det in enumerate(pred):  # per image\n                seen += 1\n\n\n                p, im0, frame = path, im0s.copy(), getattr(dataset, 'frame', 0)\n\n\n                p = Path(p)  # to Path\n                save_path = str(save_dir / p.name)  # im.jpg\n                txt_path = str(save_dir / 'labels' / p.stem) + ('' if dataset.mode == 'image' else f'_{frame}')  # im.txt\n                s += '%gx%g ' % im.shape[2:]  # print string\n                gn = torch.tensor(im0.shape)[[1, 0, 1, 0]]  # normalization gain whwh\n                imc = im0.copy() if save_crop else im0  # for save_crop\n                annotator = Annotator(im0, line_width=line_thickness, example=str(names))\n                if len(det):\n                    masks = process_mask(proto[i], det[:, 6:], det[:, :4], im.shape[2:], upsample=True)  # HWC\n                    confs = det[:, 4]\n                    clasf = det[:, 5]\n                    boxes = det[:,:4]\n                    \n                    \n                    pred_masks = []\n                    pred_labels = []\n                    pred_boxes = []\n                    pred_scores = []\n                    \n                    \n                    for mask, confidence, classification, box in zip(masks, confs, clasf, boxes):\n                        \n                        \n                        sam_mask, scores, logits = predictor.predict(\n                            mask_input = None,\n                            multimask_output=False,\n                            box=box.cpu().numpy()\n                        )\n                        \n\n                        \n                        binary_mask = sam_mask.astype(np.uint8)\n                        kernel = np.ones(shape=(3, 3), dtype=np.uint8)\n                        #binary_mask = cv2.dilate(binary_mask, kernel, 3)\n                        \n                        binary_mask = binary_mask.astype(bool)\n                        encoded_mask = encode_binary_mask(binary_mask)#.astype(bool))\n\n                        sub_file.write(f'{int(classification)} {confidence} {encoded_mask.decode()} ' )\n            \n                        pred_masks.append(torch.tensor(binary_mask).squeeze(0))\n                        pred_boxes.append(box)\n                        pred_labels.append(classification)\n                        pred_scores.append(confidence)\n                    \n\n                    preds.append({'boxes': torch.stack(pred_boxes, dim=0).cpu(),\n                                  'scores': torch.stack(pred_scores, dim=0).cpu(),\n                                  'labels': torch.stack(pred_labels, dim=0).cpu(),\n                                  'masks': torch.stack(pred_masks, dim=0).cpu()})\n        \n        \n                    if validate: \n                        annotation_list = label_dict[image_id]\n                        t_masks = []\n                        t_labels = []\n                        t_boxes = []\n                        for annotation in annotation_list:\n                            if annotation['type'] != 'blood_vessel':\n                                continue\n                            coordinates = annotation['coordinates']\n                            true_box = zip_box(coordinates)\n                            true_mask = np.zeros(shape=(512, 512), dtype=np.uint8)\n                            cv2.fillPoly(true_mask, pts=np.array(coordinates), color=1)\n                            t_masks.append(torch.tensor(true_mask.astype(bool)))\n                            t_labels.append(torch.tensor(0))\n                            t_boxes.append(torch.tensor(true_box))\n                            \n                        if len(t_boxes) != 0:\n                            target.append({'boxes': torch.stack(t_boxes, dim=0),\n                                          'labels': torch.stack(t_labels, dim=0),\n                                          'masks': torch.stack(t_masks, dim=0)})\n                        else: \n                            _ = preds.pop()\n                            \n\n            sub_file.write('\\n')\n            \n\n        if validate:\n\n            return preds, target\n        \n        \n        \n            \n        \n            ","metadata":{"execution":{"iopub.status.busy":"2023-06-22T06:47:50.790803Z","iopub.execute_input":"2023-06-22T06:47:50.791143Z","iopub.status.idle":"2023-06-22T06:47:50.819464Z","shell.execute_reply.started":"2023-06-22T06:47:50.791114Z","shell.execute_reply":"2023-06-22T06:47:50.818683Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"preds, target = segment(source='/kaggle/input/hubmap-hhv-coco/valid',\n            data='/kaggle/working/hubmap-coco.yaml',\n            validate = True,\n            imgsz=(512, 512), \n            classes=0,\n            weights='/kaggle/input/yolov7-weights-and-wheels/yolov7-fine-tune/yolov7-fine-tune/weights/best.pt',\n            name='yolov7-predict',\n            project='yolov7-predict',\n            exist_ok=True,\n            nosave=True,\n            save_txt=True,\n            view_img=True,\n            )","metadata":{"execution":{"iopub.status.busy":"2023-06-20T15:35:43.208963Z","iopub.execute_input":"2023-06-20T15:35:43.209311Z"}}},{"cell_type":"markdown","source":"mAP_60 = MeanAveragePrecision(iou_thresholds = [0.6], iou_type='segm')\nmap_dict = mAP_60(preds, target)","metadata":{}},{"cell_type":"markdown","source":"map_dict","metadata":{}},{"cell_type":"markdown","source":"print(f'Cross-validation mAP@IOU_0.6: {map_dict[\"map\"]}' )","metadata":{}},{"cell_type":"code","source":"segment(source='/kaggle/input/hubmap-hacking-the-human-vasculature/test',\n            data='/kaggle/working/hubmap-coco.yaml',\n            imgsz=(512, 512), \n            classes=0,\n            weights='/kaggle/input/yolov7-weights-and-wheels/yolov7-fine-tune/yolov7-fine-tune/weights/best.pt',\n            name='yolov7-predict',\n            project='yolov7-predict',\n            exist_ok=True,\n            nosave=True,\n            save_txt=True,\n            view_img=True,\n            )","metadata":{"execution":{"iopub.status.busy":"2023-06-22T06:47:50.820616Z","iopub.execute_input":"2023-06-22T06:47:50.821113Z","iopub.status.idle":"2023-06-22T06:48:06.197302Z","shell.execute_reply.started":"2023-06-22T06:47:50.821086Z","shell.execute_reply":"2023-06-22T06:48:06.195888Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"!cat submission.csv","metadata":{"execution":{"iopub.status.busy":"2023-06-22T06:48:06.198703Z","iopub.execute_input":"2023-06-22T06:48:06.199031Z","iopub.status.idle":"2023-06-22T06:48:06.494059Z","shell.execute_reply.started":"2023-06-22T06:48:06.199004Z","shell.execute_reply":"2023-06-22T06:48:06.492640Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]}]}