{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","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"},"kaggle":{"accelerator":"none","dataSources":[],"dockerImageVersionId":28755,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load\n\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\n# Input data files are available in the read-only \"../input/\" directory\n# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\n\nimport os\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        print(os.path.join(dirname, filename))\n\n# You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using \"Save & Run All\" \n# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session\n\n# Use the kagglehub client library to attach Kaggle resources like competitions, datasets, and models to your session\n# Learn more about kagglehub: https://github.com/Kaggle/kagglehub/blob/main/README.md\n\nimport kagglehub\n# kagglehub.dataset_download('<owner>/<dataset-slug>')","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\n\nDATA = '/kaggle/input/competitions/rsna-knee-abnormality-detection'\nprint(os.listdir(DATA))\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-22T06:59:46.297099Z","iopub.execute_input":"2026-08-22T06:59:46.297493Z","iopub.status.idle":"2026-08-22T06:59:46.302426Z","shell.execute_reply.started":"2026-08-22T06:59:46.297465Z","shell.execute_reply":"2026-08-22T06:59:46.301811Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\n\n# Search all available input datasets\nfor folder in os.listdir('/kaggle/input'):\n    print(folder)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-22T06:58:48.585786Z","iopub.execute_input":"2026-08-22T06:58:48.586186Z","iopub.status.idle":"2026-08-22T06:58:48.590847Z","shell.execute_reply.started":"2026-08-22T06:58:48.586158Z","shell.execute_reply":"2026-08-22T06:58:48.590192Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nprint(os.listdir('/kaggle/input'))\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-22T07:03:45.759759Z","iopub.execute_input":"2026-08-22T07:03:45.760414Z","iopub.status.idle":"2026-08-22T07:03:45.764818Z","shell.execute_reply.started":"2026-08-22T07:03:45.760383Z","shell.execute_reply":"2026-08-22T07:03:45.764058Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nprint(os.listdir('/kaggle/input/competitions'))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-22T07:04:34.425134Z","iopub.execute_input":"2026-08-22T07:04:34.42543Z","iopub.status.idle":"2026-08-22T07:04:34.430071Z","shell.execute_reply.started":"2026-08-22T07:04:34.425407Z","shell.execute_reply":"2026-08-22T07:04:34.429159Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport pandas as pd\n\nDATA = '/kaggle/input/competitions/rsna-knee-abnormality-detection'\n\nprint(\"📁 Files:\", os.listdir(DATA))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-22T07:04:59.671345Z","iopub.execute_input":"2026-08-22T07:04:59.671772Z","iopub.status.idle":"2026-08-22T07:04:59.67742Z","shell.execute_reply.started":"2026-08-22T07:04:59.671744Z","shell.execute_reply":"2026-08-22T07:04:59.676576Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Load all CSVs\ntrain  = pd.read_csv(f'{DATA}/train.csv')\nseries = pd.read_csv(f'{DATA}/train_series.csv')\nsub    = pd.read_csv(f'{DATA}/sample_submission.csv')\n\nprint(f\"✅ train.csv      : {train.shape}\")\nprint(f\"✅ train_series   : {series.shape}\")\nprint(f\"✅ sample_sub     : {sub.shape}\")\n\nprint(\"\\n🔍 train.csv columns:\")\nprint(list(train.columns))\n\nprint(\"\\n🔍 train_series.csv columns:\")\nprint(list(series.columns))\n\nprint(\"\\n🔍 First 3 rows of train.csv:\")\nprint(train.head(3))\n\nprint(\"\\n🔍 First 3 rows of train_series.csv:\")\nprint(series.head(3))\n\nprint(\"\\n🔍 sample_submission columns:\")\nprint(list(sub.columns))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-22T07:05:29.623908Z","iopub.execute_input":"2026-08-22T07:05:29.624189Z","iopub.status.idle":"2026-08-22T07:05:29.870074Z","shell.execute_reply.started":"2026-08-22T07:05:29.624162Z","shell.execute_reply":"2026-08-22T07:05:29.869448Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Find studies that have actual labels (not NaN)\nTARGET_COLS = ['ACL', 'MCL', 'Medial Meniscus', 'Lateral Meniscus',\n               'Medial OA', 'Lateral OA', 'PF OA', 'Effusion',\n               'Synovitis', \"Baker's\", 'Contusion', 'Fracture']\n\n# Gold-labeled = rows where at least one label is not NaN\ngold = train.dropna(subset=TARGET_COLS, how='all')\nprint(f\"✅ Gold-labeled studies : {len(gold)}\")\nprint(f\"✅ Weak-label only      : {len(train) - len(gold)}\")\n\n# Label distribution in gold studies\nprint(\"\\n📊 Positive cases per condition:\")\nprint(gold[TARGET_COLS].sum().sort_values(ascending=False))\n\n# Check DICOM folder structure\nprint(\"\\n📁 train_series folder contents (first 5):\")\ntrain_series_path = f'{DATA}/train_series'\nprint(os.listdir(train_series_path)[:5])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-22T07:07:55.808262Z","iopub.execute_input":"2026-08-22T07:07:55.80921Z","iopub.status.idle":"2026-08-22T07:07:55.890081Z","shell.execute_reply.started":"2026-08-22T07:07:55.809178Z","shell.execute_reply":"2026-08-22T07:07:55.889448Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pydicom\nimport numpy as np\nimport matplotlib.pyplot as plt\nfrom pydicom.pixel_data_handlers.util import apply_voi_lut\n\n# ── Helper: read DICOM ──\ndef read_dicom(path):\n    d   = pydicom.dcmread(path)\n    img = apply_voi_lut(d.pixel_array, d)\n    if d.PhotometricInterpretation == 'MONOCHROME1':\n        img = img.max() - img\n    img = img - img.min()\n    if img.max() > 0:\n        img = img / img.max()\n    return (img * 255).astype('uint8')\n\n# ── Get first gold-labeled study ──\nfirst_uid = gold['StudyInstanceUID'].iloc[0]\nprint(f\"🔍 Study: {first_uid[:40]}...\")\n\n# ── Find its series from train_series.csv ──\nstudy_series = series[series['StudyInstanceUID'] == first_uid]\nprint(f\"📁 Series in this study: {len(study_series)}\")\nprint(study_series[['SeriesInstanceUID', 'Anatomical_Plane']])\n\n# ── Pick the Sagittal series first ──\nsagittal = study_series[study_series['Anatomical_Plane'] == 'Sagittal']\nif len(sagittal) == 0:\n    sagittal = study_series  # fallback\n\nseries_uid  = sagittal['SeriesInstanceUID'].iloc[0]\nseries_path = f'{DATA}/train_series/{series_uid}'\nprint(f\"\\n📂 Series path: .../{series_uid[:30]}...\")\n\n# ── Load and plot 6 slices ──\ndcm_files = sorted(os.listdir(series_path))\nprint(f\"🖼️  Total slices in series: {len(dcm_files)}\")\n\n# Pick 6 evenly spaced slices\nindices = np.linspace(0, len(dcm_files)-1, 6, dtype=int)\nfig, axes = plt.subplots(1, 6, figsize=(18, 4))\nfig.suptitle(f'Knee MRI — Sagittal Plane | Study: {first_uid[:20]}...',\n             fontweight='bold', fontsize=12)\n\nfor ax, idx in zip(axes, indices):\n    img = read_dicom(f'{series_path}/{dcm_files[idx]}')\n    ax.imshow(img, cmap='gray')\n    ax.set_title(f'Slice {idx}', fontsize=9)\n    ax.axis('off')\n\nplt.tight_layout()\nplt.show()\nprint(\"✅ MRI slices displayed!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-22T07:09:05.798973Z","iopub.execute_input":"2026-08-22T07:09:05.799744Z","iopub.status.idle":"2026-08-22T07:09:06.44853Z","shell.execute_reply.started":"2026-08-22T07:09:05.799716Z","shell.execute_reply":"2026-08-22T07:09:06.447535Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Check what's actually inside train_series folder\ntrain_series_path = f'{DATA}/train_series'\n\n# Look inside first folder\nfirst_series_folder = os.listdir(train_series_path)[0]\nprint(f\"First folder: {first_series_folder}\")\n\ninner = os.listdir(f'{train_series_path}/{first_series_folder}')\nprint(f\"\\nContents inside it ({len(inner)} items):\")\nprint(inner[:5])\n\n# Check if there's another level\nfirst_inner = inner[0]\ntry:\n    deeper = os.listdir(f'{train_series_path}/{first_series_folder}/{first_inner}')\n    print(f\"\\nOne level deeper ({len(deeper)} items):\")\n    print(deeper[:5])\nexcept:\n    print(f\"\\n'{first_inner}' is a file, not a folder\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-22T07:10:18.124747Z","iopub.execute_input":"2026-08-22T07:10:18.125095Z","iopub.status.idle":"2026-08-22T07:10:18.134821Z","shell.execute_reply.started":"2026-08-22T07:10:18.125069Z","shell.execute_reply":"2026-08-22T07:10:18.134001Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ── Fixed path: 3 levels deep ──\nseries_uid  = sagittal['SeriesInstanceUID'].iloc[0]\nseries_path = f'{DATA}/train_series/{series_uid}'\n\n# Go one level deeper\ninner_folder = os.listdir(series_path)[0]\ndcm_path     = f'{series_path}/{inner_folder}'\n\ndcm_files = sorted(os.listdir(dcm_path))\nprint(f\"✅ Found {len(dcm_files)} DICOM slices\")\n\n# ── Plot 6 evenly spaced slices ──\nindices = np.linspace(0, len(dcm_files)-1, 6, dtype=int)\nfig, axes = plt.subplots(1, 6, figsize=(18, 4))\nfig.suptitle('Knee MRI — Sagittal Plane', fontweight='bold')\n\nfor ax, idx in zip(axes, indices):\n    img = read_dicom(f'{dcm_path}/{dcm_files[idx]}')\n    ax.imshow(img, cmap='gray')\n    ax.set_title(f'Slice {idx}', fontsize=9)\n    ax.axis('off')\n\nplt.tight_layout()\nplt.show()\nprint(\"✅ MRI slices displayed!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-22T07:12:32.84618Z","iopub.execute_input":"2026-08-22T07:12:32.846458Z","iopub.status.idle":"2026-08-22T07:12:32.855078Z","shell.execute_reply.started":"2026-08-22T07:12:32.846437Z","shell.execute_reply":"2026-08-22T07:12:32.85414Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ── Updated helper to get DCM path for any SeriesInstanceUID ──\ndef get_dcm_path(series_uid, split='train'):\n    series_path = f'{DATA}/{split}_series/{series_uid}'\n    inner       = os.listdir(series_path)[0]\n    return f'{series_path}/{inner}'\n\n# Test it\ntest_path = get_dcm_path(series_uid)\nprint(f\"✅ DCM path works: .../{test_path[-40:]}\")\nprint(f\"✅ Files inside  : {len(os.listdir(test_path))}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-22T07:12:36.47087Z","iopub.execute_input":"2026-08-22T07:12:36.471575Z","iopub.status.idle":"2026-08-22T07:12:36.479306Z","shell.execute_reply.started":"2026-08-22T07:12:36.471546Z","shell.execute_reply":"2026-08-22T07:12:36.478223Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Step 1: Get actual folders that exist in train_series\ntrain_series_path = f'{DATA}/train_series'\nexisting_folders  = os.listdir(train_series_path)\nprint(f\"Total series folders: {len(existing_folders)}\")\nprint(f\"\\nFirst existing folder:\\n{existing_folders[0]}\")\n\n# Step 2: Go inside it\nlevel2 = os.listdir(f'{train_series_path}/{existing_folders[0]}')\nprint(f\"\\nLevel 2 folders inside: {len(level2)}\")\nprint(f\"First level2 folder:\\n{level2[0]}\")\n\n# Step 3: Go inside that\nlevel3 = os.listdir(f'{train_series_path}/{existing_folders[0]}/{level2[0]}')\nprint(f\"\\nLevel 3 files: {len(level3)}\")\nprint(f\"Sample file: {level3[0]}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-22T07:13:18.151379Z","iopub.execute_input":"2026-08-22T07:13:18.152022Z","iopub.status.idle":"2026-08-22T07:13:18.161023Z","shell.execute_reply.started":"2026-08-22T07:13:18.15199Z","shell.execute_reply":"2026-08-22T07:13:18.160343Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Correct structure:\n# train_series / StudyInstanceUID / SeriesInstanceUID / *.dcm\n\n# Step 1: Get first gold study\nfirst_study_uid = gold['StudyInstanceUID'].iloc[0]\nprint(f\"Study UID: {first_study_uid[:50]}...\")\n\n# Step 2: List series inside that study\nstudy_path   = f'{DATA}/train_series/{first_study_uid}'\nseries_inside = os.listdir(study_path)\nprint(f\"\\nSeries inside study: {len(series_inside)}\")\n\n# Step 3: Pick first series, list DCM files\nfirst_series_uid = series_inside[0]\ndcm_path         = f'{study_path}/{first_series_uid}'\ndcm_files        = sorted(os.listdir(dcm_path))\nprint(f\"DCM files in first series: {len(dcm_files)}\")\n\n# Step 4: Visualize 6 slices\nindices = np.linspace(0, len(dcm_files)-1, 6, dtype=int)\nfig, axes = plt.subplots(1, 6, figsize=(18, 4))\nfig.suptitle('Knee MRI — First Gold Study', fontweight='bold')\n\nfor ax, idx in zip(axes, indices):\n    img = read_dicom(f'{dcm_path}/{dcm_files[idx]}')\n    ax.imshow(img, cmap='gray')\n    ax.set_title(f'Slice {idx}', fontsize=9)\n    ax.axis('off')\n\nplt.tight_layout()\nplt.show()\nprint(\"✅ MRI slices displayed!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-22T07:13:57.846571Z","iopub.execute_input":"2026-08-22T07:13:57.846923Z","iopub.status.idle":"2026-08-22T07:13:58.648356Z","shell.execute_reply.started":"2026-08-22T07:13:57.846898Z","shell.execute_reply":"2026-08-22T07:13:58.647442Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ══ MASTER VARIABLES — run this at the start of every new notebook ══\n\nimport os, pydicom, numpy as np, pandas as pd, matplotlib.pyplot as plt\nfrom pydicom.pixel_data_handlers.util import apply_voi_lut\n\nDATA = '/kaggle/input/competitions/rsna-knee-abnormality-detection'\n\n# CSVs\ntrain  = pd.read_csv(f'{DATA}/train.csv')\nseries = pd.read_csv(f'{DATA}/train_series.csv')\nsub    = pd.read_csv(f'{DATA}/sample_submission.csv')\n\n# Gold-labeled studies only\nTARGET_COLS = ['ACL', 'MCL', 'Medial Meniscus', 'Lateral Meniscus',\n               'Medial OA', 'Lateral OA', 'PF OA', 'Effusion',\n               'Synovitis', \"Baker's\", 'Contusion', 'Fracture']\ngold = train.dropna(subset=TARGET_COLS, how='all')\n\n# Folder structure:\n# DATA/train_series/{StudyInstanceUID}/{SeriesInstanceUID}/*.dcm\n\n# Helper functions\ndef get_dcm_path(study_uid, series_uid):\n    return f'{DATA}/train_series/{study_uid}/{series_uid}'\n\ndef read_dicom(path):\n    d   = pydicom.dcmread(path)\n    img = apply_voi_lut(d.pixel_array, d)\n    if d.PhotometricInterpretation == 'MONOCHROME1':\n        img = img.max() - img\n    img = img - img.min()\n    if img.max() > 0:\n        img = img / img.max()\n    return (img * 255).astype('uint8')\n\ndef get_middle_slice(study_uid, series_uid):\n    path  = get_dcm_path(study_uid, series_uid)\n    files = sorted(os.listdir(path))\n    mid   = len(files) // 2\n    return read_dicom(f'{path}/{files[mid]}')\n\nprint(\"✅ All variables and helpers ready!\")\nprint(f\"   Gold studies : {len(gold)}\")\nprint(f\"   Target cols  : {TARGET_COLS}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-22T07:22:16.364452Z","iopub.execute_input":"2026-08-22T07:22:16.364794Z","iopub.status.idle":"2026-08-22T07:22:16.513256Z","shell.execute_reply.started":"2026-08-22T07:22:16.364767Z","shell.execute_reply":"2026-08-22T07:22:16.512568Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ── Config ──\nIMG_SIZE   = 224\nBATCH_SIZE = 8\nNUM_EPOCHS = 15\nLR         = 1e-4\n\ndef read_dicom(path):\n    d   = pydicom.dcmread(path)\n    img = apply_voi_lut(d.pixel_array, d)\n    if d.PhotometricInterpretation == 'MONOCHROME1':\n        img = img.max() - img\n    img = img - img.min()\n    if img.max() > 0:\n        img = img / img.max()\n    return (img * 255).astype('uint8')\n\ndef get_representative_image(study_uid):\n    \"\"\"\n    Gets the middle slice from the Sagittal series of a study.\n    Falls back to first available series if Sagittal not found.\n    \"\"\"\n    study_path = f'{DATA}/train_series/{study_uid}'\n\n    # Check study folder exists\n    if not os.path.exists(study_path):\n        return np.zeros((IMG_SIZE, IMG_SIZE, 3), dtype=np.uint8)\n\n    # Get all series inside this study\n    all_series = os.listdir(study_path)\n    if not all_series:\n        return np.zeros((IMG_SIZE, IMG_SIZE, 3), dtype=np.uint8)\n\n    # Try to find Sagittal series using train_series.csv\n    study_series_info = series[series['StudyInstanceUID'] == study_uid]\n    sagittal = study_series_info[\n        study_series_info['Anatomical_Plane'] == 'Sagittal'\n    ]\n\n    if len(sagittal) > 0:\n        chosen_uid = sagittal['SeriesInstanceUID'].iloc[0]\n        # Match to actual folder\n        matched = [s for s in all_series if s == chosen_uid]\n        series_uid = matched[0] if matched else all_series[0]\n    else:\n        series_uid = all_series[0]\n\n    # Get DCM files\n    dcm_folder = f'{study_path}/{series_uid}'\n    dcm_files  = sorted(os.listdir(dcm_folder))\n    if not dcm_files:\n        return np.zeros((IMG_SIZE, IMG_SIZE, 3), dtype=np.uint8)\n\n    # Read middle slice\n    mid = len(dcm_files) // 2\n    img = read_dicom(f'{dcm_folder}/{dcm_files[mid]}')\n\n    # Resize to model input size\n    import cv2\n    img = cv2.resize(img, (IMG_SIZE, IMG_SIZE))\n\n    # Grayscale → 3 channels\n    img = np.stack([img, img, img], axis=-1)\n    return img\n\nprint('✅ Helper functions defined!')\n\n# Quick test\ntest_img = get_representative_image(gold['StudyInstanceUID'].iloc[0])\nprint(f'✅ Test image shape: {test_img.shape}')\nplt.imshow(test_img[:,:,0], cmap='gray')\nplt.title('Test: middle Sagittal slice')\nplt.axis('off')\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-22T07:25:30.400631Z","iopub.execute_input":"2026-08-22T07:25:30.401402Z","iopub.status.idle":"2026-08-22T07:25:30.749224Z","shell.execute_reply.started":"2026-08-22T07:25:30.401372Z","shell.execute_reply":"2026-08-22T07:25:30.748631Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from torch.utils.data import Dataset, DataLoader\nprint('✅ Dataset imported!')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-22T07:27:27.730739Z","iopub.execute_input":"2026-08-22T07:27:27.731024Z","iopub.status.idle":"2026-08-22T07:27:31.220876Z","shell.execute_reply.started":"2026-08-22T07:27:27.731002Z","shell.execute_reply":"2026-08-22T07:27:31.220182Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\nimport torch.nn as nn\nimport torch.optim as optim\nfrom torch.utils.data import Dataset, DataLoader\nimport timm\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.metrics import roc_auc_score\nimport cv2\nimport warnings\nwarnings.filterwarnings('ignore')\n\nprint('✅ All libraries imported!')\nprint(f'✅ PyTorch version: {torch.__version__}')\nprint(f'✅ Device: {torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")}')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-22T07:29:11.894374Z","iopub.execute_input":"2026-08-22T07:29:11.894991Z","iopub.status.idle":"2026-08-22T07:29:20.129263Z","shell.execute_reply.started":"2026-08-22T07:29:11.894962Z","shell.execute_reply":"2026-08-22T07:29:20.128193Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ══ CELL 1 — ALL IMPORTS (always run this first) ══\nimport os, cv2, warnings\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nimport pydicom\nfrom pydicom.pixel_data_handlers.util import apply_voi_lut\n\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nfrom torch.utils.data import Dataset, DataLoader\nimport timm\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.metrics import roc_auc_score\n\nwarnings.filterwarnings('ignore')\ndevice = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n\nprint(f'✅ All imports done!')\nprint(f'✅ Device: {device}')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-22T07:29:58.01798Z","iopub.execute_input":"2026-08-22T07:29:58.018488Z","iopub.status.idle":"2026-08-22T07:29:58.02479Z","shell.execute_reply.started":"2026-08-22T07:29:58.018462Z","shell.execute_reply":"2026-08-22T07:29:58.024013Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class KneeDataset(Dataset):\n    def __init__(self, df, is_test=False):\n        self.df      = df.reset_index(drop=True)\n        self.is_test = is_test\n\n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, idx):\n        row       = self.df.iloc[idx]\n        study_uid = row['StudyInstanceUID']\n\n        # Load image\n        img = get_representative_image(study_uid)\n\n        # Normalize to 0→1\n        img = img.astype(np.float32) / 255.0\n\n        # Convert to tensor [C, H, W]\n        img = torch.tensor(img).permute(2, 0, 1)\n\n        # ImageNet normalization\n        mean = torch.tensor([0.485, 0.456, 0.406]).view(3,1,1)\n        std  = torch.tensor([0.229, 0.224, 0.225]).view(3,1,1)\n        img  = (img - mean) / std\n\n        if self.is_test:\n            return img, study_uid\n\n        labels = torch.tensor(\n            row[TARGET_COLS].values.astype(np.float32)\n        )\n        return img, labels\n\nprint('✅ KneeDataset defined!')\n\n# Test dataset\ntest_ds = KneeDataset(gold.head(2))\nimg, lbl = test_ds[0]\nprint(f'✅ Image tensor shape : {img.shape}')\nprint(f'✅ Label tensor shape : {lbl.shape}')\nprint(f'✅ Labels             : {lbl}')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-22T07:30:24.95014Z","iopub.execute_input":"2026-08-22T07:30:24.950551Z","iopub.status.idle":"2026-08-22T07:30:25.043899Z","shell.execute_reply.started":"2026-08-22T07:30:24.950524Z","shell.execute_reply":"2026-08-22T07:30:25.043202Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Download and save weights locally while internet is ON\nimport timm\nimport torch\n\n# Download the model with pretrained weights\ntemp_model = timm.create_model('efficientnet_b0', pretrained=True)\n\n# Save weights to disk\ntorch.save(temp_model.state_dict(), 'efficientnet_b0_weights.pth')\nprint('✅ Weights saved to efficientnet_b0_weights.pth')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-22T07:44:48.540039Z","iopub.execute_input":"2026-08-22T07:44:48.540509Z","iopub.status.idle":"2026-08-22T07:44:48.901101Z","shell.execute_reply.started":"2026-08-22T07:44:48.540475Z","shell.execute_reply":"2026-08-22T07:44:48.900229Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class KneeModel(nn.Module):\n    def __init__(self, num_labels=12):\n        super().__init__()\n        # Load WITHOUT pretrained (no internet needed)\n        self.backbone = timm.create_model(\n            'efficientnet_b0',\n            pretrained=False,   # ← changed!\n            num_classes=0,\n            global_pool='avg'\n        )\n        feat_dim = self.backbone.num_features\n        self.head = nn.Sequential(\n            nn.Dropout(0.3),\n            nn.Linear(feat_dim, num_labels)\n        )\n        # Load saved weights manually\n        state = torch.load('efficientnet_b0_weights.pth', map_location='cpu')\n        # Remove classifier head weights (we don't need them)\n        state = {k: v for k, v in state.items()\n                 if not k.startswith('classifier')}\n        self.backbone.load_state_dict(state, strict=False)\n        print('✅ Pretrained weights loaded from disk!')\n\n    def forward(self, x):\n        return self.head(self.backbone(x))\n\nmodel = KneeModel(num_labels=len(TARGET_COLS)).to(device)\nprint(f'✅ Model ready on {device}')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-22T07:46:00.310386Z","iopub.execute_input":"2026-08-22T07:46:00.310804Z","iopub.status.idle":"2026-08-22T07:46:00.760057Z","shell.execute_reply.started":"2026-08-22T07:46:00.310777Z","shell.execute_reply":"2026-08-22T07:46:00.759354Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ── Split 80/20 ──\ntrain_df, val_df = train_test_split(\n    gold, test_size=0.2, random_state=42\n)\nprint(f'Train: {len(train_df)} | Val: {len(val_df)}')\n\ntrain_loader = DataLoader(\n    KneeDataset(train_df),\n    batch_size=BATCH_SIZE, shuffle=True,  num_workers=2\n)\nval_loader = DataLoader(\n    KneeDataset(val_df),\n    batch_size=BATCH_SIZE, shuffle=False, num_workers=2\n)\n\n# ── Loss, optimizer, scheduler ──\ncriterion = nn.BCEWithLogitsLoss()\noptimizer = optim.AdamW(model.parameters(), lr=LR, weight_decay=1e-4)\nscheduler = optim.lr_scheduler.CosineAnnealingLR(\n    optimizer, T_max=NUM_EPOCHS\n)\n\n# ── Training loop ──\nbest_auc = 0\nprint('\\n🚀 Training started!\\n')\n\nfor epoch in range(1, NUM_EPOCHS + 1):\n\n    # Train\n    model.train()\n    train_loss = 0\n    for imgs, labels in train_loader:\n        imgs, labels = imgs.to(device), labels.to(device)\n        optimizer.zero_grad()\n        loss = criterion(model(imgs), labels)\n        loss.backward()\n        optimizer.step()\n        train_loss += loss.item()\n\n    # Validate\n    model.eval()\n    val_loss = 0\n    all_preds, all_labels = [], []\n    with torch.no_grad():\n        for imgs, labels in val_loader:\n            imgs, labels = imgs.to(device), labels.to(device)\n            logits = model(imgs)\n            val_loss += criterion(logits, labels).item()\n            all_preds.append(torch.sigmoid(logits).cpu().numpy())\n            all_labels.append(labels.cpu().numpy())\n\n    all_preds  = np.vstack(all_preds)\n    all_labels = np.vstack(all_labels)\n\n    try:\n        auc = roc_auc_score(all_labels, all_preds, average='macro')\n    except:\n        auc = 0.5\n\n    scheduler.step()\n\n    # Save best\n    if auc > best_auc:\n        best_auc = auc\n        torch.save(model.state_dict(), 'best_model.pth')\n        flag = '✅ BEST'\n    else:\n        flag = ''\n\n    print(f'Epoch {epoch:2d}/{NUM_EPOCHS} | '\n          f'Train Loss: {train_loss/len(train_loader):.4f} | '\n          f'Val Loss: {val_loss/len(val_loader):.4f} | '\n          f'AUC: {auc:.4f} {flag}')\n\nprint(f'\\n🏆 Best AUC: {best_auc:.4f}')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-22T07:34:23.699532Z","iopub.execute_input":"2026-08-22T07:34:23.700348Z","iopub.status.idle":"2026-08-22T07:34:37.290905Z","shell.execute_reply.started":"2026-08-22T07:34:23.700318Z","shell.execute_reply":"2026-08-22T07:34:37.289945Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Fixed AUC calculation — handles missing/constant columns\ndef safe_auc(labels, preds, target_cols):\n    aucs = []\n    for i, col in enumerate(target_cols):\n        y_true = labels[:, i]\n        y_pred = preds[:, i]\n        # Skip if only one class present in validation\n        if len(np.unique(y_true)) < 2:\n            continue\n        try:\n            aucs.append(roc_auc_score(y_true, y_pred))\n        except:\n            continue\n    if len(aucs) == 0:\n        return float('nan')\n    return np.mean(aucs)\n\nprint('✅ Fixed AUC function defined!')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-22T07:36:15.361788Z","iopub.execute_input":"2026-08-22T07:36:15.362503Z","iopub.status.idle":"2026-08-22T07:36:15.36814Z","shell.execute_reply.started":"2026-08-22T07:36:15.362474Z","shell.execute_reply":"2026-08-22T07:36:15.367275Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ── Re-run training with fixed AUC ──\nmodel = KneeModel(num_labels=len(TARGET_COLS)).to(device)\noptimizer = optim.AdamW(model.parameters(), lr=LR, weight_decay=1e-4)\nscheduler = optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=NUM_EPOCHS)\ncriterion = nn.BCEWithLogitsLoss()\n\nbest_auc = 0\nprint('🚀 Training started!\\n')\n\nfor epoch in range(1, NUM_EPOCHS + 1):\n\n    # Train\n    model.train()\n    train_loss = 0\n    for imgs, labels in train_loader:\n        imgs, labels = imgs.to(device), labels.to(device)\n        optimizer.zero_grad()\n        loss = criterion(model(imgs), labels)\n        loss.backward()\n        optimizer.step()\n        train_loss += loss.item()\n\n    # Validate\n    model.eval()\n    val_loss = 0\n    all_preds, all_labels = [], []\n    with torch.no_grad():\n        for imgs, labels in val_loader:\n            imgs, labels = imgs.to(device), labels.to(device)\n            logits = model(imgs)\n            val_loss += criterion(logits, labels).item()\n            all_preds.append(torch.sigmoid(logits).cpu().numpy())\n            all_labels.append(labels.cpu().numpy())\n\n    all_preds  = np.vstack(all_preds)\n    all_labels = np.vstack(all_labels)\n\n    # Fixed AUC\n    auc = safe_auc(all_labels, all_preds, TARGET_COLS)\n    scheduler.step()\n\n    if not np.isnan(auc) and auc > best_auc:\n        best_auc = auc\n        torch.save(model.state_dict(), 'best_model.pth')\n        flag = '✅ BEST'\n    else:\n        flag = ''\n\n    print(f'Epoch {epoch:2d}/{NUM_EPOCHS} | '\n          f'Train Loss: {train_loss/len(train_loader):.4f} | '\n          f'Val Loss: {val_loss/len(val_loader):.4f} | '\n          f'AUC: {auc:.4f} {flag}')\n\nprint(f'\\n🏆 Best AUC: {best_auc:.4f}')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-22T07:36:29.029568Z","iopub.execute_input":"2026-08-22T07:36:29.030008Z","iopub.status.idle":"2026-08-22T07:36:41.593747Z","shell.execute_reply.started":"2026-08-22T07:36:29.029983Z","shell.execute_reply":"2026-08-22T07:36:41.592887Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ── Cell 6 — Generate submission ──\nmodel.load_state_dict(torch.load('best_model.pth', map_location=device))\nmodel.eval()\nprint('✅ Best model loaded')\n\ntest_df      = pd.read_csv(f'{DATA}/test.csv')\ntest_dataset = KneeDataset(test_df, is_test=True)\ntest_loader  = DataLoader(\n    test_dataset, batch_size=BATCH_SIZE,\n    shuffle=False, num_workers=2\n)\n\nprint('🔍 Running inference on test set...')\nall_preds, all_ids = [], []\n\nwith torch.no_grad():\n    for imgs, study_ids in test_loader:\n        imgs  = imgs.to(device)\n        preds = torch.sigmoid(model(imgs)).cpu().numpy()\n        all_preds.extend(preds)\n        all_ids.extend(study_ids)\n\nsubmission = pd.DataFrame(all_preds, columns=TARGET_COLS)\nsubmission.insert(0, 'StudyInstanceUID', all_ids)\nsubmission = submission[sub.columns]\nsubmission.to_csv('submission.csv', index=False)\n\nprint(f'✅ submission.csv saved!')\nprint(f'   Shape : {submission.shape}')\nprint(f'\\n🔍 Preview:')\nprint(submission.head(3))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-22T07:39:55.723744Z","iopub.execute_input":"2026-08-22T07:39:55.72422Z","iopub.status.idle":"2026-08-22T07:39:55.987251Z","shell.execute_reply.started":"2026-08-22T07:39:55.724187Z","shell.execute_reply":"2026-08-22T07:39:55.986384Z"}},"outputs":[],"execution_count":null}]}