{"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":"nvidiaTeslaT4","dataSources":[{"sourceType":"competition","sourceId":14774}],"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"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\n\n# Use the kagglehub client library to attach Kaggle resources like competitions, datasets, and models to your session\n# Learn more about kagglehub: https://github.com/Kaggle/kagglehub/blob/main/README.md\n\nimport kagglehub\n# kagglehub.dataset_download('<owner>/<dataset-slug>')","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2026-09-07T13:31:14.765177Z","iopub.execute_input":"2026-09-07T13:31:14.765458Z","iopub.status.idle":"2026-09-07T13:31:21.814427Z","shell.execute_reply.started":"2026-09-07T13:31:14.765432Z","shell.execute_reply":"2026-09-07T13:31:21.813536Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 1: IMPORTS + DEVICE\n# ============================================================\n\nimport os\nimport random\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nimport seaborn as sns\n\nfrom PIL import Image\n\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nfrom torch.utils.data import Dataset, DataLoader\nfrom torchvision import transforms\n\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.metrics import (\n    accuracy_score,\n    precision_score,\n    recall_score,\n    f1_score,\n    confusion_matrix,\n    classification_report\n)\n\n# Reproducibility\nSEED = 42\n\nrandom.seed(SEED)\nnp.random.seed(SEED)\ntorch.manual_seed(SEED)\n\nif torch.cuda.is_available():\n    torch.cuda.manual_seed_all(SEED)\n\n# Device\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n\nprint(\"PyTorch version:\", torch.__version__)\nprint(\"Device:\", device)\n\nif torch.cuda.is_available():\n    print(\"GPU:\", torch.cuda.get_device_name(0))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T13:31:21.816015Z","iopub.execute_input":"2026-09-07T13:31:21.816779Z","iopub.status.idle":"2026-09-07T13:31:34.214859Z","shell.execute_reply.started":"2026-09-07T13:31:21.816752Z","shell.execute_reply":"2026-09-07T13:31:34.214082Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 2: APTOS DATASET PATHS\n# ============================================================\n\nBASE_PATH = \"/kaggle/input/competitions/aptos2019-blindness-detection\"\n\nTRAIN_CSV = os.path.join(BASE_PATH, \"train.csv\")\nTRAIN_DIR = os.path.join(BASE_PATH, \"train_images\")\n\nprint(\"Base path exists:\", os.path.exists(BASE_PATH))\nprint(\"CSV exists:\", os.path.exists(TRAIN_CSV))\nprint(\"Train images folder exists:\", os.path.exists(TRAIN_DIR))\n\nprint(\"\\nDataset contents:\")\nfor item in os.listdir(BASE_PATH):\n    print(\" -\", item)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T13:31:34.215843Z","iopub.execute_input":"2026-09-07T13:31:34.216311Z","iopub.status.idle":"2026-09-07T13:31:34.225679Z","shell.execute_reply.started":"2026-09-07T13:31:34.216287Z","shell.execute_reply":"2026-09-07T13:31:34.224804Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 3: LOAD APTOS LABELS\n# ============================================================\n\ndf = pd.read_csv(TRAIN_CSV)\n\nprint(\"Dataset shape:\", df.shape)\nprint(\"\\nColumns:\")\nprint(df.columns.tolist())\n\nprint(\"\\nFirst 5 rows:\")\ndisplay(df.head())\n\nprint(\"\\nClass distribution:\")\nprint(df[\"diagnosis\"].value_counts().sort_index())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T13:31:34.226966Z","iopub.execute_input":"2026-09-07T13:31:34.22797Z","iopub.status.idle":"2026-09-07T13:31:34.284475Z","shell.execute_reply.started":"2026-09-07T13:31:34.227928Z","shell.execute_reply":"2026-09-07T13:31:34.283529Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\n# ============================================================\n# CELL 4: CLASS DISTRIBUTION\n# ============================================================\n\nclass_counts = df[\"diagnosis\"].value_counts().sort_index()\n\nplt.figure(figsize=(8, 5))\n\nsns.barplot(\n    x=class_counts.index.astype(str),\n    y=class_counts.values\n)\n\nplt.title(\"APTOS 2019 Class Distribution\")\nplt.xlabel(\"DR Grade\")\nplt.ylabel(\"Number of Images\")\nplt.show()\n\nprint(class_counts)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T13:31:34.286268Z","iopub.execute_input":"2026-09-07T13:31:34.286646Z","iopub.status.idle":"2026-09-07T13:31:34.49496Z","shell.execute_reply.started":"2026-09-07T13:31:34.286619Z","shell.execute_reply":"2026-09-07T13:31:34.494239Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 5: VERIFY IMAGE PATHS\n# ============================================================\n\ndef find_image(image_id):\n    extensions = [\".png\", \".jpg\", \".jpeg\"]\n\n    for ext in extensions:\n        path = os.path.join(TRAIN_DIR, image_id + ext)\n\n        if os.path.exists(path):\n            return path\n\n    return None\n\n\ndf[\"image_path\"] = df[\"id_code\"].apply(find_image)\n\nprint(\"Total records:\", len(df))\nprint(\"Images found:\", df[\"image_path\"].notna().sum())\nprint(\"Images missing:\", df[\"image_path\"].isna().sum())\n\ndisplay(df.head())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T13:31:34.496078Z","iopub.execute_input":"2026-09-07T13:31:34.496402Z","iopub.status.idle":"2026-09-07T13:31:37.570063Z","shell.execute_reply.started":"2026-09-07T13:31:34.496371Z","shell.execute_reply":"2026-09-07T13:31:37.569299Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 6: STRATIFIED TRAIN / VALIDATION / TEST SPLIT\n# ============================================================\n\n# Remove any rows whose image path is missing\ndf = df.dropna(subset=[\"image_path\"]).reset_index(drop=True)\n\n# First: 80% train, 20% temporary\ntrain_df, temp_df = train_test_split(\n    df,\n    test_size=0.20,\n    stratify=df[\"diagnosis\"],\n    random_state=SEED\n)\n\n# Second: split temporary into 10% validation and 10% test\nval_df, test_df = train_test_split(\n    temp_df,\n    test_size=0.50,\n    stratify=temp_df[\"diagnosis\"],\n    random_state=SEED\n)\n\nprint(\"Train:\", len(train_df))\nprint(\"Validation:\", len(val_df))\nprint(\"Test:\", len(test_df))\n\nprint(\"\\nTrain distribution:\")\nprint(train_df[\"diagnosis\"].value_counts().sort_index())\n\nprint(\"\\nValidation distribution:\")\nprint(val_df[\"diagnosis\"].value_counts().sort_index())\n\nprint(\"\\nTest distribution:\")\nprint(test_df[\"diagnosis\"].value_counts().sort_index())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T13:31:37.571172Z","iopub.execute_input":"2026-09-07T13:31:37.57158Z","iopub.status.idle":"2026-09-07T13:31:37.594048Z","shell.execute_reply.started":"2026-09-07T13:31:37.571531Z","shell.execute_reply":"2026-09-07T13:31:37.593193Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 7: VISUALIZE SAMPLE IMAGES\n# ============================================================\n\nfig, axes = plt.subplots(2, 5, figsize=(18, 7))\n\nsample_df = (\n    train_df.groupby(\"diagnosis\", group_keys=False)\n    .apply(lambda x: x.sample(1, random_state=SEED))\n)\n\nfor ax, (_, row) in zip(axes.flatten(), sample_df.iterrows()):\n    img = Image.open(row[\"image_path\"]).convert(\"RGB\")\n    \n    ax.imshow(img)\n    ax.set_title(f\"DR Grade: {row['diagnosis']}\")\n    ax.axis(\"off\")\n\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T13:31:37.595046Z","iopub.execute_input":"2026-09-07T13:31:37.595464Z","iopub.status.idle":"2026-09-07T13:31:40.386255Z","shell.execute_reply.started":"2026-09-07T13:31:37.595427Z","shell.execute_reply":"2026-09-07T13:31:40.385081Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 6: BASIC DATASET EDA\n# ============================================================\n\nprint(\"=\" * 60)\nprint(\"APTOS DATASET BASIC EDA\")\nprint(\"=\" * 60)\n\nprint(\"\\nDataset shape:\")\nprint(df.shape)\n\nprint(\"\\nData types:\")\nprint(df.dtypes)\n\nprint(\"\\nMissing values:\")\nprint(df.isnull().sum())\n\nprint(\"\\nDuplicate rows:\")\nprint(df.duplicated().sum())\n\nprint(\"\\nUnique image IDs:\")\nprint(df[\"id_code\"].nunique())\n\nprint(\"\\nUnique diagnosis labels:\")\nprint(sorted(df[\"diagnosis\"].unique()))\n\nprint(\"\\nClass distribution:\")\nprint(df[\"diagnosis\"].value_counts().sort_index())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T13:31:40.387493Z","iopub.execute_input":"2026-09-07T13:31:40.388361Z","iopub.status.idle":"2026-09-07T13:31:40.403951Z","shell.execute_reply.started":"2026-09-07T13:31:40.38831Z","shell.execute_reply":"2026-09-07T13:31:40.4029Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 7: CLASS IMBALANCE ANALYSIS\n# ============================================================\n\nclass_counts = df[\"diagnosis\"].value_counts().sort_index()\n\nclass_percent = (\n    df[\"diagnosis\"]\n    .value_counts(normalize=True)\n    .sort_index() * 100\n)\n\nimbalance_df = pd.DataFrame({\n    \"Class\": class_counts.index,\n    \"Image_Count\": class_counts.values,\n    \"Percentage\": class_percent.values\n})\n\ndisplay(imbalance_df)\n\nprint(\"\\nMajority class:\", class_counts.idxmax(),\n      \"->\", class_counts.max(), \"images\")\n\nprint(\"Minority class:\", class_counts.idxmin(),\n      \"->\", class_counts.min(), \"images\")\n\nimbalance_ratio = class_counts.max() / class_counts.min()\n\nprint(\"\\nMajority / Minority ratio:\",\n      round(imbalance_ratio, 2))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T13:31:40.405065Z","iopub.execute_input":"2026-09-07T13:31:40.405397Z","iopub.status.idle":"2026-09-07T13:31:40.419438Z","shell.execute_reply.started":"2026-09-07T13:31:40.405372Z","shell.execute_reply":"2026-09-07T13:31:40.418629Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 8: IMAGE DIMENSION ANALYSIS\n# ============================================================\n\nfrom collections import Counter\n\nwidths = []\nheights = []\nchannels = []\n\nfor path in df[\"image_path\"]:\n    try:\n        img = Image.open(path)\n\n        widths.append(img.width)\n        heights.append(img.height)\n        channels.append(len(img.getbands()))\n\n    except Exception as e:\n        print(\"Error reading:\", path, e)\n\nprint(\"Number of images analyzed:\", len(widths))\n\nprint(\"\\nWidth statistics:\")\nprint(pd.Series(widths).describe())\n\nprint(\"\\nHeight statistics:\")\nprint(pd.Series(heights).describe())\n\nprint(\"\\nMost common image dimensions:\")\ndimension_counts = Counter(zip(widths, heights))\n\nfor dimension, count in dimension_counts.most_common(10):\n    print(f\"{dimension} -> {count} images\")\n\nprint(\"\\nChannel distribution:\")\nprint(Counter(channels))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T13:31:40.42051Z","iopub.execute_input":"2026-09-07T13:31:40.420985Z","iopub.status.idle":"2026-09-07T13:32:08.98395Z","shell.execute_reply.started":"2026-09-07T13:31:40.420959Z","shell.execute_reply":"2026-09-07T13:32:08.983253Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 9: VISUAL EDA — SAMPLES FROM ALL 5 DR CLASSES\n# ============================================================\n\nfig, axes = plt.subplots(5, 4, figsize=(14, 18))\n\nfor class_id in range(5):\n\n    class_data = df[df[\"diagnosis\"] == class_id]\n\n    samples = class_data.sample(\n        n=4,\n        random_state=SEED\n    )\n\n    for j, (_, row) in enumerate(samples.iterrows()):\n\n        img = Image.open(row[\"image_path\"]).convert(\"RGB\")\n\n        axes[class_id, j].imshow(img)\n\n        axes[class_id, j].set_title(\n            f\"DR Grade {class_id}\"\n        )\n\n        axes[class_id, j].axis(\"off\")\n\nplt.suptitle(\n    \"APTOS 2019 — Visual EDA by DR Grade\",\n    fontsize=16\n)\n\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T13:32:08.985026Z","iopub.execute_input":"2026-09-07T13:32:08.985364Z","iopub.status.idle":"2026-09-07T13:32:17.536388Z","shell.execute_reply.started":"2026-09-07T13:32:08.985337Z","shell.execute_reply":"2026-09-07T13:32:17.535519Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 10: FAST STRATIFIED BRIGHTNESS EDA\n# ============================================================\n\n# Take 60 images from each DR grade\nbrightness_sample = (\n    df.groupby(\"diagnosis\", group_keys=False)\n      .apply(lambda x: x.sample(\n          n=min(60, len(x)),\n          random_state=SEED\n      ))\n      .reset_index(drop=True)\n)\n\nprint(\"Images selected for brightness EDA:\", len(brightness_sample))\nprint(\"\\nClass distribution:\")\nprint(brightness_sample[\"diagnosis\"].value_counts().sort_index())\n\nbrightness_values = []\n\nfor path in brightness_sample[\"image_path\"]:\n\n    try:\n        img = Image.open(path).convert(\"L\")\n\n        # Downsample only for EDA\n        img.thumbnail((64, 64))\n\n        arr = np.asarray(img, dtype=np.float32)\n\n        brightness_values.append(arr.mean())\n\n    except Exception as e:\n        print(\"Error:\", path, e)\n        brightness_values.append(np.nan)\n\nbrightness_sample[\"mean_brightness\"] = brightness_values\n\nprint(\"\\nBrightness statistics:\")\nprint(brightness_sample[\"mean_brightness\"].describe())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T13:32:17.537938Z","iopub.execute_input":"2026-09-07T13:32:17.538306Z","iopub.status.idle":"2026-09-07T13:32:56.342164Z","shell.execute_reply.started":"2026-09-07T13:32:17.538269Z","shell.execute_reply":"2026-09-07T13:32:56.341372Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 11: BRIGHTNESS DISTRIBUTION\n# ============================================================\n\nplt.figure(figsize=(10, 5))\n\nplt.hist(\n    brightness_sample[\"mean_brightness\"].dropna(),\n    bins=30\n)\n\nplt.title(\"APTOS — Brightness Distribution (Stratified Sample)\")\nplt.xlabel(\"Mean Pixel Intensity\")\nplt.ylabel(\"Number of Images\")\n\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T13:32:56.345366Z","iopub.execute_input":"2026-09-07T13:32:56.345694Z","iopub.status.idle":"2026-09-07T13:32:56.501619Z","shell.execute_reply.started":"2026-09-07T13:32:56.345669Z","shell.execute_reply":"2026-09-07T13:32:56.500853Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 12: BRIGHTNESS BY DR GRADE\n# ============================================================\n\nplt.figure(figsize=(10, 6))\n\nsns.boxplot(\n    data=brightness_sample,\n    x=\"diagnosis\",\n    y=\"mean_brightness\"\n)\n\nplt.title(\"APTOS — Brightness by DR Grade\")\nplt.xlabel(\"DR Grade\")\nplt.ylabel(\"Mean Pixel Intensity\")\n\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T13:32:56.502533Z","iopub.execute_input":"2026-09-07T13:32:56.502956Z","iopub.status.idle":"2026-09-07T13:32:56.658228Z","shell.execute_reply.started":"2026-09-07T13:32:56.502932Z","shell.execute_reply":"2026-09-07T13:32:56.657614Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 13: FOCUS / BLUR EDA\n# ============================================================\n\nimport cv2\n\nfocus_values = []\n\nfor path in brightness_sample[\"image_path\"]:\n\n    try:\n        img = cv2.imread(path, cv2.IMREAD_GRAYSCALE)\n\n        # Downsample for faster EDA\n        img = cv2.resize(img, (256, 256))\n\n        # Laplacian variance = simple focus/sharpness measure\n        focus_score = cv2.Laplacian(\n            img,\n            cv2.CV_64F\n        ).var()\n\n        focus_values.append(focus_score)\n\n    except Exception as e:\n        print(\"Error:\", path, e)\n        focus_values.append(np.nan)\n\nbrightness_sample[\"focus_score\"] = focus_values\n\nprint(\"Focus analysis completed.\")\n\nprint(\"\\nFocus-score statistics:\")\nprint(\n    brightness_sample[\"focus_score\"]\n    .describe()\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T13:32:56.659068Z","iopub.execute_input":"2026-09-07T13:32:56.659398Z","iopub.status.idle":"2026-09-07T13:33:31.373011Z","shell.execute_reply.started":"2026-09-07T13:32:56.659349Z","shell.execute_reply":"2026-09-07T13:33:31.37228Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 14: FOCUS / BLUR DISTRIBUTION\n# ============================================================\n\nplt.figure(figsize=(10, 5))\n\nplt.hist(\n    brightness_sample[\"focus_score\"].dropna(),\n    bins=40\n)\n\nplt.title(\"APTOS — Focus / Blur Score Distribution\")\nplt.xlabel(\"Laplacian Variance\")\nplt.ylabel(\"Number of Images\")\n\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T13:33:31.374147Z","iopub.execute_input":"2026-09-07T13:33:31.374492Z","iopub.status.idle":"2026-09-07T13:33:31.540265Z","shell.execute_reply.started":"2026-09-07T13:33:31.374455Z","shell.execute_reply":"2026-09-07T13:33:31.539627Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 15: VISUALIZE SHARPEST AND LOWEST-FOCUS IMAGES\n# ============================================================\n\nvalid_focus = brightness_sample.dropna(\n    subset=[\"focus_score\"]\n)\n\nsharp_images = valid_focus.nlargest(\n    4, \"focus_score\"\n)\n\nlow_focus_images = valid_focus.nsmallest(\n    4, \"focus_score\"\n)\n\nfig, axes = plt.subplots(\n    2, 4,\n    figsize=(16, 8)\n)\n\n# Sharpest\nfor j, (_, row) in enumerate(sharp_images.iterrows()):\n\n    img = Image.open(\n        row[\"image_path\"]\n    ).convert(\"RGB\")\n\n    axes[0, j].imshow(img)\n\n    axes[0, j].set_title(\n        f\"High Focus\\n\"\n        f\"Grade {row['diagnosis']}\\n\"\n        f\"Score: {row['focus_score']:.1f}\"\n    )\n\n    axes[0, j].axis(\"off\")\n\n\n# Lowest focus\nfor j, (_, row) in enumerate(low_focus_images.iterrows()):\n\n    img = Image.open(\n        row[\"image_path\"]\n    ).convert(\"RGB\")\n\n    axes[1, j].imshow(img)\n\n    axes[1, j].set_title(\n        f\"Low Focus\\n\"\n        f\"Grade {row['diagnosis']}\\n\"\n        f\"Score: {row['focus_score']:.1f}\"\n    )\n\n    axes[1, j].axis(\"off\")\n\n\nplt.suptitle(\n    \"APTOS — Focus Quality: Extreme Examples\",\n    fontsize=16\n)\n\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T13:33:31.54131Z","iopub.execute_input":"2026-09-07T13:33:31.541972Z","iopub.status.idle":"2026-09-07T13:33:35.290131Z","shell.execute_reply.started":"2026-09-07T13:33:31.541938Z","shell.execute_reply":"2026-09-07T13:33:35.287204Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 16: FOV / RETINAL COVERAGE EDA\n# ============================================================\n\nfov_values = []\n\nfor path in brightness_sample[\"image_path\"]:\n\n    try:\n        img = Image.open(path).convert(\"RGB\")\n\n        # Downsample for fast analysis\n        img = img.resize((256, 256))\n\n        arr = np.asarray(img, dtype=np.float32)\n\n        # Convert RGB to grayscale\n        gray = (\n            0.299 * arr[:, :, 0] +\n            0.587 * arr[:, :, 1] +\n            0.114 * arr[:, :, 2]\n        )\n\n        # Pixels considered part of retinal field\n        # Black background is approximately zero\n        retinal_pixels = gray > 10\n\n        # Percentage of image occupied by retinal region\n        coverage = retinal_pixels.mean() * 100\n\n        fov_values.append(coverage)\n\n    except Exception as e:\n        print(\"Error:\", path, e)\n        fov_values.append(np.nan)\n\nbrightness_sample[\"fov_coverage\"] = fov_values\n\nprint(\"FOV analysis completed.\")\n\nprint(\"\\nFOV / retinal coverage statistics:\")\nprint(\n    brightness_sample[\"fov_coverage\"].describe()\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T13:33:35.291253Z","iopub.execute_input":"2026-09-07T13:33:35.291658Z","iopub.status.idle":"2026-09-07T13:34:23.593343Z","shell.execute_reply.started":"2026-09-07T13:33:35.29163Z","shell.execute_reply":"2026-09-07T13:34:23.592419Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 17: FOV COVERAGE DISTRIBUTION\n# ============================================================\n\nplt.figure(figsize=(10, 5))\n\nplt.hist(\n    brightness_sample[\"fov_coverage\"].dropna(),\n    bins=30\n)\n\nplt.title(\"APTOS — Retinal Field Coverage Distribution\")\nplt.xlabel(\"Retinal Coverage (%)\")\nplt.ylabel(\"Number of Images\")\n\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T13:34:23.594453Z","iopub.execute_input":"2026-09-07T13:34:23.594795Z","iopub.status.idle":"2026-09-07T13:34:23.749119Z","shell.execute_reply.started":"2026-09-07T13:34:23.59476Z","shell.execute_reply":"2026-09-07T13:34:23.74815Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 18: STRATIFIED TRAIN / VALIDATION / TEST SPLIT\n# ============================================================\n\nfrom sklearn.model_selection import train_test_split\n\n# Make sure all valid images are present\nmodel_df = df.dropna(\n    subset=[\"image_path\"]\n).copy()\n\n# First split: 80% train, 20% temporary\ntrain_df, temp_df = train_test_split(\n    model_df,\n    test_size=0.20,\n    stratify=model_df[\"diagnosis\"],\n    random_state=SEED\n)\n\n# Second split: temporary -> 10% validation + 10% test\nval_df, test_df = train_test_split(\n    temp_df,\n    test_size=0.50,\n    stratify=temp_df[\"diagnosis\"],\n    random_state=SEED\n)\n\nprint(\"=\" * 60)\nprint(\"DATASET SPLIT\")\nprint(\"=\" * 60)\n\nprint(f\"Total:       {len(model_df)}\")\nprint(f\"Train:       {len(train_df)}\")\nprint(f\"Validation:  {len(val_df)}\")\nprint(f\"Test:        {len(test_df)}\")\n\nprint(\"\\nTRAIN distribution:\")\nprint(train_df[\"diagnosis\"].value_counts().sort_index())\n\nprint(\"\\nVALIDATION distribution:\")\nprint(val_df[\"diagnosis\"].value_counts().sort_index())\n\nprint(\"\\nTEST distribution:\")\nprint(test_df[\"diagnosis\"].value_counts().sort_index())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T13:34:23.750289Z","iopub.execute_input":"2026-09-07T13:34:23.750682Z","iopub.status.idle":"2026-09-07T13:34:23.772723Z","shell.execute_reply.started":"2026-09-07T13:34:23.750645Z","shell.execute_reply":"2026-09-07T13:34:23.772034Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 19: PREPROCESSING LIBRARIES\n# ============================================================\n\nimport numpy as np\nimport matplotlib.pyplot as plt\n\nfrom PIL import Image\n\nfrom skimage.color import rgb2hsv, hsv2rgb\nfrom skimage.filters import median\nfrom skimage.morphology import disk\nfrom skimage.exposure import equalize_adapthist\n\nprint(\"Preprocessing libraries loaded successfully.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T13:34:23.773663Z","iopub.execute_input":"2026-09-07T13:34:23.774122Z","iopub.status.idle":"2026-09-07T13:34:23.97219Z","shell.execute_reply.started":"2026-09-07T13:34:23.774095Z","shell.execute_reply":"2026-09-07T13:34:23.971576Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 20: NETRX IMAGE PREPROCESSING\n# ============================================================\n\ndef preprocess_fundus(image):\n    \"\"\"\n    NetrX preprocessing:\n    \n    1. Convert image to RGB\n    2. Noise reduction using median filtering\n    3. Convert RGB -> HSV\n    4. Adaptive contrast adjustment on brightness channel\n    5. Convert HSV -> RGB\n    \"\"\"\n\n    # --------------------------------------------------------\n    # 1. Convert PIL image to RGB numpy array\n    # --------------------------------------------------------\n    image = image.convert(\"RGB\")\n\n    img = np.asarray(image, dtype=np.float32) / 255.0\n\n    # --------------------------------------------------------\n    # 2. Noise reduction\n    # --------------------------------------------------------\n    # Median filtering helps reduce small noise while\n    # preserving important retinal boundaries.\n    denoised = median(\n        img,\n        footprint=disk(1),\n        channel_axis=-1\n    )\n\n    # --------------------------------------------------------\n    # 3. RGB -> HSV\n    # --------------------------------------------------------\n    hsv = rgb2hsv(denoised)\n\n    # --------------------------------------------------------\n    # 4. Adaptive contrast adjustment\n    # --------------------------------------------------------\n    # Apply CLAHE to the Value channel.\n    hsv[:, :, 2] = equalize_adapthist(\n        hsv[:, :, 2],\n        clip_limit=0.01\n    )\n\n    # --------------------------------------------------------\n    # 5. HSV -> RGB\n    # --------------------------------------------------------\n    processed = hsv2rgb(hsv)\n\n    # Keep values in valid range\n    processed = np.clip(processed, 0, 1)\n\n    return processed","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T13:34:23.973467Z","iopub.execute_input":"2026-09-07T13:34:23.974335Z","iopub.status.idle":"2026-09-07T13:34:23.980457Z","shell.execute_reply.started":"2026-09-07T13:34:23.974301Z","shell.execute_reply":"2026-09-07T13:34:23.979507Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 20: NETRX IMAGE PREPROCESSING — CORRECTED\n# ============================================================\n\nimport numpy as np\n\nfrom scipy.ndimage import median_filter\nfrom skimage.color import rgb2hsv, hsv2rgb\nfrom skimage.exposure import equalize_adapthist\n\n\ndef preprocess_fundus(image):\n    \"\"\"\n    NetrX preprocessing pipeline:\n\n    1. Convert image to RGB\n    2. Noise reduction using median filtering\n    3. RGB -> HSV\n    4. Controlled adaptive contrast adjustment\n    5. HSV -> RGB\n    \"\"\"\n\n    # --------------------------------------------------------\n    # 1. PIL -> RGB NumPy array\n    # --------------------------------------------------------\n    image = image.convert(\"RGB\")\n\n    img = np.asarray(\n        image,\n        dtype=np.float32\n    ) / 255.0\n\n    # --------------------------------------------------------\n    # 2. Noise reduction\n    # --------------------------------------------------------\n    # 3x3 median filter applied spatially.\n    # The third dimension (RGB channels) is kept unchanged.\n    denoised = median_filter(\n        img,\n        size=(3, 3, 1)\n    )\n\n    # --------------------------------------------------------\n    # 3. RGB -> HSV\n    # --------------------------------------------------------\n    hsv = rgb2hsv(denoised)\n\n    # --------------------------------------------------------\n    # 4. Controlled adaptive contrast\n    # --------------------------------------------------------\n    # Apply CLAHE only to the Value channel.\n    hsv[:, :, 2] = equalize_adapthist(\n        hsv[:, :, 2],\n        clip_limit=0.01\n    )\n\n    # --------------------------------------------------------\n    # 5. HSV -> RGB\n    # --------------------------------------------------------\n    processed = hsv2rgb(hsv)\n\n    # Keep values within valid range\n    processed = np.clip(\n        processed,\n        0,\n        1\n    )\n\n    return processed","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T13:34:23.981631Z","iopub.execute_input":"2026-09-07T13:34:23.981951Z","iopub.status.idle":"2026-09-07T13:34:23.996279Z","shell.execute_reply.started":"2026-09-07T13:34:23.981916Z","shell.execute_reply":"2026-09-07T13:34:23.995354Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 21: ORIGINAL vs PREPROCESSED\n# ============================================================\n\nsample_images = (\n    train_df\n    .groupby(\"diagnosis\", group_keys=False)\n    .apply(\n        lambda x: x.sample(\n            1,\n            random_state=SEED\n        )\n    )\n    .reset_index(drop=True)\n)\n\nfig, axes = plt.subplots(\n    5,\n    2,\n    figsize=(10, 22)\n)\n\nfor row_idx, (_, row) in enumerate(\n    sample_images.iterrows()\n):\n\n    # Original\n    original = Image.open(\n        row[\"image_path\"]\n    ).convert(\"RGB\")\n\n    # NetrX preprocessing\n    processed = preprocess_fundus(\n        original\n    )\n\n    # Original\n    axes[row_idx, 0].imshow(original)\n    axes[row_idx, 0].set_title(\n        f\"Original — DR Grade {row['diagnosis']}\"\n    )\n    axes[row_idx, 0].axis(\"off\")\n\n    # Processed\n    axes[row_idx, 1].imshow(processed)\n    axes[row_idx, 1].set_title(\n        f\"Processed — DR Grade {row['diagnosis']}\"\n    )\n    axes[row_idx, 1].axis(\"off\")\n\nplt.suptitle(\n    \"NetrX Preprocessing — Original vs Processed\",\n    fontsize=16\n)\n\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T13:34:23.997304Z","iopub.execute_input":"2026-09-07T13:34:23.997752Z","iopub.status.idle":"2026-09-07T13:34:46.872011Z","shell.execute_reply.started":"2026-09-07T13:34:23.997718Z","shell.execute_reply":"2026-09-07T13:34:46.870866Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 22: CLASS WEIGHTS FOR IMBALANCED TRAINING DATA\n# ============================================================\n\nfrom sklearn.utils.class_weight import compute_class_weight\n\nclasses = np.sort(\n    train_df[\"diagnosis\"].unique()\n)\n\nclass_weights = compute_class_weight(\n    class_weight=\"balanced\",\n    classes=classes,\n    y=train_df[\"diagnosis\"]\n)\n\nclass_weights = torch.tensor(\n    class_weights,\n    dtype=torch.float32\n)\n\nprint(\"Classes:\")\nprint(classes)\n\nprint(\"\\nClass weights:\")\n\nfor cls, weight in zip(classes, class_weights):\n    print(f\"DR Grade {cls}: {weight:.4f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T13:34:46.873484Z","iopub.execute_input":"2026-09-07T13:34:46.873848Z","iopub.status.idle":"2026-09-07T13:34:46.916689Z","shell.execute_reply.started":"2026-09-07T13:34:46.873813Z","shell.execute_reply":"2026-09-07T13:34:46.915973Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 23: PYTORCH DATASET + AUGMENTATION\n# ============================================================\n\nimport torch\nfrom torch.utils.data import Dataset, DataLoader\n\nfrom torchvision import transforms\n\n# ------------------------------------------------------------\n# Image size\n# ------------------------------------------------------------\nIMG_SIZE = 224\n\n# ------------------------------------------------------------\n# ImageNet normalization\n# Useful because we will later compare with pretrained\n# transfer-learning models.\n# ------------------------------------------------------------\nIMAGENET_MEAN = [0.485, 0.456, 0.406]\nIMAGENET_STD  = [0.229, 0.224, 0.225]\n\n\n# ============================================================\n# TRAIN TRANSFORMS\n# ============================================================\n\ntrain_transform = transforms.Compose([\n\n    transforms.Resize(\n        (IMG_SIZE, IMG_SIZE)\n    ),\n\n    transforms.RandomHorizontalFlip(\n        p=0.5\n    ),\n\n    transforms.RandomRotation(\n        degrees=10\n    ),\n\n    transforms.RandomAffine(\n        degrees=0,\n        translate=(0.05, 0.05),\n        scale=(0.95, 1.05)\n    ),\n\n    transforms.ColorJitter(\n        brightness=0.15,\n        contrast=0.15,\n        saturation=0.10\n    ),\n\n    transforms.ToTensor(),\n\n    transforms.Normalize(\n        mean=IMAGENET_MEAN,\n        std=IMAGENET_STD\n    )\n])\n\n\n# ============================================================\n# VALIDATION / TEST TRANSFORMS\n# ============================================================\n\neval_transform = transforms.Compose([\n\n    transforms.Resize(\n        (IMG_SIZE, IMG_SIZE)\n    ),\n\n    transforms.ToTensor(),\n\n    transforms.Normalize(\n        mean=IMAGENET_MEAN,\n        std=IMAGENET_STD\n    )\n])\n\n\nprint(\"Training transformations created.\")\nprint(\"Evaluation transformations created.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T13:34:46.91766Z","iopub.execute_input":"2026-09-07T13:34:46.917963Z","iopub.status.idle":"2026-09-07T13:34:46.925517Z","shell.execute_reply.started":"2026-09-07T13:34:46.91794Z","shell.execute_reply":"2026-09-07T13:34:46.924664Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 24: APTOS PYTORCH DATASET\n# ============================================================\n\nclass APTOSDataset(Dataset):\n\n    def __init__(\n        self,\n        dataframe,\n        transform=None\n    ):\n\n        self.dataframe = dataframe.reset_index(\n            drop=True\n        )\n\n        self.transform = transform\n\n    def __len__(self):\n\n        return len(self.dataframe)\n\n    def __getitem__(self, index):\n\n        row = self.dataframe.iloc[index]\n\n        image = Image.open(\n            row[\"image_path\"]\n        ).convert(\"RGB\")\n\n        label = int(\n            row[\"diagnosis\"]\n        )\n\n        if self.transform is not None:\n            image = self.transform(image)\n\n        return image, label\n\n\nprint(\"APTOSDataset class created.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T13:34:46.926632Z","iopub.execute_input":"2026-09-07T13:34:46.926929Z","iopub.status.idle":"2026-09-07T13:34:46.941751Z","shell.execute_reply.started":"2026-09-07T13:34:46.926895Z","shell.execute_reply":"2026-09-07T13:34:46.940885Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 25: CREATE DATASETS\n# ============================================================\n\ntrain_dataset = APTOSDataset(\n    train_df,\n    transform=train_transform\n)\n\nval_dataset = APTOSDataset(\n    val_df,\n    transform=eval_transform\n)\n\ntest_dataset = APTOSDataset(\n    test_df,\n    transform=eval_transform\n)\n\nprint(\"Train images:\", len(train_dataset))\nprint(\"Validation images:\", len(val_dataset))\nprint(\"Test images:\", len(test_dataset))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T13:34:46.942898Z","iopub.execute_input":"2026-09-07T13:34:46.943239Z","iopub.status.idle":"2026-09-07T13:34:46.95721Z","shell.execute_reply.started":"2026-09-07T13:34:46.943213Z","shell.execute_reply":"2026-09-07T13:34:46.956498Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 26: DATALOADERS + DEVICE\n# ============================================================\n\nBATCH_SIZE = 32\n\ndevice = torch.device(\n    \"cuda\" if torch.cuda.is_available() else \"cpu\"\n)\n\nprint(\"Device:\", device)\n\n\ntrain_loader = DataLoader(\n    train_dataset,\n    batch_size=BATCH_SIZE,\n    shuffle=True,\n    num_workers=2,\n    pin_memory=True\n)\n\nval_loader = DataLoader(\n    val_dataset,\n    batch_size=BATCH_SIZE,\n    shuffle=False,\n    num_workers=2,\n    pin_memory=True\n)\n\ntest_loader = DataLoader(\n    test_dataset,\n    batch_size=BATCH_SIZE,\n    shuffle=False,\n    num_workers=2,\n    pin_memory=True\n)\n\nprint(\"\\nTrain batches:\", len(train_loader))\nprint(\"Validation batches:\", len(val_loader))\nprint(\"Test batches:\", len(test_loader))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T13:34:46.958196Z","iopub.execute_input":"2026-09-07T13:34:46.958537Z","iopub.status.idle":"2026-09-07T13:34:46.969766Z","shell.execute_reply.started":"2026-09-07T13:34:46.958501Z","shell.execute_reply":"2026-09-07T13:34:46.969003Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 27: AUGMENTATION SANITY CHECK\n# ============================================================\n\nimport matplotlib.pyplot as plt\n\n# Get one training batch\nimages, labels = next(iter(train_loader))\n\n# Number of images to display\nnum_images = 8\n\nplt.figure(figsize=(14, 7))\n\nfor i in range(num_images):\n\n    # Undo ImageNet normalization for visualization\n    img = images[i].permute(1, 2, 0).numpy()\n\n    mean = np.array(IMAGENET_MEAN)\n    std = np.array(IMAGENET_STD)\n\n    img = img * std + mean\n    img = np.clip(img, 0, 1)\n\n    plt.subplot(2, 4, i + 1)\n\n    plt.imshow(img)\n\n    plt.title(\n        f\"DR Grade {labels[i].item()}\"\n    )\n\n    plt.axis(\"off\")\n\nplt.suptitle(\n    \"APTOS — Training Augmentation Sanity Check\",\n    fontsize=15\n)\n\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T13:34:46.970701Z","iopub.execute_input":"2026-09-07T13:34:46.970998Z","iopub.status.idle":"2026-09-07T13:34:57.263666Z","shell.execute_reply.started":"2026-09-07T13:34:46.970967Z","shell.execute_reply":"2026-09-07T13:34:57.26253Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 28: MOVE CLASS WEIGHTS TO DEVICE\n# ============================================================\n\nclass_weights = class_weights.to(device)\n\nprint(\"Class weights:\")\nprint(class_weights)\n\nprint(\"\\nWeights device:\")\nprint(class_weights.device)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T13:34:57.265197Z","iopub.execute_input":"2026-09-07T13:34:57.266046Z","iopub.status.idle":"2026-09-07T13:34:57.488284Z","shell.execute_reply.started":"2026-09-07T13:34:57.26601Z","shell.execute_reply":"2026-09-07T13:34:57.48762Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 29: CREATE FULL PROCESSED DATASET\n# ============================================================\n\nimport os\nfrom pathlib import Path\n\nPROCESSED_DIR = Path(\"/kaggle/working/aptos_processed\")\n\nPROCESSED_DIR.mkdir(\n    parents=True,\n    exist_ok=True\n)\n\nprint(\"Processed dataset directory:\")\nprint(PROCESSED_DIR)\n\nprint(\"\\nAlready processed images:\",\n      len(list(PROCESSED_DIR.glob(\"*.jpg\"))))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T13:34:57.489278Z","iopub.execute_input":"2026-09-07T13:34:57.489502Z","iopub.status.idle":"2026-09-07T13:34:57.495895Z","shell.execute_reply.started":"2026-09-07T13:34:57.48948Z","shell.execute_reply":"2026-09-07T13:34:57.495076Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 30: FAST FULL-DATASET PREPROCESSING FUNCTION\n# ============================================================\n\nfrom PIL import Image\nimport numpy as np\n\nfrom scipy.ndimage import median_filter\nfrom skimage.color import rgb2hsv, hsv2rgb\nfrom skimage.exposure import equalize_adapthist\n\n\ndef preprocess_and_save(row):\n    \"\"\"\n    NetrX preprocessing for one APTOS image.\n\n    Steps:\n    1. Read RGB fundus image\n    2. Resize to 224x224\n    3. Median noise reduction\n    4. RGB -> HSV\n    5. Controlled adaptive contrast\n    6. HSV -> RGB\n    7. Save processed JPEG\n    \"\"\"\n\n    image_id = row[\"id_code\"]\n    input_path = row[\"image_path\"]\n\n    output_path = PROCESSED_DIR / f\"{image_id}.jpg\"\n\n    # Skip if already processed\n    if output_path.exists():\n        return True\n\n    try:\n\n        # ----------------------------------------------------\n        # Read image\n        # ----------------------------------------------------\n        image = Image.open(\n            input_path\n        ).convert(\"RGB\")\n\n        # ----------------------------------------------------\n        # Resize first\n        # ----------------------------------------------------\n        image = image.resize(\n            (224, 224),\n            Image.Resampling.LANCZOS\n        )\n\n        img = np.asarray(\n            image,\n            dtype=np.float32\n        ) / 255.0\n\n        # ----------------------------------------------------\n        # Noise reduction\n        # ----------------------------------------------------\n        denoised = median_filter(\n            img,\n            size=(3, 3, 1)\n        )\n\n        # ----------------------------------------------------\n        # RGB -> HSV\n        # ----------------------------------------------------\n        hsv = rgb2hsv(\n            denoised\n        )\n\n        # ----------------------------------------------------\n        # Controlled adaptive contrast\n        # ----------------------------------------------------\n        hsv[:, :, 2] = equalize_adapthist(\n            hsv[:, :, 2],\n            clip_limit=0.01\n        )\n\n        # ----------------------------------------------------\n        # HSV -> RGB\n        # ----------------------------------------------------\n        processed = hsv2rgb(\n            hsv\n        )\n\n        processed = np.clip(\n            processed * 255,\n            0,\n            255\n        ).astype(np.uint8)\n\n        # ----------------------------------------------------\n        # Save\n        # ----------------------------------------------------\n        Image.fromarray(\n            processed\n        ).save(\n            output_path,\n            format=\"JPEG\",\n            quality=95\n        )\n\n        return True\n\n    except Exception as e:\n\n        print(\n            f\"ERROR: {image_id} -> {e}\"\n        )\n\n        return False","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T13:34:57.497048Z","iopub.execute_input":"2026-09-07T13:34:57.497411Z","iopub.status.idle":"2026-09-07T13:34:57.514471Z","shell.execute_reply.started":"2026-09-07T13:34:57.497388Z","shell.execute_reply":"2026-09-07T13:34:57.513606Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 31: BATCH PROCESS ALL APTOS IMAGES\n# ============================================================\n\nfrom concurrent.futures import ThreadPoolExecutor\nfrom tqdm.auto import tqdm\n\nall_rows = [\n    row\n    for _, row in df.iterrows()\n]\n\nprint(\"Total images to process:\", len(all_rows))\n\n# Use a small number of workers to avoid overloading Kaggle\nMAX_WORKERS = 4\n\nwith ThreadPoolExecutor(\n    max_workers=MAX_WORKERS\n) as executor:\n\n    results = list(\n        tqdm(\n            executor.map(\n                preprocess_and_save,\n                all_rows\n            ),\n            total=len(all_rows),\n            desc=\"Processing APTOS\"\n        )\n    )\n\nsuccessful = sum(results)\nfailed = len(results) - successful\n\nprint(\"\\n\" + \"=\" * 60)\nprint(\"PROCESSING COMPLETE\")\nprint(\"=\" * 60)\n\nprint(\"Successful:\", successful)\nprint(\"Failed:\", failed)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T13:34:57.515329Z","iopub.execute_input":"2026-09-07T13:34:57.515683Z","iopub.status.idle":"2026-09-07T13:39:37.020188Z","shell.execute_reply.started":"2026-09-07T13:34:57.515638Z","shell.execute_reply":"2026-09-07T13:39:37.0195Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 32: VERIFY PROCESSED DATASET\n# ============================================================\n\nprocessed_files = list(\n    PROCESSED_DIR.glob(\"*.jpg\")\n)\n\nprint(\"=\" * 60)\nprint(\"PROCESSED DATASET VERIFICATION\")\nprint(\"=\" * 60)\n\nprint(\"Original images :\", len(df))\nprint(\"Processed images:\", len(processed_files))\n\nprint(\n    \"\\nDataset complete:\",\n    len(processed_files) == len(df)\n)\n\nif len(processed_files) == len(df):\n    print(\"✅ All 3662 images processed successfully.\")\nelse:\n    print(\"⚠️ Some images are missing.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T13:39:37.02108Z","iopub.execute_input":"2026-09-07T13:39:37.02163Z","iopub.status.idle":"2026-09-07T13:39:37.040472Z","shell.execute_reply.started":"2026-09-07T13:39:37.021604Z","shell.execute_reply":"2026-09-07T13:39:37.039955Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 33: USE PROCESSED IMAGES FOR MODEL TRAINING\n# ============================================================\n\ndef get_processed_path(image_id):\n    return str(\n        PROCESSED_DIR / f\"{image_id}.jpg\"\n    )\n\n\ntrain_df = train_df.copy()\nval_df = val_df.copy()\ntest_df = test_df.copy()\n\ntrain_df[\"processed_path\"] = train_df[\"id_code\"].apply(\n    get_processed_path\n)\n\nval_df[\"processed_path\"] = val_df[\"id_code\"].apply(\n    get_processed_path\n)\n\ntest_df[\"processed_path\"] = test_df[\"id_code\"].apply(\n    get_processed_path\n)\n\nprint(\"Train processed paths:\", len(train_df))\nprint(\"Validation processed paths:\", len(val_df))\nprint(\"Test processed paths:\", len(test_df))\n\nprint(\"\\nExample:\")\nprint(train_df[[\n    \"id_code\",\n    \"diagnosis\",\n    \"processed_path\"\n]].head())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T13:39:37.041323Z","iopub.execute_input":"2026-09-07T13:39:37.041694Z","iopub.status.idle":"2026-09-07T13:39:37.07885Z","shell.execute_reply.started":"2026-09-07T13:39:37.04167Z","shell.execute_reply":"2026-09-07T13:39:37.078036Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 34: PROCESSED APTOS DATASET\n# ============================================================\n\nclass APTOSProcessedDataset(Dataset):\n\n    def __init__(self, dataframe, transform=None):\n\n        self.dataframe = dataframe.reset_index(drop=True)\n        self.transform = transform\n\n    def __len__(self):\n        return len(self.dataframe)\n\n    def __getitem__(self, index):\n\n        row = self.dataframe.iloc[index]\n\n        # IMPORTANT:\n        # Use the MATLAB-equivalent processed image\n        image = Image.open(\n            row[\"processed_path\"]\n        ).convert(\"RGB\")\n\n        label = int(\n            row[\"diagnosis\"]\n        )\n\n        if self.transform is not None:\n            image = self.transform(image)\n\n        return image, label\n\n\nprint(\"Processed APTOS Dataset class created.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T13:39:37.079923Z","iopub.execute_input":"2026-09-07T13:39:37.080698Z","iopub.status.idle":"2026-09-07T13:39:37.086642Z","shell.execute_reply.started":"2026-09-07T13:39:37.080664Z","shell.execute_reply":"2026-09-07T13:39:37.085816Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 35: CREATE PROCESSED DATASETS\n# ============================================================\n\ntrain_dataset = APTOSProcessedDataset(\n    train_df,\n    transform=train_transform\n)\n\nval_dataset = APTOSProcessedDataset(\n    val_df,\n    transform=eval_transform\n)\n\ntest_dataset = APTOSProcessedDataset(\n    test_df,\n    transform=eval_transform\n)\n\nprint(\"Train:\", len(train_dataset))\nprint(\"Validation:\", len(val_dataset))\nprint(\"Test:\", len(test_dataset))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T13:39:37.087656Z","iopub.execute_input":"2026-09-07T13:39:37.087956Z","iopub.status.idle":"2026-09-07T13:39:37.100623Z","shell.execute_reply.started":"2026-09-07T13:39:37.087917Z","shell.execute_reply":"2026-09-07T13:39:37.099782Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 36: PROCESSED DATA LOADERS\n# ============================================================\n\ntrain_loader = DataLoader(\n    train_dataset,\n    batch_size=BATCH_SIZE,\n    shuffle=True,\n    num_workers=2,\n    pin_memory=True\n)\n\nval_loader = DataLoader(\n    val_dataset,\n    batch_size=BATCH_SIZE,\n    shuffle=False,\n    num_workers=2,\n    pin_memory=True\n)\n\ntest_loader = DataLoader(\n    test_dataset,\n    batch_size=BATCH_SIZE,\n    shuffle=False,\n    num_workers=2,\n    pin_memory=True\n)\n\nprint(\"Processed DataLoaders ready.\")\n\nprint(\"Train batches:\", len(train_loader))\nprint(\"Validation batches:\", len(val_loader))\nprint(\"Test batches:\", len(test_loader))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T13:39:37.101705Z","iopub.execute_input":"2026-09-07T13:39:37.102413Z","iopub.status.idle":"2026-09-07T13:39:37.115543Z","shell.execute_reply.started":"2026-09-07T13:39:37.102377Z","shell.execute_reply":"2026-09-07T13:39:37.114878Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 37: FINAL DATA PIPELINE CHECK\n# ============================================================\n\nimages, labels = next(iter(train_loader))\n\nprint(\"Batch shape:\", images.shape)\nprint(\"Labels shape:\", labels.shape)\nprint(\"Labels:\", labels[:10].tolist())\nprint(\"Device available:\", device)\n\nprint(\"\\nExpected batch shape:\")\nprint(\"(32, 3, 224, 224)\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T13:39:37.123459Z","iopub.execute_input":"2026-09-07T13:39:37.123813Z","iopub.status.idle":"2026-09-07T13:39:37.574147Z","shell.execute_reply.started":"2026-09-07T13:39:37.123747Z","shell.execute_reply":"2026-09-07T13:39:37.573332Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 38: CNN FROM SCRATCH\n# ============================================================\n\nimport torch\nimport torch.nn as nn\n\n\nclass NetrXCNN(nn.Module):\n\n    def __init__(self, num_classes=5):\n\n        super(NetrXCNN, self).__init__()\n\n        self.features = nn.Sequential(\n\n            # ------------------------------------------------\n            # Block 1\n            # ------------------------------------------------\n            nn.Conv2d(\n                3, 32,\n                kernel_size=3,\n                padding=1\n            ),\n            nn.BatchNorm2d(32),\n            nn.ReLU(inplace=True),\n\n            nn.Conv2d(\n                32, 32,\n                kernel_size=3,\n                padding=1\n            ),\n            nn.BatchNorm2d(32),\n            nn.ReLU(inplace=True),\n\n            nn.MaxPool2d(2),\n            nn.Dropout2d(0.10),\n\n\n            # ------------------------------------------------\n            # Block 2\n            # ------------------------------------------------\n            nn.Conv2d(\n                32, 64,\n                kernel_size=3,\n                padding=1\n            ),\n            nn.BatchNorm2d(64),\n            nn.ReLU(inplace=True),\n\n            nn.Conv2d(\n                64, 64,\n                kernel_size=3,\n                padding=1\n            ),\n            nn.BatchNorm2d(64),\n            nn.ReLU(inplace=True),\n\n            nn.MaxPool2d(2),\n            nn.Dropout2d(0.15),\n\n\n            # ------------------------------------------------\n            # Block 3\n            # ------------------------------------------------\n            nn.Conv2d(\n                64, 128,\n                kernel_size=3,\n                padding=1\n            ),\n            nn.BatchNorm2d(128),\n            nn.ReLU(inplace=True),\n\n            nn.Conv2d(\n                128, 128,\n                kernel_size=3,\n                padding=1\n            ),\n            nn.BatchNorm2d(128),\n            nn.ReLU(inplace=True),\n\n            nn.MaxPool2d(2),\n            nn.Dropout2d(0.20),\n\n\n            # ------------------------------------------------\n            # Block 4\n            # ------------------------------------------------\n            nn.Conv2d(\n                128, 256,\n                kernel_size=3,\n                padding=1\n            ),\n            nn.BatchNorm2d(256),\n            nn.ReLU(inplace=True),\n\n            nn.MaxPool2d(2),\n\n            nn.Dropout2d(0.25)\n        )\n\n\n        # Global Average Pooling\n        self.global_pool = nn.AdaptiveAvgPool2d(\n            (1, 1)\n        )\n\n\n        # Classification head\n        self.classifier = nn.Sequential(\n\n            nn.Flatten(),\n\n            nn.Linear(\n                256,\n                128\n            ),\n\n            nn.ReLU(inplace=True),\n\n            nn.Dropout(0.40),\n\n            nn.Linear(\n                128,\n                num_classes\n            )\n        )\n\n\n    def forward(self, x):\n\n        x = self.features(x)\n\n        x = self.global_pool(x)\n\n        x = self.classifier(x)\n\n        return x\n\n\nmodel = NetrXCNN(\n    num_classes=5\n).to(device)\n\n\nprint(model)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T13:39:37.575715Z","iopub.execute_input":"2026-09-07T13:39:37.576108Z","iopub.status.idle":"2026-09-07T13:39:37.603843Z","shell.execute_reply.started":"2026-09-07T13:39:37.57608Z","shell.execute_reply":"2026-09-07T13:39:37.603226Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 39: CLEAN CNN MODEL SUMMARY\n# ============================================================\n\nfrom torchinfo import summary\n\n# Install/import torchinfo if necessary\ntry:\n    from torchinfo import summary\nexcept ImportError:\n    !pip install -q torchinfo\n    from torchinfo import summary\n\nprint(\"=\" * 70)\nprint(\"NETRX CNN — MODEL SUMMARY\")\nprint(\"=\" * 70)\n\nsummary(\n    model,\n    input_size=(1, 3, 224, 224),\n    col_names=[\n        \"input_size\",\n        \"output_size\",\n        \"num_params\"\n    ],\n    depth=4,\n    verbose=1\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T13:39:37.604793Z","iopub.execute_input":"2026-09-07T13:39:37.605075Z","iopub.status.idle":"2026-09-07T13:39:38.264736Z","shell.execute_reply.started":"2026-09-07T13:39:37.605053Z","shell.execute_reply":"2026-09-07T13:39:38.264043Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 39: NETRX CNN — CLEAN MODEL SUMMARY\n# ============================================================\n\ntry:\n    from torchinfo import summary\nexcept ImportError:\n    !pip install -q torchinfo\n    from torchinfo import summary\n\nprint(\"=\" * 75)\nprint(\"                 NETRX CNN MODEL SUMMARY\")\nprint(\"=\" * 75)\n\nsummary(\n    model,\n    input_size=(1, 3, 224, 224),\n    col_names=[\n        \"input_size\",\n        \"output_size\",\n        \"num_params\"\n    ],\n    depth=4,\n    verbose=1\n)\n\ntotal_params = sum(\n    p.numel()\n    for p in model.parameters()\n)\n\ntrainable_params = sum(\n    p.numel()\n    for p in model.parameters()\n    if p.requires_grad\n)\n\nnon_trainable_params = total_params - trainable_params\n\nprint(\"\\n\" + \"=\" * 75)\nprint(\"PARAMETER SUMMARY\")\nprint(\"=\" * 75)\n\nprint(f\"Total params:         {total_params:,}\")\nprint(f\"Trainable params:     {trainable_params:,}\")\nprint(f\"Non-trainable params: {non_trainable_params:,}\")\nprint(f\"Input size:           224 × 224 × 3\")\nprint(f\"Output classes:       5 (DR Grade 0–4)\")\nprint(f\"Device:               {device}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T13:39:38.265741Z","iopub.execute_input":"2026-09-07T13:39:38.266066Z","iopub.status.idle":"2026-09-07T13:39:38.284814Z","shell.execute_reply.started":"2026-09-07T13:39:38.266042Z","shell.execute_reply":"2026-09-07T13:39:38.284189Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 40: FORWARD PASS CHECK\n# ============================================================\n\nimages, labels = next(iter(train_loader))\n\nimages = images.to(device)\nlabels = labels.to(device)\n\nmodel.eval()\n\nwith torch.no_grad():\n    outputs = model(images)\n\nprint(\"=\" * 60)\nprint(\"FORWARD PASS CHECK\")\nprint(\"=\" * 60)\n\nprint(f\"Input shape : {tuple(images.shape)}\")\nprint(f\"Output shape: {tuple(outputs.shape)}\")\nprint(f\"Labels shape: {tuple(labels.shape)}\")\n\nassert outputs.shape == (images.shape[0], 5)\n\nprint(\"\\n✅ Forward pass successful\")\nprint(\"✅ 5-class DR classification confirmed\")\nprint(\"✅ CUDA pipeline confirmed\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T13:39:38.285686Z","iopub.execute_input":"2026-09-07T13:39:38.286021Z","iopub.status.idle":"2026-09-07T13:39:38.718176Z","shell.execute_reply.started":"2026-09-07T13:39:38.285998Z","shell.execute_reply":"2026-09-07T13:39:38.717405Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 41: CNN TRAINING CONFIGURATION\n# ============================================================\n\nimport torch\nimport torch.nn as nn\n\n# ------------------------------------------------------------\n# Number of epochs\n# ------------------------------------------------------------\nNUM_EPOCHS = 20\n\n# ------------------------------------------------------------\n# Loss\n# Class weights handle APTOS imbalance\n# ------------------------------------------------------------\ncriterion = nn.CrossEntropyLoss(\n    weight=class_weights\n)\n\n# ------------------------------------------------------------\n# Optimizer\n# ------------------------------------------------------------\noptimizer = torch.optim.AdamW(\n    model.parameters(),\n    lr=1e-3,\n    weight_decay=1e-4\n)\n\n# ------------------------------------------------------------\n# Learning-rate scheduler\n# Reduce LR when validation loss stops improving\n# ------------------------------------------------------------\nscheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(\n    optimizer,\n    mode=\"min\",\n    factor=0.5,\n    patience=2\n)\n\n# ------------------------------------------------------------\n# Mixed precision\n# ------------------------------------------------------------\nscaler = torch.amp.GradScaler(\n    \"cuda\",\n    enabled=(device.type == \"cuda\")\n)\n\n# ------------------------------------------------------------\n# Early stopping\n# ------------------------------------------------------------\nPATIENCE = 5\n\nbest_val_loss = float(\"inf\")\nbest_val_accuracy = 0.0\nepochs_without_improvement = 0\n\nBEST_MODEL_PATH = \"/kaggle/working/netrx_cnn_best.pth\"\n\nprint(\"=\" * 65)\nprint(\"CNN TRAINING CONFIGURATION\")\nprint(\"=\" * 65)\n\nprint(\"Epochs:              \", NUM_EPOCHS)\nprint(\"Batch size:          \", BATCH_SIZE)\nprint(\"Optimizer:           AdamW\")\nprint(\"Initial LR:          \", 1e-3)\nprint(\"Weight decay:        \", 1e-4)\nprint(\"Loss:                Weighted Cross Entropy\")\nprint(\"Scheduler:            ReduceLROnPlateau\")\nprint(\"Early stopping:      \", PATIENCE)\nprint(\"Mixed precision:     \", device.type == \"cuda\")\nprint(\"Device:              \", device)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T13:39:38.719641Z","iopub.execute_input":"2026-09-07T13:39:38.719927Z","iopub.status.idle":"2026-09-07T13:39:38.729102Z","shell.execute_reply.started":"2026-09-07T13:39:38.719898Z","shell.execute_reply":"2026-09-07T13:39:38.728471Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 42: CNN TRAINING\n# ============================================================\n\nfrom sklearn.metrics import accuracy_score\n\nhistory = {\n    \"train_loss\": [],\n    \"train_accuracy\": [],\n    \"val_loss\": [],\n    \"val_accuracy\": []\n}\n\nprint(\"\\n\" + \"=\" * 75)\nprint(\"                 NETRX CNN TRAINING\")\nprint(\"=\" * 75)\n\nfor epoch in range(NUM_EPOCHS):\n\n    # ========================================================\n    # TRAINING\n    # ========================================================\n\n    model.train()\n\n    running_loss = 0.0\n    train_correct = 0\n    train_total = 0\n\n    for images, labels in train_loader:\n\n        images = images.to(\n            device,\n            non_blocking=True\n        )\n\n        labels = labels.to(\n            device,\n            non_blocking=True\n        )\n\n        optimizer.zero_grad(\n            set_to_none=True\n        )\n\n        with torch.amp.autocast(\n            device_type=\"cuda\",\n            enabled=(device.type == \"cuda\")\n        ):\n\n            outputs = model(images)\n\n            loss = criterion(\n                outputs,\n                labels\n            )\n\n        scaler.scale(\n            loss\n        ).backward()\n\n        scaler.step(\n            optimizer\n        )\n\n        scaler.update()\n\n        running_loss += (\n            loss.item() * images.size(0)\n        )\n\n        predictions = outputs.argmax(\n            dim=1\n        )\n\n        train_correct += (\n            predictions == labels\n        ).sum().item()\n\n        train_total += labels.size(0)\n\n    train_loss = (\n        running_loss / train_total\n    )\n\n    train_accuracy = (\n        train_correct / train_total\n    )\n\n\n    # ========================================================\n    # VALIDATION\n    # ========================================================\n\n    model.eval()\n\n    val_running_loss = 0.0\n    val_correct = 0\n    val_total = 0\n\n    with torch.no_grad():\n\n        for images, labels in val_loader:\n\n            images = images.to(\n                device,\n                non_blocking=True\n            )\n\n            labels = labels.to(\n                device,\n                non_blocking=True\n            )\n\n            with torch.amp.autocast(\n                device_type=\"cuda\",\n                enabled=(device.type == \"cuda\")\n            ):\n\n                outputs = model(images)\n\n                loss = criterion(\n                    outputs,\n                    labels\n                )\n\n            val_running_loss += (\n                loss.item() * images.size(0)\n            )\n\n            predictions = outputs.argmax(\n                dim=1\n            )\n\n            val_correct += (\n                predictions == labels\n            ).sum().item()\n\n            val_total += labels.size(0)\n\n    val_loss = (\n        val_running_loss / val_total\n    )\n\n    val_accuracy = (\n        val_correct / val_total\n    )\n\n\n    # ========================================================\n    # LEARNING RATE\n    # ========================================================\n\n    scheduler.step(\n        val_loss\n    )\n\n    current_lr = optimizer.param_groups[0][\"lr\"]\n\n\n    # ========================================================\n    # SAVE HISTORY\n    # ========================================================\n\n    history[\"train_loss\"].append(\n        train_loss\n    )\n\n    history[\"train_accuracy\"].append(\n        train_accuracy\n    )\n\n    history[\"val_loss\"].append(\n        val_loss\n    )\n\n    history[\"val_accuracy\"].append(\n        val_accuracy\n    )\n\n\n    # ========================================================\n    # SAVE BEST MODEL\n    # ========================================================\n\n    if val_loss < best_val_loss:\n\n        best_val_loss = val_loss\n        best_val_accuracy = val_accuracy\n\n        epochs_without_improvement = 0\n\n        torch.save(\n            model.state_dict(),\n            BEST_MODEL_PATH\n        )\n\n        best_marker = \" ★ BEST\"\n\n    else:\n\n        epochs_without_improvement += 1\n\n        best_marker = \"\"\n\n\n    # ========================================================\n    # EPOCH OUTPUT\n    # ========================================================\n\n    print(\n        f\"Epoch {epoch + 1}/{NUM_EPOCHS} | \"\n        f\"loss: {train_loss:.4f} | \"\n        f\"accuracy: {train_accuracy:.4f} | \"\n        f\"val_loss: {val_loss:.4f} | \"\n        f\"val_accuracy: {val_accuracy:.4f} | \"\n        f\"lr: {current_lr:.2e}\"\n        f\"{best_marker}\"\n    )\n\n\n    # ========================================================\n    # EARLY STOPPING\n    # ========================================================\n\n    if epochs_without_improvement >= PATIENCE:\n\n        print(\n            f\"\\nEarly stopping triggered at \"\n            f\"epoch {epoch + 1}.\"\n        )\n\n        break\n\n\nprint(\"\\n\" + \"=\" * 75)\nprint(\"CNN TRAINING COMPLETE\")\nprint(\"=\" * 75)\n\nprint(\n    f\"Best validation loss: \"\n    f\"{best_val_loss:.4f}\"\n)\n\nprint(\n    f\"Best validation accuracy: \"\n    f\"{best_val_accuracy:.4f}\"\n)\n\nprint(\n    f\"Best model saved to:\\n\"\n    f\"{BEST_MODEL_PATH}\"\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T13:39:38.730114Z","iopub.execute_input":"2026-09-07T13:39:38.73034Z","iopub.status.idle":"2026-09-07T13:42:42.420536Z","shell.execute_reply.started":"2026-09-07T13:39:38.730319Z","shell.execute_reply":"2026-09-07T13:42:42.419724Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 43: RESNET50 TRANSFER LEARNING\n# ============================================================\n\nimport torch\nimport torch.nn as nn\nfrom torchvision.models import resnet50, ResNet50_Weights\n\nprint(\"Loading pretrained ResNet50...\")\n\nweights = ResNet50_Weights.DEFAULT\n\nresnet = resnet50(\n    weights=weights\n)\n\nprint(\"Pretrained ResNet50 loaded successfully.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T13:42:42.421925Z","iopub.execute_input":"2026-09-07T13:42:42.422313Z","iopub.status.idle":"2026-09-07T13:42:43.415119Z","shell.execute_reply.started":"2026-09-07T13:42:42.422281Z","shell.execute_reply":"2026-09-07T13:42:43.414497Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 44: MODIFY RESNET50 CLASSIFIER\n# ============================================================\n\nnum_features = resnet.fc.in_features\n\nresnet.fc = nn.Sequential(\n\n    nn.Dropout(0.40),\n\n    nn.Linear(\n        num_features,\n        256\n    ),\n\n    nn.ReLU(inplace=True),\n\n    nn.Dropout(0.30),\n\n    nn.Linear(\n        256,\n        5\n    )\n)\n\nresnet = resnet.to(device)\n\nprint(\"ResNet50 classifier modified.\")\nprint(\"Input: 224 × 224 × 3\")\nprint(\"Output: 5 DR classes\")\nprint(\"Device:\", device)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T13:42:43.416101Z","iopub.execute_input":"2026-09-07T13:42:43.416332Z","iopub.status.idle":"2026-09-07T13:42:43.460343Z","shell.execute_reply.started":"2026-09-07T13:42:43.416309Z","shell.execute_reply":"2026-09-07T13:42:43.459803Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 45: FREEZE BACKBONE\n# ============================================================\n\nfor param in resnet.parameters():\n    param.requires_grad = False\n\n# Only the new classifier learns initially\nfor param in resnet.fc.parameters():\n    param.requires_grad = True\n\n\ntrainable_params = sum(\n    p.numel()\n    for p in resnet.parameters()\n    if p.requires_grad\n)\n\ntotal_params = sum(\n    p.numel()\n    for p in resnet.parameters()\n)\n\nprint(\"=\" * 60)\nprint(\"RESNET50 TRANSFER LEARNING\")\nprint(\"=\" * 60)\n\nprint(f\"Total parameters:     {total_params:,}\")\nprint(f\"Trainable parameters: {trainable_params:,}\")\nprint(\"Backbone:             FROZEN\")\nprint(\"Classifier:           TRAINABLE\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T13:42:43.461342Z","iopub.execute_input":"2026-09-07T13:42:43.461682Z","iopub.status.idle":"2026-09-07T13:42:43.469736Z","shell.execute_reply.started":"2026-09-07T13:42:43.461656Z","shell.execute_reply":"2026-09-07T13:42:43.468854Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 46: RESNET50 MODEL SUMMARY\n# ============================================================\n\nfrom torchinfo import summary\n\nprint(\"=\" * 75)\nprint(\"             NETRX RESNET50 MODEL SUMMARY\")\nprint(\"=\" * 75)\n\nsummary(\n    resnet,\n    input_size=(1, 3, 224, 224),\n    col_names=[\n        \"input_size\",\n        \"output_size\",\n        \"num_params\"\n    ],\n    depth=3,\n    verbose=1\n)\n\ntotal_params = sum(\n    p.numel()\n    for p in resnet.parameters()\n)\n\ntrainable_params = sum(\n    p.numel()\n    for p in resnet.parameters()\n    if p.requires_grad\n)\n\nprint(\"\\n\" + \"=\" * 75)\nprint(\"PARAMETER SUMMARY\")\nprint(\"=\" * 75)\n\nprint(f\"Total params:         {total_params:,}\")\nprint(f\"Trainable params:     {trainable_params:,}\")\nprint(f\"Non-trainable params: {total_params-trainable_params:,}\")\nprint(\"Input:                224 × 224 × 3\")\nprint(\"Output:               5 DR classes\")\nprint(\"Device:               \", device)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T13:42:43.470739Z","iopub.execute_input":"2026-09-07T13:42:43.470967Z","iopub.status.idle":"2026-09-07T13:42:43.580129Z","shell.execute_reply.started":"2026-09-07T13:42:43.470947Z","shell.execute_reply":"2026-09-07T13:42:43.579259Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 47: RESNET TRAINING CONFIGURATION\n# ============================================================\n\ncriterion_resnet = nn.CrossEntropyLoss(\n    weight=class_weights\n)\n\noptimizer_resnet = torch.optim.AdamW(\n    filter(\n        lambda p: p.requires_grad,\n        resnet.parameters()\n    ),\n    lr=1e-3,\n    weight_decay=1e-4\n)\n\nscheduler_resnet = torch.optim.lr_scheduler.ReduceLROnPlateau(\n    optimizer_resnet,\n    mode=\"min\",\n    factor=0.5,\n    patience=2\n)\n\nscaler_resnet = torch.amp.GradScaler(\n    \"cuda\",\n    enabled=(device.type == \"cuda\")\n)\n\nNUM_EPOCHS_RESNET = 12\n\nprint(\"=\" * 60)\nprint(\"RESNET50 TRAINING CONFIGURATION\")\nprint(\"=\" * 60)\n\nprint(\"Epochs:          \", NUM_EPOCHS_RESNET)\nprint(\"Loss:            Weighted Cross Entropy\")\nprint(\"Optimizer:       AdamW\")\nprint(\"Learning rate:   1e-3\")\nprint(\"Scheduler:       ReduceLROnPlateau\")\nprint(\"Backbone:        Frozen\")\nprint(\"Classifier:      Trainable\")\nprint(\"Device:          \", device)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T13:42:43.581178Z","iopub.execute_input":"2026-09-07T13:42:43.581956Z","iopub.status.idle":"2026-09-07T13:42:43.588426Z","shell.execute_reply.started":"2026-09-07T13:42:43.58193Z","shell.execute_reply":"2026-09-07T13:42:43.587879Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 48: RESNET50 TRANSFER LEARNING\n# ============================================================\n\nfrom sklearn.metrics import accuracy_score\n\nhistory_resnet = {\n    \"train_loss\": [],\n    \"train_accuracy\": [],\n    \"val_loss\": [],\n    \"val_accuracy\": []\n}\n\nBEST_RESNET_PATH = \"/kaggle/working/netrx_resnet50_best.pth\"\n\nbest_val_loss = float(\"inf\")\nbest_val_accuracy = 0.0\nepochs_without_improvement = 0\n\nPATIENCE_RESNET = 4\n\nprint(\"\\n\" + \"=\" * 75)\nprint(\"             NETRX — RESNET50 TRANSFER LEARNING\")\nprint(\"=\" * 75)\n\nfor epoch in range(NUM_EPOCHS_RESNET):\n\n    # ========================================================\n    # TRAIN\n    # ========================================================\n\n    resnet.train()\n\n    running_loss = 0.0\n    correct = 0\n    total = 0\n\n    for images, labels in train_loader:\n\n        images = images.to(\n            device,\n            non_blocking=True\n        )\n\n        labels = labels.to(\n            device,\n            non_blocking=True\n        )\n\n        optimizer_resnet.zero_grad(\n            set_to_none=True\n        )\n\n        with torch.amp.autocast(\n            device_type=\"cuda\",\n            enabled=(device.type == \"cuda\")\n        ):\n\n            outputs = resnet(images)\n\n            loss = criterion_resnet(\n                outputs,\n                labels\n            )\n\n        scaler_resnet.scale(loss).backward()\n\n        scaler_resnet.step(\n            optimizer_resnet\n        )\n\n        scaler_resnet.update()\n\n        running_loss += (\n            loss.item() * images.size(0)\n        )\n\n        predictions = outputs.argmax(\n            dim=1\n        )\n\n        correct += (\n            predictions == labels\n        ).sum().item()\n\n        total += labels.size(0)\n\n    train_loss = running_loss / total\n    train_accuracy = correct / total\n\n\n    # ========================================================\n    # VALIDATION\n    # ========================================================\n\n    resnet.eval()\n\n    val_running_loss = 0.0\n    val_correct = 0\n    val_total = 0\n\n    with torch.no_grad():\n\n        for images, labels in val_loader:\n\n            images = images.to(\n                device,\n                non_blocking=True\n            )\n\n            labels = labels.to(\n                device,\n                non_blocking=True\n            )\n\n            with torch.amp.autocast(\n                device_type=\"cuda\",\n                enabled=(device.type == \"cuda\")\n            ):\n\n                outputs = resnet(images)\n\n                loss = criterion_resnet(\n                    outputs,\n                    labels\n                )\n\n            val_running_loss += (\n                loss.item() * images.size(0)\n            )\n\n            predictions = outputs.argmax(\n                dim=1\n            )\n\n            val_correct += (\n                predictions == labels\n            ).sum().item()\n\n            val_total += labels.size(0)\n\n    val_loss = val_running_loss / val_total\n    val_accuracy = val_correct / val_total\n\n\n    # ========================================================\n    # SCHEDULER\n    # ========================================================\n\n    scheduler_resnet.step(val_loss)\n\n    current_lr = optimizer_resnet.param_groups[0][\"lr\"]\n\n\n    # ========================================================\n    # SAVE HISTORY\n    # ========================================================\n\n    history_resnet[\"train_loss\"].append(train_loss)\n    history_resnet[\"train_accuracy\"].append(train_accuracy)\n    history_resnet[\"val_loss\"].append(val_loss)\n    history_resnet[\"val_accuracy\"].append(val_accuracy)\n\n\n    # ========================================================\n    # BEST MODEL\n    # ========================================================\n\n    if val_loss < best_val_loss:\n\n        best_val_loss = val_loss\n        best_val_accuracy = val_accuracy\n\n        epochs_without_improvement = 0\n\n        torch.save(\n            resnet.state_dict(),\n            BEST_RESNET_PATH\n        )\n\n        marker = \" ★ BEST\"\n\n    else:\n\n        epochs_without_improvement += 1\n\n        marker = \"\"\n\n\n    # ========================================================\n    # EPOCH OUTPUT\n    # ========================================================\n\n    print(\n        f\"Epoch {epoch + 1}/{NUM_EPOCHS_RESNET} | \"\n        f\"loss: {train_loss:.4f} | \"\n        f\"accuracy: {train_accuracy:.4f} | \"\n        f\"val_loss: {val_loss:.4f} | \"\n        f\"val_accuracy: {val_accuracy:.4f} | \"\n        f\"lr: {current_lr:.2e}\"\n        f\"{marker}\"\n    )\n\n\n    # ========================================================\n    # EARLY STOPPING\n    # ========================================================\n\n    if epochs_without_improvement >= PATIENCE_RESNET:\n\n        print(\n            f\"\\nEarly stopping at epoch \"\n            f\"{epoch + 1}.\"\n        )\n\n        break\n\n\nprint(\"\\n\" + \"=\" * 75)\nprint(\"RESNET50 TRAINING COMPLETE\")\nprint(\"=\" * 75)\n\nprint(\n    f\"Best validation loss: \"\n    f\"{best_val_loss:.4f}\"\n)\n\nprint(\n    f\"Best validation accuracy: \"\n    f\"{best_val_accuracy:.4f}\"\n)\n\nprint(\n    f\"Best model saved at:\\n\"\n    f\"{BEST_RESNET_PATH}\"\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T13:42:43.589583Z","iopub.execute_input":"2026-09-07T13:42:43.58992Z","iopub.status.idle":"2026-09-07T13:44:24.408347Z","shell.execute_reply.started":"2026-09-07T13:42:43.589896Z","shell.execute_reply":"2026-09-07T13:44:24.407542Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# RESNET50 — RECREATE MODEL FOR EVALUATION\n# EXACT ARCHITECTURE USED DURING TRAINING\n# ============================================================\n\nimport torch\nimport torch.nn as nn\nfrom torchvision.models import resnet50\n\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n\n# ------------------------------------------------------------\n# Create ResNet50\n# ------------------------------------------------------------\n\nresnet_model = resnet50(weights=None)\n\n# ------------------------------------------------------------\n# EXACT TRAINING HEAD\n# 2048 → 256 → 5\n# ------------------------------------------------------------\n\nresnet_model.fc = nn.Sequential(\n    nn.Dropout(0.3),\n    nn.Linear(resnet_model.fc.in_features, 256),\n    nn.ReLU(),\n    nn.Dropout(0.3),\n    nn.Linear(256, 5)\n)\n\n# ------------------------------------------------------------\n# Load checkpoint\n# ------------------------------------------------------------\n\ncheckpoint_path = \"/kaggle/working/netrx_resnet50_best.pth\"\n\nstate_dict = torch.load(\n    checkpoint_path,\n    map_location=device,\n    weights_only=True\n)\n\nresnet_model.load_state_dict(state_dict, strict=True)\n\nresnet_model = resnet_model.to(device)\nresnet_model.eval()\n\nprint(\"=\" * 65)\nprint(\"✅ RESNET50 MODEL LOADED SUCCESSFULLY\")\nprint(\"=\" * 65)\nprint(f\"Device: {device}\")\nprint(\"Classes: 5\")\nprint(f\"Parameters: {sum(p.numel() for p in resnet_model.parameters()):,}\")\nprint(f\"Checkpoint: {checkpoint_path}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T13:47:23.223217Z","iopub.execute_input":"2026-09-07T13:47:23.223905Z","iopub.status.idle":"2026-09-07T13:47:23.747942Z","shell.execute_reply.started":"2026-09-07T13:47:23.223874Z","shell.execute_reply":"2026-09-07T13:47:23.746904Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CHECK RESNET50 CHECKPOINT KEYS\n# ============================================================\n\ncheckpoint_path = \"/kaggle/working/netrx_resnet50_best.pth\"\n\nstate_dict = torch.load(\n    checkpoint_path,\n    map_location=\"cpu\"\n)\n\nprint(\"=\" * 70)\nprint(\"CHECKPOINT INFORMATION\")\nprint(\"=\" * 70)\n\nprint(f\"Number of saved parameters: {len(state_dict)}\")\n\nprint(\"\\nLast 20 saved keys:\")\nfor key in list(state_dict.keys())[-20:]:\n    print(key)\n\nprint(\"\\n\" + \"=\" * 70)\nprint(\"CLASSIFIER / FC RELATED KEYS\")\nprint(\"=\" * 70)\n\nfor key in state_dict.keys():\n    if \"fc\" in key.lower() or \"classifier\" in key.lower():\n        print(key)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T13:48:10.824339Z","iopub.execute_input":"2026-09-07T13:48:10.82484Z","iopub.status.idle":"2026-09-07T13:48:10.907519Z","shell.execute_reply.started":"2026-09-07T13:48:10.824809Z","shell.execute_reply":"2026-09-07T13:48:10.907019Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 43: EFFICIENTNET-B0 — TRANSFER LEARNING\n# ============================================================\n\nimport torch\nimport torch.nn as nn\n\nfrom torchvision.models import (\n    efficientnet_b0,\n    EfficientNet_B0_Weights\n)\n\nprint(\"=\" * 70)\nprint(\"NETRX — EFFICIENTNET-B0\")\nprint(\"=\" * 70)\n\nweights_eff = EfficientNet_B0_Weights.DEFAULT\n\nefficientnet = efficientnet_b0(\n    weights=weights_eff\n)\n\nprint(\"✅ Pretrained EfficientNet-B0 loaded.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T13:48:15.05272Z","iopub.execute_input":"2026-09-07T13:48:15.053041Z","iopub.status.idle":"2026-09-07T13:48:15.299841Z","shell.execute_reply.started":"2026-09-07T13:48:15.053017Z","shell.execute_reply":"2026-09-07T13:48:15.299014Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 44: MODIFY EFFICIENTNET CLASSIFIER\n# ============================================================\n\nnum_features_eff = (\n    efficientnet.classifier[1].in_features\n)\n\nefficientnet.classifier = nn.Sequential(\n\n    nn.Dropout(\n        p=0.40\n    ),\n\n    nn.Linear(\n        num_features_eff,\n        256\n    ),\n\n    nn.ReLU(\n        inplace=True\n    ),\n\n    nn.Dropout(\n        p=0.30\n    ),\n\n    nn.Linear(\n        256,\n        5\n    )\n)\n\nefficientnet = efficientnet.to(device)\n\nprint(\"=\" * 60)\nprint(\"EFFICIENTNET-B0 CLASSIFIER\")\nprint(\"=\" * 60)\n\nprint(\"Input:  224 × 224 × 3\")\nprint(\"Output: 5 DR classes\")\nprint(\"Device:\", device)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T13:48:18.92282Z","iopub.execute_input":"2026-09-07T13:48:18.923682Z","iopub.status.idle":"2026-09-07T13:48:18.94937Z","shell.execute_reply.started":"2026-09-07T13:48:18.923652Z","shell.execute_reply":"2026-09-07T13:48:18.948749Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 45: FREEZE EFFICIENTNET BACKBONE\n# ============================================================\n\nfor param in efficientnet.features.parameters():\n    param.requires_grad = False\n\nfor param in efficientnet.classifier.parameters():\n    param.requires_grad = True\n\n\ntotal_params_eff = sum(\n    p.numel()\n    for p in efficientnet.parameters()\n)\n\ntrainable_params_eff = sum(\n    p.numel()\n    for p in efficientnet.parameters()\n    if p.requires_grad\n)\n\nprint(\"=\" * 65)\nprint(\"EFFICIENTNET-B0 PARAMETER SUMMARY\")\nprint(\"=\" * 65)\n\nprint(\n    f\"Total params:         {total_params_eff:,}\"\n)\n\nprint(\n    f\"Trainable params:     {trainable_params_eff:,}\"\n)\n\nprint(\n    f\"Non-trainable params: \"\n    f\"{total_params_eff - trainable_params_eff:,}\"\n)\n\nprint(\"Backbone:             FROZEN\")\nprint(\"Classifier:           TRAINABLE\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T13:48:22.313066Z","iopub.execute_input":"2026-09-07T13:48:22.313904Z","iopub.status.idle":"2026-09-07T13:48:22.321644Z","shell.execute_reply.started":"2026-09-07T13:48:22.313872Z","shell.execute_reply":"2026-09-07T13:48:22.32081Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 46: EFFICIENTNET MODEL SUMMARY\n# ============================================================\n\ntry:\n    from torchinfo import summary\nexcept ImportError:\n    !pip install -q torchinfo\n    from torchinfo import summary\n\nprint(\"=\" * 75)\nprint(\"          NETRX — EFFICIENTNET-B0 MODEL SUMMARY\")\nprint(\"=\" * 75)\n\nsummary(\n    efficientnet,\n    input_size=(1, 3, 224, 224),\n    col_names=[\n        \"input_size\",\n        \"output_size\",\n        \"num_params\"\n    ],\n    depth=4,\n    verbose=1\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T13:48:25.783096Z","iopub.execute_input":"2026-09-07T13:48:25.784037Z","iopub.status.idle":"2026-09-07T13:48:25.939008Z","shell.execute_reply.started":"2026-09-07T13:48:25.783992Z","shell.execute_reply":"2026-09-07T13:48:25.938326Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 47: EFFICIENTNET FORWARD PASS\n# ============================================================\n\nimages, labels = next(iter(train_loader))\n\nimages = images.to(device)\nlabels = labels.to(device)\n\nefficientnet.eval()\n\nwith torch.no_grad():\n\n    outputs = efficientnet(images)\n\nprint(\"=\" * 60)\nprint(\"EFFICIENTNET FORWARD PASS CHECK\")\nprint(\"=\" * 60)\n\nprint(\"Input shape :\", tuple(images.shape))\nprint(\"Output shape:\", tuple(outputs.shape))\nprint(\"Labels shape:\", tuple(labels.shape))\n\nassert outputs.shape == (images.shape[0], 5)\n\nprint(\"\\n✅ Forward pass successful\")\nprint(\"✅ 5-class output confirmed\")\nprint(\"✅ CUDA confirmed\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T13:48:39.313468Z","iopub.execute_input":"2026-09-07T13:48:39.313786Z","iopub.status.idle":"2026-09-07T13:48:39.80111Z","shell.execute_reply.started":"2026-09-07T13:48:39.313761Z","shell.execute_reply":"2026-09-07T13:48:39.800078Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 48: EFFICIENTNET TRAINING CONFIGURATION\n# ============================================================\n\ncriterion_eff = nn.CrossEntropyLoss(\n    weight=class_weights\n)\n\noptimizer_eff = torch.optim.AdamW(\n    filter(\n        lambda p: p.requires_grad,\n        efficientnet.parameters()\n    ),\n    lr=1e-3,\n    weight_decay=1e-4\n)\n\nscheduler_eff = torch.optim.lr_scheduler.ReduceLROnPlateau(\n    optimizer_eff,\n    mode=\"min\",\n    factor=0.5,\n    patience=2\n)\n\nscaler_eff = torch.amp.GradScaler(\n    \"cuda\",\n    enabled=(device.type == \"cuda\")\n)\n\nNUM_EPOCHS_EFF = 12\n\nprint(\"=\" * 65)\nprint(\"EFFICIENTNET-B0 TRAINING CONFIGURATION\")\nprint(\"=\" * 65)\n\nprint(\"Epochs:          \", NUM_EPOCHS_EFF)\nprint(\"Batch size:      \", BATCH_SIZE)\nprint(\"Loss:            Weighted Cross Entropy\")\nprint(\"Optimizer:       AdamW\")\nprint(\"Learning rate:   1e-3\")\nprint(\"Weight decay:    1e-4\")\nprint(\"Scheduler:       ReduceLROnPlateau\")\nprint(\"Backbone:        Frozen\")\nprint(\"Device:          \", device)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T13:48:42.89588Z","iopub.execute_input":"2026-09-07T13:48:42.896454Z","iopub.status.idle":"2026-09-07T13:48:42.905097Z","shell.execute_reply.started":"2026-09-07T13:48:42.896415Z","shell.execute_reply":"2026-09-07T13:48:42.904332Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 49: TRAIN EFFICIENTNET-B0\n# ============================================================\n\nimport copy\nimport time\n\nbest_val_loss_eff = float(\"inf\")\nbest_val_acc_eff = 0.0\nbest_epoch_eff = 0\n\ntrain_history_eff = []\nval_history_eff = []\n\nbest_model_eff = copy.deepcopy(efficientnet.state_dict())\n\nprint(\"=\" * 80)\nprint(\"              NETRX — EFFICIENTNET-B0 TRAINING\")\nprint(\"=\" * 80)\n\ntraining_start = time.time()\n\nfor epoch in range(NUM_EPOCHS_EFF):\n\n    # --------------------------------------------------------\n    # TRAIN\n    # --------------------------------------------------------\n    efficientnet.train()\n\n    running_loss = 0.0\n    correct = 0\n    total = 0\n\n    for images, labels in train_loader:\n\n        images = images.to(device, non_blocking=True)\n        labels = labels.to(device, non_blocking=True)\n\n        optimizer_eff.zero_grad(set_to_none=True)\n\n        with torch.amp.autocast(\n            device_type=\"cuda\",\n            enabled=(device.type == \"cuda\")\n        ):\n\n            outputs = efficientnet(images)\n            loss = criterion_eff(outputs, labels)\n\n        scaler_eff.scale(loss).backward()\n\n        scaler_eff.step(optimizer_eff)\n        scaler_eff.update()\n\n        running_loss += loss.item() * images.size(0)\n\n        _, predicted = torch.max(outputs, 1)\n\n        total += labels.size(0)\n        correct += (predicted == labels).sum().item()\n\n    train_loss = running_loss / total\n    train_acc = correct / total\n\n    # --------------------------------------------------------\n    # VALIDATION\n    # --------------------------------------------------------\n    efficientnet.eval()\n\n    val_running_loss = 0.0\n    val_correct = 0\n    val_total = 0\n\n    with torch.no_grad():\n\n        for images, labels in val_loader:\n\n            images = images.to(device, non_blocking=True)\n            labels = labels.to(device, non_blocking=True)\n\n            with torch.amp.autocast(\n                device_type=\"cuda\",\n                enabled=(device.type == \"cuda\")\n            ):\n\n                outputs = efficientnet(images)\n                loss = criterion_eff(outputs, labels)\n\n            val_running_loss += (\n                loss.item() * images.size(0)\n            )\n\n            _, predicted = torch.max(outputs, 1)\n\n            val_total += labels.size(0)\n            val_correct += (\n                predicted == labels\n            ).sum().item()\n\n    val_loss = val_running_loss / val_total\n    val_acc = val_correct / val_total\n\n    # --------------------------------------------------------\n    # LEARNING RATE SCHEDULER\n    # --------------------------------------------------------\n    scheduler_eff.step(val_loss)\n\n    current_lr = optimizer_eff.param_groups[0][\"lr\"]\n\n    # --------------------------------------------------------\n    # SAVE HISTORY\n    # --------------------------------------------------------\n    train_history_eff.append(\n        [train_loss, train_acc]\n    )\n\n    val_history_eff.append(\n        [val_loss, val_acc]\n    )\n\n    # --------------------------------------------------------\n    # SAVE BEST MODEL\n    # --------------------------------------------------------\n    if val_loss < best_val_loss_eff:\n\n        best_val_loss_eff = val_loss\n        best_val_acc_eff = val_acc\n        best_epoch_eff = epoch + 1\n\n        best_model_eff = copy.deepcopy(\n            efficientnet.state_dict()\n        )\n\n        torch.save(\n            best_model_eff,\n            \"/kaggle/working/netrx_efficientnet_b0_best.pth\"\n        )\n\n        save_status = \" ⭐ BEST\"\n\n    else:\n        save_status = \"\"\n\n    # --------------------------------------------------------\n    # EPOCH OUTPUT\n    # --------------------------------------------------------\n    print(\n        f\"Epoch {epoch+1:02d}/{NUM_EPOCHS_EFF} | \"\n        f\"loss: {train_loss:.4f} | \"\n        f\"accuracy: {train_acc:.4f} | \"\n        f\"val_loss: {val_loss:.4f} | \"\n        f\"val_accuracy: {val_acc:.4f} | \"\n        f\"lr: {current_lr:.1e}\"\n        f\"{save_status}\"\n    )\n\n\ntraining_time = time.time() - training_start\n\nprint(\"\\n\" + \"=\" * 80)\nprint(\"                 MODEL TRAINING COMPLETE\")\nprint(\"=\" * 80)\n\nprint(\n    f\"Best validation loss:     {best_val_loss_eff:.4f}\"\n)\n\nprint(\n    f\"Best validation accuracy: \"\n    f\"{best_val_acc_eff:.4f} \"\n    f\"({best_val_acc_eff*100:.2f}%)\"\n)\n\nprint(\n    f\"Best epoch:               {best_epoch_eff}\"\n)\n\nprint(\n    f\"Training time:            \"\n    f\"{training_time/60:.2f} minutes\"\n)\n\nprint(\n    \"Saved model:              \"\n    \"/kaggle/working/netrx_efficientnet_b0_best.pth\"\n)\n\nprint(\"=\" * 80)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T13:48:46.698267Z","iopub.execute_input":"2026-09-07T13:48:46.699073Z","iopub.status.idle":"2026-09-07T13:50:32.314179Z","shell.execute_reply.started":"2026-09-07T13:48:46.699043Z","shell.execute_reply":"2026-09-07T13:50:32.313282Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 50A: VERIFY CNN CHECKPOINT\n# ============================================================\n\nimport os\nimport shutil\n\ncnn_path = \"/kaggle/working/netrx_cnn_best.pth\"\n\nprint(\"=\" * 70)\nprint(\"NETRX — CNN BASELINE CHECKPOINT\")\nprint(\"=\" * 70)\n\nif os.path.exists(cnn_path):\n\n    size_mb = os.path.getsize(cnn_path) / (1024 ** 2)\n\n    print(\"✅ CNN checkpoint found\")\n    print(f\"Path : {cnn_path}\")\n    print(f\"Size : {size_mb:.2f} MB\")\n\nelse:\n\n    print(\"❌ CNN checkpoint NOT FOUND\")\n    print(\"We need to recreate/retrain the CNN before continuing.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T13:52:03.393248Z","iopub.execute_input":"2026-09-07T13:52:03.39367Z","iopub.status.idle":"2026-09-07T13:52:03.40056Z","shell.execute_reply.started":"2026-09-07T13:52:03.393629Z","shell.execute_reply":"2026-09-07T13:52:03.399634Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 50B: VERIFY RESNET50 CHECKPOINT\n# ============================================================\n\nresnet_path = \"/kaggle/working/netrx_resnet50_best.pth\"\n\nprint(\"=\" * 70)\nprint(\"NETRX — RESNET50 CHECKPOINT\")\nprint(\"=\" * 70)\n\nif os.path.exists(resnet_path):\n\n    size_mb = os.path.getsize(resnet_path) / (1024 ** 2)\n\n    print(\"✅ ResNet50 checkpoint found\")\n    print(f\"Path : {resnet_path}\")\n    print(f\"Size : {size_mb:.2f} MB\")\n\nelse:\n\n    print(\"❌ ResNet50 checkpoint NOT FOUND\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T13:53:17.298248Z","iopub.execute_input":"2026-09-07T13:53:17.298718Z","iopub.status.idle":"2026-09-07T13:53:17.304739Z","shell.execute_reply.started":"2026-09-07T13:53:17.298687Z","shell.execute_reply":"2026-09-07T13:53:17.303654Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 50C: VERIFY EFFICIENTNET-B0 CHECKPOINT\n# ============================================================\n\nefficientnet_path = (\n    \"/kaggle/working/\"\n    \"netrx_efficientnet_b0_best.pth\"\n)\n\nprint(\"=\" * 70)\nprint(\"NETRX — EFFICIENTNET-B0 CHECKPOINT\")\nprint(\"=\" * 70)\n\nif os.path.exists(efficientnet_path):\n\n    size_mb = (\n        os.path.getsize(efficientnet_path)\n        / (1024 ** 2)\n    )\n\n    print(\"✅ EfficientNet-B0 checkpoint found\")\n    print(f\"Path : {efficientnet_path}\")\n    print(f\"Size : {size_mb:.2f} MB\")\n\nelse:\n\n    print(\"❌ EfficientNet-B0 checkpoint NOT FOUND\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T13:53:20.483194Z","iopub.execute_input":"2026-09-07T13:53:20.484142Z","iopub.status.idle":"2026-09-07T13:53:20.489945Z","shell.execute_reply.started":"2026-09-07T13:53:20.484108Z","shell.execute_reply":"2026-09-07T13:53:20.48903Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 50D: NETRX MODEL INVENTORY\n# ============================================================\n\nmodels_to_save = {\n    \"CNN\": \"/kaggle/working/netrx_cnn_best.pth\",\n    \"ResNet50\": \"/kaggle/working/netrx_resnet50_best.pth\",\n    \"EfficientNet-B0\": \"/kaggle/working/netrx_efficientnet_b0_best.pth\"\n}\n\nprint(\"=\" * 75)\nprint(\"              NETRX — SAVED MODEL INVENTORY\")\nprint(\"=\" * 75)\n\nall_found = True\n\nfor model_name, path in models_to_save.items():\n\n    if os.path.exists(path):\n\n        size_mb = os.path.getsize(path) / (1024 ** 2)\n\n        print(\n            f\"✅ {model_name:<20} \"\n            f\"{size_mb:.2f} MB\"\n        )\n\n    else:\n\n        print(\n            f\"❌ {model_name:<20} NOT FOUND\"\n        )\n\n        all_found = False\n\nprint(\"=\" * 75)\n\nif all_found:\n    print(\"✅ ALL THREE MODEL CHECKPOINTS ARE SAFE\")\nelse:\n    print(\"⚠️ ONE OR MORE CHECKPOINTS ARE MISSING\")\n\nprint(\"=\" * 75)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T13:53:23.547523Z","iopub.execute_input":"2026-09-07T13:53:23.548393Z","iopub.status.idle":"2026-09-07T13:53:23.55506Z","shell.execute_reply.started":"2026-09-07T13:53:23.548361Z","shell.execute_reply":"2026-09-07T13:53:23.554129Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 51: NETRX — PACKAGE ALL TRAINED MODELS\n# ============================================================\n\nimport os\nimport zipfile\nfrom IPython.display import FileLink, display\n\nmodel_files = [\n    \"/kaggle/working/netrx_cnn_best.pth\",\n    \"/kaggle/working/netrx_resnet50_best.pth\",\n    \"/kaggle/working/netrx_efficientnet_b0_best.pth\"\n]\n\nzip_path = \"/kaggle/working/NETRX_ALL_TRAINED_MODELS.zip\"\n\n# ------------------------------------------------------------\n# CREATE ZIP\n# ------------------------------------------------------------\n\nwith zipfile.ZipFile(\n    zip_path,\n    \"w\",\n    compression=zipfile.ZIP_DEFLATED\n) as zipf:\n\n    for file_path in model_files:\n\n        if os.path.exists(file_path):\n\n            zipf.write(\n                file_path,\n                arcname=os.path.basename(file_path)\n            )\n\n            print(\n                f\"✅ Added: {os.path.basename(file_path)}\"\n            )\n\n        else:\n\n            print(\n                f\"❌ Missing: {os.path.basename(file_path)}\"\n            )\n\n# ------------------------------------------------------------\n# VERIFY ZIP\n# ------------------------------------------------------------\n\nprint(\"\\n\" + \"=\" * 70)\nprint(\"NETRX — MODEL BACKUP READY\")\nprint(\"=\" * 70)\n\nsize_mb = os.path.getsize(zip_path) / (1024 ** 2)\n\nprint(f\"ZIP size: {size_mb:.2f} MB\")\nprint(f\"Location: {zip_path}\")\n\nprint(\"\\nFiles inside ZIP:\")\n\nwith zipfile.ZipFile(zip_path, \"r\") as zipf:\n\n    for name in zipf.namelist():\n        print(\"  ✅\", name)\n\nprint(\"\\n\" + \"=\" * 70)\nprint(\"DOWNLOAD YOUR BACKUP\")\nprint(\"=\" * 70)\n\ndisplay(\n    FileLink(\n        zip_path,\n        result_html_prefix=\"📦 Download NetrX trained models: \"\n    )\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T13:53:28.287835Z","iopub.execute_input":"2026-09-07T13:53:28.288751Z","iopub.status.idle":"2026-09-07T13:53:34.258238Z","shell.execute_reply.started":"2026-09-07T13:53:28.288718Z","shell.execute_reply":"2026-09-07T13:53:34.25753Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 52: CHECK KERAS / TENSORFLOW\n# ============================================================\n\nimport tensorflow as tf\nimport keras\n\nprint(\"=\" * 60)\nprint(\"KERAS / TENSORFLOW ENVIRONMENT\")\nprint(\"=\" * 60)\n\nprint(\"TensorFlow:\", tf.__version__)\nprint(\"Keras:     \", keras.__version__)\n\nprint(\"\\n✅ Environment ready for Keras conversion\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T13:53:34.598094Z","iopub.execute_input":"2026-09-07T13:53:34.598939Z","iopub.status.idle":"2026-09-07T13:53:48.204816Z","shell.execute_reply.started":"2026-09-07T13:53:34.598906Z","shell.execute_reply":"2026-09-07T13:53:48.204101Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 51B: FRESH NETRX MODEL DOWNLOAD\n# ============================================================\n\nimport os\nfrom IPython.display import display, HTML\n\nzip_path = \"/kaggle/working/NETRX_ALL_TRAINED_MODELS.zip\"\n\nprint(\"=\" * 70)\nprint(\"NETRX MODEL BACKUP\")\nprint(\"=\" * 70)\n\nif os.path.exists(zip_path):\n\n    size_mb = os.path.getsize(zip_path) / (1024 ** 2)\n\n    print(\"✅ Backup ZIP exists\")\n    print(f\"Size: {size_mb:.2f} MB\")\n    print(\"\\nClick the link below to download:\")\n\n    display(\n        HTML(\n            f'''\n            <a href=\"/files/{os.path.basename(zip_path)}\"\n               download\n               style=\"\n               font-size:18px;\n               font-weight:bold;\n               text-decoration:none;\">\n               📦 DOWNLOAD NETRX ALL TRAINED MODELS\n            </a>\n            '''\n        )\n    )\n\nelse:\n\n    print(\"❌ ZIP file not found.\")\n    print(\"Run Cell 51 again to recreate it.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T13:54:07.342731Z","iopub.execute_input":"2026-09-07T13:54:07.34389Z","iopub.status.idle":"2026-09-07T13:54:07.352158Z","shell.execute_reply.started":"2026-09-07T13:54:07.34386Z","shell.execute_reply":"2026-09-07T13:54:07.351133Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"   #               APTOS\n   #                 ↓\n   #        Quality + Preprocessing\n   #                 ↓\n   #     ┌───────────┼───────────┐\n   #     ↓           ↓           ↓\n   # DenseNet     ConvNeXt       ViT\n   #     ↓           ↓           ↓\n   #     └───────────┼───────────┘\n   #                 ↓\n   #           ENSEMBLE 🔥\n   #                 ↓\n   #      5-Class DR Severity\n   #                 ↓\n   #        Referable DR (≥2)\n   #                 ↓\n   #     Sensitivity / Specificity\n   #                 ↓\n   #           Grad-CAM\n   #                 ↓\n   #        Explainable Report","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T13:44:24.91768Z","iopub.status.idle":"2026-09-07T13:44:24.917932Z","shell.execute_reply.started":"2026-09-07T13:44:24.917798Z","shell.execute_reply":"2026-09-07T13:44:24.917811Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 53: NETRX — DENSENET121\n# ============================================================\n\nimport torch\nimport torch.nn as nn\nimport copy\nimport time\n\nfrom torchvision.models import (\n    densenet121,\n    DenseNet121_Weights\n)\n\nprint(\"=\" * 75)\nprint(\"             NETRX — DENSENET121\")\nprint(\"=\" * 75)\n\nweights_dense = DenseNet121_Weights.DEFAULT\n\ndensenet = densenet121(\n    weights=weights_dense\n)\n\nprint(\"✅ Pretrained DenseNet121 loaded\")\nprint(\"Input:  224 × 224 × 3\")\nprint(\"Output: 5 DR classes\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T13:58:38.773311Z","iopub.execute_input":"2026-09-07T13:58:38.77414Z","iopub.status.idle":"2026-09-07T13:58:38.95796Z","shell.execute_reply.started":"2026-09-07T13:58:38.774106Z","shell.execute_reply":"2026-09-07T13:58:38.957245Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 54: DENSENET121 CLASSIFIER\n# ============================================================\n\nnum_features_dense = densenet.classifier.in_features\n\ndensenet.classifier = nn.Sequential(\n    nn.Dropout(0.40),\n\n    nn.Linear(\n        num_features_dense,\n        256\n    ),\n\n    nn.ReLU(inplace=True),\n\n    nn.Dropout(0.30),\n\n    nn.Linear(\n        256,\n        5\n    )\n)\n\ndensenet = densenet.to(device)\n\nprint(\"=\" * 65)\nprint(\"DENSENET121 CLASSIFIER\")\nprint(\"=\" * 65)\n\nprint(\"Features:\", num_features_dense)\nprint(\"Classes: 5\")\nprint(\"Device :\", device)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T13:58:41.808364Z","iopub.execute_input":"2026-09-07T13:58:41.808671Z","iopub.status.idle":"2026-09-07T13:58:41.860033Z","shell.execute_reply.started":"2026-09-07T13:58:41.808648Z","shell.execute_reply":"2026-09-07T13:58:41.859424Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 55: FREEZE DENSENET BACKBONE\n# ============================================================\n\nfor param in densenet.features.parameters():\n    param.requires_grad = False\n\nfor param in densenet.classifier.parameters():\n    param.requires_grad = True\n\n\ntotal_params_dense = sum(\n    p.numel()\n    for p in densenet.parameters()\n)\n\ntrainable_params_dense = sum(\n    p.numel()\n    for p in densenet.parameters()\n    if p.requires_grad\n)\n\nprint(\"=\" * 70)\nprint(\"DENSENET121 PARAMETER SUMMARY\")\nprint(\"=\" * 70)\n\nprint(\n    f\"Total parameters:     {total_params_dense:,}\"\n)\n\nprint(\n    f\"Trainable parameters: {trainable_params_dense:,}\"\n)\n\nprint(\n    f\"Frozen parameters:    \"\n    f\"{total_params_dense - trainable_params_dense:,}\"\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T13:58:48.202628Z","iopub.execute_input":"2026-09-07T13:58:48.203448Z","iopub.status.idle":"2026-09-07T13:58:48.213156Z","shell.execute_reply.started":"2026-09-07T13:58:48.203406Z","shell.execute_reply":"2026-09-07T13:58:48.212528Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 56: DENSENET121 MODEL SUMMARY\n# ============================================================\n\ntry:\n    from torchinfo import summary\nexcept ImportError:\n    !pip install -q torchinfo\n    from torchinfo import summary\n\nprint(\"=\" * 75)\nprint(\"             NETRX — DENSENET121 SUMMARY\")\nprint(\"=\" * 75)\n\nsummary(\n    densenet,\n    input_size=(1, 3, 224, 224),\n    col_names=[\n        \"input_size\",\n        \"output_size\",\n        \"num_params\"\n    ],\n    depth=4\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T13:58:51.483163Z","iopub.execute_input":"2026-09-07T13:58:51.483519Z","iopub.status.idle":"2026-09-07T13:58:51.651093Z","shell.execute_reply.started":"2026-09-07T13:58:51.483489Z","shell.execute_reply":"2026-09-07T13:58:51.650279Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 57: DENSENET FORWARD PASS\n# ============================================================\n\nimages, labels = next(iter(train_loader))\n\nimages = images.to(device)\nlabels = labels.to(device)\n\ndensenet.eval()\n\nwith torch.no_grad():\n    outputs = densenet(images)\n\nprint(\"=\" * 65)\nprint(\"DENSENET121 FORWARD PASS\")\nprint(\"=\" * 65)\n\nprint(\"Input :\", tuple(images.shape))\nprint(\"Output:\", tuple(outputs.shape))\nprint(\"Labels:\", tuple(labels.shape))\n\nassert outputs.shape == (images.shape[0], 5)\n\nprint(\"\\n✅ Forward pass successful\")\nprint(\"✅ 5-class output confirmed\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T13:59:01.528046Z","iopub.execute_input":"2026-09-07T13:59:01.528863Z","iopub.status.idle":"2026-09-07T13:59:02.06879Z","shell.execute_reply.started":"2026-09-07T13:59:01.528835Z","shell.execute_reply":"2026-09-07T13:59:02.067961Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 58: DENSENET TRAINING CONFIGURATION\n# ============================================================\n\ncriterion_dense = nn.CrossEntropyLoss(\n    weight=class_weights\n)\n\noptimizer_dense = torch.optim.AdamW(\n    filter(\n        lambda p: p.requires_grad,\n        densenet.parameters()\n    ),\n    lr=1e-3,\n    weight_decay=1e-4\n)\n\nscheduler_dense = torch.optim.lr_scheduler.ReduceLROnPlateau(\n    optimizer_dense,\n    mode=\"min\",\n    factor=0.5,\n    patience=2\n)\n\nscaler_dense = torch.amp.GradScaler(\n    \"cuda\",\n    enabled=(device.type == \"cuda\")\n)\n\nNUM_EPOCHS_DENSE = 12\n\nprint(\"=\" * 70)\nprint(\"DENSENET121 TRAINING CONFIGURATION\")\nprint(\"=\" * 70)\n\nprint(\"Epochs:        \", NUM_EPOCHS_DENSE)\nprint(\"Batch size:    \", BATCH_SIZE)\nprint(\"Input size:    224 × 224\")\nprint(\"Loss:          Weighted Cross Entropy\")\nprint(\"Optimizer:     AdamW\")\nprint(\"LR:            1e-3\")\nprint(\"Weight decay:  1e-4\")\nprint(\"Backbone:      Frozen\")\nprint(\"Device:        \", device)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T13:59:05.28302Z","iopub.execute_input":"2026-09-07T13:59:05.283863Z","iopub.status.idle":"2026-09-07T13:59:05.292652Z","shell.execute_reply.started":"2026-09-07T13:59:05.283824Z","shell.execute_reply":"2026-09-07T13:59:05.291595Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 59: TRAIN DENSENET121\n# ============================================================\n\nbest_val_loss_dense = float(\"inf\")\nbest_val_acc_dense = 0.0\nbest_epoch_dense = 0\n\ntrain_history_dense = []\nval_history_dense = []\n\nbest_model_dense = copy.deepcopy(\n    densenet.state_dict()\n)\n\nprint(\"=\" * 80)\nprint(\"             NETRX — DENSENET121 TRAINING\")\nprint(\"=\" * 80)\n\ntraining_start = time.time()\n\nfor epoch in range(NUM_EPOCHS_DENSE):\n\n    # --------------------------------------------------------\n    # TRAIN\n    # --------------------------------------------------------\n    densenet.train()\n\n    running_loss = 0.0\n    correct = 0\n    total = 0\n\n    for images, labels in train_loader:\n\n        images = images.to(device, non_blocking=True)\n        labels = labels.to(device, non_blocking=True)\n\n        optimizer_dense.zero_grad(set_to_none=True)\n\n        with torch.amp.autocast(\n            device_type=\"cuda\",\n            enabled=(device.type == \"cuda\")\n        ):\n\n            outputs = densenet(images)\n\n            loss = criterion_dense(\n                outputs,\n                labels\n            )\n\n        scaler_dense.scale(loss).backward()\n\n        scaler_dense.step(\n            optimizer_dense\n        )\n\n        scaler_dense.update()\n\n        running_loss += (\n            loss.item() * images.size(0)\n        )\n\n        _, predicted = torch.max(\n            outputs,\n            1\n        )\n\n        total += labels.size(0)\n\n        correct += (\n            predicted == labels\n        ).sum().item()\n\n    train_loss = running_loss / total\n    train_acc = correct / total\n\n    # --------------------------------------------------------\n    # VALIDATION\n    # --------------------------------------------------------\n    densenet.eval()\n\n    val_running_loss = 0.0\n    val_correct = 0\n    val_total = 0\n\n    with torch.no_grad():\n\n        for images, labels in val_loader:\n\n            images = images.to(\n                device,\n                non_blocking=True\n            )\n\n            labels = labels.to(\n                device,\n                non_blocking=True\n            )\n\n            with torch.amp.autocast(\n                device_type=\"cuda\",\n                enabled=(device.type == \"cuda\")\n            ):\n\n                outputs = densenet(images)\n\n                loss = criterion_dense(\n                    outputs,\n                    labels\n                )\n\n            val_running_loss += (\n                loss.item() * images.size(0)\n            )\n\n            _, predicted = torch.max(\n                outputs,\n                1\n            )\n\n            val_total += labels.size(0)\n\n            val_correct += (\n                predicted == labels\n            ).sum().item()\n\n    val_loss = val_running_loss / val_total\n    val_acc = val_correct / val_total\n\n    scheduler_dense.step(\n        val_loss\n    )\n\n    current_lr = (\n        optimizer_dense.param_groups[0][\"lr\"]\n    )\n\n    train_history_dense.append(\n        [train_loss, train_acc]\n    )\n\n    val_history_dense.append(\n        [val_loss, val_acc]\n    )\n\n    # --------------------------------------------------------\n    # BEST MODEL\n    # --------------------------------------------------------\n    if val_loss < best_val_loss_dense:\n\n        best_val_loss_dense = val_loss\n        best_val_acc_dense = val_acc\n        best_epoch_dense = epoch + 1\n\n        best_model_dense = copy.deepcopy(\n            densenet.state_dict()\n        )\n\n        torch.save(\n            best_model_dense,\n            \"/kaggle/working/netrx_densenet121_best.pth\"\n        )\n\n        status = \" ⭐ BEST\"\n\n    else:\n\n        status = \"\"\n\n    print(\n        f\"Epoch {epoch+1:02d}/{NUM_EPOCHS_DENSE} | \"\n        f\"loss: {train_loss:.4f} | \"\n        f\"accuracy: {train_acc:.4f} | \"\n        f\"val_loss: {val_loss:.4f} | \"\n        f\"val_accuracy: {val_acc:.4f} | \"\n        f\"lr: {current_lr:.1e}\"\n        f\"{status}\"\n    )\n\n\ntraining_time_dense = (\n    time.time() - training_start\n)\n\nprint(\"\\n\" + \"=\" * 80)\nprint(\"             DENSENET121 TRAINING COMPLETE\")\nprint(\"=\" * 80)\n\nprint(\n    f\"Best validation loss: \"\n    f\"{best_val_loss_dense:.4f}\"\n)\n\nprint(\n    f\"Best validation accuracy: \"\n    f\"{best_val_acc_dense:.4f} \"\n    f\"({best_val_acc_dense*100:.2f}%)\"\n)\n\nprint(\n    f\"Best epoch: {best_epoch_dense}\"\n)\n\nprint(\n    f\"Training time: \"\n    f\"{training_time_dense/60:.2f} minutes\"\n)\n\nprint(\n    \"Saved: \"\n    \"/kaggle/working/netrx_densenet121_best.pth\"\n)\n\nprint(\"=\" * 80)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T13:59:09.392769Z","iopub.execute_input":"2026-09-07T13:59:09.393644Z","iopub.status.idle":"2026-09-07T14:00:52.951948Z","shell.execute_reply.started":"2026-09-07T13:59:09.39353Z","shell.execute_reply":"2026-09-07T14:00:52.950986Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 60: NETRX — CONVNEXT-TINY\n# ============================================================\n\nimport torch\nimport torch.nn as nn\nimport copy\nimport time\n\nfrom torchvision.models import (\n    convnext_tiny,\n    ConvNeXt_Tiny_Weights\n)\n\nprint(\"=\" * 75)\nprint(\"             NETRX — CONVNEXT-TINY\")\nprint(\"=\" * 75)\n\nweights_convnext = ConvNeXt_Tiny_Weights.DEFAULT\n\nconvnext = convnext_tiny(\n    weights=weights_convnext\n)\n\nprint(\"✅ Pretrained ConvNeXt-Tiny loaded\")\nprint(\"Input : 224 × 224 × 3\")\nprint(\"Output: ImageNet classes initially\")\nprint(\"Device:\", device)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T14:04:27.878298Z","iopub.execute_input":"2026-09-07T14:04:27.879161Z","iopub.status.idle":"2026-09-07T14:04:28.44612Z","shell.execute_reply.started":"2026-09-07T14:04:27.879131Z","shell.execute_reply":"2026-09-07T14:04:28.44541Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 61: CONVNEXT-TINY — 5-CLASS DR CLASSIFIER\n# ============================================================\n\nnum_features_convnext = (\n    convnext.classifier[2].in_features\n)\n\nconvnext.classifier = nn.Sequential(\n    nn.Flatten(),\n\n    nn.LayerNorm(\n        num_features_convnext\n    ),\n\n    nn.Dropout(0.40),\n\n    nn.Linear(\n        num_features_convnext,\n        256\n    ),\n\n    nn.GELU(),\n\n    nn.Dropout(0.30),\n\n    nn.Linear(\n        256,\n        5\n    )\n)\n\nconvnext = convnext.to(device)\n\nprint(\"=\" * 70)\nprint(\"CONVNEXT-TINY CLASSIFIER CONFIGURED\")\nprint(\"=\" * 70)\n\nprint(\"Input features :\", num_features_convnext)\nprint(\"Output classes :\", 5)\nprint(\"Classes        : 0, 1, 2, 3, 4\")\nprint(\"Device         :\", device)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T14:04:54.278704Z","iopub.execute_input":"2026-09-07T14:04:54.278977Z","iopub.status.idle":"2026-09-07T14:04:54.338075Z","shell.execute_reply.started":"2026-09-07T14:04:54.278956Z","shell.execute_reply":"2026-09-07T14:04:54.337369Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 62: FREEZE CONVNEXT-TINY BACKBONE\n# ============================================================\n\nfor param in convnext.features.parameters():\n    param.requires_grad = False\n\nfor param in convnext.classifier.parameters():\n    param.requires_grad = True\n\n\ntotal_params_convnext = sum(\n    p.numel()\n    for p in convnext.parameters()\n)\n\ntrainable_params_convnext = sum(\n    p.numel()\n    for p in convnext.parameters()\n    if p.requires_grad\n)\n\nnon_trainable_params_convnext = (\n    total_params_convnext\n    - trainable_params_convnext\n)\n\nprint(\"=\" * 70)\nprint(\"CONVNEXT-TINY PARAMETER SUMMARY\")\nprint(\"=\" * 70)\n\nprint(\n    f\"Total parameters:      {total_params_convnext:,}\"\n)\n\nprint(\n    f\"Trainable parameters:  {trainable_params_convnext:,}\"\n)\n\nprint(\n    f\"Non-trainable params:  {non_trainable_params_convnext:,}\"\n)\n\nprint(\"\\nBackbone: FROZEN\")\nprint(\"Classifier: TRAINABLE\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T14:08:00.80069Z","iopub.execute_input":"2026-09-07T14:08:00.801379Z","iopub.status.idle":"2026-09-07T14:08:00.809713Z","shell.execute_reply.started":"2026-09-07T14:08:00.801345Z","shell.execute_reply":"2026-09-07T14:08:00.808799Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 63: CONVNEXT-TINY MODEL SUMMARY\n# ============================================================\n\ntry:\n    from torchinfo import summary\nexcept ImportError:\n    !pip install -q torchinfo\n    from torchinfo import summary\n\nprint(\"=\" * 75)\nprint(\"             NETRX — CONVNEXT-TINY SUMMARY\")\nprint(\"=\" * 75)\n\nsummary(\n    convnext,\n    input_size=(1, 3, 224, 224),\n    col_names=[\n        \"input_size\",\n        \"output_size\",\n        \"num_params\"\n    ],\n    depth=4\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T14:08:44.958724Z","iopub.execute_input":"2026-09-07T14:08:44.959451Z","iopub.status.idle":"2026-09-07T14:08:46.959105Z","shell.execute_reply.started":"2026-09-07T14:08:44.959416Z","shell.execute_reply":"2026-09-07T14:08:46.958428Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 64: CONVNEXT FORWARD PASS\n# ============================================================\n\nimages, labels = next(iter(train_loader))\n\nimages = images.to(\n    device,\n    non_blocking=True\n)\n\nlabels = labels.to(\n    device,\n    non_blocking=True\n)\n\nconvnext.eval()\n\nwith torch.no_grad():\n    outputs = convnext(images)\n\nprint(\"=\" * 65)\nprint(\"CONVNEXT-TINY FORWARD PASS\")\nprint(\"=\" * 65)\n\nprint(\"Input shape :\", tuple(images.shape))\nprint(\"Output shape:\", tuple(outputs.shape))\nprint(\"Labels shape:\", tuple(labels.shape))\n\nassert outputs.shape == (\n    images.shape[0],\n    5\n)\n\nprint(\"\\n✅ Forward pass successful\")\nprint(\"✅ 5-class output confirmed\")\nprint(\"✅ CUDA confirmed\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T14:11:07.368057Z","iopub.execute_input":"2026-09-07T14:11:07.369078Z","iopub.status.idle":"2026-09-07T14:11:09.819759Z","shell.execute_reply.started":"2026-09-07T14:11:07.369043Z","shell.execute_reply":"2026-09-07T14:11:09.818538Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 65: CONVNEXT-TINY TRAINING CONFIGURATION\n# ============================================================\n\ncriterion_convnext = nn.CrossEntropyLoss(\n    weight=class_weights\n)\n\noptimizer_convnext = torch.optim.AdamW(\n    filter(\n        lambda p: p.requires_grad,\n        convnext.parameters()\n    ),\n    lr=1e-3,\n    weight_decay=1e-4\n)\n\nscheduler_convnext = torch.optim.lr_scheduler.ReduceLROnPlateau(\n    optimizer_convnext,\n    mode=\"min\",\n    factor=0.5,\n    patience=2\n)\n\nscaler_convnext = torch.amp.GradScaler(\n    \"cuda\",\n    enabled=(device.type == \"cuda\")\n)\n\nNUM_EPOCHS_CONVNEXT = 12\n\nprint(\"=\" * 70)\nprint(\"CONVNEXT-TINY TRAINING CONFIGURATION\")\nprint(\"=\" * 70)\n\nprint(\"Epochs:        \", NUM_EPOCHS_CONVNEXT)\nprint(\"Batch size:    \", BATCH_SIZE)\nprint(\"Input size:    224 × 224\")\nprint(\"Loss:          Weighted Cross Entropy\")\nprint(\"Optimizer:     AdamW\")\nprint(\"Learning rate: 1e-3\")\nprint(\"Weight decay:  1e-4\")\nprint(\"Scheduler:     ReduceLROnPlateau\")\nprint(\"Backbone:      Frozen\")\nprint(\"Device:        \", device)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T14:11:37.143451Z","iopub.execute_input":"2026-09-07T14:11:37.143819Z","iopub.status.idle":"2026-09-07T14:11:37.152751Z","shell.execute_reply.started":"2026-09-07T14:11:37.143785Z","shell.execute_reply":"2026-09-07T14:11:37.151754Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 66: TRAIN CONVNEXT-TINY\n# ============================================================\n\nbest_val_loss_convnext = float(\"inf\")\nbest_val_acc_convnext = 0.0\nbest_epoch_convnext = 0\n\ntrain_history_convnext = []\nval_history_convnext = []\n\nbest_model_convnext = copy.deepcopy(\n    convnext.state_dict()\n)\n\nprint(\"=\" * 80)\nprint(\"             NETRX — CONVNEXT-TINY TRAINING\")\nprint(\"=\" * 80)\n\ntraining_start = time.time()\n\nfor epoch in range(NUM_EPOCHS_CONVNEXT):\n\n    # --------------------------------------------------------\n    # TRAIN\n    # --------------------------------------------------------\n    convnext.train()\n\n    running_loss = 0.0\n    correct = 0\n    total = 0\n\n    for images, labels in train_loader:\n\n        images = images.to(\n            device,\n            non_blocking=True\n        )\n\n        labels = labels.to(\n            device,\n            non_blocking=True\n        )\n\n        optimizer_convnext.zero_grad(\n            set_to_none=True\n        )\n\n        with torch.amp.autocast(\n            device_type=\"cuda\",\n            enabled=(device.type == \"cuda\")\n        ):\n\n            outputs = convnext(images)\n\n            loss = criterion_convnext(\n                outputs,\n                labels\n            )\n\n        scaler_convnext.scale(\n            loss\n        ).backward()\n\n        scaler_convnext.step(\n            optimizer_convnext\n        )\n\n        scaler_convnext.update()\n\n        running_loss += (\n            loss.item() * images.size(0)\n        )\n\n        _, predicted = torch.max(\n            outputs,\n            1\n        )\n\n        total += labels.size(0)\n\n        correct += (\n            predicted == labels\n        ).sum().item()\n\n    train_loss = running_loss / total\n    train_acc = correct / total\n\n    # --------------------------------------------------------\n    # VALIDATION\n    # --------------------------------------------------------\n    convnext.eval()\n\n    val_running_loss = 0.0\n    val_correct = 0\n    val_total = 0\n\n    with torch.no_grad():\n\n        for images, labels in val_loader:\n\n            images = images.to(\n                device,\n                non_blocking=True\n            )\n\n            labels = labels.to(\n                device,\n                non_blocking=True\n            )\n\n            with torch.amp.autocast(\n                device_type=\"cuda\",\n                enabled=(device.type == \"cuda\")\n            ):\n\n                outputs = convnext(images)\n\n                loss = criterion_convnext(\n                    outputs,\n                    labels\n                )\n\n            val_running_loss += (\n                loss.item() * images.size(0)\n            )\n\n            _, predicted = torch.max(\n                outputs,\n                1\n            )\n\n            val_total += labels.size(0)\n\n            val_correct += (\n                predicted == labels\n            ).sum().item()\n\n    val_loss = val_running_loss / val_total\n    val_acc = val_correct / val_total\n\n    # --------------------------------------------------------\n    # SCHEDULER\n    # --------------------------------------------------------\n    scheduler_convnext.step(\n        val_loss\n    )\n\n    current_lr = (\n        optimizer_convnext.param_groups[0][\"lr\"]\n    )\n\n    # --------------------------------------------------------\n    # HISTORY\n    # --------------------------------------------------------\n    train_history_convnext.append(\n        [train_loss, train_acc]\n    )\n\n    val_history_convnext.append(\n        [val_loss, val_acc]\n    )\n\n    # --------------------------------------------------------\n    # SAVE BEST MODEL\n    # --------------------------------------------------------\n    if val_loss < best_val_loss_convnext:\n\n        best_val_loss_convnext = val_loss\n        best_val_acc_convnext = val_acc\n        best_epoch_convnext = epoch + 1\n\n        best_model_convnext = copy.deepcopy(\n            convnext.state_dict()\n        )\n\n        torch.save(\n            best_model_convnext,\n            \"/kaggle/working/\"\n            \"netrx_convnext_tiny_best.pth\"\n        )\n\n        status = \" ⭐ BEST\"\n\n    else:\n\n        status = \"\"\n\n    print(\n        f\"Epoch {epoch+1:02d}/{NUM_EPOCHS_CONVNEXT} | \"\n        f\"loss: {train_loss:.4f} | \"\n        f\"accuracy: {train_acc:.4f} | \"\n        f\"val_loss: {val_loss:.4f} | \"\n        f\"val_accuracy: {val_acc:.4f} | \"\n        f\"lr: {current_lr:.1e}\"\n        f\"{status}\"\n    )\n\n\ntraining_time_convnext = (\n    time.time() - training_start\n)\n\nprint(\"\\n\" + \"=\" * 80)\nprint(\"             CONVNEXT-TINY TRAINING COMPLETE\")\nprint(\"=\" * 80)\n\nprint(\n    f\"Best validation loss: \"\n    f\"{best_val_loss_convnext:.4f}\"\n)\n\nprint(\n    f\"Best validation accuracy: \"\n    f\"{best_val_acc_convnext:.4f} \"\n    f\"({best_val_acc_convnext*100:.2f}%)\"\n)\n\nprint(\n    f\"Best epoch: \"\n    f\"{best_epoch_convnext}\"\n)\n\nprint(\n    f\"Training time: \"\n    f\"{training_time_convnext/60:.2f} minutes\"\n)\n\nprint(\n    \"Saved: \"\n    \"/kaggle/working/\"\n    \"netrx_convnext_tiny_best.pth\"\n)\n\nprint(\"=\" * 80)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T14:11:54.290555Z","iopub.execute_input":"2026-09-07T14:11:54.291341Z","iopub.status.idle":"2026-09-07T14:13:51.821359Z","shell.execute_reply.started":"2026-09-07T14:11:54.29131Z","shell.execute_reply":"2026-09-07T14:13:51.820424Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 67: NETRX — VISION TRANSFORMER (ViT-B/16)\n# ============================================================\n\nimport torch\nimport torch.nn as nn\nimport copy\nimport time\n\nfrom torchvision.models import (\n    vit_b_16,\n    ViT_B_16_Weights\n)\n\nprint(\"=\" * 75)\nprint(\"             NETRX — VISION TRANSFORMER\")\nprint(\"=\" * 75)\n\nweights_vit = ViT_B_16_Weights.DEFAULT\n\nvit = vit_b_16(\n    weights=weights_vit\n)\n\nprint(\"✅ Pretrained ViT-B/16 loaded\")\nprint(\"Input : 224 × 224 × 3\")\nprint(\"Device:\", device)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T14:13:58.553Z","iopub.execute_input":"2026-09-07T14:13:58.553825Z","iopub.status.idle":"2026-09-07T14:14:01.309781Z","shell.execute_reply.started":"2026-09-07T14:13:58.553781Z","shell.execute_reply":"2026-09-07T14:14:01.30912Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 68: NETRX — VIT-B/16 5-CLASS CLASSIFIER\n# ============================================================\n\nnum_features_vit = vit.heads.head.in_features\n\nvit.heads.head = nn.Sequential(\n    nn.Dropout(0.40),\n\n    nn.Linear(\n        num_features_vit,\n        256\n    ),\n\n    nn.GELU(),\n\n    nn.Dropout(0.30),\n\n    nn.Linear(\n        256,\n        5\n    )\n)\n\nvit = vit.to(device)\n\nprint(\"=\" * 70)\nprint(\"VIT-B/16 CLASSIFIER CONFIGURED\")\nprint(\"=\" * 70)\n\nprint(\"Input features :\", num_features_vit)\nprint(\"Output classes :\", 5)\nprint(\"Classes        : 0, 1, 2, 3, 4\")\nprint(\"Device         :\", device)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T14:14:04.3731Z","iopub.execute_input":"2026-09-07T14:14:04.373922Z","iopub.status.idle":"2026-09-07T14:14:04.470112Z","shell.execute_reply.started":"2026-09-07T14:14:04.37389Z","shell.execute_reply":"2026-09-07T14:14:04.469125Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 69: FREEZE VIT-B/16 BACKBONE\n# ============================================================\n\nfor param in vit.parameters():\n    param.requires_grad = False\n\nfor param in vit.heads.parameters():\n    param.requires_grad = True\n\n\ntotal_params_vit = sum(\n    p.numel()\n    for p in vit.parameters()\n)\n\ntrainable_params_vit = sum(\n    p.numel()\n    for p in vit.parameters()\n    if p.requires_grad\n)\n\nnon_trainable_params_vit = (\n    total_params_vit\n    - trainable_params_vit\n)\n\nprint(\"=\" * 70)\nprint(\"VIT-B/16 PARAMETER SUMMARY\")\nprint(\"=\" * 70)\n\nprint(\n    f\"Total parameters:      {total_params_vit:,}\"\n)\n\nprint(\n    f\"Trainable parameters:  {trainable_params_vit:,}\"\n)\n\nprint(\n    f\"Non-trainable params:  {non_trainable_params_vit:,}\"\n)\n\nprint(\"\\nTransformer backbone: FROZEN\")\nprint(\"Classification head:  TRAINABLE\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T14:14:10.357653Z","iopub.execute_input":"2026-09-07T14:14:10.357913Z","iopub.status.idle":"2026-09-07T14:14:10.366143Z","shell.execute_reply.started":"2026-09-07T14:14:10.357893Z","shell.execute_reply":"2026-09-07T14:14:10.365068Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 70: VIT-B/16 MODEL SUMMARY\n# ============================================================\n\ntry:\n    from torchinfo import summary\nexcept ImportError:\n    !pip install -q torchinfo\n    from torchinfo import summary\n\nprint(\"=\" * 75)\nprint(\"             NETRX — VIT-B/16 SUMMARY\")\nprint(\"=\" * 75)\n\nsummary(\n    vit,\n    input_size=(1, 3, 224, 224),\n    col_names=[\n        \"input_size\",\n        \"output_size\",\n        \"num_params\"\n    ],\n    depth=3\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T14:14:20.998747Z","iopub.execute_input":"2026-09-07T14:14:20.999027Z","iopub.status.idle":"2026-09-07T14:14:21.097735Z","shell.execute_reply.started":"2026-09-07T14:14:20.999004Z","shell.execute_reply":"2026-09-07T14:14:21.096934Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 71: VIT FORWARD PASS\n# ============================================================\n\nimages, labels = next(iter(train_loader))\n\nimages = images.to(\n    device,\n    non_blocking=True\n)\n\nlabels = labels.to(\n    device,\n    non_blocking=True\n)\n\nvit.eval()\n\nwith torch.no_grad():\n    outputs = vit(images)\n\nprint(\"=\" * 65)\nprint(\"VIT-B/16 FORWARD PASS\")\nprint(\"=\" * 65)\n\nprint(\"Input shape :\", tuple(images.shape))\nprint(\"Output shape:\", tuple(outputs.shape))\nprint(\"Labels shape:\", tuple(labels.shape))\n\nassert outputs.shape == (\n    images.shape[0],\n    5\n)\n\nprint(\"\\n✅ Forward pass successful\")\nprint(\"✅ 5-class output confirmed\")\nprint(\"✅ CUDA confirmed\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T14:19:19.003515Z","iopub.execute_input":"2026-09-07T14:19:19.003833Z","iopub.status.idle":"2026-09-07T14:19:19.494573Z","shell.execute_reply.started":"2026-09-07T14:19:19.003808Z","shell.execute_reply":"2026-09-07T14:19:19.493831Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 72: VIT-B/16 TRAINING CONFIGURATION\n# ============================================================\n\ncriterion_vit = nn.CrossEntropyLoss(\n    weight=class_weights\n)\n\noptimizer_vit = torch.optim.AdamW(\n    filter(\n        lambda p: p.requires_grad,\n        vit.parameters()\n    ),\n    lr=1e-3,\n    weight_decay=1e-4\n)\n\nscheduler_vit = torch.optim.lr_scheduler.ReduceLROnPlateau(\n    optimizer_vit,\n    mode=\"min\",\n    factor=0.5,\n    patience=2\n)\n\nscaler_vit = torch.amp.GradScaler(\n    \"cuda\",\n    enabled=(device.type == \"cuda\")\n)\n\nNUM_EPOCHS_VIT = 12\n\nprint(\"=\" * 70)\nprint(\"VIT-B/16 TRAINING CONFIGURATION\")\nprint(\"=\" * 70)\n\nprint(\"Epochs:        \", NUM_EPOCHS_VIT)\nprint(\"Batch size:    \", BATCH_SIZE)\nprint(\"Input size:    224 × 224\")\nprint(\"Loss:          Weighted Cross Entropy\")\nprint(\"Optimizer:     AdamW\")\nprint(\"Learning rate: 1e-3\")\nprint(\"Weight decay:  1e-4\")\nprint(\"Scheduler:     ReduceLROnPlateau\")\nprint(\"Backbone:      Frozen\")\nprint(\"Device:        \", device)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T14:19:24.684047Z","iopub.execute_input":"2026-09-07T14:19:24.684377Z","iopub.status.idle":"2026-09-07T14:19:24.693807Z","shell.execute_reply.started":"2026-09-07T14:19:24.68434Z","shell.execute_reply":"2026-09-07T14:19:24.692876Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 73: TRAIN VIT-B/16\n# ============================================================\n\nbest_val_loss_vit = float(\"inf\")\nbest_val_acc_vit = 0.0\nbest_epoch_vit = 0\n\ntrain_history_vit = []\nval_history_vit = []\n\nbest_model_vit = copy.deepcopy(\n    vit.state_dict()\n)\n\nprint(\"=\" * 80)\nprint(\"                NETRX — VIT-B/16 TRAINING\")\nprint(\"=\" * 80)\n\ntraining_start = time.time()\n\nfor epoch in range(NUM_EPOCHS_VIT):\n\n    # --------------------------------------------------------\n    # TRAIN\n    # --------------------------------------------------------\n    vit.train()\n\n    running_loss = 0.0\n    correct = 0\n    total = 0\n\n    for images, labels in train_loader:\n\n        images = images.to(\n            device,\n            non_blocking=True\n        )\n\n        labels = labels.to(\n            device,\n            non_blocking=True\n        )\n\n        optimizer_vit.zero_grad(\n            set_to_none=True\n        )\n\n        with torch.amp.autocast(\n            device_type=\"cuda\",\n            enabled=(device.type == \"cuda\")\n        ):\n\n            outputs = vit(images)\n\n            loss = criterion_vit(\n                outputs,\n                labels\n            )\n\n        scaler_vit.scale(\n            loss\n        ).backward()\n\n        scaler_vit.step(\n            optimizer_vit\n        )\n\n        scaler_vit.update()\n\n        running_loss += (\n            loss.item() * images.size(0)\n        )\n\n        _, predicted = torch.max(\n            outputs,\n            1\n        )\n\n        total += labels.size(0)\n\n        correct += (\n            predicted == labels\n        ).sum().item()\n\n    train_loss = running_loss / total\n    train_acc = correct / total\n\n    # --------------------------------------------------------\n    # VALIDATION\n    # --------------------------------------------------------\n    vit.eval()\n\n    val_running_loss = 0.0\n    val_correct = 0\n    val_total = 0\n\n    with torch.no_grad():\n\n        for images, labels in val_loader:\n\n            images = images.to(\n                device,\n                non_blocking=True\n            )\n\n            labels = labels.to(\n                device,\n                non_blocking=True\n            )\n\n            with torch.amp.autocast(\n                device_type=\"cuda\",\n                enabled=(device.type == \"cuda\")\n            ):\n\n                outputs = vit(images)\n\n                loss = criterion_vit(\n                    outputs,\n                    labels\n                )\n\n            val_running_loss += (\n                loss.item() * images.size(0)\n            )\n\n            _, predicted = torch.max(\n                outputs,\n                1\n            )\n\n            val_total += labels.size(0)\n\n            val_correct += (\n                predicted == labels\n            ).sum().item()\n\n    val_loss = val_running_loss / val_total\n    val_acc = val_correct / val_total\n\n    # --------------------------------------------------------\n    # SCHEDULER\n    # --------------------------------------------------------\n    scheduler_vit.step(\n        val_loss\n    )\n\n    current_lr = (\n        optimizer_vit.param_groups[0][\"lr\"]\n    )\n\n    # --------------------------------------------------------\n    # HISTORY\n    # --------------------------------------------------------\n    train_history_vit.append(\n        [train_loss, train_acc]\n    )\n\n    val_history_vit.append(\n        [val_loss, val_acc]\n    )\n\n    # --------------------------------------------------------\n    # SAVE BEST MODEL\n    # --------------------------------------------------------\n    if val_loss < best_val_loss_vit:\n\n        best_val_loss_vit = val_loss\n        best_val_acc_vit = val_acc\n        best_epoch_vit = epoch + 1\n\n        best_model_vit = copy.deepcopy(\n            vit.state_dict()\n        )\n\n        torch.save(\n            best_model_vit,\n            \"/kaggle/working/\"\n            \"netrx_vit_b16_best.pth\"\n        )\n\n        status = \" ⭐ BEST\"\n\n    else:\n\n        status = \"\"\n\n    print(\n        f\"Epoch {epoch+1:02d}/{NUM_EPOCHS_VIT} | \"\n        f\"loss: {train_loss:.4f} | \"\n        f\"accuracy: {train_acc:.4f} | \"\n        f\"val_loss: {val_loss:.4f} | \"\n        f\"val_accuracy: {val_acc:.4f} | \"\n        f\"lr: {current_lr:.1e}\"\n        f\"{status}\"\n    )\n\n\ntraining_time_vit = (\n    time.time() - training_start\n)\n\nprint(\"\\n\" + \"=\" * 80)\nprint(\"                VIT-B/16 TRAINING COMPLETE\")\nprint(\"=\" * 80)\n\nprint(\n    f\"Best validation loss: \"\n    f\"{best_val_loss_vit:.4f}\"\n)\n\nprint(\n    f\"Best validation accuracy: \"\n    f\"{best_val_acc_vit:.4f} \"\n    f\"({best_val_acc_vit*100:.2f}%)\"\n)\n\nprint(\n    f\"Best epoch: \"\n    f\"{best_epoch_vit}\"\n)\n\nprint(\n    f\"Training time: \"\n    f\"{training_time_vit/60:.2f} minutes\"\n)\n\nprint(\n    \"Saved: \"\n    \"/kaggle/working/\"\n    \"netrx_vit_b16_best.pth\"\n)\n\nprint(\"=\" * 80)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T14:19:28.633053Z","iopub.execute_input":"2026-09-07T14:19:28.6339Z","iopub.status.idle":"2026-09-07T14:21:46.601718Z","shell.execute_reply.started":"2026-09-07T14:19:28.633869Z","shell.execute_reply":"2026-09-07T14:21:46.600784Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 74: NETRX — SAVE ALL TRAINED MODEL CHECKPOINTS\n# ============================================================\n\nimport os\nimport shutil\n\nmodel_files = [\n    \"/kaggle/working/netrx_cnn_best.pth\",\n    \"/kaggle/working/netrx_resnet50_best.pth\",\n    \"/kaggle/working/netrx_efficientnet_b0_best.pth\",\n    \"/kaggle/working/netrx_densenet121_best.pth\",\n    \"/kaggle/working/netrx_convnext_tiny_best.pth\",\n    \"/kaggle/working/netrx_vit_b16_best.pth\"\n]\n\nprint(\"=\" * 75)\nprint(\"             NETRX — MODEL CHECKPOINT VERIFICATION\")\nprint(\"=\" * 75)\n\nfor path in model_files:\n\n    if os.path.exists(path):\n\n        size_mb = os.path.getsize(path) / (1024 ** 2)\n\n        print(\n            f\"✅ {os.path.basename(path):40s}\"\n            f\" {size_mb:.2f} MB\"\n        )\n\n    else:\n\n        print(\n            f\"❌ MISSING: {os.path.basename(path)}\"\n        )\n\nprint(\"=\" * 75)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T14:34:16.513223Z","iopub.execute_input":"2026-09-07T14:34:16.513759Z","iopub.status.idle":"2026-09-07T14:34:16.52193Z","shell.execute_reply.started":"2026-09-07T14:34:16.513722Z","shell.execute_reply":"2026-09-07T14:34:16.52103Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 75: NETRX — ENSEMBLE LEARNING ENVIRONMENT\n# ============================================================\n\nimport os\nimport gc\nimport numpy as np\nimport pandas as pd\nimport torch\nimport torch.nn as nn\nimport copy\n\nfrom sklearn.metrics import (\n    accuracy_score,\n    f1_score,\n    classification_report,\n    confusion_matrix,\n    cohen_kappa_score\n)\n\nprint(\"=\" * 75)\nprint(\"             NETRX — ENSEMBLE LEARNING\")\nprint(\"=\" * 75)\n\nprint(\"Models available:\")\nprint(\"1. CNN\")\nprint(\"2. ResNet50\")\nprint(\"3. EfficientNet-B0\")\nprint(\"4. DenseNet121\")\nprint(\"5. ConvNeXt-Tiny\")\nprint(\"6. ViT-B/16\")\n\nprint(\"\\nTest samples:\", len(test_dataset))\nprint(\"Device:\", device)\n\nprint(\"\\n✅ Ensemble environment ready\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T14:40:20.348478Z","iopub.execute_input":"2026-09-07T14:40:20.349487Z","iopub.status.idle":"2026-09-07T14:40:20.35673Z","shell.execute_reply.started":"2026-09-07T14:40:20.349431Z","shell.execute_reply":"2026-09-07T14:40:20.356046Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 76: INSPECT SAVED CHECKPOINT STRUCTURES\n# ============================================================\n\ncheckpoint_paths = {\n    \"CNN\": \"/kaggle/working/netrx_cnn_best.pth\",\n    \"ResNet50\": \"/kaggle/working/netrx_resnet50_best.pth\",\n    \"EfficientNet-B0\": \"/kaggle/working/netrx_efficientnet_b0_best.pth\",\n    \"DenseNet121\": \"/kaggle/working/netrx_densenet121_best.pth\",\n    \"ConvNeXt-Tiny\": \"/kaggle/working/netrx_convnext_tiny_best.pth\",\n    \"ViT-B/16\": \"/kaggle/working/netrx_vit_b16_best.pth\"\n}\n\nprint(\"=\" * 80)\nprint(\"             NETRX — CHECKPOINT STRUCTURE INSPECTION\")\nprint(\"=\" * 80)\n\nfor model_name, path in checkpoint_paths.items():\n\n    print(\"\\n\" + \"-\" * 80)\n    print(model_name)\n    print(\"-\" * 80)\n\n    checkpoint = torch.load(\n        path,\n        map_location=\"cpu\"\n    )\n\n    print(\"File:\", os.path.basename(path))\n\n    if isinstance(checkpoint, dict):\n\n        print(\"Type: Dictionary\")\n        print(\"Number of keys:\", len(checkpoint))\n\n        keys = list(checkpoint.keys())\n\n        print(\"\\nFirst 5 keys:\")\n        for key in keys[:5]:\n            print(\" \", key)\n\n        print(\"\\nLast 5 keys:\")\n        for key in keys[-5:]:\n            print(\" \", key)\n\n        # Detect nested checkpoint formats\n        nested_keys = [\n            key for key in checkpoint.keys()\n            if key in [\n                \"state_dict\",\n                \"model_state_dict\",\n                \"model\",\n                \"net\"\n            ]\n        ]\n\n        if nested_keys:\n            print(\n                \"\\n⚠️ Nested checkpoint keys:\",\n                nested_keys\n            )\n\n    else:\n\n        print(\n            \"Type:\",\n            type(checkpoint)\n        )\n\nprint(\"\\n\" + \"=\" * 80)\nprint(\"CHECKPOINT INSPECTION COMPLETE\")\nprint(\"=\" * 80)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T14:40:24.838719Z","iopub.execute_input":"2026-09-07T14:40:24.839442Z","iopub.status.idle":"2026-09-07T14:40:25.360443Z","shell.execute_reply.started":"2026-09-07T14:40:24.839401Z","shell.execute_reply":"2026-09-07T14:40:25.359626Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 77: NETRX — REBUILD AND LOAD ALL TRAINED MODELS\n# ============================================================\n\nimport torch\nimport torch.nn as nn\n\nfrom torchvision.models import (\n    resnet50,\n    efficientnet_b0,\n    densenet121,\n    convnext_tiny,\n    vit_b_16\n)\n\nfrom torchvision.models import (\n    ResNet50_Weights,\n    EfficientNet_B0_Weights,\n    DenseNet121_Weights,\n    ConvNeXt_Tiny_Weights,\n    ViT_B_16_Weights\n)\n\nprint(\"=\" * 85)\nprint(\"              NETRX — LOADING TRAINED ENSEMBLE MODELS\")\nprint(\"=\" * 85)\n\n\n# ============================================================\n# 1. CNN\n# ============================================================\n\nprint(\"\\n[1/6] Loading CNN...\")\n\ntry:\n\n    cnn_ensemble = NetrXCNN(\n        num_classes=5\n    )\n\nexcept TypeError:\n\n    cnn_ensemble = NetrXCNN()\n\ncnn_state = torch.load(\n    checkpoint_paths[\"CNN\"],\n    map_location=\"cpu\"\n)\n\ncnn_ensemble.load_state_dict(\n    cnn_state,\n    strict=True\n)\n\ncnn_ensemble = cnn_ensemble.to(device)\ncnn_ensemble.eval()\n\nprint(\"✅ CNN loaded successfully\")\n\n\n# ============================================================\n# 2. RESNET50\n# ============================================================\n\nprint(\"\\n[2/6] Loading ResNet50...\")\n\nresnet_ensemble = resnet50(\n    weights=None\n)\n\nresnet_ensemble.fc = nn.Sequential(\n    nn.Dropout(0.40),\n\n    nn.Linear(\n        2048,\n        256\n    ),\n\n    nn.ReLU(),\n\n    nn.Dropout(0.30),\n\n    nn.Linear(\n        256,\n        5\n    )\n)\n\nresnet_state = torch.load(\n    checkpoint_paths[\"ResNet50\"],\n    map_location=\"cpu\"\n)\n\nresnet_ensemble.load_state_dict(\n    resnet_state,\n    strict=True\n)\n\nresnet_ensemble = resnet_ensemble.to(device)\nresnet_ensemble.eval()\n\nprint(\"✅ ResNet50 loaded successfully\")\n\n\n# ============================================================\n# 3. EFFICIENTNET-B0\n# ============================================================\n\nprint(\"\\n[3/6] Loading EfficientNet-B0...\")\n\nefficientnet_ensemble = efficientnet_b0(\n    weights=None\n)\n\nefficientnet_ensemble.classifier = nn.Sequential(\n    nn.Dropout(0.40),\n\n    nn.Linear(\n        1280,\n        256\n    ),\n\n    nn.ReLU(),\n\n    nn.Dropout(0.30),\n\n    nn.Linear(\n        256,\n        5\n    )\n)\n\nefficientnet_state = torch.load(\n    checkpoint_paths[\"EfficientNet-B0\"],\n    map_location=\"cpu\"\n)\n\nefficientnet_ensemble.load_state_dict(\n    efficientnet_state,\n    strict=True\n)\n\nefficientnet_ensemble = efficientnet_ensemble.to(device)\nefficientnet_ensemble.eval()\n\nprint(\"✅ EfficientNet-B0 loaded successfully\")\n\n\n# ============================================================\n# 4. DENSENET121\n# ============================================================\n\nprint(\"\\n[4/6] Loading DenseNet121...\")\n\ndensenet_ensemble = densenet121(\n    weights=None\n)\n\ndensenet_ensemble.classifier = nn.Sequential(\n    nn.Dropout(0.40),\n\n    nn.Linear(\n        1024,\n        256\n    ),\n\n    nn.ReLU(),\n\n    nn.Dropout(0.30),\n\n    nn.Linear(\n        256,\n        5\n    )\n)\n\ndensenet_state = torch.load(\n    checkpoint_paths[\"DenseNet121\"],\n    map_location=\"cpu\"\n)\n\ndensenet_ensemble.load_state_dict(\n    densenet_state,\n    strict=True\n)\n\ndensenet_ensemble = densenet_ensemble.to(device)\ndensenet_ensemble.eval()\n\nprint(\"✅ DenseNet121 loaded successfully\")\n\n\n# ============================================================\n# 5. CONVNEXT-TINY\n# ============================================================\n\nprint(\"\\n[5/6] Loading ConvNeXt-Tiny...\")\n\nconvnext_ensemble = convnext_tiny(\n    weights=None\n)\n\nconvnext_ensemble.classifier = nn.Sequential(\n    nn.Flatten(),\n\n    nn.LayerNorm(\n        768\n    ),\n\n    nn.Dropout(0.40),\n\n    nn.Linear(\n        768,\n        256\n    ),\n\n    nn.GELU(),\n\n    nn.Dropout(0.30),\n\n    nn.Linear(\n        256,\n        5\n    )\n)\n\nconvnext_state = torch.load(\n    checkpoint_paths[\"ConvNeXt-Tiny\"],\n    map_location=\"cpu\"\n)\n\nconvnext_ensemble.load_state_dict(\n    convnext_state,\n    strict=True\n)\n\nconvnext_ensemble = convnext_ensemble.to(device)\nconvnext_ensemble.eval()\n\nprint(\"✅ ConvNeXt-Tiny loaded successfully\")\n\n\n# ============================================================\n# 6. VIT-B/16\n# ============================================================\n\nprint(\"\\n[6/6] Loading ViT-B/16...\")\n\nvit_ensemble = vit_b_16(\n    weights=None\n)\n\nvit_ensemble.heads.head = nn.Sequential(\n    nn.Dropout(0.40),\n\n    nn.Linear(\n        768,\n        256\n    ),\n\n    nn.GELU(),\n\n    nn.Dropout(0.30),\n\n    nn.Linear(\n        256,\n        5\n    )\n)\n\nvit_state = torch.load(\n    checkpoint_paths[\"ViT-B/16\"],\n    map_location=\"cpu\"\n)\n\nvit_ensemble.load_state_dict(\n    vit_state,\n    strict=True\n)\n\nvit_ensemble = vit_ensemble.to(device)\nvit_ensemble.eval()\n\nprint(\"✅ ViT-B/16 loaded successfully\")\n\n\n# ============================================================\n# FINAL VERIFICATION\n# ============================================================\n\nensemble_models = {\n    \"CNN\": cnn_ensemble,\n    \"ResNet50\": resnet_ensemble,\n    \"EfficientNet-B0\": efficientnet_ensemble,\n    \"DenseNet121\": densenet_ensemble,\n    \"ConvNeXt-Tiny\": convnext_ensemble,\n    \"ViT-B/16\": vit_ensemble\n}\n\nprint(\"\\n\" + \"=\" * 85)\nprint(\"                 ENSEMBLE MODEL VERIFICATION\")\nprint(\"=\" * 85)\n\nfor name, model in ensemble_models.items():\n\n    model.eval()\n\n    total_params = sum(\n        p.numel()\n        for p in model.parameters()\n    )\n\n    print(\n        f\"✅ {name:20s} | \"\n        f\"Parameters: {total_params:,}\"\n    )\n\nprint(\"=\" * 85)\nprint(\"🎯 ALL 6 MODELS LOADED WITH STRICT CHECKPOINT MATCHING\")\nprint(\"=\" * 85)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T14:42:12.795172Z","iopub.execute_input":"2026-09-07T14:42:12.795675Z","iopub.status.idle":"2026-09-07T14:42:15.561821Z","shell.execute_reply.started":"2026-09-07T14:42:12.795644Z","shell.execute_reply":"2026-09-07T14:42:15.560913Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 78: NETRX — EXTRACT TEST-SET PROBABILITIES\n# ============================================================\n\nimport numpy as np\nimport torch\n\nprint(\"=\" * 85)\nprint(\"          NETRX — TEST SET PROBABILITY EXTRACTION\")\nprint(\"=\" * 85)\n\n# ------------------------------------------------------------\n# Storage\n# ------------------------------------------------------------\n\nall_probabilities = {}\ntest_labels = []\n\n# ------------------------------------------------------------\n# Extract labels ONCE\n# ------------------------------------------------------------\n\nwith torch.no_grad():\n\n    for images, labels in test_loader:\n\n        test_labels.extend(\n            labels.cpu().numpy()\n        )\n\ntest_labels = np.array(test_labels)\n\nprint(\n    f\"Test samples collected: {len(test_labels)}\"\n)\n\n# ------------------------------------------------------------\n# Run each model\n# ------------------------------------------------------------\n\nfor model_name, model in ensemble_models.items():\n\n    print(\n        f\"\\n🔄 Processing {model_name}...\"\n    )\n\n    model.eval()\n\n    model_probabilities = []\n\n    with torch.no_grad():\n\n        for images, labels in test_loader:\n\n            images = images.to(\n                device,\n                non_blocking=True\n            )\n\n            outputs = model(images)\n\n            probabilities = torch.softmax(\n                outputs,\n                dim=1\n            )\n\n            model_probabilities.append(\n                probabilities.cpu().numpy()\n            )\n\n    model_probabilities = np.concatenate(\n        model_probabilities,\n        axis=0\n    )\n\n    all_probabilities[model_name] = (\n        model_probabilities\n    )\n\n    print(\n        f\"   Shape: {model_probabilities.shape}\"\n    )\n\n    print(\n        f\"   Probability sum check: \"\n        f\"{model_probabilities[0].sum():.4f}\"\n    )\n\n# ------------------------------------------------------------\n# Final verification\n# ------------------------------------------------------------\n\nprint(\"\\n\" + \"=\" * 85)\nprint(\"             PROBABILITY EXTRACTION COMPLETE\")\nprint(\"=\" * 85)\n\nfor model_name, probs in all_probabilities.items():\n\n    print(\n        f\"✅ {model_name:20s} \"\n        f\"{probs.shape}\"\n    )\n\nprint(\n    f\"\\nGround-truth labels: {test_labels.shape}\"\n)\n\n# ------------------------------------------------------------\n# Save predictions for safety\n# ------------------------------------------------------------\n\nnp.savez_compressed(\n    \"/kaggle/working/netrx_ensemble_predictions.npz\",\n    test_labels=test_labels,\n    **{\n        name.replace(\"-\", \"_\").replace(\"/\", \"_\"): probs\n        for name, probs in all_probabilities.items()\n    }\n)\n\nprint(\n    \"\\n💾 Saved:\"\n    \"\\n/kaggle/working/netrx_ensemble_predictions.npz\"\n)\n\nprint(\"=\" * 85)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T14:42:26.807048Z","iopub.execute_input":"2026-09-07T14:42:26.807869Z","iopub.status.idle":"2026-09-07T14:42:39.419486Z","shell.execute_reply.started":"2026-09-07T14:42:26.807837Z","shell.execute_reply":"2026-09-07T14:42:39.418676Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 79: NETRX — INDIVIDUAL MODEL TEST EVALUATION\n# ============================================================\n\nfrom sklearn.metrics import (\n    accuracy_score,\n    f1_score,\n    cohen_kappa_score,\n    classification_report,\n    confusion_matrix\n)\n\nprint(\"=\" * 90)\nprint(\"              NETRX — INDIVIDUAL MODEL TEST RESULTS\")\nprint(\"=\" * 90)\n\nindividual_results = []\n\nfor model_name, probs in all_probabilities.items():\n\n    predictions = np.argmax(\n        probs,\n        axis=1\n    )\n\n    accuracy = accuracy_score(\n        test_labels,\n        predictions\n    )\n\n    macro_f1 = f1_score(\n        test_labels,\n        predictions,\n        average=\"macro\",\n        zero_division=0\n    )\n\n    weighted_f1 = f1_score(\n        test_labels,\n        predictions,\n        average=\"weighted\",\n        zero_division=0\n    )\n\n    qwk = cohen_kappa_score(\n        test_labels,\n        predictions,\n        weights=\"quadratic\"\n    )\n\n    individual_results.append({\n        \"Model\": model_name,\n        \"Accuracy\": accuracy,\n        \"Macro F1\": macro_f1,\n        \"Weighted F1\": weighted_f1,\n        \"QWK\": qwk\n    })\n\n\nresults_df = pd.DataFrame(\n    individual_results\n)\n\nresults_df = results_df.sort_values(\n    by=\"Accuracy\",\n    ascending=False\n).reset_index(drop=True)\n\n\n# ------------------------------------------------------------\n# Display\n# ------------------------------------------------------------\n\nprint(\n    results_df.to_string(\n        index=False,\n        formatters={\n            \"Accuracy\": \"{:.4f}\".format,\n            \"Macro F1\": \"{:.4f}\".format,\n            \"Weighted F1\": \"{:.4f}\".format,\n            \"QWK\": \"{:.4f}\".format\n        }\n    )\n)\n\nprint(\"\\n\" + \"=\" * 90)\nprint(\"BEST INDIVIDUAL MODEL\")\nprint(\"=\" * 90)\n\nbest_model_name = results_df.iloc[0][\"Model\"]\n\nbest_accuracy = results_df.iloc[0][\"Accuracy\"]\n\nprint(\n    f\"Model    : {best_model_name}\"\n)\n\nprint(\n    f\"Accuracy : {best_accuracy:.4f} \"\n    f\"({best_accuracy * 100:.2f}%)\"\n)\n\nprint(\"=\" * 90)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T14:43:04.119925Z","iopub.execute_input":"2026-09-07T14:43:04.120783Z","iopub.status.idle":"2026-09-07T14:43:04.170438Z","shell.execute_reply.started":"2026-09-07T14:43:04.120744Z","shell.execute_reply":"2026-09-07T14:43:04.169803Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 80: NETRX — MODEL PREDICTION DIVERSITY ANALYSIS\n# ============================================================\n\nprint(\"=\" * 90)\nprint(\"             NETRX — MODEL PREDICTION DIVERSITY\")\nprint(\"=\" * 90)\n\n# ------------------------------------------------------------\n# Create predicted labels for every model\n# ------------------------------------------------------------\n\nprediction_matrix = {}\n\nfor model_name, probs in all_probabilities.items():\n\n    prediction_matrix[model_name] = np.argmax(\n        probs,\n        axis=1\n    )\n\nprediction_df = pd.DataFrame(\n    prediction_matrix\n)\n\nprint(\"\\nPrediction matrix:\")\nprint(\n    prediction_df.head(10).to_string()\n)\n\n\n# ------------------------------------------------------------\n# Prediction agreement matrix\n# ------------------------------------------------------------\n\nmodel_names = list(all_probabilities.keys())\n\nagreement_matrix = pd.DataFrame(\n    index=model_names,\n    columns=model_names,\n    dtype=float\n)\n\nfor model_a in model_names:\n\n    for model_b in model_names:\n\n        agreement_matrix.loc[\n            model_a,\n            model_b\n        ] = np.mean(\n            prediction_matrix[model_a]\n            ==\n            prediction_matrix[model_b]\n        )\n\n\nprint(\"\\n\" + \"=\" * 90)\nprint(\"PREDICTION AGREEMENT MATRIX\")\nprint(\"=\" * 90)\n\nprint(\n    agreement_matrix.round(3).to_string()\n)\n\n\n# ------------------------------------------------------------\n# Error diversity relative to ViT\n# ------------------------------------------------------------\n\nvit_predictions = prediction_matrix[\"ViT-B/16\"]\n\nprint(\"\\n\" + \"=\" * 90)\nprint(\"ERROR DIVERSITY RELATIVE TO ViT-B/16\")\nprint(\"=\" * 90)\n\nfor model_name in model_names:\n\n    if model_name == \"ViT-B/16\":\n        continue\n\n    model_predictions = (\n        prediction_matrix[model_name]\n    )\n\n    vit_correct = (\n        vit_predictions == test_labels\n    )\n\n    model_correct = (\n        model_predictions == test_labels\n    )\n\n    # Cases where ViT is wrong but another model is correct\n    useful_corrections = np.sum(\n        (~vit_correct) &\n        (model_correct)\n    )\n\n    # Cases where ViT is correct but another model is wrong\n    harmful_changes = np.sum(\n        (vit_correct) &\n        (~model_correct)\n    )\n\n    print(\n        f\"{model_name:20s} | \"\n        f\"ViT-wrong/model-correct: \"\n        f\"{useful_corrections:3d} | \"\n        f\"ViT-correct/model-wrong: \"\n        f\"{harmful_changes:3d}\"\n    )\n\n\n# ------------------------------------------------------------\n# Save matrices\n# ------------------------------------------------------------\n\nagreement_matrix.to_csv(\n    \"/kaggle/working/\"\n    \"netrx_model_agreement_matrix.csv\"\n)\n\nprediction_df.to_csv(\n    \"/kaggle/working/\"\n    \"netrx_model_predictions.csv\",\n    index=False\n)\n\nprint(\"\\n\" + \"=\" * 90)\nprint(\"✅ DIVERSITY ANALYSIS COMPLETE\")\nprint(\"=\" * 90)\n\nprint(\n    \"Saved:\"\n    \"\\n- netrx_model_agreement_matrix.csv\"\n    \"\\n- netrx_model_predictions.csv\"\n)\n\nprint(\"=\" * 90)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T14:43:33.729643Z","iopub.execute_input":"2026-09-07T14:43:33.730454Z","iopub.status.idle":"2026-09-07T14:43:33.762808Z","shell.execute_reply.started":"2026-09-07T14:43:33.730423Z","shell.execute_reply":"2026-09-07T14:43:33.762053Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 81: NETRX — SOFT VOTING ENSEMBLE\n# ============================================================\n\nprint(\"=\" * 90)\nprint(\"              NETRX — SOFT VOTING ENSEMBLE\")\nprint(\"=\" * 90)\n\n\ndef soft_voting_ensemble(\n    probabilities_dict,\n    selected_models\n):\n    \"\"\"\n    Average the class probabilities from selected models.\n    \"\"\"\n\n    probability_stack = np.stack(\n        [\n            probabilities_dict[name]\n            for name in selected_models\n        ],\n        axis=0\n    )\n\n    ensemble_probabilities = np.mean(\n        probability_stack,\n        axis=0\n    )\n\n    ensemble_predictions = np.argmax(\n        ensemble_probabilities,\n        axis=1\n    )\n\n    return (\n        ensemble_probabilities,\n        ensemble_predictions\n    )\n\n\n# ------------------------------------------------------------\n# Ensemble 1: All 6 models\n# ------------------------------------------------------------\n\nall_six_models = [\n    \"CNN\",\n    \"ResNet50\",\n    \"EfficientNet-B0\",\n    \"DenseNet121\",\n    \"ConvNeXt-Tiny\",\n    \"ViT-B/16\"\n]\n\nprobs_all6, pred_all6 = soft_voting_ensemble(\n    all_probabilities,\n    all_six_models\n)\n\n\n# ------------------------------------------------------------\n# Ensemble 2: Strong 4-model ensemble\n# ------------------------------------------------------------\n\nstrong_four_models = [\n    \"EfficientNet-B0\",\n    \"DenseNet121\",\n    \"ConvNeXt-Tiny\",\n    \"ViT-B/16\"\n]\n\nprobs_four, pred_four = soft_voting_ensemble(\n    all_probabilities,\n    strong_four_models\n)\n\n\n# ------------------------------------------------------------\n# Ensemble 3: Top 3 complementary models\n# ------------------------------------------------------------\n\ntop_three_models = [\n    \"EfficientNet-B0\",\n    \"DenseNet121\",\n    \"ViT-B/16\"\n]\n\nprobs_three, pred_three = soft_voting_ensemble(\n    all_probabilities,\n    top_three_models\n)\n\n\n# ------------------------------------------------------------\n# Evaluation helper\n# ------------------------------------------------------------\n\ndef evaluate_ensemble(\n    name,\n    probabilities,\n    predictions\n):\n\n    accuracy = accuracy_score(\n        test_labels,\n        predictions\n    )\n\n    macro_f1 = f1_score(\n        test_labels,\n        predictions,\n        average=\"macro\",\n        zero_division=0\n    )\n\n    weighted_f1 = f1_score(\n        test_labels,\n        predictions,\n        average=\"weighted\",\n        zero_division=0\n    )\n\n    qwk = cohen_kappa_score(\n        test_labels,\n        predictions,\n        weights=\"quadratic\"\n    )\n\n    return {\n        \"Ensemble\": name,\n        \"Accuracy\": accuracy,\n        \"Macro F1\": macro_f1,\n        \"Weighted F1\": weighted_f1,\n        \"QWK\": qwk\n    }\n\n\n# ------------------------------------------------------------\n# Compare ensembles\n# ------------------------------------------------------------\n\nensemble_results = []\n\nensemble_results.append(\n    evaluate_ensemble(\n        \"All 6 Models\",\n        probs_all6,\n        pred_all6\n    )\n)\n\nensemble_results.append(\n    evaluate_ensemble(\n        \"4-Model Ensemble\",\n        probs_four,\n        pred_four\n    )\n)\n\nensemble_results.append(\n    evaluate_ensemble(\n        \"Top-3 Ensemble\",\n        probs_three,\n        pred_three\n    )\n)\n\n\nensemble_df = pd.DataFrame(\n    ensemble_results\n)\n\nprint(\n    ensemble_df.to_string(\n        index=False,\n        formatters={\n            \"Accuracy\": \"{:.4f}\".format,\n            \"Macro F1\": \"{:.4f}\".format,\n            \"Weighted F1\": \"{:.4f}\".format,\n            \"QWK\": \"{:.4f}\".format\n        }\n    )\n)\n\n\n# ------------------------------------------------------------\n# Compare against ViT\n# ------------------------------------------------------------\n\nvit_accuracy = accuracy_score(\n    test_labels,\n    prediction_matrix[\"ViT-B/16\"]\n)\n\nprint(\"\\n\" + \"=\" * 90)\nprint(\"COMPARISON WITH BEST INDIVIDUAL MODEL\")\nprint(\"=\" * 90)\n\nprint(\n    f\"ViT-B/16 accuracy : \"\n    f\"{vit_accuracy:.4f} \"\n    f\"({vit_accuracy*100:.2f}%)\"\n)\n\nfor result in ensemble_results:\n\n    improvement = (\n        result[\"Accuracy\"] - vit_accuracy\n    )\n\n    print(\n        f\"{result['Ensemble']:20s}: \"\n        f\"{result['Accuracy']:.4f} \"\n        f\"({improvement:+.4f})\"\n    )\n\n\n# ------------------------------------------------------------\n# Save current ensemble predictions\n# ------------------------------------------------------------\n\nnp.savez_compressed(\n    \"/kaggle/working/netrx_soft_voting.npz\",\n\n    test_labels=test_labels,\n\n    all6_probabilities=probs_all6,\n    all6_predictions=pred_all6,\n\n    four_model_probabilities=probs_four,\n    four_model_predictions=pred_four,\n\n    top3_probabilities=probs_three,\n    top3_predictions=pred_three\n)\n\nprint(\"\\n\" + \"=\" * 90)\nprint(\"✅ SOFT VOTING COMPLETE\")\nprint(\"Saved: /kaggle/working/netrx_soft_voting.npz\")\nprint(\"=\" * 90)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T14:43:53.278415Z","iopub.execute_input":"2026-09-07T14:43:53.279433Z","iopub.status.idle":"2026-09-07T14:43:53.318366Z","shell.execute_reply.started":"2026-09-07T14:43:53.279401Z","shell.execute_reply":"2026-09-07T14:43:53.31726Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 82A — NETRX MODEL VARIABLE CHECK\n# ============================================================\n\nimport torch\nimport torch.nn as nn\n\nprint(\"=\" * 90)\nprint(\"             NETRX — CURRENT MODEL VARIABLES\")\nprint(\"=\" * 90)\n\nfound_models = []\n\nfor name, obj in globals().items():\n\n    try:\n        if isinstance(obj, nn.Module):\n            found_models.append((name, obj.__class__.__name__))\n    except:\n        pass\n\nif len(found_models) == 0:\n\n    print(\"❌ No PyTorch model variables found in memory.\")\n\nelse:\n\n    print(f\"✅ PyTorch model objects found: {len(found_models)}\")\n    print()\n\n    for name, class_name in found_models:\n        print(f\"   {name:35s} → {class_name}\")\n\nprint(\"=\" * 90)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T14:43:59.118089Z","iopub.execute_input":"2026-09-07T14:43:59.118864Z","iopub.status.idle":"2026-09-07T14:43:59.128884Z","shell.execute_reply.started":"2026-09-07T14:43:59.118833Z","shell.execute_reply":"2026-09-07T14:43:59.127812Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 82: NETRX — VALIDATION PROBABILITY EXTRACTION\n# ============================================================\n\nimport numpy as np\nimport torch\n\nprint(\"=\" * 90)\nprint(\"          NETRX — VALIDATION PROBABILITY EXTRACTION\")\nprint(\"=\" * 90)\n\n# ------------------------------------------------------------\n# Use the ACTUAL loaded ensemble model variables\n# ------------------------------------------------------------\n\nmodels = {\n    \"CNN\": cnn_ensemble,\n    \"ResNet50\": resnet_ensemble,\n    \"EfficientNet-B0\": efficientnet_ensemble,\n    \"DenseNet121\": densenet_ensemble,\n    \"ConvNeXt-Tiny\": convnext_ensemble,\n    \"ViT-B/16\": vit_ensemble\n}\n\n# ------------------------------------------------------------\n# Extract validation probabilities\n# ------------------------------------------------------------\n\nval_probabilities = {}\n\nfor model_name, model in models.items():\n\n    print(f\"\\n🔄 Processing {model_name}...\")\n\n    model.eval()\n\n    all_probs = []\n\n    with torch.no_grad():\n\n        for images, labels in val_loader:\n\n            images = images.to(device)\n\n            outputs = model(images)\n\n            probabilities = torch.softmax(\n                outputs,\n                dim=1\n            )\n\n            all_probs.append(\n                probabilities.cpu().numpy()\n            )\n\n    model_probs = np.concatenate(\n        all_probs,\n        axis=0\n    )\n\n    val_probabilities[model_name] = model_probs\n\n    print(f\"   Shape: {model_probs.shape}\")\n\n    print(\n        f\"   Probability sum check: \"\n        f\"{model_probs[0].sum():.4f}\"\n    )\n\n\n# ------------------------------------------------------------\n# Collect validation ground-truth labels\n# ------------------------------------------------------------\n\nval_labels_list = []\n\nfor images, labels in val_loader:\n\n    val_labels_list.extend(\n        labels.numpy()\n    )\n\nval_labels = np.array(\n    val_labels_list\n)\n\n\n# ------------------------------------------------------------\n# Final verification\n# ------------------------------------------------------------\n\nprint(\"\\n\" + \"=\" * 90)\nprint(\"       VALIDATION PROBABILITY EXTRACTION COMPLETE\")\nprint(\"=\" * 90)\n\nprint(\n    f\"Validation samples: {len(val_labels)}\"\n)\n\nfor name in val_probabilities:\n\n    print(\n        f\"✅ {name:20s} \"\n        f\"{val_probabilities[name].shape}\"\n    )\n\nprint(\n    f\"\\nGround-truth labels: \"\n    f\"{val_labels.shape}\"\n)\n\nprint(\"=\" * 90)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T14:44:08.048018Z","iopub.execute_input":"2026-09-07T14:44:08.048972Z","iopub.status.idle":"2026-09-07T14:44:20.612922Z","shell.execute_reply.started":"2026-09-07T14:44:08.048939Z","shell.execute_reply":"2026-09-07T14:44:20.612059Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 83: NETRX — VALIDATION-OPTIMIZED WEIGHTED ENSEMBLE\n# ============================================================\n\nimport numpy as np\nfrom sklearn.metrics import accuracy_score, f1_score, cohen_kappa_score\n\nprint(\"=\" * 95)\nprint(\"          NETRX — VALIDATION-OPTIMIZED WEIGHTED ENSEMBLE\")\nprint(\"=\" * 95)\n\n# ------------------------------------------------------------\n# Model order\n# ------------------------------------------------------------\n\nmodel_names = [\n    \"CNN\",\n    \"ResNet50\",\n    \"EfficientNet-B0\",\n    \"DenseNet121\",\n    \"ConvNeXt-Tiny\",\n    \"ViT-B/16\"\n]\n\n# Stack probabilities\nval_stack = np.stack(\n    [val_probabilities[name] for name in model_names],\n    axis=0\n)\n\nprint(\"Validation probability stack:\")\nprint(\"Shape:\", val_stack.shape)\n\n# ------------------------------------------------------------\n# Baseline: equal-weight ensemble\n# ------------------------------------------------------------\n\nequal_weights = np.ones(6) / 6\n\nequal_probs = np.sum(\n    val_stack * equal_weights[:, None, None],\n    axis=0\n)\n\nequal_pred = np.argmax(\n    equal_probs,\n    axis=1\n)\n\nequal_acc = accuracy_score(\n    val_labels,\n    equal_pred\n)\n\nequal_f1 = f1_score(\n    val_labels,\n    equal_pred,\n    average=\"macro\"\n)\n\nequal_qwk = cohen_kappa_score(\n    val_labels,\n    equal_pred,\n    weights=\"quadratic\"\n)\n\nprint(\"\\n\" + \"-\" * 95)\nprint(\"EQUAL-WEIGHT BASELINE\")\nprint(\"-\" * 95)\n\nprint(f\"Accuracy : {equal_acc:.4f}\")\nprint(f\"Macro F1 : {equal_f1:.4f}\")\nprint(f\"QWK      : {equal_qwk:.4f}\")\n\n\n# ------------------------------------------------------------\n# Generate candidate weights\n#\n# We use a deterministic random search over the simplex.\n# Validation data only.\n# ------------------------------------------------------------\n\nrng = np.random.default_rng(42)\n\nbest_acc = -1\nbest_f1 = -1\nbest_qwk = -1\n\nbest_acc_weights = None\nbest_f1_weights = None\nbest_qwk_weights = None\n\nnum_trials = 5000\n\nprint(\"\\n\" + \"-\" * 95)\nprint(f\"SEARCHING {num_trials} WEIGHT COMBINATIONS\")\nprint(\"-\" * 95)\n\nfor trial in range(num_trials):\n\n    # Dirichlet guarantees:\n    # weights >= 0\n    # weights sum to 1\n\n    weights = rng.dirichlet(\n        np.ones(6)\n    )\n\n    ensemble_probs = np.sum(\n        val_stack * weights[:, None, None],\n        axis=0\n    )\n\n    predictions = np.argmax(\n        ensemble_probs,\n        axis=1\n    )\n\n    acc = accuracy_score(\n        val_labels,\n        predictions\n    )\n\n    f1 = f1_score(\n        val_labels,\n        predictions,\n        average=\"macro\"\n    )\n\n    qwk = cohen_kappa_score(\n        val_labels,\n        predictions,\n        weights=\"quadratic\"\n    )\n\n    if acc > best_acc:\n\n        best_acc = acc\n        best_acc_weights = weights.copy()\n\n    if f1 > best_f1:\n\n        best_f1 = f1\n        best_f1_weights = weights.copy()\n\n    if qwk > best_qwk:\n\n        best_qwk = qwk\n        best_qwk_weights = weights.copy()\n\n\n# ------------------------------------------------------------\n# Display best results\n# ------------------------------------------------------------\n\nprint(\"\\n\" + \"=\" * 95)\nprint(\"                 BEST VALIDATION ENSEMBLES\")\nprint(\"=\" * 95)\n\nprint(\"\\n🏆 BEST ACCURACY ENSEMBLE\")\nprint(f\"Validation Accuracy : {best_acc:.4f}\")\nprint(\"Weights:\")\n\nfor name, weight in zip(\n    model_names,\n    best_acc_weights\n):\n    print(\n        f\"   {name:20s}: {weight:.4f}\"\n    )\n\n\nprint(\"\\n🏆 BEST MACRO-F1 ENSEMBLE\")\nprint(f\"Validation Macro F1 : {best_f1:.4f}\")\nprint(\"Weights:\")\n\nfor name, weight in zip(\n    model_names,\n    best_f1_weights\n):\n    print(\n        f\"   {name:20s}: {weight:.4f}\"\n    )\n\n\nprint(\"\\n🏆 BEST QWK ENSEMBLE\")\nprint(f\"Validation QWK : {best_qwk:.4f}\")\nprint(\"Weights:\")\n\nfor name, weight in zip(\n    model_names,\n    best_qwk_weights\n):\n    print(\n        f\"   {name:20s}: {weight:.4f}\"\n    )\n\n\n# ------------------------------------------------------------\n# Compare improvement over equal weighting\n# ------------------------------------------------------------\n\nprint(\"\\n\" + \"=\" * 95)\nprint(\"                 VALIDATION COMPARISON\")\nprint(\"=\" * 95)\n\nprint(\n    f\"Equal-weight Accuracy : {equal_acc:.4f}\"\n)\n\nprint(\n    f\"Optimized Accuracy    : {best_acc:.4f}\"\n)\n\nprint(\n    f\"Improvement            : \"\n    f\"{best_acc - equal_acc:+.4f}\"\n)\n\nprint(\n    f\"\\nEqual-weight Macro F1 : {equal_f1:.4f}\"\n)\n\nprint(\n    f\"Optimized Macro F1    : {best_f1:.4f}\"\n)\n\nprint(\n    f\"Improvement            : \"\n    f\"{best_f1 - equal_f1:+.4f}\"\n)\n\nprint(\n    f\"\\nEqual-weight QWK      : {equal_qwk:.4f}\"\n)\n\nprint(\n    f\"Optimized QWK         : {best_qwk:.4f}\"\n)\n\nprint(\n    f\"Improvement            : \"\n    f\"{best_qwk - equal_qwk:+.4f}\"\n)\n\n\n# ------------------------------------------------------------\n# Save optimized weights\n# ------------------------------------------------------------\n\nnp.savez(\n    \"/kaggle/working/netrx_optimized_ensemble_weights.npz\",\n\n    model_names=np.array(\n        model_names\n    ),\n\n    accuracy_weights=best_acc_weights,\n\n    f1_weights=best_f1_weights,\n\n    qwk_weights=best_qwk_weights\n)\n\nprint(\"\\n\" + \"=\" * 95)\nprint(\"✅ WEIGHT OPTIMIZATION COMPLETE\")\nprint(\"=\" * 95)\n\nprint(\n    \"Saved: \"\n    \"/kaggle/working/netrx_optimized_ensemble_weights.npz\"\n)\n\nprint(\"=\" * 95)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T14:44:26.963Z","iopub.execute_input":"2026-09-07T14:44:26.963629Z","iopub.status.idle":"2026-09-07T14:44:40.236923Z","shell.execute_reply.started":"2026-09-07T14:44:26.963574Z","shell.execute_reply":"2026-09-07T14:44:40.23619Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 84A — RESTORE TEST PROBABILITIES (FIXED)\n# ============================================================\n\nimport numpy as np\n\nprint(\"=\" * 90)\nprint(\"        NETRX — RESTORING SAVED TEST PROBABILITIES\")\nprint(\"=\" * 90)\n\nprediction_file = \"/kaggle/working/netrx_ensemble_predictions.npz\"\n\ndata = np.load(\n    prediction_file,\n    allow_pickle=True\n)\n\nprint(\"\\nSaved arrays:\")\nfor key in data.files:\n    print(f\"   {key:25s} -> {data[key].shape}\")\n\n\n# ------------------------------------------------------------\n# IMPORTANT:\n# NPZ names use underscores, while our display names use\n# hyphens/slashes.\n# ------------------------------------------------------------\n\nname_mapping = {\n    \"CNN\": \"CNN\",\n    \"ResNet50\": \"ResNet50\",\n    \"EfficientNet-B0\": \"EfficientNet_B0\",\n    \"DenseNet121\": \"DenseNet121\",\n    \"ConvNeXt-Tiny\": \"ConvNeXt_Tiny\",\n    \"ViT-B/16\": \"ViT_B_16\"\n}\n\n\n# ------------------------------------------------------------\n# Restore probabilities\n# ------------------------------------------------------------\n\ntest_probabilities = {}\n\nfor display_name, npz_name in name_mapping.items():\n\n    if npz_name not in data.files:\n        raise KeyError(\n            f\"Missing '{npz_name}' in saved NPZ file.\"\n        )\n\n    test_probabilities[display_name] = data[npz_name]\n\n\n# ------------------------------------------------------------\n# Restore ground-truth labels\n# ------------------------------------------------------------\n\nif \"test_labels\" not in data.files:\n    raise KeyError(\n        \"test_labels not found in saved NPZ file.\"\n    )\n\ntest_labels = data[\"test_labels\"]\n\n\n# ------------------------------------------------------------\n# Verification\n# ------------------------------------------------------------\n\nprint(\"\\n\" + \"=\" * 90)\nprint(\"RESTORATION COMPLETE\")\nprint(\"=\" * 90)\n\nfor name in name_mapping.keys():\n\n    probs = test_probabilities[name]\n\n    print(\n        f\"✅ {name:20s} \"\n        f\"{probs.shape} | \"\n        f\"First sample probability sum: \"\n        f\"{probs[0].sum():.4f}\"\n    )\n\nprint(\n    f\"\\nGround-truth labels: {test_labels.shape}\"\n)\n\nprint(\"\\nModel probability dictionary keys:\")\nfor name in test_probabilities.keys():\n    print(f\"   ✅ {name}\")\n\nprint(\"\\n✅ test_probabilities is now ready.\")\nprint(\"=\" * 90)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T14:45:07.662723Z","iopub.execute_input":"2026-09-07T14:45:07.663228Z","iopub.status.idle":"2026-09-07T14:45:07.678292Z","shell.execute_reply.started":"2026-09-07T14:45:07.663199Z","shell.execute_reply":"2026-09-07T14:45:07.67758Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 85 — NETRX FINAL WEIGHTED ENSEMBLE TEST EVALUATION\n# ============================================================\n\nimport numpy as np\nfrom sklearn.metrics import (\n    accuracy_score,\n    f1_score,\n    cohen_kappa_score,\n    classification_report,\n    confusion_matrix\n)\n\nprint(\"=\" * 95)\nprint(\"              NETRX — FINAL WEIGHTED ENSEMBLE\")\nprint(\"=\" * 95)\n\n\n# ------------------------------------------------------------\n# 1. Validation-optimized weights\n# ------------------------------------------------------------\n\nmodel_names = [\n    \"CNN\",\n    \"ResNet50\",\n    \"EfficientNet-B0\",\n    \"DenseNet121\",\n    \"ConvNeXt-Tiny\",\n    \"ViT-B/16\"\n]\n\nweights = np.array([\n    0.0333,\n    0.0538,\n    0.1411,\n    0.1196,\n    0.3172,\n    0.3349\n])\n\nprint(\"\\nValidation-optimized weights:\")\n\nfor name, weight in zip(model_names, weights):\n    print(f\"   {name:20s}: {weight:.4f}\")\n\nprint(f\"\\nWeight sum: {weights.sum():.4f}\")\n\n\n# ------------------------------------------------------------\n# 2. Stack test probabilities\n# Shape = (6, 367, 5)\n# ------------------------------------------------------------\n\ntest_probability_stack = np.stack(\n    [\n        test_probabilities[name]\n        for name in model_names\n    ],\n    axis=0\n)\n\nprint(\"\\nTest probability stack:\")\nprint(f\"Shape: {test_probability_stack.shape}\")\n\n\n# ------------------------------------------------------------\n# 3. Weighted probability fusion\n# ------------------------------------------------------------\n\nfinal_test_probabilities = np.sum(\n    test_probability_stack *\n    weights[:, None, None],\n    axis=0\n)\n\nprint(\n    \"Final probability matrix:\",\n    final_test_probabilities.shape\n)\n\nprint(\n    \"Probability sum check:\",\n    final_test_probabilities[0].sum()\n)\n\n\n# ------------------------------------------------------------\n# 4. Final predictions\n# ------------------------------------------------------------\n\nfinal_predictions = np.argmax(\n    final_test_probabilities,\n    axis=1\n)\n\ny_true = test_labels\n\n\n# ------------------------------------------------------------\n# 5. Classification metrics\n# ------------------------------------------------------------\n\naccuracy = accuracy_score(\n    y_true,\n    final_predictions\n)\n\nmacro_f1 = f1_score(\n    y_true,\n    final_predictions,\n    average=\"macro\"\n)\n\nweighted_f1 = f1_score(\n    y_true,\n    final_predictions,\n    average=\"weighted\"\n)\n\nqwk = cohen_kappa_score(\n    y_true,\n    final_predictions,\n    weights=\"quadratic\"\n)\n\n\n# ------------------------------------------------------------\n# 6. RESULTS\n# ------------------------------------------------------------\n\nprint(\"\\n\" + \"=\" * 95)\nprint(\"                    FINAL TEST RESULTS\")\nprint(\"=\" * 95)\n\nprint(f\"Accuracy       : {accuracy:.4f} ({accuracy * 100:.2f}%)\")\nprint(f\"Macro F1       : {macro_f1:.4f}\")\nprint(f\"Weighted F1    : {weighted_f1:.4f}\")\nprint(f\"QWK            : {qwk:.4f}\")\n\n\n# ------------------------------------------------------------\n# 7. Classification report\n# ------------------------------------------------------------\n\nprint(\"\\n\" + \"=\" * 95)\nprint(\"                 CLASSIFICATION REPORT\")\nprint(\"=\" * 95)\n\nprint(\n    classification_report(\n        y_true,\n        final_predictions,\n        labels=[0, 1, 2, 3, 4],\n        target_names=[\n            \"Grade 0\",\n            \"Grade 1\",\n            \"Grade 2\",\n            \"Grade 3\",\n            \"Grade 4\"\n        ],\n        digits=4,\n        zero_division=0\n    )\n)\n\n\n# ------------------------------------------------------------\n# 8. Confusion matrix\n# ------------------------------------------------------------\n\ncm = confusion_matrix(\n    y_true,\n    final_predictions,\n    labels=[0, 1, 2, 3, 4]\n)\n\nprint(\"=\" * 95)\nprint(\"                    CONFUSION MATRIX\")\nprint(\"=\" * 95)\n\nprint(\"Rows = Actual | Columns = Predicted\")\nprint()\n\nprint(\"          Predicted\")\nprint(\"          0    1    2    3    4\")\n\nfor i, row in enumerate(cm):\n    print(\n        f\"Actual {i}   \"\n        + \" \".join(f\"{x:4d}\" for x in row)\n    )\n\n\n# ------------------------------------------------------------\n# 9. Save final predictions\n# ------------------------------------------------------------\n\nfinal_output = \"/kaggle/working/netrx_final_weighted_ensemble.npz\"\n\nnp.savez(\n    final_output,\n    probabilities=final_test_probabilities,\n    predictions=final_predictions,\n    labels=y_true,\n    weights=weights,\n    model_names=np.array(model_names)\n)\n\nprint(\"\\n\" + \"=\" * 95)\nprint(\"✅ FINAL ENSEMBLE EVALUATION COMPLETE\")\nprint(\"=\" * 95)\n\nprint(f\"Saved: {final_output}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T14:45:13.900685Z","iopub.execute_input":"2026-09-07T14:45:13.900951Z","iopub.status.idle":"2026-09-07T14:45:13.932136Z","shell.execute_reply.started":"2026-09-07T14:45:13.90093Z","shell.execute_reply":"2026-09-07T14:45:13.931371Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# # NetrX — Referable Diabetic Retinopathy Screening\n\n# For clinical screening, the five diabetic retinopathy grades are converted into a binary decision:\n\n# - **Non-Referable DR:** Grade 0–1\n# - **Referable DR:** Grade 2–4\n\n# The primary screening objective is to maximize sensitivity for referable DR while maintaining acceptable specificity.\n\n# The threshold is optimized using the validation set only and is subsequently evaluated on the held-out test set to avoid test-set leakage.\n\n# This provides a clinically relevant evaluation aligned with the SIH26038 objective of high sensitivity and specificity for referable diabetic retinopathy.","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T14:46:44.014264Z","iopub.execute_input":"2026-09-07T14:46:44.014565Z","iopub.status.idle":"2026-09-07T14:46:44.018994Z","shell.execute_reply.started":"2026-09-07T14:46:44.014523Z","shell.execute_reply":"2026-09-07T14:46:44.018032Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 87 — NETRX REFERABLE DR BASELINE\n# ============================================================\n\nimport numpy as np\nfrom sklearn.metrics import (\n    confusion_matrix,\n    accuracy_score,\n    precision_score,\n    recall_score,\n    f1_score,\n    roc_auc_score\n)\n\nprint(\"=\" * 95)\nprint(\"             NETRX — REFERABLE DR SCREENING BASELINE\")\nprint(\"=\" * 95)\n\n# ------------------------------------------------------------\n# 1. Convert 5-class labels to binary\n# ------------------------------------------------------------\n# Grade 0-1 = Non-Referable\n# Grade 2-4 = Referable\n\ny_true_binary = (test_labels >= 2).astype(int)\n\n# Current ensemble prediction = argmax\nbinary_predictions = (final_predictions >= 2).astype(int)\n\n# ------------------------------------------------------------\n# 2. Confusion matrix\n# ------------------------------------------------------------\n\ncm_binary = confusion_matrix(\n    y_true_binary,\n    binary_predictions,\n    labels=[0, 1]\n)\n\ntn, fp, fn, tp = cm_binary.ravel()\n\n# ------------------------------------------------------------\n# 3. Clinical screening metrics\n# ------------------------------------------------------------\n\nsensitivity = tp / (tp + fn) if (tp + fn) > 0 else 0\nspecificity = tn / (tn + fp) if (tn + fp) > 0 else 0\nprecision = tp / (tp + fp) if (tp + fp) > 0 else 0\nf1 = f1_score(\n    y_true_binary,\n    binary_predictions,\n    zero_division=0\n)\n\nbinary_accuracy = accuracy_score(\n    y_true_binary,\n    binary_predictions\n)\n\n# ------------------------------------------------------------\n# 4. Display results\n# ------------------------------------------------------------\n\nprint(\"\\nBinary definition:\")\nprint(\"   0 = Non-Referable DR (Grade 0–1)\")\nprint(\"   1 = Referable DR (Grade 2–4)\")\n\nprint(\"\\n\" + \"-\" * 95)\nprint(\"                    CONFUSION MATRIX\")\nprint(\"-\" * 95)\n\nprint(\"                  Predicted\")\nprint(\"                Non-Ref  Referable\")\nprint(f\"Actual Non-Ref     {tn:4d}      {fp:4d}\")\nprint(f\"Actual Referable  {fn:4d}      {tp:4d}\")\n\nprint(\"\\n\" + \"=\" * 95)\nprint(\"              REFERABLE DR PERFORMANCE\")\nprint(\"=\" * 95)\n\nprint(f\"Sensitivity / Recall : {sensitivity:.4f} ({sensitivity*100:.2f}%)\")\nprint(f\"Specificity          : {specificity:.4f} ({specificity*100:.2f}%)\")\nprint(f\"Precision / PPV      : {precision:.4f} ({precision*100:.2f}%)\")\nprint(f\"F1 Score             : {f1:.4f}\")\nprint(f\"Binary Accuracy      : {binary_accuracy:.4f} ({binary_accuracy*100:.2f}%)\")\n\nprint(\"\\n\" + \"=\" * 95)\nprint(\"                    SIH TARGET CHECK\")\nprint(\"=\" * 95)\n\nprint(\n    f\"Sensitivity >90% : \"\n    f\"{'✅ ACHIEVED' if sensitivity > 0.90 else '❌ NOT YET'}\"\n)\n\nprint(\n    f\"Specificity >85% : \"\n    f\"{'✅ ACHIEVED' if specificity > 0.85 else '❌ NOT YET'}\"\n)\n\nprint(\"=\" * 95)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T14:45:26.467806Z","iopub.execute_input":"2026-09-07T14:45:26.468577Z","iopub.status.idle":"2026-09-07T14:45:26.484519Z","shell.execute_reply.started":"2026-09-07T14:45:26.468529Z","shell.execute_reply":"2026-09-07T14:45:26.483756Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL — NETRX VALIDATION-BASED REFERABLE DR THRESHOLD\n# ============================================================\n\nimport numpy as np\nfrom sklearn.metrics import (\n    confusion_matrix,\n    f1_score,\n    accuracy_score\n)\n\n# ------------------------------------------------------------\n# 1. Model names and validation ensemble weights\n# ------------------------------------------------------------\n\nmodel_names = [\n    \"CNN\",\n    \"ResNet50\",\n    \"EfficientNet-B0\",\n    \"DenseNet121\",\n    \"ConvNeXt-Tiny\",\n    \"ViT-B/16\"\n]\n\nweights = np.array([\n    0.0333,\n    0.0538,\n    0.1411,\n    0.1196,\n    0.3172,\n    0.3349\n], dtype=np.float32)\n\n# Normalize weights\nweights = weights / weights.sum()\n\nprint(\"=\" * 80)\nprint(\"NETRX REFERABLE DR THRESHOLD OPTIMIZATION\")\nprint(\"=\" * 80)\n\nprint(\"\\nModels:\")\nfor name, weight in zip(model_names, weights):\n    print(f\"  {name:<20} : {weight:.4f}\")\n\n\n# ------------------------------------------------------------\n# 2. Stack validation probabilities\n# ------------------------------------------------------------\n\nval_probability_stack = np.stack(\n    [\n        val_probabilities[name]\n        for name in model_names\n    ],\n    axis=0\n)\n\nprint(\"\\nValidation probability stack:\")\nprint(\"Shape:\", val_probability_stack.shape)\n\n\n# ------------------------------------------------------------\n# 3. Weighted ensemble probabilities\n# ------------------------------------------------------------\n\nval_ensemble_probabilities = np.sum(\n    val_probability_stack * weights[:, None, None],\n    axis=0\n)\n\nprint(\n    \"Ensemble probability shape:\",\n    val_ensemble_probabilities.shape\n)\n\nprint(\n    \"Probability sum check:\",\n    val_ensemble_probabilities[0].sum()\n)\n\n\n# ------------------------------------------------------------\n# 4. Calculate referable DR probability\n# ------------------------------------------------------------\n# Grade 0,1 = Non-Referable\n# Grade 2,3,4 = Referable\n\nval_referable_probability = (\n    val_ensemble_probabilities[:, 2]\n    + val_ensemble_probabilities[:, 3]\n    + val_ensemble_probabilities[:, 4]\n)\n\nval_true_binary = (\n    val_labels >= 2\n).astype(int)\n\n\nprint(\"\\nReferable DR definition:\")\nprint(\"  Grade 0-1 → Non-Referable\")\nprint(\"  Grade 2-4 → Referable\")\n\n\n# ------------------------------------------------------------\n# 5. Search thresholds from 0.20 to 0.80\n# ------------------------------------------------------------\n\nthresholds = np.arange(\n    0.20,\n    0.81,\n    0.01\n)\n\nresults = []\n\nfor threshold in thresholds:\n\n    predictions = (\n        val_referable_probability >= threshold\n    ).astype(int)\n\n    tn, fp, fn, tp = confusion_matrix(\n        val_true_binary,\n        predictions,\n        labels=[0, 1]\n    ).ravel()\n\n    # Sensitivity\n    sensitivity = (\n        tp / (tp + fn)\n        if (tp + fn) > 0\n        else 0.0\n    )\n\n    # Specificity\n    specificity = (\n        tn / (tn + fp)\n        if (tn + fp) > 0\n        else 0.0\n    )\n\n    # Precision\n    precision = (\n        tp / (tp + fp)\n        if (tp + fp) > 0\n        else 0.0\n    )\n\n    # F1\n    f1 = f1_score(\n        val_true_binary,\n        predictions,\n        zero_division=0\n    )\n\n    # Accuracy\n    accuracy = accuracy_score(\n        val_true_binary,\n        predictions\n    )\n\n    results.append([\n        threshold,\n        sensitivity,\n        specificity,\n        precision,\n        f1,\n        accuracy\n    ])\n\n\nresults = np.array(results)\n\n\n# ------------------------------------------------------------\n# 6. Find threshold satisfying SIH targets\n# ------------------------------------------------------------\n\n# SIH target:\n# Sensitivity > 90%\n# Specificity > 85%\n\nvalid = results[\n    (results[:, 1] > 0.90) &\n    (results[:, 2] > 0.85)\n]\n\n\nprint(\"\\n\" + \"=\" * 80)\nprint(\"VALIDATION THRESHOLD RESULTS\")\nprint(\"=\" * 80)\n\n\nif len(valid) > 0:\n\n    # Choose highest F1 among thresholds\n    # satisfying both requirements\n\n    best_index = np.argmax(valid[:, 4])\n\n    best = valid[best_index]\n\n    best_threshold = best[0]\n\n    print(\"\\n✅ Thresholds satisfying SIH targets:\")\n    print(f\"   Number found: {len(valid)}\")\n\n    print(\"\\n🏆 SELECTED THRESHOLD\")\n    print(f\"   Threshold   : {best_threshold:.2f}\")\n    print(\n        f\"   Sensitivity : {best[1]:.4f} \"\n        f\"({best[1] * 100:.2f}%)\"\n    )\n    print(\n        f\"   Specificity : {best[2]:.4f} \"\n        f\"({best[2] * 100:.2f}%)\"\n    )\n    print(\n        f\"   Precision   : {best[3]:.4f} \"\n        f\"({best[3] * 100:.2f}%)\"\n    )\n    print(\n        f\"   F1 Score    : {best[4]:.4f}\"\n    )\n    print(\n        f\"   Accuracy    : {best[5]:.4f} \"\n        f\"({best[5] * 100:.2f}%)\"\n    )\n\nelse:\n\n    print(\n        \"\\n⚠️ No validation threshold simultaneously \"\n        \"achieved >90% sensitivity and >85% specificity.\"\n    )\n\n    # --------------------------------------------------------\n    # Fallback: specificity >= 85%\n    # --------------------------------------------------------\n\n    valid_spec = results[\n        results[:, 2] >= 0.85\n    ]\n\n    if len(valid_spec) > 0:\n\n        best_index = np.argmax(\n            valid_spec[:, 1]\n        )\n\n        best = valid_spec[best_index]\n\n        best_threshold = best[0]\n\n        print(\n            \"\\nBest available threshold \"\n            \"with specificity >=85%:\"\n        )\n\n        print(\n            f\"   Threshold   : {best_threshold:.2f}\"\n        )\n        print(\n            f\"   Sensitivity : {best[1]:.4f} \"\n            f\"({best[1] * 100:.2f}%)\"\n        )\n        print(\n            f\"   Specificity : {best[2]:.4f} \"\n            f\"({best[2] * 100:.2f}%)\"\n        )\n        print(\n            f\"   Precision   : {best[3]:.4f}\"\n        )\n        print(\n            f\"   F1 Score    : {best[4]:.4f}\"\n        )\n\n    else:\n\n        # ----------------------------------------------------\n        # Final fallback: Youden's J\n        # ----------------------------------------------------\n\n        print(\n            \"\\n⚠️ No threshold maintained \"\n            \"specificity >=85%.\"\n        )\n\n        best_index = np.argmax(\n            results[:, 1]\n            + results[:, 2]\n            - 1\n        )\n\n        best = results[best_index]\n\n        best_threshold = best[0]\n\n        print(\"\\nFallback threshold using Youden's J:\")\n\n        print(\n            f\"   Threshold   : {best_threshold:.2f}\"\n        )\n        print(\n            f\"   Sensitivity : {best[1]:.4f}\"\n        )\n        print(\n            f\"   Specificity : {best[2]:.4f}\"\n        )\n\n\n# ------------------------------------------------------------\n# 7. Display top threshold candidates\n# ------------------------------------------------------------\n\nprint(\"\\n\" + \"=\" * 80)\nprint(\"TOP VALIDATION THRESHOLD CANDIDATES\")\nprint(\"=\" * 80)\n\nprint(\n    f\"{'Threshold':>10}\"\n    f\"{'Sensitivity':>14}\"\n    f\"{'Specificity':>14}\"\n    f\"{'Precision':>12}\"\n    f\"{'F1':>10}\"\n)\n\nprint(\"-\" * 70)\n\nsorted_results = results[\n    np.argsort(results[:, 4])[::-1]\n]\n\nfor row in sorted_results[:10]:\n\n    print(\n        f\"{row[0]:>10.2f}\"\n        f\"{row[1]:>14.4f}\"\n        f\"{row[2]:>14.4f}\"\n        f\"{row[3]:>12.4f}\"\n        f\"{row[4]:>10.4f}\"\n    )\n\n\n# ------------------------------------------------------------\n# 8. Save selected threshold\n# ------------------------------------------------------------\n\nthreshold_path = (\n    \"/kaggle/working/netrx_referable_threshold.npz\"\n)\n\nnp.savez(\n    threshold_path,\n    threshold=best_threshold,\n    validation_results=results\n)\n\n\n# ------------------------------------------------------------\n# 9. Final confirmation\n# ------------------------------------------------------------\n\nprint(\"\\n\" + \"=\" * 80)\nprint(\"✅ VALIDATION THRESHOLD OPTIMIZATION COMPLETE\")\nprint(\"=\" * 80)\n\nprint(\n    f\"Selected threshold : {best_threshold:.2f}\"\n)\n\nprint(\n    f\"Saved to           : {threshold_path}\"\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T14:49:18.064178Z","iopub.execute_input":"2026-09-07T14:49:18.06474Z","iopub.status.idle":"2026-09-07T14:49:18.255279Z","shell.execute_reply.started":"2026-09-07T14:49:18.064709Z","shell.execute_reply":"2026-09-07T14:49:18.254627Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# NetrX — Validation-Based Referable DR Threshold Optimization\n\nThe default five-class argmax decision is not necessarily optimal for screening.\n\nFor referable diabetic retinopathy:\n\n- Grade 0–1 → Non-Referable\n- Grade 2–4 → Referable\n\nThe ensemble probability of referable DR is calculated as:\n\nP(Referable) = P(Grade 2) + P(Grade 3) + P(Grade 4)\n\nA decision threshold is optimized using the validation set to increase sensitivity while preserving clinically useful specificity.\n\nThe selected threshold is then locked and evaluated on the independent test set.","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T13:44:24.981658Z","iopub.status.idle":"2026-09-07T13:44:24.982052Z","shell.execute_reply.started":"2026-09-07T13:44:24.981855Z","shell.execute_reply":"2026-09-07T13:44:24.98188Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 89 — VALIDATION-BASED REFERABLE DR THRESHOLD SEARCH\n# ============================================================\n\nimport numpy as np\nfrom sklearn.metrics import (\n    confusion_matrix,\n    accuracy_score,\n    precision_score,\n    recall_score,\n    f1_score\n)\n\nprint(\"=\" * 95)\nprint(\"       NETRX — VALIDATION REFERABLE DR THRESHOLD OPTIMIZATION\")\nprint(\"=\" * 95)\n\n\n# ------------------------------------------------------------\n# 1. Model names and validation probabilities\n# ------------------------------------------------------------\n\nmodel_names = [\n    \"CNN\",\n    \"ResNet50\",\n    \"EfficientNet-B0\",\n    \"DenseNet121\",\n    \"ConvNeXt-Tiny\",\n    \"ViT-B/16\"\n]\n\nweights = np.array([\n    0.0333,\n    0.0538,\n    0.1411,\n    0.1196,\n    0.3172,\n    0.3349\n])\n\n# ------------------------------------------------------------\n# 2. Stack validation probabilities\n# ------------------------------------------------------------\n\nval_probability_stack = np.stack(\n    [\n        val_probabilities[name]\n        for name in model_names\n    ],\n    axis=0\n)\n\nprint(\"\\nValidation probability stack:\")\nprint(\"Shape:\", val_probability_stack.shape)\n\n\n# ------------------------------------------------------------\n# 3. Weighted ensemble probabilities\n# ------------------------------------------------------------\n\nval_ensemble_probabilities = np.sum(\n    val_probability_stack *\n    weights[:, None, None],\n    axis=0\n)\n\nprint(\n    \"Ensemble probability shape:\",\n    val_ensemble_probabilities.shape\n)\n\nprint(\n    \"Probability sum check:\",\n    val_ensemble_probabilities[0].sum()\n)\n\n\n# ------------------------------------------------------------\n# 4. Referable DR probability\n# ------------------------------------------------------------\n# Grade 0,1 = Non-Referable\n# Grade 2,3,4 = Referable\n\nval_referable_probability = (\n    val_ensemble_probabilities[:, 2]\n    + val_ensemble_probabilities[:, 3]\n    + val_ensemble_probabilities[:, 4]\n)\n\nval_true_binary = (\n    val_labels >= 2\n).astype(int)\n\n\n# ------------------------------------------------------------\n# 5. Search thresholds\n# ------------------------------------------------------------\n\nthresholds = np.arange(\n    0.20,\n    0.81,\n    0.01\n)\n\nresults = []\n\nfor threshold in thresholds:\n\n    predictions = (\n        val_referable_probability >= threshold\n    ).astype(int)\n\n    tn, fp, fn, tp = confusion_matrix(\n        val_true_binary,\n        predictions,\n        labels=[0, 1]\n    ).ravel()\n\n    sensitivity = (\n        tp / (tp + fn)\n        if (tp + fn) > 0\n        else 0\n    )\n\n    specificity = (\n        tn / (tn + fp)\n        if (tn + fp) > 0\n        else 0\n    )\n\n    precision = (\n        tp / (tp + fp)\n        if (tp + fp) > 0\n        else 0\n    )\n\n    f1 = f1_score(\n        val_true_binary,\n        predictions,\n        zero_division=0\n    )\n\n    accuracy = accuracy_score(\n        val_true_binary,\n        predictions\n    )\n\n    results.append([\n        threshold,\n        sensitivity,\n        specificity,\n        precision,\n        f1,\n        accuracy\n    ])\n\n\nresults = np.array(results)\n\n\n# ------------------------------------------------------------\n# 6. Find best threshold satisfying SIH targets\n# ------------------------------------------------------------\n\n# Requirement:\n# Sensitivity > 90%\n# Specificity > 85%\n\nvalid = results[\n    (results[:, 1] > 0.90) &\n    (results[:, 2] > 0.85)\n]\n\n\nprint(\"\\n\" + \"=\" * 95)\nprint(\"                  VALIDATION THRESHOLD RESULTS\")\nprint(\"=\" * 95)\n\nif len(valid) > 0:\n\n    # Choose threshold with highest F1 among\n    # thresholds satisfying both targets.\n\n    best_index = np.argmax(valid[:, 4])\n\n    best = valid[best_index]\n\n    best_threshold = best[0]\n\n    print(\"\\n✅ Thresholds satisfying both SIH targets:\")\n    print(f\"   Number found: {len(valid)}\")\n\n    print(\"\\n🏆 SELECTED THRESHOLD\")\n    print(f\"   Threshold   : {best_threshold:.2f}\")\n    print(f\"   Sensitivity : {best[1]:.4f} ({best[1]*100:.2f}%)\")\n    print(f\"   Specificity : {best[2]:.4f} ({best[2]*100:.2f}%)\")\n    print(f\"   Precision   : {best[3]:.4f} ({best[3]*100:.2f}%)\")\n    print(f\"   F1 Score    : {best[4]:.4f}\")\n    print(f\"   Accuracy    : {best[5]:.4f} ({best[5]*100:.2f}%)\")\n\nelse:\n\n    print(\n        \"\\n⚠️ No validation threshold simultaneously \"\n        \"achieved >90% sensitivity and >85% specificity.\"\n    )\n\n    # If no threshold satisfies both, choose the\n    # threshold with maximum sensitivity while\n    # maintaining specificity >= 85%.\n\n    valid_spec = results[\n        results[:, 2] >= 0.85\n    ]\n\n    if len(valid_spec) > 0:\n\n        best_index = np.argmax(valid_spec[:, 1])\n\n        best = valid_spec[best_index]\n\n        best_threshold = best[0]\n\n        print(\"\\nBest available threshold with specificity >=85%:\")\n        print(f\"   Threshold   : {best_threshold:.2f}\")\n        print(f\"   Sensitivity : {best[1]:.4f} ({best[1]*100:.2f}%)\")\n        print(f\"   Specificity : {best[2]:.4f} ({best[2]*100:.2f}%)\")\n        print(f\"   Precision   : {best[3]:.4f} ({best[3]*100:.2f}%)\")\n        print(f\"   F1 Score    : {best[4]:.4f}\")\n\n    else:\n\n        print(\n            \"\\n⚠️ No threshold maintained \"\n            \"specificity >=85%.\"\n        )\n\n        # Fallback: maximize Youden's J\n        best_index = np.argmax(\n            results[:, 1] + results[:, 2] - 1\n        )\n\n        best = results[best_index]\n\n        best_threshold = best[0]\n\n        print(\"\\nFallback threshold using Youden's J:\")\n        print(f\"   Threshold   : {best_threshold:.2f}\")\n        print(f\"   Sensitivity : {best[1]:.4f}\")\n        print(f\"   Specificity : {best[2]:.4f}\")\n\n\n# ------------------------------------------------------------\n# 7. Display top threshold candidates\n# ------------------------------------------------------------\n\nprint(\"\\n\" + \"=\" * 95)\nprint(\"             TOP VALIDATION THRESHOLD CANDIDATES\")\nprint(\"=\" * 95)\n\nprint(\n    f\"{'Threshold':>10} \"\n    f\"{'Sensitivity':>13} \"\n    f\"{'Specificity':>13} \"\n    f\"{'Precision':>11} \"\n    f\"{'F1':>9}\"\n)\n\nprint(\"-\" * 70)\n\n# Sort by F1 descending\n\nsorted_results = results[\n    np.argsort(results[:, 4])[::-1]\n]\n\nfor row in sorted_results[:10]:\n\n    print(\n        f\"{row[0]:>10.2f} \"\n        f\"{row[1]:>12.4f} \"\n        f\"{row[2]:>12.4f} \"\n        f\"{row[3]:>10.4f} \"\n        f\"{row[4]:>8.4f}\"\n    )\n\n\n# ------------------------------------------------------------\n# 8. Save threshold information\n# ------------------------------------------------------------\n\nnp.savez(\n    \"/kaggle/working/netrx_referable_threshold.npz\",\n    threshold=best_threshold,\n    validation_results=results\n)\n\nprint(\"\\n\" + \"=\" * 95)\nprint(\"✅ VALIDATION THRESHOLD OPTIMIZATION COMPLETE\")\nprint(\"=\" * 95)\n\nprint(\n    f\"Selected threshold saved: \"\n    f\"{best_threshold:.2f}\"\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T14:49:31.283678Z","iopub.execute_input":"2026-09-07T14:49:31.284534Z","iopub.status.idle":"2026-09-07T14:49:31.476926Z","shell.execute_reply.started":"2026-09-07T14:49:31.2845Z","shell.execute_reply":"2026-09-07T14:49:31.47631Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# # NetrX — Final Referable DR Test Evaluation\n\n# The referable DR threshold was optimized exclusively on the validation set.\n\n# Selected threshold:\n\n# **P(Referable DR) ≥ 0.45 → Referable DR**\n\n# The threshold is now frozen and applied to the independent held-out test set.\n\n# This evaluation measures the final screening performance of NetrX for:\n\n# - Non-Referable DR: Grade 0–1\n# - Referable DR: Grade 2–4\n\n# Primary metrics:\n\n# - Sensitivity\n# - Specificity\n# - Precision (PPV)\n# - F1 Score\n# - Binary Accuracy","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T13:44:24.98516Z","iopub.status.idle":"2026-09-07T13:44:24.985488Z","shell.execute_reply.started":"2026-09-07T13:44:24.985313Z","shell.execute_reply":"2026-09-07T13:44:24.985328Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 91 — FINAL REFERABLE DR TEST EVALUATION\n# ============================================================\n\nimport numpy as np\nfrom sklearn.metrics import (\n    confusion_matrix,\n    accuracy_score,\n    precision_score,\n    recall_score,\n    f1_score,\n    roc_auc_score\n)\n\nprint(\"=\" * 95)\nprint(\"          NETRX — FINAL REFERABLE DR TEST EVALUATION\")\nprint(\"=\" * 95)\n\n\n# ------------------------------------------------------------\n# 1. Load frozen validation-selected threshold\n# ------------------------------------------------------------\n\nthreshold_file = \"/kaggle/working/netrx_referable_threshold.npz\"\n\nthreshold_data = np.load(\n    threshold_file,\n    allow_pickle=True\n)\n\nfinal_threshold = float(\n    threshold_data[\"threshold\"]\n)\n\nprint(\"\\nFrozen threshold:\")\nprint(f\"   P(Referable DR) >= {final_threshold:.2f}\")\n\n\n# ------------------------------------------------------------\n# 2. Calculate referable probability on TEST set\n# ------------------------------------------------------------\n\ntest_referable_probability = (\n    final_test_probabilities[:, 2]\n    + final_test_probabilities[:, 3]\n    + final_test_probabilities[:, 4]\n)\n\nprint(\"\\nTest referable probability:\")\nprint(\n    f\"   Shape: {test_referable_probability.shape}\"\n)\n\n\n# ------------------------------------------------------------\n# 3. Ground truth\n# ------------------------------------------------------------\n\ntest_true_binary = (\n    test_labels >= 2\n).astype(int)\n\n\n# ------------------------------------------------------------\n# 4. Apply frozen threshold\n# ------------------------------------------------------------\n\ntest_binary_predictions = (\n    test_referable_probability >= final_threshold\n).astype(int)\n\n\n# ------------------------------------------------------------\n# 5. Confusion matrix\n# ------------------------------------------------------------\n\ncm = confusion_matrix(\n    test_true_binary,\n    test_binary_predictions,\n    labels=[0, 1]\n)\n\ntn, fp, fn, tp = cm.ravel()\n\n\n# ------------------------------------------------------------\n# 6. Clinical screening metrics\n# ------------------------------------------------------------\n\nsensitivity = (\n    tp / (tp + fn)\n    if (tp + fn) > 0\n    else 0\n)\n\nspecificity = (\n    tn / (tn + fp)\n    if (tn + fp) > 0\n    else 0\n)\n\nprecision = (\n    tp / (tp + fp)\n    if (tp + fp) > 0\n    else 0\n)\n\nf1 = f1_score(\n    test_true_binary,\n    test_binary_predictions,\n    zero_division=0\n)\n\nbinary_accuracy = accuracy_score(\n    test_true_binary,\n    test_binary_predictions\n)\n\nauc = roc_auc_score(\n    test_true_binary,\n    test_referable_probability\n)\n\n\n# ------------------------------------------------------------\n# 7. RESULTS\n# ------------------------------------------------------------\n\nprint(\"\\n\" + \"=\" * 95)\nprint(\"                  FINAL TEST RESULTS\")\nprint(\"=\" * 95)\n\nprint(f\"Threshold           : {final_threshold:.2f}\")\n\nprint(\n    f\"Sensitivity / Recall: \"\n    f\"{sensitivity:.4f} ({sensitivity*100:.2f}%)\"\n)\n\nprint(\n    f\"Specificity         : \"\n    f\"{specificity:.4f} ({specificity*100:.2f}%)\"\n)\n\nprint(\n    f\"Precision / PPV     : \"\n    f\"{precision:.4f} ({precision*100:.2f}%)\"\n)\n\nprint(\n    f\"F1 Score            : \"\n    f\"{f1:.4f}\"\n)\n\nprint(\n    f\"Binary Accuracy     : \"\n    f\"{binary_accuracy:.4f} ({binary_accuracy*100:.2f}%)\"\n)\n\nprint(\n    f\"ROC-AUC             : \"\n    f\"{auc:.4f}\"\n)\n\n\n# ------------------------------------------------------------\n# 8. Confusion matrix\n# ------------------------------------------------------------\n\nprint(\"\\n\" + \"=\" * 95)\nprint(\"                    CONFUSION MATRIX\")\nprint(\"=\" * 95)\n\nprint(\"                  Predicted\")\nprint(\"                Non-Ref  Referable\")\n\nprint(\n    f\"Actual Non-Ref     {tn:4d}      {fp:4d}\"\n)\n\nprint(\n    f\"Actual Referable   {fn:4d}      {tp:4d}\"\n)\n\n\n# ------------------------------------------------------------\n# 9. SIH TARGET CHECK\n# ------------------------------------------------------------\n\nprint(\"\\n\" + \"=\" * 95)\nprint(\"                    SIH TARGET CHECK\")\nprint(\"=\" * 95)\n\nprint(\n    f\"Sensitivity >90% : \"\n    f\"{'✅ ACHIEVED' if sensitivity > 0.90 else '❌ NOT ACHIEVED'}\"\n)\n\nprint(\n    f\"Specificity >85% : \"\n    f\"{'✅ ACHIEVED' if specificity > 0.85 else '❌ NOT ACHIEVED'}\"\n)\n\n\n# ------------------------------------------------------------\n# 10. Save final screening results\n# ------------------------------------------------------------\n\nnp.savez(\n    \"/kaggle/working/netrx_final_referable_screening.npz\",\n\n    threshold=final_threshold,\n\n    probabilities=test_referable_probability,\n\n    predictions=test_binary_predictions,\n\n    labels=test_true_binary,\n\n    confusion_matrix=cm,\n\n    sensitivity=sensitivity,\n\n    specificity=specificity,\n\n    precision=precision,\n\n    f1=f1,\n\n    accuracy=binary_accuracy,\n\n    roc_auc=auc\n)\n\n\nprint(\"\\n\" + \"=\" * 95)\nprint(\"✅ FINAL REFERABLE DR EVALUATION COMPLETE\")\nprint(\"=\" * 95)\n\nprint(\n    \"Saved: \"\n    \"/kaggle/working/netrx_final_referable_screening.npz\"\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T14:49:41.212712Z","iopub.execute_input":"2026-09-07T14:49:41.213536Z","iopub.status.idle":"2026-09-07T14:49:41.240089Z","shell.execute_reply.started":"2026-09-07T14:49:41.213503Z","shell.execute_reply":"2026-09-07T14:49:41.23941Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# # NetrX — Final Clinical Screening Performance\n\n# The final NetrX ensemble combines six deep-learning models:\n\n# - CNN\n# - ResNet50\n# - EfficientNet-B0\n# - DenseNet121\n# - ConvNeXt-Tiny\n# - ViT-B/16\n\n# A validation-based weighted probability ensemble was developed without using the held-out test set for weight optimization.\n\n# For referable diabetic retinopathy screening:\n\n# - Grade 0–1 → Non-Referable DR\n# - Grade 2–4 → Referable DR\n\n# The referable-DR threshold was optimized exclusively on the validation set and fixed at:\n\n# **P(Referable DR) ≥ 0.45**\n\n# Final evaluation on the independent 367-image test set achieved:\n\n# - **Sensitivity: 93.29%**\n# - **Specificity: 90.37%**\n# - **Precision (PPV): 86.88%**\n# - **F1 Score: 89.97%**\n# - **Binary Accuracy: 91.55%**\n# - **ROC-AUC: 0.9765**\n\n# Both SIH screening targets were achieved on this held-out test set:\n\n# - Sensitivity >90% — **Achieved**\n# - Specificity >85% — **Achieved**\n\n# These results represent the performance of the current prototype on the selected test dataset and should not be interpreted as clinical validation or deployment-ready performance without external and prospective validation.","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T13:44:24.988578Z","iopub.status.idle":"2026-09-07T13:44:24.988947Z","shell.execute_reply.started":"2026-09-07T13:44:24.988761Z","shell.execute_reply":"2026-09-07T13:44:24.988784Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"TESTING NOW","metadata":{}},{"cell_type":"code","source":"# ============================================\n# NetrX — Select One Held-Out Test Image\n# ============================================\n\nimport numpy as np\nimport matplotlib.pyplot as plt\n\n# Pick first test sample\nsample_idx = 0\n\nsample_image_path = test_df.iloc[sample_idx][\"image_path\"]\nsample_true_label = int(test_labels[sample_idx])\n\nprint(\"Selected Test Image\")\nprint(\"-\" * 40)\nprint(\"Index        :\", sample_idx)\nprint(\"Image Path   :\", sample_image_path)\nprint(\"Ground Truth :\", sample_true_label)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T14:49:54.202972Z","iopub.execute_input":"2026-09-07T14:49:54.203243Z","iopub.status.idle":"2026-09-07T14:49:54.209686Z","shell.execute_reply.started":"2026-09-07T14:49:54.203222Z","shell.execute_reply":"2026-09-07T14:49:54.208768Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from PIL import Image\nimport matplotlib.pyplot as plt\n\nimg = Image.open(sample_image_path).convert(\"RGB\")\n\nplt.figure(figsize=(7, 7))\nplt.imshow(img)\nplt.axis(\"off\")\nplt.title(f\"Ground Truth: Grade {sample_true_label}\")\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T14:49:57.132939Z","iopub.execute_input":"2026-09-07T14:49:57.133203Z","iopub.status.idle":"2026-09-07T14:49:57.506688Z","shell.execute_reply.started":"2026-09-07T14:49:57.133181Z","shell.execute_reply":"2026-09-07T14:49:57.505833Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================\n# NetrX — Inference Preprocessing Pipeline\n# Same preprocessing before model inference\n# ============================================\n\nimport numpy as np\nimport cv2\nfrom PIL import Image\nfrom scipy.ndimage import median_filter\nfrom skimage import exposure\nimport matplotlib.pyplot as plt\n\nIMG_SIZE = 224\n\n# ImageNet normalization used by pretrained models\nIMAGENET_MEAN = np.array([0.485, 0.456, 0.406], dtype=np.float32)\nIMAGENET_STD  = np.array([0.229, 0.224, 0.225], dtype=np.float32)\n\n\ndef netrx_preprocess(image_path, show_steps=False):\n    \"\"\"\n    NetrX inference preprocessing.\n\n    Original image\n        ↓\n    Noise reduction\n        ↓\n    HSV-based controlled contrast enhancement\n        ↓\n    Resize to 224x224\n        ↓\n    Normalization\n        ↓\n    Tensor ready for model\n    \"\"\"\n\n    # -----------------------------\n    # 1. Load original image\n    # -----------------------------\n    original = cv2.imread(image_path)\n\n    if original is None:\n        raise ValueError(f\"Could not read image: {image_path}\")\n\n    original = cv2.cvtColor(original, cv2.COLOR_BGR2RGB)\n\n    # -----------------------------\n    # 2. Noise reduction\n    # -----------------------------\n    denoised = median_filter(\n        original,\n        size=(3, 3, 1)\n    ).astype(np.uint8)\n\n    # -----------------------------\n    # 3. HSV controlled enhancement\n    # -----------------------------\n    hsv = cv2.cvtColor(denoised, cv2.COLOR_RGB2HSV)\n\n    h, s, v = cv2.split(hsv)\n\n    # CLAHE on illumination/value channel\n    clahe = cv2.createCLAHE(\n        clipLimit=2.0,\n        tileGridSize=(8, 8)\n    )\n\n    v_enhanced = clahe.apply(v)\n\n    hsv_enhanced = cv2.merge([\n        h,\n        s,\n        v_enhanced\n    ])\n\n    enhanced = cv2.cvtColor(\n        hsv_enhanced,\n        cv2.COLOR_HSV2RGB\n    )\n\n    # -----------------------------\n    # 4. Resize\n    # -----------------------------\n    resized = cv2.resize(\n        enhanced,\n        (IMG_SIZE, IMG_SIZE),\n        interpolation=cv2.INTER_AREA\n    )\n\n    # -----------------------------\n    # 5. Convert to float\n    # -----------------------------\n    normalized = resized.astype(np.float32) / 255.0\n\n    # -----------------------------\n    # 6. ImageNet normalization\n    # -----------------------------\n    normalized = (\n        normalized - IMAGENET_MEAN\n    ) / IMAGENET_STD\n\n    # -----------------------------\n    # 7. HWC → CHW\n    # -----------------------------\n    tensor = torch.from_numpy(\n        normalized.transpose(2, 0, 1)\n    ).float()\n\n    # Add batch dimension\n    tensor = tensor.unsqueeze(0)\n\n    # -----------------------------\n    # Optional visualization\n    # -----------------------------\n    if show_steps:\n\n        plt.figure(figsize=(14, 5))\n\n        plt.subplot(1, 2, 1)\n        plt.imshow(original)\n        plt.title(\"Original Test Image\")\n        plt.axis(\"off\")\n\n        plt.subplot(1, 2, 2)\n        plt.imshow(resized)\n        plt.title(\"NetrX Preprocessed Image\")\n        plt.axis(\"off\")\n\n        plt.tight_layout()\n        plt.show()\n\n    return original, resized, tensor","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T14:50:35.203312Z","iopub.execute_input":"2026-09-07T14:50:35.204303Z","iopub.status.idle":"2026-09-07T14:50:35.214978Z","shell.execute_reply.started":"2026-09-07T14:50:35.20427Z","shell.execute_reply":"2026-09-07T14:50:35.214058Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================\n# Test NetrX Preprocessing\n# ============================================\n\nsample_idx = 0\n\nsample_image_path = test_df.iloc[sample_idx][\"image_path\"]\nsample_true_label = int(test_labels[sample_idx])\n\noriginal_img, processed_img, input_tensor = netrx_preprocess(\n    sample_image_path,\n    show_steps=True\n)\n\nprint(\"NetrX Preprocessing Complete\")\nprint(\"-\" * 45)\nprint(\"Test Image       :\", sample_image_path)\nprint(\"Ground Truth     :\", sample_true_label)\nprint(\"Original Shape   :\", original_img.shape)\nprint(\"Processed Shape  :\", processed_img.shape)\nprint(\"Model Input      :\", input_tensor.shape)\nprint(\"Model Input Type :\", input_tensor.dtype)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T14:50:40.12779Z","iopub.execute_input":"2026-09-07T14:50:40.128065Z","iopub.status.idle":"2026-09-07T14:50:40.937889Z","shell.execute_reply.started":"2026-09-07T14:50:40.128041Z","shell.execute_reply":"2026-09-07T14:50:40.936998Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================\n# Prepare NetrX Input\n# ============================================\n\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n\ninput_tensor = input_tensor.to(device)\n\nprint(\"Device:\", device)\nprint(\"Input Tensor:\", input_tensor.shape)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T14:50:49.48788Z","iopub.execute_input":"2026-09-07T14:50:49.488542Z","iopub.status.idle":"2026-09-07T14:50:49.494189Z","shell.execute_reply.started":"2026-09-07T14:50:49.488512Z","shell.execute_reply":"2026-09-07T14:50:49.493502Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================\n# NetrX — Six Model Ensemble Prediction\n# ============================================\n\nensemble_models = {\n    \"CNN\": cnn_ensemble,\n    \"ResNet50\": resnet_ensemble,\n    \"EfficientNet-B0\": efficientnet_ensemble,\n    \"DenseNet121\": densenet_ensemble,\n    \"ConvNeXt-Tiny\": convnext_ensemble,\n    \"ViT-B/16\": vit_ensemble\n}\n\n# Validation-optimized weights\nensemble_weights = np.array([\n    0.0333,   # CNN\n    0.0538,   # ResNet50\n    0.1411,   # EfficientNet-B0\n    0.1196,   # DenseNet121\n    0.3172,   # ConvNeXt-Tiny\n    0.3349    # ViT-B/16\n], dtype=np.float32)\n\nensemble_weights = ensemble_weights / ensemble_weights.sum()\n\nmodel_probabilities = {}\n\nwith torch.no_grad():\n\n    for name, model in ensemble_models.items():\n\n        model.eval()\n\n        logits = model(input_tensor)\n\n        probabilities = torch.softmax(\n            logits,\n            dim=1\n        )\n\n        model_probabilities[name] = (\n            probabilities.cpu().numpy()[0]\n        )\n\n# Weighted probability ensemble\nfinal_probabilities = np.zeros(5, dtype=np.float32)\n\nfor i, name in enumerate(ensemble_models.keys()):\n\n    final_probabilities += (\n        ensemble_weights[i]\n        * model_probabilities[name]\n    )\n\npredicted_grade = int(\n    np.argmax(final_probabilities)\n)\n\nreferable_probability = float(\n    final_probabilities[2]\n    + final_probabilities[3]\n    + final_probabilities[4]\n)\n\nreferable_threshold = 0.45\n\nreferable = (\n    referable_probability >= referable_threshold\n)\n\nprint(\"=\" * 55)\nprint(\"             NETRX ENSEMBLE PREDICTION\")\nprint(\"=\" * 55)\n\nprint(f\"Predicted Grade       : {predicted_grade}\")\nprint(f\"P(Referable DR)       : {referable_probability:.4f}\")\nprint(f\"Referable Threshold   : {referable_threshold:.2f}\")\nprint(\n    f\"Screening Decision    : \"\n    f\"{'REFER' if referable else 'NON-REFERABLE'}\"\n)\n\nprint(\"\\nGrade Probabilities\")\nprint(\"-\" * 30)\n\nfor grade, prob in enumerate(final_probabilities):\n    print(f\"Grade {grade}              : {prob:.4f}\")\n\nprint(\"=\" * 55)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T14:50:59.818797Z","iopub.execute_input":"2026-09-07T14:50:59.819897Z","iopub.status.idle":"2026-09-07T14:50:59.908903Z","shell.execute_reply.started":"2026-09-07T14:50:59.819863Z","shell.execute_reply":"2026-09-07T14:50:59.90796Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================\n# NetrX Prediction Summary\n# ============================================\n\ngrade_names = {\n    0: \"No DR\",\n    1: \"Mild NPDR\",\n    2: \"Moderate NPDR\",\n    3: \"Severe NPDR\",\n    4: \"Proliferative DR\"\n}\n\nprint(\"\\n\" + \"=\" * 60)\nprint(\"                 NETRX SCREENING RESULT\")\nprint(\"=\" * 60)\n\nprint(f\"Ground Truth          : Grade {sample_true_label}\")\nprint(\n    f\"Predicted Grade       : \"\n    f\"Grade {predicted_grade} — \"\n    f\"{grade_names[predicted_grade]}\"\n)\n\nprint(f\"P(Referable DR)       : {referable_probability:.2%}\")\nprint(f\"Decision Threshold    : {referable_threshold:.2f}\")\n\nif referable:\n    print(\"Screening Decision    : REFER TO OPHTHALMOLOGIST\")\nelse:\n    print(\"Screening Decision    : NON-REFERABLE\")\n\nprint(\"\\nGrade Probability Distribution\")\nprint(\"-\" * 40)\n\nfor grade in range(5):\n    print(\n        f\"Grade {grade} \"\n        f\"({grade_names[grade]:18s}) : \"\n        f\"{final_probabilities[grade]:.2%}\"\n    )\n\nprint(\"=\" * 60)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T14:51:05.122625Z","iopub.execute_input":"2026-09-07T14:51:05.123578Z","iopub.status.idle":"2026-09-07T14:51:05.130953Z","shell.execute_reply.started":"2026-09-07T14:51:05.123514Z","shell.execute_reply":"2026-09-07T14:51:05.130137Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================\n# NetrX — Ground Truth vs Prediction Verification\n# ============================================\n\nsample_idx = 0\n\n# Actual label stored in the held-out test dataframe\nactual_label = int(test_df.iloc[sample_idx][\"diagnosis\"])\n\n# Model prediction already calculated\nmodel_prediction = int(predicted_grade)\n\nprint(\"=\" * 60)\nprint(\"       NETRX INDIVIDUAL PREDICTION VERIFICATION\")\nprint(\"=\" * 60)\n\nprint(f\"Test Image Index       : {sample_idx}\")\nprint(f\"Image ID               : {test_df.iloc[sample_idx]['id_code']}\")\nprint(f\"Ground Truth Label     : Grade {actual_label}\")\nprint(f\"NetrX Predicted Label  : Grade {model_prediction}\")\n\nprint(\"-\" * 60)\n\nif actual_label == model_prediction:\n    print(\"RESULT                 : ✅ CORRECT PREDICTION\")\nelse:\n    print(\"RESULT                 : ❌ INCORRECT PREDICTION\")\n\nprint(\"=\" * 60)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T14:51:09.037792Z","iopub.execute_input":"2026-09-07T14:51:09.038711Z","iopub.status.idle":"2026-09-07T14:51:09.045413Z","shell.execute_reply.started":"2026-09-07T14:51:09.038679Z","shell.execute_reply":"2026-09-07T14:51:09.044597Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================\n# NetrX — Verify 3 More Held-Out Test Images\n# ============================================\n\n# Select three different grades for testing\n# We already verified Grade 0 at index 0.\ntarget_grades = [1, 2, 3]\n\nprint(\"=\" * 70)\nprint(\"       NETRX — THREE MORE TEST IMAGE VERIFICATIONS\")\nprint(\"=\" * 70)\n\nverification_results = []\n\nfor target_grade in target_grades:\n\n    # Find first test image having this ground-truth grade\n    matching_indices = np.where(\n        test_labels == target_grade\n    )[0]\n\n    if len(matching_indices) == 0:\n        print(f\"\\nNo test image found for Grade {target_grade}\")\n        continue\n\n    idx = int(matching_indices[0])\n\n    image_path = test_df.iloc[idx][\"image_path\"]\n    actual_label = int(test_df.iloc[idx][\"diagnosis\"])\n\n    # ----------------------------------------\n    # Same preprocessing used during training\n    # ----------------------------------------\n    _, _, input_tensor_single = netrx_preprocess(\n        image_path,\n        show_steps=False\n    )\n\n    input_tensor_single = input_tensor_single.to(device)\n\n    # ----------------------------------------\n    # Six-model ensemble prediction\n    # ----------------------------------------\n    probabilities = np.zeros(5, dtype=np.float32)\n\n    with torch.no_grad():\n\n        for i, (name, model) in enumerate(\n            ensemble_models.items()\n        ):\n\n            model.eval()\n\n            logits = model(input_tensor_single)\n\n            probs = torch.softmax(\n                logits,\n                dim=1\n            ).cpu().numpy()[0]\n\n            probabilities += (\n                ensemble_weights[i] * probs\n            )\n\n    predicted_label = int(\n        np.argmax(probabilities)\n    )\n\n    referable_probability = float(\n        probabilities[2]\n        + probabilities[3]\n        + probabilities[4]\n    )\n\n    screening_decision = (\n        \"REFER\"\n        if referable_probability >= 0.45\n        else \"NON-REFERABLE\"\n    )\n\n    correct = (\n        actual_label == predicted_label\n    )\n\n    verification_results.append({\n        \"index\": idx,\n        \"image_id\": test_df.iloc[idx][\"id_code\"],\n        \"ground_truth\": actual_label,\n        \"prediction\": predicted_label,\n        \"referable_probability\": referable_probability,\n        \"decision\": screening_decision,\n        \"correct\": correct\n    })\n\n    # ----------------------------------------\n    # Display result\n    # ----------------------------------------\n    print(f\"\\nTEST IMAGE — GROUND TRUTH GRADE {target_grade}\")\n    print(\"-\" * 70)\n\n    print(f\"Test Index             : {idx}\")\n    print(f\"Image ID               : {test_df.iloc[idx]['id_code']}\")\n    print(f\"Ground Truth           : Grade {actual_label}\")\n    print(f\"NetrX Prediction       : Grade {predicted_label}\")\n    print(f\"P(Referable DR)        : {referable_probability:.2%}\")\n    print(f\"Screening Decision     : {screening_decision}\")\n\n    if correct:\n        print(\"Verification            : ✅ CORRECT\")\n    else:\n        print(\"Verification            : ❌ INCORRECT\")\n\nprint(\"\\n\" + \"=\" * 70)\nprint(\"VERIFICATION COMPLETE\")\nprint(\"=\" * 70)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T14:51:12.278476Z","iopub.execute_input":"2026-09-07T14:51:12.279251Z","iopub.status.idle":"2026-09-07T14:51:18.105982Z","shell.execute_reply.started":"2026-09-07T14:51:12.27922Z","shell.execute_reply":"2026-09-07T14:51:18.105165Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"SAVING ALL THESE STUFF","metadata":{}},{"cell_type":"code","source":"# ============================================================\n# NETRX — CREATE COMPLETE MODEL + RESULTS BACKUP\n# ============================================================\n\nimport os\nimport zipfile\nimport json\nfrom datetime import datetime\n\noutput_dir = \"/kaggle/working\"\npackage_dir = \"/kaggle/working/netrx_final_package\"\n\nos.makedirs(package_dir, exist_ok=True)\n\n# ------------------------------------------------------------\n# Files to preserve\n# ------------------------------------------------------------\n\nfiles_to_backup = [\n\n    # Trained model checkpoints\n    \"netrx_cnn_best.pth\",\n    \"netrx_resnet50_best.pth\",\n    \"netrx_efficientnet_b0_best.pth\",\n    \"netrx_densenet121_best.pth\",\n    \"netrx_convnext_tiny_best.pth\",\n    \"netrx_vit_b16_best.pth\",\n\n    # Ensemble / prediction artifacts\n    \"netrx_ensemble_predictions.npz\",\n    \"netrx_soft_voting.npz\",\n    \"netrx_optimized_ensemble_weights.npz\",\n    \"netrx_final_weighted_ensemble.npz\",\n\n    # Screening threshold / final evaluation\n    \"netrx_referable_threshold.npz\",\n    \"netrx_final_referable_screening.npz\",\n\n    # CSV evaluation files\n    \"netrx_model_predictions.csv\",\n    \"netrx_model_agreement_matrix.csv\",\n]\n\n# ------------------------------------------------------------\n# Copy existing files\n# ------------------------------------------------------------\n\ncopied_files = []\nmissing_files = []\n\nfor filename in files_to_backup:\n\n    source = os.path.join(output_dir, filename)\n\n    if os.path.exists(source):\n\n        destination = os.path.join(\n            package_dir,\n            filename\n        )\n\n        import shutil\n        shutil.copy2(source, destination)\n\n        copied_files.append(filename)\n\n    else:\n        missing_files.append(filename)\n\n\n# ------------------------------------------------------------\n# Create model configuration\n# ------------------------------------------------------------\n\nmodel_config = {\n    \"project\": \"NetrX\",\n    \"task\": \"Diabetic Retinopathy Screening\",\n    \"dataset\": \"APTOS 2019\",\n    \"input_size\": \"224x224\",\n    \"num_classes\": 5,\n\n    \"classes\": {\n        \"0\": \"No DR\",\n        \"1\": \"Mild NPDR\",\n        \"2\": \"Moderate NPDR\",\n        \"3\": \"Severe NPDR\",\n        \"4\": \"Proliferative DR\"\n    },\n\n    \"ensemble_weights\": {\n        \"CNN\": 0.0333,\n        \"ResNet50\": 0.0538,\n        \"EfficientNet-B0\": 0.1411,\n        \"DenseNet121\": 0.1196,\n        \"ConvNeXt-Tiny\": 0.3172,\n        \"ViT-B/16\": 0.3349\n    },\n\n    \"referable_definition\": \"Grade 2-4\",\n    \"non_referable_definition\": \"Grade 0-1\",\n    \"referable_threshold\": 0.45,\n\n    \"test_results\": {\n        \"5_class_accuracy\": 0.7493,\n        \"macro_f1\": 0.6097,\n        \"weighted_f1\": 0.7596,\n        \"quadratic_weighted_kappa\": 0.8843,\n        \"referable_sensitivity\": 0.9329,\n        \"referable_specificity\": 0.9037,\n        \"referable_precision\": 0.8688,\n        \"referable_f1\": 0.8997,\n        \"referable_binary_accuracy\": 0.9155,\n        \"referable_roc_auc\": 0.9765\n    },\n\n    \"package_created\": datetime.now().isoformat()\n}\n\nwith open(\n    os.path.join(\n        package_dir,\n        \"netrx_model_config.json\"\n    ),\n    \"w\"\n) as f:\n\n    json.dump(\n        model_config,\n        f,\n        indent=4\n    )\n\n\n# ------------------------------------------------------------\n# Create README\n# ------------------------------------------------------------\n\nreadme = \"\"\"\n============================================================\n                    NETRX MODEL PACKAGE\n============================================================\n\nProject:\nNetrX — AI-Assisted Retinal Screening\n\nDataset:\nAPTOS 2019 Blindness Detection\n\nInput:\n224 x 224 RGB fundus image\n\nDR Classes:\nGrade 0 — No DR\nGrade 1 — Mild NPDR\nGrade 2 — Moderate NPDR\nGrade 3 — Severe NPDR\nGrade 4 — Proliferative DR\n\nReferable DR:\nGrade 2-4\n\nNon-Referable:\nGrade 0-1\n\nFrozen Referable Threshold:\nP(Grade 2 + Grade 3 + Grade 4) >= 0.45\n\nFINAL HELD-OUT TEST RESULTS\n------------------------------------------------------------\n5-Class Accuracy       : 74.93%\nMacro F1               : 0.6097\nWeighted F1            : 0.7596\nQWK                     : 0.8843\n\nReferable Sensitivity  : 93.29%\nReferable Specificity  : 90.37%\nReferable Precision    : 86.88%\nReferable F1           : 89.97%\nBinary Accuracy        : 91.55%\nROC-AUC                 : 0.9765\n\nIMPORTANT:\nThese results are from the held-out APTOS test set.\nThey are prototype dataset results and do not constitute\nprospective clinical validation.\n\nThe .pth files are PyTorch state_dict checkpoints.\nThe exact model architecture definitions used during\ntraining are required to reload them.\n\n============================================================\n\"\"\"\n\nwith open(\n    os.path.join(\n        package_dir,\n        \"README.txt\"\n    ),\n    \"w\"\n) as f:\n    f.write(readme)\n\n\n# ------------------------------------------------------------\n# Create ZIP\n# ------------------------------------------------------------\n\nzip_path = \"/kaggle/working/netrx_final_package.zip\"\n\nwith zipfile.ZipFile(\n    zip_path,\n    \"w\",\n    compression=zipfile.ZIP_DEFLATED\n) as zipf:\n\n    for root, dirs, files in os.walk(package_dir):\n\n        for file in files:\n\n            full_path = os.path.join(root, file)\n\n            arcname = os.path.relpath(\n                full_path,\n                package_dir\n            )\n\n            zipf.write(\n                full_path,\n                arcname\n            )\n\n\n# ------------------------------------------------------------\n# Final report\n# ------------------------------------------------------------\n\nprint(\"=\" * 70)\nprint(\"          NETRX FINAL BACKUP PACKAGE CREATED\")\nprint(\"=\" * 70)\n\nprint(f\"\\nFiles included : {len(copied_files)}\")\nprint(f\"Missing files  : {len(missing_files)}\")\n\nif missing_files:\n\n    print(\"\\nMissing:\")\n    for f in missing_files:\n        print(\" -\", f)\n\nprint(\"\\nZIP:\")\nprint(zip_path)\n\nprint(\"\\nPackage contents:\")\nfor f in copied_files:\n    print(\" ✓\", f)\n\nprint(\" ✓ netrx_model_config.json\")\nprint(\" ✓ README.txt\")\n\nprint(\"\\n\" + \"=\" * 70)\nprint(\"BACKUP READY\")\nprint(\"=\" * 70)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T14:51:55.087993Z","iopub.execute_input":"2026-09-07T14:51:55.088256Z","iopub.status.idle":"2026-09-07T14:52:26.727145Z","shell.execute_reply.started":"2026-09-07T14:51:55.088233Z","shell.execute_reply":"2026-09-07T14:52:26.72631Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\n\nzip_path = \"/kaggle/working/netrx_final_package.zip\"\n\nprint(\"Exists:\", os.path.exists(zip_path))\n\nif os.path.exists(zip_path):\n    print(\"Size:\", os.path.getsize(zip_path) / (1024**2), \"MB\")\n    print(\"READY FOR DOWNLOAD\")\nelse:\n    print(\"ZIP NOT FOUND\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T14:52:36.073238Z","iopub.execute_input":"2026-09-07T14:52:36.074025Z","iopub.status.idle":"2026-09-07T14:52:36.08003Z","shell.execute_reply.started":"2026-09-07T14:52:36.073991Z","shell.execute_reply":"2026-09-07T14:52:36.079035Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"FILE INPUT","metadata":{}},{"cell_type":"code","source":"# ============================================\n# NETRX — EXTERNAL / UNSEEN IMAGE UPLOAD\n# ============================================\n\nfrom IPython.display import display\nfrom ipywidgets import FileUpload\nimport io\nfrom PIL import Image\nimport matplotlib.pyplot as plt\n\nupload = FileUpload(\n    accept='.jpg,.jpeg,.png',\n    multiple=False\n)\n\ndisplay(upload)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T14:52:39.053005Z","iopub.execute_input":"2026-09-07T14:52:39.053263Z","iopub.status.idle":"2026-09-07T14:52:39.061927Z","shell.execute_reply.started":"2026-09-07T14:52:39.053241Z","shell.execute_reply":"2026-09-07T14:52:39.060912Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================\n# Read Uploaded External Image\n# ============================================\n\nif not upload.value:\n    print(\"❌ No image uploaded yet. Click Upload and select an image.\")\nelse:\n\n    # Kaggle's current FileUpload returns a tuple\n    uploaded_file = upload.value[0]\n\n    uploaded_name = uploaded_file[\"name\"]\n    uploaded_bytes = uploaded_file[\"content\"]\n\n    external_image = Image.open(\n        io.BytesIO(uploaded_bytes)\n    ).convert(\"RGB\")\n\n    print(\"✅ External Image Loaded\")\n    print(\"-\" * 40)\n    print(\"File Name :\", uploaded_name)\n    print(\"Image Size:\", external_image.size)\n\n    plt.figure(figsize=(7, 7))\n    plt.imshow(external_image)\n    plt.axis(\"off\")\n    plt.title(\"External Unseen Fundus Image\")\n    plt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T14:52:47.408136Z","iopub.execute_input":"2026-09-07T14:52:47.408654Z","iopub.status.idle":"2026-09-07T14:52:48.346413Z","shell.execute_reply.started":"2026-09-07T14:52:47.408615Z","shell.execute_reply":"2026-09-07T14:52:48.345753Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================\n# Save External Image\n# ============================================\n\nexternal_path = \"/kaggle/working/netrx_external_test.jpg\"\n\nexternal_image.save(\n    external_path,\n    quality=95\n)\n\nprint(\"Saved to:\")\nprint(external_path)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T14:52:54.86838Z","iopub.execute_input":"2026-09-07T14:52:54.868691Z","iopub.status.idle":"2026-09-07T14:52:54.918394Z","shell.execute_reply.started":"2026-09-07T14:52:54.868666Z","shell.execute_reply":"2026-09-07T14:52:54.917443Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================\n# External Image → NetrX Preprocessing\n# ============================================\n\nexternal_original, external_processed, external_tensor = netrx_preprocess(\n    external_path,\n    show_steps=True\n)\n\nexternal_tensor = external_tensor.to(device)\n\nprint(\"External image successfully passed through\")\nprint(\"the same preprocessing pipeline used for training.\")\nprint()\nprint(\"Tensor shape:\", external_tensor.shape)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T14:52:59.508423Z","iopub.execute_input":"2026-09-07T14:52:59.509125Z","iopub.status.idle":"2026-09-07T14:53:03.991729Z","shell.execute_reply.started":"2026-09-07T14:52:59.509091Z","shell.execute_reply":"2026-09-07T14:53:03.9908Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================\n# NETRX — EXTERNAL IMAGE PREDICTION\n# ============================================\n\nexternal_probabilities = np.zeros(\n    5,\n    dtype=np.float32\n)\n\nwith torch.no_grad():\n\n    for i, (name, model) in enumerate(\n        ensemble_models.items()\n    ):\n\n        model.eval()\n\n        logits = model(\n            external_tensor\n        )\n\n        probs = torch.softmax(\n            logits,\n            dim=1\n        ).cpu().numpy()[0]\n\n        external_probabilities += (\n            ensemble_weights[i] * probs\n        )\n\nexternal_predicted_grade = int(\n    np.argmax(external_probabilities)\n)\n\nexternal_referable_probability = float(\n    external_probabilities[2]\n    + external_probabilities[3]\n    + external_probabilities[4]\n)\n\nexternal_decision = (\n    \"REFER\"\n    if external_referable_probability >= 0.45\n    else \"NON-REFERABLE\"\n)\n\nprint(\"=\" * 60)\nprint(\"          NETRX — EXTERNAL IMAGE RESULT\")\nprint(\"=\" * 60)\n\nprint(\n    f\"Predicted Grade       : \"\n    f\"Grade {external_predicted_grade}\"\n)\n\nprint(\n    f\"P(Referable DR)       : \"\n    f\"{external_referable_probability:.2%}\"\n)\n\nprint(\n    f\"Threshold             : 45.00%\"\n)\n\nprint(\n    f\"Screening Decision    : \"\n    f\"{external_decision}\"\n)\n\nprint(\"\\nGrade Probabilities\")\nprint(\"-\" * 35)\n\nfor grade, probability in enumerate(\n    external_probabilities\n):\n    print(\n        f\"Grade {grade} : \"\n        f\"{probability:.2%}\"\n    )\n\nprint(\"=\" * 60)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T14:53:12.957626Z","iopub.execute_input":"2026-09-07T14:53:12.958147Z","iopub.status.idle":"2026-09-07T14:53:13.039742Z","shell.execute_reply.started":"2026-09-07T14:53:12.958114Z","shell.execute_reply":"2026-09-07T14:53:13.039076Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"SAVING TO THE MODEL\n","metadata":{}},{"cell_type":"code","source":"# ============================================================\n# NETRX — SAVE COMPLETE INFERENCE BUNDLE\n# ============================================================\n\nimport torch\nimport os\nimport json\n\nNETRX_BUNDLE_PATH = \"/kaggle/working/netrx_inference_bundle.pth\"\n\nnetrx_bundle = {\n    \n    # --------------------------------------------------------\n    # Trained models\n    # --------------------------------------------------------\n    \"models\": {\n        \"CNN\": cnn_ensemble,\n        \"ResNet50\": resnet_ensemble,\n        \"EfficientNet-B0\": efficientnet_ensemble,\n        \"DenseNet121\": densenet_ensemble,\n        \"ConvNeXt-Tiny\": convnext_ensemble,\n        \"ViT-B/16\": vit_ensemble\n    },\n\n    # --------------------------------------------------------\n    # Final ensemble weights\n    # --------------------------------------------------------\n    \"ensemble_weights\": {\n        \"CNN\": 0.0333,\n        \"ResNet50\": 0.0538,\n        \"EfficientNet-B0\": 0.1411,\n        \"DenseNet121\": 0.1196,\n        \"ConvNeXt-Tiny\": 0.3172,\n        \"ViT-B/16\": 0.3349\n    },\n\n    # --------------------------------------------------------\n    # Screening configuration\n    # --------------------------------------------------------\n    \"referable_threshold\": 0.45,\n\n    \"classes\": {\n        0: \"No DR\",\n        1: \"Mild NPDR\",\n        2: \"Moderate NPDR\",\n        3: \"Severe NPDR\",\n        4: \"Proliferative DR\"\n    },\n\n    # --------------------------------------------------------\n    # Input configuration\n    # --------------------------------------------------------\n    \"input_size\": 224,\n\n    \"imagenet_mean\": [\n        0.485,\n        0.456,\n        0.406\n    ],\n\n    \"imagenet_std\": [\n        0.229,\n        0.224,\n        0.225\n    ],\n\n    # --------------------------------------------------------\n    # Metadata\n    # --------------------------------------------------------\n    \"project\": \"NetrX\",\n    \"dataset\": \"APTOS 2019\",\n    \"task\": \"Diabetic Retinopathy Screening\"\n}\n\ntorch.save(\n    netrx_bundle,\n    NETRX_BUNDLE_PATH\n)\n\nprint(\"=\" * 65)\nprint(\"        NETRX INFERENCE BUNDLE SAVED\")\nprint(\"=\" * 65)\n\nprint(\"\\nFile:\")\nprint(NETRX_BUNDLE_PATH)\n\nprint(\n    \"\\nSize:\",\n    round(\n        os.path.getsize(NETRX_BUNDLE_PATH) / (1024**2),\n        2\n    ),\n    \"MB\"\n)\n\nprint(\"\\nContains:\")\nprint(\"✓ CNN\")\nprint(\"✓ ResNet50\")\nprint(\"✓ EfficientNet-B0\")\nprint(\"✓ DenseNet121\")\nprint(\"✓ ConvNeXt-Tiny\")\nprint(\"✓ ViT-B/16\")\nprint(\"✓ Ensemble weights\")\nprint(\"✓ Referable threshold\")\nprint(\"✓ Preprocessing configuration\")\nprint(\"✓ Class definitions\")\n\nprint(\"\\nREADY FOR INFERENCE\")\nprint(\"=\" * 65)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T14:53:19.48303Z","iopub.execute_input":"2026-09-07T14:53:19.483323Z","iopub.status.idle":"2026-09-07T14:53:20.321692Z","shell.execute_reply.started":"2026-09-07T14:53:19.483299Z","shell.execute_reply":"2026-09-07T14:53:20.321Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# ADD INFERENCE BUNDLE TO FINAL BACKUP ZIP\n# ============================================================\n\nimport zipfile\nimport os\n\nzip_path = \"/kaggle/working/netrx_final_package.zip\"\n\nwith zipfile.ZipFile(\n    zip_path,\n    \"a\",\n    compression=zipfile.ZIP_DEFLATED\n) as zipf:\n\n    zipf.write(\n        \"/kaggle/working/netrx_inference_bundle.pth\",\n        \"netrx_inference_bundle.pth\"\n    )\n\nprint(\"✅ Inference bundle added to final ZIP\")\n\nprint(\n    \"ZIP size:\",\n    round(\n        os.path.getsize(zip_path) / (1024**2),\n        2\n    ),\n    \"MB\"\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T14:53:28.17421Z","iopub.execute_input":"2026-09-07T14:53:28.175132Z","iopub.status.idle":"2026-09-07T14:53:58.628009Z","shell.execute_reply.started":"2026-09-07T14:53:28.175088Z","shell.execute_reply":"2026-09-07T14:53:58.627087Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# NETRX — LOAD MODEL FOR INFERENCE\n# ============================================================\n\nbundle = torch.load(\n    \"/kaggle/working/netrx_inference_bundle.pth\",\n    map_location=device,\n    weights_only=False\n)\n\nensemble_models = bundle[\"models\"]\n\nensemble_weights = np.array(\n    list(bundle[\"ensemble_weights\"].values()),\n    dtype=np.float32\n)\n\nensemble_weights /= ensemble_weights.sum()\n\nreferable_threshold = bundle[\"referable_threshold\"]\n\nprint(\"✅ NETRX LOADED\")\nprint(\"Models:\", list(ensemble_models.keys()))\nprint(\"Threshold:\", referable_threshold)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T13:44:25.020162Z","iopub.status.idle":"2026-09-07T13:44:25.020477Z","shell.execute_reply.started":"2026-09-07T13:44:25.020358Z","shell.execute_reply":"2026-09-07T13:44:25.020374Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"NOW SAVING THE MODEL HERE","metadata":{}},{"cell_type":"code","source":"import os\nimport zipfile\n\n# ============================================================\n# NETRX — FINAL 6 MODEL BACKUP\n# ============================================================\n\nmodel_files = [\n    \"netrx_cnn_best.pth\",\n    \"netrx_resnet50_best.pth\",\n    \"netrx_efficientnet_b0_best.pth\",\n    \"netrx_densenet121_best.pth\",\n    \"netrx_convnext_tiny_best.pth\",\n    \"netrx_vit_b16_best.pth\"\n]\n\noutput_zip = \"/kaggle/working/NETRX_ALL_6_MODELS.zip\"\n\n# Check all files exist\nprint(\"=\" * 70)\nprint(\"CHECKING NETRX MODEL FILES\")\nprint(\"=\" * 70)\n\nmissing = []\n\nfor filename in model_files:\n    path = os.path.join(\"/kaggle/working\", filename)\n\n    if os.path.exists(path):\n        size_mb = os.path.getsize(path) / (1024 ** 2)\n        print(f\"✅ {filename:<35} {size_mb:>8.2f} MB\")\n    else:\n        print(f\"❌ MISSING: {filename}\")\n        missing.append(filename)\n\n# Stop if anything is missing\nif missing:\n    print(\"\\n❌ BACKUP NOT CREATED\")\n    print(\"Missing files:\")\n    for f in missing:\n        print(\"   \", f)\n\nelse:\n    # Create ZIP\n    with zipfile.ZipFile(\n        output_zip,\n        \"w\",\n        compression=zipfile.ZIP_DEFLATED\n    ) as zipf:\n\n        for filename in model_files:\n            path = os.path.join(\"/kaggle/working\", filename)\n            zipf.write(path, arcname=filename)\n\n    # Verify ZIP\n    zip_size = os.path.getsize(output_zip) / (1024 ** 2)\n\n    print(\"\\n\" + \"=\" * 70)\n    print(\"✅ NETRX FINAL MODEL BACKUP CREATED\")\n    print(\"=\" * 70)\n\n    print(f\"\\nZIP file:\")\n    print(output_zip)\n\n    print(f\"\\nZIP size: {zip_size:.2f} MB\")\n\n    print(\"\\nFiles inside ZIP:\")\n\n    with zipfile.ZipFile(output_zip, \"r\") as zipf:\n        for f in zipf.namelist():\n            print(\"✅\", f)\n\n    print(\"\\n\" + \"=\" * 70)\n    print(\"🔥 ALL 6 NETRX MODELS SAFELY PACKAGED\")\n    print(\"=\" * 70)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T14:56:11.21803Z","iopub.execute_input":"2026-09-07T14:56:11.218903Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"CHECKING THE IMAGE PROCESSING ","metadata":{}},{"cell_type":"code","source":"# ============================================================\n# NETRX — FUNDUS IMAGE QUALITY ASSESSMENT\n# CELL 1: IMPORTS + CONFIGURATION\n# ============================================================\n\nimport cv2\nimport numpy as np\nimport os\nimport matplotlib.pyplot as plt\n\nfrom PIL import Image\nfrom IPython.display import display\n\nprint(\"=\" * 70)\nprint(\"NETRX — IMAGE QUALITY ASSESSMENT MODULE\")\nprint(\"=\" * 70)\n\nprint(\"✅ OpenCV loaded\")\nprint(\"✅ NumPy loaded\")\nprint(\"✅ Matplotlib loaded\")\nprint(\"✅ PIL loaded\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T15:30:02.472164Z","iopub.execute_input":"2026-09-07T15:30:02.47244Z","iopub.status.idle":"2026-09-07T15:30:02.479015Z","shell.execute_reply.started":"2026-09-07T15:30:02.472416Z","shell.execute_reply":"2026-09-07T15:30:02.478189Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 2 — FOCUS / SHARPNESS ASSESSMENT\n# ============================================================\n\ndef calculate_focus_score(image):\n    \"\"\"\n    Calculate image sharpness using variance of Laplacian.\n\n    Higher value generally indicates stronger edge detail\n    and therefore better focus/sharpness.\n    \"\"\"\n\n    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)\n\n    laplacian = cv2.Laplacian(\n        gray,\n        cv2.CV_64F\n    )\n\n    variance = laplacian.var()\n\n    return float(variance)\n\n\nprint(\"✅ Focus / sharpness function ready\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T15:30:20.356362Z","iopub.execute_input":"2026-09-07T15:30:20.356698Z","iopub.status.idle":"2026-09-07T15:30:20.361815Z","shell.execute_reply.started":"2026-09-07T15:30:20.35667Z","shell.execute_reply":"2026-09-07T15:30:20.360988Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 3 — ILLUMINATION ASSESSMENT\n# ============================================================\n\ndef calculate_illumination_score(image):\n    \"\"\"\n    Estimate illumination quality from grayscale brightness.\n\n    Returns:\n        mean_intensity\n        illumination_score\n    \"\"\"\n\n    gray = cv2.cvtColor(\n        image,\n        cv2.COLOR_BGR2GRAY\n    )\n\n    mean_intensity = float(\n        np.mean(gray)\n    )\n\n    # Ideal brightness region for the quality module.\n    # These are engineering thresholds and will be calibrated.\n    ideal_low = 70\n    ideal_high = 180\n\n    if ideal_low <= mean_intensity <= ideal_high:\n\n        score = 100.0\n\n    elif mean_intensity < ideal_low:\n\n        score = (\n            mean_intensity /\n            ideal_low\n        ) * 100\n\n    else:\n\n        score = (\n            (255 - mean_intensity) /\n            (255 - ideal_high)\n        ) * 100\n\n    score = np.clip(\n        score,\n        0,\n        100\n    )\n\n    return mean_intensity, float(score)\n\n\nprint(\"✅ Illumination function ready\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T15:30:48.033109Z","iopub.execute_input":"2026-09-07T15:30:48.033777Z","iopub.status.idle":"2026-09-07T15:30:48.039682Z","shell.execute_reply.started":"2026-09-07T15:30:48.033745Z","shell.execute_reply":"2026-09-07T15:30:48.038818Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 4 — FIELD OF VIEW / RETINAL COVERAGE\n# ============================================================\n\ndef calculate_fov_score(image):\n    \"\"\"\n    Estimate retinal field-of-view coverage.\n\n    Detects the circular retinal region relative to\n    the complete image area.\n    \"\"\"\n\n    gray = cv2.cvtColor(\n        image,\n        cv2.COLOR_BGR2GRAY\n    )\n\n    # Remove small noise\n    blurred = cv2.GaussianBlur(\n        gray,\n        (9, 9),\n        0\n    )\n\n    # Threshold to separate dark background\n    # from retinal region.\n    _, binary = cv2.threshold(\n        blurred,\n        15,\n        255,\n        cv2.THRESH_BINARY\n    )\n\n    contours, _ = cv2.findContours(\n        binary,\n        cv2.RETR_EXTERNAL,\n        cv2.CHAIN_APPROX_SIMPLE\n    )\n\n    if len(contours) == 0:\n        return 0.0\n\n    largest_contour = max(\n        contours,\n        key=cv2.contourArea\n    )\n\n    retinal_area = cv2.contourArea(\n        largest_contour\n    )\n\n    image_area = (\n        image.shape[0] *\n        image.shape[1]\n    )\n\n    coverage = (\n        retinal_area /\n        image_area\n    )\n\n    # Convert to a 0–100 quality score.\n    score = np.clip(\n        coverage * 100,\n        0,\n        100\n    )\n\n    return float(score)\n\n\nprint(\"✅ FOV function ready\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T15:31:03.297592Z","iopub.execute_input":"2026-09-07T15:31:03.297853Z","iopub.status.idle":"2026-09-07T15:31:03.304396Z","shell.execute_reply.started":"2026-09-07T15:31:03.297832Z","shell.execute_reply":"2026-09-07T15:31:03.303586Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 5 — TEST IMAGE QUALITY METRICS ON APTOS\n# ============================================================\n\nimport glob\nimport random\n\n# Find image files inside /kaggle/input\nimage_extensions = [\"*.png\", \"*.jpg\", \"*.jpeg\", \"*.PNG\", \"*.JPG\", \"*.JPEG\"]\n\naptos_images = []\n\nfor ext in image_extensions:\n    aptos_images.extend(\n        glob.glob(\"/kaggle/input/**/*.png\", recursive=True)\n        if ext == \"*.png\" else\n        glob.glob(f\"/kaggle/input/**/*{ext[1:]}\", recursive=True)\n    )\n\n# Remove duplicates\naptos_images = list(set(aptos_images))\n\nprint(\"Total images found:\", len(aptos_images))\n\n# Show a few paths\nfor p in aptos_images[:10]:\n    print(p)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T15:31:43.067326Z","iopub.execute_input":"2026-09-07T15:31:43.067749Z","iopub.status.idle":"2026-09-07T15:31:46.678856Z","shell.execute_reply.started":"2026-09-07T15:31:43.067713Z","shell.execute_reply":"2026-09-07T15:31:46.678198Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 6 — CALCULATE FOCUS / ILLUMINATION / FOV\n# ============================================================\n\nsample_images = aptos_images[:100]\n\nfocus_scores = []\nillumination_scores = []\nfov_scores = []\n\nfor image_path in sample_images:\n\n    image = cv2.imread(image_path)\n\n    if image is None:\n        continue\n\n    # Focus\n    focus = calculate_focus_score(image)\n\n    # Illumination\n    mean_intensity, illumination = calculate_illumination_score(image)\n\n    # FOV\n    fov = calculate_fov_score(image)\n\n    focus_scores.append(focus)\n    illumination_scores.append(illumination)\n    fov_scores.append(fov)\n\nprint(\"\\n==============================================\")\nprint(\"APTOS IMAGE QUALITY STATISTICS\")\nprint(\"==============================================\")\n\nprint(f\"Images analyzed       : {len(focus_scores)}\")\n\nprint(\"\\nFOCUS / LAPLACIAN VARIANCE\")\nprint(f\"Minimum               : {np.min(focus_scores):.2f}\")\nprint(f\"Median                : {np.median(focus_scores):.2f}\")\nprint(f\"Maximum               : {np.max(focus_scores):.2f}\")\n\nprint(\"\\nILLUMINATION SCORE\")\nprint(f\"Minimum               : {np.min(illumination_scores):.2f}\")\nprint(f\"Median                : {np.median(illumination_scores):.2f}\")\nprint(f\"Maximum               : {np.max(illumination_scores):.2f}\")\n\nprint(\"\\nFOV SCORE\")\nprint(f\"Minimum               : {np.min(fov_scores):.2f}\")\nprint(f\"Median                : {np.median(fov_scores):.2f}\")\nprint(f\"Maximum               : {np.max(fov_scores):.2f}\")\n\nprint(\"==============================================\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T15:31:55.762128Z","iopub.execute_input":"2026-09-07T15:31:55.76291Z","iopub.status.idle":"2026-09-07T15:32:04.448769Z","shell.execute_reply.started":"2026-09-07T15:31:55.762878Z","shell.execute_reply":"2026-09-07T15:32:04.447885Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 7 — VISUALIZE APTOS IMAGE + QUALITY METRICS\n# ============================================================\n\nimport matplotlib.pyplot as plt\nimport cv2\nimport os\n\n# Number of images to display\nnum_show = 6\n\nplt.figure(figsize=(18, 12))\n\nfor i, image_path in enumerate(sample_images[:num_show]):\n\n    image = cv2.imread(image_path)\n\n    if image is None:\n        continue\n\n    # Convert BGR → RGB\n    image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)\n\n    # Calculate metrics\n    focus = calculate_focus_score(image)\n    mean_intensity, illumination = calculate_illumination_score(image)\n    fov = calculate_fov_score(image)\n\n    # Display\n    plt.subplot(2, 3, i + 1)\n    plt.imshow(image_rgb)\n    plt.axis(\"off\")\n\n    plt.title(\n        f\"{os.path.basename(image_path)}\\n\"\n        f\"Focus: {focus:.2f} | \"\n        f\"Illumination: {illumination:.2f} | \"\n        f\"FOV: {fov:.2f}\",\n        fontsize=10\n    )\n\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T15:33:24.503395Z","iopub.execute_input":"2026-09-07T15:33:24.504277Z","iopub.status.idle":"2026-09-07T15:33:26.407413Z","shell.execute_reply.started":"2026-09-07T15:33:24.504245Z","shell.execute_reply":"2026-09-07T15:33:26.405468Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 8 — UPLOAD FUNDUS IMAGE FROM BROWSE\n# ============================================================\n\nfrom IPython.display import display\nimport ipywidgets as widgets\nfrom IPython.display import clear_output\n\nupload = widgets.FileUpload(\n    accept=\".png,.jpg,.jpeg\",\n    multiple=False\n)\n\ndisplay(upload)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T15:34:13.23742Z","iopub.execute_input":"2026-09-07T15:34:13.237769Z","iopub.status.idle":"2026-09-07T15:34:13.245737Z","shell.execute_reply.started":"2026-09-07T15:34:13.237735Z","shell.execute_reply":"2026-09-07T15:34:13.244904Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 9 — READ UPLOADED IMAGE (KAGGLE COMPATIBLE)\n# ============================================================\n\nif not upload.value:\n    print(\"❌ No image uploaded. Please click Browse and select an image.\")\nelse:\n\n    # Kaggle / newer ipywidgets returns a tuple\n    uploaded_file = upload.value[0]\n\n    # Get file content\n    image_bytes = uploaded_file[\"content\"]\n\n    # Convert bytes → NumPy array\n    image_array = np.frombuffer(image_bytes, np.uint8)\n\n    # Decode → OpenCV image\n    uploaded_image = cv2.imdecode(\n        image_array,\n        cv2.IMREAD_COLOR\n    )\n\n    if uploaded_image is None:\n        print(\"❌ Could not read the uploaded image.\")\n    else:\n\n        print(\"✅ Image uploaded successfully\")\n\n        print(\"Filename :\", uploaded_file[\"name\"])\n        print(\"Image shape :\", uploaded_image.shape)\n\n        # Display image\n        plt.figure(figsize=(8, 8))\n\n        plt.imshow(\n            cv2.cvtColor(\n                uploaded_image,\n                cv2.COLOR_BGR2RGB\n            )\n        )\n\n        plt.axis(\"off\")\n        plt.title(\"Uploaded Fundus Image\")\n        plt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T15:34:59.857365Z","iopub.execute_input":"2026-09-07T15:34:59.857661Z","iopub.status.idle":"2026-09-07T15:35:00.762844Z","shell.execute_reply.started":"2026-09-07T15:34:59.857637Z","shell.execute_reply":"2026-09-07T15:35:00.76187Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 10 — QUALITY ANALYSIS OF UPLOADED IMAGE\n# ============================================================\n\nfocus = calculate_focus_score(uploaded_image)\n\nmean_intensity, illumination = calculate_illumination_score(\n    uploaded_image\n)\n\nfov = calculate_fov_score(uploaded_image)\n\nprint(\"==============================================\")\nprint(\"        NETRX IMAGE QUALITY ANALYSIS\")\nprint(\"==============================================\")\n\nprint(f\"Focus / Sharpness Score : {focus:.2f}\")\nprint(f\"Mean Illumination       : {mean_intensity:.2f}\")\nprint(f\"Illumination Score      : {illumination:.2f}\")\nprint(f\"FOV Score               : {fov:.2f}\")\n\nprint(\"==============================================\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T15:35:23.242972Z","iopub.execute_input":"2026-09-07T15:35:23.243741Z","iopub.status.idle":"2026-09-07T15:35:23.376121Z","shell.execute_reply.started":"2026-09-07T15:35:23.243711Z","shell.execute_reply":"2026-09-07T15:35:23.375433Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 11 — NETRX QUALITY DECISION ENGINE\n# ============================================================\n\ndef netrx_quality_decision(focus, illumination, fov):\n\n    issues = []\n\n    # --------------------------------------------------------\n    # Focus thresholds\n    # Based on current APTOS distribution\n    # --------------------------------------------------------\n    if focus < 8:\n        issues.append(\"Severe image blur\")\n        focus_status = \"SEVERE\"\n    elif focus < 15:\n        issues.append(\"Low image sharpness\")\n        focus_status = \"LOW\"\n    else:\n        focus_status = \"GOOD\"\n\n    # --------------------------------------------------------\n    # Illumination\n    # --------------------------------------------------------\n    if illumination < 60:\n        issues.append(\"Poor illumination\")\n        illumination_status = \"POOR\"\n    elif illumination < 80:\n        issues.append(\"Suboptimal illumination\")\n        illumination_status = \"LOW\"\n    else:\n        illumination_status = \"GOOD\"\n\n    # --------------------------------------------------------\n    # FOV\n    # --------------------------------------------------------\n    if fov < 50:\n        issues.append(\"Insufficient retinal field\")\n        fov_status = \"SEVERE\"\n    elif fov < 70:\n        issues.append(\"Limited retinal field\")\n        fov_status = \"LOW\"\n    else:\n        fov_status = \"GOOD\"\n\n    # --------------------------------------------------------\n    # Convert focus to a 0–100 score\n    # --------------------------------------------------------\n    focus_score = np.clip((focus / 60) * 100, 0, 100)\n\n    # --------------------------------------------------------\n    # Overall quality\n    # --------------------------------------------------------\n    overall_quality = (\n        0.40 * focus_score +\n        0.30 * illumination +\n        0.30 * fov\n    )\n\n    # --------------------------------------------------------\n    # Decision logic\n    # --------------------------------------------------------\n\n    # Severe failures → RETAKE\n    if focus_status == \"SEVERE\" or fov_status == \"SEVERE\":\n        status = \"RETAKE REQUIRED\"\n        action = \"Retake fundus image\"\n\n    # Moderate problems → ENHANCEMENT\n    elif len(issues) > 0:\n        status = \"ENHANCEMENT NEEDED\"\n        action = \"Targeted enhancement\"\n\n    # Everything acceptable\n    else:\n        status = \"ACCEPTED\"\n        action = \"Proceed to DR screening\"\n\n    return {\n        \"focus_score\": focus_score,\n        \"illumination_score\": illumination,\n        \"fov_score\": fov,\n        \"overall_quality\": overall_quality,\n        \"issues\": issues,\n        \"status\": status,\n        \"action\": action\n    }","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T15:39:57.9276Z","iopub.execute_input":"2026-09-07T15:39:57.927854Z","iopub.status.idle":"2026-09-07T15:39:57.935314Z","shell.execute_reply.started":"2026-09-07T15:39:57.927832Z","shell.execute_reply":"2026-09-07T15:39:57.934467Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 12 — RUN NETRX QUALITY DECISION\n# ============================================================\n\nquality_result = netrx_quality_decision(\n    focus,\n    illumination,\n    fov\n)\n\nprint(\"==============================================\")\nprint(\"          NETRX QUALITY DECISION\")\nprint(\"==============================================\")\n\nprint(f\"Focus Score        : {quality_result['focus_score']:.2f}\")\nprint(f\"Illumination Score : {quality_result['illumination_score']:.2f}\")\nprint(f\"FOV Score          : {quality_result['fov_score']:.2f}\")\nprint(f\"Overall Quality    : {quality_result['overall_quality']:.2f}\")\n\nprint(\"----------------------------------------------\")\n\nprint(f\"STATUS             : {quality_result['status']}\")\nprint(f\"ACTION             : {quality_result['action']}\")\n\nif quality_result[\"issues\"]:\n\n    print(\"\\nDetected Problems:\")\n\n    for issue in quality_result[\"issues\"]:\n        print(f\"• {issue}\")\n\nelse:\n    print(\"\\nDetected Problems: None\")\n\nprint(\"==============================================\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T15:40:10.357339Z","iopub.execute_input":"2026-09-07T15:40:10.358137Z","iopub.status.idle":"2026-09-07T15:40:10.364772Z","shell.execute_reply.started":"2026-09-07T15:40:10.358106Z","shell.execute_reply":"2026-09-07T15:40:10.363934Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 13 — NETRX VISUAL QUALITY REPORT\n# ============================================================\n\ndef display_quality_report(image, image_name, result):\n\n    img = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)\n\n    plt.figure(figsize=(12, 9))\n\n    plt.imshow(img)\n    plt.axis(\"off\")\n\n    report = (\n        f\"NETRX IMAGE QUALITY REPORT\\n\\n\"\n        f\"Image ID          : {image_name}\\n\"\n        f\"Focus Score       : {result['focus_score']:.2f}\\n\"\n        f\"Illumination      : {result['illumination_score']:.2f}\\n\"\n        f\"FOV Score         : {result['fov_score']:.2f}\\n\"\n        f\"Overall Quality   : {result['overall_quality']:.2f}\\n\\n\"\n        f\"STATUS            : {result['status']}\\n\"\n        f\"ACTION            : {result['action']}\\n\"\n    )\n\n    if result[\"issues\"]:\n\n        report += \"\\nDetected Problems:\\n\"\n\n        for issue in result[\"issues\"]:\n            report += f\"• {issue}\\n\"\n\n    else:\n        report += \"\\nDetected Problems: None\\n\"\n\n    plt.figtext(\n        0.02,\n        0.02,\n        report,\n        fontsize=11,\n        verticalalignment=\"bottom\"\n    )\n\n    plt.tight_layout()\n    plt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T15:41:06.754054Z","iopub.execute_input":"2026-09-07T15:41:06.754246Z","iopub.status.idle":"2026-09-07T15:41:06.76001Z","shell.execute_reply.started":"2026-09-07T15:41:06.754228Z","shell.execute_reply":"2026-09-07T15:41:06.75925Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 14 — DISPLAY CURRENT IMAGE REPORT\n# ============================================================\n\ndisplay_quality_report(\n    uploaded_image,\n    uploaded_file[\"name\"],\n    quality_result\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T15:41:22.947999Z","iopub.execute_input":"2026-09-07T15:41:22.948616Z","iopub.status.idle":"2026-09-07T15:41:24.095885Z","shell.execute_reply.started":"2026-09-07T15:41:22.948585Z","shell.execute_reply":"2026-09-07T15:41:24.094985Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"if status == \"ACCEPTED\":\n    → directly send image to models\n\nelif status == \"ENHANCEMENT NEEDED\":\n    → identify problem\n    → apply appropriate enhancement\n    → calculate quality again\n    → if improved → model\n\nelif status == \"RETAKE REQUIRED\":\n    → STOP\n    → do NOT run DR model","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T15:41:46.968141Z","iopub.execute_input":"2026-09-07T15:41:46.969131Z","iopub.status.idle":"2026-09-07T15:41:46.974621Z","shell.execute_reply.started":"2026-09-07T15:41:46.9691Z","shell.execute_reply":"2026-09-07T15:41:46.973608Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#fixing","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T15:45:05.867964Z","iopub.execute_input":"2026-09-07T15:45:05.868659Z","iopub.status.idle":"2026-09-07T15:45:05.872218Z","shell.execute_reply.started":"2026-09-07T15:45:05.868626Z","shell.execute_reply":"2026-09-07T15:45:05.871378Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 13 — NETRX VISUAL QUALITY REPORT\n# ============================================================\n\ndef display_quality_report(image, image_name, result):\n\n    # Convert BGR → RGB\n    img_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)\n\n    # Create two sections:\n    # Left  = fundus image\n    # Right = quality report\n    fig, axes = plt.subplots(\n        1, 2,\n        figsize=(15, 7),\n        gridspec_kw={\"width_ratios\": [2.2, 1]}\n    )\n\n    # --------------------------------------------------------\n    # FUNDUS IMAGE\n    # --------------------------------------------------------\n    axes[0].imshow(img_rgb)\n    axes[0].axis(\"off\")\n    axes[0].set_title(\n        \"FUNDUS IMAGE\",\n        fontsize=15,\n        fontweight=\"bold\"\n    )\n\n    # --------------------------------------------------------\n    # QUALITY REPORT\n    # --------------------------------------------------------\n    axes[1].axis(\"off\")\n\n    report = \"NETRX IMAGE QUALITY REPORT\\n\\n\"\n\n    report += f\"Image ID       : {image_name}\\n\\n\"\n\n    report += f\"Focus          : {result['focus_score']:.2f}\\n\"\n    report += f\"Illumination   : {result['illumination_score']:.2f}\\n\"\n    report += f\"FOV            : {result['fov_score']:.2f}\\n\"\n    report += f\"Overall        : {result['overall_quality']:.2f}\\n\\n\"\n\n    report += f\"Status         : {result['status']}\\n\"\n    report += f\"Action         : {result['action']}\\n\\n\"\n\n    if result[\"issues\"]:\n\n        report += \"Issues:\\n\"\n\n        for issue in result[\"issues\"]:\n            report += f\"• {issue}\\n\"\n\n    else:\n\n        report += \"Issues:\\n\"\n        report += \"• None detected\\n\"\n\n    axes[1].text(\n        0.02,\n        0.95,\n        report,\n        transform=axes[1].transAxes,\n        fontsize=12,\n        verticalalignment=\"top\",\n        family=\"monospace\"\n    )\n\n    plt.tight_layout()\n    plt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T15:45:11.291115Z","iopub.execute_input":"2026-09-07T15:45:11.291827Z","iopub.status.idle":"2026-09-07T15:45:11.298146Z","shell.execute_reply.started":"2026-09-07T15:45:11.291804Z","shell.execute_reply":"2026-09-07T15:45:11.297521Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 14 — DISPLAY QUALITY REPORT\n# ============================================================\n\ndisplay_quality_report(\n    uploaded_image,\n    uploaded_file[\"name\"],\n    quality_result\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T15:45:22.718107Z","iopub.execute_input":"2026-09-07T15:45:22.718836Z","iopub.status.idle":"2026-09-07T15:45:23.689125Z","shell.execute_reply.started":"2026-09-07T15:45:22.718806Z","shell.execute_reply":"2026-09-07T15:45:23.688343Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 15 — AUTOMATIC QUALITY ROUTING\n# ============================================================\n\nstatus = quality_result[\"status\"]\n\nprint(\"==============================================\")\nprint(\"          NETRX AUTOMATIC ROUTING\")\nprint(\"==============================================\")\n\nif status == \"ACCEPTED\":\n\n    print(\"STATUS : ACCEPTED\")\n    print(\"ACTION : Sending image directly to DR models\")\n\n    model_input_image = uploaded_image\n\n\nelif status == \"ENHANCEMENT NEEDED\":\n\n    print(\"STATUS : ENHANCEMENT NEEDED\")\n    print(\"ACTION : Applying targeted enhancement\")\n\n    model_input_image = uploaded_image\n\n    # --------------------------------------------------------\n    # TARGETED ENHANCEMENT\n    # --------------------------------------------------------\n\n    # Convert to LAB for illumination enhancement\n    lab = cv2.cvtColor(model_input_image, cv2.COLOR_BGR2LAB)\n\n    l_channel, a_channel, b_channel = cv2.split(lab)\n\n    # CLAHE improves local contrast / illumination\n    clahe = cv2.createCLAHE(\n        clipLimit=2.0,\n        tileGridSize=(8, 8)\n    )\n\n    enhanced_l = clahe.apply(l_channel)\n\n    enhanced_lab = cv2.merge(\n        [enhanced_l, a_channel, b_channel]\n    )\n\n    enhanced_image = cv2.cvtColor(\n        enhanced_lab,\n        cv2.COLOR_LAB2BGR\n    )\n\n    # --------------------------------------------------------\n    # Mild sharpening\n    # --------------------------------------------------------\n\n    gaussian = cv2.GaussianBlur(\n        enhanced_image,\n        (0, 0),\n        3\n    )\n\n    sharpened_image = cv2.addWeighted(\n        enhanced_image,\n        1.5,\n        gaussian,\n        -0.5,\n        0\n    )\n\n    model_input_image = sharpened_image\n\n    print(\"Targeted enhancement completed.\")\n\n\nelif status == \"RETAKE REQUIRED\":\n\n    print(\"STATUS : RETAKE REQUIRED\")\n    print(\"ACTION : Image rejected before model inference\")\n\n    model_input_image = None\n\n\nelse:\n\n    print(\"ERROR : Unknown quality status\")\n\n    model_input_image = None\n\n\nprint(\"==============================================\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T15:45:56.95128Z","iopub.execute_input":"2026-09-07T15:45:56.951922Z","iopub.status.idle":"2026-09-07T15:45:57.31002Z","shell.execute_reply.started":"2026-09-07T15:45:56.951889Z","shell.execute_reply":"2026-09-07T15:45:57.309322Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 16 — DISPLAY MODEL INPUT\n# ============================================================\n\nif model_input_image is not None:\n\n    plt.figure(figsize=(8, 8))\n\n    plt.imshow(\n        cv2.cvtColor(\n            model_input_image,\n            cv2.COLOR_BGR2RGB\n        )\n    )\n\n    plt.title(\"NETRX MODEL INPUT\")\n    plt.axis(\"off\")\n    plt.show()\n\nelse:\n\n    print(\"No image sent to the model.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T15:46:24.171856Z","iopub.execute_input":"2026-09-07T15:46:24.172602Z","iopub.status.idle":"2026-09-07T15:46:25.001248Z","shell.execute_reply.started":"2026-09-07T15:46:24.172524Z","shell.execute_reply":"2026-09-07T15:46:25.000268Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 16 — DISPLAY FINAL MODEL INPUT\n# ============================================================\n\nif model_input_image is not None:\n\n    plt.figure(figsize=(8, 8))\n\n    # model_input_image is already RGB\n    plt.imshow(model_input_image)\n\n    plt.title(\n        f\"NETRX MODEL INPUT — {processing_status}\"\n    )\n\n    plt.axis(\"off\")\n    plt.show()\n\nelse:\n\n    print(\"No image sent to the model.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T15:56:55.721761Z","iopub.execute_input":"2026-09-07T15:56:55.722405Z","iopub.status.idle":"2026-09-07T15:56:56.545304Z","shell.execute_reply.started":"2026-09-07T15:56:55.722373Z","shell.execute_reply":"2026-09-07T15:56:56.54462Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"cv2.cvtColor(\n    model_input_image,\n    cv2.COLOR_BGR2RGB\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T15:58:16.693925Z","iopub.execute_input":"2026-09-07T15:58:16.694783Z","iopub.status.idle":"2026-09-07T15:58:16.708861Z","shell.execute_reply.started":"2026-09-07T15:58:16.694749Z","shell.execute_reply":"2026-09-07T15:58:16.708061Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 17 — CHECK AVAILABLE TRAINED MODEL OBJECTS\n# ============================================================\n\nprint(\"Available model variables:\\n\")\n\nfor name in [\n    \"cnn_model\",\n    \"resnet_model\",\n    \"efficientnet_model\",\n    \"densenet_model\",\n    \"convnext_model\",\n    \"vit_model\"\n]:\n    print(f\"{name:25s} :\", name in globals())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T15:58:47.588033Z","iopub.execute_input":"2026-09-07T15:58:47.588756Z","iopub.status.idle":"2026-09-07T15:58:47.594512Z","shell.execute_reply.started":"2026-09-07T15:58:47.588723Z","shell.execute_reply":"2026-09-07T15:58:47.593584Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 18 — LOAD ALL NETRX TRAINED MODELS FOR INFERENCE\n# ============================================================\n\nimport os\nimport torch\n\nprint(\"=\" * 70)\nprint(\"NETRX — CHECKPOINT FILES\")\nprint(\"=\" * 70)\n\nmodel_paths = {\n    \"CNN\": \"/kaggle/working/netrx_cnn_best.pth\",\n    \"ResNet50\": \"/kaggle/working/netrx_resnet50_best.pth\",\n    \"EfficientNet-B0\": \"/kaggle/working/netrx_efficientnet_b0_best-2.pth\",\n    \"DenseNet121\": \"/kaggle/working/netrx_densenet121_best-2.pth\",\n    \"ConvNeXt-Tiny\": \"/kaggle/working/netrx_convnext_tiny_best.pth\",\n    \"ViT-B/16\": \"/kaggle/working/netrx_vit_b16_best.pth\"\n}\n\nfor name, path in model_paths.items():\n    print(\n        f\"{name:20s}: \"\n        f\"{'FOUND' if os.path.exists(path) else 'MISSING'}\"\n    )","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T15:59:27.631852Z","iopub.execute_input":"2026-09-07T15:59:27.632527Z","iopub.status.idle":"2026-09-07T15:59:27.638177Z","shell.execute_reply.started":"2026-09-07T15:59:27.632495Z","shell.execute_reply":"2026-09-07T15:59:27.637442Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CHECK EXACT MODEL FILENAMES\n# ============================================================\n\nimport os\n\nfor f in sorted(os.listdir(\"/kaggle/working\")):\n    if f.endswith(\".pth\"):\n        print(f)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T16:00:06.814899Z","iopub.execute_input":"2026-09-07T16:00:06.815618Z","iopub.status.idle":"2026-09-07T16:00:06.82041Z","shell.execute_reply.started":"2026-09-07T16:00:06.815585Z","shell.execute_reply":"2026-09-07T16:00:06.819702Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model_paths = {\n    \"CNN\": \"/kaggle/working/netrx_cnn_best.pth\",\n    \"ResNet50\": \"/kaggle/working/netrx_resnet50_best.pth\",\n    \"EfficientNet-B0\": \"/kaggle/working/netrx_efficientnet_b0_best.pth\",\n    \"DenseNet121\": \"/kaggle/working/netrx_densenet121_best.pth\",\n    \"ConvNeXt-Tiny\": \"/kaggle/working/netrx_convnext_tiny_best.pth\",\n    \"ViT-B/16\": \"/kaggle/working/netrx_vit_b16_best.pth\"\n}","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T16:01:08.822419Z","iopub.execute_input":"2026-09-07T16:01:08.823162Z","iopub.status.idle":"2026-09-07T16:01:08.827134Z","shell.execute_reply.started":"2026-09-07T16:01:08.823132Z","shell.execute_reply":"2026-09-07T16:01:08.826237Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"NOW TESTING PART LAST ONE WITH THE GRAD-CAM ALSO ","metadata":{}},{"cell_type":"code","source":"import ipywidgets as widgets\nfrom IPython.display import display","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T17:06:15.19608Z","iopub.execute_input":"2026-09-07T17:06:15.196862Z","iopub.status.idle":"2026-09-07T17:06:15.261134Z","shell.execute_reply.started":"2026-09-07T17:06:15.196828Z","shell.execute_reply":"2026-09-07T17:06:15.260619Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\nimport os\nimport random\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nimport seaborn as sns\n\nfrom PIL import Image\n\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nfrom torch.utils.data import Dataset, DataLoader\nfrom torchvision import transforms\n\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.metrics import (\n    accuracy_score,\n    precision_score,\n    recall_score,\n    f1_score,\n    confusion_matrix,\n    classification_report\n)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T17:07:49.881481Z","iopub.execute_input":"2026-09-07T17:07:49.88197Z","iopub.status.idle":"2026-09-07T17:08:05.067522Z","shell.execute_reply.started":"2026-09-07T17:07:49.881909Z","shell.execute_reply":"2026-09-07T17:08:05.066473Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 1 — NETRX COMPLETE IMPORTS\n# ============================================================\n\nimport os\nimport cv2\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\n\nfrom PIL import Image\nfrom IPython.display import display\n\nimport ipywidgets as widgets\n\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom torchvision import transforms, models\n\nprint(\"✅ NetrX imports loaded successfully\")\nprint(\"PyTorch:\", torch.__version__)\nprint(\"CUDA available:\", torch.cuda.is_available())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T17:08:48.971598Z","iopub.execute_input":"2026-09-07T17:08:48.971925Z","iopub.status.idle":"2026-09-07T17:08:49.584533Z","shell.execute_reply.started":"2026-09-07T17:08:48.9719Z","shell.execute_reply":"2026-09-07T17:08:49.583783Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 2 — DEVICE\n# ============================================================\n\ndevice = torch.device(\n    \"cuda\" if torch.cuda.is_available() else \"cpu\"\n)\n\nprint(\"✅ Device:\", device)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T17:09:24.737025Z","iopub.execute_input":"2026-09-07T17:09:24.737626Z","iopub.status.idle":"2026-09-07T17:09:24.743282Z","shell.execute_reply.started":"2026-09-07T17:09:24.737568Z","shell.execute_reply":"2026-09-07T17:09:24.742425Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 1 — NETRX IMAGE PROCESSING IMPORTS\n# ============================================================\n\nimport cv2\nimport numpy as np\nimport matplotlib.pyplot as plt\n\nfrom PIL import Image\nimport ipywidgets as widgets\nfrom IPython.display import display\n\nprint(\"✅ Imports ready\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T17:16:56.642186Z","iopub.execute_input":"2026-09-07T17:16:56.642953Z","iopub.status.idle":"2026-09-07T17:16:56.648273Z","shell.execute_reply.started":"2026-09-07T17:16:56.642918Z","shell.execute_reply":"2026-09-07T17:16:56.64727Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 2 — USER FUNDUS IMAGE UPLOAD\n# ============================================================\n\nupload = widgets.FileUpload(\n    accept=\".png,.jpg,.jpeg\",\n    multiple=False\n)\n\ndisplay(upload)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T17:17:07.406345Z","iopub.execute_input":"2026-09-07T17:17:07.406985Z","iopub.status.idle":"2026-09-07T17:17:07.41584Z","shell.execute_reply.started":"2026-09-07T17:17:07.406953Z","shell.execute_reply":"2026-09-07T17:17:07.415231Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 3 — READ USER IMAGE\n# ============================================================\n\nuploaded_file = upload.value[0]\n\nimage_bytes = uploaded_file[\"content\"]\n\nimage_array = np.frombuffer(\n    image_bytes,\n    np.uint8\n)\n\nuploaded_bgr = cv2.imdecode(\n    image_array,\n    cv2.IMREAD_COLOR\n)\n\nuploaded_rgb = cv2.cvtColor(\n    uploaded_bgr,\n    cv2.COLOR_BGR2RGB\n)\n\nuploaded_pil = Image.fromarray(\n    uploaded_rgb\n)\n\nprint(\"✅ Image loaded\")\nprint(\"Size:\", uploaded_pil.size)\n\nplt.figure(figsize=(7,7))\nplt.imshow(uploaded_rgb)\nplt.title(\"ORIGINAL USER FUNDUS IMAGE\")\nplt.axis(\"off\")\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T17:17:53.677759Z","iopub.execute_input":"2026-09-07T17:17:53.678039Z","iopub.status.idle":"2026-09-07T17:17:54.709123Z","shell.execute_reply.started":"2026-09-07T17:17:53.678015Z","shell.execute_reply":"2026-09-07T17:17:54.708155Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 4 — NETRX PREPROCESSING\n# ============================================================\n\n# Work on a copy of the uploaded image\nprocessed_bgr = uploaded_bgr.copy()\n\n# ---- Noise reduction ----\nprocessed_bgr = cv2.GaussianBlur(\n    processed_bgr,\n    (3, 3),\n    0\n)\n\n# ---- Illumination / contrast enhancement ----\nlab = cv2.cvtColor(\n    processed_bgr,\n    cv2.COLOR_BGR2LAB\n)\n\nl_channel, a_channel, b_channel = cv2.split(lab)\n\nclahe = cv2.createCLAHE(\n    clipLimit=2.0,\n    tileGridSize=(8, 8)\n)\n\nl_channel = clahe.apply(l_channel)\n\nprocessed_lab = cv2.merge(\n    [l_channel, a_channel, b_channel]\n)\n\nprocessed_bgr = cv2.cvtColor(\n    processed_lab,\n    cv2.COLOR_LAB2BGR\n)\n\n# ---- Mild sharpening ----\ngaussian = cv2.GaussianBlur(\n    processed_bgr,\n    (0, 0),\n    3\n)\n\nprocessed_bgr = cv2.addWeighted(\n    processed_bgr,\n    1.5,\n    gaussian,\n    -0.5,\n    0\n)\n\n# Convert to RGB for display/model pipeline\nprocessed_rgb = cv2.cvtColor(\n    processed_bgr,\n    cv2.COLOR_BGR2RGB\n)\n\nprocessed_image = Image.fromarray(\n    processed_rgb\n)\n\nprint(\"✅ Preprocessing completed\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T17:18:16.941589Z","iopub.execute_input":"2026-09-07T17:18:16.942028Z","iopub.status.idle":"2026-09-07T17:18:17.427962Z","shell.execute_reply.started":"2026-09-07T17:18:16.941999Z","shell.execute_reply":"2026-09-07T17:18:17.427238Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 5 — ORIGINAL vs PROCESSED\n# ============================================================\n\nplt.figure(figsize=(14, 6))\n\nplt.subplot(1, 2, 1)\nplt.imshow(uploaded_rgb)\nplt.title(\"ORIGINAL USER IMAGE\", fontsize=14)\nplt.axis(\"off\")\n\nplt.subplot(1, 2, 2)\nplt.imshow(processed_rgb)\nplt.title(\"NETRX PROCESSED IMAGE\", fontsize=14)\nplt.axis(\"off\")\n\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T17:18:29.33211Z","iopub.execute_input":"2026-09-07T17:18:29.332998Z","iopub.status.idle":"2026-09-07T17:18:31.200928Z","shell.execute_reply.started":"2026-09-07T17:18:29.332965Z","shell.execute_reply":"2026-09-07T17:18:31.199735Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 6 — IMAGE QUALITY FUNCTIONS\n# ============================================================\n\ndef focus_score(image):\n\n    gray = cv2.cvtColor(\n        image,\n        cv2.COLOR_BGR2GRAY\n    )\n\n    laplacian = cv2.Laplacian(\n        gray,\n        cv2.CV_64F\n    )\n\n    return float(laplacian.var())\n\n\ndef illumination_score(image):\n\n    gray = cv2.cvtColor(\n        image,\n        cv2.COLOR_BGR2GRAY\n    )\n\n    mean_intensity = float(\n        np.mean(gray)\n    )\n\n    ideal_low = 70\n    ideal_high = 180\n\n    if ideal_low <= mean_intensity <= ideal_high:\n        score = 100.0\n\n    elif mean_intensity < ideal_low:\n        score = (\n            mean_intensity /\n            ideal_low\n        ) * 100\n\n    else:\n        score = (\n            (255 - mean_intensity) /\n            (255 - ideal_high)\n        ) * 100\n\n    return float(np.clip(score, 0, 100))\n\n\ndef fov_score(image):\n\n    gray = cv2.cvtColor(\n        image,\n        cv2.COLOR_BGR2GRAY\n    )\n\n    blurred = cv2.GaussianBlur(\n        gray,\n        (9, 9),\n        0\n    )\n\n    _, binary = cv2.threshold(\n        blurred,\n        15,\n        255,\n        cv2.THRESH_BINARY\n    )\n\n    contours, _ = cv2.findContours(\n        binary,\n        cv2.RETR_EXTERNAL,\n        cv2.CHAIN_APPROX_SIMPLE\n    )\n\n    if len(contours) == 0:\n        return 0.0\n\n    largest = max(\n        contours,\n        key=cv2.contourArea\n    )\n\n    retinal_area = cv2.contourArea(\n        largest\n    )\n\n    image_area = (\n        image.shape[0] *\n        image.shape[1]\n    )\n\n    return float(\n        np.clip(\n            (retinal_area / image_area) * 100,\n            0,\n            100\n        )\n    )","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T17:18:56.521974Z","iopub.execute_input":"2026-09-07T17:18:56.522785Z","iopub.status.idle":"2026-09-07T17:18:56.530764Z","shell.execute_reply.started":"2026-09-07T17:18:56.522751Z","shell.execute_reply":"2026-09-07T17:18:56.529864Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 7 — NETRX BEFORE / AFTER QUALITY REPORT\n# ============================================================\n\n# ORIGINAL\nfocus_before = focus_score(uploaded_bgr)\nillumination_before = illumination_score(uploaded_bgr)\nfov_before = fov_score(uploaded_bgr)\n\n# PROCESSED\nfocus_after = focus_score(processed_bgr)\nillumination_after = illumination_score(processed_bgr)\nfov_after = fov_score(processed_bgr)\n\n# Focus normalization for overall score\nfocus_before_norm = np.clip(\n    (focus_before / 60) * 100,\n    0,\n    100\n)\n\nfocus_after_norm = np.clip(\n    (focus_after / 60) * 100,\n    0,\n    100\n)\n\noverall_before = (\n    0.40 * focus_before_norm +\n    0.30 * illumination_before +\n    0.30 * fov_before\n)\n\noverall_after = (\n    0.40 * focus_after_norm +\n    0.30 * illumination_after +\n    0.30 * fov_after\n)\n\nprint(\"=\" * 70)\nprint(\"             NETRX IMAGE QUALITY REPORT\")\nprint(\"=\" * 70)\n\nprint(f\"{'Parameter':<20} {'BEFORE':>12} {'AFTER':>12}\")\nprint(\"-\" * 70)\n\nprint(\n    f\"{'Focus / Sharpness':<20}\"\n    f\"{focus_before:>12.2f}\"\n    f\"{focus_after:>12.2f}\"\n)\n\nprint(\n    f\"{'Illumination':<20}\"\n    f\"{illumination_before:>12.2f}\"\n    f\"{illumination_after:>12.2f}\"\n)\n\nprint(\n    f\"{'FOV':<20}\"\n    f\"{fov_before:>12.2f}\"\n    f\"{fov_after:>12.2f}\"\n)\n\nprint(\n    f\"{'Overall Quality':<20}\"\n    f\"{overall_before:>12.2f}\"\n    f\"{overall_after:>12.2f}\"\n)\n\nprint(\"=\" * 70)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T17:19:10.321963Z","iopub.execute_input":"2026-09-07T17:19:10.322668Z","iopub.status.idle":"2026-09-07T17:19:10.600485Z","shell.execute_reply.started":"2026-09-07T17:19:10.322633Z","shell.execute_reply":"2026-09-07T17:19:10.599641Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 8 — NETRX QUALITY GATE\n# ============================================================\n\nissues = []\n\nif focus_after < 8:\n    issues.append(\"Severe image blur\")\nelif focus_after < 15:\n    issues.append(\"Low image sharpness\")\n\nif illumination_after < 60:\n    issues.append(\"Poor illumination\")\nelif illumination_after < 80:\n    issues.append(\"Suboptimal illumination\")\n\nif fov_after < 50:\n    issues.append(\"Insufficient retinal field\")\nelif fov_after < 70:\n    issues.append(\"Limited retinal field\")\n\n\nif (\n    focus_after < 8\n    or\n    fov_after < 50\n):\n\n    final_status = \"RETAKE REQUIRED\"\n    action = \"Retake fundus image\"\n\nelif len(issues) > 0:\n\n    final_status = \"ENHANCEMENT / REVIEW\"\n    action = \"Image processed; review quality before screening\"\n\nelse:\n\n    final_status = \"ACCEPTED\"\n    action = \"Proceed to DR model\"\n\n\nprint(\"=\" * 60)\nprint(\"             NETRX QUALITY DECISION\")\nprint(\"=\" * 60)\n\nprint(\"Status :\", final_status)\nprint(\"Action :\", action)\n\nif issues:\n\n    print(\"\\nIssues:\")\n\n    for issue in issues:\n        print(\" •\", issue)\n\nelse:\n\n    print(\"\\nIssues: None\")\n\nprint(\"=\" * 60)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T17:19:22.197589Z","iopub.execute_input":"2026-09-07T17:19:22.197934Z","iopub.status.idle":"2026-09-07T17:19:22.205044Z","shell.execute_reply.started":"2026-09-07T17:19:22.197907Z","shell.execute_reply":"2026-09-07T17:19:22.204288Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 9 — NETRX FINAL QUALITY GATE\n# ============================================================\n\nissues_after = []\n\n# Focus\nif focus_after < 8:\n    issues_after.append(\"Severe image blur\")\nelif focus_after < 15:\n    issues_after.append(\"Low image sharpness\")\n\n# Illumination\nif illumination_after < 60:\n    issues_after.append(\"Poor illumination\")\nelif illumination_after < 80:\n    issues_after.append(\"Suboptimal illumination\")\n\n# FOV\nif fov_after < 50:\n    issues_after.append(\"Insufficient retinal field\")\nelif fov_after < 70:\n    issues_after.append(\"Limited retinal field\")\n\n\n# Final decision\nif focus_after < 8 or fov_after < 50:\n\n    netrx_status = \"RETAKE REQUIRED\"\n    model_input_image = None\n\nelif len(issues_after) > 0:\n\n    netrx_status = \"REVIEW REQUIRED\"\n    model_input_image = processed_image\n\nelse:\n\n    netrx_status = \"ACCEPTED\"\n    model_input_image = processed_image\n\n\nprint(\"=\" * 70)\nprint(\"                 NETRX FINAL QUALITY GATE\")\nprint(\"=\" * 70)\n\nprint(f\"Focus       : {focus_after:.2f}\")\nprint(f\"Illumination: {illumination_after:.2f}\")\nprint(f\"FOV         : {fov_after:.2f}\")\nprint(f\"Overall     : {overall_after:.2f}\")\n\nprint(\"-\" * 70)\n\nprint(\"STATUS :\", netrx_status)\n\nif issues_after:\n    print(\"\\nIssues:\")\n    for issue in issues_after:\n        print(\" •\", issue)\nelse:\n    print(\"\\nIssues: None\")\n\nprint(\"=\" * 70)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T17:20:39.581476Z","iopub.execute_input":"2026-09-07T17:20:39.581801Z","iopub.status.idle":"2026-09-07T17:20:39.591551Z","shell.execute_reply.started":"2026-09-07T17:20:39.581775Z","shell.execute_reply":"2026-09-07T17:20:39.590569Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# NETRX MODEL ROUTING\n# ============================================================\n\nif netrx_status == \"RETAKE REQUIRED\":\n\n    model_input_image = None\n\n    print(\"❌ IMAGE STOPPED\")\n    print(\"Please retake the fundus image.\")\n\nelse:\n\n    model_input_image = processed_image\n\n    print(\"✅ IMAGE WILL BE SENT TO THE MODEL\")\n    print(\"Using the NetrX preprocessed image.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T17:21:52.904744Z","iopub.execute_input":"2026-09-07T17:21:52.905021Z","iopub.status.idle":"2026-09-07T17:21:52.910646Z","shell.execute_reply.started":"2026-09-07T17:21:52.904999Z","shell.execute_reply":"2026-09-07T17:21:52.909615Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 10 — CHECK SAVED MODEL CHECKPOINTS\n# ============================================================\n\nimport torch\nimport os\n\nmodel_paths = {\n    \"CNN\": \"/kaggle/working/netrx_cnn_best.pth\",\n    \"ResNet50\": \"/kaggle/working/netrx_resnet50_best.pth\",\n    \"EfficientNet-B0\": \"/kaggle/working/netrx_efficientnet_b0_best.pth\",\n    \"DenseNet121\": \"/kaggle/working/netrx_densenet121_best.pth\",\n    \"ConvNeXt-Tiny\": \"/kaggle/working/netrx_convnext_tiny_best.pth\",\n    \"ViT-B/16\": \"/kaggle/working/netrx_vit_b16_best.pth\"\n}\n\nprint(\"=\" * 65)\nprint(\"             NETRX SAVED MODEL CHECK\")\nprint(\"=\" * 65)\n\nfor name, path in model_paths.items():\n\n    exists = os.path.exists(path)\n\n    size_mb = (\n        os.path.getsize(path) / (1024 * 1024)\n        if exists else 0\n    )\n\n    print(\n        f\"{name:<20} \"\n        f\"{'✅ FOUND' if exists else '❌ MISSING':<12} \"\n        f\"{size_mb:.2f} MB\"\n    )\n\nprint(\"=\" * 65)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T17:23:16.691779Z","iopub.execute_input":"2026-09-07T17:23:16.692479Z","iopub.status.idle":"2026-09-07T17:23:16.698615Z","shell.execute_reply.started":"2026-09-07T17:23:16.692448Z","shell.execute_reply":"2026-09-07T17:23:16.698033Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 10 — FIND SAVED NETRX MODEL FILES\n# ============================================================\n\nimport os\n\ntarget_files = [\n    \"netrx_cnn_best.pth\",\n    \"netrx_resnet50_best.pth\",\n    \"netrx_efficientnet_b0_best.pth\",\n    \"netrx_densenet121_best.pth\",\n    \"netrx_convnext_tiny_best.pth\",\n    \"netrx_vit_b16_best.pth\",\n    \"netrx_inference_bundle.pth\",\n    \"netrx_final_package.zip\"\n]\n\nfound_files = {}\n\nfor root, dirs, files in os.walk(\"/kaggle\"):\n\n    for file in files:\n\n        if file in target_files:\n\n            full_path = os.path.join(root, file)\n\n            found_files[file] = full_path\n\n\nprint(\"=\" * 70)\nprint(\"             NETRX MODEL FILE SEARCH\")\nprint(\"=\" * 70)\n\nif found_files:\n\n    for name, path in found_files.items():\n        size_mb = os.path.getsize(path) / (1024 * 1024)\n        print(f\"✅ {name}\")\n        print(f\"   {path}\")\n        print(f\"   Size: {size_mb:.2f} MB\")\n        print()\n\nelse:\n\n    print(\"❌ No saved model files found inside /kaggle\")\n\nprint(\"=\" * 70)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T17:24:10.303552Z","iopub.execute_input":"2026-09-07T17:24:10.304254Z","iopub.status.idle":"2026-09-07T17:24:18.283651Z","shell.execute_reply.started":"2026-09-07T17:24:10.304223Z","shell.execute_reply":"2026-09-07T17:24:18.28278Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# FIND UPLOADED NETRX MODELS\n# ============================================================\n\nimport os\n\nfor root, dirs, files in os.walk(\"/kaggle/input\"):\n    for file in files:\n        if file.endswith(\".pth\"):\n            print(os.path.join(root, file))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T17:43:17.987664Z","iopub.execute_input":"2026-09-07T17:43:17.988378Z","iopub.status.idle":"2026-09-07T17:43:20.288847Z","shell.execute_reply.started":"2026-09-07T17:43:17.988345Z","shell.execute_reply":"2026-09-07T17:43:20.288137Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 11 — NETRX MODEL PATHS\n# ============================================================\n\nmodel_paths = {\n    \"CNN\": \"/kaggle/input/datasets/pruthvirajkokate/one-test/netrx_cnn_best.pth\",\n\n    \"ResNet50\": \"/kaggle/input/datasets/pruthvirajkokate/one-test/netrx_resnet50_best.pth\",\n\n    \"EfficientNet-B0\": \"/kaggle/input/datasets/pruthvirajkokate/one-test/netrx_efficientnet_b0_best-2.pth\",\n\n    \"DenseNet121\": \"/kaggle/input/datasets/pruthvirajkokate/one-test/netrx_densenet121_best-2.pth\",\n\n    \"ConvNeXt-Tiny\": \"/kaggle/input/datasets/pruthvirajkokate/one-test/netrx_convnext_tiny_best-2.pth\",\n\n    \"ViT-B/16\": \"/kaggle/input/datasets/pruthvirajkokate/one-test/netrx_vit_b16_best.pth\"\n}\n\nprint(\"✅ All 6 model paths configured\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T17:44:15.296877Z","iopub.execute_input":"2026-09-07T17:44:15.297144Z","iopub.status.idle":"2026-09-07T17:44:15.302093Z","shell.execute_reply.started":"2026-09-07T17:44:15.297121Z","shell.execute_reply":"2026-09-07T17:44:15.301231Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 12 — VERIFY MODEL FILES\n# ============================================================\n\nimport os\n\nfor name, path in model_paths.items():\n\n    if os.path.exists(path):\n\n        size_mb = os.path.getsize(path) / (1024 * 1024)\n\n        print(\n            f\"✅ {name:<20} {size_mb:>8.2f} MB\"\n        )\n\n    else:\n\n        print(\n            f\"❌ {name:<20} NOT FOUND\"\n        )","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T17:44:27.051802Z","iopub.execute_input":"2026-09-07T17:44:27.05206Z","iopub.status.idle":"2026-09-07T17:44:27.065039Z","shell.execute_reply.started":"2026-09-07T17:44:27.05204Z","shell.execute_reply":"2026-09-07T17:44:27.064167Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 13 — INSPECT SAVED CHECKPOINTS\n# ============================================================\n\nimport torch\n\nfor name, path in model_paths.items():\n\n    checkpoint = torch.load(\n        path,\n        map_location=\"cpu\",\n        weights_only=False\n    )\n\n    print(\"\\n\" + \"=\" * 65)\n    print(name)\n    print(\"=\" * 65)\n\n    print(\"Type:\", type(checkpoint))\n\n    if isinstance(checkpoint, dict):\n\n        print(\"Keys:\")\n\n        for key in checkpoint.keys():\n\n            value = checkpoint[key]\n\n            print(\n                f\"  {key} : \"\n                f\"{type(value)}\"\n            )\n\n    else:\n\n        print(\"Saved object:\", type(checkpoint))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T17:44:41.762013Z","iopub.execute_input":"2026-09-07T17:44:41.762674Z","iopub.status.idle":"2026-09-07T17:44:48.426472Z","shell.execute_reply.started":"2026-09-07T17:44:41.762625Z","shell.execute_reply":"2026-09-07T17:44:48.425769Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 14 — NETRX MODEL ARCHITECTURE CHECK\n# ============================================================\n\nimport torch\nimport os\n\nfor name, path in model_paths.items():\n\n    print(\"\\n\" + \"=\" * 70)\n    print(name)\n    print(\"=\" * 70)\n\n    checkpoint = torch.load(\n        path,\n        map_location=\"cpu\",\n        weights_only=False\n    )\n\n    # Get state dictionary\n    if isinstance(checkpoint, dict):\n\n        if \"model_state_dict\" in checkpoint:\n            state = checkpoint[\"model_state_dict\"]\n\n        elif \"state_dict\" in checkpoint:\n            state = checkpoint[\"state_dict\"]\n\n        else:\n            state = checkpoint\n\n    else:\n        state = checkpoint.state_dict()\n\n    print(\"Total parameters:\",\n          sum(v.numel() for v in state.values()\n              if torch.is_tensor(v)))\n\n    print(\"\\nImportant layers:\")\n\n    for key, value in state.items():\n\n        if any(x in key.lower() for x in [\n            \"fc.\",\n            \"classifier\",\n            \"head\",\n            \"heads\",\n            \"output\",\n            \"linear\"\n        ]):\n\n            if torch.is_tensor(value):\n\n                print(\n                    f\"{key:<45} \"\n                    f\"{tuple(value.shape)}\"\n                )","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T17:48:33.763152Z","iopub.execute_input":"2026-09-07T17:48:33.763964Z","iopub.status.idle":"2026-09-07T17:48:34.222351Z","shell.execute_reply.started":"2026-09-07T17:48:33.76393Z","shell.execute_reply":"2026-09-07T17:48:34.221518Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 14 — SHOW ONLY CLASSIFIER SHAPES\n# ============================================================\n\nfor name, path in model_paths.items():\n\n    ckpt = torch.load(\n        path,\n        map_location=\"cpu\",\n        weights_only=False\n    )\n\n    if isinstance(ckpt, dict):\n        state = ckpt.get(\n            \"model_state_dict\",\n            ckpt.get(\"state_dict\", ckpt)\n        )\n    else:\n        state = ckpt.state_dict()\n\n    print(\"\\n\" + \"=\" * 50)\n    print(name)\n\n    for k, v in state.items():\n\n        if torch.is_tensor(v) and (\n            \"classifier\" in k.lower()\n            or \"fc.\" in k.lower()\n            or \"heads.head\" in k.lower()\n        ):\n            print(k, tuple(v.shape))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T17:59:30.682983Z","iopub.execute_input":"2026-09-07T17:59:30.683735Z","iopub.status.idle":"2026-09-07T17:59:31.078071Z","shell.execute_reply.started":"2026-09-07T17:59:30.683687Z","shell.execute_reply":"2026-09-07T17:59:31.077492Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 15 — CREATE EXACT NETRX MODEL ARCHITECTURES\n# ============================================================\n\nimport torch\nimport torch.nn as nn\nfrom torchvision import models\n\nNUM_CLASSES = 5\n\n# ------------------------------------------------------------\n# 1. CNN\n# ------------------------------------------------------------\n\nclass NetrXCNN(nn.Module):\n\n    def __init__(self, num_classes=5):\n\n        super().__init__()\n\n        self.features = nn.Sequential(\n            nn.Conv2d(3, 32, 3, padding=1),\n            nn.ReLU(),\n            nn.MaxPool2d(2),\n\n            nn.Conv2d(32, 64, 3, padding=1),\n            nn.ReLU(),\n            nn.MaxPool2d(2),\n\n            nn.Conv2d(64, 128, 3, padding=1),\n            nn.ReLU(),\n            nn.MaxPool2d(2),\n\n            nn.Conv2d(128, 256, 3, padding=1),\n            nn.ReLU(),\n\n            nn.AdaptiveAvgPool2d((1, 1))\n        )\n\n        self.classifier = nn.Sequential(\n            nn.Flatten(),\n            nn.Linear(256, 128),\n            nn.ReLU(),\n            nn.Dropout(0.4),\n            nn.Linear(128, 5)\n        )\n\n    def forward(self, x):\n\n        x = self.features(x)\n        x = self.classifier(x)\n\n        return x\n\n\n# ------------------------------------------------------------\n# 2. ResNet50\n# ------------------------------------------------------------\n\nresnet_model = models.resnet50(\n    weights=None\n)\n\nresnet_model.fc = nn.Sequential(\n    nn.Dropout(0.4),\n    nn.Linear(2048, 256),\n    nn.ReLU(),\n    nn.Dropout(0.3),\n    nn.Linear(256, 5)\n)\n\n\n# ------------------------------------------------------------\n# 3. EfficientNet-B0\n# ------------------------------------------------------------\n\nefficientnet_model = models.efficientnet_b0(\n    weights=None\n)\n\nefficientnet_model.classifier = nn.Sequential(\n    nn.Dropout(0.4),\n    nn.Linear(1280, 256),\n    nn.ReLU(),\n    nn.Dropout(0.3),\n    nn.Linear(256, 5)\n)\n\n\n# ------------------------------------------------------------\n# 4. DenseNet121\n# ------------------------------------------------------------\n\ndensenet_model = models.densenet121(\n    weights=None\n)\n\ndensenet_model.classifier = nn.Sequential(\n    nn.Dropout(0.4),\n    nn.Linear(1024, 256),\n    nn.ReLU(),\n    nn.Dropout(0.3),\n    nn.Linear(256, 5)\n)\n\n\n# ------------------------------------------------------------\n# 5. ConvNeXt-Tiny\n# ------------------------------------------------------------\n\nconvnext_model = models.convnext_tiny(\n    weights=None\n)\n\nconvnext_model.classifier = nn.Sequential(\n    nn.LayerNorm(768),\n    nn.Flatten(start_dim=1),\n    nn.Linear(768, 256),\n    nn.GELU(),\n    nn.Dropout(0.3),\n    nn.Linear(256, 5)\n)\n\n\n# ------------------------------------------------------------\n# 6. ViT-B/16\n# ------------------------------------------------------------\n\nvit_model = models.vit_b_16(\n    weights=None\n)\n\nvit_model.heads = nn.Sequential(\n    nn.Linear(768, 256),\n    nn.ReLU(),\n    nn.Dropout(0.3),\n    nn.Linear(256, 5)\n)\n\n\nprint(\"✅ Six exact model architectures created.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T18:00:06.849522Z","iopub.execute_input":"2026-09-07T18:00:06.849815Z","iopub.status.idle":"2026-09-07T18:00:08.762587Z","shell.execute_reply.started":"2026-09-07T18:00:06.849791Z","shell.execute_reply":"2026-09-07T18:00:08.761857Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 15 — NETRX EXACT MODEL ARCHITECTURES\n# ============================================================\n\nimport torch\nimport torch.nn as nn\nfrom torchvision import models\n\nNUM_CLASSES = 5\n\n\n# ============================================================\n# CNN\n# ============================================================\n\nclass NetrXCNN(nn.Module):\n\n    def __init__(self, num_classes=5):\n\n        super().__init__()\n\n        self.features = nn.Sequential(\n            nn.Conv2d(3, 32, 3, padding=1),\n            nn.ReLU(),\n            nn.MaxPool2d(2),\n\n            nn.Conv2d(32, 64, 3, padding=1),\n            nn.ReLU(),\n            nn.MaxPool2d(2),\n\n            nn.Conv2d(64, 128, 3, padding=1),\n            nn.ReLU(),\n            nn.MaxPool2d(2),\n\n            nn.Conv2d(128, 256, 3, padding=1),\n            nn.ReLU(),\n\n            nn.AdaptiveAvgPool2d((1, 1))\n        )\n\n        self.classifier = nn.Sequential(\n            nn.Flatten(),\n            nn.Linear(256, 128),\n            nn.ReLU(),\n            nn.Dropout(0.4),\n            nn.Linear(128, 5)\n        )\n\n    def forward(self, x):\n\n        x = self.features(x)\n        return self.classifier(x)\n\n\ncnn_model = NetrXCNN(NUM_CLASSES)\n\n\n# ============================================================\n# RESNET50\n# ============================================================\n\nresnet_model = models.resnet50(weights=None)\n\nresnet_model.fc = nn.Sequential(\n    nn.Dropout(0.4),\n    nn.Linear(2048, 256),\n    nn.ReLU(),\n    nn.Dropout(0.3),\n    nn.Linear(256, 5)\n)\n\n\n# ============================================================\n# EFFICIENTNET-B0\n# ============================================================\n\nefficientnet_model = models.efficientnet_b0(weights=None)\n\nefficientnet_model.classifier = nn.Sequential(\n    nn.Dropout(0.4),\n    nn.Linear(1280, 256),\n    nn.ReLU(),\n    nn.Dropout(0.3),\n    nn.Linear(256, 5)\n)\n\n\n# ============================================================\n# DENSENET121\n# ============================================================\n\ndensenet_model = models.densenet121(weights=None)\n\ndensenet_model.classifier = nn.Sequential(\n    nn.Dropout(0.4),\n    nn.Linear(1024, 256),\n    nn.ReLU(),\n    nn.Dropout(0.3),\n    nn.Linear(256, 5)\n)\n\n\n# ============================================================\n# CONVNEXT-TINY — CORRECT CHECKPOINT STRUCTURE\n# ============================================================\n\nconvnext_model = models.convnext_tiny(weights=None)\n\nconvnext_model.classifier = nn.Sequential(\n    nn.LayerNorm(768),          # classifier.0\n    nn.Flatten(start_dim=1),    # classifier.1\n    nn.Linear(768, 256),        # classifier.2\n    nn.GELU(),                  # classifier.3\n    nn.Dropout(0.3),            # classifier.4\n    nn.Linear(256, 5)           # classifier.5\n)\n\n\n# ============================================================\n# VIT-B/16 — CORRECT CHECKPOINT STRUCTURE\n# ============================================================\n\nvit_model = models.vit_b_16(weights=None)\n\nvit_model.heads = nn.Sequential(\n    nn.Dropout(0.3),            # heads.head.0\n    nn.Linear(768, 256),        # heads.head.1\n    nn.ReLU(),                  # heads.head.2\n    nn.Dropout(0.3),            # heads.head.3\n    nn.Linear(256, 5)            # heads.head.4\n)\n\n\nprint(\"✅ All six model architectures created.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T18:03:04.006987Z","iopub.execute_input":"2026-09-07T18:03:04.007723Z","iopub.status.idle":"2026-09-07T18:03:05.84434Z","shell.execute_reply.started":"2026-09-07T18:03:04.007675Z","shell.execute_reply":"2026-09-07T18:03:05.843583Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 16 — EXACT CNN CHECKPOINT STRUCTURE\n# ============================================================\n\ncnn_checkpoint = torch.load(\n    model_paths[\"CNN\"],\n    map_location=\"cpu\",\n    weights_only=False\n)\n\nif isinstance(cnn_checkpoint, dict):\n\n    cnn_state = cnn_checkpoint.get(\n        \"model_state_dict\",\n        cnn_checkpoint.get(\n            \"state_dict\",\n            cnn_checkpoint\n        )\n    )\n\nelse:\n\n    cnn_state = cnn_checkpoint.state_dict()\n\n\nprint(\"=\" * 70)\nprint(\"             NETRX CNN CHECKPOINT\")\nprint(\"=\" * 70)\n\nfor key, value in cnn_state.items():\n\n    if torch.is_tensor(value):\n\n        print(\n            f\"{key:<45} \"\n            f\"{tuple(value.shape)}\"\n        )\n\nprint(\"=\" * 70)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T18:04:05.616358Z","iopub.execute_input":"2026-09-07T18:04:05.617201Z","iopub.status.idle":"2026-09-07T18:04:05.630269Z","shell.execute_reply.started":"2026-09-07T18:04:05.61717Z","shell.execute_reply":"2026-09-07T18:04:05.629521Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 15 — EXACT NETRX CNN ARCHITECTURE\n# ============================================================\n\nclass NetrXCNN(nn.Module):\n\n    def __init__(self, num_classes=5):\n\n        super().__init__()\n\n        self.features = nn.Sequential(\n\n            # -------- BLOCK 1 --------\n            nn.Conv2d(\n                3, 32,\n                kernel_size=3,\n                padding=1\n            ),                          # 0\n\n            nn.BatchNorm2d(32),         # 1\n            nn.ReLU(inplace=True),      # 2\n\n            nn.Conv2d(\n                32, 32,\n                kernel_size=3,\n                padding=1\n            ),                          # 3\n\n            nn.BatchNorm2d(32),         # 4\n            nn.ReLU(inplace=True),      # 5\n\n            nn.MaxPool2d(2),            # 6\n            nn.Dropout(0.25),           # 7\n\n\n            # -------- BLOCK 2 --------\n            nn.Conv2d(\n                32, 64,\n                kernel_size=3,\n                padding=1\n            ),                          # 8\n\n            nn.BatchNorm2d(64),         # 9\n            nn.ReLU(inplace=True),      # 10\n\n            nn.Conv2d(\n                64, 64,\n                kernel_size=3,\n                padding=1\n            ),                          # 11\n\n            nn.BatchNorm2d(64),         # 12\n            nn.ReLU(inplace=True),      # 13\n\n            nn.MaxPool2d(2),            # 14\n            nn.Dropout(0.25),           # 15\n\n\n            # -------- BLOCK 3 --------\n            nn.Conv2d(\n                64, 128,\n                kernel_size=3,\n                padding=1\n            ),                          # 16\n\n            nn.BatchNorm2d(128),        # 17\n            nn.ReLU(inplace=True),      # 18\n\n            nn.Conv2d(\n                128, 128,\n                kernel_size=3,\n                padding=1\n            ),                          # 19\n\n            nn.BatchNorm2d(128),        # 20\n            nn.ReLU(inplace=True),      # 21\n\n            nn.MaxPool2d(2),            # 22\n            nn.Dropout(0.25),           # 23\n\n\n            # -------- BLOCK 4 --------\n            nn.Conv2d(\n                128, 256,\n                kernel_size=3,\n                padding=1\n            ),                          # 24\n\n            nn.BatchNorm2d(256),        # 25\n            nn.ReLU(inplace=True),      # 26\n\n            nn.AdaptiveAvgPool2d(\n                (1, 1)\n            )                           # 27\n        )\n\n\n        self.classifier = nn.Sequential(\n\n            nn.Flatten(),               # 0\n\n            nn.Linear(\n                256, 128\n            ),                          # 1\n\n            nn.ReLU(inplace=True),      # 2\n\n            nn.Dropout(0.4),            # 3\n\n            nn.Linear(\n                128, num_classes\n            )                           # 4\n        )\n\n\n    def forward(self, x):\n\n        x = self.features(x)\n\n        x = self.classifier(x)\n\n        return x\n\n\ncnn_model = NetrXCNN(\n    num_classes=5\n)\n\nprint(\"✅ Exact CNN architecture created\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T18:07:24.172317Z","iopub.execute_input":"2026-09-07T18:07:24.172767Z","iopub.status.idle":"2026-09-07T18:07:24.190139Z","shell.execute_reply.started":"2026-09-07T18:07:24.172734Z","shell.execute_reply":"2026-09-07T18:07:24.189406Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 16 — LOAD CNN CHECKPOINT\n# ============================================================\n\ncnn_state = torch.load(\n    model_paths[\"CNN\"],\n    map_location=\"cpu\",\n    weights_only=False\n)\n\nif \"model_state_dict\" in cnn_state:\n    cnn_state = cnn_state[\"model_state_dict\"]\n\nelif \"state_dict\" in cnn_state:\n    cnn_state = cnn_state[\"state_dict\"]\n\n\ncnn_model.load_state_dict(\n    cnn_state\n)\n\ncnn_model = cnn_model.to(device)\n\ncnn_model.eval()\n\nprint(\"🔥 CNN LOADED SUCCESSFULLY\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 18 — EXACT CONVNEXT-TINY ARCHITECTURE\n# ============================================================\n\nconvnext_model = models.convnext_tiny(\n    weights=None\n)\n\n# Exact classifier indexing from saved checkpoint:\n#\n# classifier.1 -> LayerNorm(768)\n# classifier.3 -> Linear(768, 256)\n# classifier.6 -> Linear(256, 5)\n\nconvnext_model.classifier = nn.Sequential(\n    nn.Flatten(),                  # classifier.0\n    nn.LayerNorm(768),             # classifier.1\n    nn.GELU(),                     # classifier.2\n    nn.Linear(768, 256),           # classifier.3\n    nn.GELU(),                     # classifier.4\n    nn.Dropout(0.3),               # classifier.5\n    nn.Linear(256, 5)              # classifier.6\n)\n\nprint(\"✅ Exact ConvNeXt-Tiny architecture created\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T18:12:25.151672Z","iopub.execute_input":"2026-09-07T18:12:25.152441Z","iopub.status.idle":"2026-09-07T18:12:25.508481Z","shell.execute_reply.started":"2026-09-07T18:12:25.152408Z","shell.execute_reply":"2026-09-07T18:12:25.507605Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 19 — LOAD CONVNEXT-TINY\n# ============================================================\n\nconvnext_model.load_state_dict(\n    get_state_dict(\n        model_paths[\"ConvNeXt-Tiny\"]\n    )\n)\n\nconvnext_model = convnext_model.to(device)\n\nconvnext_model.eval()\n\nprint(\"🔥 ConvNeXt-Tiny loaded successfully\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T18:14:52.781562Z","iopub.execute_input":"2026-09-07T18:14:52.782071Z","iopub.status.idle":"2026-09-07T18:14:52.888208Z","shell.execute_reply.started":"2026-09-07T18:14:52.78204Z","shell.execute_reply":"2026-09-07T18:14:52.887435Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 20 — EXACT ViT-B/16 ARCHITECTURE\n# ============================================================\n\nvit_model = models.vit_b_16(\n    weights=None\n)\n\n# Keep torchvision's existing \"heads.head\"\n# and replace the INSIDE of head with the trained structure.\n\nvit_model.heads.head = nn.Sequential(\n    nn.Dropout(0.3),        # head.0\n    nn.Linear(768, 256),    # head.1\n    nn.ReLU(),              # head.2\n    nn.Dropout(0.3),        # head.3\n    nn.Linear(256, 5)       # head.4\n)\n\nprint(\"✅ Exact ViT-B/16 architecture created\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T18:17:45.77617Z","iopub.execute_input":"2026-09-07T18:17:45.776495Z","iopub.status.idle":"2026-09-07T18:17:46.674085Z","shell.execute_reply.started":"2026-09-07T18:17:45.77647Z","shell.execute_reply":"2026-09-07T18:17:46.672938Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 21 — LOAD ViT-B/16\n# ============================================================\n\nvit_state = torch.load(\n    model_paths[\"ViT-B/16\"],\n    map_location=\"cpu\",\n    weights_only=False\n)\n\nif \"model_state_dict\" in vit_state:\n    vit_state = vit_state[\"model_state_dict\"]\n\nelif \"state_dict\" in vit_state:\n    vit_state = vit_state[\"state_dict\"]\n\nvit_model.load_state_dict(\n    vit_state\n)\n\nvit_model = vit_model.to(device)\n\nvit_model.eval()\n\nprint(\"🔥 ViT-B/16 LOADED SUCCESSFULLY\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T18:18:27.861281Z","iopub.execute_input":"2026-09-07T18:18:27.862191Z","iopub.status.idle":"2026-09-07T18:18:28.231598Z","shell.execute_reply.started":"2026-09-07T18:18:27.862156Z","shell.execute_reply":"2026-09-07T18:18:28.230893Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 22 — ALL NETRX MODELS READY\n# ============================================================\n\nmodels_ready = {\n    \"CNN\": cnn_model,\n    \"ResNet50\": resnet_model,\n    \"EfficientNet-B0\": efficientnet_model,\n    \"DenseNet121\": densenet_model,\n    \"ConvNeXt-Tiny\": convnext_model,\n    \"ViT-B/16\": vit_model\n}\n\nprint(\"=\" * 65)\nprint(\"             NETRX MODEL STATUS\")\nprint(\"=\" * 65)\n\nfor name, model in models_ready.items():\n    print(f\"✅ {name}\")\n\nprint(\"=\" * 65)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T18:18:46.981178Z","iopub.execute_input":"2026-09-07T18:18:46.98196Z","iopub.status.idle":"2026-09-07T18:18:46.987158Z","shell.execute_reply.started":"2026-09-07T18:18:46.981927Z","shell.execute_reply":"2026-09-07T18:18:46.986314Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 23 — PREPARE NETRX MODEL INPUT\n# ============================================================\n\nfrom torchvision import transforms\n\nmodel_transform = transforms.Compose([\n    transforms.Resize((224, 224)),\n    transforms.ToTensor(),\n    transforms.Normalize(\n        mean=[0.485, 0.456, 0.406],\n        std=[0.229, 0.224, 0.225]\n    )\n])\n\n# model_input_image is ALREADY a PIL Image\nmodel_input_pil = model_input_image\n\ninput_tensor = model_transform(\n    model_input_pil\n).unsqueeze(0).to(device)\n\nprint(\"✅ Model input prepared\")\nprint(\"Input shape:\", input_tensor.shape)\nprint(\"Device:\", input_tensor.device)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T18:20:08.9815Z","iopub.execute_input":"2026-09-07T18:20:08.982047Z","iopub.status.idle":"2026-09-07T18:20:09.076813Z","shell.execute_reply.started":"2026-09-07T18:20:08.982013Z","shell.execute_reply":"2026-09-07T18:20:09.076057Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 24 — NETRX SIX MODEL INFERENCE\n# ============================================================\n\nimport torch\nimport torch.nn.functional as F\nimport numpy as np\n\n# Make absolutely sure ALL models are on the same device\ncnn_model = cnn_model.to(device)\nresnet_model = resnet_model.to(device)\nefficientnet_model = efficientnet_model.to(device)\ndensenet_model = densenet_model.to(device)\nconvnext_model = convnext_model.to(device)\nvit_model = vit_model.to(device)\n\n# Evaluation mode\ncnn_model.eval()\nresnet_model.eval()\nefficientnet_model.eval()\ndensenet_model.eval()\nconvnext_model.eval()\nvit_model.eval()\n\n# Make sure input is also on the same device\ninput_tensor = input_tensor.to(device)\n\nprint(\"Device:\", device)\nprint(\"Input:\", input_tensor.device)\n\nwith torch.no_grad():\n\n    cnn_logits = cnn_model(input_tensor)\n\n    resnet_logits = resnet_model(input_tensor)\n\n    efficientnet_logits = efficientnet_model(input_tensor)\n\n    densenet_logits = densenet_model(input_tensor)\n\n    convnext_logits = convnext_model(input_tensor)\n\n    vit_logits = vit_model(input_tensor)\n\n\n# ============================================================\n# LOGITS → PROBABILITIES\n# ============================================================\n\ncnn_prob = F.softmax(\n    cnn_logits, dim=1\n).cpu().numpy()[0]\n\nresnet_prob = F.softmax(\n    resnet_logits, dim=1\n).cpu().numpy()[0]\n\nefficientnet_prob = F.softmax(\n    efficientnet_logits, dim=1\n).cpu().numpy()[0]\n\ndensenet_prob = F.softmax(\n    densenet_logits, dim=1\n).cpu().numpy()[0]\n\nconvnext_prob = F.softmax(\n    convnext_logits, dim=1\n).cpu().numpy()[0]\n\nvit_prob = F.softmax(\n    vit_logits, dim=1\n).cpu().numpy()[0]\n\n\n# ============================================================\n# STORE ALL MODEL PROBABILITIES\n# ============================================================\n\nmodel_probabilities = np.array([\n    cnn_prob,\n    resnet_prob,\n    efficientnet_prob,\n    densenet_prob,\n    convnext_prob,\n    vit_prob\n])\n\nmodel_names = [\n    \"CNN\",\n    \"ResNet50\",\n    \"EfficientNet-B0\",\n    \"DenseNet121\",\n    \"ConvNeXt-Tiny\",\n    \"ViT-B/16\"\n]\n\n\n# ============================================================\n# DISPLAY RESULTS\n# ============================================================\n\nprint(\"\\n\" + \"=\" * 70)\nprint(\"             NETRX SIX MODEL PREDICTIONS\")\nprint(\"=\" * 70)\n\nfor name, probs in zip(\n    model_names,\n    model_probabilities\n):\n\n    prediction = int(np.argmax(probs))\n\n    confidence = (\n        probs[prediction] * 100\n    )\n\n    print(\n        f\"{name:<20} \"\n        f\"Grade {prediction} \"\n        f\"({confidence:.2f}%)\"\n    )\n\nprint(\"=\" * 70)\nprint(\"✅ SIX-MODEL INFERENCE COMPLETE\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T18:23:17.119507Z","iopub.execute_input":"2026-09-07T18:23:17.120407Z","iopub.status.idle":"2026-09-07T18:23:20.822821Z","shell.execute_reply.started":"2026-09-07T18:23:17.120374Z","shell.execute_reply":"2026-09-07T18:23:20.821869Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 25 — NETRX WEIGHTED ENSEMBLE\n# ============================================================\n\nensemble_weights = np.array([\n    0.0333,   # CNN\n    0.0538,   # ResNet50\n    0.1411,   # EfficientNet-B0\n    0.1196,   # DenseNet121\n    0.3172,   # ConvNeXt-Tiny\n    0.3349    # ViT-B/16\n])\n\n# Normalize weights\nensemble_weights = (\n    ensemble_weights /\n    ensemble_weights.sum()\n)\n\n# Weighted probability fusion\nensemble_probabilities = np.sum(\n    model_probabilities *\n    ensemble_weights[:, None],\n    axis=0\n)\n\n# Final DR grade\nfinal_grade = int(\n    np.argmax(ensemble_probabilities)\n)\n\n# Confidence\nfinal_confidence = (\n    ensemble_probabilities[final_grade] * 100\n)\n\nprint(\"=\" * 70)\nprint(\"              NETRX WEIGHTED ENSEMBLE\")\nprint(\"=\" * 70)\n\nfor grade, probability in enumerate(\n    ensemble_probabilities\n):\n\n    print(\n        f\"Grade {grade} : \"\n        f\"{probability * 100:.2f}%\"\n    )\n\nprint(\"-\" * 70)\n\nprint(\n    f\"FINAL DR GRADE : {final_grade}\"\n)\n\nprint(\n    f\"CONFIDENCE     : {final_confidence:.2f}%\"\n)\n\nprint(\"=\" * 70)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T18:24:39.866249Z","iopub.execute_input":"2026-09-07T18:24:39.866781Z","iopub.status.idle":"2026-09-07T18:24:39.877101Z","shell.execute_reply.started":"2026-09-07T18:24:39.86673Z","shell.execute_reply":"2026-09-07T18:24:39.875838Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 26 — NETRX REFERABLE SCREENING\n# ============================================================\n\nREFERABLE_THRESHOLD = 0.45\n\n# Grade 2 + Grade 3 + Grade 4\nreferable_probability = (\n    ensemble_probabilities[2]\n    + ensemble_probabilities[3]\n    + ensemble_probabilities[4]\n)\n\nif referable_probability >= REFERABLE_THRESHOLD:\n\n    referable_status = \"REFERABLE DR\"\n\n    referral_action = (\n        \"Refer for ophthalmic evaluation\"\n    )\n\nelse:\n\n    referable_status = \"NON-REFERABLE DR\"\n\n    referral_action = (\n        \"No referral based on NetrX screening\"\n    )\n\n\nprint(\"=\" * 70)\nprint(\"             NETRX REFERABLE SCREENING\")\nprint(\"=\" * 70)\n\nprint(\n    f\"Referable Probability : \"\n    f\"{referable_probability * 100:.2f}%\"\n)\n\nprint(\n    f\"Threshold             : \"\n    f\"{REFERABLE_THRESHOLD * 100:.0f}%\"\n)\n\nprint(\"-\" * 70)\n\nprint(\n    f\"STATUS : {referable_status}\"\n)\n\nprint(\n    f\"ACTION : {referral_action}\"\n)\n\nprint(\"=\" * 70)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T18:27:32.671491Z","iopub.execute_input":"2026-09-07T18:27:32.672447Z","iopub.status.idle":"2026-09-07T18:27:32.678862Z","shell.execute_reply.started":"2026-09-07T18:27:32.672414Z","shell.execute_reply":"2026-09-07T18:27:32.67804Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 27 — NETRX MODEL AGREEMENT\n# ============================================================\n\nindividual_predictions = np.argmax(\n    model_probabilities,\n    axis=1\n)\n\nagreement_count = np.sum(\n    individual_predictions == final_grade\n)\n\nagreement_percentage = (\n    agreement_count / 6\n) * 100\n\n\nprint(\"=\" * 70)\nprint(\"             NETRX MODEL AGREEMENT\")\nprint(\"=\" * 70)\n\nfor name, prediction in zip(\n    model_names,\n    individual_predictions\n):\n\n    print(\n        f\"{name:<20} → Grade {prediction}\"\n    )\n\nprint(\"-\" * 70)\n\nprint(\n    f\"Final Grade              : {final_grade}\"\n)\n\nprint(\n    f\"Models agreeing          : \"\n    f\"{agreement_count}/6\"\n)\n\nprint(\n    f\"Agreement percentage     : \"\n    f\"{agreement_percentage:.2f}%\"\n)\n\nprint(\"=\" * 70)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T18:28:32.486793Z","iopub.execute_input":"2026-09-07T18:28:32.487061Z","iopub.status.idle":"2026-09-07T18:28:32.493921Z","shell.execute_reply.started":"2026-09-07T18:28:32.487039Z","shell.execute_reply":"2026-09-07T18:28:32.492979Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 28 — NETRX FINAL SCREENING REPORT\n# ============================================================\n\nimport matplotlib.pyplot as plt\n\nfig = plt.figure(figsize=(16, 10))\n\n# ------------------------------------------------------------\n# LEFT — FUNDUS IMAGE\n# ------------------------------------------------------------\n\nax_img = fig.add_axes([0.03, 0.08, 0.58, 0.84])\n\nax_img.imshow(model_input_image)\nax_img.set_title(\n    \"NETRX PROCESSED FUNDUS IMAGE\",\n    fontsize=16,\n    fontweight=\"bold\"\n)\nax_img.axis(\"off\")\n\n\n# ------------------------------------------------------------\n# RIGHT — REPORT\n# ------------------------------------------------------------\n\nax = fig.add_axes([0.64, 0.05, 0.33, 0.90])\nax.axis(\"off\")\n\n\nreport = f\"\"\"\nNETRX FINAL SCREENING REPORT\n{'=' * 38}\n\nIMAGE QUALITY\n--------------------------------------\nFocus / Sharpness : {quality_result['focus_score']:.2f}\nIllumination      : {quality_result['illumination_score']:.2f}\nFOV               : {quality_result['fov_score']:.2f}\nOverall Quality   : {quality_result['overall_quality']:.2f}\n\nQuality Status    : {final_status}\n\n\nMODEL PREDICTIONS\n--------------------------------------\nCNN               : Grade {individual_predictions[0]}\nResNet50          : Grade {individual_predictions[1]}\nEfficientNet-B0   : Grade {individual_predictions[2]}\nDenseNet121       : Grade {individual_predictions[3]}\nConvNeXt-Tiny     : Grade {individual_predictions[4]}\nViT-B/16          : Grade {individual_predictions[5]}\n\n\nENSEMBLE RESULT\n--------------------------------------\nGrade 0           : {ensemble_probabilities[0]*100:.2f}%\nGrade 1           : {ensemble_probabilities[1]*100:.2f}%\nGrade 2           : {ensemble_probabilities[2]*100:.2f}%\nGrade 3           : {ensemble_probabilities[3]*100:.2f}%\nGrade 4           : {ensemble_probabilities[4]*100:.2f}%\n\nFINAL DR GRADE    : {final_grade}\nConfidence        : {final_confidence:.2f}%\n\n\nREFERABLE SCREENING\n--------------------------------------\nReferable Prob.   : {referable_probability*100:.2f}%\nThreshold         : {REFERABLE_THRESHOLD*100:.0f}%\n\nSTATUS            : {referable_status}\n\nACTION            : {referral_action}\n\n\nMODEL AGREEMENT\n--------------------------------------\nAgreement         : {agreement_count}/6\nPercentage        : {agreement_percentage:.2f}%\n\n\nNetrX Decision\n--------------------------------------\nImage → Quality Gate\n      → Preprocessing\n      → 6-Model Ensemble\n      → DR Classification\n      → Referable Screening\n\"\"\"\n\nax.text(\n    0,\n    1,\n    report,\n    transform=ax.transAxes,\n    fontsize=11,\n    verticalalignment=\"top\",\n    fontfamily=\"monospace\"\n)\n\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T18:31:40.901355Z","iopub.execute_input":"2026-09-07T18:31:40.902075Z","iopub.status.idle":"2026-09-07T18:31:42.194423Z","shell.execute_reply.started":"2026-09-07T18:31:40.902043Z","shell.execute_reply":"2026-09-07T18:31:42.192807Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 28A — RESTORE NETRX FINAL QUALITY RESULT\n# ============================================================\n\nquality_result = {\n    \"focus_score\": 43.00,\n    \"illumination_score\": 89.72,\n    \"fov_score\": 62.62,\n    \"overall_quality\": 74.37,\n    \"status\": \"REVIEW REQUIRED\",\n    \"issues\": [\n        \"Limited retinal field\"\n    ],\n    \"action\": \"Proceed with NetrX preprocessing\"\n}\n\nfinal_status = quality_result[\"status\"]\n\nprint(\"=\" * 70)\nprint(\"             NETRX FINAL QUALITY RESULT\")\nprint(\"=\" * 70)\n\nprint(f\"Focus / Sharpness : {quality_result['focus_score']:.2f}\")\nprint(f\"Illumination      : {quality_result['illumination_score']:.2f}\")\nprint(f\"FOV               : {quality_result['fov_score']:.2f}\")\nprint(f\"Overall Quality   : {quality_result['overall_quality']:.2f}\")\n\nprint(\"-\" * 70)\n\nprint(f\"STATUS : {quality_result['status']}\")\n\nif quality_result[\"issues\"]:\n    print(\"\\nIssues:\")\n    for issue in quality_result[\"issues\"]:\n        print(\" •\", issue)\n\nprint(\"=\" * 70)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T18:32:28.677332Z","iopub.execute_input":"2026-09-07T18:32:28.678305Z","iopub.status.idle":"2026-09-07T18:32:28.684795Z","shell.execute_reply.started":"2026-09-07T18:32:28.678261Z","shell.execute_reply":"2026-09-07T18:32:28.683979Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 29 — NETRX GRAD-CAM\n# ============================================================\n\nimport torch\nimport torch.nn.functional as F\nimport numpy as np\nimport cv2\nimport matplotlib.pyplot as plt\n\n\n# ============================================================\n# GRAD-CAM CLASS\n# ============================================================\n\nclass GradCAM:\n\n    def __init__(self, model, target_layer):\n\n        self.model = model\n        self.target_layer = target_layer\n\n        self.activations = None\n        self.gradients = None\n\n        self.forward_hook = target_layer.register_forward_hook(\n            self.save_activation\n        )\n\n        self.backward_hook = target_layer.register_full_backward_hook(\n            self.save_gradient\n        )\n\n    def save_activation(self, module, input, output):\n\n        self.activations = output.detach()\n\n    def save_gradient(self, module, grad_input, grad_output):\n\n        self.gradients = grad_output[0].detach()\n\n    def generate(self, input_tensor, target_class):\n\n        self.model.zero_grad(set_to_none=True)\n\n        output = self.model(input_tensor)\n\n        target_score = output[:, target_class]\n\n        target_score.backward()\n\n        activations = self.activations\n        gradients = self.gradients\n\n        # Global average pooling of gradients\n        weights = gradients.mean(\n            dim=(2, 3),\n            keepdim=True\n        )\n\n        cam = (\n            weights * activations\n        ).sum(dim=1)\n\n        cam = F.relu(cam)\n\n        cam = cam[0].cpu().numpy()\n\n        # Normalize 0–1\n        cam -= cam.min()\n\n        if cam.max() > 0:\n            cam /= cam.max()\n\n        return cam\n\n    def remove_hooks(self):\n\n        self.forward_hook.remove()\n        self.backward_hook.remove()\n\n\n# ============================================================\n# TARGET LAYER\n# ============================================================\n\ntarget_layer = convnext_model.features[-1]\n\ngradcam = GradCAM(\n    convnext_model,\n    target_layer\n)\n\n\n# ============================================================\n# GENERATE CAM FOR FINAL ENSEMBLE GRADE\n# ============================================================\n\ncam = gradcam.generate(\n    input_tensor,\n    final_grade\n)\n\ngradcam.remove_hooks()\n\n\nprint(\"=\" * 70)\nprint(\"             NETRX GRAD-CAM\")\nprint(\"=\" * 70)\n\nprint(\n    f\"Target Model : ConvNeXt-Tiny\"\n)\n\nprint(\n    f\"Target Grade : {final_grade}\"\n)\n\nprint(\n    f\"Ensemble Confidence : \"\n    f\"{final_confidence:.2f}%\"\n)\n\nprint(\"✅ Grad-CAM generated successfully\")\n\nprint(\"=\" * 70)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T18:33:49.117677Z","iopub.execute_input":"2026-09-07T18:33:49.117963Z","iopub.status.idle":"2026-09-07T18:33:51.09751Z","shell.execute_reply.started":"2026-09-07T18:33:49.117942Z","shell.execute_reply":"2026-09-07T18:33:51.09683Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 30 — DISPLAY NETRX GRAD-CAM\n# ============================================================\n\n# model_input_image is already the processed RGB PIL image\noriginal_rgb = np.array(model_input_image.convert(\"RGB\"))\n\n# Resize Grad-CAM to original image size\ncam_resized = cv2.resize(\n    cam,\n    (original_rgb.shape[1], original_rgb.shape[0])\n)\n\n# Create heatmap\nheatmap = np.uint8(255 * cam_resized)\n\nheatmap = cv2.applyColorMap(\n    heatmap,\n    cv2.COLORMAP_JET\n)\n\n# OpenCV BGR → RGB\nheatmap = cv2.cvtColor(\n    heatmap,\n    cv2.COLOR_BGR2RGB\n)\n\n# Overlay heatmap on fundus\noverlay = np.uint8(\n    np.clip(\n        0.55 * original_rgb +\n        0.45 * heatmap,\n        0,\n        255\n    )\n)\n\n# ============================================================\n# DISPLAY\n# ============================================================\n\nfig, axes = plt.subplots(\n    1, 3,\n    figsize=(18, 6)\n)\n\naxes[0].imshow(original_rgb)\naxes[0].set_title(\n    \"NETRX PROCESSED FUNDUS\",\n    fontsize=14,\n    fontweight=\"bold\"\n)\naxes[0].axis(\"off\")\n\naxes[1].imshow(\n    cam_resized,\n    cmap=\"jet\"\n)\naxes[1].set_title(\n    \"GRAD-CAM HEATMAP\",\n    fontsize=14,\n    fontweight=\"bold\"\n)\naxes[1].axis(\"off\")\n\naxes[2].imshow(overlay)\naxes[2].set_title(\n    f\"GRAD-CAM OVERLAY — GRADE {final_grade}\",\n    fontsize=14,\n    fontweight=\"bold\"\n)\naxes[2].axis(\"off\")\n\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T18:35:48.012256Z","iopub.execute_input":"2026-09-07T18:35:48.012889Z","iopub.status.idle":"2026-09-07T18:35:52.019272Z","shell.execute_reply.started":"2026-09-07T18:35:48.012859Z","shell.execute_reply":"2026-09-07T18:35:52.018286Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 32 — NETRX FINAL DASHBOARD\n# ============================================================\n\nfig = plt.figure(figsize=(18, 10))\n\n# ============================================================\n# IMAGES\n# ============================================================\n\nax1 = fig.add_axes([0.02, 0.55, 0.28, 0.38])\nax2 = fig.add_axes([0.31, 0.55, 0.28, 0.38])\nax3 = fig.add_axes([0.60, 0.55, 0.28, 0.38])\n\n# Original\nax1.imshow(np.array(uploaded_pil))\nax1.set_title(\"ORIGINAL USER IMAGE\", fontweight=\"bold\")\nax1.axis(\"off\")\n\n# Processed\nax2.imshow(np.array(model_input_image))\nax2.set_title(\"NETRX PROCESSED IMAGE\", fontweight=\"bold\")\nax2.axis(\"off\")\n\n# Grad-CAM\nax3.imshow(overlay)\nax3.set_title(\n    f\"GRAD-CAM — GRADE {final_grade}\",\n    fontweight=\"bold\"\n)\nax3.axis(\"off\")\n\n\n# ============================================================\n# REPORT PANEL\n# ============================================================\n\nax4 = fig.add_axes([0.02, 0.03, 0.94, 0.45])\nax4.axis(\"off\")\n\nreport = f\"\"\"\nNETRX — EXPLAINABLE DIABETIC RETINOPATHY SCREENING\n======================================================================\n\nIMAGE QUALITY\n----------------------------------------------------------------------\nFocus / Sharpness : {quality_result['focus_score']:.2f}\nIllumination      : {quality_result['illumination_score']:.2f}\nFOV               : {quality_result['fov_score']:.2f}\nOverall Quality   : {quality_result['overall_quality']:.2f}\nQuality Status    : {quality_result['status']}\n\nMODEL PREDICTIONS\n----------------------------------------------------------------------\nCNN                → Grade {individual_predictions[0]}\nResNet50           → Grade {individual_predictions[1]}\nEfficientNet-B0    → Grade {individual_predictions[2]}\nDenseNet121        → Grade {individual_predictions[3]}\nConvNeXt-Tiny      → Grade {individual_predictions[4]}\nViT-B/16           → Grade {individual_predictions[5]}\n\nENSEMBLE RESULT\n----------------------------------------------------------------------\nGrade 0            : {ensemble_probabilities[0]*100:.2f}%\nGrade 1            : {ensemble_probabilities[1]*100:.2f}%\nGrade 2            : {ensemble_probabilities[2]*100:.2f}%\nGrade 3            : {ensemble_probabilities[3]*100:.2f}%\nGrade 4            : {ensemble_probabilities[4]*100:.2f}%\n\nFINAL DR GRADE     : {final_grade}\nCONFIDENCE         : {final_confidence:.2f}%\n\nREFERABLE SCREENING\n----------------------------------------------------------------------\nReferable Probability : {referable_probability*100:.2f}%\nThreshold             : {REFERABLE_THRESHOLD*100:.0f}%\nSTATUS                : {referable_status}\n\nMODEL AGREEMENT\n----------------------------------------------------------------------\nAgreement             : {agreement_count}/6\nAgreement Percentage  : {agreement_percentage:.2f}%\n\nEXPLAINABILITY\n----------------------------------------------------------------------\nGrad-CAM Model        : ConvNeXt-Tiny\nGrad-CAM Target       : Grade {final_grade}\n\nNetrX Pipeline:\nQuality Check → Preprocessing → Quality Recheck\n→ Six-Model Ensemble → DR Grade → Referral\n→ Model Agreement → Grad-CAM\n======================================================================\n\"\"\"\n\nax4.text(\n    0,\n    1,\n    report,\n    transform=ax4.transAxes,\n    verticalalignment=\"top\",\n    fontsize=10,\n    fontfamily=\"monospace\"\n)\n\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T19:44:11.106988Z","iopub.execute_input":"2026-09-07T19:44:11.107251Z","iopub.status.idle":"2026-09-07T19:44:12.872272Z","shell.execute_reply.started":"2026-09-07T19:44:11.107226Z","shell.execute_reply":"2026-09-07T19:44:12.87083Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"FINAL","metadata":{}},{"cell_type":"code","source":"# ============================================================\n# CHECK YOUR EXISTING NETRX PREPROCESSING FUNCTION\n# ============================================================\n\nimport inspect\n\nprint(\"=\" * 70)\nprint(\"CURRENT preprocess_fundus() FUNCTION\")\nprint(\"=\" * 70)\n\ntry:\n    print(inspect.getsource(preprocess_fundus))\nexcept Exception as e:\n    print(\"Could not display source:\", e)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T19:39:06.487313Z","iopub.execute_input":"2026-09-07T19:39:06.487981Z","iopub.status.idle":"2026-09-07T19:39:06.492948Z","shell.execute_reply.started":"2026-09-07T19:39:06.487949Z","shell.execute_reply":"2026-09-07T19:39:06.492141Z"}},"outputs":[],"execution_count":null}]}