{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.10.14","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":71549,"databundleVersionId":8561470,"sourceType":"competition"},{"sourceId":9448910,"sourceType":"datasetVersion","datasetId":5743042},{"sourceId":100132,"sourceType":"modelInstanceVersion","modelInstanceId":64905,"modelId":89293},{"sourceId":109569,"sourceType":"modelInstanceVersion","modelInstanceId":85952,"modelId":84065},{"sourceId":114536,"sourceType":"modelInstanceVersion","isSourceIdPinned":true,"modelInstanceId":64905,"modelId":89293}],"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"%pip install --quiet /kaggle/input/timm_3d_deps/other/initial/9/pydicom/pydicom/pydicom-2.4.4-py3-none-any.whl\n%pip install timm_3d --no-index --quiet --find-links=/kaggle/input/timm_3d_deps/other/initial/9/timm_3d/\n%pip install torchio --no-index --quiet --find-links=/kaggle/input/timm_3d_deps/other/initial/9/torchio/\n%pip install itk --no-index --quiet --find-links=/kaggle/input/timm_3d_deps/other/initial/9/itk/itk\n%pip install skorch --no-index --quiet --find-links=/kaggle/input/timm_3d_deps/other/initial/9/skorch/skorch\n%pip install spacecutter --no-index --quiet --find-links=/kaggle/input/timm_3d_deps/other/initial/9/spacecutter/\n%pip install open3d --no-index --quiet --find-links=/kaggle/input/timm_3d_deps/other/initial/9/open3d","metadata":{"execution":{"iopub.status.busy":"2024-09-08T05:08:01.623133Z","iopub.execute_input":"2024-09-08T05:08:01.623433Z","iopub.status.idle":"2024-09-08T05:10:24.603916Z","shell.execute_reply.started":"2024-09-08T05:08:01.623398Z","shell.execute_reply":"2024-09-08T05:10:24.602791Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"%pip install --quiet /kaggle/input/timm_3d_deps/other/initial/10/pydicom/pydicom/pydicom-2.4.4-py3-none-any.whl\n%pip install timm_3d --no-index --quiet --find-links=/kaggle/input/timm_3d_deps/other/initial/10/timm_3d/\n%pip install torchio --no-index --quiet --find-links=/kaggle/input/timm_3d_deps/other/initial/10/torchio/\n%pip install itk --no-index --quiet --find-links=/kaggle/input/timm_3d_deps/other/initial/10/itk/itk\n%pip install skorch --no-index --quiet --find-links=/kaggle/input/timm_3d_deps/other/initial/10/skorch/skorch\n%pip install spacecutter --no-index --quiet --find-links=/kaggle/input/timm_3d_deps/other/initial/10/spacecutter/\n%pip install open3d --no-index --quiet --find-links=/kaggle/input/timm_3d_deps/other/initial/10/open3d\n%pip install pgzip --no-index --quiet --find-links=/kaggle/input/timm_3d_deps/other/initial/10/pgzip","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"data_path = \"/kaggle/input/rsna-2024-lumbar-spine-degenerative-classification/\"","metadata":{"execution":{"iopub.status.busy":"2024-09-08T05:10:24.606289Z","iopub.execute_input":"2024-09-08T05:10:24.607045Z","iopub.status.idle":"2024-09-08T05:10:24.611139Z","shell.execute_reply.started":"2024-09-08T05:10:24.606997Z","shell.execute_reply":"2024-09-08T05:10:24.610252Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import pandas as pd\n\ndef retrieve_test_data(data_path):\n    test_df = pd.read_csv(data_path + 'test_series_descriptions.csv')\n\n    return test_df\n\nretrieve_test_data(data_path)","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","execution":{"iopub.status.busy":"2024-09-08T05:10:24.61266Z","iopub.execute_input":"2024-09-08T05:10:24.612972Z","iopub.status.idle":"2024-09-08T05:10:25.015692Z","shell.execute_reply.started":"2024-09-08T05:10:24.612941Z","shell.execute_reply":"2024-09-08T05:10:25.014845Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import os\n\ndef retrieve_image_paths(base_path, study_id, series_id):\n    series_dir = os.path.join(base_path, str(study_id), str(series_id))\n    images = os.listdir(series_dir)\n    image_paths = [os.path.join(series_dir, img) for img in images]\n    return image_paths","metadata":{"execution":{"iopub.status.busy":"2024-09-08T05:10:25.017755Z","iopub.execute_input":"2024-09-08T05:10:25.018046Z","iopub.status.idle":"2024-09-08T05:10:25.023519Z","shell.execute_reply.started":"2024-09-08T05:10:25.018014Z","shell.execute_reply":"2024-09-08T05:10:25.02262Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import open3d as o3d\nfrom pydicom import dcmread\nimport math\nimport numpy as np\nimport cv2\nimport copy\n\ndef read_study_as_pcd(dir_path, series_types_dict=None, downsampling_factor=1, img_size=(256, 256)):\n    pcd_overall = o3d.geometry.PointCloud()\n\n    for path in glob.glob(os.path.join(dir_path, \"**/*.dcm\"), recursive=True):\n        dicom_slice = dcmread(path)\n\n        series_id = os.path.basename(os.path.dirname(path))\n        study_id = os.path.basename(os.path.dirname(os.path.dirname(path)))\n        if series_types_dict is None or int(series_id) not in series_types_dict:\n            series_desc = dicom_slice.SeriesDescription\n        else:\n            series_desc = series_types_dict[int(series_id)]\n            series_desc = series_desc.split(\" \")[-1]\n\n        x_orig, y_orig = dicom_slice.pixel_array.shape\n        img = np.expand_dims(cv2.resize(dicom_slice.pixel_array, img_size, interpolation=cv2.INTER_AREA), -1)\n        x, y, z = np.where(img)\n\n        downsampling_factor_iter = max(downsampling_factor, int(math.ceil(len(x) / 6e6)))\n\n        index_voxel = np.vstack((x, y, z))[:, ::downsampling_factor_iter]\n        grid_index_array = index_voxel.T\n        pcd = o3d.geometry.PointCloud(o3d.utility.Vector3dVector(grid_index_array.astype(np.float64)))\n\n        vals = np.expand_dims(img[x, y, z][::downsampling_factor_iter], -1)\n        if series_desc == \"T1\":\n            vals = np.pad(vals, ((0, 0), (0, 2)))\n        elif series_desc == \"T2\":\n            vals = np.pad(vals, ((0, 0), (1, 1)))\n        elif series_desc == \"T2/STIR\":\n            vals = np.pad(vals, ((0, 0), (2, 0)))\n        else:\n            raise ValueError(f\"Unknown series desc: {series_desc}\")\n\n        pcd.colors = o3d.utility.Vector3dVector(vals.astype(np.float64))\n\n        dX, dY = dicom_slice.PixelSpacing\n        dZ = dicom_slice.SliceThickness\n\n        X = np.array(list(dicom_slice.ImageOrientationPatient[:3]) + [0]) * dX\n        Y = np.array(list(dicom_slice.ImageOrientationPatient[3:]) + [0]) * dY\n\n        for z in range(int(dZ)):\n            pos = list(dicom_slice.ImagePositionPatient)\n            if series_desc == \"T2\":\n                pos[-1] += z\n            else:\n                pos[0] += z\n            S = np.array(pos + [1])\n\n            transform_matrix = np.array([X, Y, np.zeros(len(X)), S]).T\n            transform_matrix = transform_matrix @ np.matrix(\n                [[0, y_orig / img_size[1], 0, 0],\n                 [x_orig / img_size[0], 0, 0, 0],\n                 [0, 0, 1, 0],\n                 [0, 0, 0, 1]]\n            )\n\n            pcd_overall += copy.deepcopy(pcd).transform(transform_matrix)\n\n    return pcd_overall\n\n\n\ndef read_study_as_voxel_grid(dir_path, series_type_dict=None, downsampling_factor=1, img_size=(256, 256)):\n    pcd_overall = read_study_as_pcd(dir_path,\n                                    series_types_dict=series_type_dict,\n                                    downsampling_factor=downsampling_factor,\n                                    img_size=img_size)\n    box = pcd_overall.get_axis_aligned_bounding_box()\n\n    max_b = np.array(box.get_max_bound())\n    min_b = np.array(box.get_min_bound())\n\n    pts = (np.array(pcd_overall.points) - (min_b)) * (\n                (img_size[0] - 1, img_size[0] - 1, img_size[0] - 1) / (max_b - min_b))\n    coords = np.round(pts).astype(np.int32)\n    vals = np.array(pcd_overall.colors, dtype=np.float16)\n\n    grid = np.zeros((3, img_size[0], img_size[0], img_size[0]), dtype=np.float16)\n    indices = coords[:, 0], coords[:, 1], coords[:, 2]\n\n    np.maximum.at(grid[0], indices, vals[:, 0])\n    np.maximum.at(grid[1], indices, vals[:, 1])\n    np.maximum.at(grid[2], indices, vals[:, 2])\n\n\n    return grid","metadata":{"execution":{"iopub.status.busy":"2024-09-08T05:10:25.024963Z","iopub.execute_input":"2024-09-08T05:10:25.025458Z","iopub.status.idle":"2024-09-08T05:10:27.379552Z","shell.execute_reply.started":"2024-09-08T05:10:25.025415Z","shell.execute_reply":"2024-09-08T05:10:27.378508Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import numpy as np\nfrom torch.utils.data import Dataset, DataLoader\nimport torchio as tio\nimport torch.nn as nn\nimport pydicom\n\nCONDITIONS = {\n    \"Sagittal T2/STIR\": [\"Spinal Canal Stenosis\"],\n    \"Axial T2\": [\"Left Subarticular Stenosis\", \"Right Subarticular Stenosis\"],\n    \"Sagittal T1\": [\"Left Neural Foraminal Narrowing\", \"Right Neural Foraminal Narrowing\"],\n}\n\n\nclass PatientLevelTestset(Dataset):\n    def __init__(self,\n                 base_path: str,\n                 dataframe: pd.DataFrame,\n                 transform_3d=None):\n        self.base_path = base_path\n\n        self.dataframe = (dataframe[['study_id', \"series_id\", \"series_description\"]]\n                          .drop_duplicates())\n\n        self.subjects = self.dataframe[['study_id']].drop_duplicates().reset_index(drop=True)\n        self.series_descs = {e[0]: e[1] for e in self.dataframe[[\"series_id\", \"series_description\"]].drop_duplicates().values}\n\n        self.transform_3d = transform_3d\n\n    def __len__(self):\n        return len(self.subjects)\n\n    def __getitem__(self, index):\n        curr = self.subjects.iloc[index]\n        study_path = os.path.join(self.base_path, str(curr[\"study_id\"]))\n\n        study_images = read_study_as_voxel_grid(study_path, self.series_descs)\n\n        if self.transform_3d is not None:\n            study_images = self.transform_3d(torch.FloatTensor(study_images))  # .data\n            return study_images.to(torch.half), str(curr[\"study_id\"])\n\n        return torch.HalfTensor(study_images), str(curr[\"study_id\"])\n","metadata":{"execution":{"iopub.status.busy":"2024-09-08T05:10:27.38103Z","iopub.execute_input":"2024-09-08T05:10:27.381546Z","iopub.status.idle":"2024-09-08T05:10:31.646696Z","shell.execute_reply.started":"2024-09-08T05:10:27.381509Z","shell.execute_reply":"2024-09-08T05:10:31.645523Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"transform_3d = tio.Compose([\n    tio.RandomAffine(degrees=15, translation=5),\n    tio.RandomGamma(p=0.3),\n    tio.RandomNoise(p=0.3, std=0.1),\n    tio.RandomMotion(p=0.1, degrees=5),\n    tio.RandomBiasField(p=0.2),\n    tio.RandomBlur(p=0.2, std=0.5),\n    tio.RescaleIntensity([0, 1]),\n])","metadata":{"execution":{"iopub.status.busy":"2024-09-08T05:10:31.648178Z","iopub.execute_input":"2024-09-08T05:10:31.648821Z","iopub.status.idle":"2024-09-08T05:10:31.654746Z","shell.execute_reply.started":"2024-09-08T05:10:31.648784Z","shell.execute_reply":"2024-09-08T05:10:31.653848Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def create_subject_level_testset_and_loader(df: pd.DataFrame,\n                                             transform_3d,\n                                             base_path: str,\n                                             batch_size=1,\n                                             num_workers=0):\n    testset = PatientLevelTestset(base_path, df, transform_3d=transform_3d)\n    test_loader = DataLoader(testset, batch_size=batch_size, shuffle=False, num_workers=num_workers)\n\n    return testset, test_loader","metadata":{"execution":{"iopub.status.busy":"2024-09-08T05:10:31.655806Z","iopub.execute_input":"2024-09-08T05:10:31.656147Z","iopub.status.idle":"2024-09-08T05:10:34.501127Z","shell.execute_reply.started":"2024-09-08T05:10:31.656116Z","shell.execute_reply":"2024-09-08T05:10:34.500123Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import os\n\ndata = retrieve_test_data(data_path)\ndataset, dataloader = create_subject_level_testset_and_loader(data, transform_3d, os.path.join(data_path, \"test_images\"))","metadata":{"execution":{"iopub.status.busy":"2024-09-08T05:10:34.502336Z","iopub.execute_input":"2024-09-08T05:10:34.502672Z","iopub.status.idle":"2024-09-08T05:10:34.530286Z","shell.execute_reply.started":"2024-09-08T05:10:34.502639Z","shell.execute_reply":"2024-09-08T05:10:34.529603Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import torch\ndevice = torch.device(\"cuda\") if torch.cuda.is_available() else \"cpu\"","metadata":{"execution":{"iopub.status.busy":"2024-09-08T05:10:34.532967Z","iopub.execute_input":"2024-09-08T05:10:34.533247Z","iopub.status.idle":"2024-09-08T05:10:34.540452Z","shell.execute_reply.started":"2024-09-08T05:10:34.533216Z","shell.execute_reply":"2024-09-08T05:10:34.539583Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import torch.nn as nn\nimport timm_3d\nfrom spacecutter import *\nfrom spacecutter.losses import *\nfrom spacecutter.models import *\nfrom spacecutter.callbacks import *\n\n\nclass Classifier3dMultihead(nn.Module):\n    def __init__(self,\n                 backbone=\"efficientnet_lite0\",\n                 in_chans=1,\n                 out_classes=5,\n                 cutpoint_margin=0.15,\n                 pretrained=False):\n        super(Classifier3dMultihead, self).__init__()\n        self.out_classes = out_classes\n\n        self.encoder = timm_3d.create_model(\n            backbone,\n            features_only=False,\n            drop_rate=0,\n            drop_path_rate=0,\n            pretrained=pretrained,\n            in_chans=in_chans,\n            global_pool=\"max\"\n        )\n        if \"efficientnet\" in backbone:\n            head_in_dim = self.encoder.classifier.in_features\n            self.encoder.classifier = nn.Sequential(\n                nn.LayerNorm(head_in_dim),\n                nn.Dropout(0),\n            )\n\n        elif \"vit\" in backbone:\n            self.encoder.head.drop = nn.Dropout(0)\n            head_in_dim = self.encoder.head.fc.in_features\n            self.encoder.head.fc = nn.Identity()\n\n        self.heads = nn.ModuleList(\n            [nn.Sequential(\n                nn.Linear(head_in_dim, 1),\n                LogisticCumulativeLink(3)\n            ) for i in range(out_classes)]\n        )\n\n        self.ascension_callback = AscensionCallback(margin=cutpoint_margin)\n\n    def forward(self, x):\n        feat = self.encoder(x)\n        return torch.swapaxes(torch.stack([head(feat) for head in self.heads]), 0, 1)\n\n    def _ascension_callback(self):\n        for head in self.heads:\n            self.ascension_callback.clip(head[-1])","metadata":{"execution":{"iopub.status.busy":"2024-09-08T05:10:34.541529Z","iopub.execute_input":"2024-09-08T05:10:34.541815Z","iopub.status.idle":"2024-09-08T05:10:37.226707Z","shell.execute_reply.started":"2024-09-08T05:10:34.541785Z","shell.execute_reply":"2024-09-08T05:10:37.225847Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"model = Classifier3dMultihead(backbone=\"efficientnet_b0\", in_chans=3, out_classes=25).to(device)\nmodel.load_state_dict(torch.load(\"/kaggle/input/rnsaefb0/efficientnet_b0_fold_0_4.pt\"))","metadata":{"execution":{"iopub.status.busy":"2024-09-08T05:10:37.227789Z","iopub.execute_input":"2024-09-08T05:10:37.228096Z","iopub.status.idle":"2024-09-08T05:10:39.367873Z","shell.execute_reply.started":"2024-09-08T05:10:37.228063Z","shell.execute_reply":"2024-09-08T05:10:39.366913Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"CONDITIONS = {\n    \"Sagittal T2/STIR\": [\"spinal_canal_stenosis\"],\n    \"Axial T2\": [\"left_subarticular_stenosis\", \"right_subarticular_stenosis\"],\n    \"Sagittal T1\": [\"left_neural_foraminal_narrowing\", \"right_neural_foraminal_narrowing\"],\n}\n\nALL_CONDITIONS = sorted([\"spinal_canal_stenosis\", \"left_subarticular_stenosis\", \"right_subarticular_stenosis\", \"left_neural_foraminal_narrowing\", \"right_neural_foraminal_narrowing\"])\nLEVELS = [\"l1_l2\", \"l2_l3\", \"l3_l4\", \"l4_l5\", \"l5_s1\"]\n\nresults_df = pd.DataFrame({\"row_id\":[], \"normal_mild\": [], \"moderate\": [], \"severe\": []})\n\nALL_CONDITIONS","metadata":{"execution":{"iopub.status.busy":"2024-09-08T05:10:39.369243Z","iopub.execute_input":"2024-09-08T05:10:39.370002Z","iopub.status.idle":"2024-09-08T05:10:39.378333Z","shell.execute_reply.started":"2024-09-08T05:10:39.369957Z","shell.execute_reply":"2024-09-08T05:10:39.37745Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Pre-populate results df\nimport glob\nimport os\n\nstudy_ids = glob.glob(\"/kaggle/input/rsna-2024-lumbar-spine-degenerative-classification/test_images/*\")\nstudy_ids = [os.path.basename(e) for e in study_ids]\n\nresults_df = pd.DataFrame({\"row_id\":[], \"normal_mild\": [], \"moderate\": [], \"severe\": []})\nfor study_id in study_ids:\n    for condition in ALL_CONDITIONS:\n        for level in LEVELS:\n            row_id = f\"{study_id}_{condition}_{level}\"\n            results_df = results_df._append({\"row_id\": row_id, \"normal_mild\": 1/3, \"moderate\": 1/3, \"severe\": 1/3}, ignore_index=True)","metadata":{"execution":{"iopub.status.busy":"2024-09-08T05:10:39.379455Z","iopub.execute_input":"2024-09-08T05:10:39.379788Z","iopub.status.idle":"2024-09-08T05:10:39.418412Z","shell.execute_reply.started":"2024-09-08T05:10:39.379756Z","shell.execute_reply":"2024-09-08T05:10:39.41762Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"dataset.dataframe","metadata":{"execution":{"iopub.status.busy":"2024-09-08T05:10:39.41971Z","iopub.execute_input":"2024-09-08T05:10:39.420067Z","iopub.status.idle":"2024-09-08T05:10:39.428959Z","shell.execute_reply.started":"2024-09-08T05:10:39.420026Z","shell.execute_reply":"2024-09-08T05:10:39.428041Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import torch\nfrom torch.cuda.amp import autocast\nimport time\n\nstart_time = time.time()\n\nwith torch.no_grad():\n    with autocast(dtype=torch.float16):\n        model.eval()\n\n        for images, study_id in dataloader:\n            output = model(images.to(device))\n            for i, batch_out in enumerate(output):\n                batch_out = output.cpu().numpy()[i]\n                for index, level in enumerate(batch_out):\n                    row_id = f\"{study_id[i]}_{ALL_CONDITIONS[index // 5]}_{LEVELS[index % 5]}\"\n                    results_df.loc[results_df.row_id == row_id,'normal_mild'] = level[0]\n                    results_df.loc[results_df.row_id == row_id,'moderate'] = level[1]\n                    results_df.loc[results_df.row_id == row_id,'severe'] = level[2]\n                \nprint(\"--- %s seconds ---\" % (time.time() - start_time))","metadata":{"execution":{"iopub.status.busy":"2024-09-08T05:10:39.430016Z","iopub.execute_input":"2024-09-08T05:10:39.430292Z","iopub.status.idle":"2024-09-08T05:10:57.937923Z","shell.execute_reply.started":"2024-09-08T05:10:39.430262Z","shell.execute_reply":"2024-09-08T05:10:57.936949Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"results_df","metadata":{"execution":{"iopub.status.busy":"2024-09-08T05:10:57.939273Z","iopub.execute_input":"2024-09-08T05:10:57.939718Z","iopub.status.idle":"2024-09-08T05:10:57.954145Z","shell.execute_reply.started":"2024-09-08T05:10:57.939669Z","shell.execute_reply":"2024-09-08T05:10:57.953151Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"results_df.to_csv('submission.csv', index=False)","metadata":{"execution":{"iopub.status.busy":"2024-09-08T05:10:57.955549Z","iopub.execute_input":"2024-09-08T05:10:57.955988Z","iopub.status.idle":"2024-09-08T05:10:58.068309Z","shell.execute_reply.started":"2024-09-08T05:10:57.955931Z","shell.execute_reply":"2024-09-08T05:10:58.06735Z"},"trusted":true},"execution_count":null,"outputs":[]}]}