{"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":"none","dataSources":[{"sourceId":8089,"databundleVersionId":44321,"sourceType":"competition"},{"sourceId":13198640,"sourceType":"datasetVersion","datasetId":8364622}],"dockerImageVersionId":31089,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import os\nbase = \"/kaggle/input/data-science-bowl-2018\"\nprint(os.listdir(base))\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-28T11:34:07.972611Z","iopub.execute_input":"2025-09-28T11:34:07.972931Z","iopub.status.idle":"2025-09-28T11:34:07.981222Z","shell.execute_reply.started":"2025-09-28T11:34:07.972902Z","shell.execute_reply":"2025-09-28T11:34:07.98027Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import zipfile, os\n\nbase = \"/kaggle/input/data-science-bowl-2018\"\nzip_path = os.path.join(base, \"stage1_train.zip\")\nextract_dir = \"/kaggle/working/stage1_train\"\n\nwith zipfile.ZipFile(zip_path, 'r') as zf:\n    zf.extractall(extract_dir)\n\nprint(\"Extracted to:\", extract_dir)\nprint(\"First 5 sample folders:\", os.listdir(extract_dir)[:5])\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-28T11:34:46.961186Z","iopub.execute_input":"2025-09-28T11:34:46.962056Z","iopub.status.idle":"2025-09-28T11:34:51.363356Z","shell.execute_reply.started":"2025-09-28T11:34:46.962013Z","shell.execute_reply":"2025-09-28T11:34:51.362169Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nsample_id = os.listdir(extract_dir)[0]\nsample_dir = os.path.join(extract_dir, sample_id)\n\nprint(\"Contents of one sample:\", os.listdir(sample_dir))\nprint(\"Image folder:\", os.listdir(os.path.join(sample_dir, \"images\")))\nprint(\"Masks folder:\", os.listdir(os.path.join(sample_dir, \"masks\"))[:5])  # show 5 masks\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-28T11:35:01.668211Z","iopub.execute_input":"2025-09-28T11:35:01.66847Z","iopub.status.idle":"2025-09-28T11:35:01.676297Z","shell.execute_reply.started":"2025-09-28T11:35:01.668451Z","shell.execute_reply":"2025-09-28T11:35:01.675368Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import numpy as np\nfrom PIL import Image\nimport os\n\ninput_dir = extract_dir\nout_root = \"/kaggle/working/dataset\"\nimgs_out = os.path.join(out_root, \"images\")\nmasks_out = os.path.join(out_root, \"masks\")\nos.makedirs(imgs_out, exist_ok=True)\nos.makedirs(masks_out, exist_ok=True)\n\ntarget_size = (256, 256)   # you can change to (512, 512)\n\ncount = 0\nfor sid in os.listdir(input_dir):\n    sdir = os.path.join(input_dir, sid)\n    ipath = os.path.join(sdir, \"images\", sid + \".png\")\n    mdir = os.path.join(sdir, \"masks\")\n\n    if not os.path.exists(ipath) or not os.path.isdir(mdir):\n        continue\n\n    # load & resize image\n    img = Image.open(ipath).convert(\"RGB\").resize(target_size)\n    \n    # combine masks\n    combined = np.zeros(target_size[::-1], dtype=np.uint8)  # (H, W)\n    for mname in os.listdir(mdir):\n        m = Image.open(os.path.join(mdir, mname)).convert(\"L\").resize(target_size)\n        m = np.array(m)\n        combined = np.maximum(combined, (m > 0).astype(np.uint8))\n\n    # save\n    img.save(os.path.join(imgs_out, f\"{sid}.png\"))\n    Image.fromarray((combined * 255).astype(\"uint8\")).save(os.path.join(masks_out, f\"{sid}.png\"))\n    count += 1\n\nprint(\"✅ Saved\", count, \"images to\", imgs_out)\nprint(\"✅ Saved masks to\", masks_out)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-28T11:36:40.441811Z","iopub.execute_input":"2025-09-28T11:36:40.442174Z","iopub.status.idle":"2025-09-28T11:38:07.00411Z","shell.execute_reply.started":"2025-09-28T11:36:40.442153Z","shell.execute_reply":"2025-09-28T11:38:07.002924Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"Images:\", len(os.listdir(imgs_out)))\nprint(\"Masks:\", len(os.listdir(masks_out)))\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-28T11:38:17.680912Z","iopub.execute_input":"2025-09-28T11:38:17.681171Z","iopub.status.idle":"2025-09-28T11:38:17.688987Z","shell.execute_reply.started":"2025-09-28T11:38:17.681154Z","shell.execute_reply":"2025-09-28T11:38:17.688044Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import matplotlib.pyplot as plt\nimport random\n\npairs = random.sample(os.listdir(imgs_out), 3)\n\nfor sid in pairs:\n    img = Image.open(os.path.join(imgs_out, sid))\n    mask = Image.open(os.path.join(masks_out, sid))\n    \n    plt.figure(figsize=(6,3))\n    plt.subplot(1,2,1); plt.imshow(img); plt.axis(\"off\"); plt.title(\"Image\")\n    plt.subplot(1,2,2); plt.imshow(mask, cmap=\"gray\"); plt.axis(\"off\"); plt.title(\"Mask\")\n    plt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-28T11:39:02.454746Z","iopub.execute_input":"2025-09-28T11:39:02.455043Z","iopub.status.idle":"2025-09-28T11:39:03.053746Z","shell.execute_reply.started":"2025-09-28T11:39:02.455017Z","shell.execute_reply":"2025-09-28T11:39:03.052448Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\nimport torch.nn as nn\nfrom torch.utils.data import Dataset, DataLoader\nimport torchvision.transforms as T\nfrom PIL import Image\nimport numpy as np\nimport os\n\nclass NucleiDataset(Dataset):\n    def __init__(self, img_dir, mask_dir, transform=None):\n        self.img_dir = img_dir\n        self.mask_dir = mask_dir\n        self.img_names = sorted(os.listdir(img_dir))\n        self.transform = transform\n\n    def __len__(self):\n        return len(self.img_names)\n\n    def __getitem__(self, idx):\n        img_name = self.img_names[idx]\n        img_path = os.path.join(self.img_dir, img_name)\n        mask_path = os.path.join(self.mask_dir, img_name)\n\n        image = Image.open(img_path).convert(\"RGB\")\n        mask = Image.open(mask_path).convert(\"L\")\n\n        image = np.array(image)\n        mask = np.array(mask)\n\n        # normalize mask to 0/1\n        mask = (mask > 0).astype(np.float32)\n\n        if self.transform:\n            augmented = self.transform(image=image, mask=mask)\n            image = augmented[\"image\"]\n            mask = augmented[\"mask\"].unsqueeze(0)  # add channel dim\n\n        return image, mask\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-28T11:40:26.566227Z","iopub.execute_input":"2025-09-28T11:40:26.566787Z","iopub.status.idle":"2025-09-28T11:40:36.736475Z","shell.execute_reply.started":"2025-09-28T11:40:26.566754Z","shell.execute_reply":"2025-09-28T11:40:36.735472Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# corrected dataset creation using the split lists\ntrain_ds = NucleiDataset(\n    img_dir, mask_dir, transform=train_tf\n)\ntrain_ds.img_names = train_imgs   # use only train list\n\nval_ds = NucleiDataset(\n    img_dir, mask_dir, transform=val_tf\n)\nval_ds.img_names = val_imgs       # use only val list\n\n# dataloaders\ntrain_loader = DataLoader(train_ds, batch_size=8, shuffle=True, num_workers=2)\nval_loader   = DataLoader(val_ds, batch_size=8, shuffle=False, num_workers=2)\n\nprint(\"Train samples:\", len(train_ds))\nprint(\"Val samples:\", len(val_ds))\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-28T11:43:43.167588Z","iopub.execute_input":"2025-09-28T11:43:43.168071Z","iopub.status.idle":"2025-09-28T11:43:43.176576Z","shell.execute_reply.started":"2025-09-28T11:43:43.168048Z","shell.execute_reply":"2025-09-28T11:43:43.175453Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import matplotlib.pyplot as plt\n\n# take one batch from train_loader\nimages, masks = next(iter(train_loader))\n\n# pick first item in the batch\nimg = images[0].permute(1,2,0).cpu().numpy()   # [C,H,W] -> [H,W,C]\nmask = masks[0][0].cpu().numpy()\n\n# denormalize (undo normalization)\nimg = (img - img.min()) / (img.max() - img.min())  # scale back to 0-1 for display\n\nplt.figure(figsize=(6,3))\nplt.subplot(1,2,1); plt.imshow(img); plt.axis(\"off\"); plt.title(\"Image\")\nplt.subplot(1,2,2); plt.imshow(mask, cmap=\"gray\"); plt.axis(\"off\"); plt.title(\"Mask\")\nplt.show()\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-28T11:45:12.490636Z","iopub.execute_input":"2025-09-28T11:45:12.490971Z","iopub.status.idle":"2025-09-28T11:45:12.880525Z","shell.execute_reply.started":"2025-09-28T11:45:12.490946Z","shell.execute_reply":"2025-09-28T11:45:12.879464Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class UNet(nn.Module):\n    def __init__(self, in_channels=3, out_channels=1):\n        super(UNet, self).__init__()\n\n        def conv_block(in_c, out_c):\n            return nn.Sequential(\n                nn.Conv2d(in_c, out_c, kernel_size=3, padding=1),\n                nn.BatchNorm2d(out_c),\n                nn.ReLU(inplace=True),\n                nn.Conv2d(out_c, out_c, kernel_size=3, padding=1),\n                nn.BatchNorm2d(out_c),\n                nn.ReLU(inplace=True),\n            )\n\n        self.enc1 = conv_block(in_channels, 64)\n        self.enc2 = conv_block(64, 128)\n        self.enc3 = conv_block(128, 256)\n        self.enc4 = conv_block(256, 512)\n\n        self.bottom = conv_block(512, 1024)\n\n        self.up4 = nn.ConvTranspose2d(1024, 512, kernel_size=2, stride=2)\n        self.dec4 = conv_block(1024, 512)\n\n        self.up3 = nn.ConvTranspose2d(512, 256, kernel_size=2, stride=2)\n        self.dec3 = conv_block(512, 256)\n\n        self.up2 = nn.ConvTranspose2d(256, 128, kernel_size=2, stride=2)\n        self.dec2 = conv_block(256, 128)\n\n        self.up1 = nn.ConvTranspose2d(128, 64, kernel_size=2, stride=2)\n        self.dec1 = conv_block(128, 64)\n\n        self.final = nn.Conv2d(64, out_channels, kernel_size=1)\n        self.pool = nn.MaxPool2d(2)\n\n    def forward(self, x):\n        e1 = self.enc1(x)\n        e2 = self.enc2(self.pool(e1))\n        e3 = self.enc3(self.pool(e2))\n        e4 = self.enc4(self.pool(e3))\n\n        b = self.bottom(self.pool(e4))\n\n        d4 = self.up4(b)\n        d4 = torch.cat([d4, e4], dim=1)\n        d4 = self.dec4(d4)\n\n        d3 = self.up3(d4)\n        d3 = torch.cat([d3, e3], dim=1)\n        d3 = self.dec3(d3)\n\n        d2 = self.up2(d3)\n        d2 = torch.cat([d2, e2], dim=1)\n        d2 = self.dec2(d2)\n\n        d1 = self.up1(d2)\n        d1 = torch.cat([d1, e1], dim=1)\n        d1 = self.dec1(d1)\n\n        out = self.final(d1)\n        return out\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-28T11:53:47.19697Z","iopub.execute_input":"2025-09-28T11:53:47.197846Z","iopub.status.idle":"2025-09-28T11:53:47.208805Z","shell.execute_reply.started":"2025-09-28T11:53:47.197816Z","shell.execute_reply":"2025-09-28T11:53:47.207658Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"device = \"cuda\" if torch.cuda.is_available() else \"cpu\"\nmodel = UNet(in_channels=3, out_channels=1).to(device)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-28T11:53:09.527745Z","iopub.execute_input":"2025-09-28T11:53:09.528131Z","iopub.status.idle":"2025-09-28T11:53:09.813219Z","shell.execute_reply.started":"2025-09-28T11:53:09.528109Z","shell.execute_reply":"2025-09-28T11:53:09.81229Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch.nn.functional as F\n\ndef dice_loss(pred, target, smooth=1.):\n    pred = torch.sigmoid(pred)\n    pred = pred.contiguous().view(-1)\n    target = target.contiguous().view(-1)\n    intersection = (pred * target).sum()\n    return 1 - ((2. * intersection + smooth) /\n                (pred.sum() + target.sum() + smooth))\n\ndef bce_dice_loss(pred, target):\n    bce = F.binary_cross_entropy_with_logits(pred, target)\n    d = dice_loss(pred, target)\n    return bce + d\n\noptimizer = torch.optim.Adam(model.parameters(), lr=1e-4)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-28T11:53:15.097085Z","iopub.execute_input":"2025-09-28T11:53:15.097387Z","iopub.status.idle":"2025-09-28T11:53:15.105012Z","shell.execute_reply.started":"2025-09-28T11:53:15.097368Z","shell.execute_reply":"2025-09-28T11:53:15.103752Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model = UNet(in_channels=3, out_channels=1).to(device)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-28T11:54:06.242803Z","iopub.execute_input":"2025-09-28T11:54:06.243148Z","iopub.status.idle":"2025-09-28T11:54:06.518683Z","shell.execute_reply.started":"2025-09-28T11:54:06.243124Z","shell.execute_reply":"2025-09-28T11:54:06.517909Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"best_val = 1e9\nnum_epochs = 2   # quick test run, later increase to 30-50\n\nfor epoch in range(1, num_epochs+1):\n    # ---- Training ----\n    model.train()\n    train_loss = 0\n    for imgs, masks in train_loader:\n        imgs, masks = imgs.to(device), masks.to(device)\n\n        preds = model(imgs)\n        loss = bce_dice_loss(preds, masks)\n\n        optimizer.zero_grad()\n        loss.backward()\n        optimizer.step()\n\n        train_loss += loss.item()\n    train_loss /= len(train_loader)\n\n    # ---- Validation ----\n    model.eval()\n    val_loss = 0\n    with torch.no_grad():\n        for imgs, masks in val_loader:\n            imgs, masks = imgs.to(device), masks.to(device)\n            preds = model(imgs)\n            loss = bce_dice_loss(preds, masks)\n            val_loss += loss.item()\n    val_loss /= len(val_loader)\n\n    print(f\"Epoch {epoch}: Train={train_loss:.4f} | Val={val_loss:.4f}\")\n\n    # ---- Save best model ----\n    if val_loss < best_val:\n        best_val = val_loss\n        torch.save(model.state_dict(), \"best_unet_plain.pth\")\n        print(\"✅ Saved best model\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-28T11:54:11.708808Z","iopub.execute_input":"2025-09-28T11:54:11.70921Z","iopub.status.idle":"2025-09-28T13:05:39.184258Z","shell.execute_reply.started":"2025-09-28T11:54:11.709181Z","shell.execute_reply":"2025-09-28T13:05:39.182388Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def iou_score(pred, target, threshold=0.5, smooth=1e-6):\n    \"\"\"\n    pred: raw model logits [B,1,H,W]\n    target: ground truth mask [B,1,H,W]\n    \"\"\"\n    pred = torch.sigmoid(pred)\n    pred = (pred > threshold).float()\n    target = (target > 0.5).float()\n\n    intersection = (pred * target).sum(dim=(1,2,3))\n    union = (pred + target - pred * target).sum(dim=(1,2,3))\n    iou = (intersection + smooth) / (union + smooth)\n    return iou.mean().item()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-28T13:10:17.092646Z","iopub.execute_input":"2025-09-28T13:10:17.093101Z","iopub.status.idle":"2025-09-28T13:10:17.101658Z","shell.execute_reply.started":"2025-09-28T13:10:17.09307Z","shell.execute_reply":"2025-09-28T13:10:17.100112Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def dice_score(pred, target, threshold=0.5, smooth=1e-6):\n    \"\"\"\n    pred: raw model logits [B,1,H,W]\n    target: ground truth mask [B,1,H,W]\n    \"\"\"\n    pred = torch.sigmoid(pred)\n    pred = (pred > threshold).float()\n    target = (target > 0.5).float()\n\n    intersection = (pred * target).sum(dim=(1,2,3))\n    dice = (2. * intersection + smooth) / (pred.sum(dim=(1,2,3)) + target.sum(dim=(1,2,3)) + smooth)\n    return dice.mean().item()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-28T13:11:19.505857Z","iopub.execute_input":"2025-09-28T13:11:19.50617Z","iopub.status.idle":"2025-09-28T13:11:19.513266Z","shell.execute_reply.started":"2025-09-28T13:11:19.50615Z","shell.execute_reply":"2025-09-28T13:11:19.511873Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from tqdm import tqdm\n\nbest_val = 1e9\nnum_epochs = 5   # increase later\nlatest_ckpt = None\n\n# store metrics\nhistory = {\n    \"train_loss\": [], \"val_loss\": [],\n    \"train_iou\": [], \"val_iou\": [],\n    \"train_dice\": [], \"val_dice\": []\n}\n\nfor epoch in range(1, num_epochs+1):\n    # ---- Training ----\n    model.train()\n    train_loss, train_iou, train_dice = 0, 0, 0\n    loop = tqdm(train_loader, desc=f\"Epoch {epoch}/{num_epochs} [Train]\", leave=False)\n    for imgs, masks in loop:\n        imgs, masks = imgs.to(device), masks.to(device)\n\n        preds = model(imgs)\n        loss = bce_dice_loss(preds, masks)\n\n        optimizer.zero_grad()\n        loss.backward()\n        optimizer.step()\n\n        train_loss += loss.item()\n        train_iou += iou_score(preds, masks)\n        train_dice += dice_score(preds, masks)\n\n        loop.set_postfix(loss=loss.item())\n    train_loss /= len(train_loader)\n    train_iou /= len(train_loader)\n    train_dice /= len(train_loader)\n\n    # ---- Validation ----\n    model.eval()\n    val_loss, val_iou, val_dice = 0, 0, 0\n    loop = tqdm(val_loader, desc=f\"Epoch {epoch}/{num_epochs} [Val]\", leave=False)\n    with torch.no_grad():\n        for imgs, masks in loop:\n            imgs, masks = imgs.to(device), masks.to(device)\n            preds = model(imgs)\n            loss = bce_dice_loss(preds, masks)\n\n            val_loss += loss.item()\n            val_iou += iou_score(preds, masks)\n            val_dice += dice_score(preds, masks)\n\n            loop.set_postfix(loss=loss.item())\n    val_loss /= len(val_loader)\n    val_iou /= len(val_loader)\n    val_dice /= len(val_loader)\n\n    # save metrics\n    history[\"train_loss\"].append(train_loss)\n    history[\"val_loss\"].append(val_loss)\n    history[\"train_iou\"].append(train_iou)\n    history[\"val_iou\"].append(val_iou)\n    history[\"train_dice\"].append(train_dice)\n    history[\"val_dice\"].append(val_dice)\n\n    print(f\"Epoch {epoch}: \"\n          f\"Train Loss={train_loss:.4f}, IoU={train_iou:.4f}, Dice={train_dice:.4f} | \"\n          f\"Val Loss={val_loss:.4f}, IoU={val_iou:.4f}, Dice={val_dice:.4f}\")\n\n    # ---- Save best model ----\n    if val_loss < best_val:\n        best_val = val_loss\n        torch.save(model.state_dict(), \"best_unet_plain.pth\")\n        print(\"✅ Saved best model\")\n\n    # ---- Save checkpoint for this epoch ----\n    latest_ckpt = f\"unet_epoch{epoch}.pth\"\n    torch.save(model.state_dict(), latest_ckpt)\n    print(f\"💾 Saved checkpoint: {latest_ckpt}\")\n\nprint(\"\\nTraining complete.\")\nprint(\"Latest checkpoint available:\", latest_ckpt)\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# choose which model to load\nuse_best = False   # set True for best model, False for latest checkpoint\n\nif use_best and os.path.exists(\"best_unet_plain.pth\"):\n    ckpt_file = \"best_unet_plain.pth\"\nelse:\n    # find latest checkpoint\n    ckpts = [f for f in os.listdir(\"/kaggle/working\") if f.startswith(\"unet_epoch\")]\n    ckpts = sorted(ckpts, key=lambda x: int(x.replace(\"unet_epoch\",\"\").replace(\".pth\",\"\")))\n    ckpt_file = ckpts[-1] if ckpts else \"best_unet_plain.pth\"\n\nprint(\"🔄 Loading:\", ckpt_file)\nmodel.load_state_dict(torch.load(ckpt_file, map_location=device))\nmodel.eval()\n\n# visualize predictions\nval_samples = val_imgs[:3]  # first 3 validation images\nfor sid in val_samples:\n    img_path = os.path.join(img_dir, sid)\n    mask_path = os.path.join(mask_dir, sid)\n\n    img = Image.open(img_path).convert(\"RGB\").resize((256,256))\n    mask = Image.open(mask_path).convert(\"L\").resize((256,256))\n\n    x = np.array(img) / 255.0\n    x = torch.tensor(x.transpose(2,0,1)).unsqueeze(0).float().to(device)\n\n    with torch.no_grad():\n        pred = torch.sigmoid(model(x))\n    pred_mask = (pred[0,0].cpu().numpy() > 0.5).astype(np.uint8)\n\n    plt.figure(figsize=(10,3))\n    plt.subplot(1,3,1); plt.imshow(img); plt.axis(\"off\"); plt.title(\"Image\")\n    plt.subplot(1,3,2); plt.imshow(mask, cmap=\"gray\"); plt.axis(\"off\"); plt.title(\"Ground Truth\")\n    plt.subplot(1,3,3); plt.imshow(pred_mask, cmap=\"gray\"); plt.axis(\"off\"); plt.title(f\"Pred ({ckpt_file})\")\n    plt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-28T13:06:08.286371Z","iopub.execute_input":"2025-09-28T13:06:08.28673Z","iopub.status.idle":"2025-09-28T13:06:11.78626Z","shell.execute_reply.started":"2025-09-28T13:06:08.286695Z","shell.execute_reply":"2025-09-28T13:06:11.785247Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import matplotlib.pyplot as plt\n\nepochs = range(1, len(history[\"train_loss\"])+1)\n\nplt.figure(figsize=(14,4))\n\n# Loss\nplt.subplot(1,3,1)\nplt.plot(epochs, history[\"train_loss\"], label=\"Train Loss\")\nplt.plot(epochs, history[\"val_loss\"], label=\"Val Loss\")\nplt.xlabel(\"Epoch\"); plt.ylabel(\"Loss\"); plt.title(\"Loss Curve\")\nplt.legend()\n\n# IoU\nplt.subplot(1,3,2)\nplt.plot(epochs, history[\"train_iou\"], label=\"Train IoU\")\nplt.plot(epochs, history[\"val_iou\"], label=\"Val IoU\")\nplt.xlabel(\"Epoch\"); plt.ylabel(\"IoU\"); plt.title(\"IoU Curve\")\nplt.legend()\n\n# Dice\nplt.subplot(1,3,3)\nplt.plot(epochs, history[\"train_dice\"], label=\"Train Dice\")\nplt.plot(epochs, history[\"val_dice\"], label=\"Val Dice\")\nplt.xlabel(\"Epoch\"); plt.ylabel(\"Dice\"); plt.title(\"Dice Curve\")\nplt.legend()\n\nplt.show()\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import cv2, json, random\nfrom tqdm import tqdm\n\ndef masks_to_coco_split(img_dir, mask_dir, out_dir=\"/kaggle/working/coco_dataset\", val_ratio=0.2, seed=42):\n    os.makedirs(out_dir, exist_ok=True)\n\n    img_list = sorted(os.listdir(img_dir))\n    random.seed(seed)\n    random.shuffle(img_list)\n\n    split_idx = int(len(img_list) * (1 - val_ratio))\n    train_imgs = img_list[:split_idx]\n    val_imgs = img_list[split_idx:]\n\n    def build_coco(subset_imgs, subset_name):\n        images, annotations = [], []\n        categories = [{\"id\":1, \"name\":\"nucleus\"}]\n        ann_id = 1\n\n        for i, img_name in enumerate(tqdm(subset_imgs, desc=f\"Processing {subset_name}\")):\n            img_path = os.path.join(img_dir, img_name)\n            mask_path = os.path.join(mask_dir, img_name)\n\n            # read image to get size\n            img = cv2.imread(img_path)\n            h, w = img.shape[:2]\n\n            # image entry\n            images.append({\n                \"id\": i+1,\n                \"file_name\": img_name,\n                \"width\": w,\n                \"height\": h\n            })\n\n            # read mask\n            mask = cv2.imread(mask_path, 0)\n            contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)\n\n            for c in contours:\n                x,y,wc,hc = cv2.boundingRect(c)\n                area = wc * hc\n                if area < 10:  # skip tiny noise\n                    continue\n\n                annotations.append({\n                    \"id\": ann_id,\n                    \"image_id\": i+1,\n                    \"category_id\": 1,\n                    \"bbox\": [int(x), int(y), int(wc), int(hc)],\n                    \"area\": int(area),\n                    \"iscrowd\": 0\n                })\n                ann_id += 1\n\n        coco = {\"images\": images, \"annotations\": annotations, \"categories\": categories}\n        out_json = os.path.join(out_dir, f\"{subset_name}.json\")\n        with open(out_json, \"w\") as f:\n            json.dump(coco, f)\n        print(f\"✅ {subset_name}.json saved with {len(images)} images, {len(annotations)} annotations\")\n        return out_json\n\n    train_json = build_coco(train_imgs, \"train\")\n    val_json   = build_coco(val_imgs, \"val\")\n\n    return train_json, val_json\n\n# Run conversion\ntrain_json, val_json = masks_to_coco_split(img_dir, mask_dir)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-28T13:16:37.333051Z","iopub.execute_input":"2025-09-28T13:16:37.333799Z","iopub.status.idle":"2025-09-28T13:16:39.61511Z","shell.execute_reply.started":"2025-09-28T13:16:37.333765Z","shell.execute_reply":"2025-09-28T13:16:39.613764Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!cp -r /kaggle/input/nanodet-main/nanodet-main /kaggle/working/\n%cd /kaggle/working/nanodet-main\n!pip install -r requirements.txt\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-28T13:25:58.587729Z","iopub.execute_input":"2025-09-28T13:25:58.58817Z","iopub.status.idle":"2025-09-28T13:29:21.391248Z","shell.execute_reply.started":"2025-09-28T13:25:58.588137Z","shell.execute_reply":"2025-09-28T13:29:21.389619Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\n\n# make sure configs directory exists\nos.makedirs(\"configs\", exist_ok=True)\n\nconfig_text = \"\"\"\nmodel:\n  arch:\n    name: nanodet-plus-m\n  num_classes: 1   # only \"nucleus\"\n\ndata:\n  train:\n    name: coco\n    img_path: /kaggle/working/dataset/images\n    ann_path: /kaggle/working/coco_dataset/train.json\n    input_size: [320,320]\n  val:\n    name: coco\n    img_path: /kaggle/working/dataset/images\n    ann_path: /kaggle/working/coco_dataset/val.json\n    input_size: [320,320]\n\ndevice:\n  gpu_ids: [0]\n  batchsize_per_gpu: 16\n\nschedule:\n  optimizer:\n    name: AdamW\n    lr: 0.0001\n  total_epochs: 20   # start with 20, increase later\n\"\"\"\n\nwith open(\"configs/nuclei.yml\", \"w\") as f:\n    f.write(config_text)\n\nprint(\"✅ nuclei.yml config saved at configs/nuclei.yml\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-28T13:33:49.634041Z","iopub.execute_input":"2025-09-28T13:33:49.635012Z","iopub.status.idle":"2025-09-28T13:33:49.645415Z","shell.execute_reply.started":"2025-09-28T13:33:49.634971Z","shell.execute_reply":"2025-09-28T13:33:49.643818Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Fix for torch._six removal in PyTorch >=1.9\ncollate_file = \"/kaggle/working/nanodet-main/nanodet/data/collate.py\"\n\nwith open(collate_file, \"r\") as f:\n    code = f.read()\n\n# Replace old import\ncode = code.replace(\"from torch._six import string_classes\", \"string_classes = str\")\n\nwith open(collate_file, \"w\") as f:\n    f.write(code)\n\nprint(\"✅ Patched collate.py to fix torch._six issue\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-28T13:38:22.127635Z","iopub.execute_input":"2025-09-28T13:38:22.12804Z","iopub.status.idle":"2025-09-28T13:38:22.13863Z","shell.execute_reply.started":"2025-09-28T13:38:22.128009Z","shell.execute_reply":"2025-09-28T13:38:22.137512Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import sys, os\n\n# make sure NanoDet is in the Python path\nrepo_path = \"/kaggle/working/nanodet-main\"\nsys.path.append(repo_path)\nos.environ[\"PYTHONPATH\"] = repo_path + \":\" + os.environ.get(\"PYTHONPATH\", \"\")\n\n# now train with your config\n!python /kaggle/working/nanodet-main/tools/train.py /kaggle/working/nanodet-main/configs/nuclei.yml\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-28T14:21:03.839519Z","iopub.execute_input":"2025-09-28T14:21:03.839997Z","iopub.status.idle":"2025-09-28T14:21:11.995147Z","shell.execute_reply.started":"2025-09-28T14:21:03.839968Z","shell.execute_reply":"2025-09-28T14:21:11.992968Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}