{"cells":[{"metadata":{},"cell_type":"markdown","source":"<p style=\"font-size:20px\">Before I say anything y'll need to go to <a href=\"https://www.kaggle.com/pestipeti/vinbigdata-fasterrcnn-pytorch-train\">Peter Pesti</a>'s notebook and upvote it right away.<br><br>Also, if you upvote this notebook:<br> There is a chance my girlfriend will take me out for a McDonalds Date after such a long time.😋\n</p>\n\n\n\n<p style=\"font-size:20px\">So,are you here for the first time? <br>If yes:</p>\n<p style=\"font-size:27px;\">Say a Hi 👋🏽 to me on <a href=\"https://www.linkedin.com/in/vyom-bhatia-40ba79181/\">LinkedIn</a><a href=\"https://www.linkedin.com/in/vyom-bhatia-40ba79181/\"><img src=\"https://cdn2.iconfinder.com/data/icons/simple-social-media-shadow/512/14-512.png\" style=\"display:inline-block;width:15%;margin-right:-2vw;margin-left:-2vw;\"></a>!</p>\n\n<p style=\"font-size:20px\">So, what are we really doing here?</p><br>\n<p style=\"font-size:27px\"><b>Object Detection with FasterRCNN 🏃🏽‍♀️👩‍💻</b></p>"},{"metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"cell_type":"markdown","source":"# Importing Libraries\n<p style=\"font-size:20px;\">Importing the libraries in here to started. BTW, this is my first time working with Object Detection and Dicom format images.</p>"},{"metadata":{"trusted":true},"cell_type":"code","source":"\n# You can literally be drunk and yet not forget to import these bad boys.\n\nimport pandas as pd\nimport numpy as np\nimport cv2\nimport os\nimport re\nimport pydicom\nimport warnings\n\n# PIL for the Images.\nfrom PIL import Image\n\n# Transforms for the Transformations.\nimport albumentations as A\nfrom albumentations.pytorch.transforms import ToTensorV2\n\n# Py🔦\nimport torch\nimport torchvision\nfrom torchvision.models.detection.faster_rcnn import FastRCNNPredictor\nfrom torchvision.models.detection import FasterRCNN\nfrom torchvision.models.detection.rpn import AnchorGenerator\nfrom torch.utils.data import DataLoader, Dataset\nfrom torch.utils.data.sampler import SequentialSampler\n\n# Matplot for the visualizations.\nimport matplotlib.pyplot as plt","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"<p style=\"font-size:27px;\">Paths</p>"},{"metadata":{"trusted":true},"cell_type":"code","source":"# Defining the Path where all our files exist:\ndir_input = \"../input/vinbigdata-chest-xray-abnormalities-detection\"\n\n# Defining the Traning Set's Path:\ntrainpath = f'{dir_input}/train'\n\n# Defining the Test Set's Path:\ntestpath = f'{dir_input}/test'","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Preprocessing the Data\n<p style=\"font-size:20px;\">Some really basic preprocessing to make life easier and to let the FRCNN do its job but there are some complications into it. If you really want to take a dive into understanding it better, <a href =\"https://www.youtube.com/watch?v=v5bFVbQvFRk\">check this video out</a>.</p>"},{"metadata":{"trusted":true},"cell_type":"code","source":"# Grabbing the Dataframe which has the image ids.\ntrain_df = pd.read_csv(f\"{dir_input}/train.csv\")\n\n# The file has nul coordinate value there is nothing abnormal/detect lets fix that.\ntrain_df.fillna(0, inplace=True)\n\n#Time to fix the other two coordinate values of the \"No finding\" category\ntrain_df.loc[train_df[\"class_id\"] == 14, [\"x_max\", \"y_max\"]] = 1.0\n\n# The FasterRCNN handles the class_id==0 as the background.\ntrain_df[\"class_id\"] = train_df[\"class_id\"] + 1\n\n# Lets make the \"No finding\" category 0.\ntrain_df.loc[train_df[\"class_id\"] == 15, [\"class_id\"]] = 0\n\nprint(f'The total number of classes are {train_df[\"class_id\"].nunique()}.')\ntrain_df.head()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Visualizing the Data\n"},{"metadata":{"trusted":true},"cell_type":"code","source":"image_ids = train_df['image_id'].unique()\n\n# Lets make the Valid and Training set:\n\n# Grab Everything but the last 3000 ids.\ntrain_ids = image_ids[:-3000]\n\n# Grab the left over 3000 ids.\nvalid_ids = image_ids[-3000:]\n","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"filename = trainpath + \"/\" + train_ids[5] + \".dicom\"\n\nds = pydicom.dcmread(filename)\nplt.xlabel(train_df[\"class_name\"][5])\nplt.imshow(ds.pixel_array, cmap=plt.cm.bone) ","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"filename = trainpath + \"/\" + train_ids[2] + \".dicom\"\n\nds = pydicom.dcmread(filename)\nplt.xlabel(train_df[\"class_name\"][2])\nplt.imshow(ds.pixel_array, cmap=plt.cm.bone) ","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"filename = trainpath + \"/\" + train_ids[1] + \".dicom\"\n\nds = pydicom.dcmread(filename)\nplt.xlabel(train_df[\"class_name\"][1])\nplt.imshow(ds.pixel_array, cmap=plt.cm.bone) ","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"valid_df = train_df[train_df['image_id'].isin(valid_ids)]\ntrain_df = train_df[train_df['image_id'].isin(train_ids)]","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"<p style=\"font-size:27px\">Dataset</p>\n<p style=\"font-size:20px\">Defining the class for the Dataset (Heavily adapted, sorry not sorry. Never mind am I being rude?)"},{"metadata":{"trusted":true},"cell_type":"code","source":"class ObjectDetection(Dataset):\n    \n    def __init__(self, dataframe, image_dir, transforms=None):\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        \n    def __getitem__(self, index):\n        \n        image_id = self.image_ids[index]\n        records = self.df[(self.df['image_id'] == image_id)]\n        records = records.reset_index(drop=True)\n\n        dicom = pydicom.dcmread(f\"{self.image_dir}/{image_id}.dicom\")\n        \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        \n        # Seems like it's the time to Normalize.\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 records.loc[0, \"class_id\"] == 0:\n            records = records.loc[[0], :]\n        \n        boxes = records[['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(records[\"class_id\"].values, dtype=torch.int64)\n\n        # suppose all instances are not crowd\n        iscrowd = torch.zeros((records.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\n    \n    def __len__(self):\n        return self.image_ids.shape[0]","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Data Augmentation\n\n<p style=\"font-size:20px\">Starting with a transformations of the images for data augmentations. Here "},{"metadata":{"trusted":true},"cell_type":"code","source":"def get_train_transform():\n    \n    return A.Compose([\n        \n        A.Flip(0.3),\n        \n        # Normalizing the Values.\n        A.Normalize(mean=(0,0,0), std=(1,1,1),\n                    max_pixel_value = 255.0, p=1.0),\n        # Converting them to tensors.\n        ToTensorV2(p=1.0)\n        \n    ], bbox_params = {'format': 'pascal_voc', \n                      'label_fields': ['labels']})\n\n\ndef get_valid_transform():\n    \n    return A.Compose([\n        \n        # Normalizing the Values.\n        A.Normalize(mean=(0,0,0), std=(1,1,1),\n                    max_pixel_value = 255.0, p=1.0),\n        # Converting them to tensors.\n        ToTensorV2(p=1.0)\n    \n    ], bbox_params = {'format': 'pascal_voc',\n                      'label_fields': ['labels']})","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Grabbing the Model"},{"metadata":{"trusted":true},"cell_type":"code","source":"models = torchvision.models.detection.fasterrcnn_resnet50_fpn(pretrained = True)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Defining the number of classes.\nnum_classes = 15\n\nin_features = models.roi_heads.box_predictor.cls_score.in_features\n\nmodels.roi_heads.box_predictor = FastRCNNPredictor(in_features, num_classes)\n","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def collate_function(batch):\n    return tuple(zip(*batch))\n\n# Finally working to collect all the stuff written up there into datasets.\n\n# Train:\ntrain_dataset = ObjectDetection(train_df, trainpath, get_train_transform())\n\n# Valid:\nvalid_dataset = ObjectDetection(valid_df, trainpath, get_valid_transform())\n\nindices = torch.randperm(len(train_dataset)).tolist()\n\n# And ofcourse the dataloaders.\n\ntrain_data_loader = DataLoader(train_dataset, batch_size=3,\n                               shuffle=True, num_workers=8,\n                               collate_fn=collate_function)\n\nvalid_data_loader = DataLoader(valid_dataset, batch_size=8,\n                               shuffle=False, num_workers=4,\n                               collate_fn=collate_function)\n","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"device = torch.device('cuda')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"images, targets = next(iter(train_data_loader))","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Training the Model"},{"metadata":{"trusted":true},"cell_type":"code","source":"class Averager:\n    def __init__(self):\n        self.current_total = 0.0\n        self.iterations = 0.0\n        \n    def send(self, value):\n        self.current_total += value\n        self.iterations += 1\n    @property\n    def value(self):\n        if self.iterations == 0:\n            return 0\n        else:\n            return 1.0 * self.current_total / self.iterations\n        \n    def reset(self):\n        self.current_total = 0.0\n        self.iterations = 0.0","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"<p style=\"font-size:27px;\">Setting some Hyperparameters</p>"},{"metadata":{"trusted":true},"cell_type":"code","source":"models.to(device)\n\nparams = [p for p in models.parameters()] \noptimizer = torch.optim.SGD(params, lr = 0.005, momentum=0.9, weight_decay=0.0004)\n\nlr_scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=4, gamma=0.1)\n\nnum_epochs = 10","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"<p style=\"font-size:27px;\">Training the Models</p>\n<p style=\"font-size:20px;\"> Well, let's see how the model does."},{"metadata":{"trusted":true},"cell_type":"code","source":"loss_hist = Averager()\nitr = 1\n\nfor epoch in range(num_epochs):\n    loss_hist.reset()\n    \n    for images, targets in train_data_loader:\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        loss_dict = models(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 % 100 == 0:\n            print(\"The Loss of Iteration\", itr,\"is\", loss_hist.value + \".\")\n            \n        itr += 1\n        \n        break\n        \n        \n    if lr_scheduler is not None:\n        lr_scheduler.step()\n        \n    print(\"The loss for Epoch\", epoch, \"is:\", loss_hist.value)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"<p style=\"font-size:20px\">Well, this seems like the end to this notebook.<br><br> Yes. <br>\n    <br>\n    I had been sitting on this one for a while now and felt like just posting it.\n    So, this does feel like lifting a weight off my chest. Juggling College, Deep Learning, \n    NLP and of course startup ideas that seem totally unfeasible but I'd rather still pursue them.\n    Why? Because we are young my friend.</p>\n    \n    "},{"metadata":{},"cell_type":"markdown","source":"<p style=\"font-size:20px\">Please Upvote if you found it useful. It will help me edge further towards my goal of becoming a Data Scientist.<br>Please <a href=\"https://www.kaggle.com/pestipeti/\">check out the author</a> from whom I took some assistance and give them a follow!</p>"}],"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}