{"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":"# Sartorious- Torch Mask R-CNN","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19"}},{"cell_type":"markdown","source":"In this notebook our `Task is to Perform Instance Segmentation of Neuronal Cells`.\n\n* This Notebook is for who want to learn how to Proceed for this task and I will try to explain intuition behind Everything that is to be coded in the Notebook.\n* I will mention all my Learnings here as  I Proceed in the Notebook.\n* In this notebook we will start with the Visualization of the Dataset and then Proceed for Modeling with MASK RCNN model.","metadata":{}},{"cell_type":"markdown","source":"***I will recommend the learners to code side by side and learn how each & everything Works. Trust me this is the best way to learn if You wanna a Learn.***","metadata":{}},{"cell_type":"markdown","source":"*** References that I have used for this notebook***\n* [Pytorch MASK RCNN FINETUNING TUTORIAL officila docs](https://pytorch.org/tutorials/intermediate/torchvision_tutorial.html)\n* [https://www.kaggle.com/julian3833/sartorius-starter-torch-mask-r-cnn-lb-0-273/notebook](https://www.kaggle.com/julian3833/sartorius-starter-torch-mask-r-cnn-lb-0-273/notebook)\n* [https://www.kaggle.com/ishandutta/sartorius-indepth-eda-explanation-model/](https://www.kaggle.com/ishandutta/sartorius-indepth-eda-explanation-model/)","metadata":{}},{"cell_type":"markdown","source":"***Let's Start***","metadata":{}},{"cell_type":"markdown","source":"# Imports","metadata":{}},{"cell_type":"code","source":"import os\nimport time\nimport random\nimport collections\nimport cv2\n\nimport numpy as np\nimport pandas as pd\nfrom PIL import Image\nimport matplotlib.pyplot as plt\nfrom sklearn.model_selection import train_test_split\nimport itertools\n\nimport plotly.express as px\n\nimport torch\nimport torchvision\nfrom torchvision.transforms import ToPILImage\nfrom torchvision.transforms import functional as F\nfrom torch.utils.data import Dataset, DataLoader\nfrom torchvision.models.detection.faster_rcnn import FastRCNNPredictor\nfrom torchvision.models.detection.mask_rcnn import MaskRCNNPredictor\n\nfrom tqdm.notebook import tqdm\n\nimport warnings\nwarnings.simplefilter('ignore')\n\n# Activate pandas progress apply bar\ntqdm.pandas()","metadata":{"execution":{"iopub.status.busy":"2021-12-07T13:41:23.868844Z","iopub.execute_input":"2021-12-07T13:41:23.869154Z","iopub.status.idle":"2021-12-07T13:41:28.680510Z","shell.execute_reply.started":"2021-12-07T13:41:23.869119Z","shell.execute_reply":"2021-12-07T13:41:28.679451Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Fix Randomness","metadata":{}},{"cell_type":"code","source":"def fix_all_seeds(seed):\n    np.random.seed(seed)\n    random.seed(seed)\n    os.environ['PYTHONHASHSEED']=str(seed)\n    torch.manual_seed(seed)\n    torch.cuda.manual_seed(seed)\n    torch.cuda.manual_seed_all(seed)\n    \nfix_all_seeds(2001)","metadata":{"execution":{"iopub.status.busy":"2021-12-07T13:41:28.682090Z","iopub.execute_input":"2021-12-07T13:41:28.682589Z","iopub.status.idle":"2021-12-07T13:41:28.691530Z","shell.execute_reply.started":"2021-12-07T13:41:28.682551Z","shell.execute_reply":"2021-12-07T13:41:28.690872Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Configuration","metadata":{}},{"cell_type":"markdown","source":"Let's to do some General configurations","metadata":{}},{"cell_type":"code","source":" class config:\n    TRAIN_CSV = \"../input/sartorius-cell-instance-segmentation/train.csv\"\n    TRAIN_PATH = \"../input/sartorius-cell-instance-segmentation/train\"\n    TEST_PATH = \"../input/sartorius-cell-instance-segmentation/test\"\n    TRAIN_SEMI_SUPERVISED_PATH=\"../input/sartorius-cell-instance-segmentation/train_semi_supervised\"\n    \n    WIDTH = 704\n    HEIGHT = 520\n    \n    # Reduced the train dataset to 5000 rows\n    TEST = False\n    \n    DEVICE = torch.device('cuda') if torch.cuda.is_available() else torch.device('cpu')\n    \n    RESNET_MEAN = (0.485, 0.456, 0.406)\n    RESNET_STD = (0.229, 0.224, 0.225)\n    \n    IMAGE_RESIZE=(224,224)\n    \n    BATCH_SIZE = 2\n    \n    # No changes tried with the optimizer yet.\n    MOMENTUM = 0.9\n    LEARNING_RATE = 0.001\n    WEIGHT_DECAY = 0.0005\n    \n    # Changes the confidence required for a pixel to be kept for a mask. \n    # Only used 0.5 till now.\n    MASK_THRESHOLD = 0.5\n    \n    # Normalize to resnet mean and std if True.\n    NORMALIZE = False \n    \n    \n    # Use a StepLR scheduler if True. Not tried yet.\n    USE_SCHEDULER = False\n\n    # Number of epochs\n    NUM_EPOCHS = 8\n    \n    \n    BOX_DETECTIONS_PER_IMG = 539\n    \n    \n    MIN_SCORE = 0.59","metadata":{"execution":{"iopub.status.busy":"2021-12-07T07:55:26.557921Z","iopub.execute_input":"2021-12-07T07:55:26.558207Z","iopub.status.idle":"2021-12-07T07:55:26.567157Z","shell.execute_reply.started":"2021-12-07T07:55:26.558154Z","shell.execute_reply":"2021-12-07T07:55:26.566456Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# LOAD DATASET","metadata":{}},{"cell_type":"markdown","source":"Let's do Some Exploration of dataset","metadata":{}},{"cell_type":"markdown","source":"### Goal of Competition\nIn this competition we are segmenting neuronal cells in images. The training annotations are provided as run length encoded masks, and the images are in PNG format. `The number of images is small, but the number of annotated objects is quite high. The hidden test set is roughly 240 images.`\n\n### Files\ntrain.csv - IDs and masks for all training objects. None of this metadata is provided for the test set.\n\n* id - unique identifier for object\n* annotation - run length encoded pixels for the identified neuronal cell\n* width - source image width\n* height - source image height\n* cell_type - the cell line\n* plate_time - time plate was created\n* sample_date - date sample was created\n* sample_id - sample identifier\n* elapsed_timedelta - time since first image taken of sample\n\n***sample_submission.csv*** - a sample submission file in the correct format\n\n***train*** - train images in PNG format\n\n***test*** - test images in PNG format. Only a few test set images are available for download; the remainder can only be accessed by your notebooks when you submit.\n\n***train_semi_supervised*** - unlabeled images offered in case you want to use additional data for a semi-supervised approach.\n\n***LIVECell_dataset_2021*** - A mirror of the data from the LIVECell dataset. LIVECell is the predecessor dataset to this competition. You will find extra data for the SH-SHY5Y cell line, plus several other cell lines not covered in the competition dataset that may be of interest for transfer learning.","metadata":{}},{"cell_type":"code","source":"df_train=pd.read_csv(config.TRAIN_CSV, nrows=5000 if config.TEST else None)","metadata":{"execution":{"iopub.status.busy":"2021-12-07T07:55:26.568502Z","iopub.execute_input":"2021-12-07T07:55:26.568739Z","iopub.status.idle":"2021-12-07T07:55:26.909971Z","shell.execute_reply.started":"2021-12-07T07:55:26.568708Z","shell.execute_reply":"2021-12-07T07:55:26.909248Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df_train.shape","metadata":{"execution":{"iopub.status.busy":"2021-12-07T07:55:26.913618Z","iopub.execute_input":"2021-12-07T07:55:26.914038Z","iopub.status.idle":"2021-12-07T07:55:26.919130Z","shell.execute_reply.started":"2021-12-07T07:55:26.914004Z","shell.execute_reply":"2021-12-07T07:55:26.918479Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df_train.info()","metadata":{"execution":{"iopub.status.busy":"2021-12-07T07:55:26.920661Z","iopub.execute_input":"2021-12-07T07:55:26.921136Z","iopub.status.idle":"2021-12-07T07:55:26.988479Z","shell.execute_reply.started":"2021-12-07T07:55:26.921099Z","shell.execute_reply":"2021-12-07T07:55:26.987791Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"`getImagePaths` is  a simple function which help you to get the images path from the given dataset.","metadata":{}},{"cell_type":"code","source":"def getImagePaths(path):\n    \"\"\"\n    Function to Combine Directory Path with individual Image Paths\n    \n    parameters: path(string) - Path of directory\n    returns: image_names(string) - Full Image Path\n    \"\"\"\n    image_names=[]\n    for dirname,_,filenames in os.walk(path):\n        for filename in tqdm(filenames):\n            fullpath=os.path.join(dirname,filename)\n            image_names.append(fullpath)\n    return image_names","metadata":{"execution":{"iopub.status.busy":"2021-12-07T07:55:26.989966Z","iopub.execute_input":"2021-12-07T07:55:26.990141Z","iopub.status.idle":"2021-12-07T07:55:26.997971Z","shell.execute_reply.started":"2021-12-07T07:55:26.990118Z","shell.execute_reply":"2021-12-07T07:55:26.997265Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#Get complete image paths for train and test datasets\ntrain_images_path = getImagePaths(config.TRAIN_PATH)\ntest_images_path = getImagePaths(config.TEST_PATH)\ntrain_semi_supervised_path = getImagePaths(config.TRAIN_SEMI_SUPERVISED_PATH)","metadata":{"execution":{"iopub.status.busy":"2021-12-07T07:55:26.999399Z","iopub.execute_input":"2021-12-07T07:55:26.999812Z","iopub.status.idle":"2021-12-07T07:55:27.385826Z","shell.execute_reply.started":"2021-12-07T07:55:26.999783Z","shell.execute_reply":"2021-12-07T07:55:27.385171Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# UNique values in each column\nfor col in df_train.columns:\n    print(col+\": \"+str(len(df_train[col].unique())))","metadata":{"execution":{"iopub.status.busy":"2021-12-07T07:55:27.387114Z","iopub.execute_input":"2021-12-07T07:55:27.387571Z","iopub.status.idle":"2021-12-07T07:55:27.480670Z","shell.execute_reply.started":"2021-12-07T07:55:27.387536Z","shell.execute_reply":"2021-12-07T07:55:27.479914Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"This shows that\n* there is only a single size image\n* there are 3 types of cell","metadata":{"execution":{"iopub.status.busy":"2021-12-04T10:15:16.913156Z","iopub.execute_input":"2021-12-04T10:15:16.913718Z","iopub.status.idle":"2021-12-04T10:15:16.918657Z","shell.execute_reply.started":"2021-12-04T10:15:16.913682Z","shell.execute_reply":"2021-12-04T10:15:16.917708Z"}}},{"cell_type":"code","source":"print(df_train['cell_type'].unique())","metadata":{"execution":{"iopub.status.busy":"2021-12-07T07:55:27.481935Z","iopub.execute_input":"2021-12-07T07:55:27.482161Z","iopub.status.idle":"2021-12-07T07:55:27.492483Z","shell.execute_reply.started":"2021-12-07T07:55:27.482130Z","shell.execute_reply":"2021-12-07T07:55:27.491573Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"print(df_train['width'].unique())\nprint(df_train['height'].unique())","metadata":{"execution":{"iopub.status.busy":"2021-12-07T07:55:27.494059Z","iopub.execute_input":"2021-12-07T07:55:27.494461Z","iopub.status.idle":"2021-12-07T07:55:27.501973Z","shell.execute_reply.started":"2021-12-07T07:55:27.494427Z","shell.execute_reply":"2021-12-07T07:55:27.501238Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"So , all images are of size 704*520.","metadata":{}},{"cell_type":"code","source":"# images in each directory\nprint(f\"Number of train images: {len(train_images_path)}\")\nprint(f\"Number of test images:  {len(test_images_path)}\")","metadata":{"execution":{"iopub.status.busy":"2021-12-07T07:55:27.503421Z","iopub.execute_input":"2021-12-07T07:55:27.503970Z","iopub.status.idle":"2021-12-07T07:55:27.508865Z","shell.execute_reply.started":"2021-12-07T07:55:27.503933Z","shell.execute_reply":"2021-12-07T07:55:27.508158Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"There  are 606 training images but for a particular image there are many annotaions present for the cells.","metadata":{}},{"cell_type":"markdown","source":"# Distribtion Plots","metadata":{}},{"cell_type":"code","source":"def plot_distribution(x):\n    \"\"\"\n    This function will Plot the distribution according to column\n    \"\"\"\n    \n    fig = px.histogram(\n    df_train, \n    x = x,\n    width = 800,\n    height = 500,\n    )\n    \n    fig.show()","metadata":{"execution":{"iopub.status.busy":"2021-12-07T07:55:27.510377Z","iopub.execute_input":"2021-12-07T07:55:27.510980Z","iopub.status.idle":"2021-12-07T07:55:27.517075Z","shell.execute_reply.started":"2021-12-07T07:55:27.510826Z","shell.execute_reply":"2021-12-07T07:55:27.516374Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Cell Type Distribution","metadata":{}},{"cell_type":"code","source":"plot_distribution('cell_type')","metadata":{"execution":{"iopub.status.busy":"2021-12-07T07:55:27.521170Z","iopub.execute_input":"2021-12-07T07:55:27.521465Z","iopub.status.idle":"2021-12-07T07:55:38.432845Z","shell.execute_reply.started":"2021-12-07T07:55:27.521433Z","shell.execute_reply":"2021-12-07T07:55:38.432222Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"`shsy5y` cell_type is present in large number as compared to others cell type.","metadata":{}},{"cell_type":"markdown","source":"### Plate Time Distribution","metadata":{}},{"cell_type":"code","source":"plot_distribution('plate_time')","metadata":{"execution":{"iopub.status.busy":"2021-12-07T07:55:38.433799Z","iopub.execute_input":"2021-12-07T07:55:38.434125Z","iopub.status.idle":"2021-12-07T07:55:38.989995Z","shell.execute_reply.started":"2021-12-07T07:55:38.434095Z","shell.execute_reply":"2021-12-07T07:55:38.989166Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Elapsed TimeDelta Distribution","metadata":{}},{"cell_type":"code","source":"plot_distribution('elapsed_timedelta')","metadata":{"execution":{"iopub.status.busy":"2021-12-07T07:55:38.991490Z","iopub.execute_input":"2021-12-07T07:55:38.991735Z","iopub.status.idle":"2021-12-07T07:55:39.713612Z","shell.execute_reply.started":"2021-12-07T07:55:38.991702Z","shell.execute_reply":"2021-12-07T07:55:39.712942Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Image View","metadata":{}},{"cell_type":"code","source":"def display_multiple_img(images_paths,rows,cols):\n    \"\"\"\n    Function to Display Images from Dataset.\n    \n    parameters: images_path(string) - Paths of Images to be displayed\n                rows(int) - No. of Rows in Output\n                cols(int) - No. of Columns in Output\n    \"\"\"\n    \n    figure, ax=plt.subplots(nrows=rows,ncols=cols,figsize=(18,12))\n    for ind,image_path in enumerate(images_paths):\n        image=cv2.imread(image_path)\n        image=cv2.cvtColor(image,cv2.COLOR_BGR2RGB) # Converts an image from one color space to another.\n        try:\n            ax.ravel()[ind].imshow(image)\n            ax.ravel()[ind].set_axis_off()\n        except:\n            continue;\n    plt.tight_layout()\n    plt.show()","metadata":{"execution":{"iopub.status.busy":"2021-12-07T07:55:39.714750Z","iopub.execute_input":"2021-12-07T07:55:39.715071Z","iopub.status.idle":"2021-12-07T07:55:39.722362Z","shell.execute_reply.started":"2021-12-07T07:55:39.715041Z","shell.execute_reply":"2021-12-07T07:55:39.721369Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Training Images","metadata":{}},{"cell_type":"code","source":"display_multiple_img(train_images_path[100:150], 5, 5)","metadata":{"execution":{"iopub.status.busy":"2021-12-07T07:55:39.723731Z","iopub.execute_input":"2021-12-07T07:55:39.724487Z","iopub.status.idle":"2021-12-07T07:55:42.202300Z","shell.execute_reply.started":"2021-12-07T07:55:39.724451Z","shell.execute_reply":"2021-12-07T07:55:42.201673Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Training Semi Supervised Images","metadata":{}},{"cell_type":"code","source":"display_multiple_img(train_semi_supervised_path[100:125], 5, 5)","metadata":{"execution":{"iopub.status.busy":"2021-12-07T07:55:42.203461Z","iopub.execute_input":"2021-12-07T07:55:42.203798Z","iopub.status.idle":"2021-12-07T07:55:44.775411Z","shell.execute_reply.started":"2021-12-07T07:55:42.203766Z","shell.execute_reply":"2021-12-07T07:55:44.774788Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Test Images","metadata":{}},{"cell_type":"code","source":"display_multiple_img(test_images_path, 1, 3)","metadata":{"execution":{"iopub.status.busy":"2021-12-07T07:55:44.776597Z","iopub.execute_input":"2021-12-07T07:55:44.777240Z","iopub.status.idle":"2021-12-07T07:55:45.242780Z","shell.execute_reply.started":"2021-12-07T07:55:44.777203Z","shell.execute_reply":"2021-12-07T07:55:45.242181Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Mask Plots","metadata":{}},{"cell_type":"markdown","source":"***NOTE:*** In order to reduce the submission file size, our metric uses run-length encoding on the pixel values. Instead of submitting an exhaustive list of indices for your segmentation, you will submit pairs of values that contain a start position and a run length. E.g. '1 3' implies starting at pixel 1 and running a total of 3 pixels (1,2,3).","metadata":{}},{"cell_type":"markdown","source":"Let's understand by example what does rle_decode function do:\n\nmask_rle=\"23 3 28 6\"\n\ns=[23, 3, 28, 6]\n\nstarts=[23, 28]\n\nlengths=[3, 6]\n\nends=[26,34]\n\nNow, img[start:end]=1  # assign mask for obejct","metadata":{}},{"cell_type":"code","source":"def rle_decode(mask_rle,shape,color=1):\n    '''\n    mask_rle: run-length as string formated (start length)\n    shape: (height, width, channels) of array to return \n    color: color for the mask\n    Returns numpy array (mask)\n\n    '''\n    \n    s=mask_rle.split()\n    \n    starts=list(map(lambda x: int(x) -1, s[0::2])) # start array which contain list of start indices\n    lengths=list(map(int, s[1::2]))\n    ends=[x+y for x,y in zip(starts,lengths)]\n    \n    img=np.zeros((shape[0]*shape[1],shape[2]),dtype=np.float32)\n    \n    for start,end in zip(starts,ends):\n        img[start:end]=color\n        \n    return img.reshape(shape)\n    ","metadata":{"execution":{"iopub.status.busy":"2021-12-07T07:55:45.244139Z","iopub.execute_input":"2021-12-07T07:55:45.244577Z","iopub.status.idle":"2021-12-07T07:55:45.251972Z","shell.execute_reply.started":"2021-12-07T07:55:45.244542Z","shell.execute_reply":"2021-12-07T07:55:45.251328Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def build_masks(df_train,image_id,input_shape):\n    '''\n    This function is used to build mask from the annotations.\n    As we are given with only annotations\n    We have to build the mask from the annotation for a particular image_id\n    '''\n    height, width = input_shape\n    labels=df_train[df_train[\"id\"]==image_id][\"annotation\"].tolist()\n    mask=np.zeros((height,width))\n    for label in labels:\n        mask += rle_decode(label, shape=(height, width))\n    mask+=mask.clip(0,1)   #Clip (limit) the values in an array.\n    return mask\n\n\ndef plot_masks(image_id,colors=True):\n    '''\n    This function is simply used to plot a mask for particular image_id\n    '''\n    labels=df_train[df_train[\"id\"]==image_id][\"annotation\"].tolist()\n    cell_type=df_train[df_train[\"id\"]==image_id][\"cell_type\"].tolist()\n    cmap={\"shsy5y\":(0,0,255),\"astro\":(0,255,0),\"cort\":(255,0,0)}\n    \n    if colors:\n        mask=np.zeros((520,704,3))\n        for label,cell_type in zip(labels,cell_type):\n            c=cmap[cell_type]\n            mask+=rle_decode(label,shape=(520,704,3),color=c)\n    else:\n        mask=np.zeros((520,704,1))\n        for label in labels:\n            mask += rle_decode(label, shape=(520, 704, 1))\n            \n    mask = mask.clip(0, 1)\n    \n    image = cv2.imread(f\"../input/sartorius-cell-instance-segmentation/train/{image_id}.png\")\n    image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)\n\n    plt.figure(figsize=(16, 32))\n    plt.subplot(3, 1, 1)\n    plt.imshow(image)\n    plt.axis(\"off\")\n    plt.subplot(3, 1, 2)\n    plt.imshow(image)\n    plt.imshow(mask, alpha=0.5)\n    plt.axis(\"off\")\n    plt.subplot(3, 1, 3)\n    plt.imshow(mask)\n    plt.axis(\"off\")\n    \n    plt.show();","metadata":{"execution":{"iopub.status.busy":"2021-12-07T07:55:45.253338Z","iopub.execute_input":"2021-12-07T07:55:45.253768Z","iopub.status.idle":"2021-12-07T07:55:45.267914Z","shell.execute_reply.started":"2021-12-07T07:55:45.253733Z","shell.execute_reply":"2021-12-07T07:55:45.267066Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"plot_masks(\"ffdb3cc02eef\", colors=False)","metadata":{"execution":{"iopub.status.busy":"2021-12-07T07:55:45.270689Z","iopub.execute_input":"2021-12-07T07:55:45.270871Z","iopub.status.idle":"2021-12-07T07:55:46.152683Z","shell.execute_reply.started":"2021-12-07T07:55:45.270849Z","shell.execute_reply":"2021-12-07T07:55:46.149508Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Transformations","metadata":{}},{"cell_type":"markdown","source":"Done some Transfromations on the image\n\n* Horizontal and Vertical Flip for now.\n\n* Normalization to Resnet's mean and std can be performed using the parameter NORMALIZE in the top cell. [You can test it by switching ON or OFF NORMALIZE in config.]\n\n* The first 3 transformations come from this utils package by Abishek, VerticalFlip is my adaption of HorizontalFlip.","metadata":{}},{"cell_type":"code","source":"# These are slight redefinitions of torch.transformation classes\n# The difference is that they handle the target and the mask\n# Copied from Abishek, added new ones\nclass Compose:\n    def __init__(self, transforms):\n        self.transforms = transforms\n\n    def __call__(self, image, target):\n        for t in self.transforms:\n            image, target = t(image, target)\n        return image, target\n\nclass VerticalFlip:\n    def __init__(self, prob):\n        self.prob = prob\n\n    def __call__(self, image, target):\n        if random.random() < self.prob:\n            height, width = image.shape[-2:]\n            image = image.flip(-2)\n            bbox = target[\"boxes\"]\n            bbox[:, [1, 3]] = height - bbox[:, [3, 1]]\n            target[\"boxes\"] = bbox\n            target[\"masks\"] = target[\"masks\"].flip(-2)\n        return image, target\n\nclass HorizontalFlip:\n    def __init__(self, prob):\n        self.prob = prob\n\n    def __call__(self, image, target):\n        if random.random() < self.prob:\n            height, width = image.shape[-2:]\n            image = image.flip(-1)\n            bbox = target[\"boxes\"]\n            bbox[:, [0, 2]] = width - bbox[:, [2, 0]]\n            target[\"boxes\"] = bbox\n            target[\"masks\"] = target[\"masks\"].flip(-1)\n        return image, target\n\nclass Normalize:\n    def __call__(self, image, target):\n        image = F.normalize(image, config.RESNET_MEAN, config.RESNET_STD)\n        return image, target\n\nclass ToTensor:\n    def __call__(self, image, target):\n        image = F.to_tensor(image)\n        return image, target\n    \n\ndef get_transform(train):\n    transforms = [ToTensor()]\n    if config.NORMALIZE:\n        transforms.append(Normalize())\n    \n    # Data augmentation for train\n    if train: \n        transforms.append(HorizontalFlip(0.5))\n        transforms.append(VerticalFlip(0.5))\n\n    return Compose(transforms)","metadata":{"execution":{"iopub.status.busy":"2021-12-07T07:55:46.154114Z","iopub.execute_input":"2021-12-07T07:55:46.154575Z","iopub.status.idle":"2021-12-07T07:55:46.172220Z","shell.execute_reply.started":"2021-12-07T07:55:46.154537Z","shell.execute_reply":"2021-12-07T07:55:46.171458Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Training Dataset and Dataloader","metadata":{}},{"cell_type":"markdown","source":"### For training Mask R-CNN following things need to be taken care of:\n\n***Mask R-CNN***\n\n\nThe input to the model is expected to be a list of tensors, each of shape [C, H, W], one for each image, and should be in 0-1 range. Different images can have different sizes.\n\nThe behavior of the model changes depending if it is in training or evaluation mode.\n\nDuring training, the model expects both the input tensors, as well as a targets (list of dictionary), containing:\n\n* boxes (FloatTensor[N, 4]): the ground-truth boxes in [x1, y1, x2, y2] format, with 0 <= x1 < x2 <= W and 0 <= y1 < y2 <= H.\n* labels (Int64Tensor[N]): the class label for each ground-truth box\n* masks (UInt8Tensor[N, H, W]): the segmentation binary masks for each instance\n\nThe model returns a Dict[Tensor] during training, containing the classification and regression losses for both the RPN and the R-CNN, and the mask loss.\n\nDuring inference, the model requires only the input tensors, and returns the post-processed predictions as a List[Dict[Tensor]], one for each input image. The fields of the Dict are as follows, where N is the number of detected instances:\n\n* boxes (FloatTensor[N, 4]): the predicted boxes in [x1, y1, x2, y2] format, with 0 <= x1 < x2 <= W and 0 <= y1 < y2 <= H.\n* labels (Int64Tensor[N]): the predicted labels for each instance\n* scores (Tensor[N]): the scores or each instance\n* masks (UInt8Tensor[N, 1, H, W]): the predicted masks for each instance, in 0-1 range. In order to obtain the final segmentation masks, the soft masks can be thresholded, generally with a value of 0.5 (mask >= 0.5)\n\n\n[Read the Pytorch docs](https://pytorch.org/vision/stable/models.html#object-detection-instance-segmentation-and-person-keypoint-detection)","metadata":{}},{"cell_type":"markdown","source":"[Go to this docs of finetuning Mask RCNN](https://pytorch.org/tutorials/intermediate/torchvision_tutorial.html)","metadata":{}},{"cell_type":"code","source":"class CellDataset(Dataset):\n    def __init__(self,image_dir,df,transforms=None,resize=False):\n        self.transforms=transforms\n        self.image_dir=image_dir\n        self.df=df\n        \n        self.should_resize=resize is not False\n        # resize height and width of image\n        if self.should_resize:\n            self.height=int(config.HEIGHT*resize)\n            self.width=int(config.WIDTH*resize)\n        else:\n            self.height=config.HEIGHT\n            self.width=config.WIDTH\n            \n        # Creating a default dict - image_info\n        # default dict can never raises key error\n        # It provides a default value for the key that does not exists.\n        self.image_info=collections.defaultdict(dict)  \n        # temp_df contain all annotations of particular image_id\n        temp_df=self.df.groupby('id')['annotation'].agg(lambda x: list(x)).reset_index()\n        \n        # image_info dict will contain all info about particular image and its all annotations\n        for index,row in temp_df.iterrows():\n            self.image_info[index]={\n                'image_id':row['id'],\n                'image_path':os.path.join(self.image_dir,row['id']+ '.png'),\n                'annotations':row[\"annotation\"]\n            }\n            \n    def get_box(self,a_mask):\n        ''' Get the bounding box of a given mask '''\n        pos = np.where(a_mask)   # find out the position where a_mask=1\n        xmin = np.min(pos[1])  # min pos will give min co-ordinate\n        xmax = np.max(pos[1])   # max-position give max co-ordinate\n        ymin = np.min(pos[0])\n        ymax = np.max(pos[0])\n        return [xmin, ymin, xmax, ymax]\n    \n    def __getitem__(self,idx):\n        ''' Get the image and the target'''\n        \n        img_path=self.image_info[idx][\"image_path\"]\n        img=Image.open(img_path).convert(\"RGB\")\n        \n        if self.should_resize:\n            img=img.resize((self.width,self.height),resample=Image.BILINEAR)\n            \n        info=self.image_info[idx]  \n        n_objects=len(info['annotations'])  #no. of onjects present in an image\n        # creating a masks of Zeros of shape(n_onjects,height,width)\n        masks=np.zeros((len(info['annotations']),self.height,self.width),dtype=np.uint8)\n        boxes=[]\n            \n        # For each annotation create a mask image\n        for i,annotation in enumerate(info['annotations']):\n            a_mask=rle_decode(annotation,(config.HEIGHT,config.WIDTH))\n            a_mask=Image.fromarray(a_mask)  # Creates an image memory from an object exporting the array interface\n            \n            # resizing the mask also\n            if self.should_resize:\n                a_mask=a_mask.resize((self.width,self.height),resample=Image.BILINEAR)\n                \n            a_mask=np.array(a_mask) > 0\n            masks[i,:,:]=a_mask # store the ith mask\n            \n            # finding the bounding box of respective mask for each annotation\n            boxes.append(self.get_box(a_mask))\n                \n            \n        #dummy labels\n        labels=[1 for _ in range(n_objects)]\n        \n        # convert all into tensors\n        boxes=torch.as_tensor(boxes,dtype=torch.float32)\n        labels = torch.as_tensor(labels, dtype=torch.int64)\n        masks = torch.as_tensor(masks, dtype=torch.uint8)\n        \n        image_id=torch.tensor([idx])\n        #area=(xmax-xmin)*(ymax-ymin)\n        area = (boxes[:, 3] - boxes[:, 1]) * (boxes[:, 2] - boxes[:, 0])\n        iscrowd = torch.zeros((n_objects,), dtype=torch.int64)\n\n        # This is the required target for the Mask R-CNN\n        target = {\n            'boxes': boxes,\n            'labels': labels,\n            'masks': masks,\n            'image_id': image_id,\n            'area': area,\n            'iscrowd': iscrowd\n        }\n\n        if self.transforms is not None:\n            img, target = self.transforms(img, target)\n\n        return img, target\n\n    def __len__(self):\n        return len(self.image_info)     \n        ","metadata":{"execution":{"iopub.status.busy":"2021-12-07T07:55:46.173467Z","iopub.execute_input":"2021-12-07T07:55:46.173701Z","iopub.status.idle":"2021-12-07T07:55:46.200356Z","shell.execute_reply.started":"2021-12-07T07:55:46.173669Z","shell.execute_reply":"2021-12-07T07:55:46.199459Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"ds_train=CellDataset(config.TRAIN_PATH,df_train,resize=False, transforms=get_transform(train=True))\n# Data loader. Combines a dataset and a sampler, and provides an iterable over the given dataset.\ndl_train=DataLoader(ds_train,batch_size=config.BATCH_SIZE,shuffle=True,num_workers=2,collate_fn=lambda x:tuple(zip(*x)))","metadata":{"execution":{"iopub.status.busy":"2021-12-07T07:55:46.201871Z","iopub.execute_input":"2021-12-07T07:55:46.202041Z","iopub.status.idle":"2021-12-07T07:55:46.277953Z","shell.execute_reply.started":"2021-12-07T07:55:46.202020Z","shell.execute_reply":"2021-12-07T07:55:46.277343Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Training","metadata":{}},{"cell_type":"markdown","source":"### Model","metadata":{}},{"cell_type":"markdown","source":"Learn how to fine tune your Model","metadata":{}},{"cell_type":"markdown","source":"[TORCHVISION OBJECT DETECTION FINETUNING TUTORIAL\n](https://pytorch.org/tutorials/intermediate/torchvision_tutorial.html)","metadata":{}},{"cell_type":"code","source":"# Override pythorch checkpoint with an \"offline\" version of the file\n!mkdir -p /root/.cache/torch/hub/checkpoints/\n!cp ../input/cocopre/maskrcnn_resnet50_fpn_coco-bf2d0c1e.pth /root/.cache/torch/hub/checkpoints/maskrcnn_resnet50_fpn_coco-bf2d0c1e.pth","metadata":{"execution":{"iopub.status.busy":"2021-12-07T07:55:46.280018Z","iopub.execute_input":"2021-12-07T07:55:46.280480Z","iopub.status.idle":"2021-12-07T07:55:47.986411Z","shell.execute_reply.started":"2021-12-07T07:55:46.280443Z","shell.execute_reply":"2021-12-07T07:55:47.985444Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def get_model():\n    # dummy value of classsification head\n    NUM_CLASSES=2\n    \n    if config.NORMALIZE:\n        model=torchvision.models.detection.maskrcnn_resnet50_fpn(pretrained=True,box_detections_per_img=config.BOX_DETECTIONS_PER_IMG,image_mean=config.RESNET_MEAN,image_std=config.RESNET_STD)\n    else:\n        model=torchvision.models.detection.maskrcnn_resnet50_fpn(pretrained=True,box_detections_per_img=config.BOX_DETECTIONS_PER_IMG)\n        \n    # get the number of input features for the classifier\n    in_features=model.roi_heads.box_predictor.cls_score.in_features\n    \n    # replace the pre-trained head with a new one\n    model.roi_heads.box_predictor=FastRCNNPredictor(in_features,NUM_CLASSES)\n    \n    # now get the number of input features for the mask classifier\n    in_features_mask=model.roi_heads.mask_predictor.conv5_mask.in_channels\n    \n    hidden_layer=256\n    # and replace the mask predictor with the new one\n    model.roi_heads.mask_predictor=MaskRCNNPredictor(in_features_mask,hidden_layer,NUM_CLASSES)\n    \n    return model\n\n\n# Get the Mask R-CNN model\n# The model does classification, bounding boxes and MASKs for individuals, all at the same time\n# We only care about MASKS\n\nmodel=get_model()\nmodel.to(config.DEVICE)\n\n# TODO: try removing this for\nfor param in model.parameters():\n    param.requires_grad=True\n    \nmodel.train();","metadata":{"execution":{"iopub.status.busy":"2021-12-07T07:55:47.990073Z","iopub.execute_input":"2021-12-07T07:55:47.990295Z","iopub.status.idle":"2021-12-07T07:55:48.910366Z","shell.execute_reply.started":"2021-12-07T07:55:47.990266Z","shell.execute_reply":"2021-12-07T07:55:48.909551Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Training loop","metadata":{}},{"cell_type":"code","source":"params=[p for p in model.parameters() if p.requires_grad]\noptimizer=torch.optim.SGD(params,lr=config.LEARNING_RATE,momentum=config.MOMENTUM,weight_decay=config.WEIGHT_DECAY)\n\nlr_schedule=torch.optim.lr_scheduler.StepLR(optimizer,step_size=5,gamma=0.1)\nn_batches=len(dl_train)\n\nfor epoch in range(1,config.NUM_EPOCHS+1):\n    print(f\"Starting epoch {epoch} of {config.NUM_EPOCHS}\")\n\n    time_start=time.time()\n    loss_accum=0.0\n    loss_mask_accum=0.0\n    \n    for batch_idx,(images,targets) in enumerate(dl_train,1):\n        \n        #Predict\n        # By default newly created tensors are created on CPU, if not specified otherwise. So this applies also for your images and targets.\n        # The problem here is that all operands of an operation need to be on the same device! If you leave out the to and use CPU tensors as input you will get an error message.\n        images=list(image.to(config.DEVICE) for image in images)\n        targets=[{k:v.to(config.DEVICE) for k,v in t.items()} for t in targets]\n        \n        loss_dict=model(images,targets)     # Returns losses and detections\n        loss=sum(loss for loss in loss_dict.values())\n        \n        # backprop\n        optimizer.zero_grad()\n        loss.backward()\n        optimizer.step()\n        \n        # Logging\n        loss_mask=loss_dict['loss_mask'].item()\n        loss_accum+=loss.item()\n        loss_mask_accum+=loss_mask\n        \n        if batch_idx% 50 ==0:\n            print(f\"[Batch {batch_idx:3d} / {n_batches:3d}] Batch train loss: {loss.item():7.3f}. Mask-only loss: {loss_mask:7.3f}\")\n            \n    if config.USE_SCHEDULER:\n        lr_scheduler.step()\n        \n    \n    # Train losses\n    train_loss = loss_accum / n_batches\n    train_loss_mask = loss_mask_accum / n_batches\n    \n    \n    elapsed = time.time() - time_start\n    \n    \n    torch.save(model.state_dict(), f\"pytorch_model-e{epoch}.bin\")\n    prefix = f\"[Epoch {epoch:2d} / {config.NUM_EPOCHS:2d}]\"\n    print(f\"{prefix} Train mask-only loss: {train_loss_mask:7.3f}\")\n    print(f\"{prefix} Train loss: {train_loss:7.3f}. [{elapsed:.0f} secs]\")","metadata":{"execution":{"iopub.status.busy":"2021-12-07T08:01:40.407492Z","iopub.execute_input":"2021-12-07T08:01:40.407965Z","iopub.status.idle":"2021-12-07T08:37:06.164950Z","shell.execute_reply.started":"2021-12-07T08:01:40.407913Z","shell.execute_reply":"2021-12-07T08:37:06.164114Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Analyze prediction results for train set¶","metadata":{}},{"cell_type":"markdown","source":"***Now, you are imagining what does model.eval() is doing in below code***\n* `model.eval()` is a kind of switch for some specific layers/parts of the model that behave differently during training and inference (evaluating) time. For example, Dropouts Layers, BatchNorm Layers etc. You need to turn off them during model evaluation, and .eval() will do it for you. In addition, the common practice for evaluating/validation is using `torch.no_grad()` in pair with model.eval() to turn off gradients computation:\n*[Reference](https://stackoverflow.com/questions/60018578/what-does-model-eval-do-in-pytorch)    ","metadata":{}},{"cell_type":"code","source":"# Plots: the image, The image + the ground truth mask, The image + the predicted mask\ndef analyze_train_sample(model, ds_train, sample_index):\n    \n    img, targets = ds_train[sample_index]\n    plt.imshow(img.numpy().transpose((1,2,0)))\n    plt.title(\"Image\")\n    plt.show()\n    \n    masks = np.zeros((config.HEIGHT, config.WIDTH))\n    for mask in targets['masks']:\n        masks = np.logical_or(masks, mask)\n    plt.imshow(img.numpy().transpose((1,2,0)))\n    plt.imshow(masks, alpha=0.3)\n    plt.title(\"Ground truth\")\n    plt.show()\n    \n    model.eval()\n    with torch.no_grad():\n        preds = model([img.to(config.DEVICE)])[0]\n\n    plt.imshow(img.cpu().numpy().transpose((1,2,0)))\n    all_preds_masks = np.zeros((config.HEIGHT, config.WIDTH))\n    for mask in preds['masks'].cpu().detach().numpy():\n        all_preds_masks = np.logical_or(all_preds_masks, mask[0] > config.MASK_THRESHOLD)\n    plt.imshow(all_preds_masks, alpha=0.4)\n    plt.title(\"Predictions\")\n    plt.show()","metadata":{"execution":{"iopub.status.busy":"2021-12-07T08:54:15.160226Z","iopub.execute_input":"2021-12-07T08:54:15.160817Z","iopub.status.idle":"2021-12-07T08:54:15.170276Z","shell.execute_reply.started":"2021-12-07T08:54:15.160780Z","shell.execute_reply":"2021-12-07T08:54:15.169479Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# NOTE: It puts the model in eval mode!! Revert for re-training\nanalyze_train_sample(model, ds_train, 20)","metadata":{"execution":{"iopub.status.busy":"2021-12-07T08:54:15.696879Z","iopub.execute_input":"2021-12-07T08:54:15.697128Z","iopub.status.idle":"2021-12-07T08:54:16.987822Z","shell.execute_reply.started":"2021-12-07T08:54:15.697100Z","shell.execute_reply":"2021-12-07T08:54:16.987135Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# NOTE: It puts the model in eval mode!! Revert for re-training\nanalyze_train_sample(model, ds_train, 100)","metadata":{"execution":{"iopub.status.busy":"2021-12-07T08:54:28.209080Z","iopub.execute_input":"2021-12-07T08:54:28.209364Z","iopub.status.idle":"2021-12-07T08:54:29.452998Z","shell.execute_reply.started":"2021-12-07T08:54:28.209329Z","shell.execute_reply":"2021-12-07T08:54:29.452341Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"analyze_train_sample(model, ds_train, 2)","metadata":{"execution":{"iopub.status.busy":"2021-12-07T08:54:44.943846Z","iopub.execute_input":"2021-12-07T08:54:44.944103Z","iopub.status.idle":"2021-12-07T08:54:46.083019Z","shell.execute_reply.started":"2021-12-07T08:54:44.944074Z","shell.execute_reply":"2021-12-07T08:54:46.082354Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Prediction","metadata":{}},{"cell_type":"markdown","source":"# Test Dataset and DataLoader","metadata":{}},{"cell_type":"code","source":"class CellTestDataset(Dataset):\n    def __init__(self, image_dir, transforms=None):\n        self.transforms = transforms\n        self.image_dir = image_dir\n        self.image_ids = [f[:-4]for f in os.listdir(self.image_dir)]\n    \n    def __getitem__(self, idx):\n        image_id = self.image_ids[idx]\n        image_path = os.path.join(self.image_dir, image_id + '.png')\n        image = Image.open(image_path).convert(\"RGB\")\n\n        if self.transforms is not None:\n            image, _ = self.transforms(image=image, target=None)\n        return {'image': image, 'image_id': image_id}\n\n    def __len__(self):\n        return len(self.image_ids)","metadata":{"execution":{"iopub.status.busy":"2021-12-07T08:56:04.566346Z","iopub.execute_input":"2021-12-07T08:56:04.566615Z","iopub.status.idle":"2021-12-07T08:56:04.573657Z","shell.execute_reply.started":"2021-12-07T08:56:04.566584Z","shell.execute_reply":"2021-12-07T08:56:04.572911Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"ds_test = CellTestDataset(config.TEST_PATH, transforms=get_transform(train=False))\nds_test[0]","metadata":{"execution":{"iopub.status.busy":"2021-12-07T08:56:30.616271Z","iopub.execute_input":"2021-12-07T08:56:30.616929Z","iopub.status.idle":"2021-12-07T08:56:30.644388Z","shell.execute_reply.started":"2021-12-07T08:56:30.616889Z","shell.execute_reply":"2021-12-07T08:56:30.643668Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Utilities","metadata":{}},{"cell_type":"code","source":"def rle_encoding(x):\n    '''\n    This function convert again convert Mask into run length encoding\n    '''\n    dots = np.where(x.flatten() == 1)[0]\n    run_lengths = []\n    prev = -2\n    for b in dots:\n        if (b>prev+1): run_lengths.extend((b + 1, 0))\n        run_lengths[-1] += 1\n        prev = b\n    return ' '.join(map(str, run_lengths))\n\n\ndef remove_overlapping_pixels(mask, other_masks):\n    for other_mask in other_masks:\n        if np.sum(np.logical_and(mask, other_mask)) > 0:\n            mask[np.logical_and(mask, other_mask)] = 0\n    return mask","metadata":{"execution":{"iopub.status.busy":"2021-12-07T08:56:50.772329Z","iopub.execute_input":"2021-12-07T08:56:50.773012Z","iopub.status.idle":"2021-12-07T08:56:50.779390Z","shell.execute_reply.started":"2021-12-07T08:56:50.772975Z","shell.execute_reply":"2021-12-07T08:56:50.778334Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Below cell provide you the sample of above rle_encoding function for a particular sample mask","metadata":{}},{"cell_type":"code","source":"sample = rle_encoding(np.array([[0,1,0,0,1,1],[1,0,0,0,0,0]]))\nprint(sample)","metadata":{"execution":{"iopub.status.busy":"2021-12-07T13:48:31.011853Z","iopub.execute_input":"2021-12-07T13:48:31.012152Z","iopub.status.idle":"2021-12-07T13:48:31.018086Z","shell.execute_reply.started":"2021-12-07T13:48:31.012122Z","shell.execute_reply":"2021-12-07T13:48:31.017170Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Run Predictions","metadata":{}},{"cell_type":"code","source":"model.eval();\n\nsubmission = []\nfor sample in ds_test:\n    img = sample['image']\n    image_id = sample['image_id']\n    with torch.no_grad():\n        result = model([img.to(config.DEVICE)])[0]\n    \n    previous_masks = []\n    for i, mask in enumerate(result[\"masks\"]):\n        \n        # Filter-out low-scoring results. Not tried yet.\n        score = result[\"scores\"][i].cpu().item()\n        if score < config.MIN_SCORE:\n            continue\n        \n        mask = mask.cpu().numpy()\n        # Keep only highly likely pixels\n        binary_mask = mask > config.MASK_THRESHOLD\n        binary_mask = remove_overlapping_pixels(binary_mask, previous_masks)\n        previous_masks.append(binary_mask)\n        rle = rle_encoding(binary_mask)\n        submission.append((image_id, rle))\n    \n    # Add empty prediction if no RLE was generated for this image\n    all_images_ids = [image_id for image_id, rle in submission]\n    if image_id not in all_images_ids:\n        submission.append((image_id, \"\"))\n\ndf_sub = pd.DataFrame(submission, columns=['id', 'predicted'])\ndf_sub.to_csv(\"submission.csv\", index=False)\ndf_sub.head()","metadata":{"execution":{"iopub.status.busy":"2021-12-07T08:59:00.112455Z","iopub.execute_input":"2021-12-07T08:59:00.113109Z","iopub.status.idle":"2021-12-07T08:59:03.558784Z","shell.execute_reply.started":"2021-12-07T08:59:00.113071Z","shell.execute_reply":"2021-12-07T08:59:03.558093Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]}]}