{"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":"# Imports","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","execution":{"iopub.status.busy":"2023-07-30T11:34:23.870814Z","iopub.execute_input":"2023-07-30T11:34:23.871202Z","iopub.status.idle":"2023-07-30T11:34:24.499667Z","shell.execute_reply.started":"2023-07-30T11:34:23.871170Z","shell.execute_reply":"2023-07-30T11:34:24.498626Z"}}},{"cell_type":"code","source":"import pandas as pd\nimport numpy as np\nimport matplotlib.pyplot as plt\nimport seaborn as sns\nimport copy\nimport os\nimport cv2\nimport warnings\nwarnings.filterwarnings(\"ignore\")\nfrom sklearn.model_selection import ShuffleSplit\nimport torch\nimport torch.nn as nn\nfrom torch.utils.data import Dataset,DataLoader\nfrom torch.utils.data import Subset\nimport torchvision.transforms.functional as tf\nimport torch.optim as optim\nimport torchvision\nfrom torchvision.models.detection import fasterrcnn_resnet50_fpn\nfrom torchvision.models.detection import FasterRCNN_ResNet50_FPN_Weights\nfrom torchvision.models.detection.faster_rcnn import FastRCNNPredictor\n\n%matplotlib inline","metadata":{"execution":{"iopub.status.busy":"2023-07-31T03:41:32.693413Z","iopub.execute_input":"2023-07-31T03:41:32.694206Z","iopub.status.idle":"2023-07-31T03:41:37.567267Z","shell.execute_reply.started":"2023-07-31T03:41:32.694169Z","shell.execute_reply":"2023-07-31T03:41:37.566103Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import torch\nnp.random.seed(42)\ntorch.manual_seed(42)","metadata":{"execution":{"iopub.status.busy":"2023-07-31T03:41:37.573777Z","iopub.execute_input":"2023-07-31T03:41:37.574829Z","iopub.status.idle":"2023-07-31T03:41:37.588089Z","shell.execute_reply.started":"2023-07-31T03:41:37.574793Z","shell.execute_reply":"2023-07-31T03:41:37.587007Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"data=pd.read_csv(\"/kaggle/input/global-wheat-detection/train.csv\")\ndata.head()","metadata":{"execution":{"iopub.status.busy":"2023-07-31T03:41:42.079891Z","iopub.execute_input":"2023-07-31T03:41:42.080545Z","iopub.status.idle":"2023-07-31T03:41:42.427885Z","shell.execute_reply.started":"2023-07-31T03:41:42.080512Z","shell.execute_reply":"2023-07-31T03:41:42.426838Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def bbox_ops(string):\n    ls=string.split()\n    bbox=[]\n    for i in range(4):\n        bbox.append(np.float32(ls[i].strip(\"[],\")))\n        \n    return bbox","metadata":{"execution":{"iopub.status.busy":"2023-07-31T03:41:42.903953Z","iopub.execute_input":"2023-07-31T03:41:42.904658Z","iopub.status.idle":"2023-07-31T03:41:42.909970Z","shell.execute_reply.started":"2023-07-31T03:41:42.904625Z","shell.execute_reply":"2023-07-31T03:41:42.908818Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"data[\"bbox\"]=data[\"bbox\"].apply(bbox_ops)\n\ndata[\"x0\"]=data[\"bbox\"].apply(lambda x:x[0])\ndata[\"y0\"]=data[\"bbox\"].apply(lambda x:x[1])\ndata[\"w\"]=data[\"bbox\"].apply(lambda x:x[2])\ndata[\"h\"]=data[\"bbox\"].apply(lambda x:x[3])\n\ndata.drop(columns=[\"bbox\",\"source\"],axis=1,inplace=True)\ndata.head()","metadata":{"execution":{"iopub.status.busy":"2023-07-31T03:41:43.579000Z","iopub.execute_input":"2023-07-31T03:41:43.579356Z","iopub.status.idle":"2023-07-31T03:41:45.215945Z","shell.execute_reply.started":"2023-07-31T03:41:43.579329Z","shell.execute_reply":"2023-07-31T03:41:45.214845Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"data[\"image_id\"].nunique()","metadata":{"execution":{"iopub.status.busy":"2023-07-31T03:41:45.217804Z","iopub.execute_input":"2023-07-31T03:41:45.218850Z","iopub.status.idle":"2023-07-31T03:41:45.242410Z","shell.execute_reply.started":"2023-07-31T03:41:45.218817Z","shell.execute_reply":"2023-07-31T03:41:45.241061Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Configuration","metadata":{}},{"cell_type":"code","source":"dataframe=data\nimage_dir=\"/kaggle/input/global-wheat-detection/train\"\ntest_dir=\"/kaggle/input/global-wheat-detection/test\"\nmodel_weights_file=\"model.pth\"\n\ndevice=\"cuda\" if torch.cuda.is_available() else \"cpu\"\n\nbatch_size=4\nlearning_rate=3e-5\nepochs=50\n\nthreshold=0.5\niou_threshold=0.8","metadata":{"execution":{"iopub.status.busy":"2023-07-31T03:41:45.398423Z","iopub.execute_input":"2023-07-31T03:41:45.398791Z","iopub.status.idle":"2023-07-31T03:41:45.429122Z","shell.execute_reply.started":"2023-07-31T03:41:45.398761Z","shell.execute_reply":"2023-07-31T03:41:45.427842Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Custom Dataset","metadata":{}},{"cell_type":"code","source":"\"\"\"The input to the model is expected to be a list of tensors, each of shape [C, H, W], \n   one for each image, and should be in 0-1 range. Different images can have different \n   sizes.The behavior of the model changes depending if it is in training or evaluation \n   mode.\"\"\"\n\nclass WheatDataset(Dataset):\n    def __init__(self,dataframe,image_dir):\n        super().__init__()\n        self.dataframe=dataframe\n        self.img_list=sorted(self.dataframe[\"image_id\"].unique())\n        self.img_dir=image_dir\n        \n    def __len__(self):\n        return len(self.img_list)\n    \n    def __getitem__(self,idx):\n        img_name=self.img_list[idx]+\".jpg\"\n        img_path=os.path.join(self.img_dir,img_name)\n        img=cv2.imread(img_path)\n        img=cv2.cvtColor(img,cv2.COLOR_BGR2RGB)\n        img=tf.to_tensor(img)\n        \n        inter=self.dataframe[self.dataframe[\"image_id\"]==self.img_list[idx]]\n        boxes=inter[[\"x0\",\"y0\",\"w\",\"h\"]].values\n        area=boxes[:,2]*boxes[:,3]\n        \n        # converting bounding box from x0y0wh format to x0y0x1y1 format\n        boxes[:,2]=boxes[:,0]+boxes[:,2]\n        boxes[:,3]=boxes[:,1]+boxes[:,3]\n        \n        labels=torch.ones((boxes.shape[0]),dtype=torch.int64)\n        iscrowd=torch.zeros((boxes.shape[0]),dtype=torch.uint8)\n        \n        target={}\n        target[\"boxes\"]=torch.as_tensor(boxes,dtype=torch.float32)\n        target[\"area\"]=torch.as_tensor(area,dtype=torch.float32)\n        target[\"labels\"]=labels\n        target[\"iscrowd\"]=iscrowd\n        target[\"id\"]=torch.tensor(idx)\n        \n        return img,target","metadata":{"execution":{"iopub.status.busy":"2023-07-31T03:41:46.862307Z","iopub.execute_input":"2023-07-31T03:41:46.863025Z","iopub.status.idle":"2023-07-31T03:41:46.876133Z","shell.execute_reply.started":"2023-07-31T03:41:46.862986Z","shell.execute_reply":"2023-07-31T03:41:46.874947Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train_ds=WheatDataset(dataframe,image_dir)\nval_ds=WheatDataset(dataframe,image_dir)","metadata":{"execution":{"iopub.status.busy":"2023-07-31T03:41:47.649748Z","iopub.execute_input":"2023-07-31T03:41:47.650128Z","iopub.status.idle":"2023-07-31T03:41:47.681421Z","shell.execute_reply.started":"2023-07-31T03:41:47.650097Z","shell.execute_reply":"2023-07-31T03:41:47.680497Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"ss=ShuffleSplit(n_splits=1,test_size=0.2,random_state=1)\n\nindexs=range(len(train_ds))\nfor train_idx,val_idx in ss.split(indexs):\n    print(f\"Train dataset length: {len(train_idx)}\")\n    print(f\"Validation dataset length: {len(val_idx)}\")","metadata":{"execution":{"iopub.status.busy":"2023-07-31T03:41:48.583081Z","iopub.execute_input":"2023-07-31T03:41:48.583433Z","iopub.status.idle":"2023-07-31T03:41:48.591205Z","shell.execute_reply.started":"2023-07-31T03:41:48.583405Z","shell.execute_reply":"2023-07-31T03:41:48.589992Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train_ds=Subset(train_ds,train_idx)\nval_ds=Subset(val_ds,val_idx)","metadata":{"execution":{"iopub.status.busy":"2023-07-31T03:41:49.049745Z","iopub.execute_input":"2023-07-31T03:41:49.050123Z","iopub.status.idle":"2023-07-31T03:41:49.055560Z","shell.execute_reply.started":"2023-07-31T03:41:49.050091Z","shell.execute_reply":"2023-07-31T03:41:49.054386Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Data Visualization","metadata":{}},{"cell_type":"code","source":"def show(img,boxes):\n    boxes=boxes.detach().numpy().astype(np.int32)\n    sample=img.permute(1,2,0).numpy().copy()\n    \n    for box in boxes:\n        cv2.rectangle(sample,(box[0], box[1]),(box[2], box[3]),(220, 0, 0), 3)\n    \n    plt.axis(\"off\");\n    plt.imshow(sample);","metadata":{"execution":{"iopub.status.busy":"2023-07-31T03:41:50.277598Z","iopub.execute_input":"2023-07-31T03:41:50.277966Z","iopub.status.idle":"2023-07-31T03:41:50.284286Z","shell.execute_reply.started":"2023-07-31T03:41:50.277929Z","shell.execute_reply":"2023-07-31T03:41:50.283185Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"plt.figure(figsize=(8,8))\n\nimg,target=next(iter(train_ds))\nshow(img,target[\"boxes\"])\nplt.savefig(\"1.png\")","metadata":{"execution":{"iopub.status.busy":"2023-07-31T03:41:51.093769Z","iopub.execute_input":"2023-07-31T03:41:51.094454Z","iopub.status.idle":"2023-07-31T03:41:52.226418Z","shell.execute_reply.started":"2023-07-31T03:41:51.094418Z","shell.execute_reply":"2023-07-31T03:41:52.224589Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"plt.figure(figsize=(8,8))\n\nimg,target=next(iter(val_ds))\nshow(img,target[\"boxes\"])\nplt.savefig(\"2.png\")","metadata":{"execution":{"iopub.status.busy":"2023-07-31T03:41:52.639799Z","iopub.execute_input":"2023-07-31T03:41:52.640459Z","iopub.status.idle":"2023-07-31T03:41:53.554819Z","shell.execute_reply.started":"2023-07-31T03:41:52.640425Z","shell.execute_reply":"2023-07-31T03:41:53.553882Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# DataLoader","metadata":{}},{"cell_type":"code","source":"def collate_fn(batch):\n    return tuple(zip(*batch))","metadata":{"execution":{"iopub.status.busy":"2023-07-31T03:42:04.929993Z","iopub.execute_input":"2023-07-31T03:42:04.930509Z","iopub.status.idle":"2023-07-31T03:42:04.935845Z","shell.execute_reply.started":"2023-07-31T03:42:04.930462Z","shell.execute_reply":"2023-07-31T03:42:04.934834Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train_dl=DataLoader(train_ds,batch_size=batch_size,shuffle=True,num_workers=2,\n                    pin_memory=True if torch.cuda.is_available else False,\n                    collate_fn=collate_fn)\nval_dl=DataLoader(val_ds,batch_size=batch_size,shuffle=False,num_workers=2,\n                  pin_memory=True if torch.cuda.is_available else False,\n                  collate_fn=collate_fn)","metadata":{"execution":{"iopub.status.busy":"2023-07-31T03:42:05.933876Z","iopub.execute_input":"2023-07-31T03:42:05.934889Z","iopub.status.idle":"2023-07-31T03:42:05.941675Z","shell.execute_reply.started":"2023-07-31T03:42:05.934848Z","shell.execute_reply":"2023-07-31T03:42:05.940703Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Model","metadata":{}},{"cell_type":"code","source":"# load a model pre-trained on COCO\nweights=FasterRCNN_ResNet50_FPN_Weights.DEFAULT\nmodel=fasterrcnn_resnet50_fpn(weights=weights)\n\n# replace the classifier with a new one, that has\n# num_classes which is user-defined\n# 1 class (person) + background\nnum_classes = 2 \n\n# get number of input features for the classifier\nin_features=model.roi_heads.box_predictor.cls_score.in_features\n\n\n# replace the pre-trained head with a new one\nmodel.roi_heads.box_predictor=FastRCNNPredictor(in_channels=in_features, \n                                                num_classes=num_classes)\n\nmodel.to(device)","metadata":{"_kg_hide-output":true,"execution":{"iopub.status.busy":"2023-07-31T03:56:57.052364Z","iopub.execute_input":"2023-07-31T03:56:57.053005Z","iopub.status.idle":"2023-07-31T03:56:58.292848Z","shell.execute_reply.started":"2023-07-31T03:56:57.052973Z","shell.execute_reply":"2023-07-31T03:56:58.291877Z"},"collapsed":true,"jupyter":{"outputs_hidden":true},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"for params in model.children():\n    print(params)","metadata":{"_kg_hide-output":true,"execution":{"iopub.status.busy":"2023-07-31T03:56:58.294309Z","iopub.execute_input":"2023-07-31T03:56:58.294658Z","iopub.status.idle":"2023-07-31T03:56:58.305164Z","shell.execute_reply.started":"2023-07-31T03:56:58.294632Z","shell.execute_reply":"2023-07-31T03:56:58.303970Z"},"collapsed":true,"jupyter":{"outputs_hidden":true},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"\"\"\"ConvNet as fixed feature extractor: Here, we will freeze the weights for the backbone of\n   the network (resnet50 with feature pyramid network). The Regional Proposal network and \n   Region of Interest heads will be fine tuned using transfer learning.\"\"\"\n\nclassification_head=list(model.children())[-2:]\n\nfor children in list(model.children())[:-2]:\n    for params in children.parameters():\n        params.requires_grad=False\n        \nparameters=[]\nfor heads in classification_head:\n    for params in heads.parameters():\n        parameters.append(params)\n        \n\noptimizer=optim.Adam(parameters,lr=learning_rate)\nlr_scheduler=optim.lr_scheduler.ReduceLROnPlateau(optimizer, mode='min', factor=0.1, \n                                                  patience=8, threshold=0.0001)\n\n        \nprint(classification_head)","metadata":{"execution":{"iopub.status.busy":"2023-07-31T03:56:58.323156Z","iopub.execute_input":"2023-07-31T03:56:58.323968Z","iopub.status.idle":"2023-07-31T03:56:58.336451Z","shell.execute_reply.started":"2023-07-31T03:56:58.323890Z","shell.execute_reply":"2023-07-31T03:56:58.334388Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Training","metadata":{}},{"cell_type":"code","source":"def get_lr(optimizer):\n    for params in optimizer.param_groups:\n        return params[\"lr\"]","metadata":{"execution":{"iopub.status.busy":"2023-07-31T03:57:32.812824Z","iopub.execute_input":"2023-07-31T03:57:32.813510Z","iopub.status.idle":"2023-07-31T03:57:32.818206Z","shell.execute_reply.started":"2023-07-31T03:57:32.813478Z","shell.execute_reply":"2023-07-31T03:57:32.817255Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"loss_history={\"training_loss\":[],\n              \"validation_loss\":[]}\n\ntrain_len=len(train_dl.dataset)\nval_len=len(val_dl.dataset)\n\nbest_validation_loss=np.inf\nbest_weights=copy.deepcopy(model.state_dict())\n\nfor epoch in range(epochs):\n    \n    training_loss=0.0\n    validation_loss=0.0\n    current_lr=get_lr(optimizer)\n    \n    #During training, the model expects both the input tensors, as well as a targets \n    model.train()\n    for imgs,targets in train_dl:\n        imgs=[img.to(device) for img in imgs]\n        targets=[{k:v.to(device) for (k,v) in d.items()} for d in targets]\n        \n        \"\"\"The model returns a Dict[Tensor] during training, containing the classification\n           and regression losses for both the RPN and the R-CNN.\"\"\"\n        \n        loss_dict=model(imgs,targets)\n        losses=sum(loss for loss in loss_dict.values())\n        training_loss+=losses.item()\n        \n        optimizer.zero_grad()\n        losses.backward()\n        optimizer.step()\n        \n    with torch.no_grad():\n        for imgs,targets in val_dl:\n            imgs=[img.to(device) for img in imgs]\n            targets=[{k:v.to(device) for (k,v) in d.items()} for d in targets]\n            \n            \"\"\"The model returns a Dict[Tensor] during training, containing the classification\n               and regression losses for both the RPN and the R-CNN.\"\"\"\n            \n            loss_dict=model(imgs,targets)\n            losses=sum(loss for loss in loss_dict.values())\n            validation_loss+=losses.item()\n            \n            \n    lr_scheduler.step(validation_loss)\n    if current_lr!=get_lr(optimizer):\n        print(\"Loading best Model weights\")\n        model.load_state_dict(best_weights)\n    \n    if validation_loss<best_validation_loss:\n        best_validation_loss=validation_loss\n        best_weights=copy.deepcopy(model.state_dict())\n        print(\"Updating Best Model weights\")\n        \n    \n    loss_history[\"training_loss\"].append(training_loss/train_len)\n    loss_history[\"validation_loss\"].append(validation_loss/val_len)\n            \n    print(f\"\\n{epoch+1}/{epochs}\")\n    print(f\"Training Loss: {training_loss/train_len}\")\n    print(f\"Validation_loss: {validation_loss/val_len}\")\n    print(\"\\n\"+\"*\"*50)","metadata":{"_kg_hide-output":true,"execution":{"iopub.status.busy":"2023-07-31T03:57:40.100278Z","iopub.execute_input":"2023-07-31T03:57:40.101442Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"sns.lineplot(x=range(epochs),y=loss_history[\"training_loss\"],label=\"Train Losses\");\nsns.lineplot(x=range(epochs),y=loss_history[\"validation_loss\"],label=\"Validation Losses\");\nplt.title(\"Training Validation Datasets Losses Plot\");\nplt.legend();\nplt.savefig(\"3.jpg\")","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Saving Model State","metadata":{}},{"cell_type":"code","source":"torch.save(best_weights,model_weights_file)","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Inference","metadata":{}},{"cell_type":"code","source":"model.load_state_dict(torch.load(model_weights_file))\nmodel.to(device)","metadata":{"execution":{"iopub.status.busy":"2023-07-30T13:17:20.000120Z","iopub.execute_input":"2023-07-30T13:17:20.000547Z","iopub.status.idle":"2023-07-30T13:17:20.133947Z","shell.execute_reply.started":"2023-07-30T13:17:20.000512Z","shell.execute_reply":"2023-07-30T13:17:20.132728Z"},"collapsed":true,"jupyter":{"outputs_hidden":true},"_kg_hide-output":true,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def show(img,boxes,ax,color=(255,0,0)):\n    boxes=boxes.detach().cpu().numpy().astype(np.int32)\n    sample=img.permute(1,2,0).numpy().copy()\n    \n    for box in boxes:\n        cv2.rectangle(sample,(box[0], box[1]),(box[2], box[3]),color, 3)\n    \n    ax.axis(\"off\");\n    ax.imshow(sample);","metadata":{"execution":{"iopub.status.busy":"2023-07-30T13:17:20.184368Z","iopub.execute_input":"2023-07-30T13:17:20.185171Z","iopub.status.idle":"2023-07-30T13:17:20.193476Z","shell.execute_reply.started":"2023-07-30T13:17:20.185126Z","shell.execute_reply":"2023-07-30T13:17:20.191943Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"\"\"\"During inference, the model requires only the input tensors, and returns the \n   post-processed predictions as a List[Dict[Tensor]], one for each input image. The fields\n   of the Dict are as follows:\n   - boxes (FloatTensor[N, 4]): the predicted boxes in [x0, y0, x1, y1] format\n   - labels (Int64Tensor[N]): the predicted labels for each image\n   - scores (Tensor[N]): the scores or each prediction\"\"\"\n\nfig,axes=plt.subplots(4,2,figsize=(8,16))\nplt.subplots_adjust(wspace=0.1,hspace=0.1)\n\nimgs,targets=next(iter(train_dl))\nmodel.eval()\noutput=model([img.to(device) for img in imgs])\n\nfor i,idx in enumerate(range(len(imgs))):\n    img=imgs[idx]\n    predictions=output[idx]\n    \n    #real bounding boxes\n    show(img,targets[idx][\"boxes\"],axes[i,0],color=(0,255,0))\n    \n    #non-max suppression\n    #threshold=0.5\n    #iou_threshold=0.8\n    \"\"\"Non-max suppression is the final step of these object detection algorithms and is \n       used to select the most appropriate bounding box for the object.\n       The NMS takes two things into account\n        -The objectiveness score is given by the model\n        -The overlap or IOU of the bounding boxes\"\"\"\n    \n    pp_boxes=predictions[\"boxes\"][predictions[\"scores\"]>=threshold]\n    scores=predictions[\"scores\"][predictions[\"scores\"]>=threshold]\n    nms=torchvision.ops.nms(pp_boxes,scores,iou_threshold=iou_threshold)\n    pp_boxes=pp_boxes[nms]\n\n    \n    show(img,pp_boxes,axes[i,1]);\n    \nplt.savefig(\"4.png\")","metadata":{"execution":{"iopub.status.busy":"2023-07-30T13:17:20.454622Z","iopub.execute_input":"2023-07-30T13:17:20.455056Z","iopub.status.idle":"2023-07-30T13:17:25.055268Z","shell.execute_reply.started":"2023-07-30T13:17:20.455026Z","shell.execute_reply":"2023-07-30T13:17:25.052737Z"},"trusted":true},"execution_count":null,"outputs":[]}]}