{"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":"# Faster RCNN Implementation on VinBIGData X-Ray Images using PyTorch Lightning Module\nMake sure to turn on GPU and internet in the right side bar on Kaggle","metadata":{}},{"cell_type":"code","source":"!pip install pytorch_lightning","metadata":{"execution":{"iopub.status.busy":"2022-08-01T12:58:33.694403Z","iopub.execute_input":"2022-08-01T12:58:33.695342Z","iopub.status.idle":"2022-08-01T12:58:46.273236Z","shell.execute_reply.started":"2022-08-01T12:58:33.695225Z","shell.execute_reply":"2022-08-01T12:58:46.272163Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"!pip install pytorch-lightning==1.2.2","metadata":{"execution":{"iopub.status.busy":"2022-08-01T13:04:46.821859Z","iopub.execute_input":"2022-08-01T13:04:46.82223Z","iopub.status.idle":"2022-08-01T13:04:58.856879Z","shell.execute_reply.started":"2022-08-01T13:04:46.822201Z","shell.execute_reply":"2022-08-01T13:04:58.855786Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import pandas as pd\nimport numpy as np\nimport cv2\nimport os\n\nimport albumentations as A\nfrom albumentations.pytorch.transforms import ToTensorV2\n\nimport torch\nimport torchvision\nfrom torchvision.ops import box_iou, MultiScaleRoIAlign\nfrom torchvision import models\n\nfrom torchvision.models.detection.faster_rcnn import FastRCNNPredictor\n\nfrom torch.utils.data import DataLoader, Dataset\nimport pytorch_lightning as pl\nfrom pytorch_lightning.metrics import AveragePrecision\n\nimport gc\n\nSEED=2484\nDEVICE=torch.device('cuda')\npl.utilities.seed.seed_everything(SEED)","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","scrolled":true,"execution":{"iopub.status.busy":"2022-08-01T13:05:03.281388Z","iopub.execute_input":"2022-08-01T13:05:03.281768Z","iopub.status.idle":"2022-08-01T13:05:03.314917Z","shell.execute_reply.started":"2022-08-01T13:05:03.281733Z","shell.execute_reply":"2022-08-01T13:05:03.313973Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"print(DEVICE)","metadata":{"execution":{"iopub.status.busy":"2022-08-01T07:27:38.730446Z","iopub.execute_input":"2022-08-01T07:27:38.730821Z","iopub.status.idle":"2022-08-01T07:27:38.735589Z","shell.execute_reply.started":"2022-08-01T07:27:38.730781Z","shell.execute_reply":"2022-08-01T07:27:38.73468Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Generate list of image filenames for train, valid and test sets\nLists are generated from the train directory containing png files.","metadata":{}},{"cell_type":"code","source":"filepaths = []\n\nfor dirname, _, filenames in os.walk('../input/vinbigdata-chest-xray-original-png/train'):\n    for filename in filenames:\n        filepaths.append(filename)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Assign 80% of the data to train set, 10% to validation set and 10% to test set.","metadata":{}},{"cell_type":"code","source":"trainNum = int(0.8 * len(filenames))\nvalidNum = int(0.1 * len(filenames))\ntestNum = int(0.1 * len(filenames))","metadata":{"execution":{"iopub.status.busy":"2022-08-01T07:29:19.986465Z","iopub.execute_input":"2022-08-01T07:29:19.986851Z","iopub.status.idle":"2022-08-01T07:29:19.991707Z","shell.execute_reply.started":"2022-08-01T07:29:19.986815Z","shell.execute_reply":"2022-08-01T07:29:19.990632Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"trainImages = filepaths[:trainNum]\n\ntrainFiles = []\n\nfor image in trainImages:\n    newImage = image.split('.')[0]\n    trainFiles.append(newImage)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Read in given train data\nWe are using this as our entire train, validation and test datasets.\nSpecify datatypes to save memory when reading the dataset.","metadata":{}},{"cell_type":"code","source":"df_dtypes = {\n    'image_id': 'string',\n    'class_name': 'string',\n    'class_id': 'int64',\n    'rad_id': 'string',\n    'x_min': 'float64',\n    'y_min': 'float64',\n    'x_max': 'float64',\n    'y_max': 'float64'\n}\n\nall_df = pd.read_csv('../input/vinbigdata-chest-xray-abnormalities-detection/train.csv', dtype = df_dtypes)\ntrain_df = all_df[all_df['image_id'].isin(trainFiles)].reset_index(drop = True)\ntrain_df","metadata":{"execution":{"iopub.status.busy":"2022-08-01T07:29:46.898757Z","iopub.execute_input":"2022-08-01T07:29:46.899123Z","iopub.status.idle":"2022-08-01T07:29:47.140623Z","shell.execute_reply.started":"2022-08-01T07:29:46.899091Z","shell.execute_reply":"2022-08-01T07:29:47.139717Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"validImages = filepaths[trainNum : trainNum + validNum]\n\nvalidFiles = []\n\nfor image in validImages:\n    newImage = image.split('.')[0]\n    validFiles.append(newImage)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Create subset of train dataset for validation using list of validation image filenames.","metadata":{}},{"cell_type":"code","source":"valid_df = all_df[all_df['image_id'].isin(validFiles)].reset_index(drop = True)\nvalid_df","metadata":{"execution":{"iopub.status.busy":"2022-08-01T07:30:01.010512Z","iopub.execute_input":"2022-08-01T07:30:01.010867Z","iopub.status.idle":"2022-08-01T07:30:01.046414Z","shell.execute_reply.started":"2022-08-01T07:30:01.010832Z","shell.execute_reply":"2022-08-01T07:30:01.045706Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"testImages = filepaths[trainNum + validNum :]\n\ntestFiles = []\n\nfor image in testImages:\n    testImage = image.split('.')[0]\n    testFiles.append(testImage)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Create subset of train dataset for testing using list of test image filenames.","metadata":{}},{"cell_type":"code","source":"test_df = all_df[all_df['image_id'].isin(testFiles)].reset_index(drop = True)\ntest_df","metadata":{"execution":{"iopub.status.busy":"2022-08-01T07:30:14.587405Z","iopub.execute_input":"2022-08-01T07:30:14.587752Z","iopub.status.idle":"2022-08-01T07:30:14.623615Z","shell.execute_reply.started":"2022-08-01T07:30:14.587718Z","shell.execute_reply":"2022-08-01T07:30:14.622724Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Set directory to read images from.","metadata":{}},{"cell_type":"code","source":"train_dir = '../input/vinbigdata-chest-xray-original-png/train'","metadata":{"execution":{"iopub.status.busy":"2022-08-01T07:30:20.574633Z","iopub.execute_input":"2022-08-01T07:30:20.575056Z","iopub.status.idle":"2022-08-01T07:30:20.579416Z","shell.execute_reply.started":"2022-08-01T07:30:20.575008Z","shell.execute_reply":"2022-08-01T07:30:20.578506Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Modelling\nCode is adapted from: https://www.kaggle.com/code/sanjayarvind/fasterrcnn-pytorch-lightning/notebook","metadata":{}},{"cell_type":"markdown","source":"## Image preprocessing","metadata":{}},{"cell_type":"code","source":"class imageProcessing(Dataset):\n    def __init__(self, dataframe, image_dir, transforms=None, phase='train'):\n        super().__init__()\n\n        self.image_ids = dataframe['image_id'].unique()\n        self.df = dataframe\n        self.image_dir = image_dir\n        self.transforms = transforms\n        self.phase = phase\n\n    def __getitem__(self, idx):\n        # select one image id from relevant dataframe\n        image_id = self.image_ids[idx]\n        # select all rows for that image from relevant dataframe\n        records = self.df[self.df['image_id'] == image_id]\n        \n        # read in images, convert to tensor datatype and normalise\n        image = cv2.imread(f'{self.image_dir}/{image_id}.png', cv2.IMREAD_COLOR)\n        image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB).astype(np.float32) /= 255.0\n        \n        # run for validation and test sets\n        if self.phase == 'test':\n            if self.transforms:\n                sample = {\n                    'image': image,\n                }\n                \n                # transform image to tensor to input to PyTorch model\n                sample = self.transforms(**sample)\n                image = sample['image']\n            \n            # delete variables to save memory\n            del records, sample\n            gc.collect()\n            \n            return image, image_id\n        \n        # select box coordinates from dataframe rows for this image\n        boxes = records[['x_min', 'y_min', 'x_max', 'y_max']].values\n        \n        # calculate area of bounding box\n        height = boxes[:, 3] - boxes[:, 1]\n        width = boxes[:, 2] - boxes[:, 0]\n        area = height * width\n        # convert to area to tensor\n        area = torch.as_tensor(area, dtype=torch.float32)\n        \n        # shift all class_ids up by 1\n        labels = torch.squeeze(torch.as_tensor((records.class_id.values + 1), dtype=torch.int64))\n        # make tensor of zeros to fill in coordinates for bounding box where there is no finding in image\n        background = torch.zeros((records.shape[0],), dtype=torch.int64)\n        \n        target = {}\n        target['boxes'] = boxes\n        target['labels'] = labels\n        target['area'] = area\n        target['image_id'] = torch.tensor([idx])\n        target['background'] = background\n        \n        if self.transforms:\n            sample = {\n                'image': image,\n                'bboxes': target['boxes'],\n                'labels': labels\n            }\n            # apply transform function to convert data to tensor\n            sample = self.transforms(**sample)\n            image = sample['image']\n            \n            target['boxes'] = torch.as_tensor(sample['bboxes'])\n            \n        # delete unnecessary variables to save memory    \n        del image_id, records, boxes, area, labels, background, sample\n        gc.collect()\n\n        return image, target\n\n    # compute number of records for this image\n    def __len__(self):\n        return self.image_ids.shape[0]","metadata":{"execution":{"iopub.status.busy":"2022-08-01T07:30:24.271886Z","iopub.execute_input":"2022-08-01T07:30:24.272261Z","iopub.status.idle":"2022-08-01T07:30:24.287565Z","shell.execute_reply.started":"2022-08-01T07:30:24.272211Z","shell.execute_reply":"2022-08-01T07:30:24.286665Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Transform input images to tensors","metadata":{}},{"cell_type":"code","source":"def get_train_transform():\n    return A.Compose([\n        A.Flip(0.5),\n        ToTensorV2(p=1.0)\n    ], bbox_params={'format': 'pascal_voc', 'label_fields': ['labels']})\n\ndef get_valid_transform():\n    return A.Compose([\n        ToTensorV2(p=1.0)\n    ])","metadata":{"execution":{"iopub.status.busy":"2022-08-01T09:11:51.977027Z","iopub.execute_input":"2022-08-01T09:11:51.977502Z","iopub.status.idle":"2022-08-01T09:11:51.990197Z","shell.execute_reply.started":"2022-08-01T09:11:51.977462Z","shell.execute_reply":"2022-08-01T09:11:51.98909Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Create file to log train losses","metadata":{}},{"cell_type":"code","source":"!touch loss_train.log","metadata":{"execution":{"iopub.status.busy":"2022-08-01T09:12:12.484702Z","iopub.execute_input":"2022-08-01T09:12:12.485131Z","iopub.status.idle":"2022-08-01T09:12:13.213762Z","shell.execute_reply.started":"2022-08-01T09:12:12.48509Z","shell.execute_reply":"2022-08-01T09:12:13.212384Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Create batch of images for each model step","metadata":{}},{"cell_type":"code","source":"def collate_fn(batch):\n    return tuple(zip(*batch))","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Create LightningModule class to run model","metadata":{}},{"cell_type":"code","source":"class VinDetector(pl.LightningModule):\n    def __init__(self, lr, **kwargs):\n        super().__init__()\n        \n        # download pretrained Faster R-CNN model with ResNet50 CNN architecture\n        self.model = models.detection.fasterrcnn_resnet50_fpn(pretrained = True)\n        # 14 abnormalities + background class\n        num_classes = 15\n        in_features = self.model.roi_heads.box_predictor.cls_score.in_features\n        # train head of model while freezing learned layers\n        self.model.roi_heads.box_predictor = FastRCNNPredictor(in_features, num_classes)\n        \n        # hyperparamters -- can change these\n        self.learning_rate = lr\n        self.batch_size = 4\n\n    # run during inference\n    def forward(self, x):\n        return self.model(x)\n    \n    # get dataframe and directory for image processing\n    def prepare_data(self):\n        df = train_df\n        # drop all rows where there is no finding to avoid model attempting to detect these\n        df = df[df['class_id'] != 14].reset_index(drop=True)\n        # process dataframe and images for inputting to model\n        self.train_dataset = imageProcessing(df, '../input/vinbigdata-chest-xray-original-png/train', get_train_transform())\n    \n    # returns iterable form of train dataset\n    def train_dataloader(self):\n        return DataLoader(self.train_dataset, batch_size=self.batch_size, shuffle=True, pin_memory=True, num_workers=4, collate_fn=collate_fn)\n    \n    # calculate loss for each batch of train images\n    def training_step(self, batch, batch_idx):\n        images, targets = batch\n        targets = [{k: v for k, v in t.items()} for t in targets]\n        loss_dict = self.model(images, targets)\n        loss = sum(loss for loss in loss_dict.values())\n        \n        # display progress on progress bar for each batch\n        self.log('Loss', loss, on_step=True, on_epoch=True, prog_bar=True)\n        \n        # log train loss for each batch in file\n        file = open('./loss_normalised.log', \"a\")\n        file.write(f'{batch_idx}:{loss}\\n')\n        file.close()\n        \n        # delete unnecessary variables to save memory\n        del images, targets, loss_dict\n        gc.collect()\n        \n        return {\"loss\": loss}\n    \n    # choose optimisers for stochastic gradient descent and learning rate\n    def configure_optimizers(self):\n        optimizer = torch.optim.SGD(self.model.parameters(), lr=self.learning_rate, momentum=0.95, weight_decay=1e-5, nesterov=True)\n        scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=6, eta_min=0, verbose=True)\n        return [optimizer], [scheduler]","metadata":{"execution":{"iopub.status.busy":"2022-08-01T09:12:55.437326Z","iopub.execute_input":"2022-08-01T09:12:55.437746Z","iopub.status.idle":"2022-08-01T09:12:55.453454Z","shell.execute_reply.started":"2022-08-01T09:12:55.437703Z","shell.execute_reply":"2022-08-01T09:12:55.452259Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Train Model","metadata":{}},{"cell_type":"code","source":"learning_rate = 0.001\n# create model\nfirst_model = VinDetector(learning_rate)","metadata":{"execution":{"iopub.status.busy":"2022-08-01T09:13:00.75349Z","iopub.execute_input":"2022-08-01T09:13:00.753889Z","iopub.status.idle":"2022-08-01T09:13:02.175682Z","shell.execute_reply.started":"2022-08-01T09:13:00.753853Z","shell.execute_reply":"2022-08-01T09:13:02.1747Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Train model","metadata":{}},{"cell_type":"code","source":"trainers = pl.Trainer(max_epochs=1, gpus=1, progress_bar_refresh_rate=100)\ntrainers.fit(first_model)","metadata":{"execution":{"iopub.status.busy":"2022-08-01T09:13:05.492392Z","iopub.execute_input":"2022-08-01T09:13:05.492763Z","iopub.status.idle":"2022-08-01T09:46:06.365758Z","shell.execute_reply.started":"2022-08-01T09:13:05.492728Z","shell.execute_reply":"2022-08-01T09:46:06.364731Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Check log contents\nBatch index:batch loss","metadata":{}},{"cell_type":"code","source":"!head loss_train.log","metadata":{"scrolled":true,"execution":{"iopub.status.busy":"2022-08-01T09:47:02.210037Z","iopub.execute_input":"2022-08-01T09:47:02.210562Z","iopub.status.idle":"2022-08-01T09:47:02.941684Z","shell.execute_reply.started":"2022-08-01T09:47:02.210508Z","shell.execute_reply":"2022-08-01T09:47:02.940624Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Testing model\nProcess dataframe and images, then get iterable dataloader","metadata":{}},{"cell_type":"code","source":"test_dataset = imageProcessing(test_df, '../input/vinbigdata-chest-xray-original-png/train', get_valid_transform(), phase='test')\ntest_data_loader = DataLoader(test_dataset, batch_size=8, shuffle=False, num_workers=4, collate_fn=collate_fn)","metadata":{"execution":{"iopub.status.busy":"2022-08-01T09:47:36.859598Z","iopub.execute_input":"2022-08-01T09:47:36.860055Z","iopub.status.idle":"2022-08-01T09:47:36.873347Z","shell.execute_reply.started":"2022-08-01T09:47:36.860013Z","shell.execute_reply":"2022-08-01T09:47:36.87171Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Run model for inference","metadata":{}},{"cell_type":"code","source":"detection_threshold = 0.5\nresults = []\n\nfirst_model.model.to(DEVICE)\nfirst_model.model.eval()","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"with torch.no_grad():\n    for images, image_ids in test_data_loader:\n        # send each image batch to GPU\n        images = list(image.to(DEVICE) for image in images)\n        # save dict of predictions from this batch of images\n        outputs = first_model.model(images)\n        \n        # loop through each image in batch\n        for i, image in enumerate(images):\n            # set flag for new prediction\n            a = True\n            \n            image_id = image_ids[i]\n            # create dict for prediction with 'no finding' as default\n            result = {\n                'image_id': image_id,\n                'class_id': 14,\n                'scores': 1.0,\n                'x_min': 0,\n                'y_min': 0,\n                'x_max': 1,\n                'y_max': 1\n            }\n            \n            # get predicted bounding box coordinates, classes and classifcation probabilities\n            boxes = outputs[i]['boxes'].data.cpu().numpy()\n            labels = outputs[i]['labels'].data.cpu().numpy()\n            scores = outputs[i]['scores'].data.cpu().numpy()\n            \n            # at least one object was detected\n            if len(boxes) > 0:\n                # shift all class_ids down by 1\n                labels = labels - 1\n                # map all '-1's to 14, meaning 'no finding'\n                labels[labels == -1] = 14\n                \n                # only accept detections with classification probability >= 0.5\n                selected = scores >= detection_threshold\n                boxes = boxes[selected].astype(np.int32)\n                scores = scores[selected]\n                labels = labels[selected]\n                \n                # check there is still at least one detected object\n                if len(boxes) > 0:\n                    # switch flag to add to results\n                    a = False\n                    \n                    # loop through each detection\n                    for i in zip(labels, boxes, scores):\n                        # create dict for each prediction\n                        result = {\n                            'image_id': image_id,\n                            'class_id': i[0],\n                            'scores': i[2],\n                            'x_min': i[1][0],\n                            'y_min': i[1][1],\n                            'x_max': i[1][2],\n                            'y_max': i[1][3]\n                        }\n                        results.append(result)\n                    \n            # for no new detection\n            if a is True:\n                results.append(result)","metadata":{"execution":{"iopub.status.busy":"2022-08-01T09:47:41.815721Z","iopub.execute_input":"2022-08-01T09:47:41.816104Z","iopub.status.idle":"2022-08-01T10:04:37.164561Z","shell.execute_reply.started":"2022-08-01T09:47:41.81607Z","shell.execute_reply":"2022-08-01T10:04:37.163396Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Convert list of dictionaries to dataframe","metadata":{}},{"cell_type":"code","source":"results_df = pd.DataFrame(results)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Save detections to csv file\nThis dataframe is used for drawing detected bounding boxes over images in the next notebook","metadata":{}},{"cell_type":"code","source":"results_df.to_csv('./detections.csv', index=False)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Formatting dataframes\nMap class_ids to class names","metadata":{}},{"cell_type":"code","source":"classes = {     \n    0: 'Aortic enlargement',\n    1: 'Atelectasis',\n    2: 'Cardiomegaly',\n    3: 'Calcification',\n    4: 'Consolidation',\n    5: 'ILD',\n    6: 'Infiltration',\n    7: 'Lung Opacity',\n    8: 'Nodule/Mass',\n    9: 'Other lesion',\n    10: 'Pleural effusion',\n    11: 'Pleural thickening',\n    12: 'Pneumothorax',\n    13: 'Pulmonary fibrosis',\n    14: 'No finding'\n}","metadata":{"execution":{"iopub.status.busy":"2022-08-01T05:55:12.784618Z","iopub.execute_input":"2022-08-01T05:55:12.785067Z","iopub.status.idle":"2022-08-01T05:55:12.790533Z","shell.execute_reply.started":"2022-08-01T05:55:12.785016Z","shell.execute_reply":"2022-08-01T05:55:12.789516Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"results_df['class_name'] = results_df['class_id'].map(classes)","metadata":{"execution":{"iopub.status.busy":"2022-08-01T05:55:16.343941Z","iopub.execute_input":"2022-08-01T05:55:16.344313Z","iopub.status.idle":"2022-08-01T05:55:16.351189Z","shell.execute_reply.started":"2022-08-01T05:55:16.344276Z","shell.execute_reply":"2022-08-01T05:55:16.350231Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"pred_df = results_df.copy()\npred_df","metadata":{"execution":{"iopub.status.busy":"2022-08-01T05:55:18.584434Z","iopub.execute_input":"2022-08-01T05:55:18.584805Z","iopub.status.idle":"2022-08-01T05:55:18.610606Z","shell.execute_reply.started":"2022-08-01T05:55:18.584751Z","shell.execute_reply":"2022-08-01T05:55:18.609875Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Read in meta data about all images","metadata":{}},{"cell_type":"code","source":"all_meta = pd.read_csv('../input/vinbigdata-chest-xray-original-png/train_meta.csv')\nall_meta","metadata":{"execution":{"iopub.status.busy":"2022-08-01T05:55:30.423758Z","iopub.execute_input":"2022-08-01T05:55:30.424171Z","iopub.status.idle":"2022-08-01T05:55:30.469942Z","shell.execute_reply.started":"2022-08-01T05:55:30.424128Z","shell.execute_reply":"2022-08-01T05:55:30.468769Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Join image dimensions from meta dataframe to detections","metadata":{}},{"cell_type":"code","source":"det_df = pd.merge(pred_df, all_meta, on = 'image_id')\ndet_df","metadata":{"execution":{"iopub.status.busy":"2022-08-01T05:55:33.907535Z","iopub.execute_input":"2022-08-01T05:55:33.907921Z","iopub.status.idle":"2022-08-01T05:55:33.949184Z","shell.execute_reply.started":"2022-08-01T05:55:33.907873Z","shell.execute_reply":"2022-08-01T05:55:33.948263Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Normalise the bounding box coordinates","metadata":{}},{"cell_type":"code","source":"det_df['x_min_norm'] = det_df['x_min'] / det_df['dim0']\ndet_df['x_max_norm'] = det_df['x_max'] / det_df['dim0']\ndet_df['y_min_norm'] = det_df['y_min'] / det_df['dim1']\ndet_df['y_max_norm'] = det_df['y_max'] / det_df['dim1']\ndet_df","metadata":{"execution":{"iopub.status.busy":"2022-08-01T05:55:37.330256Z","iopub.execute_input":"2022-08-01T05:55:37.330653Z","iopub.status.idle":"2022-08-01T05:55:37.367583Z","shell.execute_reply.started":"2022-08-01T05:55:37.330616Z","shell.execute_reply":"2022-08-01T05:55:37.366641Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"ann_df = test_df.copy()\nann_df","metadata":{"execution":{"iopub.status.busy":"2022-08-01T05:55:41.058385Z","iopub.execute_input":"2022-08-01T05:55:41.058758Z","iopub.status.idle":"2022-08-01T05:55:41.084268Z","shell.execute_reply.started":"2022-08-01T05:55:41.058721Z","shell.execute_reply":"2022-08-01T05:55:41.083336Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"gt_df = pd.merge(ann_df, all_meta, on = 'image_id')\ngt_df","metadata":{"execution":{"iopub.status.busy":"2022-08-01T05:55:44.604443Z","iopub.execute_input":"2022-08-01T05:55:44.604814Z","iopub.status.idle":"2022-08-01T05:55:44.642176Z","shell.execute_reply.started":"2022-08-01T05:55:44.604763Z","shell.execute_reply":"2022-08-01T05:55:44.641171Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"gt_df['x_min_norm'] = gt_df['x_min'] / gt_df['dim0']\ngt_df['x_max_norm'] = gt_df['x_max'] / gt_df['dim0']\ngt_df['y_min_norm'] = gt_df['y_min'] / gt_df['dim1']\ngt_df['y_max_norm'] = gt_df['y_max'] / gt_df['dim1']\ngt_df","metadata":{"execution":{"iopub.status.busy":"2022-08-01T05:55:48.328064Z","iopub.execute_input":"2022-08-01T05:55:48.328429Z","iopub.status.idle":"2022-08-01T05:55:48.36605Z","shell.execute_reply.started":"2022-08-01T05:55:48.328394Z","shell.execute_reply":"2022-08-01T05:55:48.3652Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Calculate mean Average Precision and Average Precision\nReformat actual and detected values","metadata":{}},{"cell_type":"code","source":"ann = gt_df[['image_id', 'class_name', 'x_min_norm', 'x_max_norm', 'y_min_norm', 'y_max_norm']].values\ndet = det_df[['image_id', 'class_name', 'scores', 'x_min_norm', 'x_max_norm', 'y_min_norm', 'y_max_norm']].values","metadata":{"execution":{"iopub.status.busy":"2022-08-01T05:55:59.006562Z","iopub.execute_input":"2022-08-01T05:55:59.006953Z","iopub.status.idle":"2022-08-01T05:55:59.019105Z","shell.execute_reply.started":"2022-08-01T05:55:59.0069Z","shell.execute_reply":"2022-08-01T05:55:59.017916Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"!pip install map_boxes","metadata":{"execution":{"iopub.status.busy":"2022-07-31T11:40:11.836978Z","iopub.execute_input":"2022-07-31T11:40:11.837334Z","iopub.status.idle":"2022-07-31T11:40:20.21168Z","shell.execute_reply.started":"2022-07-31T11:40:11.837303Z","shell.execute_reply":"2022-07-31T11:40:20.210769Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"from map_boxes import mean_average_precision_for_boxes","metadata":{"execution":{"iopub.status.busy":"2022-07-31T11:40:53.206788Z","iopub.execute_input":"2022-07-31T11:40:53.207166Z","iopub.status.idle":"2022-07-31T11:40:55.885965Z","shell.execute_reply.started":"2022-07-31T11:40:53.207133Z","shell.execute_reply":"2022-07-31T11:40:55.884858Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Classes, average precisions, actual objects","metadata":{}},{"cell_type":"code","source":"mean_ap, average_precisions = mean_average_precision_for_boxes(ann, det)","metadata":{"execution":{"iopub.status.busy":"2022-07-31T12:49:30.423597Z","iopub.execute_input":"2022-07-31T12:49:30.423954Z","iopub.status.idle":"2022-07-31T12:49:30.613235Z","shell.execute_reply.started":"2022-07-31T12:49:30.423923Z","shell.execute_reply":"2022-07-31T12:49:30.611795Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Class distributions","metadata":{}},{"cell_type":"code","source":"import matplotlib.pyplot as plt\nimport seaborn as sns","metadata":{"execution":{"iopub.status.busy":"2022-07-31T14:01:46.440725Z","iopub.execute_input":"2022-07-31T14:01:46.441083Z","iopub.status.idle":"2022-07-31T14:01:46.506386Z","shell.execute_reply.started":"2022-07-31T14:01:46.44105Z","shell.execute_reply":"2022-07-31T14:01:46.505656Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Predicted class distributions","metadata":{}},{"cell_type":"code","source":"plt.figure(figsize=(6, 6))\nsns.countplot(y = 'class_name', data = det_df, )\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2022-07-31T14:21:17.630683Z","iopub.execute_input":"2022-07-31T14:21:17.631032Z","iopub.status.idle":"2022-07-31T14:21:17.815255Z","shell.execute_reply.started":"2022-07-31T14:21:17.631002Z","shell.execute_reply":"2022-07-31T14:21:17.814436Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Actual class distribution","metadata":{}},{"cell_type":"code","source":"plt.figure(figsize=(6, 6))\nsns.countplot(y = 'class_name', data = gt_df)\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2022-07-31T14:21:37.403702Z","iopub.execute_input":"2022-07-31T14:21:37.404041Z","iopub.status.idle":"2022-07-31T14:21:37.620245Z","shell.execute_reply.started":"2022-07-31T14:21:37.40401Z","shell.execute_reply":"2022-07-31T14:21:37.619546Z"},"trusted":true},"execution_count":null,"outputs":[]}]}