{"metadata":{"kernelspec":{"name":"python3","display_name":"Python 3","language":"python"},"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":"nvidiaTeslaT4","dataSources":[{"sourceId":52254,"databundleVersionId":9674523,"sourceType":"competition"}],"dockerImageVersionId":31192,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"id":"2274ca45","cell_type":"markdown","source":"# CT DICOM Preprocessing Pipeline\nComplete preprocessing for abdominal trauma CT classification","metadata":{}},{"id":"8edd2ef3-92d7-4c92-ab69-1887fad28d66","cell_type":"code","source":"!pip install segmentation-models-pytorch\nimport os\nimport glob\nimport numpy as np\nimport pydicom\nimport cv2\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nfrom torch.utils.data import Dataset, DataLoader\nfrom torch.cuda.amp import autocast, GradScaler\nfrom tqdm.notebook import tqdm\nimport segmentation_models_pytorch as smp\n\n# --- 1. CONFIGURATION ---\nCONFIG = {\n    \"INPUT_DIR\": \"/kaggle/input/rsna-2023-abdominal-trauma-detection/train_images\",\n    \"IMG_SIZE\": 256,\n    \"BATCH_SIZE\": 16,      # Good for RTX 3060 / P100\n    \"ACCUM_STEPS\": 2,      # Effective Batch = 32\n    \"LR\": 1e-4,\n    \"EPOCHS\": 5,\n    \"DEVICE\": \"cuda\" if torch.cuda.is_available() else \"cpu\",\n    \"NUM_WORKERS\": 2       # Uses CPU to load DICOMs while GPU trains\n}\n\n# --- 2. PREPROCESSING UTILS (ON-THE-FLY) ---\ndef get_windowing(image, slope, intercept, window_center, window_width):\n    \"\"\"Apply CT windowing to raw pixels.\"\"\"\n    img_hu = image * slope + intercept\n    img_min = window_center - window_width // 2\n    img_max = window_center + window_width // 2\n    img_window = np.clip(img_hu, img_min, img_max)\n    return img_window\n\ndef load_dicom_volume(series_path):\n    \"\"\"Reads DICOMs, selects a random chunk, and preprocesses.\"\"\"\n    dicom_files = sorted(glob.glob(os.path.join(series_path, \"*.dcm\")))\n    \n    if not dicom_files:\n        return None\n\n    # --- EFFICIENCY HACK ---\n    # Instead of loading 500 slices, pick 3 random consecutive slices (2.5D)\n    # This makes it FAST enough for on-the-fly training.\n    if len(dicom_files) < 3: \n        return None\n    \n    start_idx = np.random.randint(0, len(dicom_files) - 3)\n    selected_files = dicom_files[start_idx : start_idx+3]\n    \n    processed_chunk = []\n    \n    for f in selected_files:\n        try:\n            ds = pydicom.dcmread(f)\n            slope = float(getattr(ds, 'RescaleSlope', 1))\n            intercept = float(getattr(ds, 'RescaleIntercept', 0))\n            pixel_data = ds.pixel_array.astype(np.float32)\n            \n            # Soft Tissue Window (Abdomen)\n            img = get_windowing(pixel_data, slope, intercept, 40, 400)\n            \n            # Normalize to [0, 1]\n            img = (img - (40 - 200)) / 400 \n            \n            # Resize\n            img = cv2.resize(img, (CONFIG['IMG_SIZE'], CONFIG['IMG_SIZE']))\n            processed_chunk.append(img)\n        except:\n            return None\n\n    if len(processed_chunk) != 3: \n        return None\n\n    # Stack -> (256, 256, 3)\n    return np.dstack(processed_chunk)\n\n# --- 3. DIRECT DATASET (NO SAVING TO DISK) ---\nclass DirectTraumaDataset(Dataset):\n    def __init__(self, root_dir):\n        self.root_dir = root_dir\n        # Get list of all patient folders\n        self.patient_ids = sorted(os.listdir(root_dir))\n        # Filter out non-folders if any\n        self.patient_ids = [p for p in self.patient_ids if os.path.isdir(os.path.join(root_dir, p))]\n        \n    def __len__(self):\n        return len(self.patient_ids)\n    \n    def __getitem__(self, idx):\n        patient_id = self.patient_ids[idx]\n        patient_path = os.path.join(self.root_dir, patient_id)\n        \n        # Get first available series (simplified for training loop)\n        series_list = os.listdir(patient_path)\n        if not series_list:\n            return torch.zeros((3, CONFIG['IMG_SIZE'], CONFIG['IMG_SIZE'])), torch.zeros((1, CONFIG['IMG_SIZE'], CONFIG['IMG_SIZE']))\n            \n        series_id = series_list[0]\n        series_path = os.path.join(patient_path, series_id)\n        \n        # Load Data (CPU Intense)\n        image = load_dicom_volume(series_path)\n        \n        if image is None:\n            # Fallback if load fails\n            image = np.zeros((CONFIG['IMG_SIZE'], CONFIG['IMG_SIZE'], 3), dtype=np.float32)\n            \n        # To Tensor (C, H, W)\n        image = torch.from_numpy(image).permute(2, 0, 1).float()\n        \n        # Dummy Mask (Replace with real labels later)\n        mask = torch.zeros((1, CONFIG['IMG_SIZE'], CONFIG['IMG_SIZE'])).float()\n        \n        return image, mask\n\n# --- 4. TRAINING LOOP ---\ndef train_on_the_fly():\n    print(f\"🚀 Initializing On-the-Fly Pipeline on {CONFIG['DEVICE']}...\")\n    \n    # Dataset\n    dataset = DirectTraumaDataset(CONFIG['INPUT_DIR'])\n    print(f\"Found {len(dataset)} patients.\")\n    \n    loader = DataLoader(\n        dataset, \n        batch_size=CONFIG['BATCH_SIZE'], \n        shuffle=True, \n        num_workers=CONFIG['NUM_WORKERS'], \n        pin_memory=True\n    )\n    \n    # --- FIX: CHANGED ENCODER TO 'mobilenet_v2' ---\n    model = smp.Unet(\n        encoder_name=\"mobilenet_v2\",      # Supported & Fast\n        encoder_weights=\"imagenet\", \n        in_channels=3, \n        classes=1\n    ).to(CONFIG['DEVICE'])\n    \n    optimizer = optim.AdamW(model.parameters(), lr=CONFIG['LR'])\n    criterion = nn.BCEWithLogitsLoss()\n    scaler = GradScaler()\n\n    # Loop\n    for epoch in range(CONFIG['EPOCHS']):\n        model.train()\n        epoch_loss = 0\n        pbar = tqdm(loader, desc=f\"Epoch {epoch+1}\")\n        \n        for i, (img, mask) in enumerate(pbar):\n            img, mask = img.to(CONFIG['DEVICE']), mask.to(CONFIG['DEVICE'])\n            \n            # Mixed Precision Step\n            with autocast():\n                pred = model(img)\n                loss = criterion(pred, mask) / CONFIG['ACCUM_STEPS']\n            \n            scaler.scale(loss).backward()\n            \n            if (i + 1) % CONFIG['ACCUM_STEPS'] == 0:\n                scaler.step(optimizer)\n                scaler.update()\n                optimizer.zero_grad()\n                \n            epoch_loss += loss.item() * CONFIG['ACCUM_STEPS']\n            pbar.set_postfix(loss=loss.item() * CONFIG['ACCUM_STEPS'])\n            \n        print(f\"Epoch {epoch+1} Complete. Avg Loss: {epoch_loss/len(loader):.4f}\")\n\n# Run\nif __name__ == \"__main__\":\n    train_on_the_fly()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-21T10:18:05.867494Z","iopub.execute_input":"2025-11-21T10:18:05.86817Z","iopub.status.idle":"2025-11-21T10:26:31.072296Z","shell.execute_reply.started":"2025-11-21T10:18:05.868136Z","shell.execute_reply":"2025-11-21T10:26:31.071353Z"}},"outputs":[],"execution_count":null}]}