{"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":"## RSNA 2022 Cervical Spine Fracture Detection : Ensembling best solutions\n\n\nOriginal Notebooks : \n\n- [[infer] PyTorch-EffNetV2 single-model LB:0.49](https://www.kaggle.com/code/vslaykovsky/infer-pytorch-effnetv2-single-model-lb-0-49/data?scriptVersionId=104434908)\n\n- [RNSA - 3D model [Infer] [PyTorch]](https://www.kaggle.com/code/samuelcortinhas/rnsa-3d-model-infer-pytorch/data)","metadata":{}},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"<div class=\"alert alert-block alert-info\" style=\"text-align:center; font-size:28px;\">\n    Solution 1 : [infer] PyTorch-EffNetV2 single-model LB:0.49\n</div>","metadata":{}},{"cell_type":"markdown","source":"### 1. Imports, constants, dependencies","metadata":{}},{"cell_type":"code","source":"try:\n    import pylibjpeg\nexcept:\n    # Offline dependencies:\n    !mkdir -p /root/.cache/torch/hub/checkpoints/\n    !cp ../input/rsna-2022-whl/efficientnet_v2_s-dd5fe13b.pth  /root/.cache/torch/hub/checkpoints/\n\n    !pip install /kaggle/input/rsna-2022-whl/{pydicom-2.3.0-py3-none-any.whl,pylibjpeg-1.4.0-py3-none-any.whl,python_gdcm-3.0.15-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl}\n    !pip install /kaggle/input/rsna-2022-whl/{torch-1.12.1-cp37-cp37m-manylinux1_x86_64.whl,torchvision-0.13.1-cp37-cp37m-manylinux1_x86_64.whl}","metadata":{"_kg_hide-output":true,"execution":{"iopub.status.busy":"2022-10-12T09:20:31.058067Z","iopub.execute_input":"2022-10-12T09:20:31.05917Z","iopub.status.idle":"2022-10-12T09:22:25.548476Z","shell.execute_reply.started":"2022-10-12T09:20:31.059074Z","shell.execute_reply":"2022-10-12T09:22:25.54719Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import gc\nimport glob\nimport os\nimport re\n\nimport cv2\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport pandas as pd\nimport pydicom as dicom\nimport torch\nimport torchvision as tv\nfrom sklearn.model_selection import GroupKFold\nfrom torch.cuda.amp import GradScaler, autocast\nfrom torchvision.models.feature_extraction import create_feature_extractor\nfrom tqdm.notebook import tqdm\n\nimport wandb\n\npd.set_option('display.max_rows', 1000)\npd.set_option('display.max_columns', 1000)\nplt.rcParams['figure.figsize'] = (20, 5)\n\n\n# Effnet\nWEIGHTS = tv.models.efficientnet.EfficientNet_V2_S_Weights.DEFAULT\nRSNA_2022_PATH = '../input/rsna-2022-cervical-spine-fracture-detection'\nTRAIN_IMAGES_PATH = f'{RSNA_2022_PATH}/train_images'\nTEST_IMAGES_PATH = f'{RSNA_2022_PATH}/test_images'\nEFFNET_CHECKPOINTS_PATH = '../input/rsna-2022-base-effnetv2'\n\n# MODEL_NAMES = [f'effnetv2']\n\n# This notebook supports ensembles and single model predictions. Uncomment to switch to ensemble prediction:\nMODEL_NAMES = [f'effnetv2-f{i}' for i in range(5)]\n\n# Common\nFRAC_COLS = [f'C{i}_effnet_frac' for i in range(1, 8)]\nVERT_COLS = [f'C{i}_effnet_vert' for i in range(1, 8)]\n\ntry:\n    from kaggle_secrets import UserSecretsClient\n    IS_KAGGLE = True\nexcept:\n    IS_KAGGLE = False\n\n\n# Switch to offline for submission\nos.environ[\"WANDB_MODE\"] = \"offline\"\n\nif os.environ[\"WANDB_MODE\"] == \"online\":\n    if IS_KAGGLE:\n        os.environ['WANDB_API_KEY'] = UserSecretsClient().get_secret(\"WANDB_API_KEY\")\n\nif not IS_KAGGLE:\n    print('Running locally')\n    RSNA_2022_PATH = '/mnt/rsna2022'\n    TRAIN_IMAGES_PATH = '/mnt/rsna2022/train_images'\n    TEST_IMAGES_PATH = '/mnt/rsna2022/test_images'\n    METADATA_PATH = '/home/vslaykovsky/Downloads/'\n    EFFNET_CHECKPOINTS_PATH = 'frac_checkpoints'\n    os.environ['WANDB_API_KEY'] = 'yourkeyhere'\n\n\nDEVICE = 'cuda' if torch.cuda.is_available() else 'cpu'\nif DEVICE == 'cuda':\n    BATCH_SIZE = 32\nelse:\n    BATCH_SIZE = 2","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2022-10-12T09:22:25.552633Z","iopub.execute_input":"2022-10-12T09:22:25.553148Z","iopub.status.idle":"2022-10-12T09:22:27.887675Z","shell.execute_reply.started":"2022-10-12T09:22:25.553104Z","shell.execute_reply":"2022-10-12T09:22:27.886544Z"},"jupyter":{"source_hidden":true},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### 2. Loading train/eval/test dataframes","metadata":{}},{"cell_type":"code","source":"def load_df_test():\n    df_test = pd.read_csv(f'{RSNA_2022_PATH}/test.csv')\n\n    if df_test.iloc[0].row_id == '1.2.826.0.1.3680043.10197_C1':\n        # test_images and test.csv are inconsistent in the dev dataset, fixing labels for the dev run.\n        df_test = pd.DataFrame({\n            \"row_id\": ['1.2.826.0.1.3680043.22327_C1', '1.2.826.0.1.3680043.25399_C1', '1.2.826.0.1.3680043.5876_C1'],\n            \"StudyInstanceUID\": ['1.2.826.0.1.3680043.22327', '1.2.826.0.1.3680043.25399', '1.2.826.0.1.3680043.5876'],\n            \"prediction_type\": [\"C1\", \"C1\", \"patient_overall\"]}\n        )\n    return df_test\n\ndf_test = load_df_test()\ndf_test","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","_kg_hide-output":true,"execution":{"iopub.status.busy":"2022-10-12T09:22:27.889499Z","iopub.execute_input":"2022-10-12T09:22:27.890427Z","iopub.status.idle":"2022-10-12T09:22:27.927399Z","shell.execute_reply.started":"2022-10-12T09:22:27.890384Z","shell.execute_reply":"2022-10-12T09:22:27.926579Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"test_slices = glob.glob(f'{TEST_IMAGES_PATH}/*/*')\ntest_slices = [re.findall(f'{TEST_IMAGES_PATH}/(.*)/(.*).dcm', s)[0] for s in test_slices]\ndf_test_slices = pd.DataFrame(data=test_slices, columns=['StudyInstanceUID', 'Slice']).astype({'Slice': int}).sort_values(['StudyInstanceUID', 'Slice']).reset_index(drop=True)\ndf_test_slices","metadata":{"_kg_hide-output":true,"execution":{"iopub.status.busy":"2022-10-12T09:22:27.931202Z","iopub.execute_input":"2022-10-12T09:22:27.93322Z","iopub.status.idle":"2022-10-12T09:22:28.081238Z","shell.execute_reply.started":"2022-10-12T09:22:27.933192Z","shell.execute_reply":"2022-10-12T09:22:28.079879Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### 3. Dataset class","metadata":{}},{"cell_type":"code","source":"def load_dicom(path):\n    \"\"\"\n    This supports loading both regular and compressed JPEG images. \n    See the first sell with `pip install` commands for the necessary dependencies\n    \"\"\"\n    img=dicom.dcmread(path)\n    img.PhotometricInterpretation = 'YBR_FULL'\n    data = img.pixel_array    \n    data = data - np.min(data)\n    if np.max(data) != 0:\n        data = data / np.max(data)\n    data=(data * 255).astype(np.uint8)\n    return cv2.cvtColor(data, cv2.COLOR_GRAY2RGB), img\n\n\nim, meta = load_dicom(f'{TRAIN_IMAGES_PATH}/1.2.826.0.1.3680043.10001/1.dcm')\nplt.figure()\nplt.imshow(im)\nplt.title('regular image')\n\nim, meta = load_dicom(f'{TRAIN_IMAGES_PATH}/1.2.826.0.1.3680043.10014/1.dcm')\nplt.figure()\nplt.imshow(im)\nplt.title('jpeg')","metadata":{"_kg_hide-output":true,"execution":{"iopub.status.busy":"2022-10-12T09:22:28.083108Z","iopub.execute_input":"2022-10-12T09:22:28.083591Z","iopub.status.idle":"2022-10-12T09:22:28.870187Z","shell.execute_reply.started":"2022-10-12T09:22:28.083491Z","shell.execute_reply":"2022-10-12T09:22:28.869321Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"class EffnetDataSet(torch.utils.data.Dataset):    \n    def __init__(self, df, path, transforms=None):\n        super().__init__()\n        self.df = df\n        self.path = path\n        self.transforms = transforms\n        \n    def __getitem__(self, i):\n        path = os.path.join(self.path, self.df.iloc[i].StudyInstanceUID, f'{self.df.iloc[i].Slice}.dcm')        \n        \n        try:\n            img = load_dicom(path)[0]         \n            img = np.transpose(img, (2, 0, 1))  # Pytorch uses (batch, channel, height, width) order. Converting (height, width, channel) -> (channel, height, width)\n            if self.transforms is not None:\n                img = self.transforms(torch.as_tensor(img))\n        except Exception as ex:\n            print(ex)\n            return None\n        \n        if 'C1_fracture' in self.df:\n            frac_targets = torch.as_tensor(self.df.iloc[i][['C1_fracture', 'C2_fracture', 'C3_fracture', 'C4_fracture', 'C5_fracture', 'C6_fracture', 'C7_fracture']].astype('float32').values)\n            vert_targets = torch.as_tensor(self.df.iloc[i][['C1', 'C2', 'C3', 'C4', 'C5', 'C6', 'C7']].astype('float32').values)\n            frac_targets = frac_targets * vert_targets   # we only enable targets that are visible on the current slice\n            return img, frac_targets, vert_targets\n        return img        \n    \n    def __len__(self):\n        return len(self.df)","metadata":{"_kg_hide-output":true,"execution":{"iopub.status.busy":"2022-10-12T09:22:28.871493Z","iopub.execute_input":"2022-10-12T09:22:28.87227Z","iopub.status.idle":"2022-10-12T09:22:28.883196Z","shell.execute_reply.started":"2022-10-12T09:22:28.87223Z","shell.execute_reply":"2022-10-12T09:22:28.882178Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Only X values returned by the test dataset\nds_test = EffnetDataSet(df_test_slices, TEST_IMAGES_PATH, WEIGHTS.transforms())\nX = ds_test[42]\nX.shape","metadata":{"execution":{"iopub.status.busy":"2022-10-12T09:22:28.887921Z","iopub.execute_input":"2022-10-12T09:22:28.897071Z","iopub.status.idle":"2022-10-12T09:22:29.136995Z","shell.execute_reply.started":"2022-10-12T09:22:28.897009Z","shell.execute_reply":"2022-10-12T09:22:29.13594Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"class EffnetModel(torch.nn.Module):\n    def __init__(self):\n        super().__init__()\n        effnet = tv.models.efficientnet_v2_s()\n        self.model = create_feature_extractor(effnet, ['flatten'])\n        self.nn_fracture = torch.nn.Sequential(\n            torch.nn.Linear(1280, 7),\n        )\n        self.nn_vertebrae = torch.nn.Sequential(\n            torch.nn.Linear(1280, 7),\n        )\n\n    def forward(self, x):\n        # returns logits\n        x = self.model(x)['flatten']\n        return self.nn_fracture(x), self.nn_vertebrae(x)\n\n    def predict(self, x):\n        frac, vert = self.forward(x)\n        return torch.sigmoid(frac), torch.sigmoid(vert)\n\nmodel = EffnetModel()\nmodel.predict(torch.randn(1, 3, 512, 512))\ndel model","metadata":{"execution":{"iopub.status.busy":"2022-10-12T09:22:29.138766Z","iopub.execute_input":"2022-10-12T09:22:29.139154Z","iopub.status.idle":"2022-10-12T09:22:31.02992Z","shell.execute_reply.started":"2022-10-12T09:22:29.139119Z","shell.execute_reply":"2022-10-12T09:22:31.02879Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def load_model(model, name, path='.'):\n    data = torch.load(os.path.join(path, f'{name}.tph'), map_location=DEVICE)\n    model.load_state_dict(data)\n    return model","metadata":{"execution":{"iopub.status.busy":"2022-10-12T09:22:31.031638Z","iopub.execute_input":"2022-10-12T09:22:31.03206Z","iopub.status.idle":"2022-10-12T09:22:31.038046Z","shell.execute_reply.started":"2022-10-12T09:22:31.032005Z","shell.execute_reply":"2022-10-12T09:22:31.037094Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"effnet_models = [load_model(EffnetModel(), name, EFFNET_CHECKPOINTS_PATH).to(DEVICE) for name in MODEL_NAMES]","metadata":{"execution":{"iopub.status.busy":"2022-10-12T09:22:31.042628Z","iopub.execute_input":"2022-10-12T09:22:31.043524Z","iopub.status.idle":"2022-10-12T09:22:41.993334Z","shell.execute_reply.started":"2022-10-12T09:22:31.043481Z","shell.execute_reply":"2022-10-12T09:22:41.992339Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### 7. Submission","metadata":{}},{"cell_type":"code","source":"from typing import List\n\n\ndef predict_effnet(models: List[EffnetModel], ds, max_batches=1e9):\n    dl_test = torch.utils.data.DataLoader(ds, batch_size=BATCH_SIZE, shuffle=False, num_workers=os.cpu_count())\n    for m in models:\n        m.eval()\n\n    with torch.no_grad():\n        predictions = []\n        for idx, X in enumerate(tqdm(dl_test, miniters=10)):\n            pred = torch.zeros(len(X), 14).to(DEVICE)\n            for m in models:\n                y1, y2 = m.predict(X.to(DEVICE))\n                pred += torch.concat([y1, y2], dim=1) / len(models)\n            predictions.append(pred)\n            if idx >= max_batches:\n                break\n        return torch.concat(predictions).cpu().numpy()\n\n# Quick test\npredict_effnet([EffnetModel().to(DEVICE)], ds_test, max_batches=2).shape","metadata":{"execution":{"iopub.status.busy":"2022-10-12T09:22:41.994804Z","iopub.execute_input":"2022-10-12T09:22:41.995212Z","iopub.status.idle":"2022-10-12T09:22:46.443138Z","shell.execute_reply.started":"2022-10-12T09:22:41.995169Z","shell.execute_reply":"2022-10-12T09:22:46.442013Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"effnet_pred = predict_effnet(effnet_models, ds_test)\n\ndf_effnet_pred = pd.DataFrame(\n    data=effnet_pred, columns=[f'C{i}_effnet_frac' for i in range(1, 8)] + [f'C{i}_effnet_vert' for i in range(1, 8)]\n)","metadata":{"execution":{"iopub.status.busy":"2022-10-12T09:22:46.444887Z","iopub.execute_input":"2022-10-12T09:22:46.445671Z","iopub.status.idle":"2022-10-12T09:23:24.297186Z","shell.execute_reply.started":"2022-10-12T09:22:46.445624Z","shell.execute_reply":"2022-10-12T09:23:24.296074Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df_test_pred = pd.concat([df_test_slices, df_effnet_pred], axis=1).sort_values(['StudyInstanceUID', 'Slice'])\ndf_test_pred","metadata":{"_kg_hide-output":true,"execution":{"iopub.status.busy":"2022-10-12T09:23:24.300288Z","iopub.execute_input":"2022-10-12T09:23:24.301114Z","iopub.status.idle":"2022-10-12T09:23:24.335003Z","shell.execute_reply.started":"2022-10-12T09:23:24.301069Z","shell.execute_reply":"2022-10-12T09:23:24.333998Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def plot_sample_patient(df_pred):\n    patient = np.random.choice(df_pred.StudyInstanceUID)\n    df = df_pred.query('StudyInstanceUID == @patient').reset_index()\n\n    df[[f'C{i}_effnet_frac' for i in range(1, 8)]].plot(\n        title=f'Patient {patient}, fracture prediction',\n        ax=(plt.subplot(1, 2, 1)))\n\n    df[[f'C{i}_effnet_vert' for i in range(1, 8)]].plot(\n        title=f'Patient {patient}, vertebrae prediction',\n        ax=plt.subplot(1, 2, 2)\n    )\n\nplot_sample_patient(df_test_pred)","metadata":{"execution":{"iopub.status.busy":"2022-10-12T09:23:24.336593Z","iopub.execute_input":"2022-10-12T09:23:24.336957Z","iopub.status.idle":"2022-10-12T09:23:25.02858Z","shell.execute_reply.started":"2022-10-12T09:23:24.336919Z","shell.execute_reply":"2022-10-12T09:23:25.027568Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def patient_prediction(df):\n    c1c7 = np.average(df[FRAC_COLS].values, axis=0, weights=df[VERT_COLS].values)\n    pred_patient_overall = 1 - np.prod(1 - c1c7)\n    return pd.Series(data=np.concatenate([[pred_patient_overall], c1c7]), index=['patient_overall'] + [f'C{i}' for i in range(1, 8)])\n\ndf_patient_pred = df_test_pred.groupby('StudyInstanceUID').apply(lambda df: patient_prediction(df))\ndf_patient_pred","metadata":{"_kg_hide-output":true,"execution":{"iopub.status.busy":"2022-10-12T09:23:25.029871Z","iopub.execute_input":"2022-10-12T09:23:25.030885Z","iopub.status.idle":"2022-10-12T09:23:25.059022Z","shell.execute_reply.started":"2022-10-12T09:23:25.03085Z","shell.execute_reply":"2022-10-12T09:23:25.058009Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df_sub = df_test.copy()\ndf_sub = df_sub.set_index('StudyInstanceUID').join(df_patient_pred)\ndf_sub['fractured'] = df_sub.apply(lambda r: r[r.prediction_type], axis=1)\n#df_sub[['row_id', 'fractured']].to_csv('submission1.csv', index=False)\ndf_sub","metadata":{"execution":{"iopub.status.busy":"2022-10-12T09:23:25.060384Z","iopub.execute_input":"2022-10-12T09:23:25.060914Z","iopub.status.idle":"2022-10-12T09:23:25.082647Z","shell.execute_reply.started":"2022-10-12T09:23:25.060876Z","shell.execute_reply":"2022-10-12T09:23:25.081562Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"<div class=\"alert alert-block alert-info\" style=\"text-align:center; font-size:28px;\">\n    Solution 2 : RNSA - 3D model [Infer] [PyTorch]\n</div>","metadata":{}},{"cell_type":"code","source":"!pip install -qU ../input/for-pydicom/python_gdcm-3.0.14-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl ../input/for-pydicom/pylibjpeg-1.4.0-py3-none-any.whl --find-links frozen_packages --no-index","metadata":{"_kg_hide-output":true,"execution":{"iopub.status.busy":"2022-10-12T09:23:25.084337Z","iopub.execute_input":"2022-10-12T09:23:25.084841Z","iopub.status.idle":"2022-10-12T09:23:43.829406Z","shell.execute_reply.started":"2022-10-12T09:23:25.084806Z","shell.execute_reply":"2022-10-12T09:23:43.828205Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"!pip install -q kaggle_vol3d_classify -f ../input/cervical-spine-fracture-detection-npz-3d-volumes/frozen_packages --no-index","metadata":{"_kg_hide-output":true,"execution":{"iopub.status.busy":"2022-10-12T09:23:43.83102Z","iopub.execute_input":"2022-10-12T09:23:43.831769Z","iopub.status.idle":"2022-10-12T09:23:57.953944Z","shell.execute_reply.started":"2022-10-12T09:23:43.831721Z","shell.execute_reply":"2022-10-12T09:23:57.952663Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\n%matplotlib inline\nimport matplotlib.patches as patches\nimport seaborn as sns\nsns.set(style='darkgrid', font_scale=1.6)\nimport cv2\nimport os\nfrom os import listdir\nimport re\nimport gc\nimport random\nimport pydicom\nfrom pydicom.pixel_data_handlers.util import apply_voi_lut\nfrom tqdm.auto import tqdm\nfrom pprint import pprint\nfrom time import time\nimport itertools\nfrom skimage import measure\nfrom mpl_toolkits.mplot3d.art3d import Poly3DCollection\nimport nibabel as nib\nfrom glob import glob\nimport warnings\n#warnings.filterwarnings(\"ignore\", category=DeprecationWarning)\n#warnings.filterwarnings(\"ignore\", category=UserWarning)\n#warnings.filterwarnings(\"ignore\", category=FutureWarning)\nimport zipfile\nfrom scipy import ndimage\nfrom sklearn.model_selection import train_test_split\nfrom joblib import Parallel, delayed\nfrom PIL import Image\nfrom dipy.denoise.nlmeans import nlmeans\nfrom dipy.denoise.noise_estimate import estimate_sigma\nfrom kaggle_volclassif.utils import interpolate_volume\nfrom skimage import exposure\n\n# Pytorch\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nimport torch.optim.lr_scheduler as lr_scheduler\nfrom torch.utils.data import Dataset, DataLoader\nimport torchvision.transforms as transforms\nimport torch.nn.functional as F","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2022-10-12T09:23:57.956226Z","iopub.execute_input":"2022-10-12T09:23:57.956618Z","iopub.status.idle":"2022-10-12T09:23:58.250555Z","shell.execute_reply.started":"2022-10-12T09:23:57.956575Z","shell.execute_reply":"2022-10-12T09:23:58.249577Z"},"jupyter":{"source_hidden":true},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Reproducibility","metadata":{}},{"cell_type":"code","source":"# Set random seeds\ndef set_seed(seed=0):\n    np.random.seed(seed)\n    random.seed(seed)\n    torch.manual_seed(seed)\nset_seed()","metadata":{"execution":{"iopub.status.busy":"2022-10-12T09:23:58.251848Z","iopub.execute_input":"2022-10-12T09:23:58.252239Z","iopub.status.idle":"2022-10-12T09:23:58.261053Z","shell.execute_reply.started":"2022-10-12T09:23:58.252186Z","shell.execute_reply":"2022-10-12T09:23:58.260133Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Config","metadata":{}},{"cell_type":"code","source":"# Hyperparameters\nBATCH_SIZE = 1\n\n# Config device\ndevice = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\ndevice","metadata":{"execution":{"iopub.status.busy":"2022-10-12T09:23:58.262515Z","iopub.execute_input":"2022-10-12T09:23:58.263015Z","iopub.status.idle":"2022-10-12T09:23:58.27272Z","shell.execute_reply.started":"2022-10-12T09:23:58.26298Z","shell.execute_reply":"2022-10-12T09:23:58.271713Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Load tables\n","metadata":{}},{"cell_type":"code","source":"# Load metadata\ntrain_df = pd.read_csv(\"../input/rsna-2022-cervical-spine-fracture-detection/train.csv\")\ntrain_bbox = pd.read_csv(\"../input/rsna-2022-cervical-spine-fracture-detection/train_bounding_boxes.csv\")\ntest_df = pd.read_csv(\"../input/rsna-2022-cervical-spine-fracture-detection/test.csv\")\nss = pd.read_csv(\"../input/rsna-2022-cervical-spine-fracture-detection/sample_submission.csv\")\n\n# Print dataframe shapes\nprint('train shape:', train_df.shape)\nprint('train bbox shape:', train_bbox.shape)\nprint('test shape:', test_df.shape)\nprint('ss shape:', ss.shape)\nprint('')\n\n# Show first few entries\ntrain_df.head(3)","metadata":{"_kg_hide-output":true,"execution":{"iopub.status.busy":"2022-10-12T09:23:58.274392Z","iopub.execute_input":"2022-10-12T09:23:58.274748Z","iopub.status.idle":"2022-10-12T09:23:58.32735Z","shell.execute_reply.started":"2022-10-12T09:23:58.274706Z","shell.execute_reply":"2022-10-12T09:23:58.326368Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Debug","metadata":{}},{"cell_type":"code","source":"debug = False\nif len(ss)==3:\n    debug = True\n    \n    # Fix mismatch with test_images folder\n    test_df = pd.DataFrame(columns = ['row_id','StudyInstanceUID','prediction_type'])\n    for i in ['1.2.826.0.1.3680043.22327','1.2.826.0.1.3680043.25399','1.2.826.0.1.3680043.5876']:\n        for j in ['C1','C2','C3','C4','C5','C6','C7','patient_overall']:\n            test_df = test_df.append({'row_id':i+'_'+j,'StudyInstanceUID':i,'prediction_type':j},ignore_index=True)\n    \n    # Sample submission\n    ss = pd.DataFrame(test_df['row_id'])\n    ss['fractured'] = 0.5\n    \n    display(test_df.head(3))","metadata":{"_kg_hide-output":true,"execution":{"iopub.status.busy":"2022-10-12T09:23:58.328725Z","iopub.execute_input":"2022-10-12T09:23:58.32963Z","iopub.status.idle":"2022-10-12T09:23:58.390131Z","shell.execute_reply.started":"2022-10-12T09:23:58.329594Z","shell.execute_reply":"2022-10-12T09:23:58.389077Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Load volumes","metadata":{}},{"cell_type":"code","source":"# Convert dicom images to 3d tensor\ndef convert_volume(dir_path, out_dir = \"test_volumes\", size = (224, 224, 224)):\n    ls_imgs = glob(os.path.join(dir_path, \"*.dcm\"))\n    ls_imgs = sorted(ls_imgs, key=lambda p: int(os.path.splitext(os.path.basename(p))[0]))\n\n    imgs = []\n    for p_img in ls_imgs:\n        dicom = pydicom.dcmread(p_img)\n        img = apply_voi_lut(dicom.pixel_array, dicom)\n        img = cv2.resize(img, size[:2], interpolation=cv2.INTER_LINEAR)\n        imgs.append(img.tolist())\n    vol = torch.tensor(imgs, dtype=torch.float32)\n\n    vol = (vol - vol.min()) / float(vol.max() - vol.min())\n    vol = interpolate_volume(vol, size).numpy()\n    \n    # https://scikit-image.org/docs/stable/auto_examples/color_exposure/plot_adapt_hist_eq_3d.html\n    vol = exposure.equalize_adapthist(vol, kernel_size=np.array([64, 64, 64]), clip_limit=0.01)\n    # vol = exposure.equalize_hist(vol)\n    vol = np.clip(vol * 255, 0, 255).astype(np.uint8)\n    \n    path_pt = os.path.join(out_dir, f\"{os.path.basename(dir_path)}.pt\")\n    torch.save(torch.tensor(vol), path_pt)","metadata":{"execution":{"iopub.status.busy":"2022-10-12T09:23:58.391856Z","iopub.execute_input":"2022-10-12T09:23:58.393015Z","iopub.status.idle":"2022-10-12T09:23:58.403297Z","shell.execute_reply.started":"2022-10-12T09:23:58.392981Z","shell.execute_reply":"2022-10-12T09:23:58.402315Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Make directory\nos.mkdir('/kaggle/working/test_volumes')\n\n# Get paths\nls_dirs = [p for p in glob(os.path.join(\"../input/rsna-2022-cervical-spine-fracture-detection\", \"test_images\", \"*\")) if os.path.isdir(p)]\nprint(f\"volumes: {len(ls_dirs)}\")\n\n# Convert volumes\n_= Parallel(n_jobs=3)(delayed(convert_volume)(p_dir, out_dir='/kaggle/working/test_volumes') for p_dir in tqdm(ls_dirs))","metadata":{"_kg_hide-output":true,"execution":{"iopub.status.busy":"2022-10-12T09:23:58.404595Z","iopub.execute_input":"2022-10-12T09:23:58.405106Z","iopub.status.idle":"2022-10-12T09:24:37.553607Z","shell.execute_reply.started":"2022-10-12T09:23:58.405066Z","shell.execute_reply":"2022-10-12T09:24:37.552327Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Torch dataset","metadata":{}},{"cell_type":"code","source":"# Dataset for test set only\nclass RSNADataset(Dataset):\n    # Initialise\n    def __init__(self, subset='test', df_table=test_df):\n        super().__init__()\n        \n        self.subset = subset\n        self.df_table = df_table\n        \n        # Image paths\n        self.volume_dir = '/kaggle/working/test_volumes/'\n        \n    # Get item in position given by index\n    def __getitem__(self, index):\n        \n        # load 3d volume\n        patient = self.df_table.loc[index,'StudyInstanceUID']\n        path = os.path.join(self.volume_dir, f'{patient}.pt')\n        vol = torch.load(path).to(torch.float32)\n        \n        return (vol.unsqueeze(0), patient)\n\n    # Length of dataset\n    def __len__(self):\n        return len(self.df_table)\n","metadata":{"execution":{"iopub.status.busy":"2022-10-12T09:24:37.558881Z","iopub.execute_input":"2022-10-12T09:24:37.559596Z","iopub.status.idle":"2022-10-12T09:24:37.571396Z","shell.execute_reply.started":"2022-10-12T09:24:37.55955Z","shell.execute_reply":"2022-10-12T09:24:37.570365Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Test dataset\ntest_table = pd.DataFrame(pd.unique(test_df['StudyInstanceUID']),columns=['StudyInstanceUID'])\ntest_dataset = RSNADataset(subset='test', df_table = test_table)","metadata":{"execution":{"iopub.status.busy":"2022-10-12T09:24:37.576266Z","iopub.execute_input":"2022-10-12T09:24:37.579057Z","iopub.status.idle":"2022-10-12T09:24:37.588702Z","shell.execute_reply.started":"2022-10-12T09:24:37.579002Z","shell.execute_reply":"2022-10-12T09:24:37.587659Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Torch dataloader","metadata":{}},{"cell_type":"code","source":"# Dataloader\ntest_loader = DataLoader(dataset=test_dataset, batch_size=BATCH_SIZE, shuffle=False)","metadata":{"execution":{"iopub.status.busy":"2022-10-12T09:24:37.599022Z","iopub.execute_input":"2022-10-12T09:24:37.601354Z","iopub.status.idle":"2022-10-12T09:24:37.60786Z","shell.execute_reply.started":"2022-10-12T09:24:37.601316Z","shell.execute_reply":"2022-10-12T09:24:37.607074Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### 3. Model","metadata":{}},{"cell_type":"code","source":"# 3D convolutional neural network\nclass Conv3DNet(nn.Module):\n    def __init__(self):\n        super().__init__()\n        \n        # Layers\n        self.conv1 = nn.Conv3d(in_channels=1, out_channels=16, kernel_size=7, stride=1, padding=0)\n        self.pool = nn.MaxPool3d(kernel_size=2, stride=2, padding=0)\n        self.norm1 = nn.BatchNorm3d(num_features=16)\n        self.conv2 = nn.Conv3d(in_channels=16, out_channels=32, kernel_size=3, stride=1, padding=0)\n        self.norm2 = nn.BatchNorm3d(num_features=32)\n        self.conv3 = nn.Conv3d(in_channels=32, out_channels=64, kernel_size=3, stride=1, padding=0)\n        self.norm3 = nn.BatchNorm3d(num_features=64)\n        self.avg = nn.AdaptiveAvgPool3d((7, 1, 1))\n        self.flat = nn.Flatten()\n        self.relu = nn.ReLU()\n        self.lin1 = nn.Linear(in_features=448, out_features=128)\n        self.lin2 = nn.Linear(in_features=128, out_features=8)\n        \n    def forward(self, x):\n        # Conv block 1\n        out = self.conv1(x)\n        out = self.relu(out)\n        out = self.pool(out)\n        out = self.norm1(out)\n        \n        # Conv block 2\n        out = self.conv2(out)\n        out = self.relu(out)\n        out = self.pool(out)\n        out = self.norm2(out)\n        \n        # Conv block 3\n        out = self.conv3(out)\n        out = self.relu(out)\n        out = self.pool(out)\n        out = self.norm3(out)\n        \n        # Average & flatten\n        out = self.avg(out)\n        out = self.flat(out)\n        \n        # Fully connected layer\n        out = self.lin1(out)\n        out = self.relu(out)\n        \n        # Output layer (no sigmoid needed)\n        out = self.lin2(out)\n        \n        return out\n\nmodel = Conv3DNet().to(device)\n","metadata":{"execution":{"iopub.status.busy":"2022-10-12T09:24:37.611301Z","iopub.execute_input":"2022-10-12T09:24:37.613236Z","iopub.status.idle":"2022-10-12T09:24:37.631482Z","shell.execute_reply.started":"2022-10-12T09:24:37.613191Z","shell.execute_reply":"2022-10-12T09:24:37.630547Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Load model","metadata":{}},{"cell_type":"code","source":"# Load checkpoint\nPATH='../input/rsna-trained-3d-model-weights-pytorch/Conv3DNet.pt'\nif torch.cuda.is_available():\n    checkpoint = torch.load(PATH)\nelse:\n    checkpoint = torch.load(PATH, map_location=torch.device('cpu'))\n\n# Load states\nmodel.load_state_dict(checkpoint['model_state_dict'])\nepoch = checkpoint['epoch']\nloss = checkpoint['loss']\nval_loss = checkpoint['val_loss']\n\n# Evaluation mode\nmodel.eval()\nmodel.to(device)","metadata":{"_kg_hide-output":true,"execution":{"iopub.status.busy":"2022-10-12T09:24:37.632851Z","iopub.execute_input":"2022-10-12T09:24:37.634121Z","iopub.status.idle":"2022-10-12T09:24:37.732986Z","shell.execute_reply.started":"2022-10-12T09:24:37.63408Z","shell.execute_reply":"2022-10-12T09:24:37.732067Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Print final loss and epoch\nprint('Final epoch:', epoch)\nprint('Final loss:', loss)\nprint('Final valid loss:', val_loss)","metadata":{"execution":{"iopub.status.busy":"2022-10-12T09:24:37.734614Z","iopub.execute_input":"2022-10-12T09:24:37.734998Z","iopub.status.idle":"2022-10-12T09:24:37.741454Z","shell.execute_reply.started":"2022-10-12T09:24:37.734959Z","shell.execute_reply":"2022-10-12T09:24:37.740248Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Inference on test set","metadata":{}},{"cell_type":"code","source":"test_df['fractured']=0.5\nwith torch.no_grad():\n    # Loop over batches\n    for i, (imgs, patient) in enumerate(test_loader):\n        print(f'Iteration {i+1}/{len(test_loader)}')\n        # Send to device\n        imgs = imgs.to(device)\n        \n        # Make predictions\n        preds = model(imgs)\n        \n        # Apply sigmoid\n        sig = nn.Sigmoid()\n        preds = sig(preds)\n        preds = preds.to('cpu')\n        \n        # Save preds\n        test_df.loc[test_df['StudyInstanceUID']==patient[0],'fractured'] = preds.numpy().squeeze()\n        \nprint('Inference complete!')","metadata":{"execution":{"iopub.status.busy":"2022-10-12T09:24:37.743275Z","iopub.execute_input":"2022-10-12T09:24:37.744144Z","iopub.status.idle":"2022-10-12T09:24:38.276477Z","shell.execute_reply.started":"2022-10-12T09:24:37.744082Z","shell.execute_reply":"2022-10-12T09:24:38.275073Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Submission","metadata":{}},{"cell_type":"code","source":"submission = test_df[['row_id','fractured']]\n#submission.to_csv('submission2.csv', index=False)\nsubmission.head(3)","metadata":{"execution":{"iopub.status.busy":"2022-10-12T09:24:38.278505Z","iopub.execute_input":"2022-10-12T09:24:38.278896Z","iopub.status.idle":"2022-10-12T09:24:38.291133Z","shell.execute_reply.started":"2022-10-12T09:24:38.278857Z","shell.execute_reply":"2022-10-12T09:24:38.290058Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Ensembling Solution","metadata":{}},{"cell_type":"code","source":"#sub = df_sub[['row_id', 'fractured']].copy()\nsub = submission.copy()","metadata":{"execution":{"iopub.status.busy":"2022-10-12T09:24:42.603162Z","iopub.execute_input":"2022-10-12T09:24:42.603581Z","iopub.status.idle":"2022-10-12T09:24:42.608591Z","shell.execute_reply.started":"2022-10-12T09:24:42.603547Z","shell.execute_reply":"2022-10-12T09:24:42.607544Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"for i in range(df_sub.shape[0]):\n    idx = df_sub['row_id'][i]\n    val1 = df_sub['fractured'][df_sub['row_id'] == str(idx)].values[0]\n    val2 = submission['fractured'][submission['row_id'] == str(idx)].values[0]\n\n    sub['fractured'][sub['row_id'] == str(idx)] = val1*0.909 + val2*0.091\n    \nsub.to_csv(\"submission.csv\", index=False)\nsub","metadata":{"execution":{"iopub.status.busy":"2022-10-12T09:24:45.246696Z","iopub.execute_input":"2022-10-12T09:24:45.247108Z","iopub.status.idle":"2022-10-12T09:24:45.272119Z","shell.execute_reply.started":"2022-10-12T09:24:45.247073Z","shell.execute_reply":"2022-10-12T09:24:45.27018Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"<div class=\"alert alert-block alert-warning\" style=\"text-align:center; font-size:28px;\">\n    Thanks for reading 🤗\n</div>","metadata":{}}]}