{"cells":[{"metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"cell_type":"code","source":"# # This Python 3 environment comes with many helpful analytics libraries installed\n# # It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python\n# # For example, here's several helpful packages to load\n\n# import numpy as np # linear algebra\n# import pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\n# # Input data files are available in the read-only \"../input/\" directory\n# # For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\n\n# import os\n# for dirname, _, filenames in os.walk('/kaggle/input'):\n#     for filename in filenames:\n#         print(os.path.join(dirname, filename))\n\n# # You can write up to 5GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using \"Save & Run All\" \n# # You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","trusted":true},"cell_type":"code","source":"import pandas as pd\nimport numpy as np\nimport cv2\nimport os\nimport re\nimport matplotlib.pyplot as plt\nfrom tqdm import tqdm_notebook as tqdm\n\nimport torch\nimport torch.nn as nn\nimport torchvision","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Hyper-params\nMODEL_PATH = '/kaggle/input/fpnup50/model_best.pth'\ninput_size = 512\nIN_SCALE = 1024 // input_size\nMODEL_SCALE = 4","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"DIR_INPUT = '/kaggle/input/global-wheat-detection'\nDIR_TEST = f'{DIR_INPUT}/test'","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def pred2box(hm, regr, thresh=0.99):\n    # make binding box from heatmaps\n    # thresh: threahold for logits.\n    \n    # get center\n    pred = hm > thresh\n    pred_center = np.where(hm > thresh)\n    # get regressions\n    pred_r = regr[:, pred].T\n    \n    # wrap as boxes\n    # [xmin, ymin, width, height]\n    # size as original image\n    boxes = []\n    scores = hm[pred]\n    for i, b in enumerate(pred_r):\n        arr = np.array([pred_center[1][i] * MODEL_SCALE - b[0] * input_size // 2, \n                        pred_center[0][i] * MODEL_SCALE - b[1] * input_size // 2,\n                        int(b[0] * input_size), int(b[1] * input_size)])\n        arr = np.clip(arr, 0, input_size)\n        # filter\n        # if arr[0] < 0 or arr[1] < 0 or arr[0] > input_size or arr[1] > input_size: pass\n        boxes.append(arr)\n    return np.asarray(boxes), scores","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# functions for plotting results\n# def showbox(img, hm, regr, thresh=0.9):\n#     boxes, _ = pred2box(hm, regr, thresh=thresh)\n#     print('preds:', boxes.shape)\n#     sample = img\n    \n#     for box in boxes:\n#         # upper-left, lower-right\n#         cv2.rectangle(sample,\n#                      (int(box[0]), int(box[1]+box[3])),\n#                      (int(box[0]+box[2]), int(box[1])),\n#                      (220, 0, 0), 3)\n#     return sample\ndef boxshow(img, boxes, color=(220, 0, 0)):\n    sample = img\n    for box in boxes:\n        cv2.rectangle(sample,\n                      (int(box[0]), int(box[1] + box[3])),\n                      (int(box[0] + box[2]), int(box[1])),\n                      color, 3)\n    return sample","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# pool duplicates\ndef pool(data):\n    stride = 3\n    for y in np.arange(1, data.shape[1]-1, stride):\n        for x in np.arange(1, data.shape[0]-1, stride):\n            a_2d = data[x-1:x+2, y-1:y+2]\n            max = np.asarray(np.unravel_index(np.argmax(a_2d), a_2d.shape))\n            for c1 in range(3):\n                for c2 in range(3):\n                    if not (c1 == max[0] and c2 == max[1]):\n                        data[x+c1-1, y+c2-1] = -1\n    return data\n\n# NMS is required to remove duplicate boxes\ndef nms(boxes, scores, overlap=0.25, top_k=200):\n#     print(boxes.shape)\n#     print(scores.shape)\n    scores = torch.from_numpy(scores)\n    count = 0\n    keep = scores.new(scores.size(0)).zero_().long()\n    boxes = boxes.reshape(-1, 4)\n    boxes = torch.from_numpy(np.array([boxes[:,0], boxes[:,1], boxes[:,0]+boxes[:,2], boxes[:,1]+boxes[:,3]]).T.reshape(-1, 4))\n    \n    x1 = boxes[:, 0]\n    y1 = boxes[:, 1]\n    x2 = boxes[:, 2]\n    y2 = boxes[:, 3]\n    area = torch.mul(x2-x1, y2-y1)\n    \n    tmp_x1 = boxes.new()\n    tmp_y1 = boxes.new()\n    tmp_x2 = boxes.new()\n    tmp_y2 = boxes.new()\n    tmp_w = boxes.new()\n    tmp_h = boxes.new()\n    \n    v, idx = scores.sort(0)\n    \n    idx = idx[-top_k:]\n    \n    while idx.numel() > 0:\n        i = idx[-1]\n        keep[count] = i\n        count += 1\n        \n        if idx.size(0) == 1:\n            break\n            \n        idx = idx[:-1]\n        \n        torch.index_select(x1, 0, idx, out=tmp_x1)\n        torch.index_select(y1, 0, idx, out=tmp_y1)\n        torch.index_select(x2, 0, idx, out=tmp_x2)\n        torch.index_select(y2, 0, idx, out=tmp_y2)\n        \n        tmp_x1 = torch.clamp(tmp_x1, min=x1[i])\n        tmp_y1 = torch.clamp(tmp_y1, min=y1[i])\n        tmp_x2 = torch.clamp(tmp_x2, max=x2[i])\n        tmp_y2 = torch.clamp(tmp_y2, max=y2[i])\n        \n        tmp_w.resize_as_(tmp_x2)\n        tmp_h.resize_as_(tmp_y2)\n        \n        tmp_w = tmp_x2 - tmp_x1\n        tmp_h = tmp_y2 - tmp_y1\n        \n        tmp_w = torch.clamp(tmp_w, min=0.0)\n        tmp_h = torch.clamp(tmp_h, min=0.0)\n        \n        inter = tmp_w * tmp_h\n        \n        rem_areas = torch.index_select(area, 0, idx)\n        union = (rem_areas - inter) + area[i]\n        IoU = inter / union\n        \n        idx  = idx[IoU.le(overlap)]\n        \n    return keep.numpy(), count\n","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"from torchvision import transforms\n\n# Submission\nclass WheatDatasetTest(torch.utils.data.Dataset):\n    def __init__(self, image_dir):\n        self.image_dir = image_dir\n        self.img_id = os.listdir(self.image_dir)\n        \n    def __len__(self):\n        return len(self.img_id)\n    \n    def __getitem__(self, idx):\n        img = cv2.imread(os.path.join(self.image_dir, self.img_id[idx]), cv2.IMREAD_COLOR)\n        img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n        img = cv2.resize(img, (input_size, input_size))\n        img = img.astype(np.float32) / 255.0\n        img = img.transpose([2, 0, 1])\n        return img, self.img_id[idx]","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"testdataset = WheatDatasetTest(DIR_TEST)\ntest_loader = torch.utils.data.DataLoader(testdataset, batch_size=16, shuffle=False, num_workers=0)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"import torch.nn as nn\nimport torch\nimport torchvision\nimport torch.nn.functional as F\nimport math\n\n\nclass BasicBlock(nn.Module):\n    def __init__(self, in_ch, out_ch, stride=1, downsample=None):\n        super(BasicBlock, self).__init__()\n        norm_layer = nn.BatchNorm2d\n        self.conv1 = nn.Conv2d(in_ch, out_ch, 3, stride=stride, padding=1)\n        self.bn1 = norm_layer(out_ch)\n        self.relu = nn.ReLU(inplace=True)\n        self.conv2 = nn.Conv2d(out_ch, out_ch, 3, padding=1)\n        self.bn2 = norm_layer(out_ch)\n        self.downsample = downsample\n\n    def forward(self, x):\n        identity = x\n\n        out = self.conv1(x)\n        out = self.bn1(out)\n        out = self.relu(out)\n\n        out = self.conv2(out)\n        out = self.bn2(out)\n\n        if self.downsample is not None:\n            identity = self.downsample(x)\n\n        out += identity\n        out = self.relu(out)\n\n        return out\n\n\nclass double_conv(nn.Module):\n    def __init__(self, in_ch, out_ch):\n        super(double_conv, self).__init__()\n        self.conv = nn.Sequential(\n            nn.Conv2d(in_ch, out_ch, 3, padding=1),\n            nn.BatchNorm2d(out_ch),\n            nn.ReLU(inplace=True),\n            nn.Conv2d(out_ch, out_ch, 3, padding=1),\n            nn.BatchNorm2d(out_ch),\n            nn.ReLU(inplace=True)\n        )\n\n    def forward(self, x):\n        x = self.conv(x)\n        return x\n\n\nclass up(nn.Module):\n    def __init__(self, in_ch, out_ch, bilinear=True):\n        super(up, self).__init__()\n        if bilinear:\n            self.up = nn.Upsample(scale_factor=2, mode='bilinear', align_corners=True)\n        else:\n            self.up = nn.ConvTranspose2d(in_ch, in_ch, 2, stride=2)\n        # self.shrink = nn.Sequential(\n        #     nn.Conv2d(in_ch, in_ch//2, 3, padding=1),\n        #     nn.BatchNorm2d(in_ch // 2),\n        #     nn.ReLU(inplace=True)\n        # )\n        self.conv = double_conv(in_ch, out_ch)\n\n    def forward(self, x1, x2=None):\n        x1 = self.up(x1)\n        # x1 = self.shrink(x1)\n        if x2 is not None:\n            x = torch.cat([x2, x1], dim=1)\n        else:\n            x = x1\n        x = self.conv(x)\n        return x\n\n\nclass centernet(nn.Module):\n    def __init__(self, num_class=1):\n        super(centernet, self).__init__()\n        basemodel = torchvision.models.resnet18(pretrained=True)\n        basemodel = nn.Sequential(*list(basemodel.children())[:-2])\n        self.basemodel = basemodel\n\n        num_ch = 512\n        self.up1 = up(num_ch, 512)\n        self.up2 = up(512, 256)\n        self.up3 = up(256, 256)\n\n        # self.outc = nn.Conv2d(256, num_class, 1)\n        # self.outr = nn.Conv2d(256, 2, 1)\n        self.outc = nn.Sequential(\n            nn.Conv2d(256, 128, kernel_size=3, padding=1, bias=True),\n            nn.ReLU(inplace=True),\n            nn.Dropout(p=0.2),\n            nn.Conv2d(128, num_class, kernel_size=1, stride=1, padding=0))\n        self.outr = nn.Sequential(\n            nn.Conv2d(256, 128, kernel_size=3, padding=1, bias=True),\n            nn.ReLU(inplace=True),\n            nn.Dropout(p=0.2),\n            nn.Conv2d(128, 2, kernel_size=1, stride=1, padding=0))\n\n        for m in self.modules():\n            if isinstance(m, nn.Conv2d):\n                nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')\n            elif isinstance(m, (nn.BatchNorm2d, nn.GroupNorm)):\n                nn.init.constant_(m.weight, 1)\n                nn.init.constant_(m.bias, 0)\n\n    def forward(self, x):\n        x = self.basemodel(x)\n        x = self.up1(x)\n        x = self.up2(x)\n        x = self.up3(x)\n        hm = self.outc(x)\n        wh = self.outr(x)\n        ret = {'hm': hm, 'wh': wh}\n        return ret\n\n\nclass CenterNetRes34(nn.Module):\n    def __init__(self, num_class=1):\n        super(CenterNetRes34, self).__init__()\n        basemodel = torchvision.models.resnet34(pretrained=True)\n        basemodel = nn.Sequential(*list(basemodel.children())[:-2])\n        self.basemodel = basemodel\n\n        num_ch = 512\n        self.up1 = up(num_ch, 512)\n        self.up2 = up(512, 256)\n        self.up3 = up(256, 256)\n\n        self.outc = nn.Sequential(\n                nn.Conv2d(256, 128, kernel_size=3, padding=1, bias=True),\n                nn.ReLU(inplace=True),\n                nn.Dropout(p=0.2),\n                nn.Conv2d(128, num_class, kernel_size=1, stride=1, padding=0))\n        self.outr = nn.Sequential(\n                nn.Conv2d(256, 128, kernel_size=3, padding=1, bias=True),\n                nn.ReLU(inplace=True),\n                nn.Dropout(p=0.2),\n                nn.Conv2d(128, 2, kernel_size=1, stride=1, padding=0))\n\n        for m in self.modules():\n            if isinstance(m, nn.Conv2d):\n                nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')\n            elif isinstance(m, (nn.BatchNorm2d, nn.GroupNorm)):\n                nn.init.constant_(m.weight, 1)\n                nn.init.constant_(m.bias, 0)\n\n    def forward(self, x):\n        x = self.basemodel(x)\n        x = self.up1(x)\n        x = self.up2(x)\n        x = self.up3(x)\n        hm = self.outc(x)\n        wh = self.outr(x)\n        ret = {'hm': hm, 'wh': wh}\n        return ret\n\n\n## FPN\nclass Botttleneck(nn.Module):\n    expansion = 4\n\n    def __init__(self, in_planes, planes, stride=1, act = nn.LeakyReLU(negative_slope=0.1, inplace=True)):\n        super(Botttleneck, self).__init__()\n        self.conv1 = nn.Conv2d(in_planes, planes, kernel_size=1, bias=False)\n        self.bn1 = nn.BatchNorm2d(planes)\n        self.conv2 = nn.Conv2d(planes, planes, kernel_size=3, stride=stride, padding=1, bias=False)\n        self.bn2 = nn.BatchNorm2d(planes)\n        self.conv3 = nn.Conv2d(planes, self.expansion*planes, kernel_size=1, bias=False)\n        self.bn3 = nn.BatchNorm2d(self.expansion*planes)\n\n        self.shortcut = nn.Sequential()\n        if stride != 1 or in_planes != self.expansion * planes:\n            self.shortcut = nn.Sequential(\n                nn.Conv2d(in_planes, self.expansion * planes, kernel_size=1, stride=stride, bias=False),\n                nn.BatchNorm2d(self.expansion * planes)\n            )\n        self.act = act\n\n    def forward(self, x):\n        out = self.act(self.bn1(self.conv1(x)))\n        out = self.act(self.bn2(self.conv2(out)))\n        out = self.bn3(self.conv3(out))\n        out += self.shortcut(x)\n        out = self.act(out)\n        return out\n\n\nclass UpSampleConcat(nn.Module):\n    def __init__(self, in_ch, out_ch, scale=2):\n        super(UpSampleConcat, self).__init__()\n        self.conv = double_conv(in_ch*2, out_ch)\n        self.up = Upsampler(scale, in_ch)\n\n    def forward(self, x, y):\n        _, _, H, W = y.size()\n        # x1 = F.interpolate(x, size=(H, W), mode='bilinear', align_corners=False)\n        x1 = self.up(x)\n        out = self.conv(torch.cat([x1, y], dim=1))\n        return out\n\n\nclass Upsampler(nn.Sequential):\n    def __init__(self, scale, n_feats, bn=True, act=False, bias=True):\n\n        m = []\n        if (scale & (scale - 1)) == 0:    # Is scale = 2^n?\n            for _ in range(int(math.log(scale, 2))):\n                m.append(nn.Conv2d(n_feats, 4 * n_feats, 3, padding=1, bias=bias))\n                m.append(nn.PixelShuffle(2))\n                if bn:\n                    m.append(nn.BatchNorm2d(n_feats))\n                if act == 'relu':\n                    m.append(nn.ReLU(True))\n                elif act == 'prelu':\n                    m.append(nn.PReLU(n_feats))\n                elif act == 'leakylu':\n                    m.append(nn.LeakyReLU(negative_slope=0.1, inplace=True))\n\n        elif scale == 3:\n            m.append(nn.Conv2d(n_feats, 9 * n_feats, 3, padding=1, bias=bias))\n            m.append(nn.PixelShuffle(3))\n            if bn:\n                m.append(nn.BatchNorm2d(n_feats))\n            if act == 'relu':\n                m.append(nn.ReLU(True))\n            elif act == 'prelu':\n                m.append(nn.PReLU(n_feats))\n            elif act == 'leakylu':\n                m.append(nn.LeakyReLU(negative_slope=0.1, inplace=True))\n\n        else:\n            raise NotImplementedError\n\n        super(Upsampler, self).__init__(*m)\n\n\nclass FPN(nn.Module):\n    def __init__(self, block, num_blocks, num_class=1, act=nn.LeakyReLU(negative_slope=0.1, inplace=True)):\n        super(FPN, self).__init__()\n        self.in_planes = 64\n        self.act = act\n\n        self.conv1 = nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3, bias=False)\n        self.bn1 = nn.BatchNorm2d(64)\n\n        # Bottom-up layers\n        self.layer1 = self._make_layer(block, 64, num_blocks[0], stride=1)\n        self.layer2 = self._make_layer(block, 128, num_blocks[1], stride=2)\n        self.layer3 = self._make_layer(block, 256, num_blocks[2], stride=2)\n        self.layer4 = self._make_layer(block, 512, num_blocks[3], stride=2)\n\n        # Top layer\n        self.toplayer = nn.Conv2d(2048, 256, kernel_size=1, stride=1, padding=0)\n\n        # Lateral layers\n        self.latlayer1 = nn.Conv2d(1024, 256, kernel_size=1, stride=1, padding=0)\n        self.latlayer2 = nn.Conv2d(512, 256, kernel_size=1, stride=1, padding=0)\n        self.latlayer3 = nn.Conv2d(256, 256, kernel_size=1, stride=1, padding=0)\n\n        # upsample Concat\n        self.up1 = UpSampleConcat(256, 256)\n        self.up2 = UpSampleConcat(256, 256)\n        self.up3 = UpSampleConcat(256, 256)\n\n        self.outc = nn.Sequential(\n            nn.Conv2d(256, 128, kernel_size=3, padding=1, bias=True),\n            nn.ReLU(inplace=True),\n            nn.Dropout(p=0.2),\n            nn.Conv2d(128, num_class, kernel_size=1, stride=1, padding=0))\n        self.outr = nn.Sequential(\n            nn.Conv2d(256, 128, kernel_size=3, padding=1, bias=True),\n            nn.ReLU(inplace=True),\n            nn.Dropout(p=0.2),\n            nn.Conv2d(128, 2, kernel_size=1, stride=1, padding=0))\n\n    def _make_layer(self, block, planes, num_blocks, stride):\n        strides = [stride] + [1] * (num_blocks-1)\n        layers = []\n        for stride in strides:\n            layers.append(block(self.in_planes, planes, stride))\n            self.in_planes = planes * block.expansion\n        return nn.Sequential(*layers)\n\n    # def _upsample_add(self, x, y):\n    #     _, _, H, W = y.size()\n    #     return F.interpolate(x, size=(H, W), mode='bilinear', align_corners=False) + y\n\n    def forward(self, x):\n        c1 = self.act(self.bn1(self.conv1(x)))\n        c1 = F.max_pool2d(c1, kernel_size=3, stride=2, padding=1)\n        c2 = self.layer1(c1)\n        c3 = self.layer2(c2)\n        c4 = self.layer3(c3)\n        c5 = self.layer4(c4)\n\n        p5 = self.toplayer(c5)\n        # p4 = self._upsample_add(p5, self.latlayer1(c4))\n        # p3 = self._upsample_add(p4, self.latlayer2(c3))\n        # p2 = self._upsample_add(p3, self.latlayer3(c2))\n        p4 = self.up1(p5, self.latlayer1(c4))\n        p3 = self.up2(p4, self.latlayer2(c3))\n        p2 = self.up3(p3, self.latlayer3(c2))\n\n        hm = self.outc(p2)\n        wh = self.outr(p2)\n        ret = {'hm': hm, 'wh': wh}\n        return ret\n\n\ndef FPNMaker():\n    return FPN(Botttleneck, [2, 2, 2, 2], num_class=1)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# model = centernet(num_class=1)\nmodel = FPNMaker()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n\nmodel.to(device)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# model.load_state_dict(torch.load(MODEL_PATH))\n# model.load_state_dict(torch.load(MODEL_PATH, map_location=lambda storage, loc:storage))\nmodel_old = torch.load(MODEL_PATH, map_location=lambda storage, loc:storage)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"state_dict_ = model_old['state_dict']","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"model.load_state_dict(state_dict_, strict=False)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def format_prediction_string(boxes, scores):\n    pred_strings = []\n    for s, b in zip(scores, boxes.astype(int)):\n        # xmin, ymin, w, h\n        pred_strings.append(f'{s:.4f} {b[0]*IN_SCALE} {b[1]*IN_SCALE} {b[2]*IN_SCALE} {b[3]*IN_SCALE}')\n    return ' '.join(pred_strings)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"model.eval()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"thresh = 0.45\nresults = []\nfor images, image_ids in tqdm(test_loader):\n    images = images.to(device)\n#     print(images.shape)\n    with torch.no_grad():\n        outputs = model(images)\n#     print(outputs)\n        \n    for hm, regr, image_id in zip(outputs['hm'], outputs['wh'], image_ids):\n#         print(hm.shape)\n    \n        # process predictions\n        hm = hm.cpu().numpy().squeeze(0)\n        regr = regr.cpu().numpy()\n        hm = torch.sigmoid(torch.from_numpy(hm)).numpy()\n        hm = pool(hm)\n        \n        boxes, scores = pred2box(hm, regr, thresh)\n#         print(boxes.shape)\n#         print(scores.shape)\n        # Filter by nms\n        keep, count = nms(boxes, scores, overlap=0.15)\n        boxes = boxes[keep[:count]]\n        scores = scores[keep[:count]]\n        \n        preds_sorted_idx = np.argsort(scores)[::-1]\n        boxes_sorted = boxes[preds_sorted_idx]\n        scores_sorted = scores[preds_sorted_idx]\n        \n#         img = cv2.imread(os.path.join(DIR_INPUT, 'test', image_id))\n#         img = cv2.resize(cv2.cvtColor(img, cv2.COLOR_BGR2RGB), (input_size, input_size))\n#         sample = boxshow(img, boxes_sorted, color=(220, 0, 0))\n#         plt.imshow(sample)\n#         plt.show()\n        \n        \n        result = {\n            'image_id': image_id[:-4],\n            'PredictionString': format_prediction_string(boxes, scores)\n        }\n        results.append(result)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"test_df = pd.DataFrame(results, columns=['image_id', 'PredictionString'])\ntest_df.head(10)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"test_df.to_csv('submission.csv', index=False)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"","execution_count":null,"outputs":[]}],"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":4,"nbformat_minor":4}