{"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":"code","source":"import os\nimport sys\nimport glob\nimport json\n\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\n\nfrom tqdm.notebook import tqdm\nfrom joblib import Parallel, delayed\n\nimport cv2\nimport pydicom\n\nimport torch\nfrom torch import nn\nfrom torch.utils.data import Dataset, DataLoader\nfrom torchvision import transforms, models\n\nfrom sklearn.metrics import precision_score, recall_score","metadata":{"execution":{"iopub.status.busy":"2022-12-02T16:44:50.43354Z","iopub.execute_input":"2022-12-02T16:44:50.434203Z","iopub.status.idle":"2022-12-02T16:44:53.11274Z","shell.execute_reply.started":"2022-12-02T16:44:50.434095Z","shell.execute_reply":"2022-12-02T16:44:53.111789Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Save DCM file to PNG  \nReference: https://www.kaggle.com/code/theoviel/dicom-resized-png-jpg\n\nThis data has already been converted (resized png/jpg) and uploaded as a Kaggle Dataset by the user [here](https://www.kaggle.com/code/theoviel/dicom-resized-png-jpg?scriptVersionId=112537192&cellId=1) , so let's take the advantage of this resource instead.","metadata":{}},{"cell_type":"code","source":"def dicom_to_png(file_path, img_size=512, save_folder=\"\", extension=\"png\"):\n    patient = file_path.split('/')[-2]\n    image = file_path.split('/')[-1][:-4]\n    \n    dicom = pydicom.dcmread(file_path)\n    img = dicom.pixel_array\n    \n    img = (img - img.min()) / (img.max() - img.min())\n    \n    if dicom.PhotometricInterpretation == \"MONOCHROME1\":\n        img = 1 - img\n        \n    img = cv2.resize(img, (img_size, img_size))\n    \n    cv2.imwrite(\n        save_folder + f\"{patient}_{image}.{extension}\", \n        (img*225).astype(np.uint8)\n    )","metadata":{"execution":{"iopub.status.busy":"2022-12-02T16:44:53.114756Z","iopub.execute_input":"2022-12-02T16:44:53.115352Z","iopub.status.idle":"2022-12-02T16:44:53.123489Z","shell.execute_reply.started":"2022-12-02T16:44:53.115313Z","shell.execute_reply":"2022-12-02T16:44:53.121542Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"You can skip the below cell if you choose to use converted png images from above mentioned link.","metadata":{}},{"cell_type":"code","source":"# let's create a varaible SKIP_SAVE in order to skip above function ie., if we are using images from added data\n\nSKIP_SAVE = True\n\nTRAIN_IMAGES_FOLDER = \"/kaggle/input/rsna-breast-cancer-256-pngs\"\n\nTRAIN_META_DATA = \"/kaggle/input/rsna-breast-cancer-detection/train.csv\"","metadata":{"execution":{"iopub.status.busy":"2022-12-02T16:44:53.12529Z","iopub.execute_input":"2022-12-02T16:44:53.125679Z","iopub.status.idle":"2022-12-02T16:44:53.133583Z","shell.execute_reply.started":"2022-12-02T16:44:53.12564Z","shell.execute_reply":"2022-12-02T16:44:53.132654Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"if SKIP_SAVE:\n    train_images_dir = TRAIN_IMAGES_FOLDER\n    print(f\"Number of images: {len(glob.glob(train_images_dir+'/*'))}\")\n\nelse:\n    train_images = glob.glob(\"/kaggle/input/rsna-breast-cancer-detection/train_images/*/*.dcm\")\n    print(f\"Number of images: {len(train_images)}\")\n\n    SAVE_FOLDER = \"/kaggle/working/train_images/\"\n    SIZE = 512\n    EXTENSION = \"png\"\n\n    os.makedirs(SAVE_FOLDER, exist_ok=True)\n\n    _ = Parallel(n_jobs=4)(\n        delayed(dicom_to_png)(uid, img_size=SIZE, save_folder=SAVE_FOLDER, extension=EXTENSION)\n        for uid in tqdm(train_images)\n    )\n    \n    train_images_dir = SAVE_FOLDER\n","metadata":{"execution":{"iopub.status.busy":"2022-12-02T16:44:53.136571Z","iopub.execute_input":"2022-12-02T16:44:53.136935Z","iopub.status.idle":"2022-12-02T16:44:53.72318Z","shell.execute_reply.started":"2022-12-02T16:44:53.136901Z","shell.execute_reply":"2022-12-02T16:44:53.722164Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Understand Meta Data\n\nDataset Description: https://www.kaggle.com/competitions/rsna-breast-cancer-detection/data","metadata":{}},{"cell_type":"code","source":"df = pd.read_csv(TRAIN_META_DATA)\ndf.head()","metadata":{"execution":{"iopub.status.busy":"2022-12-02T16:44:53.724982Z","iopub.execute_input":"2022-12-02T16:44:53.725753Z","iopub.status.idle":"2022-12-02T16:44:53.872185Z","shell.execute_reply.started":"2022-12-02T16:44:53.725711Z","shell.execute_reply":"2022-12-02T16:44:53.871196Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Our images are ready either by using dataset uploaded by above mentioned user or  by saving png file in current working directory using above function.","metadata":{}},{"cell_type":"markdown","source":"### Loading Data","metadata":{}},{"cell_type":"markdown","source":"Train and Validation data","metadata":{}},{"cell_type":"code","source":"patient_ids = df['patient_id'].unique()\nnp.random.shuffle(patient_ids)\n\ntrain_size = int(len(patient_ids) * 0.8)\n\ntrain_ids = patient_ids[:train_size]\nval_ids = patient_ids[train_size:]\n\nprint(f\"Train: {len(train_ids)} Val: {len(val_ids)}\")","metadata":{"execution":{"iopub.status.busy":"2022-12-02T16:44:53.876734Z","iopub.execute_input":"2022-12-02T16:44:53.879018Z","iopub.status.idle":"2022-12-02T16:44:53.895804Z","shell.execute_reply.started":"2022-12-02T16:44:53.87898Z","shell.execute_reply":"2022-12-02T16:44:53.894643Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"columns_to_drop = [\"laterality\", \"view\", \"age\", \n                   \"biopsy\", \"invasive\", \"BIRADS\", \n                   \"implant\", \"density\", \"machine_id\",\n                   \"difficult_negative_case\", \"site_id\"]\n\ntrain_df = df[df[\"patient_id\"].isin(train_ids)].drop(columns_to_drop, axis=1).reset_index(drop=True)\nval_df = df[df[\"patient_id\"].isin(val_ids)].drop(columns_to_drop, axis=1).reset_index(drop=True)","metadata":{"execution":{"iopub.status.busy":"2022-12-02T16:44:53.900656Z","iopub.execute_input":"2022-12-02T16:44:53.903197Z","iopub.status.idle":"2022-12-02T16:44:53.927498Z","shell.execute_reply.started":"2022-12-02T16:44:53.90316Z","shell.execute_reply":"2022-12-02T16:44:53.926587Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train_df[\"cancer\"].value_counts(), val_df[\"cancer\"].value_counts()","metadata":{"execution":{"iopub.status.busy":"2022-12-02T16:44:53.931582Z","iopub.execute_input":"2022-12-02T16:44:53.933822Z","iopub.status.idle":"2022-12-02T16:44:53.949035Z","shell.execute_reply.started":"2022-12-02T16:44:53.933785Z","shell.execute_reply":"2022-12-02T16:44:53.948151Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Dataset Class","metadata":{}},{"cell_type":"code","source":"class CancerDataset(Dataset):\n    \n    def __init__(self, dataframe, images_dir, transform=None):\n        super().__init__()\n        self.df = dataframe.copy()\n        self.transform = transform\n        \n        self.images_dir = images_dir\n        \n    def __getitem__(self, idx):\n        img_path = os.path.join(self.images_dir, f\"{self.df.loc[idx, 'patient_id']}_{self.df.loc[idx, 'image_id']}.png\")\n        img = cv2.imread(img_path)\n        \n        if self.transform:\n            img = self.transform(img)\n            \n        label = self.df.loc[idx, \"cancer\"]\n        label = torch.tensor(label, dtype=torch.float32).unsqueeze(0)\n        return img, label\n        \n    def __len__(self):\n        return len(self.df)","metadata":{"execution":{"iopub.status.busy":"2022-12-02T16:44:53.953081Z","iopub.execute_input":"2022-12-02T16:44:53.95535Z","iopub.status.idle":"2022-12-02T16:44:53.965892Z","shell.execute_reply.started":"2022-12-02T16:44:53.955308Z","shell.execute_reply":"2022-12-02T16:44:53.964786Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## PyTorch Dataset","metadata":{}},{"cell_type":"code","source":"IMG_SIZE = 256\n\ntrain_transform = transforms.Compose([\n    transforms.ToTensor(),\n    transforms.ColorJitter(brightness=(0.5, 1.5)),\n    transforms.Resize((IMG_SIZE, IMG_SIZE)),\n])\n\nval_transform = transforms.Compose([\n    transforms.ToTensor(),\n    transforms.Resize((IMG_SIZE, IMG_SIZE)),\n])\n\ntrain_dataset = CancerDataset(train_df, train_images_dir, transform=train_transform)\nval_dataset = CancerDataset(val_df, train_images_dir, transform=val_transform)","metadata":{"execution":{"iopub.status.busy":"2022-12-02T16:44:53.972947Z","iopub.execute_input":"2022-12-02T16:44:53.975216Z","iopub.status.idle":"2022-12-02T16:44:53.984271Z","shell.execute_reply.started":"2022-12-02T16:44:53.975178Z","shell.execute_reply":"2022-12-02T16:44:53.983239Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Sample Image","metadata":{}},{"cell_type":"code","source":"img, label = train_dataset[5]\nplt.imshow(img.permute(1, 2, 0).numpy(), cmap=\"bone\")","metadata":{"execution":{"iopub.status.busy":"2022-12-02T16:44:53.989174Z","iopub.execute_input":"2022-12-02T16:44:53.991687Z","iopub.status.idle":"2022-12-02T16:44:54.308115Z","shell.execute_reply.started":"2022-12-02T16:44:53.991651Z","shell.execute_reply":"2022-12-02T16:44:54.307236Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Model Class","metadata":{}},{"cell_type":"code","source":"class BreastCancerResNet34(nn.Module):\n\n    def __init__(self):\n        super().__init__()\n        # use pretrained model\n        self.backbone = models.resnet34(pretrained=True)\n        # replace the last layer\n        num_feat = self.backbone.fc.in_features\n        self.backbone.fc = nn.Linear(num_feat, 1)\n    \n    def forward(self, img):\n        out = self.backbone(img)\n        out = torch.sigmoid(out)\n        return out","metadata":{"execution":{"iopub.status.busy":"2022-12-02T16:44:54.312152Z","iopub.execute_input":"2022-12-02T16:44:54.314253Z","iopub.status.idle":"2022-12-02T16:44:54.322593Z","shell.execute_reply.started":"2022-12-02T16:44:54.314215Z","shell.execute_reply":"2022-12-02T16:44:54.321728Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"model = BreastCancerResNet34()\nparams = sum(p.numel() for p in model.parameters())\ndel model\n\nprint(f\"Num parameters:  {round(params/1000000, 1)} m.\")","metadata":{"execution":{"iopub.status.busy":"2022-12-02T16:44:54.326831Z","iopub.execute_input":"2022-12-02T16:44:54.329505Z","iopub.status.idle":"2022-12-02T16:44:58.553579Z","shell.execute_reply.started":"2022-12-02T16:44:54.32947Z","shell.execute_reply":"2022-12-02T16:44:58.552511Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Training Pipeline","metadata":{}},{"cell_type":"code","source":"DEVICE = \"cuda:0\" if torch.cuda.is_available() else \"cpu\"\nDEVICE","metadata":{"execution":{"iopub.status.busy":"2022-12-02T16:44:58.554967Z","iopub.execute_input":"2022-12-02T16:44:58.555632Z","iopub.status.idle":"2022-12-02T16:44:58.633369Z","shell.execute_reply.started":"2022-12-02T16:44:58.555588Z","shell.execute_reply":"2022-12-02T16:44:58.632293Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def train_batch(batch, model, criterion, optimizer, threshold=0.5):\n    \n    optimizer.zero_grad()\n    \n    images, labels = batch\n    images, labels = images.to(DEVICE), labels.to(DEVICE)\n    \n    preds = model(images)\n    \n    precision = precision_score(labels.detach().cpu().numpy(), preds.detach().cpu().numpy() > threshold, zero_division=0)\n    recall = recall_score(labels.detach().cpu().numpy(), preds.detach().cpu().numpy() > threshold, zero_division=0)\n    \n    loss = criterion(preds, labels)\n    loss.backward()\n    \n    optimizer.step()\n    \n    return loss, precision, recall\n\ndef eval_batch(batch, model, criterion, threshold=0.5):\n    \n    images, labels = batch\n    images, labels = images.to(DEVICE), labels.to(DEVICE)\n    \n    preds = model(images)\n    \n    precision = precision_score(labels.detach().cpu().numpy(), preds.detach().cpu().numpy() > threshold, zero_division=0)\n    recall = recall_score(labels.detach().cpu().numpy(), preds.detach().cpu().numpy() > threshold, zero_division=0)\n    \n    loss = criterion(preds, labels)\n    \n    return loss, precision, recall","metadata":{"execution":{"iopub.status.busy":"2022-12-02T16:44:58.635315Z","iopub.execute_input":"2022-12-02T16:44:58.636168Z","iopub.status.idle":"2022-12-02T16:44:58.655163Z","shell.execute_reply.started":"2022-12-02T16:44:58.636127Z","shell.execute_reply":"2022-12-02T16:44:58.65408Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def train_loop(train_dataset, val_dataset, n_epoch, batch_size=16, num_workers=8, lr=1e-4):\n    \n    model = BreastCancerResNet34().to(DEVICE)\n    criterion = nn.BCELoss(reduction=\"mean\")\n    optimizer = torch.optim.Adam(model.parameters(), lr=lr)\n    \n    train_dataloader = DataLoader(train_dataset, batch_size=batch_size, num_workers=num_workers, shuffle=True)\n    val_dataloader = DataLoader(val_dataset, batch_size=batch_size, num_workers=num_workers, shuffle=False)\n    \n    for epoch in range(1, n_epoch+1):\n        \n        print(f\"Epoch: {epoch}\\n\", \"-\"*50)\n        \n        trn_loss = 0\n        trn_precision = 0\n        trn_recall = 0\n        \n        val_loss = 0\n        val_precision = 0\n        val_recall = 0\n        \n        model.train()\n        \n        for batch in train_dataloader:\n            loss, precision, recall = train_batch(batch, model, criterion, optimizer)\n            \n            with torch.no_grad():\n                trn_loss += loss\n                trn_precision += precision\n                trn_recall += recall\n                \n        print(f\"Train Loss: {trn_loss / len(train_dataloader):6} Precision: {trn_precision / len(train_dataloader):6}, Recall: {trn_recall / len(train_dataloader):6}\")\n              \n        model.eval()\n        \n        for batch in val_dataloader:\n              with torch.no_grad():\n                    loss, precision, recall = eval_batch(batch, model, criterion)\n                    print(loss, precision, recall)\n                    val_loss += loss\n                    val_precision += precision\n                    val_recall += recall\n                    \n        print(f\"Val Loss: {val_loss / len(val_dataloader)} Precision: {val_precision / len(val_dataloader)} Recall: {val_recall / len(val_dataloader)}\")\n        \n        torch.save(model.state_dict(), f\"bc_resnet32_ep_{epoch}.pth\")\n    \n    return model","metadata":{"execution":{"iopub.status.busy":"2022-12-02T16:44:58.658671Z","iopub.execute_input":"2022-12-02T16:44:58.65943Z","iopub.status.idle":"2022-12-02T16:44:58.670198Z","shell.execute_reply.started":"2022-12-02T16:44:58.659396Z","shell.execute_reply":"2022-12-02T16:44:58.669292Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Model Training","metadata":{}},{"cell_type":"code","source":"EPOCHS = 3\nBATCH_SIZE = 8\nNUM_WORKERS = 2\nLEARNING_RATE = 1e-4\n\nmodel = train_loop(train_dataset, val_dataset, n_epoch=EPOCHS, \n                   batch_size=BATCH_SIZE, num_workers=NUM_WORKERS, lr=LEARNING_RATE)","metadata":{"execution":{"iopub.status.busy":"2022-12-02T11:42:53.59381Z","iopub.execute_input":"2022-12-02T11:42:53.594183Z","iopub.status.idle":"2022-12-02T12:10:36.183541Z","shell.execute_reply.started":"2022-12-02T11:42:53.594151Z","shell.execute_reply":"2022-12-02T12:10:36.181555Z"},"scrolled":true,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Inference","metadata":{}},{"cell_type":"code","source":"test_images = glob.glob(\"/kaggle/input/rsna-breast-cancer-detection/test_images/*/*.dcm\")\nprint(f\"Number of images: {len(test_images)}\")","metadata":{"execution":{"iopub.status.busy":"2022-12-02T16:45:05.808595Z","iopub.execute_input":"2022-12-02T16:45:05.808988Z","iopub.status.idle":"2022-12-02T16:45:05.820663Z","shell.execute_reply.started":"2022-12-02T16:45:05.808955Z","shell.execute_reply":"2022-12-02T16:45:05.819709Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Resize and Convert Test DCM images to PNG","metadata":{}},{"cell_type":"code","source":"SAVE_FOLDER = \"/kaggle/working/test_images/\"\nSIZE = 512\nEXTENSION = \"png\"\n\nos.makedirs(SAVE_FOLDER, exist_ok=True)\n\n_ = Parallel(n_jobs=4)(\n    delayed(dicom_to_png)(uid, img_size=SIZE, save_folder=SAVE_FOLDER, extension=EXTENSION)\n    for uid in tqdm(test_images)\n)","metadata":{"execution":{"iopub.status.busy":"2022-12-02T16:45:08.057801Z","iopub.execute_input":"2022-12-02T16:45:08.058533Z","iopub.status.idle":"2022-12-02T16:45:11.524724Z","shell.execute_reply.started":"2022-12-02T16:45:08.058496Z","shell.execute_reply":"2022-12-02T16:45:11.523479Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Sample Test Image","metadata":{}},{"cell_type":"code","source":"test_samp = glob.glob(SAVE_FOLDER+\"/*\")[0]\nplt.figure(figsize=(10, 10))\nplt.imshow(cv2.imread(test_samp), cmap=\"bone\")","metadata":{"execution":{"iopub.status.busy":"2022-12-02T16:45:29.856067Z","iopub.execute_input":"2022-12-02T16:45:29.856471Z","iopub.status.idle":"2022-12-02T16:45:30.209427Z","shell.execute_reply.started":"2022-12-02T16:45:29.856431Z","shell.execute_reply":"2022-12-02T16:45:30.208412Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Inference on Test Images","metadata":{}},{"cell_type":"code","source":"test_df = pd.read_csv(\"/kaggle/input/rsna-breast-cancer-detection/test.csv\")\ntest_df['cancer'] = -1\ntest_df","metadata":{"execution":{"iopub.status.busy":"2022-12-02T16:51:49.102439Z","iopub.execute_input":"2022-12-02T16:51:49.102824Z","iopub.status.idle":"2022-12-02T16:51:49.122479Z","shell.execute_reply.started":"2022-12-02T16:51:49.102792Z","shell.execute_reply":"2022-12-02T16:51:49.121605Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"test_dir = \"/kaggle/working/test_images/\"\n\ntest_transform = transforms.Compose([\n    transforms.ToTensor(),\n    transforms.Resize((IMG_SIZE, IMG_SIZE)),\n])\n\nmodel = BreastCancerResNet34().to(DEVICE)\nmodel.load_state_dict(torch.load(fr\"/kaggle/input/rsna-breast-cancer-models/bc_resnet32_ep_1.pth\"))\nmodel.eval()\n\npreds = []\n\nfor i in range(len(test_df)): \n    img_path = os.path.join(test_dir, f\"{test_df.loc[i, 'patient_id']}_{test_df.loc[i, 'image_id']}.png\")\n    img = cv2.imread(img_path)\n    img = test_transform(img)\n    img = img.unsqueeze(0)\n    img = img.to(DEVICE)\n    \n    pred = model(img).detach().cpu().numpy()[0].item()\n    preds.append(pred)\n\npreds","metadata":{"execution":{"iopub.status.busy":"2022-12-02T16:51:53.591671Z","iopub.execute_input":"2022-12-02T16:51:53.592076Z","iopub.status.idle":"2022-12-02T16:51:54.407825Z","shell.execute_reply.started":"2022-12-02T16:51:53.59204Z","shell.execute_reply":"2022-12-02T16:51:54.406836Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Submission File","metadata":{}},{"cell_type":"code","source":"# # https://www.kaggle.com/code/theoviel/rsna-breast-baseline-inference?scriptVersionId=112699917&cellId=28\n\ntest_df['cancer'] = preds\ntest_df['prediction_id'] = test_df['patient_id'].astype(str)+ \"_\" + df['laterality']\ntest_df","metadata":{"execution":{"iopub.status.busy":"2022-12-02T16:52:02.411757Z","iopub.execute_input":"2022-12-02T16:52:02.412146Z","iopub.status.idle":"2022-12-02T16:52:02.417722Z","shell.execute_reply.started":"2022-12-02T16:52:02.412114Z","shell.execute_reply":"2022-12-02T16:52:02.416528Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"sub = test_df[['prediction_id', 'cancer']].groupby(\"prediction_id\").mean().reset_index() # \nsub.to_csv('submission.csv', index=False)\nsub.head()","metadata":{"execution":{"iopub.status.busy":"2022-12-02T16:53:43.632366Z","iopub.execute_input":"2022-12-02T16:53:43.632753Z","iopub.status.idle":"2022-12-02T16:53:43.651724Z","shell.execute_reply.started":"2022-12-02T16:53:43.632718Z","shell.execute_reply":"2022-12-02T16:53:43.650893Z"},"trusted":true},"execution_count":null,"outputs":[]}]}