{"cells":[{"metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"cell_type":"code","source":"import pandas as pd\nimport numpy as np\nimport cv2\nimport os\nimport re\nimport pydicom\nimport gc\nfrom tqdm import tqdm\n\nfrom PIL import Image\n\nimport albumentations\nfrom albumentations.pytorch.transforms import ToTensorV2\n\nimport torch\nimport torchvision\n\nfrom sklearn import model_selection\n\nfrom torchvision.models.detection.faster_rcnn import FastRCNNPredictor\nfrom torchvision.models.detection import FasterRCNN\nfrom torchvision.models.detection.rpn import AnchorGenerator\n\nfrom torch.utils.data import DataLoader, Dataset\nfrom torch.utils.data.sampler import SequentialSampler\n\nfrom matplotlib import pyplot as plt\n\n\nimport warnings\nwarnings.filterwarnings(\"ignore\")","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# checking if cuda is available\nfrom torch import device as device_\n\ndevice = device_(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nprint(device)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# reading the training data as train dataframe\ntrain = pd.read_csv(\"../input/vinbigdata-chest-xray-abnormalities-detection/train.csv\")\ntrain.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# filling the NaN places in train data with 0\ntrain.fillna(0, inplace=True)\ntrain.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# changing the x_max & y_max columns where the class_name is No finding & class_id is 14 respectively as 1.0\ntrain.loc[train[\"class_id\"] == 14, ['x_max', 'y_max']] = 1.0\ntrain.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# As FasterRCNN handles class_id == 0 as the background \n\n# So first we will increase the value of each class id by 1\ntrain[\"class_id\"] = train[\"class_id\"] + 1\ntrain.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# then make class_id - 15 = class_id 0 \ntrain.loc[train[\"class_id\"] == 15, [\"class_id\"]] = 0\ntrain.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"label_dict = {0 : \"No finding\",\n              1 : \"Aortic enlargement\",\n              2 : \"Atelectasis\",\n              3 : \"Calcification\",\n              4 : \"Cardiomegaly\",\n              5 : \"Consolidation\",\n              6 : \"ILD\",\n              7 : \"Infiltration\",\n              8 : \"Lung Opacity\",\n              9 : \"Nodule/Mass\",\n             10 : \"Other lesion\",\n             11 : \"Pleural effusion\",\n             12 : \"Pleural thickening\",\n             13 : \"Pneumothorax\",\n             14 : \"Pulmonary fibrosis\"\n             }","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# sort by image_id \ntrain.sort_values(by='image_id').head(20)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# checking the dicom image sample\n\ndicom = pydicom.dcmread(\"../input/vinbigdata-chest-xray-abnormalities-detection/train/0007d316f756b3fa0baea2ff514ce945.dicom\")\nimage = dicom.pixel_array * dicom.RescaleSlope + dicom.RescaleIntercept\n\nplt.imshow(image, cmap='gray')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# create folds\ndf = train\nno_of_folds = 5\n\ndf[\"kfold\"] = -1    \ndf = df.sample(frac=1).reset_index(drop=True)\ny = df.class_id.values\nkf = model_selection.StratifiedKFold(n_splits=no_of_folds)\n\nfor f, (t_, v_) in enumerate(kf.split(X=df, y=y)):\n    df.loc[v_, 'kfold'] = f\n    \nfold = 0\ndf_train = df[df.kfold != fold].reset_index(drop=True)\ndf_valid = df[df.kfold == fold].reset_index(drop=True)\n\ndf_valid.head()","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","trusted":true},"cell_type":"code","source":"from torch.utils.data import Dataset\n\nclass xray_dataset(Dataset):\n    def __init__(self, df, transforms = None):\n        self.df = df\n        self.image_ids = df['image_id'].unique()\n        self.transforms = transforms\n\n    def __len__(self):\n        return len(self.image_ids)\n\n    def __getitem__(self, index):\n        image_id = self.image_ids[index]\n        bboxes = self.df[self.df['image_id'] == image_id]\n        bboxes = bboxes.reset_index(drop=True)\n\n        dicom = pydicom.dcmread('../input/vinbigdata-chest-xray-abnormalities-detection/train/'+ self.image_ids[index] +'.dicom')\n        image = dicom.pixel_array\n        \n        if \"PhotometricInterpretation\" in dicom:\n            if dicom.PhotometricInterpretation == \"MONOCHROME1\":\n                image = np.amax(image) - image\n        \n        intercept = dicom.RescaleIntercept if \"RescaleIntercept\" in dicom else 0.0\n        slope = dicom.RescaleSlope if \"RescaleSlope\" in dicom else 1.0\n        \n        if slope != 1:\n            image = slope * image.astype(np.float64)\n            image = image.astype(np.int16)\n        \n        image += np.int16(intercept)        \n        image = np.stack([image, image, image])\n        image = image.astype('float32')\n        image = image - image.min()\n        image = image / image.max()\n        image = image * 255.0\n        image = image.transpose(1,2,0)\n       \n        if bboxes.loc[0, \"class_id\"] == 0:\n            bboxes = bboxes.loc[[0], :]\n        \n        boxes = bboxes[['x_min', 'y_min', 'x_max', 'y_max']].values\n        area = (boxes[:, 3] - boxes[:, 1]) * (boxes[:, 2] - boxes[:, 0])\n        area = torch.as_tensor(area, dtype=torch.float32)\n        labels = torch.tensor(bboxes[\"class_id\"].values, dtype=torch.int64)\n\n        # suppose all instances are not crowd\n        iscrowd = torch.zeros((bboxes.shape[0],), dtype=torch.int64)\n\n        target = {}\n        target['boxes'] = boxes\n        target['labels'] = labels\n        target['image_id'] = torch.tensor([index])\n        target['area'] = area\n        target['iscrowd'] = iscrowd\n\n        if self.transforms:\n            sample = {\n                'image': image,\n                'bboxes': target['boxes'],\n                'labels': labels\n            }\n            sample = self.transforms(**sample)\n            image = sample['image']\n            \n            target['boxes'] = torch.tensor(sample['bboxes'])\n\n        if target[\"boxes\"].shape[0] == 0:\n            # Albumentation cuts the target (class 14, 1x1px in the corner)\n            target[\"boxes\"] = torch.from_numpy(np.array([[0.0, 0.0, 1.0, 1.0]]))\n            target[\"area\"] = torch.tensor([1.0], dtype=torch.float32)\n            target[\"labels\"] = torch.tensor([0], dtype=torch.int64)\n            \n        return image, target","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def get_train_transform():\n    return albumentations.Compose([\n        albumentations.Flip(0.5),\n        albumentations.ShiftScaleRotate(scale_limit=0.1, rotate_limit=45, p=0.25),\n        albumentations.LongestMaxSize(max_size=800, p=1.0),\n\n        # FasterRCNN will normalize.\n        albumentations.Normalize(mean=(0, 0, 0), std=(1, 1, 1), max_pixel_value=255.0, p=1.0),\n        ToTensorV2(p=1.0)\n    ], bbox_params={'format': 'pascal_voc', 'label_fields': ['labels']})\n\ndef get_valid_transform():\n    return albumentations.Compose([\n        albumentations.Normalize(mean=(0, 0, 0), std=(1, 1, 1), max_pixel_value=255.0, p=1.0),\n        ToTensorV2(p=1.0)\n    ], bbox_params={'format': 'pascal_voc', 'label_fields': ['labels']})","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"train_data = xray_dataset(df_train, get_train_transform())\nval_data = xray_dataset(df_valid, get_valid_transform())","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"idx = 100\nimg = train_data[idx][0].permute(1, 2, 0).cpu().numpy()\nboxes = train_data[idx][1][\"boxes\"].cpu().numpy().astype(np.int32)\nlabels = train_data[idx][1][\"labels\"].cpu().numpy().astype(np.int32)\n\nfig, ax = plt.subplots(1, 1, figsize = (12,6))\n\nfor box, label in zip(boxes, labels):\n    cv2.rectangle(img,\n                (box[0], box[1]),\n                (box[2], box[3]),\n                (220, 0, 0), \n                 1)\n    cv2.putText(img, \n                label_dict[label], \n                (box[0], box[1]-10), \n                cv2.FONT_HERSHEY_SIMPLEX, \n                0.9, \n                (220, 0, 0), \n                3)\n      \nax.set_axis_off()\nax.imshow(img)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def collate_fn(batch):\n    return tuple(zip(*batch))\n\ntraining_dataloader = DataLoader(\n    train_data,\n    batch_size=8,\n    shuffle=True,\n    num_workers=4,\n    collate_fn=collate_fn,\n    drop_last=True\n)\n\nval_dataloader = DataLoader(\n    val_data,\n    batch_size=8,\n    shuffle=False,\n    num_workers=4,\n    collate_fn=collate_fn,\n    drop_last=True\n)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# load a model; pre-trained on COCO\nmodel = torchvision.models.detection.fasterrcnn_resnet50_fpn(pretrained=True)\nnum_classes = 15\n\n# get number of input features for the classifier\nin_features = model.roi_heads.box_predictor.cls_score.in_features\n\n# replace the pre-trained head with a new one\nmodel.roi_heads.box_predictor = FastRCNNPredictor(in_features, num_classes)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"model.to(device)\nparams = [p for p in model.parameters() if p.requires_grad]\noptimizer = torch.optim.SGD(params, lr=0.005, momentum=0.9, weight_decay=0.0005)\nscheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=4, gamma=0.1)\n\nnum_epochs = 5","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# defining the training loop\ndef train_loop_fn(data_loader, model, optimizer, device, scheduler=None):\n    running_loss = 0.0\n    \n    for images, labels in tqdm(data_loader):\n        \n        images = list(image.to(device) for image in images)\n        labels = [{k: v.to(device) for k, v in l.items()} for l in labels]\n        \n        optimizer.zero_grad()\n\n        loss_dict = model(images, labels)\n\n        losses = sum(loss for loss in loss_dict.values())\n\n        losses.backward()\n        optimizer.step()\n\n        running_loss += losses.item()\n        \n        del images, labels\n        gc.collect()\n        torch.cuda.empty_cache()\n            \n    train_loss = running_loss / float(len(train_data))\n    scheduler.step(train_loss)\n    \n    return train_loss","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def eval_loop_fn(data_loader, model, device):\n    running_loss = 0.0\n    \n    for images, labels in tqdm(data_loader):\n        \n        images = list(image.to(device) for image in images)\n        labels = [{k: v.to(device) for k, v in l.items()} for l in labels]\n        \n        loss_dict = model(images, labels)\n        losses = sum(loss for loss in loss_dict.values())\n\n        running_loss += losses.item()\n        \n        del images , labels\n        gc.collect()\n        torch.cuda.empty_cache()\n    \n    valid_loss = running_loss / float(len(val_data))\n    \n    return valid_loss","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def _run():\n    no_of_folds = 5\n    for i in range(no_of_folds):\n        a_string = \"*\" * 20\n\n        print(a_string, \" FOLD NUMBER \", i, a_string)\n        \n        df_train = df[df.kfold != no_of_folds].reset_index(drop=True)\n        df_valid = df[df.kfold == no_of_folds].reset_index(drop=True)\n        \n        all_losses = []\n        \n        for epoch in range(num_epochs):\n            print(f\"Epoch --> {epoch+1} / {num_epochs}\")\n            print(f\"-------------------------------\")\n\n            train_loss = train_loop_fn(training_dataloader, model, optimizer, device, scheduler)\n            print('training Loss: {:.4f}'.format(train_loss))\n\n            valid_loss = eval_loop_fn(val_dataloader, model, device)\n            print('validation Loss: {:.4f}'.format(valid_loss))\n            \n            all_losses.append(valid_loss)\n        print()\n        \n        if i < 1:\n            best_loss = min(all_losses)\n            best_model = copy.deepcopy(model)\n        else:\n            if best_loss < min(all_losses):\n                continue\n            else:\n                best_loss = min(all_losses)\n                best_model = copy.deepcopy(model)\n        \n    print('\\n======================Saving the best model==============================')\n    torch.save(best_model,'./xray_FRCNN_model.bin')\n    print()\n    print('& The lowest loss across we got among all the folds is : {:.4f}'.format(best_loss))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"if __name__ == \"__main__\":\n    _run()","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}