{"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":"code","source":"import numpy as np\nimport os\nimport pandas as pd\nimport matplotlib.pyplot as plt\nimport cv2\nimport random\n\nimport sys\nsys.path.append('../input/tensorflow-great-barrier-reef')\n","metadata":{"_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# utilities declaration\nimport shutil\n\nfrom tqdm.notebook import tqdm # estimate and display the progress bar\ntqdm.pandas() # to use progress.apply\n\nfrom joblib import Parallel, delayed \n# parallel : readable parallel mapping\n# delayed : ","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"!pip install -qU wandb\n!pip install -qU bbox-utility ","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"from bbox.utils import coco2yolo, coco2voc, voc2yolo, draw_bboxes, load_image, clip_bbox, str2annot, annot2str","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"np.random.seed(32)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import wandb\n\ntry:\n    from kaggle_secrets import UserSecretsClient # kaggle api\n    user_secrets = UserSecretsClient()\n    api_key = user_secrets.get_secret(\"WANDB\")\n    wandb.login(key=api_key)\n    anonymous = None\nexcept:\n    wandb.login(anonymous='must')\n    print('To use your W&B account,\\nGo to Add-ons -> Secrets and provide your W&B access token. Use the Label name as WANDB. \\nGet your W&B access token from here: https://wandb.ai/authorize')","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# constant declaration\nIMAGE_SIZE = 2560\nFOLD = 4\nMODEL = 'yolov5s6'# yolov5, maybe using yolov5x yolov5l\nBATCH = 4\nEPOCHS = 30 # 40\n\nPROJECT = 'great-barrier-reef'\nNAME  = f'{MODEL}-dim{IMAGE_SIZE}-fold{FOLD}'\n\n# REMOVE_NOBOX = True\nROOT_DIR = '../input/tensorflow-great-barrier-reef'\nIMAGE_DIR = './images' # save images here for yolov5\nLABEL_DIR = './labels' # save labels here for yolov5\n","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# make parent directory if it does not exist and pass a directory to a terminal\n!mkdir -p {IMAGE_DIR}\n!mkdir -p {LABEL_DIR}","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df = pd.read_csv(f'{ROOT_DIR}/train.csv')\ndf['old_image_path'] = f'{ROOT_DIR}/train_images/video_' + df.video_id.astype(str) + '/' + df.video_frame.astype(str) + '.jpg'\ndf['image_path'] = f'{IMAGE_DIR}/' + df.image_id + '.jpg'\ndf['label_path'] = f'{LABEL_DIR}/' + df.image_id + '.txt'\ndf['annotations'] = df['annotations'].progress_apply(eval) # eval for evaluates the specified expression, insecure\n# still can't figure out what does this line do\n","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df['num_bbox'] = df['annotations'].progress_apply(lambda x : len(x)) # count number of boxes in each image\ndata = (df.num_bbox > 0).value_counts() # 2 types of number of boxes\nprint(f'No bounding box : {data[0] * 100/(data[0] + data[1]):0.2f}% | With bounding box : {data[1] * 100/(data[0] + data[1]) : 0.2f}%')","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"remove no bouding box images","metadata":{}},{"cell_type":"code","source":"len(df)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df = df.drop(df.query('num_bbox == 0').sample(frac=.95).index)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"len(df)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df['num_bbox'] = df['annotations'].progress_apply(lambda x : len(x)) # count number of boxes in each image\ndata = (df.num_bbox > 0).value_counts() # 2 types of number of boxes\nprint(f'No bounding box : {data[0] * 100/(data[0] + data[1]):0.2f}% | With bounding box : {data[1] * 100/(data[0] + data[1]) : 0.2f}%')","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"len(df)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"enhancement functions","metadata":{}},{"cell_type":"markdown","source":"experiment image enhancement","metadata":{}},{"cell_type":"code","source":"def recover_clahe(sceneRadiance) : # improvement of the above function \n    clahe = cv2.createCLAHE(clipLimit=7, tileGridSize=(14,14))\n    for i in range(3) : \n        sceneRadiance[:,:,i] = clahe.apply((sceneRadiance[:,:,i]))\n    return sceneRadiance\n","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def gamma_correction(img, gamma=1/0.6) : # gamma enhancement\n    R = 255.0\n    img = img.astype(np.uint32) / R\n    new_image = R * np.power(img, gamma)\n    return new_image.astype(np.uint8)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def plot_img(img_dir,num_items,func,mode):\n    img_list = random.sample(os.listdir(img_dir), num_items)\n\n    for i in range(len(img_list)):\n        full_path = img_dir + '/' + img_list[i]\n        img_temp1 = plt.imread(full_path)\n        img_temp_cv = cv2.imread(full_path)\n        plt.figure(figsize=(20,15))\n        plt.subplot(1,2,1)\n        plt.imshow(img_temp1);\n        plt.subplot(1,2,2)\n        if mode == 'plt':\n            plt.imshow(func(img_temp1));\n        elif mode == 'cv2':\n            plt.imshow(func(img_temp_cv));","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# vid_0_dir = \"../input/tensorflow-great-barrier-reef/train_images/video_0\"\n# num_items1 = 4\n# plot_img(vid_0_dir,num_items1,recover_clahe,\"cv2\")","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# vid_0_dir = \"../input/tensorflow-great-barrier-reef/train_images/video_0\"\n# num_items1 = 4\n# plot_img(vid_0_dir,num_items1,gamma_correction,\"cv2\")","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"need to copy the images to working since YOLOv5 need to write and /kaggle/input does not allow to do it","metadata":{}},{"cell_type":"code","source":"def plot_img2(img_dir,num_items,func, func2, mode):\n    img_list = random.sample(os.listdir(img_dir), num_items)\n\n    for i in range(len(img_list)):\n        full_path = img_dir + '/' + img_list[i]\n        img_temp1 = plt.imread(full_path)\n        img_temp_cv = cv2.imread(full_path)\n        plt.figure(figsize=(20,15))\n        plt.subplot(1,2,1)\n        plt.imshow(img_temp1);\n        plt.subplot(1,2,2)\n        if mode == 'plt':\n            plt.imshow(func(img_temp1));\n        elif mode == 'cv2':\n            plt.imshow(func2(func(img_temp_cv)));","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# plot_img2(vid_0_dir,num_items1,gamma_correction, recover_clahe,  \"cv2\")","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def image_enhancement(image):\n    return gamma_correction(recover_clahe(image))","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# def copy_files(row) : # copy after apply image enhancement \n#     # shutil.copyfile(row.old_image_path, row.image_path)\n#     img = cv2.imread(row.old_image_path)\n#     # img = image_enhancement(img)\n#     cv2.imwrite(row.image_path, img)\n#     return\ndef copy_files(row) : # copy after apply image enhancement \n    shutil.copyfile(row.old_image_path, row.image_path)\n    return","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"make this progress faster by using joblib which uses parralel computing","metadata":{}},{"cell_type":"code","source":"# image_paths = df.old_image_path.tolist() # why we use this\n_ = Parallel(n_jobs = -1, backend='threading')( delayed(copy_files)(row) for _, row in tqdm(df.iterrows(), total=len(df)) )\n\n# n_jobs = -1, using all the CPUs\n# function tqdm to display these progress bar\n# df.iterrows : DataFrame.iterrows is a generator which yields both the index and row (as a Series)\n# delayed of joblib : to delay the execution of functions\n## we'd like to call copy_files sometime later\n## Returned is the tuple (function, [arguments  without keywords] , {argument with keywords})\n## example : (delay, [row], {kwargs})\n\n# delayed(copy_files)(row) for _, row in tqdm(df.iterrows(), total=len(df)) : return list and pass to the parallel function\n","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def get_bbox(annots) : \n    bboxes = [list(annot.values()) for annot in annots]\n    return bboxes\n\ndef get_imgsize(row) : \n    row['width'], row['height'] = imagesize.get(row['image_path']) # where is imagesize\n    return row\n\ncolors = [(220,20,60)]","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df['bboxes'] = df.annotations.progress_apply(get_bbox)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df['width'] = 1280\ndf['height'] = 720\ndf.head()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"create labels files, convert from COCO format to YOLO format\nmust be normalized","metadata":{}},{"cell_type":"code","source":"cnt = 0 \nall_boxes = []\nbboxes_info = []\nfor index in tqdm(range(df.shape[0])) : \n    row = df.iloc[index] # iloc only works with index but loc can and vice versa\n    image_height = row.height\n    image_width = row.width \n    # print(row.bboxes) each row.bboxes have several bboxes\n    coco_bboxes = np.array(row.bboxes).astype(np.float32).copy() # convert to np array, change dtype \n    num_bbox = len(coco_bboxes) # num boxes in each image\n\n    names = ['cots'] * num_bbox # copy these array to number of num_bbbox\n    labels = np.array([0] * num_bbox)[..., None].astype(str) # name just a name, labels is marked from 0 to n \n    # [..., None] flatten these labels into a vector\n\n    with open(row.label_path, 'w') as f: # open each files to write\n        if num_bbox < 1 : \n            annot = '' # write nothing\n            f.write(annot) \n            cnt += 1 # count the number of wrong filter \n            continue \n        # has bbox case \n        voc_bboxes = coco2voc(coco_bboxes, image_height, image_width) # convert coco to voc\n        voc_bboxes = clip_bbox(voc_bboxes, image_height, image_width) # wtf \n        yolo_bboxes = voc2yolo(voc_bboxes, image_height, image_width).astype(str) # voc box to yolo format\n        all_boxes.extend(yolo_bboxes.astype(float)) # list extend\n        bboxes_info.extend([[row.image_id, row.video_id, row.sequence]] * len(yolo_bboxes)) # copy then \n        annots = np.concatenate([labels, yolo_bboxes], axis=1) # concatenate to the labels\n        string = annot2str(annots) # convert it into string then\n        f.write(string) # write it in the file\n\nprint('Missing :', cnt)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"from sklearn.model_selection import GroupKFold\nkf = GroupKFold(n_splits = 5) # 5-fold with sepcific sequence is split to validation set\ndf = df.reset_index(drop = True)\ndf['fold'] = -1 # garbage value, will be fix later\n# groups is df.sequence\nfor fold, (train_index, val_index) in enumerate(kf.split(df, y = df.video_id.tolist(), groups = df.sequence)) :\n    df.loc[val_index, 'fold'] = fold\ndf.fold.value_counts()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"bbox_df = pd.DataFrame(np.concatenate([bboxes_info, all_boxes], axis = 1), \n                      columns = ['image_id', 'video_id', 'sequence', 'xmid', 'ymid', 'w', 'h'])\nbbox_df[['xmid', 'ymid', 'w', 'h']] = bbox_df[['xmid', 'ymid', 'w', 'h']].astype(float)\nbbox_df['area'] = bbox_df.w * bbox_df.h * 1280 * 720 # calculate the area\nbbox_df = bbox_df.merge(df[['image_id','fold']], on='image_id', how='left')\nbbox_df.head(2)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"from scipy.stats import gaussian_kde\n\nall_boxes = np.array(all_boxes)\n\nx_val = all_boxes[...,0]\ny_val = all_boxes[...,1]\n\n# Calculate the point density\nxy = np.vstack([x_val,y_val])\nz = gaussian_kde(xy)(xy)\n\nfig, ax = plt.subplots(figsize = (10, 10))\nax.scatter(x_val, y_val, c=z, s=100, cmap='viridis')\nplt.show()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"x_val = all_boxes[...,2]\ny_val = all_boxes[...,3]\n\n# Calculate the point density\nxy = np.vstack([x_val,y_val])\nz = gaussian_kde(xy)(xy)\n\nfig, ax = plt.subplots(figsize = (10, 10))\n# ax.axis('off')\nax.scatter(x_val, y_val, c=z, s=100, cmap='viridis')\n# ax.set_xlabel('bbox_width')\n# ax.set_ylabel('bbox_height')\nplt.show()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import matplotlib as mpl\nimport seaborn as sns\n\nf, ax = plt.subplots(figsize=(12, 6))\nsns.despine(f)\n\nsns.histplot(\n    bbox_df,\n    x=\"area\", hue=\"fold\",\n    multiple=\"stack\",\n    palette=\"viridis\",\n    edgecolor=\".3\",\n    linewidth=.5,\n    log_scale=True,\n)\nax.xaxis.set_major_formatter(mpl.ticker.ScalarFormatter())\nax.set_xticks([500, 1000, 2000, 5000, 10000]);","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df2 = df[(df.num_bbox>0)].sample(100) # takes samples with bbox\ny = 3; x = 2\nplt.figure(figsize=(12.8*x, 7.2*y))\nfor idx in range(x*y):\n    row = df2.iloc[idx]\n    img           = load_image(row.image_path)\n    image_height  = row.height\n    image_width   = row.width\n    with open(row.label_path) as f:\n        annot = str2annot(f.read())\n    bboxes_yolo = annot[...,1:]\n    labels      = annot[..., 0].astype(int).tolist()\n    names         = ['cots'] * len(bboxes_yolo)\n    plt.subplot(y, x, idx+1)\n    plt.imshow(draw_bboxes(img = img,\n                           bboxes = bboxes_yolo, \n                           classes = names,\n                           class_ids = labels,\n                           class_name = True, \n                           colors = colors, \n                           bbox_format = 'yolo',\n                           line_thickness = 2))\n    plt.axis('OFF')\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train_files = []\nval_files = []\ntrain_df = df.query('fold!=@FOLD') #wtf\nvalid_df = df.query('fold==@FOLD')\ntrain_files += list(train_df.image_path.unique())\nval_files += list(valid_df.image_path.unique())\nlen(train_files), len(val_files)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import yaml\n\ncwd = '/kaggle/working/'\n\nwith open(os.path.join(cwd, 'train.txt'), 'w') as f: # write all train image directory in \n    for path in train_df.image_path.tolist() : \n        f.write(path + '\\n')\n\nwith open(os.path.join(cwd, 'val.txt'), 'w') as f : \n    for path in valid_df.image_path.tolist() : \n        f.write(path + '\\n')\n        \ndata = dict(\n    path = '/kaggle/working',\n    train = os.path.join(cwd, 'train.txt'),\n    val = os.path.join(cwd, 'val.txt'),\n    nc = 1, # num classes\n    name = ['cots']\n)\n\nwith open(os.path.join(cwd, 'gbr.yaml'), 'w') as output_file : \n    yaml.dump(data, output_file, default_flow_style = False)\n    \nf = open(os.path.join(cwd, 'gbr.yaml'), 'r')\nprint('yaml:')\nprint(f.read())","metadata":{"scrolled":true,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"%%writefile /kaggle/working/hyp.yaml\nlr0: 0.01  # initial learning rate (SGD=1E-2, Adam=1E-3)\nlrf: 0.1  # final OneCycleLR learning rate (lr0 * lrf)\nmomentum: 0.937  # SGD momentum/Adam beta1\nweight_decay: 0.0005  # optimizer weight decay 5e-4\nwarmup_epochs: 3.0  # warmup epochs (fractions ok)\nwarmup_momentum: 0.8  # warmup initial momentum\nwarmup_bias_lr: 0.1  # warmup initial bias lr\nbox: 0.05  # box loss gain\ncls: 0.5  # cls loss gain\ncls_pw: 1.0  # cls BCELoss positive_weight\nobj: 1.0  # obj loss gain (scale with pixels)\nobj_pw: 1.0  # obj BCELoss positive_weight\niou_t: 0.20  # IoU training threshold\nanchor_t: 4.0  # anchor-multiple threshold\n# anchors: 3  # anchors per output layer (0 to ignore)\nfl_gamma: 0.0  # focal loss gamma (efficientDet default gamma=1.5)\nhsv_h: 0.015  # image HSV-Hue augmentation (fraction)\nhsv_s: 0.7  # image HSV-Saturation augmentation (fraction)\nhsv_v: 0.4  # image HSV-Value augmentation (fraction)\ndegrees: 0.30  # image rotation (+/- deg)\ntranslate: 0.10  # image translation (+/- fraction)\nscale: 0.10  # image scale (+/- gain)\nshear: 2.0  # image shear (+/- deg)\nperspective: 0.0  # image perspective (+/- fraction), range 0-0.001\nflipud: 0.0  # image flip up-down (probability)\nfliplr: 0.5  # image flip left-right (probability)\nmosaic: 0.2  # image mosaic (probability)\nmixup: 0.5 # image mixup (probability)\ncopy_paste: 0.0  # segment copy-paste (probability)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"%cd /kaggle/working\n!rm -r /kaggle/working/yolov5\n! git clone https://github.com/ultralytics/yolov5 # clone\n!cp -r /kaggle/input/yolov5-lib-ds /kaggle/working/yolov5\n%cd yolov5\n%pip install -qr requirements.txt  # install\n\nfrom yolov5 import utils\ndisplay = utils.notebook_init()  # check","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"train yolov5","metadata":{}},{"cell_type":"code","source":"!python train.py --img {IMAGE_SIZE} \\\n--batch {BATCH}\\\n--epochs {EPOCHS}\\\n--data /kaggle/working/gbr.yaml\\\n--hyp /kaggle/working/hyp.yaml\\\n--weights {MODEL}.pt\\\n--project {PROJECT} --name {NAME}\\\n--freeze 9 \\\n--exist-ok","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"OUTPUT_DIR = '{}/{}'.format(PROJECT, NAME)\n!ls {OUTPUT_DIR}","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import matplotlib.pyplot as plt\nplt.figure(figsize = (10, 10))\nplt.imshow(plt.imread(f'{OUTPUT_DIR}/train_batch0.jpg'))\n\nplt.figure(figsize = (10, 10))\nplt.imshow(plt.imread(f'{OUTPUT_DIR}/train_batch1.jpg'))\n\nplt.figure(figsize = (10, 10))\nplt.imshow(plt.imread(f'{OUTPUT_DIR}/train_batch2.jpg'))","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"fig, ax = plt.subplots(3, 2, figsize = (2*9,3*5), constrained_layout = True)\nfor row in range(3):\n    ax[row][0].imshow(plt.imread(f'{OUTPUT_DIR}/val_batch{row}_labels.jpg'))\n    ax[row][0].set_xticks([])\n    ax[row][0].set_yticks([])\n    ax[row][0].set_title(f'{OUTPUT_DIR}/val_batch{row}_labels.jpg', fontsize = 12)\n    \n    ax[row][1].imshow(plt.imread(f'{OUTPUT_DIR}/val_batch{row}_pred.jpg'))\n    ax[row][1].set_xticks([])\n    ax[row][1].set_yticks([])\n    ax[row][1].set_title(f'{OUTPUT_DIR}/val_batch{row}_pred.jpg', fontsize = 12)\nplt.show()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"plt.figure(figsize=(30,15))\nplt.axis('off')\nplt.imshow(plt.imread(f'{OUTPUT_DIR}/results.png'));","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"plt.figure(figsize=(12,10))\nplt.axis('off')\nplt.imshow(plt.imread(f'{OUTPUT_DIR}/confusion_matrix.png'));","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"for metric in ['F1', 'PR', 'P', 'R']:\n    print(f'Metric: {metric}')\n    plt.figure(figsize=(12,10))\n    plt.axis('off')\n    plt.imshow(plt.imread(f'{OUTPUT_DIR}/{metric}_curve.png'));\n    plt.show()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"- read the paper \n- load pretrain : done\n- check the out-bound and not normalized box : done\n- fine tune : done\n- yolo5x out of memory => yolov5l : done\n- 0-10% background image : done\n- save model and get the submission from it : done\n- large image size, done but not experiment yet\n- little bit tune in number of freeze layer \n","metadata":{}},{"cell_type":"code","source":"os.chdir(\"/kaggle/\")","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"!ls\n%cd working\n!ls","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"! rm -r images\n! rm -r labels","metadata":{"trusted":true},"execution_count":null,"outputs":[]}]}