{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.12.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[],"dockerImageVersionId":28755,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import os\nimport time\nimport numpy as np\nimport cv2\nimport torch\nimport torch.nn as nn\nfrom torch.utils.data import DataLoader\nfrom torchvision import datasets, transforms\nfrom sklearn.metrics import cohen_kappa_score, classification_report, confusion_matrix\nimport timm\nfrom PIL import Image\nimport matplotlib.pyplot as plt\nimport seaborn as sns\n\ndevice = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\nprint(\"Device:\", device)\n\nbase_path = '/kaggle/input/datasets/harsha1289/combined-dr-dataset-aptosidridmessidoreyepacs'\nlabels_map = {0: 'No DR', 1: 'Mild', 2: 'Moderate', 3: 'Severe', 4: 'Proliferative DR'}","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-03T01:22:42.837531Z","iopub.execute_input":"2026-09-03T01:22:42.838387Z","iopub.status.idle":"2026-09-03T01:22:42.845311Z","shell.execute_reply.started":"2026-09-03T01:22:42.838347Z","shell.execute_reply":"2026-09-03T01:22:42.844288Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"img_size = 224\n\ntrain_transform = transforms.Compose([\n    transforms.Resize((img_size, img_size)),\n    transforms.RandomHorizontalFlip(),\n    transforms.RandomRotation(20),\n    transforms.ToTensor(),\n    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n])\n\neval_transform = transforms.Compose([\n    transforms.Resize((img_size, img_size)),\n    transforms.ToTensor(),\n    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n])\n\ntrain_dataset = datasets.ImageFolder(f'{base_path}/train', transform=train_transform)\nval_dataset = datasets.ImageFolder(f'{base_path}/val', transform=eval_transform)\ntest_dataset = datasets.ImageFolder(f'{base_path}/test', transform=eval_transform)\n\nprint(\"Kelas terdeteksi:\", train_dataset.classes)\nprint(\"Jumlah data - Train:\", len(train_dataset), \"| Val:\", len(val_dataset), \"| Test:\", len(test_dataset))\n\ntrain_loader = DataLoader(train_dataset, batch_size=32, shuffle=True, num_workers=0)\nval_loader = DataLoader(val_dataset, batch_size=32, shuffle=False, num_workers=0)\ntest_loader = DataLoader(test_dataset, batch_size=32, shuffle=False, num_workers=0)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-03T01:22:42.846603Z","iopub.execute_input":"2026-09-03T01:22:42.846964Z","iopub.status.idle":"2026-09-03T01:22:57.842198Z","shell.execute_reply.started":"2026-09-03T01:22:42.846940Z","shell.execute_reply":"2026-09-03T01:22:57.841357Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model = timm.create_model('efficientnet_b3', pretrained=True, num_classes=5)\nmodel = model.to(device)\n\ncriterion = nn.CrossEntropyLoss()\noptimizer = torch.optim.Adam(model.parameters(), lr=1e-4)\n\nprint(\"Model siap:\", type(model).__name__)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-03T01:22:57.843169Z","iopub.execute_input":"2026-09-03T01:22:57.843466Z","iopub.status.idle":"2026-09-03T01:22:58.219966Z","shell.execute_reply.started":"2026-09-03T01:22:57.843435Z","shell.execute_reply":"2026-09-03T01:22:58.219232Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def train_epoch(model, loader, criterion, optimizer, device):\n    model.train()\n    total_loss = 0\n    start = time.time()\n    for i, (images, labels) in enumerate(loader):\n        images, labels = images.to(device), labels.to(device)\n        optimizer.zero_grad()\n        outputs = model(images)\n        loss = criterion(outputs, labels)\n        loss.backward()\n        optimizer.step()\n        total_loss += loss.item()\n        if i % 20 == 0:\n            elapsed = time.time() - start\n            print(f\"  Batch {i}/{len(loader)} - loss: {loss.item():.4f} - {elapsed:.1f}s\")\n    return total_loss / len(loader)\n\ndef validate(model, loader, criterion, device):\n    model.eval()\n    total_loss = 0\n    all_preds, all_labels = [], []\n    with torch.no_grad():\n        for images, labels in loader:\n            images, labels = images.to(device), labels.to(device)\n            outputs = model(images)\n            loss = criterion(outputs, labels)\n            total_loss += loss.item()\n            preds = outputs.argmax(dim=1).cpu().numpy()\n            all_preds.extend(preds)\n            all_labels.extend(labels.cpu().numpy())\n    kappa = cohen_kappa_score(all_labels, all_preds, weights='quadratic')\n    return total_loss / len(loader), kappa, all_preds, all_labels\n\nprint(\"Fungsi training & validasi siap.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-03T01:22:58.221406Z","iopub.execute_input":"2026-09-03T01:22:58.221626Z","iopub.status.idle":"2026-09-03T01:22:58.230687Z","shell.execute_reply.started":"2026-09-03T01:22:58.221604Z","shell.execute_reply":"2026-09-03T01:22:58.230056Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"MODEL_PATH_BEST = '/kaggle/working/model_dr_best.pth'\n\nbest_kappa = 0\nnum_epochs = 5  \n\nfor epoch in range(num_epochs):\n    print(f\"\\n=== Epoch {epoch+1}/{num_epochs} ===\")\n    train_loss = train_epoch(model, train_loader, criterion, optimizer, device)\n    val_loss, val_kappa, _, _ = validate(model, val_loader, criterion, device)\n    print(f\"Epoch {epoch+1} selesai | Train Loss: {train_loss:.4f} | Val Loss: {val_loss:.4f} | Val Kappa: {val_kappa:.4f}\")\n    \n    if val_kappa > best_kappa:\n        best_kappa = val_kappa\n        torch.save(model.state_dict(), MODEL_PATH_BEST)\n        print(f\"  → Model terbaik baru disimpan (Kappa: {best_kappa:.4f})\")\n\nprint(f\"\\nTraining selesai. Model terbaik: Kappa {best_kappa:.4f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-03T01:22:58.231842Z","iopub.execute_input":"2026-09-03T01:22:58.232186Z","iopub.status.idle":"2026-09-03T02:03:40.352634Z","shell.execute_reply.started":"2026-09-03T01:22:58.232160Z","shell.execute_reply":"2026-09-03T02:03:40.351730Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model.load_state_dict(torch.load(MODEL_PATH_BEST))\nmodel.eval()\n\ntest_loss, test_kappa, test_preds, test_labels = validate(model, test_loader, criterion, device)\nprint(f\"Test Kappa: {test_kappa:.4f}\")\nprint()\nprint(classification_report(test_labels, test_preds, target_names=list(labels_map.values())))\n\ncm = confusion_matrix(test_labels, test_preds)\nplt.figure(figsize=(8,6))\nsns.heatmap(cm, annot=True, fmt='d', cmap='Blues', xticklabels=labels_map.values(), yticklabels=labels_map.values())\nplt.xlabel('Prediksi')\nplt.ylabel('Sebenarnya')\nplt.title('Confusion Matrix - Test Set')\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-03T02:03:40.353813Z","iopub.execute_input":"2026-09-03T02:03:40.354330Z","iopub.status.idle":"2026-09-03T02:06:07.637045Z","shell.execute_reply.started":"2026-09-03T02:03:40.354299Z","shell.execute_reply":"2026-09-03T02:06:07.636097Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import numpy as np\nimport cv2\nfrom PIL import Image\nimport matplotlib.pyplot as plt\n\n# Load ulang model dari file terbaik (tidak bergantung variabel training sebelumnya)\nmodel = timm.create_model('efficientnet_b3', pretrained=False, num_classes=5)\nmodel.load_state_dict(torch.load('/kaggle/working/model_dr_best.pth'))\nmodel = model.to(device)\nmodel.eval()\nprint(\"Model terbaik berhasil di-load (Kappa 0.7936).\")\n\ndef crop_retina_circle(image):\n    img_array = np.array(image)\n    gray = cv2.cvtColor(img_array, cv2.COLOR_RGB2GRAY)\n    gray_blur = cv2.medianBlur(gray, 5)\n    h, w = gray.shape\n    circles = cv2.HoughCircles(\n        gray_blur, cv2.HOUGH_GRADIENT, dp=1, minDist=w//2,\n        param1=50, param2=30, minRadius=int(w*0.25), maxRadius=int(w*0.5)\n    )\n    if circles is not None:\n        circles = np.uint16(np.around(circles))\n        x, y, r = circles[0][0]\n        r = int(r * 0.9)\n        x0, y0 = max(0, x-r), max(0, y-r)\n        x1, y1 = min(w, x+r), min(h, y+r)\n        cropped = img_array[y0:y1, x0:x1]\n        return Image.fromarray(cropped)\n    return image\n\ndef normalize_color(image, method='clahe'):\n    img_array = np.array(image)\n    if method == 'clahe':\n        lab = cv2.cvtColor(img_array, cv2.COLOR_RGB2LAB)\n        l, a, b = cv2.split(lab)\n        clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))\n        l_eq = clahe.apply(l)\n        lab_eq = cv2.merge([l_eq, a, b])\n        result = cv2.cvtColor(lab_eq, cv2.COLOR_LAB2RGB)\n        return Image.fromarray(result)\n    elif method == 'gray_world':\n        img_float = img_array.astype(np.float32)\n        avg_r, avg_g, avg_b = np.mean(img_float[:,:,0]), np.mean(img_float[:,:,1]), np.mean(img_float[:,:,2])\n        avg_gray = (avg_r + avg_g + avg_b) / 3\n        img_float[:,:,0] *= (avg_gray / avg_r)\n        img_float[:,:,1] *= (avg_gray / avg_g)\n        img_float[:,:,2] *= (avg_gray / avg_b)\n        return Image.fromarray(np.clip(img_float, 0, 255).astype(np.uint8))\n    return image\n\ndef preprocess_pipeline(image, use_crop=True, color_method='clahe'):\n    if use_crop:\n        image = crop_retina_circle(image)\n    if color_method:\n        image = normalize_color(image, method=color_method)\n    return image\n\nprint(\"Fungsi preprocessing siap.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-03T02:17:15.586014Z","iopub.execute_input":"2026-09-03T02:17:15.586301Z","iopub.status.idle":"2026-09-03T02:17:15.994835Z","shell.execute_reply.started":"2026-09-03T02:17:15.586276Z","shell.execute_reply":"2026-09-03T02:17:15.994070Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"labels_map = {0: 'No DR', 1: 'Mild', 2: 'Moderate', 3: 'Severe', 4: 'Proliferative DR'}\n\ndef diagnose_image(img_path, model, eval_transform, device, use_preprocessing=True, show=True):\n    image_raw = Image.open(img_path).convert('RGB')\n    \n    if use_preprocessing:\n        image_final = preprocess_pipeline(image_raw, use_crop=True, color_method='clahe')\n    else:\n        image_final = image_raw\n    \n    img_tensor = eval_transform(image_final).unsqueeze(0).to(device)\n    \n    model.eval()\n    with torch.no_grad():\n        output = model(img_tensor)\n        probs = torch.softmax(output, dim=1)[0].cpu().numpy()\n        pred_class = int(np.argmax(probs))\n    \n    if show:\n        fig, axes = plt.subplots(1, 2, figsize=(10,4))\n        axes[0].imshow(image_raw)\n        axes[0].set_title('Foto Asli')\n        axes[0].axis('off')\n        axes[1].imshow(image_final)\n        axes[1].set_title('Setelah Preprocessing')\n        axes[1].axis('off')\n        plt.show()\n        \n        print(f\"\\n=== HASIL DIAGNOSIS ===\")\n        print(f\"Prediksi: {labels_map[pred_class]}\")\n        print(f\"Confidence: {probs[pred_class]*100:.2f}%\\n\")\n        print(\"Distribusi semua kelas:\")\n        for i, label in labels_map.items():\n            print(f\"  {label}: {probs[i]*100:.2f}%\")\n    \n    return pred_class, probs\n\nprint(\"Fungsi diagnosis siap.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-03T03:13:27.638437Z","iopub.execute_input":"2026-09-03T03:13:27.639203Z","iopub.status.idle":"2026-09-03T03:13:27.649676Z","shell.execute_reply.started":"2026-09-03T03:13:27.639172Z","shell.execute_reply":"2026-09-03T03:13:27.648991Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"=== TANPA preprocessing ===\")\npred_class_raw, probs_raw = diagnose_image(\n    '/kaggle/input/datasets/abdurraufbhirawa296/test123/test 2 eye.jpeg',\n    model, eval_transform, device,\n    use_preprocessing=False\n)\n\nprint(\"\\n\\n=== DENGAN preprocessing (crop + CLAHE) ===\")\npred_class_prep, probs_prep = diagnose_image(\n    '/kaggle/input/datasets/abdurraufbhirawa296/test123/test 2 eye.jpeg',\n    model, eval_transform, device,\n    use_preprocessing=True\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-03T03:21:30.468408Z","iopub.execute_input":"2026-09-03T03:21:30.468911Z","iopub.status.idle":"2026-09-03T03:21:30.947691Z","shell.execute_reply.started":"2026-09-03T03:21:30.468880Z","shell.execute_reply":"2026-09-03T03:21:30.946601Z"}},"outputs":[],"execution_count":null}]}