{"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":36363,"databundleVersionId":4050810,"sourceType":"competition"}],"dockerImageVersionId":31090,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import os, pandas as pd\n\ndata_dir = \"/kaggle/input/rsna-2022-cervical-spine-fracture-detection\"\n\n# Load CSV\ntrain_csv = os.path.join(data_dir, \"train.csv\")\ndf = pd.read_csv(train_csv)\n\nprint(\"Total studies:\", len(df))\nprint(df.head())\n\n# Example: check one study folder\nstudy_id = df.loc[0, \"StudyInstanceUID\"]\nstudy_path = os.path.join(data_dir, \"train_images\", study_id)\nprint(\"Study path:\", study_path)\nprint(\"DICOM slices in this study:\", len(os.listdir(study_path)))\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-06T11:02:33.568676Z","iopub.execute_input":"2025-10-06T11:02:33.568985Z","iopub.status.idle":"2025-10-06T11:02:33.900109Z","shell.execute_reply.started":"2025-10-06T11:02:33.568961Z","shell.execute_reply":"2025-10-06T11:02:33.899447Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ===============================================================\n# 🦴 RSNA 2022 Cervical Spine Fracture Detection - PyTorch Pipeline\n# Author: [Your Name]\n# Description: End-to-end DICOM → ResNet18 classification training,\n# validation, and prediction visualization.\n# ===============================================================\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-06T11:02:33.90085Z","iopub.execute_input":"2025-10-06T11:02:33.901217Z","iopub.status.idle":"2025-10-06T11:02:33.904407Z","shell.execute_reply.started":"2025-10-06T11:02:33.901196Z","shell.execute_reply":"2025-10-06T11:02:33.903824Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ===============================\n# 1️⃣ Imports\n# ===============================\nimport os, random\nimport numpy as np\nimport pandas as pd\nfrom PIL import Image\nimport pydicom\nfrom tqdm import tqdm\n\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nfrom torch.utils.data import Dataset, DataLoader\nfrom sklearn.model_selection import StratifiedKFold\nimport torchvision.transforms as T\nimport timm\nimport torch.nn.functional as F\nfrom sklearn.metrics import f1_score\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-06T13:30:52.607236Z","iopub.execute_input":"2025-10-06T13:30:52.607526Z","iopub.status.idle":"2025-10-06T13:30:52.612815Z","shell.execute_reply.started":"2025-10-06T13:30:52.607503Z","shell.execute_reply":"2025-10-06T13:30:52.61196Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ===============================\n# 2️⃣ Paths\n# ===============================\nBASE_PATH = \"/kaggle/input/rsna-2022-cervical-spine-fracture-detection\"\nTRAIN_IMG_DIR = os.path.join(BASE_PATH, \"train_images\")\nCSV_PATH = os.path.join(BASE_PATH, \"train.csv\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-06T13:31:04.457855Z","iopub.execute_input":"2025-10-06T13:31:04.458474Z","iopub.status.idle":"2025-10-06T13:31:04.462099Z","shell.execute_reply.started":"2025-10-06T13:31:04.458448Z","shell.execute_reply":"2025-10-06T13:31:04.461363Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ===============================\n# 3️⃣ Load CSV\n# ===============================\ndf = pd.read_csv(CSV_PATH, encoding='utf-8-sig')\ndf[\"StudyInstanceUID\"] = df[\"StudyInstanceUID\"].astype(str).str.strip()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-06T13:31:14.204853Z","iopub.execute_input":"2025-10-06T13:31:14.205114Z","iopub.status.idle":"2025-10-06T13:31:14.219289Z","shell.execute_reply.started":"2025-10-06T13:31:14.205097Z","shell.execute_reply":"2025-10-06T13:31:14.218618Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ===============================\n# 4️⃣ Dataset class (2.5D)\n# ===============================\nclass SpineDataset2p5D(Dataset):\n    def __init__(self, df, img_dir, transform=None):\n        self.df = df\n        self.img_dir = img_dir\n        self.transform = transform\n\n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, idx):\n        row = self.df.iloc[idx]\n        patient_id = row[\"StudyInstanceUID\"]\n        folder_path = os.path.join(self.img_dir, patient_id)\n        dicom_files = sorted([f for f in os.listdir(folder_path) if f.endswith(\".dcm\")])\n        n = len(dicom_files)\n        if n < 3:\n            dicom_files += [dicom_files[-1]]*(3-n)\n            n = 3\n        mid_idx = n // 2\n        slice_idxs = [max(mid_idx-1,0), mid_idx, min(mid_idx+1,n-1)]\n        imgs = []\n        for i in slice_idxs:\n            dcm_path = os.path.join(folder_path, dicom_files[i])\n            dcm = pydicom.dcmread(dcm_path)\n            img = dcm.pixel_array.astype(np.float32)\n            img = (img - np.min(img)) / (np.max(img) - np.min(img) + 1e-5)\n            img = (img * 255).astype(np.uint8)\n            imgs.append(img)\n        img_stack = np.stack(imgs, axis=-1)\n        img_stack = Image.fromarray(img_stack).convert(\"RGB\")\n        if self.transform:\n            img_stack = self.transform(img_stack)\n        label = torch.tensor(row[\"patient_overall\"], dtype=torch.long)\n        return img_stack, label\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-06T13:31:28.569612Z","iopub.execute_input":"2025-10-06T13:31:28.56986Z","iopub.status.idle":"2025-10-06T13:31:28.577816Z","shell.execute_reply.started":"2025-10-06T13:31:28.569843Z","shell.execute_reply":"2025-10-06T13:31:28.576992Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\n# ===============================\n# 5️⃣ Transforms\n# ===============================\ntrain_transforms = T.Compose([\n    T.Resize((224,224)),\n    T.RandomHorizontalFlip(),\n    T.RandomRotation(10),\n    T.ToTensor(),\n    T.Normalize(mean=[0.485,0.456,0.406], std=[0.229,0.224,0.225])\n])\n\nval_transforms = T.Compose([\n    T.Resize((224,224)),\n    T.ToTensor(),\n    T.Normalize(mean=[0.485,0.456,0.406], std=[0.229,0.224,0.225])\n])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-06T13:31:39.748776Z","iopub.execute_input":"2025-10-06T13:31:39.749355Z","iopub.status.idle":"2025-10-06T13:31:39.755218Z","shell.execute_reply.started":"2025-10-06T13:31:39.74933Z","shell.execute_reply":"2025-10-06T13:31:39.754554Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ===============================\n# 6️⃣ Device\n# ===============================\nDEVICE = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nprint(\"Using device:\", DEVICE)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-06T13:31:48.268927Z","iopub.execute_input":"2025-10-06T13:31:48.269203Z","iopub.status.idle":"2025-10-06T13:31:48.273413Z","shell.execute_reply.started":"2025-10-06T13:31:48.269185Z","shell.execute_reply":"2025-10-06T13:31:48.272775Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ===============================\n# 7️⃣ Stratified K-Fold\n# ===============================\nNUM_FOLDS = 5\nskf = StratifiedKFold(n_splits=NUM_FOLDS, shuffle=True, random_state=42)\ndf[\"fold\"] = -1\nfor fold, (train_idx, val_idx) in enumerate(skf.split(df, df[\"patient_overall\"])):\n    df.loc[val_idx, \"fold\"] = fold\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-06T13:31:57.785777Z","iopub.execute_input":"2025-10-06T13:31:57.786072Z","iopub.status.idle":"2025-10-06T13:31:57.796698Z","shell.execute_reply.started":"2025-10-06T13:31:57.78605Z","shell.execute_reply":"2025-10-06T13:31:57.795914Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ===============================\n# 8️⃣ FocalLoss\n# ===============================\nclass FocalLoss(nn.Module):\n    def __init__(self, gamma=2, weight=None):\n        super().__init__()\n        self.gamma = gamma\n        self.weight = weight\n\n    def forward(self, inputs, targets):\n        logp = F.cross_entropy(inputs, targets, weight=self.weight, reduction='none')\n        pt = torch.exp(-logp)\n        loss = ((1 - pt) ** self.gamma) * logp\n        return loss.mean()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-06T13:32:13.117203Z","iopub.execute_input":"2025-10-06T13:32:13.117929Z","iopub.status.idle":"2025-10-06T13:32:13.12268Z","shell.execute_reply.started":"2025-10-06T13:32:13.117903Z","shell.execute_reply":"2025-10-06T13:32:13.121991Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ===============================\n# 9️⃣ Training & Validation\n# ===============================\ndef train_one_epoch(model, loader, criterion, optimizer, scaler):\n    model.train()\n    running_loss = 0.0\n    preds_all, labels_all = [], []\n    for imgs, labels in loader:\n        imgs, labels = imgs.to(DEVICE), labels.to(DEVICE)\n        optimizer.zero_grad()\n        with torch.amp.autocast(device_type=\"cuda\"):\n            outputs = model(imgs)\n            loss = criterion(outputs, labels)\n        scaler.scale(loss).backward()\n        scaler.step(optimizer)\n        scaler.update()\n        running_loss += loss.item() * imgs.size(0)\n        preds_all.append(outputs.argmax(1).cpu().numpy())\n        labels_all.append(labels.cpu().numpy())\n    epoch_loss = running_loss / len(loader.dataset)\n    epoch_f1 = f1_score(np.concatenate(labels_all), np.concatenate(preds_all), average=\"macro\")\n    return epoch_loss, epoch_f1\n\ndef validate_one_epoch(model, loader, criterion):\n    model.eval()\n    running_loss = 0.0\n    preds_all, labels_all = [], []\n    with torch.no_grad():\n        for imgs, labels in loader:\n            imgs, labels = imgs.to(DEVICE), labels.to(DEVICE)\n            outputs = model(imgs)\n            loss = criterion(outputs, labels)\n            running_loss += loss.item() * imgs.size(0)\n            preds_all.append(outputs.argmax(1).cpu().numpy())\n            labels_all.append(labels.cpu().numpy())\n    val_loss = running_loss / len(loader.dataset)\n    val_f1 = f1_score(np.concatenate(labels_all), np.concatenate(preds_all), average=\"macro\")\n    return val_loss, val_f1\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-06T13:32:25.420805Z","iopub.execute_input":"2025-10-06T13:32:25.421075Z","iopub.status.idle":"2025-10-06T13:32:25.42941Z","shell.execute_reply.started":"2025-10-06T13:32:25.421057Z","shell.execute_reply":"2025-10-06T13:32:25.428589Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\n# ===============================\n# 🔟 K-Fold Training Loop\n# ===============================\nEPOCHS = 25\nBATCH_SIZE = 8\n\nfor fold in range(NUM_FOLDS):\n    print(f\"\\n================ Fold {fold+1} ================\")\n    train_df = df[df[\"fold\"] != fold].reset_index(drop=True)\n    val_df = df[df[\"fold\"] == fold].reset_index(drop=True)\n\n    train_dataset = SpineDataset2p5D(train_df, TRAIN_IMG_DIR, transform=train_transforms)\n    val_dataset = SpineDataset2p5D(val_df, TRAIN_IMG_DIR, transform=val_transforms)\n    train_loader = DataLoader(train_dataset, batch_size=BATCH_SIZE, shuffle=True, num_workers=2)\n    val_loader = DataLoader(val_dataset, batch_size=BATCH_SIZE, shuffle=False, num_workers=2)\n\n    model = timm.create_model(\"tf_efficientnet_b4_ns\", pretrained=True, num_classes=2).to(DEVICE)\n    criterion = FocalLoss()\n    optimizer = optim.Adam(model.parameters(), lr=1e-4)\n    scheduler = optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=EPOCHS)\n    scaler = torch.amp.GradScaler()\n\n    best_f1 = 0\n    for epoch in range(EPOCHS):\n        train_loss, train_f1 = train_one_epoch(model, train_loader, criterion, optimizer, scaler)\n        val_loss, val_f1 = validate_one_epoch(model, val_loader, criterion)\n        scheduler.step()\n        print(f\"Epoch {epoch+1}/{EPOCHS} - Train Loss:{train_loss:.4f} | Train F1:{train_f1:.4f} | Val Loss:{val_loss:.4f} | Val F1:{val_f1:.4f}\")\n\n        if val_f1 > best_f1:\n            best_f1 = val_f1\n            torch.save(model.state_dict(), f\"best_model_fold{fold}.pth\")\n            print(f\"✅ Saved new best model with F1: {best_f1:.4f}\")\n\nprint(\"\\n🎉 Training completed!\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-06T13:32:39.030711Z","iopub.execute_input":"2025-10-06T13:32:39.031025Z","iopub.status.idle":"2025-10-06T14:47:11.744807Z","shell.execute_reply.started":"2025-10-06T13:32:39.031002Z","shell.execute_reply":"2025-10-06T14:47:11.743998Z"}},"outputs":[],"execution_count":null}]}