{"metadata":{"kernelspec":{"name":"python3","display_name":"Python 3","language":"python"},"language_info":{"name":"python","version":"3.11.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"jupytext":{"cell_metadata_filter":"-all","main_language":"python","notebook_metadata_filter":"-all"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceId":99552,"databundleVersionId":13851420,"sourceType":"competition"},{"sourceId":13476892,"sourceType":"datasetVersion","datasetId":8164557},{"sourceId":616553,"sourceType":"modelInstanceVersion","modelInstanceId":453105,"modelId":469402}],"dockerImageVersionId":31090,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Training code + readme is here\n\n[![GitLab Repo](https://img.shields.io/badge/GitLab-Repository-orange?logo=gitlab)](https://gitlab.com/nn_projects/rsna_aneurysm_project)\n","metadata":{}},{"cell_type":"code","source":"import os\nimport shutil\nfrom collections import defaultdict\n\nimport pandas as pd\nimport polars as pl\nimport pydicom\nimport torch\n\nimport kaggle_evaluation.rsna_inference_server","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-23T11:26:57.274217Z","iopub.execute_input":"2025-10-23T11:26:57.274457Z","iopub.status.idle":"2025-10-23T11:27:04.72974Z","shell.execute_reply.started":"2025-10-23T11:26:57.274424Z","shell.execute_reply":"2025-10-23T11:27:04.728987Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import sys\nsys.path.append(\"/kaggle/input/rsna-python-src/\")\n\nfrom src.load_dicoms import *\nfrom src.constants import *\nfrom src.nn_models import *\nimport pandas as pd","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-23T11:27:04.730566Z","iopub.execute_input":"2025-10-23T11:27:04.731207Z","iopub.status.idle":"2025-10-23T11:27:26.330662Z","shell.execute_reply.started":"2025-10-23T11:27:04.731178Z","shell.execute_reply":"2025-10-23T11:27:26.330051Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import numpy as np\nimport torch\nimport torchvision\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom torch.nn.utils.rnn import pack_padded_sequence\nfrom torchvision.models import resnet50, ResNet50_Weights\nfrom huggingface_hub import hf_hub_download\nfrom torch.amp import GradScaler, autocast\nimport torchvision.models as models\nimport timm\nimport albumentations as A\nfrom albumentations.pytorch import ToTensorV2","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-23T11:27:26.332201Z","iopub.execute_input":"2025-10-23T11:27:26.332697Z","iopub.status.idle":"2025-10-23T11:27:58.775926Z","shell.execute_reply.started":"2025-10-23T11:27:26.33267Z","shell.execute_reply":"2025-10-23T11:27:58.775168Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"The following network shape idea has been taken from \n\nhttps://www.kaggle.com/code/yosukeyama/rsna2025-32ch-img-infer-lb-0-69-share","metadata":{}},{"cell_type":"code","source":"class Cnn2dAdapter25(nn.Module):\n    def __init__(self, input_channels, num_classes=14):\n        super().__init__()\n        self.input_channels = input_channels\n        self.num_classes = num_classes\n\n        self.backbone = timm.create_model(\n                \"tf_efficientnetv2_s.in21k_ft_in1k\", \n                num_classes=self.num_classes, \n                pretrained=False,\n                drop_rate=0.0,\n                in_chans=self.input_channels,\n                drop_path_rate=0.0\n            )\n        \n    def forward(self, x):\n\n        x = x.permute(1, 2, 0, 3,4).squeeze(1)\n        x = self.backbone(x)\n        return x\n\n\ntransform_train25 = A.Compose([ \n                    A.Resize(384, 384),\n                    A.HorizontalFlip(p=0.5), \n                    A.VerticalFlip(p=0.5), \n                    A.RandomRotate90(p=0.5), \n                    A.ShiftScaleRotate(shift_limit=0.0625, scale_limit=0.1, rotate_limit=30, p=.5),\n                    A.ElasticTransform(p=0.4),\n                    A.OpticalDistortion(p=0.4),\n                    A.GridDistortion(p=0.4),\n])\n\npreprocess25 = A.Compose([ \n                    A.Resize(384, 384),\n                    ])\n\ndef process_volume25(volume, modality, transform):\n    img_3d_seq = apply_transform_1d(volume, modality)# list of [C = 1, x, y ]\n    volume = np.stack(img_3d_seq, axis = 0)#[D, x, y ]\n    volume = torch.from_numpy(transform(volume = volume)['volume'])\n    volume = volume.unsqueeze(1)#[D, C = 1, x, y ]\n    d,c,h,w = volume.shape\n    volume = volume.unsqueeze(1).permute(1, 2, 0, 3, 4)\n    volume = F.interpolate(volume, size=(32, h, w), mode='trilinear', align_corners=False)\n    volume = volume.permute(2, 0, 1, 3, 4)\n    return volume","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-23T11:27:58.776647Z","iopub.execute_input":"2025-10-23T11:27:58.776863Z","iopub.status.idle":"2025-10-23T11:27:58.792353Z","shell.execute_reply.started":"2025-10-23T11:27:58.776837Z","shell.execute_reply":"2025-10-23T11:27:58.791549Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"The two following models (Cnn2dAdapter12 and Cnn2dAdapter13) have been trained using the weights of \n\nhttps://huggingface.co/Lab-Rasool/RadImageNet\n\nhttps://github.com/BMEII-AI/RadImageNet\n\n```\n@article{doi:10.1148/ryai.210315,\nauthor = {Mei, Xueyan and Liu, Zelong and Robson, Philip M. and Marinelli, Brett and Huang, Mingqian and Doshi, Amish and Jacobi, Adam and Cao, Chendi and Link, Katherine E. and Yang, Thomas and Wang, Ying and Greenspan, Hayit and Deyer, Timothy and Fayad, Zahi A. and Yang, Yang},\ntitle = {RadImageNet: An Open Radiologic Deep Learning Research Dataset for Effective Transfer Learning},\njournal = {Radiology: Artificial Intelligence},\nvolume = {0},\nnumber = {ja},\npages = {e210315},\nyear = {0},\ndoi = {10.1148/ryai.210315},\nURL = {https://doi.org/10.1148/ryai.210315},\neprint = {https://doi.org/10.1148/ryai.210315}}\n```","metadata":{}},{"cell_type":"code","source":"class Cnn2dAdapter12(nn.Module):\n    def __init__(self, input_channels, num_classes=13):\n        super().__init__()\n        self.input_channels = input_channels\n        self.num_classes = num_classes\n\n        self.backbone = timm.create_model(\n                \"resnet50\", \n                num_classes=self.num_classes, \n                pretrained=False,\n                drop_rate=0.0,\n                in_chans=self.input_channels,\n                drop_path_rate=0.0\n            )\n\n    def forward(self, x, l = []):\n        x = x.permute(1, 2, 0, 3,4).squeeze(1)\n        x = self.backbone(x)\n        return x         \n\n\ntransform_train12 = A.Compose([\n            A.Resize(height=512, width=512),\n            A.ShiftScaleRotate(shift_limit=0.1, scale_limit=0.1, rotate_limit=10, p=0.5),\n            A.Affine(shear=10, p=0.5),\n            A.Normalize(mean=(0.485, 0.456, 0.406),std=(0.229, 0.224, 0.225))\n])\n\npreprocess12 = A.Compose([ \n            A.Resize(512, 512),\n            A.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n])\n\n\ndef process_volume12(volume, modality, transform):\n    img_3d_seq = apply_transform_1d(volume, modality)# list of [C = 1, x, y ]\n    volume = np.stack(img_3d_seq, axis = 0)#[D, x, y ]\n    volume = 255*volume.astype(np.uint8)\n    volume = torch.from_numpy(transform(volume = volume)['volume'])\n    volume = volume.unsqueeze(1)#[D, C = 1, x, y ]\n    d,c,h,w = volume.shape\n    volume = volume.unsqueeze(1).permute(1, 2, 0, 3, 4)\n    volume = F.interpolate(volume, size=(256, h, w), mode='trilinear', align_corners=False)\n    volume = volume.permute(2, 0, 1, 3, 4)#1 channels\n    return volume","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-23T11:27:58.793144Z","iopub.execute_input":"2025-10-23T11:27:58.793637Z","iopub.status.idle":"2025-10-23T11:27:58.818345Z","shell.execute_reply.started":"2025-10-23T11:27:58.793611Z","shell.execute_reply":"2025-10-23T11:27:58.817588Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class Cnn2dAdapter13(nn.Module):\n    def __init__(self, input_channels, num_classes=14):\n        super().__init__()\n        self.input_channels = input_channels\n        self.num_classes = num_classes\n\n        self.backbone = timm.create_model(\n                \"resnet50\", \n                num_classes=self.num_classes, \n                pretrained=False,\n                drop_rate=0.0,\n                in_chans=self.input_channels,\n                drop_path_rate=0.0\n            )\n\n    def forward(self, x, l = []):\n        x = x.permute(1, 2, 0, 3,4).squeeze(1)\n        x = self.backbone(x)\n        return x         \n\n\ntransform_train13 = A.Compose([\n            A.Resize(height=512, width=512),\n            A.ShiftScaleRotate(shift_limit=0.1, scale_limit=0.1, rotate_limit=10, p=0.5),\n            A.Affine(shear=10, p=0.5),\n            A.Normalize(mean=(0.485, 0.456, 0.406),std=(0.229, 0.224, 0.225))\n])\n\npreprocess13 = A.Compose([ \n            A.Resize(512, 512),\n            A.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n])\n\ndef process_volume13(volume, modality, transform):\n    img_3d_seq = apply_transform_1d(volume, modality)# list of [C = 1, x, y ]\n    volume = np.stack(img_3d_seq, axis = 0)#[D, x, y ]\n    volume = 255*volume.astype(np.uint8)\n    volume = torch.from_numpy(transform(volume = volume)['volume'])\n    volume = volume.unsqueeze(1)#[D, C = 1, x, y ]\n    d,c,h,w = volume.shape\n    volume = volume.unsqueeze(1).permute(1, 2, 0, 3, 4)\n    volume = F.interpolate(volume, size=(256, h, w), mode='trilinear', align_corners=False)\n    volume = volume.permute(2, 0, 1, 3, 4)#1 channels\n    return volume","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-23T11:27:58.819058Z","iopub.execute_input":"2025-10-23T11:27:58.81924Z","iopub.status.idle":"2025-10-23T11:27:58.830066Z","shell.execute_reply.started":"2025-10-23T11:27:58.819224Z","shell.execute_reply":"2025-10-23T11:27:58.829317Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def load_models(model_path_list, device):\n    with torch.no_grad():\n        pytorch_models = []\n\n        model = Cnn2dAdapter25(32)\n        model.load_state_dict(torch.load(model_path_list[0], weights_only=True))\n        model.eval()\n        model = model.to(device)\n        pytorch_models.append(model)\n    \n        model = Cnn2dAdapter12(256)\n        model.load_state_dict(torch.load(model_path_list[1], weights_only=True))\n        model.eval()\n        model = model.to(device)\n        pytorch_models.append(model)\n\n        model = Cnn2dAdapter13(256)\n        model.load_state_dict(torch.load(model_path_list[2], weights_only=True))\n        model.eval()\n        model = model.to(device)\n        pytorch_models.append(model)\n        \n        return pytorch_models","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-23T11:27:58.830741Z","iopub.execute_input":"2025-10-23T11:27:58.830903Z","iopub.status.idle":"2025-10-23T11:27:58.840561Z","shell.execute_reply.started":"2025-10-23T11:27:58.830889Z","shell.execute_reply":"2025-10-23T11:27:58.840001Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"The evaluation API requires that you set up a server which will respond to inference requests. We have already defined the server; you just need write the predict function. When we evaluate your submission on the hidden test set the client defined in `rsna_gateway` will run in a different container with direct access to the hidden test set and hand off the data series by series.\n\nYour code will always have access to the published copies of the files.","metadata":{}},{"cell_type":"code","source":"ID_COL = 'SeriesInstanceUID'\n\nLABEL_COLS = [\n    'Left Infraclinoid Internal Carotid Artery',\n    'Right Infraclinoid Internal Carotid Artery',\n    'Left Supraclinoid Internal Carotid Artery',\n    'Right Supraclinoid Internal Carotid Artery',\n    'Left Middle Cerebral Artery',\n    'Right Middle Cerebral Artery',\n    'Anterior Communicating Artery',\n    'Left Anterior Cerebral Artery',\n    'Right Anterior Cerebral Artery',\n    'Left Posterior Communicating Artery',\n    'Right Posterior Communicating Artery',\n    'Basilar Tip',\n    'Other Posterior Circulation',\n    'Aneurysm Present',\n]\n\n# All tags (other than PixelData and SeriesInstanceUID) that may be in a test set dcm file\nDICOM_TAG_ALLOWLIST = [\n    'BitsAllocated',\n    'BitsStored',\n    'Columns',\n    'FrameOfReferenceUID',\n    'HighBit',\n    'ImageOrientationPatient',\n    'ImagePositionPatient',\n    'InstanceNumber',\n    'Modality',\n    'PatientID',\n    'PhotometricInterpretation',\n    'PixelRepresentation',\n    'PixelSpacing',\n    'PlanarConfiguration',\n    'RescaleIntercept',\n    'RescaleSlope',\n    'RescaleType',\n    'Rows',\n    'SOPClassUID',\n    'SOPInstanceUID',\n    'SamplesPerPixel',\n    'SliceThickness',\n    'SpacingBetweenSlices',\n    'StudyInstanceUID',\n    'TransferSyntaxUID',\n]\n\n# Replace this function with your inference code.\n# You can return either a Pandas or Polars dataframe, though Polars is recommended.\n# Each prediction (except the very first) must be returned within 30 minutes of the series being provided.\nMODELS = []\n\ndef predict(series_path: str) -> pl.DataFrame | pd.DataFrame:\n    \"\"\"Make a prediction.\"\"\"\n    with torch.no_grad():\n\n        model_path_list = [\"/kaggle/input/rsna_ensemble/pytorch/default/10/25_09_2025_08_52_13_cnn_adapter_effnetv2_389/model_6.pt\",\n                            \"/kaggle/input/rsna_ensemble/pytorch/default/10/12_10_2025_23_37_48_rad_img_net_512_output_13/model_12.pt\",\n                            \"/kaggle/input/rsna_ensemble/pytorch/default/10/13_10_2025_17_40_11_rad_img_net_512_output_14/model_7.pt\"]\n\n        preprocess_transforms = [preprocess25, preprocess12, preprocess13]\n        process_volumes = [process_volume25, process_volume12, process_volume13]\n        train_transforms =  [transform_train25, transform_train12, transform_train13]\n\n        weights = [1.0, 1.0, 1.0]\n        \n        if torch.cuda.is_available():\n            device = torch.device(\"cuda\")\n        else:\n            device = torch.device(\"cpu\")\n    \n        global MODELS\n        # Load models if not already loaded\n        if not MODELS:\n            print(\"LOADING MODELS\")\n            MODELS = load_models(model_path_list, device)\n        \n        series_id = os.path.basename(series_path)\n    \n        all_filepaths = []\n        for root, dirs, files in os.walk(series_path):\n            for d in dirs:\n                series_to_files_dict[d]\n            for file in files:\n                if file.endswith('.dcm'):\n                    all_filepaths.append(str(os.path.join(root, file)))\n        all_filepaths.sort()\n    \n        try:\n            serie, error, volume, rvolume2, rvolume3, rspacing, modality, z_positions = load_dicom_serie(series_id, all_filepaths, load_data = True)\n        except Exception as e:\n            print(\"Error type:\", type(e).__name__)\n            print(\"Error message:\", e)\n            print(\"Warning load_dicom_series failed\")\n            print(series_id)\n            volume = np.zeros((256, 512, 512))\n            modality = np.array([0])\n            try:\n                serie, error = load_dicom_serie(series_id, all_filepaths, load_data = True)\n                print(error)\n            except:\n                print(\"Warning load_dicom_series failed 2\")\n                pass\n            pass\n            \n        \n        final_logits_per_model = []\n        TTA_NUM = 2\n        for i in range(0, len(MODELS)):\n            model_logits = []\n            weight = weights[i]\n            volume_pre = process_volumes[i](volume, modality, preprocess_transforms[i])\n            logits = MODELS[i](volume_pre.to(device))\n    \n            if(logits.shape[1] == 13):\n                aneurysm_present = torch.max(logits[:, 0:13], dim=1, keepdim=True).values\n                logits_modified = torch.cat([logits[:, 0:13], aneurysm_present], dim=1)\n                logits = logits_modified\n            model_logits.append(logits)\n    \n            for j in range(0, TTA_NUM):\n                volume_tta = process_volumes[i](volume, modality, train_transforms[i])\n                logits_tta = MODELS[i](volume_tta.to(device))\n                if(logits_tta.shape[1] == 13):\n                    aneurysm_present = torch.max(logits_tta[:, 0:13], dim=1, keepdim=True).values\n                    logits_modified = torch.cat([logits_tta[:, 0:13], aneurysm_present], dim=1)\n                    logits_tta = logits_modified\n                model_logits.append(logits_tta)\n    \n            model_stacked_logits = torch.cat(model_logits)\n            model_averaged_logits = torch.mean(model_stacked_logits, 0)\n            final_logits_per_model.append(weight * model_averaged_logits)\n    \n        averaged_logits = torch.stack(final_logits_per_model).sum(dim=0)\n        label = torch.sigmoid(averaged_logits)\n        label = label.cpu().detach().numpy()\n    \n        # ... do some machine learning magic ...\n        predictions = pl.DataFrame(\n            data=[[series_id] + label.tolist()],\n            schema=[ID_COL, *LABEL_COLS],\n            orient='row',\n        )\n        # ----------------------------------------------------------------------\n    \n        if isinstance(predictions, pl.DataFrame):\n            assert predictions.columns == [ID_COL, *LABEL_COLS]\n        elif isinstance(predictions, pd.DataFrame):\n            assert (predictions.columns == [ID_COL, *LABEL_COLS]).all()\n        else:\n            raise TypeError('The predict function must return a DataFrame')\n    \n        # ----------------------------- IMPORTANT ------------------------------\n        # You MUST have the following code in your `predict` function\n        # to prevent \"out of disk space\" errors. This is a temporary workaround\n        # as we implement improvements to our evaluation system.\n        shutil.rmtree('/kaggle/shared', ignore_errors=True)\n        # ----------------------------------------------------------------------\n    \n    return predictions.drop(ID_COL)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-23T11:27:58.84112Z","iopub.execute_input":"2025-10-23T11:27:58.841284Z","iopub.status.idle":"2025-10-23T11:27:58.857157Z","shell.execute_reply.started":"2025-10-23T11:27:58.84127Z","shell.execute_reply":"2025-10-23T11:27:58.856614Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"inference_server = kaggle_evaluation.rsna_inference_server.RSNAInferenceServer(predict)\n\nif os.getenv('KAGGLE_IS_COMPETITION_RERUN'):\n    inference_server.serve()\nelse:\n    inference_server.run_local_gateway()\n    display(pl.read_parquet('/kaggle/working/submission.parquet'))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-23T11:27:58.858968Z","iopub.execute_input":"2025-10-23T11:27:58.859154Z","iopub.status.idle":"2025-10-23T11:29:20.732172Z","shell.execute_reply.started":"2025-10-23T11:27:58.859139Z","shell.execute_reply":"2025-10-23T11:29:20.73163Z"}},"outputs":[],"execution_count":null}]}