{"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"name":"python","version":"3.12.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"}},"nbformat_minor":4,"nbformat":4,"cells":[{"id":"0a571493-6192-4012-807a-7cffcd0e3457","cell_type":"markdown","source":"# RSNA-MICCAI Brain Tumor Radiogenomic Classification\n### Version simplifiee et amelioree (inspiree du TP du Prof Amakdouf)\n\n**Objectif** : predire le statut de methylation du promoteur **MGMT** (0 = non methyle, 1 = methyle) a partir de l'IRM, sans biopsie.\n\n**Idee cle pour ameliorer le resultat** :\n\nLe TP de base entraine generalement un modele sur **une seule sequence IRM** (souvent FLAIR).on ameliore le signal en **fusionnant 3 sequences IRM directement dans les 3 canaux de l'image** (comme une image RGB) :\n\n- Canal Rouge = **FLAIR**\n- Canal Vert = **T1wCE**\n- Canal Bleu = **T2w**\n\nCette fusion multi-sequences donne generalement un meilleur AUC qu'une seule sequence, car chaque sequence met en evidence des caracteristiques differentes de la tumeur, sans complexifier le code (un seul reseau, un seul entrainement).\n\n**Metrique** : AUC (Area Under the ROC Curve)\n","metadata":{}},{"id":"c8bfee0b-9de4-4bcd-ac7f-f6450a9253b2","cell_type":"markdown","source":"## 1. Imports et configuration","metadata":{}},{"id":"bcaba75a-bf94-4bf7-b890-c90cac62baa6","cell_type":"code","source":"import os, re, glob, random, warnings\nwarnings.filterwarnings(\"ignore\")\n\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nimport cv2\nimport pydicom\nimport torch\nimport torch.nn as nn\nfrom torch.utils.data import Dataset, DataLoader\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.metrics import roc_auc_score\nimport timm\nfrom tqdm.notebook import tqdm\n\nrandom.seed(42); np.random.seed(42); torch.manual_seed(42)\nDEVICE = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nprint(\"Device :\", DEVICE)\n\n# ---- Configuration (simple, tout au meme endroit) ----\nDATA_DIR   = \"/kaggle/input/competitions/rsna-miccai-brain-tumor-radiogenomic-classification\"\nIMG_SIZE   = 224\nN_SLICES   = 5 \nEPOCHS     = 10\nBATCH_SIZE = 8\nLR         = 1e-4\nEXCLUDED_IDS = [109, 123, 709]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-10T22:42:12.034512Z","iopub.execute_input":"2026-07-10T22:42:12.035196Z","iopub.status.idle":"2026-07-10T22:42:12.043968Z","shell.execute_reply.started":"2026-07-10T22:42:12.035164Z","shell.execute_reply":"2026-07-10T22:42:12.043279Z"}},"outputs":[],"execution_count":null},{"id":"7dd671b1-3d00-4e51-b074-26e183bab250","cell_type":"markdown","source":"## 2. Chargement des labels","metadata":{}},{"id":"691d1947-4c97-4199-b965-62c5d217c7b7","cell_type":"code","source":"df = pd.read_csv(os.path.join(DATA_DIR, \"train_labels.csv\"))\ndf = df[~df[\"BraTS21ID\"].isin(EXCLUDED_IDS)].reset_index(drop=True)\nprint(df.shape)\ndf[\"MGMT_value\"].value_counts()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-10T22:42:18.215294Z","iopub.execute_input":"2026-07-10T22:42:18.215733Z","iopub.status.idle":"2026-07-10T22:42:18.244849Z","shell.execute_reply.started":"2026-07-10T22:42:18.215703Z","shell.execute_reply":"2026-07-10T22:42:18.244163Z"}},"outputs":[],"execution_count":null},{"id":"64889eff-4e2e-46fe-8297-09a42aaf7736","cell_type":"markdown","source":"## 3. Fonctions de chargement des images DICOM\n\nPour chaque patient, on prend les coupes centrales (celles qui contiennent le plus souvent la tumeur) de chaque sequence, et on les moyenne pour obtenir **une seule image 2D par sequence**.","metadata":{}},{"id":"a1568672-f69e-4013-b986-3b37f1a8f9f7","cell_type":"code","source":"def load_sequence_image(patient_id, mri_type, split=\"train\", n_slices=N_SLICES):\n    root = os.path.join(DATA_DIR, split, str(patient_id).zfill(5), mri_type)\n    paths = sorted(\n        glob.glob(os.path.join(root, \"*.dcm\")),\n        key=lambda p: int(re.search(r\"(\\d+)\\.dcm$\", p).group(1))\n    )\n\n    if len(paths) == 0:\n        return np.zeros((IMG_SIZE, IMG_SIZE), dtype=np.uint8)\n\n    mid = len(paths) // 2\n    half = n_slices // 2\n    selected = paths[max(0, mid - half): mid + half + 1]\n\n    slices = []\n    for p in selected:\n        img = pydicom.dcmread(p).pixel_array.astype(np.float32)\n        if img.max() > img.min():\n            img = (img - img.min()) / (img.max() - img.min())\n        img = cv2.resize(img, (IMG_SIZE, IMG_SIZE))\n        slices.append(img)\n\n    return (np.mean(slices, axis=0) * 255).astype(np.uint8)\n\n\ndef load_patient_rgb(patient_id, split=\"train\"):\n    flair  = load_sequence_image(patient_id, \"FLAIR\",  split)\n    t1wce  = load_sequence_image(patient_id, \"T1wCE\", split)\n    t2w    = load_sequence_image(patient_id, \"T2w\",   split)\n    return np.stack([flair, t1wce, t2w], axis=-1)  # (H, W, 3)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-10T22:43:28.463758Z","iopub.execute_input":"2026-07-10T22:43:28.464649Z","iopub.status.idle":"2026-07-10T22:43:28.473111Z","shell.execute_reply.started":"2026-07-10T22:43:28.464619Z","shell.execute_reply":"2026-07-10T22:43:28.471883Z"}},"outputs":[],"execution_count":null},{"id":"d76264e1-b75d-4580-9a6f-c9cba04aa1d9","cell_type":"code","source":"sample_id = df[\"BraTS21ID\"].iloc[0]\nrgb = load_patient_rgb(sample_id)\n\nplt.figure(figsize=(4, 4))\nplt.imshow(rgb)\nplt.title(f\"Patient {sample_id} - fusion FLAIR(R) / T1wCE(G) / T2w(B)\")\nplt.axis(\"off\")\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-10T22:43:31.958586Z","iopub.execute_input":"2026-07-10T22:43:31.959144Z","iopub.status.idle":"2026-07-10T22:43:32.351736Z","shell.execute_reply.started":"2026-07-10T22:43:31.959113Z","shell.execute_reply":"2026-07-10T22:43:32.351016Z"}},"outputs":[],"execution_count":null},{"id":"cb20e2c5-e5b2-4e0d-8910-5b9cc9fcebb5","cell_type":"markdown","source":"## 4. Dataset PyTorch (simple)","metadata":{}},{"id":"4f22c574-8735-4450-a181-e1e8bc03cdba","cell_type":"code","source":"class MRIDataset(Dataset):\n    def __init__(self, dataframe, split=\"train\", is_train=True):\n        self.df = dataframe.reset_index(drop=True)\n        self.split = split\n        self.is_train = is_train\n\n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, idx):\n        row = self.df.iloc[idx]\n        img = load_patient_rgb(row[\"BraTS21ID\"], self.split).astype(np.float32) / 255.0\n        img = torch.tensor(img).permute(2, 0, 1)  # (3, H, W)\n\n        if self.is_train:\n            label = torch.tensor(row[\"MGMT_value\"], dtype=torch.float32)\n            return img, label\n        return img, row[\"BraTS21ID\"]\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-10T22:43:44.524331Z","iopub.execute_input":"2026-07-10T22:43:44.524756Z","iopub.status.idle":"2026-07-10T22:43:44.531345Z","shell.execute_reply.started":"2026-07-10T22:43:44.524726Z","shell.execute_reply":"2026-07-10T22:43:44.530480Z"}},"outputs":[],"execution_count":null},{"id":"58d72af7-dcd5-4a48-8d91-adb52c0195a7","cell_type":"markdown","source":"## 5. Modele\nutulise **EfficientNet-B0**","metadata":{}},{"id":"9f47a664-d2d7-4d29-865f-0f673558300b","cell_type":"code","source":"def build_model():\n    model = timm.create_model(\"efficientnet_b0\", pretrained=True, num_classes=1)\n    return model.to(DEVICE)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-10T22:43:51.190431Z","iopub.execute_input":"2026-07-10T22:43:51.190826Z","iopub.status.idle":"2026-07-10T22:43:51.195712Z","shell.execute_reply.started":"2026-07-10T22:43:51.190799Z","shell.execute_reply":"2026-07-10T22:43:51.194859Z"}},"outputs":[],"execution_count":null},{"id":"44b95172-0ec2-4505-9eac-2ea8b838675b","cell_type":"markdown","source":"## 6. Entrainement (split simple train/validation)","metadata":{}},{"id":"27242ab6-5fc2-44ca-98ed-985c39af9c7b","cell_type":"code","source":"train_df, val_df = train_test_split(\n    df, test_size=0.2, stratify=df[\"MGMT_value\"], random_state=42\n)\n\ntrain_loader = DataLoader(MRIDataset(train_df), batch_size=BATCH_SIZE, shuffle=True,  num_workers=2)\nval_loader   = DataLoader(MRIDataset(val_df),   batch_size=BATCH_SIZE, shuffle=False, num_workers=2)\n\nmodel = build_model()\noptimizer = torch.optim.Adam(model.parameters(), lr=LR)\ncriterion = nn.BCEWithLogitsLoss()\n\nbest_auc = 0\n\nfor epoch in range(EPOCHS):\n    # -- entrainement --\n    model.train()\n    train_losses = []\n    for imgs, labels in tqdm(train_loader, leave=False):\n        imgs, labels = imgs.to(DEVICE), labels.to(DEVICE)\n        optimizer.zero_grad()\n        outputs = model(imgs).squeeze(1)\n        loss = criterion(outputs, labels)\n        loss.backward()\n        optimizer.step()\n        train_losses.append(loss.item())\n\n    # -- validation --\n    model.eval()\n    val_preds, val_labels = [], []\n    with torch.no_grad():\n        for imgs, labels in val_loader:\n            imgs = imgs.to(DEVICE)\n            outputs = torch.sigmoid(model(imgs).squeeze(1))\n            val_preds.extend(outputs.cpu().numpy())\n            val_labels.extend(labels.numpy())\n\n    val_auc = roc_auc_score(val_labels, val_preds)\n    print(f\"Epoch {epoch+1}/{EPOCHS} - train_loss: {np.mean(train_losses):.4f} - val_auc: {val_auc:.4f}\")\n\n    if val_auc > best_auc:\n        best_auc = val_auc\n        torch.save(model.state_dict(), \"best_model.pth\")\n\nprint(\"Meilleur AUC validation :\", best_auc)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-10T22:44:06.233319Z","iopub.execute_input":"2026-07-10T22:44:06.233923Z","iopub.status.idle":"2026-07-10T22:46:50.133569Z","shell.execute_reply.started":"2026-07-10T22:44:06.233893Z","shell.execute_reply":"2026-07-10T22:46:50.132638Z"}},"outputs":[],"execution_count":null},{"id":"00f67216-d117-4242-b174-98c042421e29","cell_type":"markdown","source":"## 6-1. Evaluation detaillee : matrice de confusion, courbe ROC, AUC\n\nOn recharge le meilleur modele sauvegarde (`best_model.pth`) et on l'evalue sur le jeu de validation avec :\n- la **courbe ROC** et l'**AUC**\n- la **matrice de confusion** (seuil 0.5)\n- le **rapport de classification** (precision, rappel, F1)\n","metadata":{}},{"id":"b84796f1-7f28-4e00-9812-8e502b6bffc0","cell_type":"code","source":"from sklearn.metrics import roc_curve, confusion_matrix, classification_report, ConfusionMatrixDisplay\n\n# On recharge le meilleur modele (celui qui a eu le meilleur AUC pendant l'entrainement)\nmodel.load_state_dict(torch.load(\"best_model.pth\"))\nmodel.eval()\n\nval_preds, val_labels = [], []\nwith torch.no_grad():\n    for imgs, labels in val_loader:\n        imgs = imgs.to(DEVICE)\n        outputs = torch.sigmoid(model(imgs).squeeze(1))\n        val_preds.extend(outputs.cpu().numpy())\n        val_labels.extend(labels.numpy())\n\nval_preds = np.array(val_preds)\nval_labels = np.array(val_labels)\n\nfinal_auc = roc_auc_score(val_labels, val_preds)\nprint(f\"AUC (validation) : {final_auc:.4f}\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-10T22:46:58.823997Z","iopub.execute_input":"2026-07-10T22:46:58.824498Z","iopub.status.idle":"2026-07-10T22:47:01.709418Z","shell.execute_reply.started":"2026-07-10T22:46:58.824460Z","shell.execute_reply":"2026-07-10T22:47:01.708274Z"}},"outputs":[],"execution_count":null},{"id":"331dd408-5372-4c86-91a2-3dc2f5f50b05","cell_type":"code","source":"# ---- Courbe ROC ----\nfpr, tpr, thresholds = roc_curve(val_labels, val_preds)\n\nplt.figure(figsize=(5, 5))\nplt.plot(fpr, tpr, label=f\"AUC = {final_auc:.4f}\")\nplt.plot([0, 1], [0, 1], linestyle=\"--\", color=\"gray\", label=\"Modele aleatoire\")\nplt.xlabel(\"Taux de faux positifs\")\nplt.ylabel(\"Taux de vrais positifs\")\nplt.title(\"Courbe ROC - MGMT\")\nplt.legend()\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-10T22:47:07.871346Z","iopub.execute_input":"2026-07-10T22:47:07.872152Z","iopub.status.idle":"2026-07-10T22:47:08.026219Z","shell.execute_reply.started":"2026-07-10T22:47:07.872114Z","shell.execute_reply":"2026-07-10T22:47:08.025532Z"}},"outputs":[],"execution_count":null},{"id":"a5489f1a-8ed0-4c14-967e-2c4e43bf0b44","cell_type":"code","source":"# ---- Matrice de confusion (seuil = 0.5) ----\nval_pred_labels = (val_preds >= 0.5).astype(int)\n\ncm = confusion_matrix(val_labels, val_pred_labels)\n\ndisp = ConfusionMatrixDisplay(confusion_matrix=cm, display_labels=[\"Non methyle (0)\", \"Methyle (1)\"])\ndisp.plot(cmap=\"Blues\", values_format=\"d\")\nplt.title(\"Matrice de confusion\")\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-10T22:47:26.463372Z","iopub.execute_input":"2026-07-10T22:47:26.464093Z","iopub.status.idle":"2026-07-10T22:47:26.621603Z","shell.execute_reply.started":"2026-07-10T22:47:26.464054Z","shell.execute_reply":"2026-07-10T22:47:26.620587Z"}},"outputs":[],"execution_count":null},{"id":"4923ab67-939f-4582-9f0f-3d4cec2c855d","cell_type":"code","source":"print(classification_report(val_labels, val_pred_labels, target_names=[\"Non methyle (0)\", \"Methyle (1)\"]))\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-10T22:47:42.454232Z","iopub.execute_input":"2026-07-10T22:47:42.454669Z","iopub.status.idle":"2026-07-10T22:47:42.472730Z","shell.execute_reply.started":"2026-07-10T22:47:42.454638Z","shell.execute_reply":"2026-07-10T22:47:42.472022Z"}},"outputs":[],"execution_count":null},{"id":"5e92c68d-21f3-4250-b7a6-2e0594bc6372","cell_type":"markdown","source":"## 6-2. Courbes d'entrainement (loss et AUC par epoque)\n\nPour visualiser directement le surapprentissage, il faut garder l'historique des metriques a chaque epoque. **Remplacez la boucle d'entrainement de la section 6** par la version ci-dessous (identique, mais elle enregistre l'historique dans des listes).","metadata":{}},{"id":"c587fca3-5582-45f8-9528-5e3c35a623e8","cell_type":"code","source":"train_losses_hist, val_auc_hist = [], []\n\nmodel = build_model()\noptimizer = torch.optim.Adam(model.parameters(), lr=LR)\ncriterion = nn.BCEWithLogitsLoss()\nbest_auc = 0\n\nfor epoch in range(EPOCHS):\n    model.train()\n    train_losses = []\n    for imgs, labels in tqdm(train_loader, leave=False):\n        imgs, labels = imgs.to(DEVICE), labels.to(DEVICE)\n        optimizer.zero_grad()\n        outputs = model(imgs).squeeze(1)\n        loss = criterion(outputs, labels)\n        loss.backward()\n        optimizer.step()\n        train_losses.append(loss.item())\n\n    model.eval()\n    val_preds, val_labels = [], []\n    with torch.no_grad():\n        for imgs, labels in val_loader:\n            imgs = imgs.to(DEVICE)\n            outputs = torch.sigmoid(model(imgs).squeeze(1))\n            val_preds.extend(outputs.cpu().numpy())\n            val_labels.extend(labels.numpy())\n\n    val_auc = roc_auc_score(val_labels, val_preds)\n    train_losses_hist.append(np.mean(train_losses))\n    val_auc_hist.append(val_auc)\n\n    print(f\"Epoch {epoch+1}/{EPOCHS} - train_loss: {np.mean(train_losses):.4f} - val_auc: {val_auc:.4f}\")\n\n    if val_auc > best_auc:\n        best_auc = val_auc\n        torch.save(model.state_dict(), \"best_model.pth\")\n\nprint(\"Meilleur AUC validation :\", best_auc)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-10T22:47:51.463365Z","iopub.execute_input":"2026-07-10T22:47:51.463822Z","iopub.status.idle":"2026-07-10T22:50:05.457873Z","shell.execute_reply.started":"2026-07-10T22:47:51.463794Z","shell.execute_reply":"2026-07-10T22:50:05.456714Z"}},"outputs":[],"execution_count":null},{"id":"8b566da6-9b9d-4d47-89b8-33af446aeb48","cell_type":"code","source":"fig, axes = plt.subplots(1, 2, figsize=(12, 4))\n\naxes[0].plot(range(1, EPOCHS + 1), train_losses_hist, marker=\"o\")\naxes[0].set_title(\"Train loss par epoque\")\naxes[0].set_xlabel(\"Epoque\")\naxes[0].set_ylabel(\"Loss\")\n\naxes[1].plot(range(1, EPOCHS + 1), val_auc_hist, marker=\"o\", color=\"orange\")\naxes[1].axhline(0.5, linestyle=\"--\", color=\"gray\", label=\"AUC aleatoire (0.5)\")\naxes[1].set_title(\"Val AUC par epoque\")\naxes[1].set_xlabel(\"Epoque\")\naxes[1].set_ylabel(\"AUC\")\naxes[1].legend()\n\nplt.tight_layout()\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-10T22:50:14.588181Z","iopub.execute_input":"2026-07-10T22:50:14.589098Z","iopub.status.idle":"2026-07-10T22:50:14.896755Z","shell.execute_reply.started":"2026-07-10T22:50:14.589058Z","shell.execute_reply":"2026-07-10T22:50:14.896072Z"}},"outputs":[],"execution_count":null},{"id":"57d6f965-8d36-4477-b489-1505f84bff72","cell_type":"markdown","source":"#### j'ai essayer avec une pipeline plus développé mais entrainement echeck a cause de kagle gpu : https://www.kaggle.com/code/elmahdimajdi/notebook8bd7ec9bd8","metadata":{}}]}