{"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":"nvidiaTeslaT4","dataSources":[{"sourceId":61446,"databundleVersionId":6962461,"sourceType":"competition"}],"dockerImageVersionId":31090,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"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","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-08-31T16:35:16.703088Z","iopub.execute_input":"2025-08-31T16:35:16.703387Z","iopub.status.idle":"2025-08-31T16:35:47.571736Z","shell.execute_reply.started":"2025-08-31T16:35:16.703355Z","shell.execute_reply":"2025-08-31T16:35:47.570666Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!pip install -U \\\n  albumentations==2.0.8 \\\n  opencv-python-headless==4.12.0.88 \\\n  tifffile==2025.5.24 \\\n  torchmetrics==1.4.0 \\\n  numpy==2.2.4 \\\n  scikit-learn==1.7.1 \\\n  scipy==1.16.0 \\\n  transformers==4.55.3","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-19T16:52:41.890380Z","iopub.execute_input":"2025-09-19T16:52:41.890611Z","iopub.status.idle":"2025-09-19T16:54:35.235517Z","shell.execute_reply.started":"2025-09-19T16:52:41.890587Z","shell.execute_reply":"2025-09-19T16:54:35.234818Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# === ONE-CELL PIPELINE: DeepLabV3-ResNet50 (no U-Net) for vessel segmentation ===\n\n!pip install numpy==1.23.5 scipy==1.14.1 opencv-python-headless==4.10.0.84 scikit-learn==1.2.2 albumentations==1.3.0 tifffile\n\nimport os, random\nfrom pathlib import Path\nfrom glob import glob\n\nimport numpy as np\nimport pandas as pd\nimport cv2\nimport tifffile as tiff\nfrom tqdm import tqdm\n\nimport torch\nimport torch.nn as nn\nfrom torch.utils.data import Dataset, DataLoader\nfrom torch.cuda.amp import autocast, GradScaler\n\nimport albumentations as A\nfrom albumentations.pytorch import ToTensorV2\n\nimport torchvision\nfrom torchvision.models.segmentation import deeplabv3_resnet50, DeepLabV3_ResNet50_Weights\nfrom torchvision.models._utils import IntermediateLayerGetter\nfrom torchmetrics.functional import dice as tm_dice\n\n# ------------------------------- CONFIG -------------------------------\nDATA = Path(\"/kaggle/input/blood-vessel-segmentation\")\nTRAIN = DATA/\"train\"\nTEST = DATA/\"test\"\nRLE_CSV = DATA/\"train_rles.csv\"\nOUTDIR = Path(\"./outputs\"); OUTDIR.mkdir(exist_ok=True)\n\nCFG = dict(\n    img_size=512,\n    batch_size=4,\n    num_workers=2,\n    epochs=16,\n    lr=1e-3,\n    weight_decay=1e-5,\n    seed=42,\n    mask_threshold=0.35,\n    any_threshold=0.5,\n    tta=True,\n    use_mixed_precision=True,\n)\n\nrandom.seed(CFG[\"seed\"]); np.random.seed(CFG[\"seed\"]); torch.manual_seed(CFG[\"seed\"])\n\n# ------------------------------- UTILITIES -------------------------------\ndef rle_encode(mask):\n    m = mask.flatten(order='F')\n    m = np.concatenate([[0], m, [0]])\n    runs = np.where(m[1:] != m[:-1])[0] + 1\n    runs[1::2] -= runs[::2]\n    return \" \".join(map(str, runs))\n\ndef rle_decode(rle, shape):\n    if not isinstance(rle, str) or rle.strip()==\"\":\n        return np.zeros(shape, dtype=np.uint8)\n    s = np.asarray([int(x) for x in rle.split()], dtype=int)\n    starts, lengths = s[0::2]-1, s[1::2]\n    ends = starts + lengths\n    img = np.zeros(shape[0]*shape[1], dtype=np.uint8)\n    for lo, hi in zip(starts, ends): img[lo:hi] = 1\n    return img.reshape(shape, order='F')\n\ndef read_tiff(path):\n    img = cv2.imread(str(path), -1)\n    if img is None: img = tiff.imread(str(path))\n    if img.ndim==3: img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)\n    return img\n\n# ------------------------------- DATA DISCOVERY -------------------------------\ndef find_train_items(root):\n    items = []\n    for ds in sorted(os.listdir(root)):\n        dpath = Path(root)/ds\n        if not (dpath/\"images\").exists(): continue\n        for ip in sorted(dpath.glob(\"images/*.tif\")):\n            sid = f\"{ds}_{Path(ip).stem}\"\n            lpath = dpath/\"labels\"/Path(ip).name\n            if lpath.exists(): items.append((ip,str(lpath),sid))\n            else: items.append((ip,None,sid))\n    return items\n\npairs = find_train_items(TRAIN)\nprint(\"Train slices:\", len(pairs))\n\nrle_map = {}\nif RLE_CSV.exists():\n    df_rle = pd.read_csv(RLE_CSV)\n    rle_map = dict(zip(df_rle.id.values, df_rle.rle.fillna(\"\")))\n\n# ------------------------------- DATASET -------------------------------\nclass KidneySegDataset(Dataset):\n    def __init__(self, items, augment=True, size=512):\n        self.items = items\n        self.size = size\n        if augment:\n            self.tf = A.Compose([\n                A.LongestMaxSize(max_size=size),\n                A.PadIfNeeded(size, size, border_mode=cv2.BORDER_CONSTANT, value=0, mask_value=0),\n                A.RandomRotate90(p=0.5), A.Flip(p=0.5),\n                A.Affine(scale=(0.9,1.1), rotate=(-10,10), shear=(-8,8), translate_percent=(-0.05,0.05), p=0.5),\n                A.ElasticTransform(p=0.2, alpha=50, sigma=7, alpha_affine=10),\n                A.RandomBrightnessContrast(p=0.35), A.CLAHE(clip_limit=2.0, p=0.3),\n                A.GaussianBlur(blur_limit=(3,5), p=0.2),\n                A.CoarseDropout(max_holes=6, max_height=32, max_width=32, p=0.2),\n                A.Normalize(mean=(0.5,), std=(0.5,)), ToTensorV2()\n            ])\n        else:\n            self.tf = A.Compose([\n                A.LongestMaxSize(max_size=size),\n                A.PadIfNeeded(size, size, border_mode=cv2.BORDER_CONSTANT, value=0, mask_value=0),\n                A.Normalize(mean=(0.5,), std=(0.5,)), ToTensorV2()\n            ])\n    def __len__(self): return len(self.items)\n    def __getitem__(self, i):\n        ipath, lpath, sid = self.items[i]\n        \n        img = read_tiff(ipath)\n        if img.dtype != np.uint8:\n            img = cv2.normalize(img, None, 0, 255, cv2.NORM_MINMAX).astype(np.uint8)\n        \n        if lpath is not None and Path(lpath).exists():\n            mask = read_tiff(lpath)\n            mask = (mask > 0).astype(np.uint8)\n        else:\n            mask = rle_decode(rle_map.get(sid, \"\"), img.shape)\n        \n        if img.ndim == 2:\n            img = img[..., None]\n        \n        aug = self.tf(image=img, mask=mask)\n        x = aug[\"image\"].float()\n        if x.shape[0] == 1:\n            x = x.repeat(3, 1, 1)\n        \n        y = torch.as_tensor(aug[\"mask\"], dtype=torch.float).unsqueeze(0)  # 1 x H x W\n        \n        return x, y, sid\n\n        \n\n\n\n# ------------------------------- LOAD DATA -------------------------------\nrandom.shuffle(pairs)\nsplit=int(0.9*len(pairs))\ntrain_ds=KidneySegDataset(pairs[:split],augment=True,size=CFG[\"img_size\"])\nvalid_ds=KidneySegDataset(pairs[split:],augment=False,size=CFG[\"img_size\"])\n\ntrain_loader=DataLoader(train_ds,batch_size=CFG[\"batch_size\"],shuffle=True,num_workers=CFG[\"num_workers\"],pin_memory=True,drop_last=True)\nvalid_loader=DataLoader(valid_ds,batch_size=CFG[\"batch_size\"]*2,shuffle=False,num_workers=CFG[\"num_workers\"],pin_memory=True)\n\n# ------------------------------- MODEL -------------------------------\nweights = DeepLabV3_ResNet50_Weights.DEFAULT\nnet = deeplabv3_resnet50(weights=weights, aux_loss=True)\nin_ch = net.classifier[-1].in_channels\nnet.classifier[-1] = nn.Conv2d(in_ch, 1, kernel_size=1)\nnet.aux_classifier=None\n\nclass AnyVesselHead(nn.Module):\n    def __init__(self, in_ch=2048):\n        super().__init__()\n        self.pool = nn.AdaptiveAvgPool2d(1)\n        self.fc = nn.Linear(in_ch, 1)\n    def forward(self, feat):\n        return self.fc(self.pool(feat).flatten(1))\n\naux_head = AnyVesselHead().cuda()\nnet = net.cuda()\n\n\n\n# Grab both layer4 features and the 'out' output for the classifier\nfrom torchvision.models._utils import IntermediateLayerGetter\n\n# Keep original backbone for DeepLab classifier\nbackbone_orig = net.backbone\n\n# Create a separate IntermediateLayerGetter to grab layer4 features for aux head\nreturn_layers = {\"layer4\": \"layer4_out\"}\nbackbone_for_aux = IntermediateLayerGetter(backbone_orig, return_layers=return_layers)\n\ndef forward_with_feats(images):\n    \"\"\"\n    Returns segmentation logits and layer4 features for aux head.\n    \"\"\"\n    # Layer4 features for aux head\n    layer4_feats = backbone_for_aux(images)[\"layer4_out\"]  # 2048 channels\n\n    # Full DeepLab segmentation (original backbone + classifier)\n    mask_logits = net(images)[\"out\"]  # 1 channel segmentation\n\n    return mask_logits, layer4_feats\n\n\n# ------------------------------- LOSS & OPTIMIZER -------------------------------\nbce = nn.BCEWithLogitsLoss()\ndef dice_loss(logits, targets, eps=1e-6):\n    probs = torch.sigmoid(logits)\n    num = 2*(probs*targets).sum(dim=(2,3)) + eps\n    den = probs.sum(dim=(2,3)) + targets.sum(dim=(2,3)) + eps\n    return 1 - (num/den).mean()\n\ndef loss_fn(mask_logits, mask_true, any_logits):\n    l_mask = 0.5*bce(mask_logits, mask_true) + 0.5*dice_loss(mask_logits, mask_true)\n    any_true = (mask_true.sum(dim=(2,3))>0).float()\n    l_any = bce(any_logits, any_true)\n    return l_mask + 0.2*l_any, l_mask.detach(), l_any.detach()\n\noptimizer = torch.optim.AdamW(list(net.parameters()) + list(aux_head.parameters()), lr=CFG[\"lr\"], weight_decay=CFG[\"weight_decay\"])\nscaler = GradScaler(enabled=CFG[\"use_mixed_precision\"])\n\n# ------------------------------- TRAIN / VALID -------------------------------\ndef evaluate():\n    net.eval(); aux_head.eval()\n    dices=[]\n    with torch.no_grad():\n        for x,y,_ in valid_loader:\n            x,y = x.cuda(), y.cuda()\n            with autocast(enabled=CFG[\"use_mixed_precision\"]):\n                mask_logits, feats = forward_with_feats(x)\n            p=(torch.sigmoid(mask_logits)>CFG[\"mask_threshold\"]).float()\n            for i in range(p.size(0)):\n                dices.append(tm_dice(p[i,0].to(torch.bool), y[i,0].to(torch.bool)).item())\n    return float(np.mean(dices)) if len(dices) else 0.0\n\nbest_dice=0.0\nfor epoch in range(1,CFG[\"epochs\"]+1):\n    net.train(); aux_head.train()\n    pbar=tqdm(train_loader,desc=f\"Epoch {epoch}\")\n    run_loss=0.0\n    for x,y,_ in pbar:\n        x,y = x.cuda(non_blocking=True), y.cuda(non_blocking=True)\n        optimizer.zero_grad(set_to_none=True)\n        with autocast(enabled=CFG[\"use_mixed_precision\"]):\n            mask_logits, feats = forward_with_feats(x)\n            any_logits = aux_head(feats)\n            loss,lm,la = loss_fn(mask_logits, y, any_logits)\n        scaler.scale(loss).backward()\n        scaler.step(optimizer); scaler.update()\n        run_loss = 0.98*run_loss + 0.02*loss.item() if run_loss>0 else loss.item()\n        pbar.set_postfix(loss=f\"{run_loss:.4f}\")\n    val_dice = evaluate()\n    print(f\"val dice: {val_dice:.4f}\")\n    if val_dice>best_dice:\n        best_dice=val_dice\n        torch.save({\"net\":net.state_dict(),\"aux\":aux_head.state_dict()}, OUTDIR/\"best_deeplab50.pt\")\n        print(\"Saved best.\")\n\n# ------------------------------- INFERENCE -------------------------------\ndef preprocess_for_infer(img, size):\n    tf = A.Compose([\n        A.LongestMaxSize(max_size=size),\n        A.PadIfNeeded(size, size, border_mode=cv2.BORDER_CONSTANT, value=0),\n        A.Normalize(mean=(0.5,), std=(0.5,)),\n        ToTensorV2(),\n    ])\n    out=tf(image=img[...,None])\n    x=out[\"image\"].float().permute(2,0,1).repeat(3,1,1)\n    return x, out\n\n@torch.no_grad()\ndef predict_batch(imgs3):\n    imgs3=imgs3.cuda()\n    with autocast(enabled=CFG[\"use_mixed_precision\"]):\n        mlog, feats = forward_with_feats(imgs3)\n        probs=torch.sigmoid(mlog)\n        anyp=torch.sigmoid(aux_head(feats)).squeeze(1)\n        if CFG[\"tta\"]:\n            mlog2, feats2 = forward_with_feats(torch.flip(imgs3,dims=[-1]))\n            pr2=torch.sigmoid(mlog2); pr2=torch.flip(pr2,dims=[-1])\n            ap2=torch.sigmoid(aux_head(feats2)).squeeze(1)\n            mlog3, feats3 = forward_with_feats(torch.flip(imgs3,dims=[-2]))\n            pr3=torch.sigmoid(mlog3); pr3=torch.flip(pr3,dims=[-2])\n            ap3=torch.sigmoid(aux_head(feats3)).squeeze(1)\n            probs=(probs+pr2+pr3)/3.0\n            anyp=(anyp+ap2+ap3)/3.0\n    return probs[:,0].cpu().numpy(), anyp.cpu().numpy()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-19T16:55:27.915953Z","iopub.execute_input":"2025-09-19T16:55:27.916470Z","iopub.status.idle":"2025-09-19T20:29:45.317734Z","shell.execute_reply.started":"2025-09-19T16:55:27.916436Z","shell.execute_reply":"2025-09-19T20:29:45.316693Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#from glob import glob\n\n# Ensure your model and aux_head are loaded with the best weights if not already in memory:\n# checkpoint = torch.load(\"./outputs/best_deeplab50.pt\", map_location='cuda')\n# net.load_state_dict(checkpoint[\"net\"])\n# aux_head.load_state_dict(checkpoint[\"aux\"])\n# net.eval(); aux_head.eval()\nfrom pathlib import Path\nDATA = Path(\"/kaggle/input/blood-vessel-segmentation\")\nTEST = DATA/\"test\"\n\nfrom glob import glob\nids, rles, any_flags, any_probs = [], [], [], []\ntest_paths = sorted(glob(str(TEST / \"*/*.tif\")))\n\nfor path in tqdm(test_paths, desc=\"Inference\"):\n    img = read_tiff(path)\n    img = cv2.normalize(img, None, 0, 255, cv2.NORM_MINMAX).astype(np.uint8)\n    if img.ndim == 2:\n        img = img[..., None]\n    x, _ = preprocess_for_infer(img, CFG[\"img_size\"])\n    x = x.unsqueeze(0)\n    probs, anyp = predict_batch(x)\n    mask = (probs[0] > CFG[\"mask_threshold\"]).astype(np.uint8)\n    rle = rle_encode(mask)\n    img_id = Path(path).stem\n    ids.append(img_id)\n    rles.append(rle)\n    any_flags.append(int(anyp[0] > CFG[\"any_threshold\"]))\n    any_probs.append(float(anyp[0]))\n\nsub = pd.DataFrame({\"id\": ids, \"rle\": rles})\nsub.to_csv(\"submission.csv\", index=False)\naux = pd.DataFrame({\"id\": ids, \"has_vessel\": any_flags, \"prob_any_vessel\": any_probs})\naux.to_csv(\"slice_posneg.csv\", index=False)\nprint(f\"Saved submission.csv ({len(sub)} rows) and slice_posneg.csv\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-19T20:30:10.856403Z","iopub.execute_input":"2025-09-19T20:30:10.856887Z","iopub.status.idle":"2025-09-19T20:30:10.873362Z","shell.execute_reply.started":"2025-09-19T20:30:10.856864Z","shell.execute_reply":"2025-09-19T20:30:10.872712Z"}},"outputs":[],"execution_count":null}]}