{"cells":[{"metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"cell_type":"code","source":"import cv2\nimport os\nimport ast\nimport torchvision\nimport torch  \nimport numbers\nimport random\nimport math\n\nimport numpy as np\nimport pandas as pd \nimport matplotlib.pyplot as plt \n\n\nfrom PIL import Image\nfrom torch.utils.data import Dataset, DataLoader\nfrom torchvision import utils\nfrom torchvision.transforms import Compose, functional\nfrom torchvision.models.detection import FasterRCNN\nfrom torchvision.models.detection.faster_rcnn import FastRCNNPredictor\n\nDIR_INPUT = '/kaggle/input/global-wheat-detection'\nDIR_TRAIN = f'{DIR_INPUT}/train'\nDIR_TEST = f'{DIR_INPUT}/test'","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"df = pd.read_csv(DIR_INPUT + '/train.csv')\ndf_test = pd.read_csv(f'{DIR_INPUT}/sample_submission.csv')\n\ndf.sample(20)\n\nfor i,row in df.iterrows():\n    box = ast.literal_eval(row['bbox'])\n    xmin = box[0]\n    ymin = box[1]\n    w = box[2]\n    h = box[3]\n    df.at[i, 'x0'] = xmin\n    df.at[i, 'y0'] = ymin\n    df.at[i, 'x1'] = xmin + w\n    df.at[i, 'y1'] = ymin + h\n    \ndf = df.drop(columns=['width', 'height', 'source', 'bbox'])\ndf.head(10)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Inspecting images\nfig = plt.figure(figsize=(32,32))\n\ncolumns=3\nrows=1\n\nfor i in range(1, columns*rows + 1):  \n    row = np.random.randint(0, len(df))\n    img_id = df.iloc[row][['image_id']].values[0]\n    img = cv2.imread(DIR_TRAIN + '/' + img_id + '.jpg')\n    bboxes = df.loc[df['image_id'] == img_id, ['x0', 'y0', 'x1', 'y1']].values.tolist()\n    for bbox in bboxes:  \n        bbox = list(map(int, bbox))\n        cv2.rectangle(img,\n                      (bbox[0], bbox[1]), (bbox[2],bbox[3]),\n                      color=(0, 255, 0), thickness=3)\n        \n    fig.add_subplot(rows, columns, i)\n    plt.imshow(img)\n    \nplt.show() ","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"class WheatDataset(Dataset):\n    \"\"\"Dataclass for wheat dataset\"\"\"\n    \n    def __init__(self, dataframe, img_dir, transform=None):\n        self.dataframe = dataframe\n        self.img_dir = img_dir\n        self.images = dataframe['image_id'].unique()\n        self.transform = transform\n   \n    def __getitem__(self, idx):\n        target = {}\n    \n        img_id = self.images[idx]\n        #target['image_id'] = img_id\n\n        img_arr = cv2.imread(f'{self.img_dir}/{img_id}.jpg', cv2.IMREAD_COLOR)\n        img_arr = cv2.cvtColor(img_arr, cv2.COLOR_BGR2RGB).astype(np.uint8)\n        img_arr = img_arr / 255\n        \n        boxes_array = np.array(self.dataframe.loc[self.dataframe['image_id'] == img_id, ['x0', 'y0', 'x1', 'y1']])\n        boxes = torch.tensor(boxes_array, dtype=torch.float32)\n        target['boxes'] = boxes\n        \n        area = []\n        for box in boxes:\n            width = box[2] - box[0]\n            height = box[3] - box[1]\n            area.append(width * height) \n        target['area'] = torch.tensor(area) \n      \n        labels = torch.ones((len(self.images)), dtype=torch.int64)\n        target['labels'] = labels\n               \n        iscrowd = torch.zeros((len(self.images)), dtype=torch.uint8)\n        target['iscrowd'] = iscrowd\n        \n        if self.transform is not None:\n            img_arr = self.transform(img_arr)\n            \n        return img_arr, target, img_id\n            \n    def __len__(self):\n        return len(self.images)\n    ","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"class ToTensor(object):\n    \"\"\"Convert ndarrays in sample to Tensors\"\"\"\n    \n    def __call__(self, img):  \n        img_arr = np.array(img).transpose((2,0,1))\n        img_arr = torch.tensor(img_arr, dtype=torch.float32)\n          \n        return img_arr\n    \nclass ToPILImage(object):\n    \"\"\"Convert a tensor or an ndarray to PIL image.\n       If \"mode\" is None there are some assumptions made about the input data:       \n         - If the input has 4 channels, the ``mode`` is assumed to be ``RGBA``.\n         - If the input has 3 channels, the ``mode`` is assumed to be ``RGB``.\n         - If the input has 2 channels, the ``mode`` is assumed to be ``LA``.\n         - If the input has 1 channel, the ``mode`` is determined by the data type\"\"\"\n    \n    def __init__(self, mode=None):\n        self.mode = mode\n        \n    def __call__(self, img):\n        img_arr = functional.to_pil_image(img, self.mode)\n        return img_arr\n    \nclass RandomHorizontalFlip(object):\n    def __init__(self, p=0.5):\n        self.p = p\n    \n    def __call__(self, img):\n        \n        if random.random() < self.p:\n            img_arr = functional.hflip(img)\n            return img_arr\n        else:\n            return img\n       \nclass RandomRotation(object):\n    def __init__(self, degrees, resample=False, expand=False, center=None, fill=None):\n        if isinstance(degrees, numbers.Number):\n            if degrees < 0:\n                raise ValueError(\"If degrees is a single number, it must be positive.\")\n            self.degrees = (-degrees, degrees)\n        else:\n            if len(degrees) != 2:\n                raise ValueError(\"If degrees is a sequence, it must be of len 2.\")\n            self.degrees = degrees\n            \n        self.resample = resample\n        self.expand = expand\n        self.center = center\n        self.fill = fill\n        \n    @staticmethod\n    def get_params(degrees):\n        angle = random.uniform(degrees[0], degrees[1])\n        return angle\n    \n    def __call__(self, img):\n        angle = self.get_params(self.degrees)\n        img_arr = functional.rotate(img, angle, self.resample, self.expand, self.center, self.fill)\n        return img_arr\n        \nclass Normalize(object):\n    \"\"\"Normalize image to 0 mean and unit variance\"\"\"\n    \n    def __init__(self, mean, std, inplace=False):\n        self.mean = mean\n        self.std = std\n        self.inplace = False\n \n    def __call__(self, img):\n        normalized_img_arr = functional.normalize(img, self.mean, self.std, self.inplace)     \n        return normalized_img_arr\n\nclass ColorJitter(object):\n    \"\"\"Randomly change the brightness, contrast, and saturation of an image\"\"\"\n    \n    def __init__(self, brightness=0, contrast=0, saturation=0, hue=0):\n        self.brightness = self._check_input(brightness, 'brightness')\n        self.contrast = self._check_input(contrast, 'contrast')\n        self.saturation = self._check_input(saturation, 'saturation')\n        self.hue = self._check_input(hue, 'hue', center=0, bound=(-0.5, 0.5),\n                                     clip_first_on_zero=False)\n\n    def _check_input(self, value, name, center=1, bound=(0, float('inf')), clip_first_on_zero=True):\n        if isinstance(value, numbers.Number):\n            if value < 0:\n                raise ValueError(\"If {} is a single number, it must be non negative.\".format(name))\n            value = [center - value, center + value]\n            if clip_first_on_zero:\n                value[0] = max(value[0], 0)\n        elif isinstance(value, (tuple, list)) and len(value) == 2:\n            if not bound[0] <= value[0] <= value[1] <= bound[1]:\n                raise ValueError(\"{} values should be between {}\".format(name, bound))\n        else:\n            raise TypeError(\"{} should be a single number or a list/tuple with lenght 2.\".format(name))\n\n        # if value is 0 or (1., 1.) for brightness/contrast/saturation\n        # or (0., 0.) for hue, do nothing\n        if value[0] == value[1] == center:\n            value = None\n        return value\n\n    @staticmethod\n    def get_params(brightness, contrast, saturation, hue):\n        \"\"\"Get a randomized transform to be applied on image\"\"\"\n\n        transforms = []\n\n        if brightness is not None:\n            brightness_factor = random.uniform(brightness[0], brightness[1])\n            transforms.append(Lambda(lambda img: functional.adjust_brightness(img, brightness_factor)))\n\n        if contrast is not None:\n            contrast_factor = random.uniform(contrast[0], contrast[1])\n            transforms.append(Lambda(lambda img: functional.adjust_contrast(img, contrast_factor)))\n\n        if saturation is not None:\n            saturation_factor = random.uniform(saturation[0], saturation[1])\n            transforms.append(Lambda(lambda img: functional.adjust_saturation(img, saturation_factor)))\n\n        if hue is not None:\n            hue_factor = random.uniform(hue[0], hue[1])\n            transforms.append(Lambda(lambda img: functional.adjust_hue(img, hue_factor)))\n\n        random.shuffle(transforms)\n        transform = Compose(transforms)\n\n        return transform\n\n    def __call__(self, img):\n    \n        transform = self.get_params(self.brightness, self.contrast,\n                                    self.saturation, self.hue)\n        \n        transformed_img_arr = transform(img)\n        \n        return transformed_img_arr\n    \nclass Lambda(object):\n    \"\"\"Apply a user-defined lambda as a transform.\n\n    Args:\n        lambd (function): Lambda/function to be used for transform.\n    \"\"\"\n\n    def __init__(self, lambd):\n        assert callable(lambd), repr(type(lambd).__name__) + \" object is not callable\"\n        self.lambd = lambd\n\n    def __call__(self, img):\n        return self.lambd(img)\n\n    def __repr__(self):\n        return self.__class__.__name__ + '()'\n    \nclass Averager:\n    def __init__(self):\n        self.current_value = 0.0\n        self.iter = 0.0\n        \n    def send(self, value):\n        self.current_value += value\n        self.iter += 1\n        \n    def value(self):\n        if self.iter == 0:\n            return 0\n        else:\n            return 1.0 * self.current_value / self.iter\n    \n    def reset(self):\n        self.current_value = 0.0\n        self.iter = 0.0","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def initiate_model(num_classes):    \n    \"\"\"Initiates a faster-rcnn pretrained on COCO and changes the head,\n       Returns a model that can we used for the wheat-box-dection task\"\"\"\n    # load a model pre-trained pre-trained on COCO\n    model = torchvision.models.detection.fasterrcnn_resnet50_fpn(pretrained=True)\n    # get number of input features for the classifier & replace pre-trained head with a new one\n    in_features = model.roi_heads.box_predictor.cls_score.in_features\n    model.roi_heads.box_predictor = FastRCNNPredictor(in_features, num_classes)\n    \n    return model\n\ndef get_transform(train):\n    \"\"\"Applies all the transformations to dataset class\"\"\"\n    transform = []\n    #transform.append(ToPILImage())\n    if train:\n        transform.append(RandomHorizontalFlip(0.1))\n        transform.append(RandomRotation((-5,5)))\n        transform.append(ColorJitter(brightness=(0,0.8), hue=0.5, saturation=0.8))\n\n    transform.append(ToTensor())\n    #transform.append(Normalize((0.0,), (1,)))  \n    \n    return Compose(transform)\n\ndef collate_fn(batch):\n    # youtube.com/watch?v=eKp5YH9ltnE\n    return tuple(zip(*batch))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def train():\n    \n    NUM_CLASSES = 2\n    NUM_EPOCS = 5\n    BATCH_SIZE = 1\n    \n    # Train on GPU or CPU\n    device = torch.device('cuda') if torch.cuda.is_available() else torch.device('cpu')\n    print(f'Running on: {device}')\n    \n    # Initiate training and test datasets\n    train_data = WheatDataset(df, DIR_TRAIN,transform=get_transform(train=False))\n    val_data = WheatDataset(df, DIR_TRAIN, transform=get_transform(train=False))\n    \n    # Split dataset in train & test (use 250 images for test)\n    indices = torch.randperm(len(train_data)).tolist()\n    train_data = torch.utils.data.Subset(train_data, indices[:-250])\n    val_data = torch.utils.data.Subset(val_data, indices[-250:])\n    \n    print(f'Number of training examples: {len(train_data)}, Number of validation examples: {len(val_data)}')\n    \n    # Initiate training and test data loaders\n    data_loader_train = torch.utils.data.DataLoader(train_data, batch_size=BATCH_SIZE, \n                                                   shuffle=True, num_workers=2, collate_fn=collate_fn)\n                                               \n    \n    data_loader_val = torch.utils.data.DataLoader(val_data, batch_size=BATCH_SIZE,\n                                                   shuffle=False, num_workers=2, collate_fn=collate_fn)\n    # Initiate model\n    model = initiate_model(NUM_CLASSES)\n    model.to(device)\n    \n    # Get the learnable parameters of our model\n    params = [p for p in model.parameters() if p.requires_grad]\n    # Initaite optimizer on learnable parameters\n    optimizer = torch.optim.SGD(params, lr=0.005, momentum=0.9, weight_decay=0.0005)\n    # Set up a dynamic learning rate in order to converage faser\n    lr_scheduler = torch.optim.lr_scheduler.StepLR(optimizer,\n                                               step_size=3,\n                                               gamma=0.1)\n    \n    # Initiate averager class to keep track of losses\n    loss_hist = Averager()\n    val_loss_hist = Averager()\n    # Set to high value on purpose\n    least_loss = 10e6\n    # Allow model to learn parameters\n    model.train()\n    \n    print(f'Starting training....')\n    \n    for epoch in range(NUM_EPOCS):\n        \n        loss_hist.reset()\n        val_loss_hist.reset()\n        itr = 1\n        \n        for images,targets,img_id in data_loader_train:    \n            \n            images = list(image.to(device) for image in images)  \n            targets = [{k: v.to(device) for k, v in t.items()} for t in targets]\n           \n            #final = []\n            #for target in targets:\n                #result = {}\n                #for k,v in target.items():\n                    #result[k] = v.to(device)\n                #final.append(result)  \n\n            loss_dict = model(images, targets)   \n            \n            losses = sum(loss for loss in loss_dict.values())        \n            loss_value = losses.item()\n            \n            loss_hist.send(loss_value)\n            \n            optimizer.zero_grad()\n            losses.backward()\n            optimizer.step()\n            \n            if itr % 50 == 0:\n                print(f'Iteration #{itr}, loss: {loss_hist.value()}')\n                \n            itr += 1\n            \n        # Validation            \n        for images, targets, img_id in data_loader_val:\n            images = list(image.to(device) for image in images)  \n            targets = [{k: v.to(device) for k, v in t.items()} for t in targets]        \n\n            val_loss_dict = model(images, targets) \n            val_losses = sum(loss for loss in val_loss_dict.values())\n            val_loss_value = val_losses.item()\n            val_loss_hist.send(val_loss_value)\n            \n        if val_loss_value < least_loss:\n            least_loss = val_loss_hist.value()\n            lval = round(least_loss, 4)\n            torch.save(model.state_dict(), f'fastercnn-wheatdetection-epoch:{epoch}-loss:{lval}.pth')\n            torch.save(model.state_dict(), 'best_weights.pth')\n\n        else:\n            if lr_scheduler is not None:\n                lr_scheduler.step()\n\n        print(f\"Epoch #{epoch}, training_loss: {loss_hist.value()}, validation_loss: {val_loss_hist.value()}\")\n            \n    print('Done!')\n    \ntrain()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"class WheatTestDataset(Dataset):\n    \"\"\"Dataclass for wheat dataset\"\"\"\n    \n    def __init__(self, dataframe, img_dir, transform=None):\n        self.dataframe = dataframe\n        self.img_dir = img_dir\n        self.images = dataframe['image_id'].unique()\n        self.transform = transform\n   \n    def __getitem__(self, idx):\n        img_id = self.images[idx]\n\n        img_arr = cv2.imread(f'{self.img_dir}/{img_id}.jpg', cv2.IMREAD_COLOR)\n        img_arr = cv2.cvtColor(img_arr, cv2.COLOR_BGR2RGB).astype(np.uint8)\n        img_arr = img_arr / 255\n        \n        if self.transform is not None:\n            img_arr = self.transform(img_arr)\n            \n        return img_arr, img_id\n            \n    def __len__(self):\n        return len(self.images)\n    \n    \ndef format_prediction_string(boxes, scores):\n    pred_strings = []\n    for j in zip(scores, boxes):\n        pred_strings.append(\"{0:.4f} {1} {2} {3} {4}\".format(j[0], j[1][0], j[1][1], j[1][2], j[1][3]))\n\n    return \" \".join(pred_strings)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def evaluate():\n    \n    WEIGHTS_FILE = '/kaggle/working/best_weights.pth'    \n    DETECTION_THRESHOLD = 0.5\n    BATCH_SIZE = 1\n    NUM_CLASSES = 2\n    \n    results = []\n    \n    # Initiate test dataset \n    test_dataset = WheatTestDataset(df_test, DIR_TEST, get_transform(train=False))\n    \n    # Load model\n    model = initiate_model(NUM_CLASSES)\n    model.load_state_dict(torch.load(WEIGHTS_FILE))\n    model.eval()\n    \n    # Set device (cpu or GPU)\n    device = torch.device('cpu') \n    print(f'Evaluating on {device}')\n    \n    # Dataset itr object\n    data_loader_test = torch.utils.data.DataLoader(test_dataset, batch_size=BATCH_SIZE,\n                                                   shuffle=False, num_workers=2, collate_fn=collate_fn)\n    \n    itr = 1\n    print(f'Starting evaluation...')\n    \n    for images, img_id  in data_loader_test:\n        images = list(image.to(device) for image in images)  \n        output = model(images) \n        \n        if itr % 5 == 0:\n            print(f'Evaluated {itr} images')\n            \n        itr += 1\n        \n        for i, image in enumerate(images):\n\n            boxes = output[i]['boxes'].data.cpu().numpy()\n            scores = output[i]['scores'].data.cpu().numpy()\n\n            boxes = boxes[scores >= DETECTION_THRESHOLD].astype(np.int32)\n            scores = scores[scores >= DETECTION_THRESHOLD]\n            image_id = img_id[i]\n\n            boxes[:, 2] = boxes[:, 2] - boxes[:, 0]\n            boxes[:, 3] = boxes[:, 3] - boxes[:, 1]\n\n            result = {\n                'image_id': image_id,\n                'PredictionString': format_prediction_string(boxes, scores)\n            }\n\n            results.append(result)\n\n    print(f'Writing submision file...')\n    test_df = pd.DataFrame(results, columns=['image_id', 'PredictionString'])\n    test_df.to_csv('submission.csv', index=False)\n    sub.to\n    \n    print(f'Done')\n         \nevaluate()","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}