{"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":13422580,"sourceType":"datasetVersion","datasetId":8519219}],"dockerImageVersionId":31154,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load\n\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\n# Input data files are available in the read-only \"../input/\" directory\n# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\n\nimport os\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        print(os.path.join(dirname, filename))\n\n# You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using \"Save & Run All\" \n# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-10-18T14:44:29.928448Z","iopub.execute_input":"2025-10-18T14:44:29.928693Z","iopub.status.idle":"2025-10-18T14:44:37.189577Z","shell.execute_reply.started":"2025-10-18T14:44:29.928668Z","shell.execute_reply":"2025-10-18T14:44:37.188747Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport numpy as np\nimport glob\nimport scipy.io\nimport pandas as pd\nimport matplotlib.pyplot as plt\nfrom PIL import Image\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nfrom torch.utils.data import Dataset, DataLoader, random_split\nfrom torchvision import transforms\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-18T14:44:37.191024Z","iopub.execute_input":"2025-10-18T14:44:37.191439Z","iopub.status.idle":"2025-10-18T14:44:44.624488Z","shell.execute_reply.started":"2025-10-18T14:44:37.191412Z","shell.execute_reply":"2025-10-18T14:44:44.623875Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Prepare Glaucoma Dataset","metadata":{}},{"cell_type":"code","source":"class FullGlaucomaDataset(Dataset):\n    def __init__(self, fundus_dir, origa_mat, origa_img_dir, acrima_dir, transform=None):\n        self.samples = []\n        self.transform = transform\n        \n        # 1. Fundus images\n        for label_dir in [\"Glaucoma_Negative\", \"Glaucoma_Positive\"]:\n            full_dir = os.path.join(fundus_dir, \"Train\", label_dir)\n            label = 0 if \"Negative\" in label_dir else 1\n            for f in glob.glob(full_dir + \"/*.jpg\"):\n                self.samples.append((f,label))\n        \n        # 2. ORIGA\n        origa_data = scipy.io.loadmat(origa_mat)['Origa']\n        # ORIGA MATLAB struct -> list of dict-like items\n        for i in range(len(origa_data[0])):\n            fname = origa_data[0][i]['Filename'][0]\n            label = int(origa_data[0][i]['Glaucoma'][0][0])\n            img_path = os.path.join(origa_img_dir, fname)\n            if os.path.exists(img_path):\n                self.samples.append((img_path, label))\n        \n        # 3. ACRIMA\n        for f in glob.glob(acrima_dir + \"/*.jpg\"):\n            fname = os.path.basename(f)\n            # Glaucoma label if filename has _g_\n            label = 1 if \"_g_\" in fname else 0\n            self.samples.append((f,label))\n        \n    def __len__(self):\n        return len(self.samples)\n    \n    def __getitem__(self, idx):\n        path, label = self.samples[idx]\n        img = Image.open(path).convert(\"RGB\")\n        if self.transform:\n            img = self.transform(img)\n        return img, label\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-18T14:44:51.656694Z","iopub.execute_input":"2025-10-18T14:44:51.656950Z","iopub.status.idle":"2025-10-18T14:44:51.664310Z","shell.execute_reply.started":"2025-10-18T14:44:51.656930Z","shell.execute_reply":"2025-10-18T14:44:51.663596Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Train/Validatin Split","metadata":{}},{"cell_type":"code","source":"import torch\nfrom torch.utils.data import DataLoader, random_split\nfrom torchvision import transforms\n\n# 1️⃣ Transforms\ntransform_train = transforms.Compose([\n    transforms.Grayscale(num_output_channels=1),\n    transforms.Resize((64, 64)),\n    transforms.RandomHorizontalFlip(),\n    transforms.RandomRotation(15),\n    transforms.ToTensor(),\n    transforms.Normalize((0.5,), (0.5,))\n])\n\ntransform_val = transforms.Compose([\n    transforms.Grayscale(num_output_channels=1),\n    transforms.Resize((64, 64)),\n    transforms.ToTensor(),\n    transforms.Normalize((0.5,), (0.5,))\n])\n\n# 2️⃣ Paths\nfundus_dir = \"/kaggle/input/glaucoma-detection/Fundus_Train_Val_Data/Fundus_Scanes_Sorted\"\noriga_mat = \"/kaggle/input/glaucoma-detection/ORIGA/ORIGA/OrigaList.mat\"\noriga_img_dir = \"/kaggle/input/glaucoma-detection/ORIGA/ORIGA/Images\"\nacrima_dir = \"/kaggle/input/glaucoma-detection/ACRIMA/Images\"\n\n# 3️⃣ Instantiate full dataset\nfull_dataset = FullGlaucomaDataset(\n    fundus_dir=fundus_dir,      # <-- correct argument\n    origa_mat=origa_mat,\n    origa_img_dir=origa_img_dir,\n    acrima_dir=acrima_dir,\n    transform=transform_train\n)\n\n# 4️⃣ Train/validation split (80/20)\ntrain_size = int(0.8 * len(full_dataset))\nval_size = len(full_dataset) - train_size\ntrain_dataset, val_dataset = random_split(full_dataset, [train_size, val_size])\n\n# 5️⃣ DataLoaders\ntrain_loader = DataLoader(train_dataset, batch_size=32, shuffle=True)\nval_loader = DataLoader(val_dataset, batch_size=32, shuffle=False)\n\nprint(\"Total samples:\", len(full_dataset))\nprint(\"Train:\", len(train_dataset), \"Validation:\", len(val_dataset))\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-18T14:44:56.716332Z","iopub.execute_input":"2025-10-18T14:44:56.716599Z","iopub.status.idle":"2025-10-18T14:44:57.068658Z","shell.execute_reply.started":"2025-10-18T14:44:56.716579Z","shell.execute_reply":"2025-10-18T14:44:57.067913Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Visualize Some Images","metadata":{}},{"cell_type":"code","source":"import matplotlib.pyplot as plt\n\n# Function to show a few images from a DataLoader\ndef show_samples(loader, num_samples=5):\n    images, labels = next(iter(loader))  # get one batch\n    for i in range(num_samples):\n        img = images[i].squeeze().cpu()  # remove channel dim and move to CPU\n        label = labels[i].item()\n        plt.imshow(img, cmap='gray')\n        plt.title(f'Label: {label} (0=No Glaucoma, 1=Glaucoma)')\n        plt.axis('off')\n        plt.show()\n\n# Show samples from training set\nprint(\"Training set samples:\")\nshow_samples(train_loader, num_samples=5)\n\n# Show samples from validation set\nprint(\"Validation set samples:\")\nshow_samples(val_loader, num_samples=5)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-18T14:45:31.190901Z","iopub.execute_input":"2025-10-18T14:45:31.191227Z","iopub.status.idle":"2025-10-18T14:45:35.892294Z","shell.execute_reply.started":"2025-10-18T14:45:31.191204Z","shell.execute_reply":"2025-10-18T14:45:35.891491Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# LeNet Model","metadata":{}},{"cell_type":"code","source":"import torch\nimport torch.nn as nn\nimport torch.nn.functional as F\n\nclass AdvancedLeNet(nn.Module):\n    def __init__(self):\n        super(AdvancedLeNet, self).__init__()\n        # Convolutional layers\n        self.conv1 = nn.Conv2d(1, 16, kernel_size=5, padding=2)  # 1x64x64 -> 16x64x64\n        self.bn1 = nn.BatchNorm2d(16)\n        self.pool = nn.MaxPool2d(2, 2)                            # 16x64x64 -> 16x32x32\n\n        self.conv2 = nn.Conv2d(16, 32, kernel_size=5, padding=2) # 16x32x32 -> 32x32x32\n        self.bn2 = nn.BatchNorm2d(32)\n        # pool -> 32x16x16\n\n        self.conv3 = nn.Conv2d(32, 64, kernel_size=3, padding=1) # 32x16x16 -> 64x16x16\n        self.bn3 = nn.BatchNorm2d(64)\n        # pool -> 64x8x8\n\n        # Fully connected layers\n        self.fc1 = nn.Linear(64 * 8 * 8, 256)\n        self.dropout1 = nn.Dropout(0.5)\n        self.fc2 = nn.Linear(256, 128)\n        self.dropout2 = nn.Dropout(0.5)\n        self.fc3 = nn.Linear(128, 2)  # 2 classes: glaucoma / non-glaucoma\n\n    def forward(self, x):\n        x = self.pool(F.relu(self.bn1(self.conv1(x))))\n        x = self.pool(F.relu(self.bn2(self.conv2(x))))\n        x = self.pool(F.relu(self.bn3(self.conv3(x))))\n        x = x.view(-1, 64 * 8 * 8)\n        x = F.relu(self.fc1(x))\n        x = self.dropout1(x)\n        x = F.relu(self.fc2(x))\n        x = self.dropout2(x)\n        x = self.fc3(x)\n        return x\n\n# Example usage\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nmodel = AdvancedLeNet().to(device)\nprint(model)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-18T14:46:02.431374Z","iopub.execute_input":"2025-10-18T14:46:02.431657Z","iopub.status.idle":"2025-10-18T14:46:02.723398Z","shell.execute_reply.started":"2025-10-18T14:46:02.431630Z","shell.execute_reply":"2025-10-18T14:46:02.722698Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Weighted Loss (Class Imbalance)","metadata":{}},{"cell_type":"code","source":"# # Compute class weights\n# labels = [label for _, label in all_samples]\n# class_counts = np.bincount(labels)\n# class_weights = 1.0 / class_counts\n# weights = torch.tensor(class_weights, dtype=torch.float).to(device)\n\n# criterion = nn.CrossEntropyLoss(weight=weights)\n# optimizer = optim.Adam(model.parameters(), lr=0.001)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-18T08:53:38.239877Z","iopub.execute_input":"2025-10-18T08:53:38.240670Z","iopub.status.idle":"2025-10-18T08:53:38.248777Z","shell.execute_reply.started":"2025-10-18T08:53:38.240645Z","shell.execute_reply":"2025-10-18T08:53:38.248281Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Training Loop","metadata":{}},{"cell_type":"code","source":"import torch\nimport torch.nn as nn\nimport torch.optim as optim\nimport matplotlib.pyplot as plt\n\n# 1️⃣ Model, criterion, optimizer\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nmodel = AdvancedLeNet().to(device)\n\ncriterion = nn.CrossEntropyLoss()\noptimizer = optim.Adam(model.parameters(), lr=0.001)\n\n# 2️⃣ Training parameters\nnum_epochs = 20\n\n# Lists for tracking metrics\ntrain_losses, val_losses = [], []\ntrain_accuracies, val_accuracies = [], []\n\n# 3️⃣ Training loop\nfor epoch in range(num_epochs):\n    model.train()\n    running_loss = 0.0\n    correct = 0\n    total = 0\n\n    for images, labels in train_loader:\n        images, labels = images.to(device), labels.to(device)\n\n        optimizer.zero_grad()\n        outputs = model(images)\n        loss = criterion(outputs, labels)\n        loss.backward()\n        optimizer.step()\n\n        running_loss += loss.item() * images.size(0)\n        _, predicted = torch.max(outputs, 1)\n        total += labels.size(0)\n        correct += (predicted == labels).sum().item()\n\n    train_loss = running_loss / total\n    train_acc = correct / total\n    train_losses.append(train_loss)\n    train_accuracies.append(train_acc)\n\n    # Validation\n    model.eval()\n    val_loss = 0.0\n    val_correct = 0\n    val_total = 0\n    with torch.no_grad():\n        for images, labels in val_loader:\n            images, labels = images.to(device), labels.to(device)\n            outputs = model(images)\n            loss = criterion(outputs, labels)\n\n            val_loss += loss.item() * images.size(0)\n            _, predicted = torch.max(outputs, 1)\n            val_total += labels.size(0)\n            val_correct += (predicted == labels).sum().item()\n\n    val_loss /= val_total\n    val_acc = val_correct / val_total\n    val_losses.append(val_loss)\n    val_accuracies.append(val_acc)\n\n    print(f\"Epoch [{epoch+1}/{num_epochs}] \"\n          f\"Train Loss: {train_loss:.4f}, Train Acc: {train_acc:.4f} \"\n          f\"Val Loss: {val_loss:.4f}, Val Acc: {val_acc:.4f}\")\n\n# 4️⃣ Save the trained model\ntorch.save(model.state_dict(), \"advanced_lenet_glaucoma.pth\")\nprint(\"Model saved as advanced_lenet_glaucoma.pth\")\n\n# 5️⃣ Plot the metrics\nplt.figure(figsize=(12,5))\n\n# 📉 Loss\nplt.subplot(1,2,1)\nplt.plot(range(1, num_epochs+1), train_losses, 'b-', label='Train Loss')\nplt.plot(range(1, num_epochs+1), val_losses, 'r-', label='Validation Loss')\nplt.xlabel('Epoch')\nplt.ylabel('Loss')\nplt.title('Loss vs Epoch')\nplt.legend()\nplt.grid(True, linestyle='--', alpha=0.6)\n\n# 📈 Accuracy\nplt.subplot(1,2,2)\nplt.plot(range(1, num_epochs+1), train_accuracies, 'b-', label='Train Accuracy')\nplt.plot(range(1, num_epochs+1), val_accuracies, 'r-', label='Validation Accuracy')\nplt.xlabel('Epoch')\nplt.ylabel('Accuracy')\nplt.title('Accuracy vs Epoch')\nplt.legend()\nplt.grid(True, linestyle='--', alpha=0.6)\n\nplt.tight_layout()\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-18T14:46:18.682243Z","iopub.execute_input":"2025-10-18T14:46:18.682519Z","iopub.status.idle":"2025-10-18T14:52:37.175899Z","shell.execute_reply.started":"2025-10-18T14:46:18.682498Z","shell.execute_reply":"2025-10-18T14:52:37.175202Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Load & Test Model","metadata":{}},{"cell_type":"code","source":"import torch\nimport torch.nn as nn\nfrom sklearn.metrics import classification_report, confusion_matrix\nimport seaborn as sns\nimport matplotlib.pyplot as plt\n\n# 1️⃣ Define your class names\n# 👇 Replace these with your actual class labels if different\nclasses = ['Healthy', 'Glaucoma']\n\n# 2️⃣ Load the model\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nmodel = AdvancedLeNet().to(device)\nmodel.load_state_dict(torch.load(\"advanced_lenet_glaucoma.pth\", map_location=device))\nmodel.eval()\n\n# 3️⃣ Evaluate on test/validation data\nall_preds = []\nall_labels = []\n\nwith torch.no_grad():\n    for images, labels in val_loader:  # or test_loader if available\n        images, labels = images.to(device), labels.to(device)\n        outputs = model(images)\n        _, predicted = torch.max(outputs, 1)\n        all_preds.extend(predicted.cpu().numpy())\n        all_labels.extend(labels.cpu().numpy())\n\n# 4️⃣ Accuracy\naccuracy = sum([p == t for p, t in zip(all_preds, all_labels)]) / len(all_labels)\nprint(f\"✅ Model Accuracy: {accuracy * 100:.2f}%\")\n\n# 5️⃣ Classification Report\nprint(\"\\n📊 Classification Report:\")\nprint(classification_report(all_labels, all_preds, target_names=classes))\n\n# 6️⃣ Confusion Matrix\ncm = confusion_matrix(all_labels, all_preds)\nplt.figure(figsize=(6, 5))\nsns.heatmap(cm, annot=True, fmt=\"d\", cmap=\"Blues\",\n            xticklabels=classes, yticklabels=classes)\nplt.xlabel(\"Predicted\")\nplt.ylabel(\"True\")\nplt.title(\"Confusion Matrix\")\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-18T14:55:22.976929Z","iopub.execute_input":"2025-10-18T14:55:22.977723Z","iopub.status.idle":"2025-10-18T14:55:41.639863Z","shell.execute_reply.started":"2025-10-18T14:55:22.977695Z","shell.execute_reply":"2025-10-18T14:55:41.639211Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\nimport matplotlib.pyplot as plt\nimport numpy as np\n\n# 1️⃣ Define class names\nclasses = ['Healthy', 'Glaucoma']\n\n# 2️⃣ Load model\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nmodel = AdvancedLeNet().to(device)\nmodel.load_state_dict(torch.load(\"advanced_lenet_glaucoma.pth\", map_location=device))\nmodel.eval()\n\n# 3️⃣ Function to show image predictions\ndef imshow(img, title):\n    img = img / 2 + 0.5  # unnormalize if you normalized [-1,1]\n    npimg = img.numpy().transpose((1, 2, 0))\n    plt.imshow(npimg)\n    plt.axis('off')\n    plt.title(title, fontsize=12)\n    plt.show()\n\n# 4️⃣ Get some random samples from validation/test loader\ndataiter = iter(val_loader)\nimages, labels = next(dataiter)\nimages, labels = images.to(device), labels.to(device)\n\n# 5️⃣ Get predictions\noutputs = model(images)\n_, preds = torch.max(outputs, 1)\n\n# 6️⃣ Display first few predictions visually\nfor i in range(5):  # show first 5 images\n    img = images[i].cpu()\n    true_label = classes[labels[i].item()]\n    pred_label = classes[preds[i].item()]\n    color = \"green\" if true_label == pred_label else \"red\"\n    imshow(img.cpu(), title=f\"Predicted: {pred_label} | Actual: {true_label}\",)\n\n# 7️⃣ Overall accuracy\ncorrect = (preds == labels).sum().item()\ntotal = labels.size(0)\naccuracy = 100 * correct / total\nprint(f\"\\n✅ Model Accuracy on this batch: {accuracy:.2f}%\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-18T14:55:41.640948Z","iopub.execute_input":"2025-10-18T14:55:41.641219Z","iopub.status.idle":"2025-10-18T14:55:43.828802Z","shell.execute_reply.started":"2025-10-18T14:55:41.641197Z","shell.execute_reply":"2025-10-18T14:55:43.828027Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport random\n\nclasses = ['Healthy', 'Glaucoma']\n\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nmodel = AdvancedLeNet().to(device)\nmodel.load_state_dict(torch.load(\"advanced_lenet_glaucoma.pth\", map_location=device))\nmodel.eval()\n\ndef imshow(img, title):\n    img = img / 2 + 0.5\n    npimg = img.cpu().numpy().transpose((1, 2, 0))\n    plt.imshow(npimg)\n    plt.axis('off')\n    plt.title(title, fontsize=12)\n    plt.show()\n\n# Collect all glaucoma samples from validation set\nglaucoma_indices = [i for i, (_, label) in enumerate(val_loader.dataset) if label == 1]\n\ncorrect_glaucoma = 0\ntotal_glaucoma = len(glaucoma_indices)\n\n# Randomly pick 5 glaucoma samples to visualize\nsample_indices = random.sample(glaucoma_indices, min(5, len(glaucoma_indices)))\n\nfor idx in glaucoma_indices:  # loop over all glaucoma samples for accuracy\n    image, label = val_loader.dataset[idx]\n    image = image.unsqueeze(0).to(device)\n\n    output = model(image)\n    _, pred = torch.max(output, 1)\n\n    if pred.item() == 1:\n        correct_glaucoma += 1\n\n# Glaucoma-only accuracy\nglaucoma_acc = correct_glaucoma / total_glaucoma * 100\nprint(f\"\\n🧠 Glaucoma-Only Accuracy: {glaucoma_acc:.2f}%\")\n\n# Show a few random glaucoma predictions\nprint(\"\\n🔍 Sample Predictions (Glaucoma Cases):\")\nfor idx in sample_indices:\n    image, label = val_loader.dataset[idx]\n    image = image.unsqueeze(0).to(device)\n\n    output = model(image)\n    _, pred = torch.max(output, 1)\n    pred_label = classes[pred.item()]\n    true_label = classes[label]\n\n    imshow(image.squeeze(0).cpu(), title=f\"Predicted: {pred_label} | Actual: {true_label}\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-18T14:55:43.829600Z","iopub.execute_input":"2025-10-18T14:55:43.829918Z","iopub.status.idle":"2025-10-18T14:56:08.866109Z","shell.execute_reply.started":"2025-10-18T14:55:43.829899Z","shell.execute_reply":"2025-10-18T14:56:08.865269Z"}},"outputs":[],"execution_count":null}]}