{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"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"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":99552,"databundleVersionId":13190393,"sourceType":"competition"}],"dockerImageVersionId":31089,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Imports","metadata":{}},{"cell_type":"code","source":"import os\nimport ast\nimport numpy as np\nimport pandas as pd\nimport pydicom\nimport nibabel as nib\nimport matplotlib.pyplot as plt\nimport cv2\nfrom scipy import ndimage\nfrom tqdm import tqdm\nfrom concurrent.futures import ProcessPoolExecutor, as_completed\nfrom typing import Sequence, Tuple\nimport warnings\nwarnings.filterwarnings('ignore')\n\nimport tensorflow as tf\nfrom tensorflow.keras import layers, models\nimport pydicom\nfrom scipy.ndimage import zoom\nimport glob\nfrom collections import deque","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-08-20T13:28:12.551457Z","iopub.execute_input":"2025-08-20T13:28:12.551765Z","iopub.status.idle":"2025-08-20T13:28:32.779985Z","shell.execute_reply.started":"2025-08-20T13:28:12.551739Z","shell.execute_reply":"2025-08-20T13:28:32.779178Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Preprocessing for RestNet3D-18","metadata":{}},{"cell_type":"code","source":"preprocessing = True\nSERIES_ROOT_TRAIN = \"/kaggle/input/rsna-intracranial-aneurysm-detection/series\"\nTRAIN_CSV         = \"/kaggle/input/rsna-intracranial-aneurysm-detection/train.csv\"\nLOCALIZER_CSV     = \"/kaggle/input/rsna-intracranial-aneurysm-detection/train_localizers.csv\"\n\nID_COL = 'SeriesInstanceUID'\nLABEL_COLS = [\n    'Left Infraclinoid Internal Carotid Artery', 'Right Infraclinoid Internal Carotid Artery',\n    'Left Supraclinoid Internal Carotid Artery', 'Right Supraclinoid Internal Carotid Artery',\n    'Left Middle Cerebral Artery', 'Right Middle Cerebral Artery', 'Anterior Communicating Artery',\n    'Left Anterior Cerebral Artery', 'Right Anterior Cerebral Artery',\n    'Left Posterior Communicating Artery', 'Right Posterior Communicating Artery',\n    'Basilar Tip', 'Other Posterior Circulation', 'Aneurysm Present',\n]\nTARGET_COL = 'Aneurysm Present'\n\nMODEL_WEIGHT = \"/kaggle/working/model/RSNA_Intracranial_Aneurysm_Detection\"\n\nDEBUG = False\nTRAIN = True\n\nTARGET_SIZE = (32, 384, 384) ","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-20T13:28:32.781451Z","iopub.execute_input":"2025-08-20T13:28:32.782013Z","iopub.status.idle":"2025-08-20T13:28:32.787951Z","shell.execute_reply.started":"2025-08-20T13:28:32.781988Z","shell.execute_reply":"2025-08-20T13:28:32.786961Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Data analysis","metadata":{}},{"cell_type":"code","source":"train_df = pd.read_csv(TRAIN_CSV)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-20T13:28:32.788817Z","iopub.execute_input":"2025-08-20T13:28:32.789041Z","iopub.status.idle":"2025-08-20T13:28:32.835153Z","shell.execute_reply.started":"2025-08-20T13:28:32.789021Z","shell.execute_reply":"2025-08-20T13:28:32.834373Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Process","metadata":{}},{"cell_type":"code","source":"class CustomResNet3D:\n    def __init__(self, target_size, label_cols):\n        self.input_shape = (*target_size, 1)\n        self.num_classes = len(label_cols) - 1\n        self.model = self._build_model()\n\n    def _conv3d_block(self, x, filters, kernel_size=3, stride=1, dropout_rate=0.3):\n        shortcut = x\n        x = layers.Conv3D(filters, kernel_size, strides=stride, padding='same')(x)\n        x = layers.BatchNormalization()(x)\n        x = layers.ReLU()(x)\n\n        x = layers.Conv3D(filters, kernel_size, strides=1, padding='same')(x)\n        x = layers.BatchNormalization()(x)\n\n        if stride != 1 or shortcut.shape[-1] != filters:\n            shortcut = layers.Conv3D(filters, 1, strides=stride, padding='same')(shortcut)\n            shortcut = layers.BatchNormalization()(shortcut)\n\n        x = layers.Add()([x, shortcut])\n        x = layers.ReLU()(x)\n        if dropout_rate > 0:\n            x = layers.Dropout(dropout_rate)(x)\n        return x\n\n    def _build_model(self):\n        inputs = layers.Input(shape=self.input_shape)\n        x = layers.Conv3D(32, 7, strides=2, padding='same')(inputs)\n        x = layers.BatchNormalization()(x)\n        x = layers.ReLU()(x)\n        x = layers.MaxPooling3D(pool_size=3, strides=2, padding='same')(x)\n\n        x = self._conv3d_block(x, 32)\n        x = self._conv3d_block(x, 64, stride=2)\n        x = self._conv3d_block(x, 128, stride=2)\n        x = self._conv3d_block(x, 256, stride=2)\n\n        x = layers.GlobalAveragePooling3D()(x)\n\n        class_output = layers.Dense(self.num_classes, activation='softmax', name='class_output')(x)\n        label_output = layers.Dense(1, activation='sigmoid', name='label_output')(x)\n\n        return models.Model(inputs=inputs, outputs=[class_output, label_output])\n\n    def compile(self, optimizer='adam'):\n        self.model.compile(\n            optimizer=optimizer,\n            loss={\n                'class_output': 'categorical_crossentropy',\n                'label_output': 'binary_crossentropy',\n            },\n            metrics={\n                'class_output': 'accuracy',\n                'label_output': 'accuracy',\n            }\n        )\n\n    def summary(self):\n        return self.model.summary()\n\n    def get_model(self):\n        return self.model\n        \n    # Preprocessing\n    def filtering(self, image): #TODO\n        image = image.astype(np.float32)\n        mean, std = np.mean(image), np.std(image)\n        if std > 0:\n            image = (image - mean) / std\n        else:\n            image = image - mean\n        return image\n\n    def resize_and_pad(self, image, target_shape): #TODO\n        h, w = image.shape\n        th, tw = target_shape\n        new_h = min(h, th)\n        new_w = min(w, tw)\n        resized = cv2.resize(image, (new_w, new_h), interpolation=cv2.INTER_AREA)\n        pad_h = th - new_h\n        pad_w = tw - new_w\n        padded = np.pad(resized, ((0, pad_h), (0, pad_w)), \n                        mode='constant', constant_values=0)\n        return padded\n\n    def resize_image(self, img: np.ndarray, target_shape: Tuple[int, int]) -> np.ndarray:\n        \"\"\"\n        Resize an image to match the target shape using interpolation.\n    \n        Parameters:\n        - img: Input image as a NumPy array.\n        - target_shape: Tuple (height, width) for the desired output size.\n    \n        Returns:\n        - Resized image as a NumPy array.\n        \"\"\"\n        target_height, target_width = target_shape\n    \n        # Choose interpolation method based on scaling direction\n        if target_height > img.shape[0] or target_width > img.shape[1]:\n            interpolation = cv2.INTER_CUBIC  # better for upscaling\n        else:\n            interpolation = cv2.INTER_AREA   # better for downscaling\n    \n        resized = cv2.resize(img, (target_width, target_height), interpolation=interpolation)\n        return resized\n    \n    def preprocessing(self, img, target_shape): #TODO\n        # print(target_shape)\n        image = self.filtering(img)\n        image = self.resize_image(image, target_shape)\n        # print(target_shape)\n        return np.array(image).astype(np.float32)  # float16 possible\n    \n    # Processing\n    ## loading weight \n    def load_latest_weights(self, save_dir=MODEL_WEIGHT):\n        ckpts = sorted(glob.glob(os.path.join(save_dir, \"weights_*.h5\")))\n        if not ckpts:\n            print(\"Aucun checkpoint trouvé.\")\n            return False\n        latest = ckpts[-1]\n        self.model.load_weights(latest)\n        print(f\"Poids chargés depuis : {latest}\")\n        return True\n        \n    def _save_weights_fifo(self, save_dir=MODEL_WEIGHT, max_keep=5):\n        os.makedirs(save_dir, exist_ok=True)\n        # Nom du fichier basé sur timestamp\n        import datetime\n        timestamp = datetime.datetime.now().strftime(\"%Y%m%d_%H%M%S\")\n        filepath = os.path.join(save_dir, f\"weights_{timestamp}.h5\")\n        self.model.save_weights(filepath)\n    \n        # FIFO : garde seulement les derniers fichiers\n        ckpts = sorted(glob.glob(os.path.join(save_dir, \"weights_*.h5\")))\n        if len(ckpts) > max_keep:\n            for old_ckpt in ckpts[:-max_keep]:\n                try:\n                    os.remove(old_ckpt)\n                except OSError:\n                    pass\n    ## loading data\n    def load_and_process_dicom_series(self, series_path, target_shape=None):\n\n        \"\"\"\n        Charge un volume DICOM complet, applique le prétraitement slice par slice,\n        et retourne un tenseur (1, D, H, W, 1) sans altérer la profondeur.\n    \n        Args:\n            series_path (str): Dossier contenant les fichiers DICOM\n            target_shape (tuple): Taille (H, W) des slices après resize/pad\n        \"\"\"\n        if target_shape is None:\n            target_shape = (self.input_shape[0:3]) # (H, W) à partir de input_shape = (D,H,W,1)\n        dicom_files = sorted(os.listdir(series_path))\n        \n        if len(dicom_files) == 1:\n            dcm = pydicom.dcmread(os.path.join(series_path, dicom_files[0]))\n            volume = dcm.pixel_array  # (D, H, W)\n            processed_slices = [self.preprocessing(slice_, target_shape[1:3]) for slice_ in volume]\n        else:\n            slices = [pydicom.dcmread(os.path.join(series_path, f)).pixel_array\n                      for f in dicom_files]\n            processed_slices = [self.preprocessing(slice_, target_shape[1:3]) for slice_ in slices]\n        # print(np.shape(processed_slices))\n        volume = np.array(processed_slices, dtype=np.float32)\n        # print(volume.shape, target_shape, flush = True)\n        factors = [t / s for s, t in zip(volume.shape, target_shape)]\n        resized_vol = zoom(volume, zoom=factors, order=1)\n\n    \n        # --- Ajout channel + batch ---\n        volume = np.expand_dims(resized_vol, axis=-1)   # (D, H, W, 1)\n        volume = np.expand_dims(volume, axis=0)    # (1, D, H, W, 1)\n        return tf.convert_to_tensor(volume, dtype=tf.float32)\n    \n    def train_on_batch_samples(self, sample_paths, labels_class, labels_binary, \n                               batch_size=4, epochs=10, shuffle=True):\n        \"\"\"\n        Entraîne le modèle sur un batch de volumes DICOM.\n    \n        Args:\n            sample_paths (list[str]): Liste des chemins vers les dossiers séries DICOM.\n            labels_class (list[int]): Labels pour la classification multi-classes.\n            labels_binary (list[int/float]): Labels binaires.\n            batch_size (int): Taille de batch.\n            epochs (int): Nombre d'époques d'entraînement.\n            shuffle (bool): Mélanger les données à chaque époque.\n        \"\"\"\n        import numpy as np\n        import tensorflow as tf\n    \n        # --- Préparation des données ---\n        X = []\n        Y_class = []\n        Y_label = []\n    \n        for path, lc, lb in zip(sample_paths, labels_class, labels_binary):\n            volume = self.load_and_process_dicom_series(path)  # (1, D, H, W, 1)\n            X.append(volume.numpy()[0])       # on enlève la dim batch ajoutée par load\n            Y_class.append(lc)\n            Y_label.append(lb)\n    \n        # Conversion en tenseurs\n        X = np.array(X, dtype=np.float32)  # (N, D, H, W, 1)\n        Y_class = tf.one_hot(Y_class, depth=self.num_classes)\n        Y_label = np.array(Y_label, dtype=np.float32)\n    \n        # --- Dataset TensorFlow ---\n        dataset = tf.data.Dataset.from_tensor_slices((\n            X,\n            {\n                'class_output': Y_class,\n                'label_output': Y_label,\n            }\n        ))\n    \n        if shuffle:\n            dataset = dataset.shuffle(buffer_size=len(sample_paths))\n    \n        dataset = dataset.batch(batch_size).prefetch(tf.data.AUTOTUNE)\n\n        callbacks = [\n                    tf.keras.callbacks.EarlyStopping(patience=5, restore_best_weights=True),\n                    tf.keras.callbacks.ReduceLROnPlateau(patience=3, factor=0.5),\n                    tf.keras.callbacks.ModelCheckpoint(\n                        filepath=os.path.join(MODEL_WEIGHT, \"best_model.h5\"),\n                        save_best_only=True,\n        \t\t\t\t\t\t\t\t\t\t\t\t\t \n                        save_weights_only=True\n                    )\n        ]\n        self.model.fit(dataset, epochs=epochs, callbacks=callbacks)\n\n    # Prédict\n    def predict_from_series(self, series_path, target_shape=None):\n        \"\"\"\n        Charge un volume DICOM complet, le prétraite et renvoie les prédictions du modèle.\n        \n        Args:\n            series_path (str): chemin du dossier contenant la série DICOM.\n            target_shape (tuple): taille (H, W) des slices pour le prétraitement.\n        \n        Returns:\n            dict: Prédictions pour 'class_output', 'label_output'\n        \"\"\"\n        if target_shape is None:\n            target_shape = (self.input_shape[0:3]) # (H, W) à partir de input_shape = (D,H,W,1)\n        # Chargement et prétraitement du volume\n        volume = self.load_and_process_dicom_series(series_path, target_shape)\n        \n        # Prédiction\n        preds = self.model.predict(volume)\n        \n        # Renvoyer sous forme lisible\n        return {\n            'class_output': preds[0][0],  # vecteur de probabilités\n            'label_output': float(preds[1][0]),  # probabilité\n        }\n    def decision_predict(self, series_path, target_shape=None):\n        \"\"\"\n        Appelle predict_from_series, puis ajoute une décision binaire\n        pour class_output et label_output.\n        \"\"\"\n        # Appel de la fonction d'origine\n        preds = self.predict_from_series(series_path, target_shape)\n    \n        # Copie pour ne pas écraser l’original\n        result = preds.copy()\n    \n        # Arrondis pour obtenir 0 ou 1\n        result['SeriesInstanceUID'] =  file_name = os.path.basename(series_path)\n        result['class_output_abs'] = np.round(result['class_output']).astype(int)\n        result['label_output_abs'] = int(np.round(result['label_output']))\n        return result","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-20T13:28:32.837023Z","iopub.execute_input":"2025-08-20T13:28:32.837311Z","iopub.status.idle":"2025-08-20T13:28:32.8703Z","shell.execute_reply.started":"2025-08-20T13:28:32.837287Z","shell.execute_reply":"2025-08-20T13:28:32.86919Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"resnet3d = CustomResNet3D(target_size=TARGET_SIZE, label_cols=LABEL_COLS)\nresnet3d.compile()\nresnet3d.load_latest_weights()\n\nmodel = resnet3d.get_model()\n\nif DEBUG:\n    resnet3d.summary()\n    \n    # Optionnel : visualisation graphique\n    from tensorflow.keras.utils import plot_model\n    plot_model(model, to_file='resnet3d.png', show_shapes=True, expand_nested=True)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-20T13:28:32.871211Z","iopub.execute_input":"2025-08-20T13:28:32.871636Z","iopub.status.idle":"2025-08-20T13:28:33.256657Z","shell.execute_reply.started":"2025-08-20T13:28:32.871609Z","shell.execute_reply":"2025-08-20T13:28:33.255806Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"if DEBUG:\n    import os\n    os.makedirs(MODEL_WEIGHT, exist_ok=True)\n    from datetime import datetime\n    timestamp = datetime.now().strftime(\"%Y%m%d-%H%M%S\")\n    resnet3d.get_model().save_weights(os.path.join(MODEL_WEIGHT,f\"weights_{timestamp}.weights.h5\"))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-20T13:28:33.257503Z","iopub.execute_input":"2025-08-20T13:28:33.257828Z","iopub.status.idle":"2025-08-20T13:28:33.262722Z","shell.execute_reply.started":"2025-08-20T13:28:33.257795Z","shell.execute_reply":"2025-08-20T13:28:33.261687Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Train","metadata":{}},{"cell_type":"code","source":"# Lecture des fichiers\ntrain_df = pd.read_csv(TRAIN_CSV)\n\n# Harmonisation du type de clé si besoin\ntrain_df[\"SeriesInstanceUID\"] = train_df[\"SeriesInstanceUID\"].astype(str)\n\n# Remplacer tous les NaN par 0\ntrain_df = train_df.fillna(0)\n\n# Suppression des colonnes inutiles\ncols_to_drop = [\"PatientSex\", \"PatientAge\"]\ntrain_df = train_df.drop(columns=cols_to_drop)\nif DEBUG:\n    print(train_df.columns)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-20T13:28:33.263688Z","iopub.execute_input":"2025-08-20T13:28:33.264024Z","iopub.status.idle":"2025-08-20T13:28:33.308005Z","shell.execute_reply.started":"2025-08-20T13:28:33.264Z","shell.execute_reply":"2025-08-20T13:28:33.306931Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def extract_labels_and_paths(df, label_cols, dicom_dir):\n    \"\"\"\n    Extrait labels_class, labels_binary et mes_paths\n    à partir d'un DataFrame fusionné.\n\n    Args:\n        df (pd.DataFrame): DataFrame contenant les colonnes labels, 'Aneurysm Present', et 'SeriesInstanceUID'.\n        label_cols (list): Liste complète LABEL_COLS (incluant 'Aneurysm Present' en dernier).\n        dicom_dir (str): Chemin racine contenant les fichiers DICOM.\n\n    Returns:\n        tuple: (labels_class, labels_binary, mes_paths)\n    \"\"\"\n    # --- 1) labels_class ---\n    labels_class = []\n    for _, row in df.iterrows():\n        sub_labels = row[label_cols[:-1]]\n        if sub_labels.max() == 1:\n            idx = sub_labels[sub_labels == 1].index[0]\n            class_index = label_cols[:-1].index(idx)\n        else:\n            class_index = -1  # ou autre valeur sentinelle\n        labels_class.append(class_index)\n\n    # --- 2) labels_binary ---\n    labels_binary = df['Aneurysm Present'].astype(int).tolist()\n    \n    # --- 3) mes_paths ---\n    mes_paths = [\n        os.path.join(dicom_dir, str(uid))\n        for uid in df[\"SeriesInstanceUID\"]\n    ]\n\n    return labels_class, labels_binary, mes_paths\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-20T13:28:33.308985Z","iopub.execute_input":"2025-08-20T13:28:33.309372Z","iopub.status.idle":"2025-08-20T13:28:33.316211Z","shell.execute_reply.started":"2025-08-20T13:28:33.30934Z","shell.execute_reply":"2025-08-20T13:28:33.315318Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"labels_class, labels_binary, mes_paths = extract_labels_and_paths(\n    train_df,\n    LABEL_COLS,\n    SERIES_ROOT_TRAIN\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-20T13:28:33.3171Z","iopub.execute_input":"2025-08-20T13:28:33.317376Z","iopub.status.idle":"2025-08-20T13:28:35.651865Z","shell.execute_reply.started":"2025-08-20T13:28:33.317356Z","shell.execute_reply":"2025-08-20T13:28:35.650712Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"if DEBUG:\n    print(labels_class[:5])\n    print(labels_binary[:5])\n    print(mes_paths[:5])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-20T13:28:35.654348Z","iopub.execute_input":"2025-08-20T13:28:35.654619Z","iopub.status.idle":"2025-08-20T13:28:35.659153Z","shell.execute_reply.started":"2025-08-20T13:28:35.654596Z","shell.execute_reply":"2025-08-20T13:28:35.658125Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"if DEBUG:\n    init = 0\n    test_size = 10\n    resnet3d.train_on_batch_samples(mes_paths[init:init+test_size], labels_class[init:init+test_size], \n                                    labels_binary[init:init+test_size], \n                                    batch_size=4, epochs=10, shuffle=True)\n    ","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-20T13:28:35.660283Z","iopub.execute_input":"2025-08-20T13:28:35.660565Z","iopub.status.idle":"2025-08-20T13:28:35.679842Z","shell.execute_reply.started":"2025-08-20T13:28:35.660536Z","shell.execute_reply":"2025-08-20T13:28:35.678723Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"if TRAIN:\n    resnet3d.train_on_batch_samples(mes_paths, labels_class, \n                                        labels_binary, \n                                        batch_size=64, epochs=20, shuffle=True)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-20T13:28:35.680749Z","iopub.execute_input":"2025-08-20T13:28:35.680999Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Predict","metadata":{}},{"cell_type":"code","source":"%%time\nimport os\nimport shutil\nfrom collections import defaultdict\n\nimport pandas as pd\nimport polars as pl\nimport pydicom\n\nimport kaggle_evaluation.rsna_inference_server","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"resnet3d = CustomResNet3D(target_size=TARGET_SIZE, label_cols=LABEL_COLS)\nresnet3d.compile()\nresnet3d.load_latest_weights()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def prediction_to_dataframe(pred_result, label_cols):\n    \"\"\"\n    Transforme un dictionnaire de prédiction en DataFrame d'une seule ligne.\n\n    Parameters\n    ----------\n    pred_result : dict\n        Doit contenir :\n        - \"SeriesInstanceUID\" (str)\n        - \"class_output_abs\" (liste ou array de numériques)\n        - \"label_output_abs\" (numérique)\n    label_cols : list[str]\n        Liste des noms de colonnes pour class_output_abs.\n\n    Returns\n    -------\n    pd.DataFrame\n        DataFrame avec SeriesInstanceUID + colonnes labels + Aneurysm Present.\n    \"\"\"\n    row_data = [pred_result[\"SeriesInstanceUID\"]] \\\n             + list(map(float, pred_result[\"class_output_abs\"])) \\\n             + [float(pred_result[\"label_output_abs\"])]\n    columns = [\"SeriesInstanceUID\"] + label_cols\n    return pd.DataFrame([row_data], columns=columns)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def predict(series_path):\n    \"\"\"Transforme la sortie de decision_predict en DataFrame formaté.\"\"\"\n    pred_result = resnet3d.decision_predict(series_path)\n    return prediction_to_dataframe(pred_result, LABEL_COLS)\n    \nif DEBUG:\n    pred = resnet3d.decision_predict(mes_paths[0])\n    print(pred)\n    df = prediction_to_dataframe(pred, LABEL_COLS)\n    print(predict(mes_paths[0]))","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import shutil\nimport os\n\n# Exemple : adapter vers ton répertoire réel\nshare_dir = \"/kaggle/working\"\n\n# Supprime tout le contenu du répertoire (⚠️ irréversible)\nfor item in os.listdir(share_dir):\n    item_path = os.path.join(share_dir, item)\n    if os.path.isdir(item_path):\n        shutil.rmtree(item_path)\n    else:\n        os.remove(item_path)","metadata":{"trusted":true},"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},"outputs":[],"execution_count":null}]}