{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.12.12","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,"databundleVersionId":875431},{"sourceType":"datasetVersion","sourceId":15210700,"datasetId":9732474,"databundleVersionId":16105475}],"dockerImageVersionId":31286,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import os\nimport cv2\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\n\nfrom tqdm import tqdm\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.utils.class_weight import compute_class_weight\n\nimport tensorflow as tf\nfrom tensorflow.keras.preprocessing.image import ImageDataGenerator\nfrom tensorflow.keras.applications import ResNet50, DenseNet121, EfficientNetB2\nfrom tensorflow.keras.layers import Dense, GlobalAveragePooling2D, Dropout\nfrom tensorflow.keras.models import Model\nfrom tensorflow.keras.optimizers import Adam","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2026-03-16T08:07:42.787252Z","iopub.execute_input":"2026-03-16T08:07:42.787550Z","iopub.status.idle":"2026-03-16T08:07:42.792646Z","shell.execute_reply.started":"2026-03-16T08:07:42.787524Z","shell.execute_reply":"2026-03-16T08:07:42.791940Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"APTOS_IMG_PATH = \"/kaggle/input/competitions/aptos2019-blindness-detection/train_images\"\nAPTOS_CSV = \"/kaggle/input/competitions/aptos2019-blindness-detection/train.csv\"\n\nIDRID_IMG_PATH = \"/kaggle/input/datasets/giyu09/idrid-dataset/B. Disease Grading/1. Original Images/a. Training Set\"\nIDRID_CSV = \"/kaggle/input/datasets/giyu09/idrid-dataset/B. Disease Grading/2. Groundtruths/a. IDRiD_Disease Grading_Training Labels.csv\"","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-16T08:07:45.945049Z","iopub.execute_input":"2026-03-16T08:07:45.945331Z","iopub.status.idle":"2026-03-16T08:07:45.949093Z","shell.execute_reply.started":"2026-03-16T08:07:45.945308Z","shell.execute_reply":"2026-03-16T08:07:45.948518Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"aptos_df = pd.read_csv(APTOS_CSV)\naptos_df[\"id_code\"] = aptos_df[\"id_code\"] + \".png\"\n\naptos_df[\"path\"] = aptos_df[\"id_code\"].apply(\n    lambda x: os.path.join(APTOS_IMG_PATH, x)\n)\n\naptos_df.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-16T08:07:48.396227Z","iopub.execute_input":"2026-03-16T08:07:48.396542Z","iopub.status.idle":"2026-03-16T08:07:48.435187Z","shell.execute_reply.started":"2026-03-16T08:07:48.396516Z","shell.execute_reply":"2026-03-16T08:07:48.434623Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"idrid_df = pd.read_csv(IDRID_CSV)\n\nidrid_df.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-16T08:07:51.675308Z","iopub.execute_input":"2026-03-16T08:07:51.675626Z","iopub.status.idle":"2026-03-16T08:07:51.718985Z","shell.execute_reply.started":"2026-03-16T08:07:51.675598Z","shell.execute_reply":"2026-03-16T08:07:51.718394Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"idrid_df.rename(columns={\n    \"Image name\": \"id_code\",\n    \"Retinopathy grade\": \"diagnosis\"\n}, inplace=True)\n\nidrid_df[\"id_code\"] = idrid_df[\"id_code\"] + \".jpg\"\nidrid_df[\"path\"] = idrid_df[\"id_code\"].apply(lambda x: os.path.join(IDRID_IMG_PATH, x))\nidrid_df.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-16T08:07:53.861018Z","iopub.execute_input":"2026-03-16T08:07:53.861622Z","iopub.status.idle":"2026-03-16T08:07:53.876922Z","shell.execute_reply.started":"2026-03-16T08:07:53.861591Z","shell.execute_reply":"2026-03-16T08:07:53.876284Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"combined_df = pd.concat([aptos_df, idrid_df], ignore_index=True)\n\nprint(\"Total images:\", len(combined_df))\ncombined_df.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-16T08:07:56.208264Z","iopub.execute_input":"2026-03-16T08:07:56.208974Z","iopub.status.idle":"2026-03-16T08:07:56.227031Z","shell.execute_reply.started":"2026-03-16T08:07:56.208940Z","shell.execute_reply":"2026-03-16T08:07:56.226417Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"combined_df[\"diagnosis\"].value_counts()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-16T08:07:58.594854Z","iopub.execute_input":"2026-03-16T08:07:58.595537Z","iopub.status.idle":"2026-03-16T08:07:58.604177Z","shell.execute_reply.started":"2026-03-16T08:07:58.595508Z","shell.execute_reply":"2026-03-16T08:07:58.603603Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"combined_df[\"diagnosis\"].value_counts().sort_index().plot(\n    kind=\"bar\",\n    title=\"Class Distribution (APTOS + IDRiD)\"\n)\n\nplt.xlabel(\"DR Severity Class\")\nplt.ylabel(\"Number of Images\")\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-16T08:08:00.663513Z","iopub.execute_input":"2026-03-16T08:08:00.663836Z","iopub.status.idle":"2026-03-16T08:08:00.896504Z","shell.execute_reply.started":"2026-03-16T08:08:00.663810Z","shell.execute_reply":"2026-03-16T08:08:00.895809Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"clean_dir = \"/kaggle/working/clean_images_merged\"\nos.makedirs(clean_dir, exist_ok=True)\n\nprint(\"Folder created:\", clean_dir)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-16T09:17:20.802367Z","iopub.execute_input":"2026-03-16T09:17:20.802688Z","iopub.status.idle":"2026-03-16T09:17:20.807393Z","shell.execute_reply.started":"2026-03-16T09:17:20.802661Z","shell.execute_reply":"2026-03-16T09:17:20.806676Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def preprocess_retina(img_path, img_size=224):\n\n    img = cv2.imread(img_path)\n\n    # Retina crop\n    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)\n    _, thresh = cv2.threshold(gray, 10, 255, cv2.THRESH_BINARY)\n\n    coords = cv2.findNonZero(thresh)\n    x, y, w, h = cv2.boundingRect(coords)\n\n    img = img[y:y+h, x:x+w]\n\n    # CLAHE on L channel\n    lab = cv2.cvtColor(img, cv2.COLOR_BGR2LAB)\n    l, a, b = cv2.split(lab)\n\n    clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))\n    l = clahe.apply(l)\n\n    lab = cv2.merge((l, a, b))\n    img = cv2.cvtColor(lab, cv2.COLOR_LAB2BGR)\n\n    # Mild blur\n    img = cv2.GaussianBlur(img, (3,3), 0)\n\n    # Resize\n    img = cv2.resize(img, (img_size, img_size))\n\n    return img","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-16T09:17:30.327668Z","iopub.execute_input":"2026-03-16T09:17:30.328269Z","iopub.status.idle":"2026-03-16T09:17:30.333895Z","shell.execute_reply.started":"2026-03-16T09:17:30.328240Z","shell.execute_reply":"2026-03-16T09:17:30.333205Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"clean_paths = []\n\nfor i, row in combined_df.iterrows():\n\n    img_path = row[\"path\"]\n\n    processed = preprocess_retina(img_path)\n\n    save_path = os.path.join(clean_dir, row[\"id_code\"])\n\n    cv2.imwrite(save_path, processed)\n\n    clean_paths.append(save_path)\n\n    if i % 200 == 0:\n        print(f\"{i}/{len(combined_df)} images processed\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-16T09:17:51.315322Z","iopub.execute_input":"2026-03-16T09:17:51.315923Z","iopub.status.idle":"2026-03-16T09:30:41.610202Z","shell.execute_reply.started":"2026-03-16T09:17:51.315894Z","shell.execute_reply":"2026-03-16T09:30:41.609592Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"combined_df[\"clean_path\"] = clean_paths","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-16T09:33:00.907998Z","iopub.execute_input":"2026-03-16T09:33:00.908748Z","iopub.status.idle":"2026-03-16T09:33:00.912916Z","shell.execute_reply.started":"2026-03-16T09:33:00.908720Z","shell.execute_reply":"2026-03-16T09:33:00.912173Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"sample = combined_df.iloc[0][\"clean_path\"]\n\nimg = cv2.imread(sample)\n\nplt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))\nplt.axis(\"off\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-16T09:33:30.461998Z","iopub.execute_input":"2026-03-16T09:33:30.462315Z","iopub.status.idle":"2026-03-16T09:33:30.590774Z","shell.execute_reply.started":"2026-03-16T09:33:30.462289Z","shell.execute_reply":"2026-03-16T09:33:30.590057Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"sample = combined_df.iloc[0][\"path\"]\nclean = combined_df.iloc[0][\"clean_path\"]\n\norig = cv2.imread(sample)\nproc = cv2.imread(clean)\n\nplt.figure(figsize=(10,5))\n\nplt.subplot(1,2,1)\nplt.imshow(cv2.cvtColor(orig, cv2.COLOR_BGR2RGB))\nplt.title(\"Original\")\n\nplt.subplot(1,2,2)\nplt.imshow(cv2.cvtColor(proc, cv2.COLOR_BGR2RGB))\nplt.title(\"Preprocessed\")\n\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-16T09:33:34.441999Z","iopub.execute_input":"2026-03-16T09:33:34.442654Z","iopub.status.idle":"2026-03-16T09:33:35.276377Z","shell.execute_reply.started":"2026-03-16T09:33:34.442616Z","shell.execute_reply":"2026-03-16T09:33:35.275597Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_df, temp_df = train_test_split(\n    combined_df,\n    test_size=0.30,\n    stratify=combined_df[\"diagnosis\"],\n    random_state=42\n)\n\nprint(\"Train size:\", len(train_df))\nprint(\"Temp size:\", len(temp_df))\n\n\nval_df, test_df = train_test_split(\n    temp_df,\n    test_size=0.5,\n    stratify=temp_df[\"diagnosis\"],\n    random_state=42\n)\n\nprint(\"Validation size:\", len(val_df))\nprint(\"Test size:\", len(test_df))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-16T09:37:22.665782Z","iopub.execute_input":"2026-03-16T09:37:22.666549Z","iopub.status.idle":"2026-03-16T09:37:22.680381Z","shell.execute_reply.started":"2026-03-16T09:37:22.666520Z","shell.execute_reply":"2026-03-16T09:37:22.679742Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_datagen = ImageDataGenerator(\n    rescale=1./255,\n    rotation_range=15,\n    horizontal_flip=True,\n    zoom_range=0.05\n)\n\nval_test_datagen = ImageDataGenerator(rescale=1./255)\n\n\ntrain_gen = train_datagen.flow_from_dataframe(\n    dataframe=train_df,\n    x_col=\"clean_path\",\n    y_col=\"diagnosis\",\n    target_size=(224,224),\n    batch_size=16,\n    class_mode=\"categorical\",\n    seed=42\n)\n\nval_gen = val_test_datagen.flow_from_dataframe(\n    dataframe=val_df,\n    x_col=\"clean_path\",\n    y_col=\"diagnosis\",\n    target_size=(224,224),\n    batch_size=16,\n    class_mode=\"categorical\"\n)\n\ntest_gen = val_test_datagen.flow_from_dataframe(\n    dataframe=test_df,\n    x_col=\"clean_path\",\n    y_col=\"diagnosis\",\n    target_size=(224,224),\n    batch_size=16,\n    class_mode=\"categorical\",\n    shuffle=False\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-16T09:37:47.381529Z","iopub.execute_input":"2026-03-16T09:37:47.382244Z","iopub.status.idle":"2026-03-16T09:37:47.428970Z","shell.execute_reply.started":"2026-03-16T09:37:47.382208Z","shell.execute_reply":"2026-03-16T09:37:47.428362Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class_weights = compute_class_weight(\n    class_weight=\"balanced\",\n    classes=np.unique(train_df[\"diagnosis\"]),\n    y=train_df[\"diagnosis\"]\n)\n\nclass_weights = {i: class_weights[i] for i in range(len(class_weights))}\n\nprint(class_weights)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-16T09:37:55.078419Z","iopub.execute_input":"2026-03-16T09:37:55.079120Z","iopub.status.idle":"2026-03-16T09:37:55.086496Z","shell.execute_reply.started":"2026-03-16T09:37:55.079092Z","shell.execute_reply":"2026-03-16T09:37:55.085713Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(train_gen.class_indices)\nprint(\"Train samples:\", train_gen.samples)\nprint(\"Validation samples:\", val_gen.samples)\nprint(\"Test samples:\", test_gen.samples)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-16T09:38:07.241134Z","iopub.execute_input":"2026-03-16T09:38:07.241707Z","iopub.status.idle":"2026-03-16T09:38:07.245788Z","shell.execute_reply.started":"2026-03-16T09:38:07.241676Z","shell.execute_reply":"2026-03-16T09:38:07.245083Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from tensorflow.keras.callbacks import EarlyStopping, ReduceLROnPlateau, ModelCheckpoint\n\nearly_stop = EarlyStopping(\n    monitor=\"val_loss\",\n    patience=4,\n    restore_best_weights=True\n)\n\nreduce_lr = ReduceLROnPlateau(\n    monitor=\"val_loss\",\n    factor=0.3,\n    patience=2,\n    min_lr=1e-6\n)\n\ncheckpoint = ModelCheckpoint(\n    \"best_resnet_model.h5\",\n    monitor=\"val_accuracy\",\n    save_best_only=True\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-16T08:58:16.322280Z","iopub.execute_input":"2026-03-16T08:58:16.322987Z","iopub.status.idle":"2026-03-16T08:58:16.326883Z","shell.execute_reply.started":"2026-03-16T08:58:16.322958Z","shell.execute_reply":"2026-03-16T08:58:16.326254Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"base_model = ResNet50(\n    weights=\"imagenet\",\n    include_top=False,\n    input_shape=(224,224,3)\n)\n\nfor layer in base_model.layers:\n    layer.trainable = False\n\nx = base_model.output\nx = GlobalAveragePooling2D()(x)\nx = Dropout(0.5)(x)\noutput = Dense(5, activation=\"softmax\")(x)\n\nmodel = Model(base_model.input, output)\n\nmodel.compile(\n    optimizer=Adam(learning_rate=1e-4),\n    loss=\"categorical_crossentropy\",\n    metrics=[\"accuracy\"]\n)\n\nmodel.summary()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-16T09:41:26.890167Z","iopub.execute_input":"2026-03-16T09:41:26.890779Z","iopub.status.idle":"2026-03-16T09:41:28.497433Z","shell.execute_reply.started":"2026-03-16T09:41:26.890731Z","shell.execute_reply":"2026-03-16T09:41:28.496899Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"history = model.fit(\n    train_gen,\n    validation_data=val_gen,\n    epochs=15,\n    class_weight=class_weights,\n    callbacks=[early_stop, reduce_lr, checkpoint]\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-16T09:42:20.102675Z","iopub.execute_input":"2026-03-16T09:42:20.103442Z","iopub.status.idle":"2026-03-16T09:45:22.248281Z","shell.execute_reply.started":"2026-03-16T09:42:20.103412Z","shell.execute_reply":"2026-03-16T09:45:22.247448Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}