{"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":"gpu","dataSources":[{"sourceId":99552,"databundleVersionId":13851420,"sourceType":"competition"},{"sourceId":13077470,"sourceType":"datasetVersion","datasetId":8282474}],"dockerImageVersionId":31193,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"!pip install --no-deps /kaggle/input/monai-pkg/monai_pkg/monai-1.4.0-py3-none-any.whl","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-21T11:11:23.880859Z","iopub.execute_input":"2025-11-21T11:11:23.881125Z","iopub.status.idle":"2025-11-21T11:11:27.148137Z","shell.execute_reply.started":"2025-11-21T11:11:23.881104Z","shell.execute_reply":"2025-11-21T11:11:27.147455Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"pip install SimpleITK","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-21T11:11:27.149792Z","iopub.execute_input":"2025-11-21T11:11:27.150076Z","iopub.status.idle":"2025-11-21T11:11:30.205535Z","shell.execute_reply.started":"2025-11-21T11:11:27.150052Z","shell.execute_reply":"2025-11-21T11:11:30.204579Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os, glob\nimport pandas as pd\nimport numpy as np\nimport matplotlib.pyplot as plt\nimport pandas as pd         \nimport shutil\n\nimport cv2\nfrom PIL import Image\nimport pydicom\nfrom pydicom.pixels import apply_voi_lut\n\nimport SimpleITK as sitk\n\nimport monai\nfrom monai.transforms import (\n    Compose, LoadImaged, EnsureChannelFirstd, ScaleIntensityd,\n    Spacingd, Orientationd, ResizeD, ToTensord\n)\nfrom monai.data import Dataset, DataLoader\nfrom monai.transforms.transform import Transform\n\n\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nfrom torch.utils.tensorboard import SummaryWriter\n\nfrom monai.networks.nets import DenseNet121\nfrom sklearn.metrics import roc_auc_score, accuracy_score\n\nfrom tqdm import tqdm","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-21T11:11:30.206623Z","iopub.execute_input":"2025-11-21T11:11:30.206886Z","iopub.status.idle":"2025-11-21T11:12:11.246148Z","shell.execute_reply.started":"2025-11-21T11:11:30.20686Z","shell.execute_reply":"2025-11-21T11:12:11.24558Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 01. Save the localizers for 0 and 1 \nlocalizers for 1 are already given <br> \nFor 0, get the middle slice i.e. Localizer of 0 = no. of slises / 2","metadata":{}},{"cell_type":"code","source":"base_path = \"/kaggle/input/rsna-intracranial-aneurysm-detection/\"\nsave_dir = \"/kaggle/working/\"","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-21T11:19:22.637199Z","iopub.execute_input":"2025-11-21T11:19:22.637941Z","iopub.status.idle":"2025-11-21T11:19:22.641933Z","shell.execute_reply.started":"2025-11-21T11:19:22.637905Z","shell.execute_reply":"2025-11-21T11:19:22.641108Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Load the main training data containing SeriesInstanceUID and aneurysm labels\ndf = pd.read_csv(base_path + \"train.csv\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-21T11:12:11.251923Z","iopub.execute_input":"2025-11-21T11:12:11.252167Z","iopub.status.idle":"2025-11-21T11:12:11.300784Z","shell.execute_reply.started":"2025-11-21T11:12:11.252144Z","shell.execute_reply":"2025-11-21T11:12:11.300225Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Load the localizers file, mapping SeriesInstanceUID and SOPInstanceUID\nloc = pd.read_csv(base_path + \"train_localizers.csv\")\nloc.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-21T11:12:11.30143Z","iopub.execute_input":"2025-11-21T11:12:11.301802Z","iopub.status.idle":"2025-11-21T11:12:11.337229Z","shell.execute_reply.started":"2025-11-21T11:12:11.301777Z","shell.execute_reply":"2025-11-21T11:12:11.336721Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# count number of subjects per modality\nmodality_counts = df[\"Modality\"].value_counts()\n\nprint(\"Cases per modality:\")\nprint(modality_counts)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-21T11:12:11.338909Z","iopub.execute_input":"2025-11-21T11:12:11.339131Z","iopub.status.idle":"2025-11-21T11:12:11.346852Z","shell.execute_reply.started":"2025-11-21T11:12:11.339115Z","shell.execute_reply":"2025-11-21T11:12:11.346082Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Select only CTA scans with SeriesInstanceUID and aneurysm labels\ndf_cta = df[df[\"Modality\"] == \"CTA\"][[\"SeriesInstanceUID\", \"Aneurysm Present\"]]\n\n# Merge CTA list with localizer SOPInstanceUIDs\ndf_cta = df_cta.merge(loc, on=\"SeriesInstanceUID\", how=\"left\")\n\n# Keeping only SeriesInstanceUID, SOPInstanceUID, and aneurysm labels\ndf_cta = df_cta[[\"SeriesInstanceUID\", \"SOPInstanceUID\", \"Aneurysm Present\"]]\n\n# Save\ndf_cta.to_csv(\"CTA_loc.csv\", index=False)\ndf_cta.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-21T11:12:11.347706Z","iopub.execute_input":"2025-11-21T11:12:11.3483Z","iopub.status.idle":"2025-11-21T11:12:11.377143Z","shell.execute_reply.started":"2025-11-21T11:12:11.348274Z","shell.execute_reply":"2025-11-21T11:12:11.376504Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Function to extract the SOPInstanceUID of the middle slice\ndef get_middle_sop(series_uid):\n    print(series_uid)\n    series_path = os.path.join(base_path, \"series\", series_uid)\n    \n    # List all dicom files\n    dcm_files = [os.path.join(series_path, f) \n                 for f in os.listdir(series_path) if f.endswith(\".dcm\")]\n\n    # Sort by InstanceNumber\n    dcm_files_sorted = sorted(dcm_files, key=lambda x: int(pydicom.dcmread(x).InstanceNumber))\n\n    # Pick the middle slice\n    mid_idx = len(dcm_files_sorted) // 2\n    mid_dcm = pydicom.dcmread(dcm_files_sorted[mid_idx])\n\n    return mid_dcm.SOPInstanceUID","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-21T11:12:11.377744Z","iopub.execute_input":"2025-11-21T11:12:11.377939Z","iopub.status.idle":"2025-11-21T11:12:11.387919Z","shell.execute_reply.started":"2025-11-21T11:12:11.377925Z","shell.execute_reply":"2025-11-21T11:12:11.387207Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# For CTA cases without localizer SOPInstanceUID AND labeled as 0, assign the SOPInstanceUID of the middle slice\nfor idx, row in df_cta.iterrows():\n    if pd.isna(row[\"SOPInstanceUID\"]) and row[\"Aneurysm Present\"] == 0:\n        sop_uid = get_middle_sop(row[\"SeriesInstanceUID\"])\n        df_cta.at[idx, \"SOPInstanceUID\"] = sop_uid","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-21T11:12:11.388653Z","iopub.execute_input":"2025-11-21T11:12:11.388887Z","iopub.status.idle":"2025-11-21T11:12:32.300418Z","shell.execute_reply.started":"2025-11-21T11:12:11.388868Z","shell.execute_reply":"2025-11-21T11:12:32.299874Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Save the CTA slices SeriesInstanceUID, SOPInstanceUID (for both 0 and 1) and Aneurysm Present \ndf_cta.to_csv(\"CTA_loc_0_1.csv\", index=False)\n\ndf_cta","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-21T11:12:32.301177Z","iopub.execute_input":"2025-11-21T11:12:32.301432Z","iopub.status.idle":"2025-11-21T11:12:32.310446Z","shell.execute_reply.started":"2025-11-21T11:12:32.301409Z","shell.execute_reply":"2025-11-21T11:12:32.309902Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 02. Save the preprocessed files \nFirst, select the 5 files (SOPInstanceUID ± 2) for each patient <br>\nSuperimpose the 5 slices giving 1 .png image per subject","metadata":{}},{"cell_type":"code","source":"# Load the localizers\n\n# loc = pd.read_csv(\"/kaggle/input/cta-localizers-0-1/CTA_loc_0_1.csv\")\nloc = pd.read_csv(\"/kaggle/working/CTA_loc_0_1.csv\")\nloc.head(5)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-21T11:12:32.311351Z","iopub.execute_input":"2025-11-21T11:12:32.312174Z","iopub.status.idle":"2025-11-21T11:12:32.343249Z","shell.execute_reply.started":"2025-11-21T11:12:32.312155Z","shell.execute_reply":"2025-11-21T11:12:32.342711Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Function to select the 5 Slices (SOPInstanceUID ± 2)\ndef get_slices(series_uid, base_path, neighbors=2):\n\n    # get the SOPinstanceUID for the series_uid\n    get_idx = loc.index[loc[\"SeriesInstanceUID\"] == series_uid].tolist()\n    idx = get_idx[0]\n    sop_uid = loc.loc[idx, \"SOPInstanceUID\"]\n    # print(\"SeriesUID: \", series_uid)\n    # print(\"Target SOP: \", sop_uid)\n\n    # define series path to acess .dcm files\n    series_path = os.path.join(base_path, \"series\", series_uid)\n\n    # List all DICOM files in that series\n    dcm_files = [os.path.join(series_path, f) for f in os.listdir(series_path) if f.endswith(\".dcm\")]\n    # print(\"No. of .dcm files in series: \", len(dcm_files))\n\n    # Sort them by InstanceNumber (better than filename order)\n    dcm_files_sorted = sorted(dcm_files, key=lambda x: int(pydicom.dcmread(x).InstanceNumber))\n\n    # Find index of the target SOPInstanceUID\n    target_file = os.path.join(series_path, sop_uid + \".dcm\")\n    target_idx = dcm_files_sorted.index(target_file)\n    # print(\"SOP slice index: \",target_idx)\n\n    # Select 2 before, target, 2 after\n    slice_indices = list(range(max(target_idx - neighbors, 0), min(target_idx + neighbors + 1, len(dcm_files_sorted))))\n    selected_files = [dcm_files_sorted[i] for i in slice_indices]\n    # print(\"Selected SOP slice index\", slice_indices)\n\n    # print(\"Selected slices:\")\n    \n    return selected_files","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-21T11:12:32.344066Z","iopub.execute_input":"2025-11-21T11:12:32.34431Z","iopub.status.idle":"2025-11-21T11:12:32.350237Z","shell.execute_reply.started":"2025-11-21T11:12:32.344288Z","shell.execute_reply":"2025-11-21T11:12:32.349697Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"For each file: <br>\n1.Get pixel for each file <br> \n2.Apply default windowing <br>\n3.Filter pixels corresponding to HU value range <br>\n\nSuperimpose all processed files and normalise the value to greyscale","metadata":{}},{"cell_type":"code","source":"# Function to get the Superimposed .png file for each subject \n\n# Lower and upper limit for HU values to retain the corresponding pixels\nllimit = 100\nulimit = 350\n\ndef combine_slices(selected_files, save_path):\n    tls = []\n    for pth in selected_files:\n        dcm_i = pydicom.dcmread(pth)\n        data = dcm_i.pixel_array\n    \n        # Safe slope and intercept\n        slope = getattr(dcm_i, \"RescaleSlope\", 1)\n        intercept = getattr(dcm_i, \"RescaleIntercept\", 0)\n        hu_arr_i = dcm_i.pixel_array * slope + intercept\n    \n        # Apply default windowing if present\n        if \"WindowWidth\" in dcm_i and \"WindowCenter\" in dcm_i:\n            # print(\"Dataset has windowing\")\n            windowed = apply_voi_lut(dcm_i.pixel_array, dcm_i)\n            data = ((windowed - np.min(windowed)) / (np.max(windowed) - np.min(windowed)) * 255).astype(np.uint8)\n        else:\n            pixel_array_numpy = dcm_i.pixel_array\n            data = ((pixel_array_numpy - np.min(pixel_array_numpy)) / (np.max(pixel_array_numpy) - np.min(pixel_array_numpy)) * 255).astype(np.uint8)\n    \n        # HU masking\n        mask_lower = hu_arr_i >= llimit\n        mask_upper = hu_arr_i <= ulimit\n        combined_mask = mask_lower & mask_upper\n        data = np.where(combined_mask, data, 0)\n    \n        # Thresholding\n        _, thresh = cv2.threshold(data, 0, 255, cv2.THRESH_BINARY)\n        tls.append(data)\n\n    # Combine slices\n    combImg = sum(tls)\n    combImg = ((combImg - np.min(combImg)) / (np.max(combImg) - np.min(combImg)) * 255).astype(np.uint8)\n    \n    # Save & plot\n    img = Image.fromarray(combImg, \"L\")\n    img.save(save_path)\n    \n    # plt.imshow(combImg, cmap=\"gray\")\n    # plt.axis(\"off\")\n    # plt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-21T11:12:32.350963Z","iopub.execute_input":"2025-11-21T11:12:32.351208Z","iopub.status.idle":"2025-11-21T11:12:32.368167Z","shell.execute_reply.started":"2025-11-21T11:12:32.35119Z","shell.execute_reply":"2025-11-21T11:12:32.367451Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Function to execute get_slices and combine_slices, and saving the image path of .png and respective labels\ndef build_monai_dataset(loc_df, base_path, out_dir=\"/kaggle/working/outputs/\", neighbors=2, llimit=100, ulimit=350):\n\n    os.makedirs(out_dir, exist_ok=True)\n    dataset = []\n\n    for idx, row in loc.iterrows():\n        series_uid = row[\"SeriesInstanceUID\"]\n        label = int(row[\"Aneurysm Present\"])\n\n        try:\n            # Get slices around SOPInstanceUID\n            selected_files = get_slices(\n                series_uid, base_path, neighbors\n            )\n\n            # Save combined image\n            save_path = os.path.join(out_dir, f\"{series_uid}_combined.png\")\n            comb_img = combine_slices(\n                selected_files, save_path)\n\n\n            # Append dict\n            dataset.append({\"img\": save_path, \"label\": label})\n\n            print(f\"{series_uid} → Label {label}, saved at {save_path}\")\n\n        except Exception as e:\n            print(f\"Failed for {series_uid}: {e}\")\n\n    return dataset","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-21T11:12:32.368936Z","iopub.execute_input":"2025-11-21T11:12:32.369172Z","iopub.status.idle":"2025-11-21T11:12:32.386232Z","shell.execute_reply.started":"2025-11-21T11:12:32.369149Z","shell.execute_reply":"2025-11-21T11:12:32.385489Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"dataset = build_monai_dataset(loc, base_path=\"/kaggle/input/rsna-intracranial-aneurysm-detection\")\n\nprint(dataset[:3])  # check first 3","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-21T11:13:12.437964Z","iopub.execute_input":"2025-11-21T11:13:12.438731Z","iopub.status.idle":"2025-11-21T11:13:58.938317Z","shell.execute_reply.started":"2025-11-21T11:13:12.438702Z","shell.execute_reply":"2025-11-21T11:13:58.937597Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 03. Classification ","metadata":{}},{"cell_type":"code","source":"# Load the localizers\n\n# loc = pd.read_csv(\"/kaggle/input/cta-localizers-0-1/CTA_loc_0_1.csv\")\nloc = pd.read_csv(\"/kaggle/working/CTA_loc_0_1.csv\")\nloc.head(5)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-21T11:14:23.15706Z","iopub.execute_input":"2025-11-21T11:14:23.157344Z","iopub.status.idle":"2025-11-21T11:14:23.167556Z","shell.execute_reply.started":"2025-11-21T11:14:23.157324Z","shell.execute_reply":"2025-11-21T11:14:23.166914Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Define superimposed .png paths\n# img_path = \"/kaggle/input/cta-preprocessed/outputs\"\nimg_path = \"/kaggle/working/outputs\"","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-21T11:15:11.598217Z","iopub.execute_input":"2025-11-21T11:15:11.598831Z","iopub.status.idle":"2025-11-21T11:15:11.60217Z","shell.execute_reply.started":"2025-11-21T11:15:11.598803Z","shell.execute_reply":"2025-11-21T11:15:11.601456Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Create Data dictionary\ndata_dicts = []\nfor sid, label in zip(loc[\"SeriesInstanceUID\"], loc[\"Aneurysm Present\"]):\n    png_file = os.path.join(img_path, f\"{sid}_combined.png\")\n    if os.path.exists(png_file):\n        data_dicts.append({\n            \"image\": png_file,\n            \"label\": int(label)\n        })\n\nprint(f\"Created {len(data_dicts)} entries\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-21T11:15:15.392528Z","iopub.execute_input":"2025-11-21T11:15:15.393149Z","iopub.status.idle":"2025-11-21T11:15:15.398646Z","shell.execute_reply.started":"2025-11-21T11:15:15.393121Z","shell.execute_reply":"2025-11-21T11:15:15.3979Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Define Monai Transforms\ntrain_transforms = Compose([\n    LoadImaged(keys=[\"image\"]),\n    EnsureChannelFirstd(keys=[\"image\"]),\n    ScaleIntensityd(keys=[\"image\"]),\n    ResizeD(keys=[\"image\"], spatial_size=(224, 224)),\n    ToTensord(keys=[\"image\", \"label\"])\n])\n\nval_transforms = Compose([\n    LoadImaged(keys=[\"image\"]),\n    EnsureChannelFirstd(keys=[\"image\"]),\n    ScaleIntensityd(keys=[\"image\"]),\n    ResizeD(keys=[\"image\"], spatial_size=(224, 224)),\n    ToTensord(keys=[\"image\", \"label\"])\n])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-21T11:15:36.578692Z","iopub.execute_input":"2025-11-21T11:15:36.57896Z","iopub.status.idle":"2025-11-21T11:15:36.590306Z","shell.execute_reply.started":"2025-11-21T11:15:36.578939Z","shell.execute_reply":"2025-11-21T11:15:36.589655Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 70% train (first 35), 30% val (last 15)\n\nsplit_idx = int(0.7 * len(data_dicts))\ntrain_dicts = data_dicts[:split_idx]\nval_dicts   = data_dicts[split_idx:]\n\n# train_dicts = data_dicts[:35]\n# val_dicts   = data_dicts[35:]\n\n# Define datasets\ntrain_ds = Dataset(data=train_dicts, transform=train_transforms)\nval_ds   = Dataset(data=val_dicts,   transform=val_transforms)\n\n# Define dataloaders\ntrain_loader = DataLoader(train_ds, batch_size=16, shuffle=True, num_workers=0)\nval_loader   = DataLoader(val_ds,   batch_size=16, num_workers=0)\n\nprint(f\"Train size: {len(train_ds)}, Val size: {len(val_ds)}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-21T11:15:38.367502Z","iopub.execute_input":"2025-11-21T11:15:38.36826Z","iopub.status.idle":"2025-11-21T11:15:38.377783Z","shell.execute_reply.started":"2025-11-21T11:15:38.368236Z","shell.execute_reply":"2025-11-21T11:15:38.377119Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")  \n\n#define model\nmodel = DenseNet121(\n    spatial_dims=2,      # Input is 2D \n    in_channels=1,       # DICOMs are grayscale → 1 input channel\n    out_channels=1       # Binary classification (Aneurysm Present vs Not)\n).to(device)             # Move model to GPU/CPU\n\ncriterion = nn.BCEWithLogitsLoss()   # Binary classification loss (works with raw logits)\noptimizer = optim.Adam(model.parameters(), lr=1e-4)  # Adam optimizer","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-21T11:15:40.817263Z","iopub.execute_input":"2025-11-21T11:15:40.817877Z","iopub.status.idle":"2025-11-21T11:15:41.578526Z","shell.execute_reply.started":"2025-11-21T11:15:40.817853Z","shell.execute_reply":"2025-11-21T11:15:41.577943Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def train_one_epoch(model, loader):\n    model.train()\n    running_loss = 0.0\n    all_labels, all_preds = [], []\n\n    for batch in tqdm(loader, desc=\"Training\", leave=False):\n        images = batch[\"image\"].to(device)\n        labels = batch[\"label\"].to(device).float().unsqueeze(1)\n\n        optimizer.zero_grad()\n        outputs = model(images)\n        loss = criterion(outputs, labels)\n        loss.backward()\n        optimizer.step()\n\n        running_loss += loss.item()\n\n        # For metrics\n        probs = torch.sigmoid(outputs).detach().cpu().numpy()\n        all_preds.extend(probs)\n        all_labels.extend(labels.cpu().numpy())\n\n    all_labels = np.array(all_labels)\n    all_preds = np.array(all_preds)\n\n    # Metrics\n    auc = roc_auc_score(all_labels, all_preds)\n    acc = ((all_preds > 0.5).astype(int) == all_labels).mean()\n\n    return running_loss / len(loader), auc, acc","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-21T11:18:24.420568Z","iopub.execute_input":"2025-11-21T11:18:24.421236Z","iopub.status.idle":"2025-11-21T11:18:24.42711Z","shell.execute_reply.started":"2025-11-21T11:18:24.421212Z","shell.execute_reply":"2025-11-21T11:18:24.426235Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def validate(model, loader, save_outputs=False):\n    model.eval()\n    running_loss = 0.0\n    all_labels, all_preds = [], []\n\n    with torch.no_grad():\n        for batch in loader:\n            images = batch[\"image\"].to(device)\n            labels = batch[\"label\"].to(device).float().unsqueeze(1)\n\n            outputs = model(images)\n            loss = criterion(outputs, labels)\n            running_loss += loss.item()\n\n            probs = torch.sigmoid(outputs).cpu().numpy()\n\n            all_labels.extend(labels.cpu().numpy())\n            all_preds.extend(probs)\n\n    all_labels = np.array(all_labels)\n    all_preds = np.array(all_preds)\n\n    auc = roc_auc_score(all_labels, all_preds)\n    acc = ((all_preds > 0.5).astype(int) == all_labels).mean()\n\n    return running_loss / len(loader), auc, acc","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-21T11:18:34.207754Z","iopub.execute_input":"2025-11-21T11:18:34.208017Z","iopub.status.idle":"2025-11-21T11:18:34.214519Z","shell.execute_reply.started":"2025-11-21T11:18:34.207997Z","shell.execute_reply":"2025-11-21T11:18:34.213761Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"num_epochs = 5\nbest_auc = 0\n\nfor epoch in range(num_epochs):\n    print(f\"\\n===== Epoch {epoch+1}/{num_epochs} =====\")\n\n    train_loss, train_auc, train_acc = train_one_epoch(model, train_loader)\n    val_loss,   val_auc,   val_acc   = validate(model, val_loader)\n\n    print(\n        f\"Train -> Loss: {train_loss:.4f},  AUC: {train_auc:.4f},  Acc: {train_acc:.4f}\\n\"\n        f\"Val   -> Loss: {val_loss:.4f},    AUC: {val_auc:.4f},    Acc: {val_acc:.4f}\"\n    )\n\n    # Save latest model\n    torch.save(model.state_dict(), os.path.join(save_dir, f\"densenet_epoch{epoch+1}.pth\"))\n\n    # Save best based on validation AUC\n    if val_auc > best_auc:\n        best_auc = val_auc\n        torch.save(model.state_dict(), os.path.join(save_dir, \"best_densenet_aneurysm.pth\"))\n        print(f\"Best model updated at epoch {epoch+1} (AUC={val_auc:.4f})\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-21T11:19:28.624833Z","iopub.execute_input":"2025-11-21T11:19:28.625359Z","iopub.status.idle":"2025-11-21T11:19:30.600446Z","shell.execute_reply.started":"2025-11-21T11:19:28.625326Z","shell.execute_reply":"2025-11-21T11:19:30.599694Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def visualize_predictions(model, loader, num_images=5):\n    model.eval()\n    images_shown = 0\n    correct = 0\n    total = 0\n    \n    with torch.no_grad():\n        for batch in loader:\n            images = batch[\"image\"].to(device)   # shape: [B, C, H, W]\n            labels = batch[\"label\"].cpu().numpy()\n            outputs = model(images)\n            probs = torch.sigmoid(outputs).cpu().numpy()\n            preds = (probs > 0.5).astype(int)   # Threshold at 0.5\n            \n            # Accuracy calculation\n            correct += (preds.flatten() == labels.flatten()).sum()\n            total += len(labels)\n            \n            # Show only a few images\n            for i in range(len(images)):\n                if images_shown >= num_images:\n                    break\n                \n                plt.imshow(images[i, 0, :, :].cpu(), cmap=\"gray\")  \n                # ^ just 2D slice, no middle slice needed\n                \n                plt.title(f\"True: {labels[i]} | Pred: {preds[i][0]}\")\n                plt.axis(\"off\")\n                plt.show()\n                \n                images_shown += 1\n            \n            if images_shown >= num_images:\n                break\n    \n    accuracy = correct / total\n    print(f\"Validation Accuracy: {accuracy:.4f}\")\n\nvisualize_predictions(model, val_loader, num_images=15)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-21T11:19:42.812039Z","iopub.execute_input":"2025-11-21T11:19:42.812688Z","iopub.status.idle":"2025-11-21T11:19:43.41881Z","shell.execute_reply.started":"2025-11-21T11:19:42.812665Z","shell.execute_reply":"2025-11-21T11:19:43.418078Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}