{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.11","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceId":36363,"databundleVersionId":4050810,"sourceType":"competition"}],"dockerImageVersionId":31011,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"!pip install -q --no-deps \\\n    iterative-stratification \\\n    kornia==0.7.2 \\\n    albumentations==1.4.3 \\\n    pydicom pylibjpeg pylibjpeg-libjpeg pylibjpeg-openjpeg \\\n    nibabel tqdm","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-05-06T04:25:12.858389Z","iopub.execute_input":"2025-05-06T04:25:12.85875Z","iopub.status.idle":"2025-05-06T04:25:18.614544Z","shell.execute_reply.started":"2025-05-06T04:25:12.858713Z","shell.execute_reply":"2025-05-06T04:25:18.613782Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import cv2\nimport albumentations as A\n\n# Hàm load DICOM\ndef load_dcm(path):\n    import pydicom\n    ds = pydicom.dcmread(str(path))\n    img = ds.pixel_array.astype(np.float32)\n    img = (img - img.min()) / (img.max() - img.min() + 1e-5)  # Chuẩn hóa\n    return img\n\n# Tạo class Dataset cho phân đoạn\nclass SegSliceDS(Dataset):\n    def __init__(self, uids, transform=None):\n        self.samples = []\n        for uid in tqdm(uids, desc=\"Build seg-slice list\"):\n            nii_path = MASK_DIR / f\"{uid}.nii\"\n            if not nii_path.exists():\n                continue\n            mask = nib.load(str(nii_path)).get_fdata().astype(np.uint8)  # sagittal\n            mask = np.transpose(mask, (2, 1, 0))  # ≈ align axial\n            for z in range(mask.shape[0]):\n                if mask[z].max() == 0: continue  # Bỏ qua những ảnh không có mask\n                dcm_path = IMG_DIR / uid / f\"{z+1}.dcm\"\n                if not dcm_path.exists(): continue\n                self.samples.append((dcm_path, mask[z]))\n        self.transform = transform\n\n    def __len__(self):\n        return len(self.samples)\n\n    def __getitem__(self, idx):\n        dcm_path, m = self.samples[idx]\n        img = load_dcm(dcm_path)\n        m = cv2.resize(m, (IMG_SIZE, IMG_SIZE), interpolation=cv2.INTER_NEAREST)\n        m[m > 7] = 0  # Lọc các giá trị không hợp lệ\n        img = cv2.resize(img, (IMG_SIZE, IMG_SIZE))\n\n        if self.transform:\n            aug = self.transform(image=img, mask=m)\n            img, m = aug[\"image\"], aug[\"mask\"]\n\n        img = torch.tensor(img).unsqueeze(0).float()  # [1, H, W]\n        m = torch.tensor(m).long()  # CrossEntropyLoss cho segmentation\n        return img, m\n\n# Data augmentation cho segmentation\naug_seg = A.Compose([A.HorizontalFlip(p=0.5)], additional_targets={'mask': 'mask'})\n\n# Tạo dataset phân đoạn và DataLoader cho training và validation\ntrain_seg_ds = SegSliceDS(train_uids, transform=aug_seg)\nval_seg_ds = SegSliceDS(val_uids, transform=None)\n\n# Tạo DataLoader\ntrain_seg_ld = DataLoader(train_seg_ds, batch_size=BS_SEG, shuffle=True, num_workers=4, pin_memory=True)\nval_seg_ld = DataLoader(val_seg_ds, batch_size=BS_SEG, shuffle=False, num_workers=4, pin_memory=True)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-06T04:25:23.807491Z","iopub.status.idle":"2025-05-06T04:25:23.807945Z","shell.execute_reply.started":"2025-05-06T04:25:23.807767Z","shell.execute_reply":"2025-05-06T04:25:23.807783Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\nimport torch.nn as nn\nimport torch.nn.functional as F\n\nclass UNetPlusPlus(nn.Module):\n    def __init__(self, n_cls=8):\n        super(UNetPlusPlus, self).__init__()\n\n        self.d1 = self.conv_block(1, 32)\n        self.d2 = self.conv_block(32, 64)\n        self.d3 = self.conv_block(64, 128)\n        self.d4 = self.conv_block(128, 256)\n        self.d5 = self.conv_block(256, 512)\n        \n        self.up1 = self.upconv_block(32 + 64, 64)\n        self.up2 = self.upconv_block(64 + 128, 128)\n        self.up3 = self.upconv_block(128 + 256, 256)\n        self.up4 = self.upconv_block(256 + 512, 512)\n        \n        self.head = nn.Conv2d(512, n_cls, kernel_size=1)\n    \n    def conv_block(self, in_channels, out_channels):\n        return nn.Sequential(\n            nn.Conv2d(in_channels, out_channels, kernel_size=3, padding=1),\n            nn.ReLU(inplace=True),\n            nn.Conv2d(out_channels, out_channels, kernel_size=3, padding=1),\n            nn.ReLU(inplace=True)\n        )\n    \n    def upconv_block(self, in_channels, out_channels):\n        return nn.Sequential(\n            nn.ConvTranspose2d(in_channels, out_channels, kernel_size=2, stride=2),\n            nn.ReLU(inplace=True)\n        )\n\n    def forward(self, x):\n        c1 = self.d1(x)\n        c2 = self.d2(c1)\n        c3 = self.d3(c2)\n        c4 = self.d4(c3)\n        c5 = self.d5(c4)\n        \n        u1 = self.up1(torch.cat([c1, c2], dim=1))\n        u2 = self.up2(torch.cat([c2, c3], dim=1))\n        u3 = self.up3(torch.cat([c3, c4], dim=1))\n        u4 = self.up4(torch.cat([c4, c5], dim=1))\n        \n        return self.head(u4)\n\n# Khởi tạo mô hình U-Net++\nunet_plus = UNetPlusPlus().to(DEVICE)\noptimizer = torch.optim.Adam(unet_plus.parameters(), lr=1e-3)\ncriterion = nn.CrossEntropyLoss()\n\n# Hàm huấn luyện và đánh giá mỗi epoch\ndef run_seg_epoch(loader, training=True):\n    unet_plus.train(training)\n    total_loss = 0.0\n    for img, mask in loader:\n        img, mask = img.to(DEVICE), mask.to(DEVICE)\n        pred = unet_plus(img)\n        loss = criterion(pred, mask)\n        \n        if training:\n            optimizer.zero_grad()\n            loss.backward()\n            optimizer.step()\n        \n        total_loss += loss.item() * img.size(0)\n    \n    return total_loss / len(loader.dataset)\n\n# Huấn luyện mô hình phân đoạn\nfor ep in range(1, EPOCH_SEG + 1):\n    train_loss = run_seg_epoch(train_seg_ld, True)\n    val_loss = run_seg_epoch(val_seg_ld, False)\n    print(f\"Epoch {ep}/{EPOCH_SEG}  Train Loss: {train_loss:.4f}  Val Loss: {val_loss:.4f}\")\n    # Lưu mô hình tốt nhất\n    if ep == 1 or val_loss < best_val_loss:\n        best_val_loss = val_loss\n        torch.save(unet_plus.state_dict(), \"/kaggle/working/unet_plus_best.pth\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-06T04:25:23.808576Z","iopub.status.idle":"2025-05-06T04:25:23.80884Z","shell.execute_reply.started":"2025-05-06T04:25:23.808724Z","shell.execute_reply":"2025-05-06T04:25:23.808734Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torchvision.models as models\nfrom torch import nn, optim\n\n# Khởi tạo ResNet18\nresnet18 = models.resnet18(weights=None)\nresnet18.conv1 = nn.Conv2d(1, 64, 7, 2, 3, bias=False)\nresnet18.fc = nn.Linear(resnet18.fc.in_features, 7)\nresnet18 = resnet18.to(DEVICE)\n\n# Loss function và optimizer\ncriterion_cls = nn.BCEWithLogitsLoss()\noptimizer_cls = optim.Adam(resnet18.parameters(), lr=1e-4)\n\n# Hàm huấn luyện phân loại\ndef run_cls_epoch(dataloader, training=True):\n    resnet18.train(training)\n    total_loss = 0.0\n    correct = 0\n    total_labels = 0\n    for imgs, labels in tqdm(dataloader):\n        imgs, labels = imgs.to(DEVICE), labels.to(DEVICE)\n        logits = resnet18(imgs)\n        loss = criterion_cls(logits, labels)\n        \n        if training:\n            optimizer_cls.zero_grad()\n            loss.backward()\n            optimizer_cls.step()\n        \n        total_loss += loss.item() * imgs.size(0)\n        preds = torch.sigmoid(logits) > 0.5\n        correct += (preds == labels).sum().item()\n        total_labels += labels.numel()\n\n    avg_loss = total_loss / len(dataloader.dataset)\n    accuracy = correct / total_labels\n    return avg_loss, accuracy\n\n# Huấn luyện phân loại\nfor ep in range(1, EPOCH_CLS + 1):\n    train_loss, train_acc = run_cls_epoch(train_cls_ld, True)\n    val_loss, val_acc = run_cls_epoch(val_cls_ld, False)\n    print(f\"Epoch {ep}/{EPOCH_CLS}  Train Loss: {train_loss:.4f}  Train Acc: {train_acc:.4f}  Val Loss: {val_loss:.4f}  Val Acc: {val_acc:.4f}\")\n    if ep == 1 or val_loss < best_val_loss_cls:\n        best_val_loss_cls = val_loss\n        torch.save(resnet18.state_dict(), \"/kaggle/working/resnet18_best.pth\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-06T04:25:23.809778Z","iopub.status.idle":"2025-05-06T04:25:23.809996Z","shell.execute_reply.started":"2025-05-06T04:25:23.809893Z","shell.execute_reply":"2025-05-06T04:25:23.809903Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Đánh giá mô hình phân đoạn\neval_seg(val_seg_ld, unet_plus, DEVICE)\n\n# Đánh giá mô hình phân loại\neval_cls(val_cls_ld, resnet18, DEVICE)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-06T04:25:23.811498Z","iopub.status.idle":"2025-05-06T04:25:23.811799Z","shell.execute_reply.started":"2025-05-06T04:25:23.811651Z","shell.execute_reply":"2025-05-06T04:25:23.811665Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Tính toán các metric cho phân loại và phân đoạn\nmicro_auc = roc_auc_score(y_true, y_prob, average=\"micro\")\nmacro_auc = roc_auc_score(y_true, y_prob, average=\"macro\")\nmicro_f1 = f1_score(y_true, y_pred, average=\"micro\")\nmacro_f1 = f1_score(y_true, y_pred, average=\"macro\")\nprint(f\"MICRO AUC={micro_auc:.3f} F1={micro_f1:.3f}\")\nprint(f\"MACRO AUC={macro_auc:.3f} F1={macro_f1:.3f}\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-06T04:25:23.812983Z","iopub.status.idle":"2025-05-06T04:25:23.813277Z","shell.execute_reply.started":"2025-05-06T04:25:23.813109Z","shell.execute_reply":"2025-05-06T04:25:23.813124Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Đánh giá phân loại\neval_cls(val_cls_ld, resnet, DEVICE)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-06T04:25:23.814111Z","iopub.status.idle":"2025-05-06T04:25:23.814347Z","shell.execute_reply.started":"2025-05-06T04:25:23.814239Z","shell.execute_reply":"2025-05-06T04:25:23.81425Z"}},"outputs":[],"execution_count":null}]}