{"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":"## Libraries","metadata":{}},{"cell_type":"code","source":"# online\n!pip install python-gdcm\n!pip install pylibjpeg pylibjpeg-libjpeg pydicom\n!pip install torchvision --upgrade","metadata":{"execution":{"iopub.status.busy":"2022-09-26T08:02:13.336738Z","iopub.execute_input":"2022-09-26T08:02:13.337411Z","iopub.status.idle":"2022-09-26T08:03:56.898623Z","shell.execute_reply.started":"2022-09-26T08:02:13.337324Z","shell.execute_reply":"2022-09-26T08:03:56.896893Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import os\nimport gc\nimport re\nimport sys\nimport cv2\nimport time\nimport glob\nimport random\nimport pydicom\nfrom tqdm.notebook import tqdm\nimport nibabel as nib\nimport pandas as pd\nimport numpy as np\nimport matplotlib.pyplot as plt\nimport seaborn as sns\nsns.set_theme()\nfrom pydicom.pixel_data_handlers.util import apply_voi_lut\nfrom sklearn.model_selection import GroupKFold\nfrom kaggle_secrets import UserSecretsClient\n\nimport torch\nfrom numba import cuda\nfrom torch import nn\nfrom torch import optim\nfrom torch.utils.data import Dataset\nfrom torch.utils.data import DataLoader\nfrom torch.nn.utils.rnn import pack_sequence\nimport torchvision\nfrom torchvision.models.feature_extraction import create_feature_extractor, get_graph_node_names","metadata":{"execution":{"iopub.status.busy":"2022-09-26T08:03:56.902287Z","iopub.execute_input":"2022-09-26T08:03:56.903094Z","iopub.status.idle":"2022-09-26T08:04:00.419469Z","shell.execute_reply.started":"2022-09-26T08:03:56.903049Z","shell.execute_reply":"2022-09-26T08:04:00.418487Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Constants","metadata":{}},{"cell_type":"code","source":"DATA_DIR = \"../input/rsna-2022-cervical-spine-fracture-detection\"\nTRAIN_DIR = os.path.join(DATA_DIR, \"train_images\")\nTEST_DIR = os.path.join(DATA_DIR, \"test_images\")\nVERT_DET_DIR = \"../input/vertebrae-detection-checkpoints\"\n\nEFFNET_CHECKPOINTS_PATH = \"../input/rsna-2022-base-effnetv2\"\nMODEL_NAMES = [f'effnetv2-f{i}' for i in range(5)]\nEFFNET_WEIGHTS = torchvision.models.efficientnet.EfficientNet_V2_S_Weights.DEFAULT\nDEVICE = \"cuda\" if torch.cuda.is_available() else \"cpu\"\nSEED = 0\nFOLD = 0","metadata":{"execution":{"iopub.status.busy":"2022-09-26T08:04:00.421167Z","iopub.execute_input":"2022-09-26T08:04:00.42186Z","iopub.status.idle":"2022-09-26T08:04:00.499946Z","shell.execute_reply.started":"2022-09-26T08:04:00.421823Z","shell.execute_reply":"2022-09-26T08:04:00.496679Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Setting the seed","metadata":{}},{"cell_type":"code","source":"def set_seed(seed):\n    '''\n    Sets the seed of the entire notebook for reproducibility.\n    '''\n    np.random.seed(seed)\n    random.seed(seed)\n    torch.manual_seed(seed)\n    torch.cuda.manual_seed(seed)\n    # when running on the CuDNN backend, two further options must be set\n    torch.backends.cudnn.deterministic = True\n    torch.backends.cudnn.benchmark = False\n    # set a fixed value for the hash seed\n    os.environ['PYTHONHASHSEED'] = str(seed)\n    \nset_seed(SEED)","metadata":{"execution":{"iopub.status.busy":"2022-09-26T08:04:00.502488Z","iopub.execute_input":"2022-09-26T08:04:00.50295Z","iopub.status.idle":"2022-09-26T08:04:00.927061Z","shell.execute_reply.started":"2022-09-26T08:04:00.502912Z","shell.execute_reply":"2022-09-26T08:04:00.926051Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Datasets\n","metadata":{}},{"cell_type":"code","source":"train_df = pd.read_csv(\"../input/rsna-effnetv2-baseline-train-csv/effnet_v2_train.csv\")\nprint(train_df.shape)\ntrain_df","metadata":{"execution":{"iopub.status.busy":"2022-09-26T08:04:00.929761Z","iopub.execute_input":"2022-09-26T08:04:00.930071Z","iopub.status.idle":"2022-09-26T08:04:03.348102Z","shell.execute_reply.started":"2022-09-26T08:04:00.930044Z","shell.execute_reply":"2022-09-26T08:04:03.347183Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def calc_pct_first_last_vert(train_df):\n    train_df[\"any_vertebrae\"] = (train_df[[f\"C{i}\" for i in np.arange(1,8)]] != 0).any(axis=1).astype(int)\n    patient_dicts = []\n    for patient_id, group in train_df.groupby(\"StudyInstanceUID\"):\n        group = group.reset_index(drop=True)\n        num_slices = group.shape[0]\n        pct_first_vert = group[\"any_vertebrae\"].idxmax()/num_slices\n        pct_last_vert = group[\"any_vertebrae\"][::-1].idxmax()/num_slices\n        patient_dicts.append({\"StudyInstanceUID\": patient_id, \"num_slices\": num_slices, \"pct_first_vert\": pct_first_vert, \"pct_last_vert\": pct_last_vert})\n    return pd.DataFrame(patient_dicts)","metadata":{"execution":{"iopub.status.busy":"2022-09-26T08:04:03.349446Z","iopub.execute_input":"2022-09-26T08:04:03.351252Z","iopub.status.idle":"2022-09-26T08:04:03.358726Z","shell.execute_reply.started":"2022-09-26T08:04:03.351214Z","shell.execute_reply":"2022-09-26T08:04:03.357773Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"vert_stats_df = calc_pct_first_last_vert(train_df)\nprint(vert_stats_df.shape)\nvert_stats_df","metadata":{"execution":{"iopub.status.busy":"2022-09-26T08:04:03.360494Z","iopub.execute_input":"2022-09-26T08:04:03.360863Z","iopub.status.idle":"2022-09-26T08:04:04.341385Z","shell.execute_reply.started":"2022-09-26T08:04:03.360829Z","shell.execute_reply":"2022-09-26T08:04:04.340419Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"vert_stats_df[vert_stats_df[\"num_slices\"]>800]","metadata":{"execution":{"iopub.status.busy":"2022-09-26T08:04:04.342747Z","iopub.execute_input":"2022-09-26T08:04:04.343305Z","iopub.status.idle":"2022-09-26T08:04:04.354593Z","shell.execute_reply.started":"2022-09-26T08:04:04.343268Z","shell.execute_reply":"2022-09-26T08:04:04.353545Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"max_slices = 700\nplt.figure(figsize=(15, 10))\nvert_stats_df.loc[vert_stats_df[\"num_slices\"] > max_slices, \"pct_first_vert\"].plot(kind=\"hist\", bins=30)\nplt.title(f\"Number of patients with slices > {max_slices}: {(vert_stats_df['num_slices'] > max_slices).sum()}\")\nplt.xlabel(\"Pct first vertebrae slice\")\nplt.ylabel(\"Number of patients\");","metadata":{"execution":{"iopub.status.busy":"2022-09-26T08:04:04.356446Z","iopub.execute_input":"2022-09-26T08:04:04.356871Z","iopub.status.idle":"2022-09-26T08:04:04.718146Z","shell.execute_reply.started":"2022-09-26T08:04:04.356808Z","shell.execute_reply":"2022-09-26T08:04:04.717248Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"plt.figure(figsize=(15, 10))\nvert_stats_df.loc[vert_stats_df[\"num_slices\"] > max_slices, \"pct_last_vert\"].plot(kind=\"hist\", bins=30)\nplt.title(f\"Number of patients with slices > {max_slices}: {(vert_stats_df['num_slices'] > max_slices).sum()}\")\nplt.xlabel(\"Pct last vertebrae slice\")\nplt.ylabel(\"Number of patients\");","metadata":{"execution":{"iopub.status.busy":"2022-09-26T08:04:04.721734Z","iopub.execute_input":"2022-09-26T08:04:04.722041Z","iopub.status.idle":"2022-09-26T08:04:05.037826Z","shell.execute_reply.started":"2022-09-26T08:04:04.72201Z","shell.execute_reply":"2022-09-26T08:04:05.036874Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"plt.figure(figsize=(15, 10))\ntrain_df.groupby(by=\"StudyInstanceUID\").size().plot(kind=\"hist\", bins=30)\nplt.xlabel(\"Number of slices\")\nplt.ylabel(\"Number of patients\");","metadata":{"execution":{"iopub.status.busy":"2022-09-26T08:04:05.039343Z","iopub.execute_input":"2022-09-26T08:04:05.039944Z","iopub.status.idle":"2022-09-26T08:04:05.378949Z","shell.execute_reply.started":"2022-09-26T08:04:05.039905Z","shell.execute_reply":"2022-09-26T08:04:05.37802Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def drop_first_last_slices(train_df):    \n    dfs = []\n    for patient_id, group in train_df.groupby(\"StudyInstanceUID\"):\n        num_slices = group.shape[0]\n        if num_slices >= 1000:\n            group = group.iloc[np.floor(num_slices*0.15).astype(int) : np.ceil(num_slices*0.75).astype(int),:]\n        elif num_slices >= 800:\n            group = group.iloc[np.floor(num_slices*0.15).astype(int) : np.ceil(num_slices*0.85).astype(int),:]\n        elif num_slices >= 700:\n            group = group.iloc[np.floor(num_slices*0.05).astype(int) : np.ceil(num_slices*0.9).astype(int),:]\n        dfs.append(group)\n    return pd.concat(dfs)","metadata":{"execution":{"iopub.status.busy":"2022-09-26T08:04:05.380438Z","iopub.execute_input":"2022-09-26T08:04:05.380774Z","iopub.status.idle":"2022-09-26T08:04:05.388958Z","shell.execute_reply.started":"2022-09-26T08:04:05.380739Z","shell.execute_reply":"2022-09-26T08:04:05.387947Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"proc_train_df = drop_first_last_slices(train_df)\nproc_train_df.shape\nplt.figure(figsize=(15, 10))\nproc_train_df.groupby(by=\"StudyInstanceUID\").size().plot(kind=\"hist\", bins=30)\nplt.xlabel(\"Number of slices\")\nplt.ylabel(\"Number of patients\");","metadata":{"execution":{"iopub.status.busy":"2022-09-26T08:04:05.39049Z","iopub.execute_input":"2022-09-26T08:04:05.391168Z","iopub.status.idle":"2022-09-26T08:04:06.870863Z","shell.execute_reply.started":"2022-09-26T08:04:05.391133Z","shell.execute_reply":"2022-09-26T08:04:06.869926Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Dataset class","metadata":{}},{"cell_type":"code","source":"def load_dicom(path):\n    \"\"\"\n    Supports loading both regular and compressed JPEG images.\n    \"\"\"\n    ds = pydicom.dcmread(path)\n    ds.PhotometricInterpretation = 'YBR_FULL'\n    img = ds.pixel_array\n    img = img - np.min(img)\n    if np.max(img) != 0:\n        img = img / np.max(img)\n    img = (img * 255).astype(np.uint8)\n    return cv2.cvtColor(img, cv2.COLOR_GRAY2RGB)\n\n\ndef show_image(img, is_batch=False):\n    if is_batch:\n        img = batch[0,:,:,:]\n        img = np.transpose(img, (1, 2, 0))\n    plt.figure(figsize=(6, 6))\n    plt.imshow(img, cmap=\"gray\")\n    plt.axis(\"off\")","metadata":{"execution":{"iopub.status.busy":"2022-09-26T08:04:06.872163Z","iopub.execute_input":"2022-09-26T08:04:06.873002Z","iopub.status.idle":"2022-09-26T08:04:06.881595Z","shell.execute_reply.started":"2022-09-26T08:04:06.872947Z","shell.execute_reply":"2022-09-26T08:04:06.880631Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"img = load_dicom(f\"{TRAIN_DIR}/1.2.826.0.1.3680043.10001/1.dcm\")\nshow_image(img)\ndel img","metadata":{"execution":{"iopub.status.busy":"2022-09-26T08:04:06.882888Z","iopub.execute_input":"2022-09-26T08:04:06.883316Z","iopub.status.idle":"2022-09-26T08:04:07.133826Z","shell.execute_reply.started":"2022-09-26T08:04:06.88328Z","shell.execute_reply":"2022-09-26T08:04:07.132892Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"class EffnetModel(torch.nn.Module):\n    def __init__(self):\n        super().__init__()\n        effnet = torchvision.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\n    \n# https://pytorch.org/blog/FX-feature-extraction-torchvision/   \ndef get_feat_extractor(fold):\n    model = EffnetModel().to(DEVICE)\n    model.load_state_dict(torch.load(os.path.join(EFFNET_CHECKPOINTS_PATH, f\"effnetv2-f{fold}.tph\"), map_location=DEVICE))\n    model.eval()\n    return create_feature_extractor(model, return_nodes=[\"model.flatten\"])","metadata":{"execution":{"iopub.status.busy":"2022-09-26T08:04:07.134783Z","iopub.execute_input":"2022-09-26T08:04:07.135123Z","iopub.status.idle":"2022-09-26T08:04:07.144454Z","shell.execute_reply.started":"2022-09-26T08:04:07.135089Z","shell.execute_reply":"2022-09-26T08:04:07.143474Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"class EffnetFeatsDataset(Dataset):\n    def __init__(self, df, path, fold):\n        self.df = df\n        self.patients = df[\"StudyInstanceUID\"].unique()\n        self.path = path\n        self.transforms = EFFNET_WEIGHTS.transforms()\n        self.feat_extractor = get_feat_extractor(fold)\n    \n    def __len__(self):\n        return len(self.patients)\n    \n    def __getitem__(self, idx):\n        # TODO: create embeddings in two parts for patients with more than 650 slices\n        pat_id = self.patients[idx]\n        pat_slices = self.df.loc[self.df[\"StudyInstanceUID\"] == pat_id, \"Slice\"]\n        img3d = torch.empty((len(pat_slices), 3, 384, 384), device=DEVICE)\n        for i, pat_slice in enumerate(pat_slices):\n            img_path = os.path.join(self.path, pat_id, f\"{pat_slice}.dcm\")\n            # 2d image has RGB channels\n            img2d = load_dicom(img_path)\n            # Pytorch uses [batch, channel, height, width] order\n            img2d = np.transpose(img2d, (2, 0, 1)) # (3, 512, 512)\n            img2d = self.transforms(torch.from_numpy(img2d)) # (3, 384, 384)\n            img3d[i, :, :, :] = img2d # (num_slices, 3, 384, 384)    \n        with torch.no_grad():\n            img_feats = self.feat_extractor(img3d)[\"model.flatten\"]  # (num_slices, 1280)\n        \n        torch.cuda.empty_cache()\n        del img3d\n        \n        return img_feats.to(\"cpu\"), pat_id\n  \n\ndef save_img_feats(df, fold):\n    ds = EffnetFeatsDataset(df, TRAIN_DIR, fold)\n    for img_feats, pat_id in tqdm(ds, total=len(ds)):\n        torch.save(img_feats, f\"./{pat_id}_fold_{fold}.pt\")","metadata":{"execution":{"iopub.status.busy":"2022-09-26T08:04:07.145943Z","iopub.execute_input":"2022-09-26T08:04:07.146887Z","iopub.status.idle":"2022-09-26T08:04:07.158976Z","shell.execute_reply.started":"2022-09-26T08:04:07.146852Z","shell.execute_reply":"2022-09-26T08:04:07.158204Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"save_img_feats(proc_train_df, FOLD)","metadata":{"execution":{"iopub.status.busy":"2022-09-26T08:04:07.160178Z","iopub.execute_input":"2022-09-26T08:04:07.161132Z","iopub.status.idle":"2022-09-26T08:14:17.587344Z","shell.execute_reply.started":"2022-09-26T08:04:07.161098Z","shell.execute_reply":"2022-09-26T08:14:17.585115Z"},"trusted":true},"execution_count":null,"outputs":[]}]}