{"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"},"vscode":{"interpreter":{"hash":"f7241b2af102f7e024509099765066b36197b195077f7bfac6e5bc041ba17c8c"}},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceId":99552,"databundleVersionId":13851420,"sourceType":"competition"},{"sourceId":13373153,"sourceType":"datasetVersion","datasetId":8369449},{"sourceId":13374858,"sourceType":"datasetVersion","datasetId":8304789},{"sourceId":264678664,"sourceType":"kernelVersion"}],"dockerImageVersionId":31090,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"EVAL = False\nDEBUG = False\n\nimport os\nif os.getenv('KAGGLE_IS_COMPETITION_RERUN'):\n    EVAL = False\n    DEBUG = False","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-13T21:16:24.897048Z","iopub.execute_input":"2025-10-13T21:16:24.897339Z","iopub.status.idle":"2025-10-13T21:16:24.901328Z","shell.execute_reply.started":"2025-10-13T21:16:24.897318Z","shell.execute_reply":"2025-10-13T21:16:24.900635Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Imports","metadata":{}},{"cell_type":"code","source":"import os\nimport gc\nimport cv2\nimport sys\nimport glob\nimport json\nimport torch\nimport joblib\nimport shutil\nimport numpy as np\nimport polars as pl\nimport pandas as pd\nimport seaborn as sns\nimport matplotlib.pyplot as plt\nimport torch.nn.functional as F\n\nfrom tqdm import tqdm\nfrom scipy.special import expit\nfrom collections import Counter, defaultdict\n\npd.set_option('max_colwidth', 400)\npd.set_option('display.max_columns', 400)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-13T21:12:02.41432Z","iopub.execute_input":"2025-10-13T21:12:02.414559Z","iopub.status.idle":"2025-10-13T21:12:05.557701Z","shell.execute_reply.started":"2025-10-13T21:12:02.414538Z","shell.execute_reply":"2025-10-13T21:12:05.557121Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"if os.path.exists(\"/kaggle/input/rsna-2025-pkgs/\"):\n    !pip install /kaggle/input/rsna-2025-pkgs/segmentation_models_pytorch-0.5.0-py3-none-any.whl --no-deps\n    !pip install /kaggle/input/rsna-2025-pkgs/dicomsdl-0.109.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl --no-deps","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-13T21:12:05.558841Z","iopub.execute_input":"2025-10-13T21:12:05.559213Z","iopub.status.idle":"2025-10-13T21:12:09.408303Z","shell.execute_reply.started":"2025-10-13T21:12:05.559187Z","shell.execute_reply":"2025-10-13T21:12:09.407582Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"sys.path.append(\"/kaggle/input/rsna-intracranial-aneurysm-code/src\")\n\nfrom params import CLASSES_SEG, CLASSES\n\nfrom data.processing import process, ct_windowing\nfrom data.utils import get_largest_component_bbox, to_axial\n\nfrom model_zoo.models_lvl2 import define_model as define_model_lvl2\nfrom model_zoo.models_seg import define_model as define_model_seg\nfrom model_zoo.models import define_model\n\nfrom inference.lvl2 import preprocess_features, resize_fts_torch\nfrom inference.utils import predict_modality, preprocessing_modality\nfrom inference.extract_features import preprocess_images\nfrom inference.processing import load_metadata, load_frame\n\nfrom util.logger import Config\nfrom util.torch import load_model_weights\nfrom util.plots import plot_sample, show_label_colors, plot_features\nfrom util.metrics import *","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-13T21:12:09.40928Z","iopub.execute_input":"2025-10-13T21:12:09.409555Z","iopub.status.idle":"2025-10-13T21:13:04.80612Z","shell.execute_reply.started":"2025-10-13T21:12:09.409517Z","shell.execute_reply":"2025-10-13T21:13:04.805337Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Params","metadata":{}},{"cell_type":"code","source":"USE_FP16 = True\nDEVICE = \"cuda\"\nNUM_WORKERS = os.cpu_count()\n\nFOLD = 0  # if DEBUG else \"fullfit_0\"","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-13T21:13:04.807673Z","iopub.execute_input":"2025-10-13T21:13:04.807914Z","iopub.status.idle":"2025-10-13T21:13:04.811536Z","shell.execute_reply.started":"2025-10-13T21:13:04.80789Z","shell.execute_reply":"2025-10-13T21:13:04.810839Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Skull","metadata":{}},{"cell_type":"code","source":"WINDOW = False\nSEG_SIZE = (128, 128, 128)\n\nEXP_FOLDER_SKULL = \"/kaggle/input/rsna-2025-weights-1/2025-09-12_25/\"  # TotalSegmentor r18\nSKULL_TH = 0.5","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-13T21:13:04.812335Z","iopub.execute_input":"2025-10-13T21:13:04.812615Z","iopub.status.idle":"2025-10-13T21:13:04.832787Z","shell.execute_reply.started":"2025-10-13T21:13:04.812589Z","shell.execute_reply":"2025-10-13T21:13:04.832105Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"config_skull = Config(json.load(open(EXP_FOLDER_SKULL + \"config.json\", \"r\")))\n\nskull_model = define_model_seg(\n    config_skull.decoder_name,\n    config_skull.name,\n    num_classes=config_skull.num_classes,\n    num_classes_aux=config_skull.num_classes_aux,\n    increase_stride=config_skull.increase_stride,\n    use_cls=config_skull.use_cls,\n    n_channels=config_skull.n_channels,\n    use_3d=config_skull.use_3d,\n    pretrained=False\n).to(DEVICE)\n\nfold = 0\nskull_model = load_model_weights(skull_model, EXP_FOLDER_SKULL + f\"{config_skull.name}_{fold}.pt\")\nskull_model = skull_model.to(DEVICE).eval()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-13T21:13:04.833426Z","iopub.execute_input":"2025-10-13T21:13:04.833601Z","iopub.status.idle":"2025-10-13T21:13:06.894624Z","shell.execute_reply.started":"2025-10-13T21:13:04.833586Z","shell.execute_reply":"2025-10-13T21:13:06.893872Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Image Models","metadata":{}},{"cell_type":"code","source":"# IMG_EXP_FOLDERS = [        \n#     (\"img\", \"/kaggle/input/rsna-2025-weights-1/2025-10-03_6/\"),   # maxvit_rmlp_base coroT - 0.872\n#     (\"img_s\", \"/kaggle/input/rsna-2025-weights-1/2025-10-03_5/\")  # coatnet2 384 ext  - 0.864\n# ]\n\n# IMG_EXP_FOLDERS = [  # 0.884 \n#     (\"img\", \"/kaggle/input/rsna-2025-weights-1/2025-10-07_5/\"),   # maxvit_rmlp_base\n#     (\"img_s\", \"/kaggle/input/rsna-2025-weights-1/2025-10-07_6/\")  # coatnet2 384\n# ]\n\nIMG_EXP_FOLDERS = [        \n    (\"img\", \"/kaggle/input/rsna-2025-weights-1/2025-10-10_2/\"),   # maxvit_rmlp_base\n    (\"img\", \"/kaggle/input/rsna-2025-weights-1/2025-10-10_3/\"),   # coatnet2 384\n    (\"img\", \"/kaggle/input/rsna-2025-weights-1/2025-10-11_10/\"), \n]\n\nFOLDS = [0]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-13T21:46:40.422398Z","iopub.execute_input":"2025-10-13T21:46:40.423117Z","iopub.status.idle":"2025-10-13T21:46:40.4269Z","shell.execute_reply.started":"2025-10-13T21:46:40.42309Z","shell.execute_reply":"2025-10-13T21:46:40.426186Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"img_models = defaultdict(list)\n\nFOLDS = [0] if DEBUG or EVAL else ['fullfit_0']\n\nfor mode, exp_folder in IMG_EXP_FOLDERS:\n    config = Config(json.load(open(exp_folder + \"config.json\", \"r\")))\n\n    for fold in FOLDS:\n        model = define_model(\n            config.name,\n            num_classes=config.num_classes,\n            num_classes_aux=config.num_classes_aux,\n            n_channels=config.n_channels,\n            drop_rate=config.drop_rate,\n            drop_path_rate=config.drop_path_rate,\n            pooling=config.pooling if hasattr(config, \"pooling\") else \"avg\",\n            img_size=config.resize[0],\n            head_3d=config.head_3d,\n            delta=config.delta if hasattr(config, \"delta\") else 2,\n            n_frames=config.n_frames,\n            use_mask=config.use_mask,\n            pretrained=False,\n            verbose=0,\n        )\n        model = model.to(DEVICE).eval()\n\n        weights = exp_folder + f\"{config.name}_{fold}.pt\"\n        model = load_model_weights(model, weights, verbose=1)\n        img_models[(mode, exp_folder)].append(model)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-13T21:46:41.677869Z","iopub.execute_input":"2025-10-13T21:46:41.67813Z","iopub.status.idle":"2025-10-13T21:46:50.578013Z","shell.execute_reply.started":"2025-10-13T21:46:41.678107Z","shell.execute_reply":"2025-10-13T21:46:50.577196Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Level 2","metadata":{}},{"cell_type":"code","source":"# LVL2_EXP_FOLDERS = [\n#     \"/kaggle/input/rsna-2025-weights-1/2025-10-10_5/\"\n# ]\n\n# LVL2_EXP_FOLDERS = [\"../logs/2025-10-10/5/\"]\n\nLVL2_EXP_FOLDERS = [\n    # 2 models - 0.891\n    # \"/kaggle/input/rsna-2025-weights-1/2025-10-13_9/\",  # CV 0.889 - RNN 2 models restrict 64 ax128\n    # \"/kaggle/input/rsna-2025-weights-1/2025-10-13_10/\",  # CV 0.890  - Transfo 2 models ax128\n\n    # 3 models - 0.895\n    \"/kaggle/input/rsna-2025-weights-1/2025-10-13_13/\",  # CV 0.892 - RNN 3 models restrict 64 ax128\n    \"/kaggle/input/rsna-2025-weights-1/2025-10-13_14/\",  # CV 0.894  - Transfo 3 models ax128\n]\n\nFOLDS = [0] if DEBUG or EVAL else [0, 1, 2, 3]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-13T21:46:50.579086Z","iopub.execute_input":"2025-10-13T21:46:50.579297Z","iopub.status.idle":"2025-10-13T21:46:50.583827Z","shell.execute_reply.started":"2025-10-13T21:46:50.579281Z","shell.execute_reply":"2025-10-13T21:46:50.583032Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"cp -r /kaggle/input/rsna-intracranial-aneurysm-code/src/configs ./","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-13T21:13:14.898683Z","iopub.execute_input":"2025-10-13T21:13:14.898904Z","iopub.status.idle":"2025-10-13T21:13:15.072579Z","shell.execute_reply.started":"2025-10-13T21:13:14.898879Z","shell.execute_reply":"2025-10-13T21:13:15.071611Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"lvl2_models = []\n\nfor exp_folder in LVL2_EXP_FOLDERS:\n    config_lvl2 = Config(json.load(open(exp_folder + \"config.json\", \"r\")))\n\n    for fold in FOLDS:\n        model = define_model_lvl2(\n            config_lvl2.name,\n            ft_dim=config_lvl2.ft_dim,\n            layer_dim=config_lvl2.layer_dim,\n            dense_dim=config_lvl2.dense_dim,\n            p=config_lvl2.p,\n            num_classes=config_lvl2.num_classes,\n            num_classes_aux=config_lvl2.num_classes_aux,\n            n_crop_fts=config_lvl2.n_crop_fts,\n            layer=config_lvl2.layer,\n            \n        )\n        model = model.to(DEVICE).eval()\n\n        weights = exp_folder + f\"{config_lvl2.name}_{fold}.pt\"\n        model = load_model_weights(model, weights, verbose=1)\n        lvl2_models.append([config_lvl2, model])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-13T21:46:51.678479Z","iopub.execute_input":"2025-10-13T21:46:51.678756Z","iopub.status.idle":"2025-10-13T21:46:52.027583Z","shell.execute_reply.started":"2025-10-13T21:46:51.678732Z","shell.execute_reply":"2025-10-13T21:46:52.026757Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Main","metadata":{}},{"cell_type":"code","source":"def pipeline(series, data_path, plot=False, verbose=False):\n    df_series = load_metadata(series, data_path)\n\n    orientation = df_series['orientation'].values[0]\n    spacing = df_series['slice_spacing'].values[0]\n    modality = Counter(df_series['Modality']).most_common()[0][0]\n    \n    if verbose:\n        print(f'Orientation: {orientation} - Spacing: {spacing} - Modality {modality}')\n\n    # df_series = trim(df_series)\n    # if spacing:\n    #     if spacing < 0.5 and len(df_series) > 300:\n    #         if verbose:\n    #             print(f'Downsample series with spacing {spacing}')\n    #         ids = np.arange(0, len(df_series), 2, dtype=int)\n    #         spacing *= 2\n    #         df_series = df_series.iloc[ids].reset_index(drop=True)\n\n    images = joblib.Parallel(n_jobs=os.cpu_count())(\n        joblib.delayed(load_frame)(\n            path,\n        ) for path in df_series['path'].values\n    )\n    images = np.array(images)\n    if len(images.shape) == 4:\n        images = images.squeeze(0)\n\n    images = torch.from_numpy(images).to(DEVICE)  # Could move to GPU earlier ?\n\n    if verbose:\n        display(df_series.head(1))\n        print('Image shape', images.shape)\n\n    # Windowing\n    try:\n        if df_series[\"Modality\"].values[0] == \"CT\":\n            b = df_series[\"RescaleIntercept\"].values[0]\n            m = df_series[\"RescaleSlope\"].values[0]\n    \n            if m is not None and b is not None:\n                if m != 1 or b != 0:\n                    min_ = images.min()\n                    if min_ >= -100 or min_ == -2000:  # Ian fix\n                        images = m * images + b\n    \n            # if WINDOW:  # Maybe later ??\n            #     images = ct_windowing(images, mode=\"bone\", normalize=False)    \n    except:\n        pass\n\n    # if PLOT:\n    #     plt.figure(figsize=(5, 5))\n    #     plt.imshow(images.amax(0).cpu().numpy(), cmap=\"bone\")\n    #     plt.axis(False)\n    #     plt.show()\n\n    ###################\n    # Skull inference #\n    ###################\n\n    # try:\n    #     max_ = torch.quantile(images[::4, ::2, ::2].reshape(-1).float(), 0.99)\n    # except RuntimeError:\n    #     try:\n    #         max_ = torch.quantile(images[::8, ::4, ::4].reshape(-1).float(), 0.99)\n    #     except:\n    #         max_ = 0\n    # max_ = max_ if max_ > 0 else None\n\n    images = torch.clamp(images, -1000, None)\n\n    x = F.interpolate(\n        images.unsqueeze(0).unsqueeze(0).float(),\n        SEG_SIZE,\n        mode=\"trilinear\",\n    )\n    # x = torch.clamp(x, -1000, max_)  # NEW\n    x = (x - x.min()) / (x.max() - x.min())\n\n    with torch.inference_mode():\n        with torch.amp.autocast(\"cuda\", enabled=True):\n            pred = skull_model(x)[0]\n            mask_skull = F.interpolate(pred, images.shape, mode=\"trilinear\")[0][0].sigmoid()\n        \n        th = SKULL_TH\n        if mask_skull.max() < SKULL_TH:\n            th = min(0.05, mask_skull.max() / 2)\n        \n        mask_skull = (mask_skull > th).cpu().numpy().astype(np.uint8)\n\n        # TODO: criterion os mask_skull size to filter outliers\n\n    (zmin, zmax), (ymin, ymax), (xmin, xmax) = get_largest_component_bbox(mask_skull)\n    images_crop = images[zmin:zmax, ymin:ymax, xmin:xmax]\n\n    if verbose:\n        print(f'Skull crop : [{zmin}; {zmax}]')\n        print('Crop size', images_crop.size())\n\n    if plot:\n        plt.figure(figsize=(5, 5))\n        plt.imshow(images_crop[len(images_crop) // 2].cpu().numpy(), cmap=\"bone\")\n        plt.axis(False)\n        plt.show()       \n        \n    # images_crop = torch.from_numpy(\n    #     np.load(os.path.join('../input/processed/skull_crops/', series + \".npy\"))\n    # ).to(DEVICE)\n\n    ################\n    # Image models #\n    ################\n    images_crop = images_crop[:MAX_STACK_SIZE]\n\n    if orientation == \"sagittal\":\n        imgs = to_axial(\n            images_crop.clone().float(),\n            orientation,\n            resize=None,\n            fixed=True,\n        )\n        imgs = F.interpolate(\n            imgs.unsqueeze(0).unsqueeze(0), (128, 512, 512), mode=\"trilinear\"\n        )[0, 0]  # should be enough\n        spacing = 1\n    else:\n        imgs = images_crop.clone().float()\n\n    x, masks = preprocess_images(\n        imgs, modality, config, masks=None, spacing=spacing\n    )\n    \n    x = x[:MAX_STACK_SIZES[orientation]]\n\n    if verbose:\n        print('Input size', x.size())\n\n    if len(x) > RESTRICT_SIZE:\n        x = x[::2].clone()\n\n    if len(x) > RESTRICT_SIZE * 2:\n        x = x[::2].clone()\n\n    if verbose:\n        print('Restricted input size', x.size())\n\n    image_model_preds = defaultdict(list)\n    for (mode, exp_folder) in img_models:\n        batches = np.array_split(np.arange(x.size(0)), (x.size(0) // BATCH_SIZE) + 1)\n\n        p = []\n        for model in img_models[(mode, exp_folder)]:\n            with torch.inference_mode():\n                with torch.amp.autocast(\"cuda\", enabled=USE_FP16):\n                    preds = torch.cat(\n                        [model(x[b], mask=None)[0].detach() for b in batches],\n                        axis=0\n                    )\n                    p.append(preds)\n\n        if \"kaggle\" in exp_folder:\n            k = \"../logs/\" + '/'.join(exp_folder.split('/', 4)[-1].split('_'))\n        else:\n            k = exp_folder\n        image_model_preds[(mode, k)] = torch.stack(p, 0).mean(0)\n    \n    if plot:\n        for k in image_model_preds:\n            plot_features(image_model_preds[k].cpu().sigmoid(), figsize=(10, 5))\n\n    ###############\n    # Lvl2 models #\n    ###############\n\n    lvl2_preds = []\n    for cfg, model in lvl2_models:\n        # create input\n        fts_dict = preprocess_features(cfg, image_model_preds)\n\n        # for k in fts_dict:\n        #     try:\n        #         plot_features(fts_dict[k].cpu().sigmoid()[0], figsize=(10, 5))\n        #     except:\n        #         continue\n\n        # for k in fts_dict:\n        #     print(k, fts_dict[k].mean(), fts_dict[k].max(), fts_dict[k].size())\n\n        with torch.inference_mode():\n            p = model(fts_dict)[0][0].sigmoid().cpu().numpy()\n            lvl2_preds.append(p)\n    lvl2_preds = np.mean(lvl2_preds, 0)\n\n    if verbose:\n        print('\\n-> Preds:\\n')\n        for i, c in enumerate(CLASSES):\n            print(f'{c} : {lvl2_preds[i] :.3f}')\n\n    return lvl2_preds","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-13T21:46:56.55045Z","iopub.execute_input":"2025-10-13T21:46:56.550729Z","iopub.status.idle":"2025-10-13T21:46:56.569931Z","shell.execute_reply.started":"2025-10-13T21:46:56.550709Z","shell.execute_reply":"2025-10-13T21:46:56.569153Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Sample","metadata":{}},{"cell_type":"code","source":"WINDOW = False\nUSE_FP16 = True\n\nMAX_STACK_SIZES = {\"axial\": 300, \"sagittal\": 100, \"coronal\": 75}\nMAX_STACK_SIZE = 300\nRESTRICT_SIZE = 64\n\nBATCH_SIZE = 32","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-13T21:46:57.759586Z","iopub.execute_input":"2025-10-13T21:46:57.759839Z","iopub.status.idle":"2025-10-13T21:46:57.76395Z","shell.execute_reply.started":"2025-10-13T21:46:57.759819Z","shell.execute_reply":"2025-10-13T21:46:57.763041Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df = pd.read_csv(LVL2_EXP_FOLDERS[0] + 'df_val_0.csv')[['SeriesInstanceUID', \"Modality\"]]\ny = pd.read_csv('/kaggle/input/rsna-intracranial-aneurysm-detection/train.csv')[[\"SeriesInstanceUID\"] + CLASSES]\ndf = df.merge(y, on=\"SeriesInstanceUID\")\n\ndf.head(1)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-13T21:46:59.523915Z","iopub.execute_input":"2025-10-13T21:46:59.524427Z","iopub.status.idle":"2025-10-13T21:46:59.562762Z","shell.execute_reply.started":"2025-10-13T21:46:59.524404Z","shell.execute_reply":"2025-10-13T21:46:59.562158Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Sample","metadata":{}},{"cell_type":"code","source":"if DEBUG:\n    DATA_PATH = \"/kaggle/input/rsna-intracranial-aneurysm-detection/series/\"\n    \n    idx = np.random.randint(len(df))\n    # idx = 4\n    series = df['SeriesInstanceUID'].values[idx]\n\n    # series = '1.2.826.0.1.3680043.8.498.72399220582146674554688600955035038848'\n    # series = '1.2.826.0.1.3680043.8.498.62690866817595040372272270948444876445'\n    # idx = df['SeriesInstanceUID'].values.tolist().index(series)\n\n    preds = pipeline(series, DATA_PATH, plot=False, verbose=True)\n\n    gc.collect()\n    torch.cuda.empty_cache()\n\n    pred_val = np.mean([\n        np.load(e + \"pred_val_0.npy\") for e in LVL2_EXP_FOLDERS\n    ], 0)\n    delta = np.max(np.abs(pred_val[idx] - preds))\n\n    print()\n    if delta > 0.01:\n        for i, c in enumerate(CLASSES):\n            d = np.abs(pred_val[idx][i] - preds[i])\n            print(f'{\"❌\" if d > 0.01 else \"✅\"} {c} : {preds[i] :.3f}  (delta={d:.2f})')\n    else:\n        print('✅ Predictions ok !')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-13T21:47:00.94362Z","iopub.execute_input":"2025-10-13T21:47:00.944251Z","iopub.status.idle":"2025-10-13T21:47:10.841414Z","shell.execute_reply.started":"2025-10-13T21:47:00.944224Z","shell.execute_reply":"2025-10-13T21:47:10.84068Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Main\n- 100 series:\n  - Loading : 4min\n  - Loading with multi-processing & dicomsdl: 2min xx\n  - Loading + to_gpu + Skull + Modality : 6min\n  - Loading + to_gpu + Skull + Modality + 1 model : 11min\n  - Loading + to_gpu + Skull + Modality + 1 model restrict 200: 10min\n  - Total: 25min\n  - Total + dicomsdl multi-proc + restrict: 19 min 30s\n- 200 series:\n  - Total: 37 min 30s\n- 100 series, 2x T4, 2 models \n  -  10'27\"","metadata":{}},{"cell_type":"markdown","source":"- 10 samples\n   - 3x maxvit_rmlp_base_rw_384: 2'36\n   - 1x maxvit_rmlp_base_rw_384: 1'04\n   - 1x coatnet_rmlp_2_384: 47\"\n   - 2x coat + 1x maxvit: 1'54\"\n   - No img model: 22s\n- P100 : 1:16\n- T4: 1:16","metadata":{}},{"cell_type":"code","source":"if EVAL:\n    DATA_PATH = \"/kaggle/input/rsna-intracranial-aneurysm-detection/series/\"\n    N = 200\n\n    preds = []\n    for series in tqdm(df['SeriesInstanceUID'].values[:N]):\n        p = pipeline(series, DATA_PATH)\n        preds.append(p)\n    preds = np.array(preds)\n\n    df.head(N).to_csv('df_val.csv', index=False)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-13T21:47:13.307118Z","iopub.execute_input":"2025-10-13T21:47:13.307376Z","iopub.status.idle":"2025-10-13T22:11:07.725044Z","shell.execute_reply.started":"2025-10-13T21:47:13.307354Z","shell.execute_reply":"2025-10-13T22:11:07.724281Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"if EVAL:\n    try:\n        try:\n            aucs, auc_w = weighted_multilabel_auc(\n                df[CLASSES].values[:N],\n                preds,\n                average=False\n            )\n    \n            print()\n            for i in range(len(aucs)):\n                print(f\"-  AUC:  {aucs[i]:.3f}  -  {CLASSES[i]}\")\n            print(f\"\\n -> CV AUC : {auc_w:.3f}\\n\")\n        except:\n            aucs, auc_w = weighted_multilabel_auc(\n                df[CLASSES].values[:N][:, -1:],\n                preds[:, -1:],\n                average=False\n            )\n            print(f\"\\n -> Aneurysm Present AUC : {auc_w:.3f}\\n\")\n    except:\n        pass","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-13T22:11:07.726379Z","iopub.execute_input":"2025-10-13T22:11:07.726649Z","iopub.status.idle":"2025-10-13T22:11:07.742819Z","shell.execute_reply.started":"2025-10-13T22:11:07.726626Z","shell.execute_reply":"2025-10-13T22:11:07.741929Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"if EVAL:\n    # Reference Val score\n    pred_val = np.mean([\n        np.load(e + \"pred_val_0.npy\") for e in LVL2_EXP_FOLDERS\n    ], 0)\n    \n    try:\n        try:\n            aucs, auc_w = weighted_multilabel_auc(\n                df[CLASSES].values[:N],\n                pred_val[:N],\n                average=False\n            )\n    \n            print()\n            for i in range(len(aucs)):\n                print(f\"-  AUC:  {aucs[i]:.3f}  -  {CLASSES[i]}\")\n            print(f\"\\n -> CV AUC : {auc_w:.3f}\\n\")\n        except:\n            aucs, auc_w = weighted_multilabel_auc(\n                df[CLASSES].values[:N][:, -1:],\n                pred_val[:N, -1:],\n                average=False\n            )\n            print(f\"\\n -> Aneurysm Present AUC : {auc_w:.3f}\\n\")\n    except:\n        pass","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-13T22:11:07.743636Z","iopub.execute_input":"2025-10-13T22:11:07.74392Z","iopub.status.idle":"2025-10-13T22:11:07.766454Z","shell.execute_reply.started":"2025-10-13T22:11:07.743897Z","shell.execute_reply":"2025-10-13T22:11:07.765832Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## API","metadata":{}},{"cell_type":"code","source":"def predict(series_path: str) -> pl.DataFrame | pd.DataFrame:\n    \"\"\"\n    Make a prediction.\n    \"\"\"\n    series_id = os.path.basename(series_path)\n    folder = series_path.rsplit('/', 1)[0] + \"/\"\n\n    preds = pipeline(series_id, folder, plot=False, verbose=False)\n\n    gc.collect()\n    torch.cuda.empty_cache()\n\n    predictions = pl.DataFrame(\n        data=[list(preds)],\n        schema=CLASSES,\n        orient='row',\n    )\n    shutil.rmtree('/kaggle/shared', ignore_errors=True)\n    return predictions","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-13T18:27:56.541419Z","iopub.execute_input":"2025-10-13T18:27:56.541604Z","iopub.status.idle":"2025-10-13T18:27:56.558638Z","shell.execute_reply.started":"2025-10-13T18:27:56.541585Z","shell.execute_reply":"2025-10-13T18:27:56.558036Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"%%time\nimport kaggle_evaluation.rsna_inference_server\n\nshutil.rmtree('/kaggle/shared', ignore_errors=True)\ninference_server = kaggle_evaluation.rsna_inference_server.RSNAInferenceServer(predict)\n\nif os.getenv('KAGGLE_IS_COMPETITION_RERUN'):\n    inference_server.serve()\nelse:\n    if os.path.exists('df_val.csv'):\n        data_paths=('df_val.csv', \"/kaggle/input/rsna-intracranial-aneurysm-detection/series/\")\n    else:\n        data_paths = None\n    print(data_paths)\n    inference_server.run_local_gateway(data_paths=data_paths)\n    display(pd.read_parquet('/kaggle/working/submission.parquet'))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-13T18:27:56.559399Z","iopub.execute_input":"2025-10-13T18:27:56.559725Z","iopub.status.idle":"2025-10-13T18:28:23.756074Z","shell.execute_reply.started":"2025-10-13T18:27:56.559701Z","shell.execute_reply":"2025-10-13T18:28:23.755317Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Done ! ","metadata":{}}]}