{"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":"Name: Aviral Jain | \nRoll No: 18AG3AI08 | \nBTP Guide: Prof. Rajendra Machavaram\n\n# Application of Convolutional Neural Networks for Wheat Head Detection","metadata":{}},{"cell_type":"markdown","source":"I have saved the training weights at: https://www.kaggle.com/aviraljain898/fasterrcnn-weights\n\nLoad model weights instead of training from scratch again.","metadata":{}},{"cell_type":"markdown","source":"Firstly we set up the required environment experiment and install necessary libraries and dependencies. ","metadata":{}},{"cell_type":"code","source":"!pip3 install torch==1.9.0+cu111 torchvision==0.10.0+cu111 torchaudio==0.9.0 -f https://download.pytorch.org/whl/torch_stable.html\n!pip install pycocotools \n!wget https://raw.githubusercontent.com/pytorch/vision/main/references/detection/engine.py\n!wget https://raw.githubusercontent.com/pytorch/vision/main/references/detection/utils.py\n!wget https://raw.githubusercontent.com/pytorch/vision/main/references/detection/transforms.py\n!wget https://raw.githubusercontent.com/pytorch/vision/main/references/detection/coco_utils.py\n!wget https://raw.githubusercontent.com/pytorch/vision/main/references/detection/coco_eval.py    ","metadata":{"execution":{"iopub.status.busy":"2021-10-25T10:21:09.439343Z","iopub.execute_input":"2021-10-25T10:21:09.439677Z","iopub.status.idle":"2021-10-25T10:24:51.370798Z","shell.execute_reply.started":"2021-10-25T10:21:09.439588Z","shell.execute_reply":"2021-10-25T10:24:51.370034Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import torch\nprint(torch.__version__)\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nimport torch.nn as nn\nimport albumentations as A\nfrom albumentations.pytorch import ToTensorV2 \nimport torch\nimport torchvision\nfrom torchvision import datasets,transforms\nfrom tqdm import tqdm\nimport cv2\nfrom torch.utils.data import Dataset,DataLoader\nimport torch.optim as optim\nfrom PIL import Image\nimport os\nimport torch.nn.functional as F\nimport ast","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","execution":{"iopub.status.busy":"2021-10-25T10:25:55.436933Z","iopub.execute_input":"2021-10-25T10:25:55.437554Z","iopub.status.idle":"2021-10-25T10:25:58.413395Z","shell.execute_reply.started":"2021-10-25T10:25:55.437509Z","shell.execute_reply":"2021-10-25T10:25:58.412680Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# configure the hyperparameters here\nLR = 1e-4\nSPLIT = 0.2\nDEVICE = \"cuda\" if torch.cuda.is_available() else \"cpu\"\nBATCH_SIZE = 4\nEPOCHS = 2\nDATAPATH = '../input/global-wheat-detection'","metadata":{"execution":{"iopub.status.busy":"2021-10-25T10:26:01.151661Z","iopub.execute_input":"2021-10-25T10:26:01.152208Z","iopub.status.idle":"2021-10-25T10:26:01.211858Z","shell.execute_reply.started":"2021-10-25T10:26:01.152166Z","shell.execute_reply":"2021-10-25T10:26:01.210967Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"!ls '../input/global-wheat-detection'","metadata":{"execution":{"iopub.status.busy":"2021-10-25T10:26:01.434768Z","iopub.execute_input":"2021-10-25T10:26:01.435199Z","iopub.status.idle":"2021-10-25T10:26:02.104717Z","shell.execute_reply.started":"2021-10-25T10:26:01.435163Z","shell.execute_reply":"2021-10-25T10:26:02.103943Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df = pd.read_csv(DATAPATH + '/train.csv')\ndf.bbox = df.bbox.apply(ast.literal_eval)","metadata":{"execution":{"iopub.status.busy":"2021-10-25T10:26:02.106860Z","iopub.execute_input":"2021-10-25T10:26:02.107160Z","iopub.status.idle":"2021-10-25T10:26:04.725871Z","shell.execute_reply.started":"2021-10-25T10:26:02.107123Z","shell.execute_reply":"2021-10-25T10:26:04.724761Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df = df.groupby(\"image_id\")[\"bbox\"].apply(list).reset_index(name=\"bboxes\")","metadata":{"execution":{"iopub.status.busy":"2021-10-25T10:26:04.732272Z","iopub.execute_input":"2021-10-25T10:26:04.732635Z","iopub.status.idle":"2021-10-25T10:26:05.104452Z","shell.execute_reply.started":"2021-10-25T10:26:04.732593Z","shell.execute_reply":"2021-10-25T10:26:05.103547Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def train_test_split(dataFrame,split):\n    len_tot = len(dataFrame)\n    val_len = int(split*len_tot)\n    train_len = len_tot-val_len\n    train_data,val_data = dataFrame.iloc[:train_len][:],dataFrame.iloc[train_len:][:]\n    return train_data,val_data","metadata":{"execution":{"iopub.status.busy":"2021-10-25T10:26:05.109373Z","iopub.execute_input":"2021-10-25T10:26:05.111929Z","iopub.status.idle":"2021-10-25T10:26:05.121400Z","shell.execute_reply.started":"2021-10-25T10:26:05.111876Z","shell.execute_reply":"2021-10-25T10:26:05.120658Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train_data_df,val_data_df = train_test_split(df,SPLIT)","metadata":{"execution":{"iopub.status.busy":"2021-10-25T10:26:05.125496Z","iopub.execute_input":"2021-10-25T10:26:05.126106Z","iopub.status.idle":"2021-10-25T10:26:05.132742Z","shell.execute_reply.started":"2021-10-25T10:26:05.126078Z","shell.execute_reply":"2021-10-25T10:26:05.131968Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train_data_df","metadata":{"execution":{"iopub.status.busy":"2021-10-25T10:26:05.135948Z","iopub.execute_input":"2021-10-25T10:26:05.136204Z","iopub.status.idle":"2021-10-25T10:26:05.279243Z","shell.execute_reply.started":"2021-10-25T10:26:05.136179Z","shell.execute_reply":"2021-10-25T10:26:05.278602Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"class WheatDataset(Dataset):\n    def __init__(self,data,root_dir,transform=None,train=True):\n        self.data = data\n        self.root_dir = root_dir\n        self.image_names = self.data.image_id.values\n        self.bboxes = self.data.bboxes.values\n        self.transform = transform\n        self.isTrain = train\n        \n    def __len__(self):\n        return len(self.data)\n    \n    def __getitem__(self,index):\n#         print(self.image_names)\n#         print(self.bboxes)\n        img_path = os.path.join(self.root_dir,self.image_names[index]+\".jpg\")\n        image = cv2.imread(img_path, cv2.IMREAD_COLOR)\n        image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB).astype(np.float32)\n        image /= 255.0\n        bboxes = torch.tensor(self.bboxes[index],dtype=torch.float64)\n#         print(bboxes)\n        \"\"\"\n            As per the docs of torchvision\n            we need bboxes in format (xmin,ymin,xmax,ymax)\n            Currently we have them in format (xmin,ymin,width,height)\n        \"\"\"\n        bboxes[:,2] = bboxes[:,0]+bboxes[:,2]\n        bboxes[:,3] = bboxes[:,1]+bboxes[:,3]\n#         print(image.size,type(image))\n        \"\"\"\n            we need to return image and a target dictionary\n            target:\n                boxes,labels,image_id,area,iscrowd\n        \"\"\"\n        area = (bboxes[:,3]-bboxes[:,1])*(bboxes[:,2]-bboxes[:,0])\n        area = torch.as_tensor(area,dtype=torch.float32)\n        \n        # there is only one class\n        labels = torch.ones((len(bboxes),),dtype=torch.int64)\n        \n        # suppose all instances are not crowded\n        iscrowd = torch.zeros((len(bboxes),),dtype=torch.int64)\n        \n        target = {}\n        target['boxes'] = bboxes\n        target['labels']= labels\n        target['image_id'] = torch.tensor([index])\n        target[\"area\"] = area\n        target['iscrowd'] = iscrowd\n        \n        if self.transform is not None:\n            sample = {\n                'image': image,\n                'bboxes': target['boxes'],\n                'labels': labels\n            }\n            sample = self.transform(**sample)\n            image = sample['image']\n            \n            target['boxes'] = torch.stack(tuple(map(torch.tensor, zip(*sample['bboxes'])))).permute(1, 0)\n            \n        return image,target","metadata":{"execution":{"iopub.status.busy":"2021-10-25T10:26:05.280526Z","iopub.execute_input":"2021-10-25T10:26:05.280775Z","iopub.status.idle":"2021-10-25T10:26:05.295057Z","shell.execute_reply.started":"2021-10-25T10:26:05.280742Z","shell.execute_reply":"2021-10-25T10:26:05.293797Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#Apply necessary transformations\n\ntrain_transform = A.Compose([\n    A.Flip(0.5),\n    ToTensorV2(p=1.0)\n],bbox_params = {'format':\"pascal_voc\",'label_fields': ['labels']})\n\nval_transform = A.Compose([\n      ToTensorV2(p=1.0)\n],bbox_params = {'format':\"pascal_voc\",\"label_fields\":['labels']})","metadata":{"execution":{"iopub.status.busy":"2021-10-25T10:26:05.296620Z","iopub.execute_input":"2021-10-25T10:26:05.296911Z","iopub.status.idle":"2021-10-25T10:26:05.306085Z","shell.execute_reply.started":"2021-10-25T10:26:05.296859Z","shell.execute_reply":"2021-10-25T10:26:05.305266Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def collate_fn(batch):\n    return tuple(zip(*batch))","metadata":{"execution":{"iopub.status.busy":"2021-10-25T10:26:05.308074Z","iopub.execute_input":"2021-10-25T10:26:05.308889Z","iopub.status.idle":"2021-10-25T10:26:05.314367Z","shell.execute_reply.started":"2021-10-25T10:26:05.308716Z","shell.execute_reply":"2021-10-25T10:26:05.313588Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train_data = WheatDataset(train_data_df,DATAPATH+\"/train\",transform=train_transform)\nvalid_data = WheatDataset(val_data_df,DATAPATH+\"/train\",transform=val_transform)","metadata":{"execution":{"iopub.status.busy":"2021-10-25T10:26:10.400168Z","iopub.execute_input":"2021-10-25T10:26:10.400926Z","iopub.status.idle":"2021-10-25T10:26:10.406023Z","shell.execute_reply.started":"2021-10-25T10:26:10.400884Z","shell.execute_reply":"2021-10-25T10:26:10.405250Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"image,target = train_data.__getitem__(0)\nprint(image.shape)\n\nplt.imshow(image.permute(1, 2, 0))","metadata":{"execution":{"iopub.status.busy":"2021-10-25T10:27:58.763411Z","iopub.execute_input":"2021-10-25T10:27:58.764060Z","iopub.status.idle":"2021-10-25T10:27:59.222512Z","shell.execute_reply.started":"2021-10-25T10:27:58.764012Z","shell.execute_reply":"2021-10-25T10:27:59.221831Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"from torchvision.models.detection.faster_rcnn import FastRCNNPredictor\n\n#We use the pretrained Faster-RCNN so that our training is faster\nmodel = torchvision.models.detection.fasterrcnn_resnet50_fpn(pretrained=True)\n\nnum_classes = 2\nin_features = model.roi_heads.box_predictor.cls_score.in_features\nmodel.roi_heads.box_predictor = FastRCNNPredictor(in_features,num_classes)","metadata":{"execution":{"iopub.status.busy":"2021-10-25T10:26:13.083283Z","iopub.execute_input":"2021-10-25T10:26:13.083560Z","iopub.status.idle":"2021-10-25T10:26:20.361743Z","shell.execute_reply.started":"2021-10-25T10:26:13.083525Z","shell.execute_reply":"2021-10-25T10:26:20.361012Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"class Averager:\n    def __init__(self):\n        self.current_total=0.0\n        self.iterations = 0.0\n    def send(self,value):\n        self.current_total+=value\n        self.iterations+=1\n    \n    @property\n    def value(self):\n        if self.iterations == 0:\n            return 0\n        else:\n            return 1.0*self.current_total/self.iterations\n    def reset(self):\n        self.current_total = 0.0\n        self.iterations = 0.0","metadata":{"execution":{"iopub.status.busy":"2021-10-25T10:26:42.597114Z","iopub.execute_input":"2021-10-25T10:26:42.597378Z","iopub.status.idle":"2021-10-25T10:26:42.603055Z","shell.execute_reply.started":"2021-10-25T10:26:42.597348Z","shell.execute_reply":"2021-10-25T10:26:42.602097Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train_dataloader = DataLoader(train_data,batch_size=BATCH_SIZE,shuffle=True,collate_fn=collate_fn)\nval_dataloader = DataLoader(valid_data,batch_size=BATCH_SIZE,shuffle=False,collate_fn=collate_fn)\n\nprint(len(train_dataloader))\nprint(len(val_dataloader))","metadata":{"execution":{"iopub.status.busy":"2021-10-25T10:26:43.584069Z","iopub.execute_input":"2021-10-25T10:26:43.584747Z","iopub.status.idle":"2021-10-25T10:26:43.591943Z","shell.execute_reply.started":"2021-10-25T10:26:43.584706Z","shell.execute_reply":"2021-10-25T10:26:43.591033Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train_loss = []\nval_loss = []\nmodel = model.to(DEVICE)\nparams =[p for p in model.parameters() if p.requires_grad]\n\n#We compile the model using Adaptive Momentum Optimizer\noptimizer = optim.Adam(params,lr=LR)\nloss_hist = Averager()\nitr = 1\nlr_scheduler=None","metadata":{"execution":{"iopub.status.busy":"2021-10-25T10:26:47.762216Z","iopub.execute_input":"2021-10-25T10:26:47.762492Z","iopub.status.idle":"2021-10-25T10:26:50.873319Z","shell.execute_reply.started":"2021-10-25T10:26:47.762460Z","shell.execute_reply":"2021-10-25T10:26:50.872580Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#training loop takes some time, better to use saved weights\n\nfrom engine import train_one_epoch, evaluate\n\nloss_hist = Averager()\nitr = 1\nmodel = model.float()\nfor epoch in range(EPOCHS):\n    loss_hist.reset()\n    \n    for images, targets in train_dataloader:\n        \n        images = list(image.to(DEVICE) for image in images)\n        targets = [{k: v.to(DEVICE) for k, v in t.items()} for t in targets]\n\n        loss_dict = model(images, targets)\n\n        losses = sum(loss for loss in loss_dict.values())\n        print(losses.dtype)\n        loss_value = losses.item()\n\n        loss_hist.send(loss_value)\n\n        optimizer.zero_grad()\n        losses.backward()\n        optimizer.step()\n\n        if itr % 50 == 0:\n            print(f\"Iteration #{itr} loss: {loss_value}\")\n\n        itr += 1\n    \n    # update the learning rate\n    if lr_scheduler is not None:\n        lr_scheduler.step()\n\n    print(f\"Epoch #{epoch} loss: {loss_hist.value}\")  \n    evaluate(model, val_dataloader, device=device)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Save the training weights","metadata":{}},{"cell_type":"code","source":"torch.save(model.state_dict(), 'fasterrcnn_resnet50_fpn.pth')","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Load the saved weights","metadata":{}},{"cell_type":"code","source":"model.load_state_dict(torch.load(\"../input/fasterrcnn-weights/fasterrcnn_resnet50_fpn_best.pth\"))","metadata":{"execution":{"iopub.status.busy":"2021-10-25T10:27:15.662518Z","iopub.execute_input":"2021-10-25T10:27:15.662799Z","iopub.status.idle":"2021-10-25T10:27:19.394593Z","shell.execute_reply.started":"2021-10-25T10:27:15.662753Z","shell.execute_reply":"2021-10-25T10:27:19.393858Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Now let's test the model and see the results","metadata":{}},{"cell_type":"code","source":"images, targets = next(iter(val_dataloader))\nimages = list(img.to(DEVICE) for img in images)\nprint(images[0].shape)\ntargets = [{k: v.to(DEVICE) for k, v in t.items()} for t in targets]\nboxes = targets[1]['boxes'].cpu().numpy().astype(np.int32)\nsample = images[1].permute(1,2,0).cpu().numpy()","metadata":{"execution":{"iopub.status.busy":"2021-10-25T10:27:27.172712Z","iopub.execute_input":"2021-10-25T10:27:27.173561Z","iopub.status.idle":"2021-10-25T10:27:27.404909Z","shell.execute_reply.started":"2021-10-25T10:27:27.173498Z","shell.execute_reply":"2021-10-25T10:27:27.403536Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"model.eval()\ncpu_device = torch.device(\"cpu\")\n# print(images[0].shape)\noutputs = model(images)\noutputs = [{k: v.to(cpu_device) for k, v in t.items()} for t in outputs]","metadata":{"execution":{"iopub.status.busy":"2021-10-25T10:27:27.885380Z","iopub.execute_input":"2021-10-25T10:27:27.885619Z","iopub.status.idle":"2021-10-25T10:27:28.110295Z","shell.execute_reply.started":"2021-10-25T10:27:27.885591Z","shell.execute_reply":"2021-10-25T10:27:28.109630Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"fig, ax = plt.subplots(1, 1, figsize=(16, 8))\n\nfor box in boxes:\n    cv2.rectangle(sample,\n                  (box[0], box[1]),\n                  (box[2], box[3]),\n                  (220, 0, 0), 3)\n    \nax.set_axis_off()\nax.imshow(sample)","metadata":{"execution":{"iopub.status.busy":"2021-10-25T10:27:32.886625Z","iopub.execute_input":"2021-10-25T10:27:32.887198Z","iopub.status.idle":"2021-10-25T10:27:33.375294Z","shell.execute_reply.started":"2021-10-25T10:27:32.887157Z","shell.execute_reply":"2021-10-25T10:27:33.374686Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Define function to calculate Intersection Over Union (IOU)","metadata":{}},{"cell_type":"code","source":"def bb_intersection_over_union(boxA, boxB):\n\t# determine the (x, y)-coordinates of the intersection rectangle\n\txA = max(boxA[0], boxB[0])\n\tyA = max(boxA[1], boxB[1])\n\txB = min(boxA[2], boxB[2])\n\tyB = min(boxA[3], boxB[3])\n\t# compute the area of intersection rectangle\n\tinterArea = max(0, xB - xA + 1) * max(0, yB - yA + 1)\n\t# compute the area of both the prediction and ground-truth\n\t# rectangles\n\tboxAArea = (boxA[2] - boxA[0] + 1) * (boxA[3] - boxA[1] + 1)\n\tboxBArea = (boxB[2] - boxB[0] + 1) * (boxB[3] - boxB[1] + 1)\n\t# compute the intersection over union by taking the intersection\n\t# area and dividing it by the sum of prediction + ground-truth\n\t# areas - the interesection area\n\tiou = interArea / float(boxAArea + boxBArea - interArea)\n\t# return the intersection over union value\n\treturn interArea, float(boxAArea + boxBArea - interArea)","metadata":{"execution":{"iopub.status.busy":"2021-10-25T10:29:02.169171Z","iopub.execute_input":"2021-10-25T10:29:02.169885Z","iopub.status.idle":"2021-10-25T10:29:02.176381Z","shell.execute_reply.started":"2021-10-25T10:29:02.169847Z","shell.execute_reply":"2021-10-25T10:29:02.175737Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"images, targets = next(iter(val_dataloader))\nimages = list(img.to(DEVICE) for img in images)\n#print(images[0].shape)\ntargets = [{k: v.to(DEVICE) for k, v in t.items()} for t in targets]\nboxes = targets[1]\n#sample = images[1].permute(1,2,0).cpu().numpy()\n\nmodel.eval()\ncpu_device = torch.device(\"cpu\")\n# print(images[0].shape)\noutputs = model(images)\noutputs = [{k: v.to(cpu_device) for k, v in t.items()} for t in outputs]\n#outputs = outputs\n\nval_iou = []\n\nfor i in range(len(outputs)):\n    b1 = targets[i]['boxes'].cpu().numpy().astype(np.int32)\n    b2 = outputs[i]['boxes'].detach().numpy().astype(np.int32)\n    sample = images[i].permute(1,2,0).cpu().numpy()\n    \n    scores = outputs[i]['scores'].detach().numpy()\n   # print(scores)\n    #b2 = b2[scores >= 0.5].astype(np.int32)\n    #print(b1.shape)\n    #print(b1)\n    \n    b2 = [x for _ , x in sorted(zip(scores,b2), key=lambda x: x[0], reverse = True)]\n    b2 = b2[:(b1.shape[0])]\n    b_good = []\n    #print(len(b2))\n    numerator = 0\n    denominator = 0\n    \n    for bb2 in b2:\n        for bb1 in b1:\n            #print(bb1, bb2)\n            o = bb_intersection_over_union(bb1, bb2)\n           # print(o)\n            num = o[0]\n            dem = o[1]\n            if num>0 and num/dem>0.5:\n                numerator += num\n                denominator +=dem\n                b_good.append(bb2)\n    iou = numerator/denominator\n    print(iou)\n    fig, ax = plt.subplots(1, 1, figsize=(16, 8))\n\n    for box in b1:\n        cv2.rectangle(sample,\n                      (box[0], box[1]),\n                      (box[2], box[3]),\n                      (220, 0, 0), 3)\n    for box in b_good:\n        cv2.rectangle(sample,\n                      (box[0], box[1]),\n                      (box[2], box[3]),\n                      (0, 0, 220), 3)\n    ax.set_axis_off()\n    ax.imshow(sample)\n    \n    \n    \nprint(val_iou)","metadata":{"execution":{"iopub.status.busy":"2021-10-25T10:29:04.111921Z","iopub.execute_input":"2021-10-25T10:29:04.112414Z","iopub.status.idle":"2021-10-25T10:29:06.710329Z","shell.execute_reply.started":"2021-10-25T10:29:04.112375Z","shell.execute_reply":"2021-10-25T10:29:06.709679Z"},"trusted":true},"execution_count":null,"outputs":[]}]}