{"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":"## load library","metadata":{}},{"cell_type":"code","source":"!pip install /kaggle/input/rsnamodules/dicomsdl-0.109.1-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl","metadata":{"execution":{"iopub.status.busy":"2023-01-31T03:27:55.642387Z","iopub.execute_input":"2023-01-31T03:27:55.642724Z","iopub.status.idle":"2023-01-31T03:28:29.307372Z","shell.execute_reply.started":"2023-01-31T03:27:55.642646Z","shell.execute_reply":"2023-01-31T03:28:29.305997Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import numpy as np\nimport pandas as pd\nimport matplotlib as mpl\nimport matplotlib.pyplot as plt\nfrom joblib import Parallel, delayed\nfrom tqdm.notebook import tqdm\nimport cv2\nimport joblib\nimport os\nfrom multiprocessing import cpu_count\nimport dicomsdl\n\nimport torch\nimport torch.nn as nn\nfrom torch.optim import Adam\nfrom torch.utils.data import DataLoader, Dataset, WeightedRandomSampler\nimport ignite\nfrom ignite.engine import Events, create_supervised_trainer, create_supervised_evaluator\nfrom ignite.metrics import Accuracy, Loss, RunningAverage, Precision\nfrom ignite.contrib.handlers import ProgressBar\nfrom sklearn.model_selection import train_test_split\nfrom torchvision import models, transforms\n\n# CV2 set number of threads to 1 for speedup in parallell function mapping\ncv2.setNumThreads(1)\n\n# Pandas DataFrame Display Options\npd.options.display.max_colwidth = 99","metadata":{"execution":{"iopub.status.busy":"2023-01-31T03:28:29.309688Z","iopub.execute_input":"2023-01-31T03:28:29.310386Z","iopub.status.idle":"2023-01-31T03:28:33.998173Z","shell.execute_reply.started":"2023-01-31T03:28:29.310345Z","shell.execute_reply":"2023-01-31T03:28:33.997173Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"test = pd.read_csv('/kaggle/input/rsna-breast-cancer-detection/test.csv')\n\ndef get_file_path(args):\n    patient_id, image_id = args\n    return f'/kaggle/input/rsna-breast-cancer-detection/test_images/{patient_id}/{image_id}.dcm'\n    \ntest['file_path'] = test[['patient_id', 'image_id']].apply(get_file_path, axis=1)\n\ndisplay(test.info())\ndisplay(test.head())","metadata":{"execution":{"iopub.status.busy":"2023-01-31T03:28:33.999608Z","iopub.execute_input":"2023-01-31T03:28:34.000993Z","iopub.status.idle":"2023-01-31T03:28:34.075016Z","shell.execute_reply.started":"2023-01-31T03:28:34.000953Z","shell.execute_reply":"2023-01-31T03:28:34.073909Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def process(file_path, img_size=None, debug=False, save=True, save_dir='./'):\n    \"\"\"\n    Read the dicom data and get the image\n    Args:\n        file_path: The path of the dicom file\n        img_size: Size of the output image\n    \"\"\"\n\n#     dicom = pydicom.read_file(file_path)\n#     img = dicom.pixel_array\n    dicom = dicomsdl.open(file_path)\n    img = dicom.pixelData()\n\n    # Save original image for debug purposes\n    if debug:\n        fig, axes = plt.subplots(1, 2, figsize=(20,10))\n        image0 = np.copy(img)\n        axes[0].imshow(image0)\n        axes[0].set_title('Original Image')\n        axes[0].axis('off')\n    else:\n        axes = False\n    \n    # Some images have 0 values as highest intensity and need to be inverted\n    if dicom.PhotometricInterpretation == \"MONOCHROME1\":\n        img = np.max(img) - img\n\n    if img_size:\n        img = cv2.resize(img, img_size)\n    \n    # Normalize [0,1] range\n    img = (img - img.min()) / (img.max() - img.min())\n\n    # Convert to uint8 image in range [0, 255]\n    img = (img * 255).astype(np.uint8)\n    \n    # Resize\n    if img_size is not None:\n        img = cv2.resize(img, img_size, interpolation=cv2.INTER_AREA)\n    \n    if debug:\n        axes[1].imshow(img)\n        axes[1].set_title('Processed Image')\n        axes[1].axis('off')\n        plt.show()\n\n    # Save Only\n    if save:\n        patient_id = file_path.split('/')[-2]\n        image_id = file_path.split('/')[-1].split('.')[0]\n        path = os.path.join(save_dir, f'{patient_id}_{image_id}.png')\n        cv2.imwrite(path, img)","metadata":{"execution":{"iopub.status.busy":"2023-01-31T03:28:34.078015Z","iopub.execute_input":"2023-01-31T03:28:34.078982Z","iopub.status.idle":"2023-01-31T03:28:34.091556Z","shell.execute_reply.started":"2023-01-31T03:28:34.078937Z","shell.execute_reply":"2023-01-31T03:28:34.090328Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"!mkdir test_img","metadata":{"execution":{"iopub.status.busy":"2023-01-31T03:28:34.093091Z","iopub.execute_input":"2023-01-31T03:28:34.094029Z","iopub.status.idle":"2023-01-31T03:28:35.103222Z","shell.execute_reply.started":"2023-01-31T03:28:34.093986Z","shell.execute_reply":"2023-01-31T03:28:35.101371Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Preprocess a single image and saves it\ndef preprocess_and_save_image(args):\n    (patient_id, laterality), g = args\n    for row_idx, row in g.iterrows():\n        process(file_path=row['file_path'], img_size=(512, 512), save_dir='./test_img')","metadata":{"execution":{"iopub.status.busy":"2023-01-31T03:28:35.105249Z","iopub.execute_input":"2023-01-31T03:28:35.105626Z","iopub.status.idle":"2023-01-31T03:28:35.115612Z","shell.execute_reply.started":"2023-01-31T03:28:35.105587Z","shell.execute_reply":"2023-01-31T03:28:35.113322Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Preprocess all images in parallel using Joblib\njobs = [joblib.delayed(preprocess_and_save_image)(args) for args in test.groupby(['patient_id', 'laterality'])]\nSUBMISSION_ROWS = joblib.Parallel(\n    n_jobs=cpu_count(),\n    verbose= os.environ['KAGGLE_KERNEL_RUN_TYPE'] == 'Interactive',\n    backend='multiprocessing',\n    prefer='threads',\n)(jobs)","metadata":{"execution":{"iopub.status.busy":"2023-01-31T03:28:35.116831Z","iopub.execute_input":"2023-01-31T03:28:35.11721Z","iopub.status.idle":"2023-01-31T03:28:37.812967Z","shell.execute_reply.started":"2023-01-31T03:28:35.117175Z","shell.execute_reply":"2023-01-31T03:28:37.811619Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"!ls test_img","metadata":{"execution":{"iopub.status.busy":"2023-01-31T03:28:37.815396Z","iopub.execute_input":"2023-01-31T03:28:37.815831Z","iopub.status.idle":"2023-01-31T03:28:38.778768Z","shell.execute_reply.started":"2023-01-31T03:28:37.815782Z","shell.execute_reply":"2023-01-31T03:28:38.777608Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def patchify(batch, patch_size):\n    \"\"\"\n    Patchify the batch of images\n        \n    Shape:\n        batch: (b, h, w, c)\n        output: (b, nh, nw, ph, pw, c)\n    \"\"\"\n    b, c, h, w = batch.shape\n    ph, pw = patch_size\n    nh, nw = h // ph, w // pw\n\n    batch_patches = torch.reshape(batch, (b, c, nh, ph, nw, pw))\n    batch_patches = torch.permute(batch_patches, (0, 1, 2, 4, 3, 5))\n\n    return batch_patches","metadata":{"execution":{"iopub.status.busy":"2023-01-31T03:28:38.780382Z","iopub.execute_input":"2023-01-31T03:28:38.781489Z","iopub.status.idle":"2023-01-31T03:28:38.789066Z","shell.execute_reply.started":"2023-01-31T03:28:38.781447Z","shell.execute_reply":"2023-01-31T03:28:38.787825Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## model","metadata":{}},{"cell_type":"code","source":"def get_mlp(in_features, hidden_units, out_features):\n    \"\"\"\n    Returns a MLP head\n    \"\"\"\n    dims = [in_features] + hidden_units + [out_features]\n    layers = []\n    for dim1, dim2 in zip(dims[:-2], dims[1:-1]):\n        layers.append(nn.Linear(dim1, dim2))\n        layers.append(nn.ReLU())\n    layers.append(nn.Linear(dims[-2], dims[-1]))\n    return nn.Sequential(*layers)","metadata":{"execution":{"iopub.status.busy":"2023-01-31T03:28:38.79389Z","iopub.execute_input":"2023-01-31T03:28:38.794246Z","iopub.status.idle":"2023-01-31T03:28:38.801259Z","shell.execute_reply.started":"2023-01-31T03:28:38.794203Z","shell.execute_reply":"2023-01-31T03:28:38.800249Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"class Img2Seq(nn.Module):\n    \"\"\"\n    This layers takes a batch of images as input and\n    returns a batch of sequences\n    \n    Shape:\n        input: (b, h, w, c)\n        output: (b, s, d)\n    \"\"\"\n    def __init__(self, img_size, patch_size, n_channels, d_model):\n        super().__init__()\n        self.patch_size = patch_size\n        self.img_size = img_size\n\n        nh, nw = img_size[0] // patch_size[0], img_size[1] // patch_size[1]\n        n_tokens = nh * nw\n\n        token_dim = patch_size[0] * patch_size[1] * n_channels\n        self.linear = nn.Linear(token_dim, d_model)\n        self.cls_token = nn.Parameter(torch.randn(1, 1, d_model))\n        self.pos_emb = nn.Parameter(torch.randn(n_tokens, d_model))\n\n    def __call__(self, batch):\n        batch = patchify(batch, self.patch_size)\n\n        b, c, nh, nw, ph, pw = batch.shape\n\n        # Flattening the patches\n        batch = torch.permute(batch, [0, 2, 3, 4, 5, 1])\n        batch = torch.reshape(batch, [b, nh * nw, ph * pw * c])\n\n        batch = self.linear(batch)\n        cls = self.cls_token.expand([b, -1, -1])\n        emb = batch + self.pos_emb\n\n        return torch.cat([cls, emb], axis=1)","metadata":{"execution":{"iopub.status.busy":"2023-01-31T03:28:38.80275Z","iopub.execute_input":"2023-01-31T03:28:38.803326Z","iopub.status.idle":"2023-01-31T03:28:38.815182Z","shell.execute_reply.started":"2023-01-31T03:28:38.80325Z","shell.execute_reply":"2023-01-31T03:28:38.814146Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"class ViT(nn.Module):\n    def __init__(\n        self,\n        img_size,\n        patch_size,\n        n_channels,\n        d_model,\n        nhead,\n        dim_feedforward,\n        blocks,\n        mlp_head_units,\n        n_classes,\n    ):\n        super().__init__()\n        \"\"\"\n        Args:\n            img_size: Size of the image\n            patch_size: Size of the patch\n            n_channels: Number of image channels\n            d_model: The number of features in the transformer encoder\n            nhead: The number of heads in the multiheadattention models\n            dim_feedforward: The dimension of the feedforward network model in the encoder\n            blocks: The number of sub-encoder-layers in the encoder\n            mlp_head_units: The hidden units of mlp_head\n            n_classes: The number of output classes\n        \"\"\"\n        self.img2seq = Img2Seq(img_size, patch_size, n_channels, d_model)\n\n        encoder_layer = nn.TransformerEncoderLayer(\n            d_model, nhead, dim_feedforward, activation=\"gelu\", batch_first=True\n        )\n        self.transformer_encoder = nn.TransformerEncoder(\n            encoder_layer, blocks\n        )\n        self.mlp = get_mlp(d_model, mlp_head_units, n_classes)\n        \n        self.output = nn.Sigmoid() if n_classes == 1 else nn.Softmax()\n\n    def __call__(self, batch):\n\n        batch = self.img2seq(batch)\n        batch = self.transformer_encoder(batch)\n        batch = batch[:, 0, :]\n        batch = self.mlp(batch)\n        output = self.output(batch)\n        return output","metadata":{"execution":{"iopub.status.busy":"2023-01-31T03:28:38.816834Z","iopub.execute_input":"2023-01-31T03:28:38.817399Z","iopub.status.idle":"2023-01-31T03:28:38.83211Z","shell.execute_reply.started":"2023-01-31T03:28:38.817357Z","shell.execute_reply":"2023-01-31T03:28:38.831202Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Model Hyperparameters","metadata":{}},{"cell_type":"code","source":"img_size = (512, 512)\npatch_size = (16, 16)\nn_channels = 1\nd_model = 1024\nnhead = 4\ndim_feedforward = 2048\nblocks = 8\nmlp_head_units = [1024, 512]\nn_classes = 1\ndevice = torch.device('cuda') if torch.cuda.is_available() else torch.device('cpu')","metadata":{"execution":{"iopub.status.busy":"2023-01-31T03:28:38.833458Z","iopub.execute_input":"2023-01-31T03:28:38.834382Z","iopub.status.idle":"2023-01-31T03:28:38.958212Z","shell.execute_reply.started":"2023-01-31T03:28:38.834307Z","shell.execute_reply":"2023-01-31T03:28:38.957003Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"model = ViT(\n    img_size = (512, 512),\n    patch_size = (16, 16),\n    n_channels = 1,\n    d_model = 1024,\n    nhead = 4,\n    dim_feedforward = 1024,\n    blocks = 8,\n    mlp_head_units = [512, 512],\n    n_classes = 1,\n)\n\nmodel_path = '/kaggle/input/vision-transformer-pytorch/best_model_vit.pt'\nmodel.load_state_dict(torch.load(model_path, map_location=device))\nmodel = model.to(device)","metadata":{"execution":{"iopub.status.busy":"2023-01-31T03:28:38.959877Z","iopub.execute_input":"2023-01-31T03:28:38.960319Z","iopub.status.idle":"2023-01-31T03:28:45.723449Z","shell.execute_reply.started":"2023-01-31T03:28:38.960291Z","shell.execute_reply":"2023-01-31T03:28:45.722392Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Dataset and Dataloader","metadata":{}},{"cell_type":"code","source":"class RSNADataset(Dataset):\n    \n    def __init__(self, df, img_path):\n        self.df = df\n        self.img_path = img_path\n        \n    def __len__(self):\n        return len(self.df)\n    \n    def __getitem__(self, idx):\n        patient_id, image_id = self.df.iloc[idx][['patient_id', 'image_id']]\n        file = os.path.join(self.img_path, f'{patient_id}_{image_id}.png')\n        file = cv2.imread(file, cv2.COLOR_BGR2GRAY)\n        clahe = cv2.createCLAHE(clipLimit = 15, tileGridSize=[8, 8])\n        file = clahe.apply(file)\n        file = file / file.max()\n        X = torch.tensor(file[np.newaxis].astype('float32')).to(device)\n        return X","metadata":{"execution":{"iopub.status.busy":"2023-01-31T03:28:45.724897Z","iopub.execute_input":"2023-01-31T03:28:45.725326Z","iopub.status.idle":"2023-01-31T03:28:45.735145Z","shell.execute_reply.started":"2023-01-31T03:28:45.72528Z","shell.execute_reply":"2023-01-31T03:28:45.734039Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"img_path = 'test_img'\ntest_ds = RSNADataset(test, img_path)\ntest_loader = DataLoader(test_ds, batch_size=1)","metadata":{"execution":{"iopub.status.busy":"2023-01-31T03:28:45.737058Z","iopub.execute_input":"2023-01-31T03:28:45.73744Z","iopub.status.idle":"2023-01-31T03:28:45.748813Z","shell.execute_reply.started":"2023-01-31T03:28:45.737402Z","shell.execute_reply":"2023-01-31T03:28:45.747844Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"model.eval()\nresult = []\nwith torch.no_grad():\n    for imgs in test_loader:\n        imgs = imgs.to(device)\n        outputs = model(imgs)\n        result.append(outputs.cpu().item())","metadata":{"execution":{"iopub.status.busy":"2023-01-31T03:28:45.750051Z","iopub.execute_input":"2023-01-31T03:28:45.750483Z","iopub.status.idle":"2023-01-31T03:28:48.090362Z","shell.execute_reply.started":"2023-01-31T03:28:45.750448Z","shell.execute_reply":"2023-01-31T03:28:48.08917Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"print(result)","metadata":{"execution":{"iopub.status.busy":"2023-01-31T03:28:48.092985Z","iopub.execute_input":"2023-01-31T03:28:48.093802Z","iopub.status.idle":"2023-01-31T03:28:48.101375Z","shell.execute_reply.started":"2023-01-31T03:28:48.093756Z","shell.execute_reply":"2023-01-31T03:28:48.099651Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"sub_df = pd.DataFrame(data = {\"prediction_id\": test['prediction_id'], \"cancer\": pd.Series(result,dtype='float')})\nsub_df.head()","metadata":{"execution":{"iopub.status.busy":"2023-01-31T03:28:48.103262Z","iopub.execute_input":"2023-01-31T03:28:48.104167Z","iopub.status.idle":"2023-01-31T03:28:48.118914Z","shell.execute_reply.started":"2023-01-31T03:28:48.104125Z","shell.execute_reply":"2023-01-31T03:28:48.117508Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"sub_df = sub_df.groupby(\"prediction_id\").mean().reset_index()\nsub_df.head()","metadata":{"execution":{"iopub.status.busy":"2023-01-31T03:28:48.12052Z","iopub.execute_input":"2023-01-31T03:28:48.120946Z","iopub.status.idle":"2023-01-31T03:28:48.137662Z","shell.execute_reply.started":"2023-01-31T03:28:48.120909Z","shell.execute_reply":"2023-01-31T03:28:48.136942Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"sub_df['cancer'] = sub_df['cancer'].apply(lambda x: np.int8(x > 0.5))\ndisplay(sub_df.info())\ndisplay(sub_df.head())","metadata":{"execution":{"iopub.status.busy":"2023-01-31T03:28:48.138903Z","iopub.execute_input":"2023-01-31T03:28:48.139718Z","iopub.status.idle":"2023-01-31T03:28:48.16189Z","shell.execute_reply.started":"2023-01-31T03:28:48.139691Z","shell.execute_reply":"2023-01-31T03:28:48.16074Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"sub_df.to_csv('submission.csv', index=False)","metadata":{"execution":{"iopub.status.busy":"2023-01-31T03:28:48.163233Z","iopub.execute_input":"2023-01-31T03:28:48.163667Z","iopub.status.idle":"2023-01-31T03:28:48.172168Z","shell.execute_reply.started":"2023-01-31T03:28:48.163631Z","shell.execute_reply":"2023-01-31T03:28:48.17123Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Sanity Check\ndisplay(pd.read_csv('submission.csv').head())","metadata":{"execution":{"iopub.status.busy":"2023-01-31T03:28:48.173959Z","iopub.execute_input":"2023-01-31T03:28:48.17433Z","iopub.status.idle":"2023-01-31T03:28:48.187165Z","shell.execute_reply.started":"2023-01-31T03:28:48.174296Z","shell.execute_reply":"2023-01-31T03:28:48.186143Z"},"trusted":true},"execution_count":null,"outputs":[]}]}