{"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":8089,"databundleVersionId":44321,"sourceType":"competition"}],"dockerImageVersionId":31193,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import os, zipfile\n\nzip_path = \"/kaggle/input/data-science-bowl-2018/stage1_train.zip\"\nextract_path = \"/kaggle/working/stage1_train\"\n\n# Nếu đã giải nén rồi thì không giải nén lại nữa\nif not os.path.exists(extract_path):\n    with zipfile.ZipFile(zip_path, 'r') as zip_ref:\n        zip_ref.extractall(extract_path)\n    print(\"✅ Đã giải nén xong!\")\nelse:\n    print(\"📂 Thư mục đã tồn tại, bỏ qua bước giải nén.\")\n\nprint(\"Số folder train:\", len(os.listdir(extract_path)))\nprint(\"Ví dụ vài ID:\", os.listdir(extract_path)[:5])\n","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-11-17T07:19:05.449781Z","iopub.execute_input":"2025-11-17T07:19:05.450051Z","iopub.status.idle":"2025-11-17T07:19:09.643869Z","shell.execute_reply.started":"2025-11-17T07:19:05.450022Z","shell.execute_reply":"2025-11-17T07:19:09.643146Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nos.listdir(\"/kaggle/working\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-17T07:19:09.645195Z","iopub.execute_input":"2025-11-17T07:19:09.645761Z","iopub.status.idle":"2025-11-17T07:19:09.651104Z","shell.execute_reply.started":"2025-11-17T07:19:09.645739Z","shell.execute_reply":"2025-11-17T07:19:09.650510Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\n\nimport cv2\nimport numpy as np\nimport torch\nimport torch.nn as nn\nfrom torch.utils.data import Dataset, DataLoader, random_split\nimport albumentations as A\nfrom albumentations.pytorch import ToTensorV2\n\ndevice = \"cuda\" if torch.cuda.is_available() else \"cpu\"\ndevice\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-17T07:19:09.651786Z","iopub.execute_input":"2025-11-17T07:19:09.652024Z","iopub.status.idle":"2025-11-17T07:19:14.838627Z","shell.execute_reply.started":"2025-11-17T07:19:09.652002Z","shell.execute_reply":"2025-11-17T07:19:14.837827Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"IMG_SIZE = 256\n\nclass NucleiDataset(Dataset):\n    def __init__(self, root, transform=None):\n        self.root = root\n        self.ids = os.listdir(root)\n        self.transform = transform\n\n    def __len__(self):\n        return len(self.ids)\n\n    def __getitem__(self, idx):\n        img_id = self.ids[idx]\n        base = os.path.join(self.root, img_id)\n\n        # 1. Đọc ảnh RGB\n        img_dir = os.path.join(base, \"images\")\n        img_name = os.listdir(img_dir)[0]\n        img_path = os.path.join(img_dir, img_name)\n        image = cv2.imread(img_path)\n        image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)\n\n        # 2. Gộp tất cả mask thành 1 mask nhị phân\n        mask = np.zeros((image.shape[0], image.shape[1]), dtype=np.uint8)\n        mask_dir = os.path.join(base, \"masks\")\n        if os.path.exists(mask_dir):\n            for m_name in os.listdir(mask_dir):\n                m_path = os.path.join(mask_dir, m_name)\n                m = cv2.imread(m_path, cv2.IMREAD_GRAYSCALE)\n                mask = np.maximum(mask, m)\n\n        mask = (mask > 0).astype(np.uint8)  # 0/1\n\n        # 3. Resize\n        image = cv2.resize(image, (IMG_SIZE, IMG_SIZE))\n        mask  = cv2.resize(mask, (IMG_SIZE, IMG_SIZE), interpolation=cv2.INTER_NEAREST)\n\n        # 4. Áp dụng transform (augment + normalize)\n        if self.transform:\n            aug = self.transform(image=image, mask=mask)\n            image, mask = aug[\"image\"], aug[\"mask\"]\n        else:\n            image = torch.tensor(image.transpose(2,0,1), dtype=torch.float32) / 255.\n            mask = torch.tensor(mask, dtype=torch.float32).unsqueeze(0)\n\n        return image, mask\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-17T07:19:14.840341Z","iopub.execute_input":"2025-11-17T07:19:14.840687Z","iopub.status.idle":"2025-11-17T07:19:14.848410Z","shell.execute_reply.started":"2025-11-17T07:19:14.840668Z","shell.execute_reply":"2025-11-17T07:19:14.847835Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\ntrain_tf = A.Compose([\n    A.HorizontalFlip(p=0.5),\n    A.VerticalFlip(p=0.5),\n    A.RandomRotate90(p=0.5),\n    A.Normalize(mean=(0.5,0.5,0.5), std=(0.5,0.5,0.5)),\n    ToTensorV2()\n])\n\nval_tf = A.Compose([\n    A.Normalize(mean=(0.5,0.5,0.5), std=(0.5,0.5,0.5)),\n    ToTensorV2()\n])\n\nDATA_ROOT = \"/kaggle/working/stage1_train\"\n\nfull_ds = NucleiDataset(DATA_ROOT)  # tạm chưa gán transform\nval_size = int(0.2 * len(full_ds))\ntrain_size = len(full_ds) - val_size\n\ntrain_ds, val_ds = random_split(full_ds, [train_size, val_size])\n\n# gán transform cho 2 tập\ntrain_ds.dataset.transform = train_tf\nval_ds.dataset.transform   = val_tf\n\ntrain_loader = DataLoader(train_ds, batch_size=4, shuffle=True)\nval_loader   = DataLoader(val_ds, batch_size=4, shuffle=False)\n\nprint(\"Số ảnh train:\", len(train_ds))\nprint(\"Số ảnh val:\", len(val_ds))\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-17T07:19:14.849100Z","iopub.execute_input":"2025-11-17T07:19:14.849348Z","iopub.status.idle":"2025-11-17T07:19:14.885157Z","shell.execute_reply.started":"2025-11-17T07:19:14.849332Z","shell.execute_reply":"2025-11-17T07:19:14.884643Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import matplotlib.pyplot as plt\n\nimg, mask = train_ds[0]   # img: tensor (3,H,W), mask: (H,W) nếu dùng ToTensorV2\n\n# chuyển ảnh về dạng hiển thị được\nif isinstance(img, torch.Tensor):\n    img_show = img.permute(1,2,0).cpu().numpy()\n    img_show = (img_show * 0.5 + 0.5).clip(0,1)  # nếu dùng normalize 0.5\nelse:\n    img_show = img\n\nif isinstance(mask, torch.Tensor):\n    mask_show = mask.cpu().numpy()\n    if mask_show.ndim == 3:\n        mask_show = mask_show[0]\n\nplt.figure(figsize=(8,4))\nplt.subplot(1,2,1); plt.imshow(img_show); plt.title(\"Ảnh gốc\"); plt.axis(\"off\")\nplt.subplot(1,2,2); plt.imshow(mask_show, cmap=\"gray\"); plt.title(\"Mask gộp\"); plt.axis(\"off\")\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-17T07:19:14.885795Z","iopub.execute_input":"2025-11-17T07:19:14.886039Z","iopub.status.idle":"2025-11-17T07:19:15.182674Z","shell.execute_reply.started":"2025-11-17T07:19:14.886014Z","shell.execute_reply":"2025-11-17T07:19:15.181941Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\nimport torch.nn as nn\nimport torch.nn.functional as F\n\nclass DoubleConv(nn.Module):\n    def __init__(self, in_ch, out_ch):\n        super().__init__()\n        self.conv = nn.Sequential(\n            nn.Conv2d(in_ch, out_ch, 3, padding=1),\n            nn.ReLU(inplace=True),\n            nn.Conv2d(out_ch, out_ch, 3, padding=1),\n            nn.ReLU(inplace=True),\n        )\n\n    def forward(self, x):\n        return self.conv(x)\n\nclass UNet(nn.Module):\n    def __init__(self, n_channels=3, n_classes=1):\n        super().__init__()\n        self.down1 = DoubleConv(n_channels, 64)\n        self.down2 = DoubleConv(64, 128)\n        self.down3 = DoubleConv(128, 256)\n        self.down4 = DoubleConv(256, 512)\n\n        self.pool = nn.MaxPool2d(2)\n\n        self.up1 = nn.ConvTranspose2d(512, 256, 2, stride=2)\n        self.conv1 = DoubleConv(512, 256)\n\n        self.up2 = nn.ConvTranspose2d(256, 128, 2, stride=2)\n        self.conv2 = DoubleConv(256, 128)\n\n        self.up3 = nn.ConvTranspose2d(128, 64, 2, stride=2)\n        self.conv3 = DoubleConv(128, 64)\n\n        self.outc = nn.Conv2d(64, n_classes, 1)\n\n    def forward(self, x):\n        x1 = self.down1(x)\n        x2 = self.down2(self.pool(x1))\n        x3 = self.down3(self.pool(x2))\n        x4 = self.down4(self.pool(x3))\n\n        u1 = self.up1(x4)\n        u1 = torch.cat([u1, x3], dim=1)\n        u1 = self.conv1(u1)\n\n        u2 = self.up2(u1)\n        u2 = torch.cat([u2, x2], dim=1)\n        u2 = self.conv2(u2)\n\n        u3 = self.up3(u2)\n        u3 = torch.cat([u3, x1], dim=1)\n        u3 = self.conv3(u3)\n\n        return self.outc(u3)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-17T07:19:15.183521Z","iopub.execute_input":"2025-11-17T07:19:15.183808Z","iopub.status.idle":"2025-11-17T07:19:15.192009Z","shell.execute_reply.started":"2025-11-17T07:19:15.183791Z","shell.execute_reply":"2025-11-17T07:19:15.191486Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#loss + Dice\nbce = nn.BCEWithLogitsLoss()\n\ndef dice_loss(pred, target, smooth=1.):\n    pred = torch.sigmoid(pred)\n    pred = pred.view(pred.size(0), -1)\n    target = target.view(target.size(0), -1)\n    inter = (pred * target).sum()\n    return 1 - (2*inter + smooth)/(pred.sum()+target.sum()+smooth)\n\ndef dice_coef(pred, target, smooth=1e-7):\n    pred = torch.sigmoid(pred)\n    pred = (pred > 0.5).float()\n    pred = pred.view(pred.size(0), -1)\n    target = target.view(target.size(0), -1)\n    inter = (pred * target).sum(dim=1)\n    return ((2*inter + smooth) / (pred.sum(dim=1) + target.sum(dim=1) + smooth)).mean()\n\ndef iou_score(pred, target, smooth=1e-7):\n    pred = torch.sigmoid(pred)\n    pred = (pred > 0.5).float()\n    pred = pred.view(pred.size(0), -1)\n    target = target.view(target.size(0), -1)\n    inter = (pred * target).sum(dim=1)\n    union = pred.sum(dim=1) + target.sum(dim=1) - inter\n    return ((inter + smooth) / (union + smooth)).mean()\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-17T07:19:15.192674Z","iopub.execute_input":"2025-11-17T07:19:15.192875Z","iopub.status.idle":"2025-11-17T07:19:15.207958Z","shell.execute_reply.started":"2025-11-17T07:19:15.192851Z","shell.execute_reply":"2025-11-17T07:19:15.207283Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def train_one_model(model, train_loader, val_loader, epochs=5, lr=1e-4):\n    model = model.to(device)\n    opt = torch.optim.Adam(model.parameters(), lr=lr)\n\n    for ep in range(epochs):\n        model.train()\n        train_loss = 0.0\n\n        for imgs, masks in train_loader:\n            imgs  = imgs.to(device)\n            masks = masks.to(device)\n            if masks.ndim == 3:\n                masks = masks.unsqueeze(1)\n            masks = masks.float()\n\n            opt.zero_grad()\n            logits = model(imgs)\n            loss   = bce(logits, masks) + dice_loss(logits, masks)\n            loss.backward()\n            opt.step()\n            train_loss += loss.item()\n\n        # 🔹 tính Dice & IoU trên val\n        model.eval()\n        val_dice, val_iou, n = 0.0, 0.0, 0\n        with torch.no_grad():\n            for imgs, masks in val_loader:\n                imgs  = imgs.to(device)\n                masks = masks.to(device)\n                if masks.ndim == 3:\n                    masks = masks.unsqueeze(1)\n                masks = masks.float()\n                logits = model(imgs)\n                val_dice += dice_coef(logits, masks).item() * imgs.size(0)\n                val_iou  += iou_score(logits, masks).item() * imgs.size(0)\n                n += imgs.size(0)\n\n        val_dice /= n\n        val_iou  /= n\n\n        print(f\"Epoch {ep+1}/{epochs} | Loss: {train_loss/len(train_loader):.4f} \"\n              f\"| Val Dice: {val_dice:.4f} | Val IoU: {val_iou:.4f}\")\n\n    return model\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-17T07:19:15.208732Z","iopub.execute_input":"2025-11-17T07:19:15.208975Z","iopub.status.idle":"2025-11-17T07:19:15.222466Z","shell.execute_reply.started":"2025-11-17T07:19:15.208953Z","shell.execute_reply":"2025-11-17T07:19:15.221759Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"unet = UNet()\nunet = train_one_model(unet, train_loader, val_loader, epochs=10)\n\ndef show_pred(model, ds, idx=0):\n    model.eval()\n    img, m = ds[idx]\n    img_t = img.unsqueeze(0).to(device)\n\n    with torch.no_grad():\n        pred = torch.sigmoid(model(img_t))[0,0].cpu().numpy()\n\n    pred_bin = (pred > 0.5).astype(\"uint8\")\n\n    img_np = img.permute(1,2,0).cpu().numpy()\n    img_np = (img_np * 0.5 + 0.5).clip(0,1)\n\n    plt.figure(figsize=(12,4))\n    plt.subplot(1,3,1); plt.imshow(img_np); plt.title(\"Original\"); plt.axis(\"off\")\n    plt.subplot(1,3,2); plt.imshow(m.squeeze().cpu(), cmap=\"gray\"); plt.title(\"GT Mask\"); plt.axis(\"off\")\n    plt.subplot(1,3,3)\n    plt.imshow(img_np)\n    plt.imshow(pred_bin, cmap=\"jet\", alpha=0.4)\n    plt.title(\"Prediction\"); plt.axis(\"off\")\n    plt.show()\n\nshow_pred(unet, val_ds, 0)\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-17T07:19:15.223976Z","iopub.execute_input":"2025-11-17T07:19:15.224305Z"}},"outputs":[],"execution_count":null}]}