{"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":14774,"databundleVersionId":875431,"sourceType":"competition"}],"dockerImageVersionId":31041,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"!pip install -q timm seaborn opendatasets\n\nimport pandas as pd\nimport numpy as np\nimport matplotlib.pyplot as plt\nimport seaborn as sns\nimport cv2\nimport os\nfrom sklearn.model_selection import StratifiedKFold\nfrom sklearn.metrics import classification_report, confusion_matrix\nimport torch\nfrom torch.utils.data import Dataset, DataLoader\nimport torchvision.transforms as T\nimport timm\nimport torch.nn as nn\nimport torch.optim as optim\nfrom torchvision import models\nfrom PIL import Image","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-05-14T18:24:34.359064Z","iopub.execute_input":"2025-05-14T18:24:34.360118Z","iopub.status.idle":"2025-05-14T18:24:37.733505Z","shell.execute_reply.started":"2025-05-14T18:24:34.360088Z","shell.execute_reply":"2025-05-14T18:24:37.732610Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Load CSVs\ntrain_df = pd.read_csv(\"/kaggle/input/aptos2019-blindness-detection/train.csv\")\ntest_df = pd.read_csv(\"/kaggle/input/aptos2019-blindness-detection/test.csv\")\n\nprint(\"📊 Label Distribution:\")\nsns.countplot(data=train_df, x=\"diagnosis\")\nplt.title(\"DR Class Distribution\")\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-14T18:24:37.735308Z","iopub.execute_input":"2025-05-14T18:24:37.735552Z","iopub.status.idle":"2025-05-14T18:24:38.252324Z","shell.execute_reply.started":"2025-05-14T18:24:37.735527Z","shell.execute_reply":"2025-05-14T18:24:38.251455Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Visualize one image from each class\nimg_dir = \"/kaggle/input/aptos2019-blindness-detection/train_images\"\n\nfig, axes = plt.subplots(1, 5, figsize=(20, 5))\nfor i in range(5):\n    img_id = train_df[train_df.diagnosis == i].iloc[0].id_code\n    path = f\"/kaggle/input/aptos2019-blindness-detection/train_images/{img_id}.png\"\n    img = cv2.imread(path)\n    img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n    axes[i].imshow(img)\n    axes[i].set_title(f\"Class {i}\")\n    axes[i].axis('off')\nplt.suptitle(\"Sample Images per Class\")\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-14T18:24:38.253004Z","iopub.execute_input":"2025-05-14T18:24:38.253294Z","iopub.status.idle":"2025-05-14T18:24:41.970841Z","shell.execute_reply.started":"2025-05-14T18:24:38.253272Z","shell.execute_reply":"2025-05-14T18:24:41.969982Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def preprocess_image(path, size=512):\n    image = cv2.imread(path)\n    image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)\n    lab = cv2.cvtColor(image, cv2.COLOR_RGB2LAB)\n    l, a, b = cv2.split(lab)\n    clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8))\n    cl = clahe.apply(l)\n    limg = cv2.merge((cl, a, b))\n    final = cv2.cvtColor(limg, cv2.COLOR_LAB2RGB)\n    final = cv2.resize(final, (size, size))\n    return final\n\n# Visualize processed image\nexample = train_df.iloc[0].id_code\nimg_path = f\"/kaggle/input/aptos2019-blindness-detection/train_images/{example}.png\"\nprocessed_img = preprocess_image(img_path)\n\nplt.imshow(processed_img)\nplt.title(\"Preprocessed Sample\")\nplt.axis(\"off\")\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-14T18:24:41.971854Z","iopub.execute_input":"2025-05-14T18:24:41.972398Z","iopub.status.idle":"2025-05-14T18:24:42.641974Z","shell.execute_reply.started":"2025-05-14T18:24:41.972370Z","shell.execute_reply":"2025-05-14T18:24:42.641309Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class DRDataset(Dataset):\n    def __init__(self, df, img_dir, transform=None):\n        self.df = df\n        self.img_dir = img_dir\n        self.transform = transform\n\n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, idx):\n        row = self.df.iloc[idx]\n        img_id = row.id_code\n        img_path = f\"/kaggle/input/aptos2019-blindness-detection/train_images/{img_id}.png\"\n\n        if not os.path.exists(img_path):\n            print(f\"❌ Image not found: {img_path}\")\n\n        image = preprocess_image(img_path)\n        image = Image.fromarray(image)  # Required for torchvision transforms\n        label = row.diagnosis\n\n        if self.transform:\n            image = self.transform(image)\n\n        return image, label","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-14T18:24:42.644369Z","iopub.execute_input":"2025-05-14T18:24:42.645000Z","iopub.status.idle":"2025-05-14T18:24:42.652552Z","shell.execute_reply.started":"2025-05-14T18:24:42.644973Z","shell.execute_reply":"2025-05-14T18:24:42.651636Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Transforms\ntransform = T.Compose([\n    T.RandomHorizontalFlip(),\n    T.RandomRotation(10),\n    T.ToTensor(),\n    T.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])\n])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-14T18:24:42.653452Z","iopub.execute_input":"2025-05-14T18:24:42.653646Z","iopub.status.idle":"2025-05-14T18:24:42.669642Z","shell.execute_reply.started":"2025-05-14T18:24:42.653630Z","shell.execute_reply":"2025-05-14T18:24:42.669087Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class DRModel(nn.Module):\n    def __init__(self, backbone='mobilenetv3_large_100', num_classes=5):\n        super(DRModel, self).__init__()\n        self.backbone = timm.create_model(backbone, pretrained=True)\n        in_features = self.backbone.classifier.in_features\n        self.backbone.classifier = nn.Sequential(\n            nn.Linear(in_features, 256),\n            nn.ReLU(),\n            nn.Dropout(0.3),\n            nn.Linear(256, num_classes)\n        )\n\n    def forward(self, x):\n        return self.backbone(x)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-14T18:24:42.670325Z","iopub.execute_input":"2025-05-14T18:24:42.670544Z","iopub.status.idle":"2025-05-14T18:24:42.687602Z","shell.execute_reply.started":"2025-05-14T18:24:42.670516Z","shell.execute_reply":"2025-05-14T18:24:42.687058Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class FocalLoss(nn.Module):\n    def __init__(self, gamma=2.0, weight=None):\n        super(FocalLoss, self).__init__()\n        self.gamma = gamma\n        self.ce = nn.CrossEntropyLoss(weight=weight)\n\n    def forward(self, input, target):\n        logp = self.ce(input, target)\n        p = torch.exp(-logp)\n        loss = (1 - p) ** self.gamma * logp\n        return loss.mean()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-14T18:24:42.688449Z","iopub.execute_input":"2025-05-14T18:24:42.688697Z","iopub.status.idle":"2025-05-14T18:24:42.714721Z","shell.execute_reply.started":"2025-05-14T18:24:42.688681Z","shell.execute_reply":"2025-05-14T18:24:42.714178Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"BATCH_SIZE = 16\nEPOCHS = 5  # Start low for Colab; increase later\nLR = 1e-4\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-14T18:24:42.715569Z","iopub.execute_input":"2025-05-14T18:24:42.715835Z","iopub.status.idle":"2025-05-14T18:24:42.824983Z","shell.execute_reply.started":"2025-05-14T18:24:42.715807Z","shell.execute_reply":"2025-05-14T18:24:42.824376Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def train_one_epoch(model, loader, optimizer, loss_fn):\n    model.train()\n    total_loss = 0\n    for imgs, labels in loader:\n        imgs, labels = imgs.to(device), labels.to(device)\n        optimizer.zero_grad()\n        outputs = model(imgs)\n        loss = loss_fn(outputs, labels)\n        loss.backward()\n        optimizer.step()\n        total_loss += loss.item()\n    return total_loss / len(loader)\n\n\ndef evaluate(model, loader):\n    model.eval()\n    preds, targets = [], []\n    probs_list = []  # List to store probabilities\n\n    with torch.no_grad():\n        for imgs, labels in loader:\n            imgs = imgs.to(device)  # Move imgs to device\n            labels = labels.to(device)  # Move labels to device\n\n            outputs = model(imgs)\n\n            # Apply softmax to get probabilities (logits -> probabilities)\n            probs = torch.softmax(outputs, dim=1).cpu().numpy()  # Apply softmax here\n\n            # Get predicted class (from probabilities)\n            pred = np.argmax(probs, axis=1)\n            preds.extend(pred)\n\n            targets.extend(labels.cpu().numpy())  # Ensure labels are on CPU for compatibility\n            probs_list.extend(probs)  # Store the probabilities for later analysis if needed\n\n    return preds, targets, probs_list","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-14T18:24:42.825894Z","iopub.execute_input":"2025-05-14T18:24:42.826109Z","iopub.status.idle":"2025-05-14T18:24:42.848935Z","shell.execute_reply.started":"2025-05-14T18:24:42.826093Z","shell.execute_reply":"2025-05-14T18:24:42.848443Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)\n\nfor fold, (train_idx, val_idx) in enumerate(skf.split(train_df, train_df.diagnosis)):\n    if fold != 4:\n        continue  # Skip first 4 folds\n    print(f\"\\n📂 Fold {fold+1}/5\")\n\n    train_data = train_df.iloc[train_idx]\n    val_data = train_df.iloc[val_idx]\n\n    train_ds = DRDataset(train_data, img_dir, transform)\n    val_ds = DRDataset(val_data, img_dir, transform)\n\n    train_loader = DataLoader(train_ds, batch_size=BATCH_SIZE, shuffle=True, num_workers=2)\n    val_loader = DataLoader(val_ds, batch_size=BATCH_SIZE, shuffle=False, num_workers=2)\n\n    model = DRModel().to(device)\n    optimizer = optim.Adam(model.parameters(), lr=LR)\n    loss_fn = FocalLoss()\n\n    best_score = -np.inf  # Initialize the best score variable\n\n    for epoch in range(EPOCHS):\n        train_loss = train_one_epoch(model, train_loader, optimizer, loss_fn)\n        preds, targets = evaluate(model, val_loader)\n        \n        qwk = cohen_kappa_score(targets, preds, weights='quadratic')\n        print(f\"Epoch {epoch+1}/{EPOCHS} | Loss: {train_loss:.4f} | QWK: {qwk:.4f}\")\n        print(classification_report(targets, preds, digits=3))\n        \n        # Confusion Matrix\n        cm = confusion_matrix(targets, preds)\n        sns.heatmap(cm, annot=True, fmt='d', cmap='Blues')\n        plt.title(f\"Fold {fold+1} - Epoch {epoch+1} Confusion Matrix\")\n        plt.xlabel(\"Predicted\")\n        plt.ylabel(\"Actual\")\n        plt.show()\n        \n        # If this epoch gives a better QWK, save the model\n        if qwk > best_score:\n            best_score = qwk\n            torch.save(model.state_dict(), f\"best_model_fold{fold+1}.pt\")  # Save best model\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-14T21:25:30.247322Z","iopub.status.idle":"2025-05-14T21:25:30.247581Z","shell.execute_reply.started":"2025-05-14T21:25:30.247456Z","shell.execute_reply":"2025-05-14T21:25:30.247475Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def predict_image(path, model):\n    model.eval()\n    img = preprocess_image(path)\n    img = Image.fromarray(img)\n    img = transform(img).unsqueeze(0).to(device)\n\n    with torch.no_grad():\n        out = model(img)\n        pred = torch.argmax(out, dim=1).item()\n    return pred\n\n# Predict on 5 random samples\nmodel.eval()\nsample_paths = train_df.sample(5).id_code.values\nfor img_id in sample_paths:\n    img_path = f\"/kaggle/input/aptos2019-blindness-detection/train_images/{img_id}.png\"\n    pred = predict_image(img_path, model)\n    print(f\"🖼️ {img_id}.png → Predicted DR Stage: {pred}\")\n    img = cv2.imread(img_path)\n    plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))\n    plt.title(f\"Predicted: {pred}\")\n    plt.axis(\"off\")\n    plt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-14T21:08:49.549258Z","iopub.execute_input":"2025-05-14T21:08:49.549573Z","iopub.status.idle":"2025-05-14T21:08:54.092656Z","shell.execute_reply.started":"2025-05-14T21:08:49.549540Z","shell.execute_reply":"2025-05-14T21:08:54.091562Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from sklearn.metrics import cohen_kappa_score\nprint(\"Quadratic Weighted Kappa:\", cohen_kappa_score(targets, preds, weights='quadratic'))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-14T21:08:54.094008Z","iopub.execute_input":"2025-05-14T21:08:54.094364Z","iopub.status.idle":"2025-05-14T21:08:54.102372Z","shell.execute_reply.started":"2025-05-14T21:08:54.094335Z","shell.execute_reply":"2025-05-14T21:08:54.101540Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import random\nfrom torchvision import transforms\nimport matplotlib.pyplot as plt\n\n# Load your best model (you can change to fold1, fold2, etc.)\nmodel = DRModel().to(device)\nmodel.load_state_dict(torch.load(\"dr_model_fold4.pt\"))\nmodel.eval()\n\n# Define transform again if needed\ntest_transform = transforms.Compose([\n    transforms.Resize((512, 512)),\n    transforms.ToTensor(),\n    transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])  # Include normalization\n])\n\n# Pick 5 random samples from validation set (use val_data from fold 5)\nsample_idxs = random.sample(range(len(val_data)), 5)\nfig, axs = plt.subplots(1, 5, figsize=(20, 5))\n\nfor i, idx in enumerate(sample_idxs):\n    row = val_data.iloc[idx]\n    img_path = os.path.join(img_dir, row['id_code'] + \".png\")\n    image = Image.open(img_path).convert(\"RGB\")\n    input_tensor = test_transform(image).unsqueeze(0).to(device)\n\n    with torch.no_grad():\n        output = model(input_tensor)\n        pred = torch.argmax(output, dim=1).item()\n\n    true_label = row['diagnosis']\n\n    axs[i].imshow(image)\n    axs[i].axis('off')\n    axs[i].set_title(f\"GT: {true_label} | Pred: {pred}\", fontsize=12)\n\nplt.tight_layout()\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-14T21:28:29.487741Z","iopub.execute_input":"2025-05-14T21:28:29.488050Z","iopub.status.idle":"2025-05-14T21:28:32.178754Z","shell.execute_reply.started":"2025-05-14T21:28:29.488028Z","shell.execute_reply":"2025-05-14T21:28:32.177663Z"}},"outputs":[],"execution_count":null}]}