{"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":"gpu","dataSources":[{"sourceType":"competition","sourceId":14774,"databundleVersionId":875431}],"dockerImageVersionId":31328,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# 1. استدعاء المكتبات الأساسية\nimport os\nimport cv2\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nimport seaborn as sns\nfrom PIL import Image\n\n\n\n# تثبيت العشوائية لضمان استقرار النتائج (Reproducibility)\nSEED = 42\nnp.random.seed(SEED)\nimport tensorflow as tf\ntf.random.set_seed(SEED)\n\nprint(\"Libraries imported successfully!\")","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2026-07-07T19:48:57.620935Z","iopub.execute_input":"2026-07-07T19:48:57.621679Z","iopub.status.idle":"2026-07-07T19:48:57.626912Z","shell.execute_reply.started":"2026-07-07T19:48:57.621641Z","shell.execute_reply":"2026-07-07T19:48:57.626050Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 2. إعداد مسارات البيانات\nBASE_PATH = '/kaggle/input/competitions/aptos2019-blindness-detection/'\nTRAIN_IMG_PATH = os.path.join(BASE_PATH, 'train_images/')\nTEST_IMG_PATH = os.path.join(BASE_PATH, 'test_images/')\n\n# قراءة ملفات الـ CSV\ntrain_df = pd.read_csv(os.path.join(BASE_PATH, 'train.csv'))\ntest_df = pd.read_csv(os.path.join(BASE_PATH, 'test.csv'))\n\n# 🚀 التعديل هنا: تحويل بيانات التدريب فقط إلى فئتين (0 = سليم، 1 = مصاب)\n# (ملف test.csv لا نلمسه لأنه لا يحتوي على تشخيص)\ntrain_df['diagnosis'] = train_df['diagnosis'].apply(lambda x: 0 if int(x) == 0 else 1)\n\n# إضافة صيغة .png لأسماء الصور لتسهيل قراءتها لاحقاً\ntrain_df['id_code'] = train_df['id_code'].apply(lambda x: x + '.png')\ntest_df['id_code'] = test_df['id_code'].apply(lambda x: x + '.png')\n\n# عرض أول 5 صفوف من بيانات التدريب\nprint(f\"Number of training samples: {len(train_df)}\")\ndisplay(train_df.head())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-07T19:48:57.628187Z","iopub.execute_input":"2026-07-07T19:48:57.628501Z","iopub.status.idle":"2026-07-07T19:48:57.656598Z","shell.execute_reply.started":"2026-07-07T19:48:57.628453Z","shell.execute_reply":"2026-07-07T19:48:57.655788Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 3. عرض توزيع الفئات (Class Distribution)\nplt.figure(figsize=(10, 6))\nsns.countplot(x='diagnosis', data=train_df, palette='viridis')\nplt.title('Distribution of Diabetic Retinopathy Stages', fontsize=16)\nplt.xlabel('Diagnosis (0 = No DR, 1 = DR)', fontsize=12)\nplt.ylabel('Number of Images', fontsize=12)\nplt.grid(axis='y', linestyle='--', alpha=0.7)\nplt.show()\n\n# طباعة الأرقام بدقة\nprint(train_df['diagnosis'].value_counts())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-07T19:48:57.657753Z","iopub.execute_input":"2026-07-07T19:48:57.657951Z","iopub.status.idle":"2026-07-07T19:48:57.793669Z","shell.execute_reply.started":"2026-07-07T19:48:57.657933Z","shell.execute_reply":"2026-07-07T19:48:57.792872Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def display_samples_per_class(df, img_path):\n    fig, axes = plt.subplots(1, 2, figsize=(10, 5)) # تعديل الحجم وعدد الصور\n    \n    # أسماء الفئات لتوضيحها في الرسم\n    class_names =['0: Normal (No DR)', '1: Diabetic Retinopathy (DR)']\n    \n    for diagnosis_class in range(2): # 🚀 التعديل هنا (2 بدلا من 5)\n        sample = df[df['diagnosis'] == diagnosis_class].sample(1, random_state=SEED)\n        img_name = sample['id_code'].values[0]\n        \n        img_array = cv2.imread(os.path.join(img_path, img_name))\n        img_array = cv2.cvtColor(img_array, cv2.COLOR_BGR2RGB)\n        \n        axes[diagnosis_class].imshow(img_array)\n        axes[diagnosis_class].set_title(class_names[diagnosis_class], fontsize=14)\n        axes[diagnosis_class].axis('off')\n        \n    plt.tight_layout()\n    plt.show()\n\n# استدعاء الدالة لعرض الصور\ndisplay_samples_per_class(train_df, TRAIN_IMG_PATH)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-07T19:48:57.795014Z","iopub.execute_input":"2026-07-07T19:48:57.795365Z","iopub.status.idle":"2026-07-07T19:48:58.913877Z","shell.execute_reply.started":"2026-07-07T19:48:57.795312Z","shell.execute_reply":"2026-07-07T19:48:58.913080Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 5. دوال معالجة الصور (Image Preprocessing)\n\ndef crop_image_from_gray(img, tol=7):\n    \"\"\"\n    هذه الدالة تقوم بالبحث عن البيكسلات السوداء (أقل من درجة معينة tol)\n    وتقوم بقصها للتركيز على كرة العين فقط.\n    \"\"\"\n    if img.ndim == 2:\n        mask = img > tol\n        return img[np.ix_(mask.any(1), mask.any(0))]\n    elif img.ndim == 3:\n        gray_img = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)\n        mask = gray_img > tol\n        \n        check_shape = img[:,:,0][np.ix_(mask.any(1), mask.any(0))].shape[0]\n        if (check_shape == 0): # إذا كانت الصورة مظلمة جداً بالكامل\n            return img \n        else:\n            img1 = img[:,:,0][np.ix_(mask.any(1), mask.any(0))]\n            img2 = img[:,:,1][np.ix_(mask.any(1), mask.any(0))]\n            img3 = img[:,:,2][np.ix_(mask.any(1), mask.any(0))]\n            img = np.dstack([img1, img2, img3])\n        return img\n\ndef preprocess_image(image_path, img_size=224):\n    \"\"\"\n    الدالة الشاملة التي تقرأ الصورة، تقصها، تغير حجمها،\n    وتطبق فلتر Ben Graham لإبراز الأوعية الدموية.\n    \"\"\"\n    # 1. قراءة الصورة\n    image = cv2.imread(image_path)\n    image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)\n    \n    # 2. قص الحواف السوداء\n    image = crop_image_from_gray(image)\n    \n    # 3. توحيد حجم الصورة (224x224 هو الحجم القياسي لمعظم الموديلات)\n    image = cv2.resize(image, (img_size, img_size))\n    \n    # 4. تطبيق تقنية Ben Graham (Gaussian Blur & Blending)\n    # هذه المعادلة تقوم بتقليل تأثير الإضاءة السيئة وتوضيح معالم الشبكية\n    image = cv2.addWeighted(image, 4, cv2.GaussianBlur(image, (0,0), 10), -4, 128)\n    \n    return image\n\nprint(\"Preprocessing functions defined successfully!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-07T19:48:58.915030Z","iopub.execute_input":"2026-07-07T19:48:58.915462Z","iopub.status.idle":"2026-07-07T19:48:58.924705Z","shell.execute_reply.started":"2026-07-07T19:48:58.915435Z","shell.execute_reply":"2026-07-07T19:48:58.923887Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 7. تقسيم البيانات إلى تدريب (80%) وتقييم (20%)\nfrom sklearn.model_selection import train_test_split\n\n# نحول عمود التشخيص إلى String لأن DataGenerator يحتاج الفئات كنصوص (Categorical)\ntrain_df['diagnosis'] = train_df['diagnosis'].astype(str)\n\n# تقسيم البيانات مع الحفاظ على نسبة الفئات (Stratified Split)\ntrain_data, val_data = train_test_split(\n    train_df, \n    test_size=0.20, \n    random_state=SEED, \n    stratify=train_df['diagnosis']\n)\n\nprint(f\"Training data size: {len(train_data)} images\")\nprint(f\"Validation data size: {len(val_data)} images\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-07T19:48:58.926383Z","iopub.execute_input":"2026-07-07T19:48:58.927002Z","iopub.status.idle":"2026-07-07T19:48:59.054549Z","shell.execute_reply.started":"2026-07-07T19:48:58.926980Z","shell.execute_reply":"2026-07-07T19:48:59.053917Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 8. دالة المعالجة المتوافقة مع Keras\ndef keras_preprocess(img_array):\n    \"\"\"\n    هذه الدالة تستقبل الصورة من Keras كـ Array، \n    تطبق عليها القص وفلتر Ben Graham، ثم تعيدها جاهزة للموديل.\n    \"\"\"\n    # تحويل نوع البيانات لتتوافق مع مكتبة cv2\n    img = img_array.astype(np.uint8)\n    \n    # 1. القص\n    img = crop_image_from_gray(img)\n    \n    # 2. تغيير الحجم إلى 224x224 (حجم موديل EfficientNet أو ResNet)\n    img = cv2.resize(img, (224, 224))\n    \n    # 3. فلتر Ben Graham لإبراز الأوعية الدموية\n    img = cv2.addWeighted(img, 4, cv2.GaussianBlur(img, (0,0), 10), -4, 128)\n    \n    # 4. التطبيع (Normalization): تحويل القيم من 0-255 إلى 0-1 ليسهل التدريب\n    img = img.astype(np.float32) / 255.0\n    \n    return img\n\nprint(\"Keras Preprocessing function is ready!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-07T19:48:59.055580Z","iopub.execute_input":"2026-07-07T19:48:59.056120Z","iopub.status.idle":"2026-07-07T19:48:59.061304Z","shell.execute_reply.started":"2026-07-07T19:48:59.056096Z","shell.execute_reply":"2026-07-07T19:48:59.060613Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 9. تجهيز مولدات البيانات (Data Generators)\nfrom tensorflow.keras.preprocessing.image import ImageDataGenerator\n\n# إعدادات مولد التدريب (مع Augmentation لمنع الحفظ Overfitting)\ntrain_datagen = ImageDataGenerator(\n    preprocessing_function=keras_preprocess, # ربط دالة المعالجة الخاصة بنا\n    rotation_range=20,                       # دوران عشوائي\n    zoom_range=0.15,                         # تقريب عشوائي\n    horizontal_flip=True,                    # انعكاس أفقي\n    vertical_flip=True,                      # انعكاس عمودي\n    fill_mode='constant', cval=0             # ملء الفراغات باللون الأسود إن وجدت\n)\n\n# إعدادات مولد التقييم (بدون Augmentation، فقط معالجة)\nval_datagen = ImageDataGenerator(\n    preprocessing_function=keras_preprocess\n)\n\n# تحديد حجم الدفعة (Batch Size) وحجم الصورة\nBATCH_SIZE = 32\nIMG_SIZE = (224, 224)\n\nprint(\"Building Train Generator...\")\ntrain_generator = train_datagen.flow_from_dataframe(\n    dataframe=train_data,\n    directory=TRAIN_IMG_PATH,\n    x_col=\"id_code\",\n    y_col=\"diagnosis\",\n    target_size=IMG_SIZE,\n    batch_size=BATCH_SIZE,\n    class_mode=\"binary\", # 🚀 التعديل هنا في كل من مولد التدريب والتقييم\n    seed=SEED\n)\n\nprint(\"\\nBuilding Validation Generator...\")\nval_generator = val_datagen.flow_from_dataframe(\n    dataframe=val_data,\n    directory=TRAIN_IMG_PATH,\n    x_col=\"id_code\",\n    y_col=\"diagnosis\",\n    target_size=IMG_SIZE,\n    batch_size=BATCH_SIZE,\n    class_mode=\"binary\", # 🚀 التعديل هنا في كل من مولد التدريب والتقييم\n    seed=SEED\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-07T19:48:59.062503Z","iopub.execute_input":"2026-07-07T19:48:59.062760Z","iopub.status.idle":"2026-07-07T19:49:08.210148Z","shell.execute_reply.started":"2026-07-07T19:48:59.062738Z","shell.execute_reply":"2026-07-07T19:49:08.209573Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 10. بناء نموذج CNN من الصفر (Custom Architecture)\nfrom tensorflow.keras.models import Sequential\nfrom tensorflow.keras.layers import Conv2D, MaxPooling2D, Flatten, Dense, Dropout, BatchNormalization\nfrom tensorflow.keras.optimizers import Adam\n\n# تعريف الموديل\nmodel = Sequential([\n    # Block 1: استخراج الميزات الأساسية (الحواف والألوان)\n    Conv2D(32, (3, 3), padding='same', activation='relu', input_shape=(224, 224, 3)),\n    BatchNormalization(),\n    MaxPooling2D(pool_size=(2, 2)),\n\n    # Block 2: استخراج ميزات أعقد\n    Conv2D(64, (3, 3), padding='same', activation='relu'),\n    BatchNormalization(),\n    MaxPooling2D(pool_size=(2, 2)),\n\n    # Block 3: استخراج ميزات متوسطة المستوى (بداية تشكل الأشكال)\n    Conv2D(128, (3, 3), padding='same', activation='relu'),\n    BatchNormalization(),\n    MaxPooling2D(pool_size=(2, 2)),\n\n    # Block 4: التركيز على التفاصيل الدقيقة للشبكية\n    Conv2D(256, (3, 3), padding='same', activation='relu'),\n    BatchNormalization(),\n    MaxPooling2D(pool_size=(2, 2)),\n\n    # Block 5: مستوى عميق جداً\n    Conv2D(512, (3, 3), padding='same', activation='relu'),\n    BatchNormalization(),\n    MaxPooling2D(pool_size=(2, 2)),\n    Dropout(0.3), # إضافة Dropout لمنع الحفظ بعد التعمق\n\n    # جزء التصنيف (Classification Head)\n    Flatten(),                             # تحويل الخرائط ثنائية الأبعاد إلى مصفوفة خطية\n    Dense(256, activation='relu'),         # طبقة مخفية للتحليل النهائي\n    BatchNormalization(),\n    Dropout(0.5),                          # إطفاء 50% من الخلايا لمنع الموديل من حفظ بيانات التدريب\n    # Dense(5, activation='softmax') # احذف هذا\n    Dense(1, activation='sigmoid')   # 🚀 ضع هذا بدلاً منه        # طبقة الخرج (5 فئات مع Softmax لاحتساب الاحتمالات)\n])\n\n# طباعة ملخص الموديل\nmodel.summary()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-07T19:49:08.211002Z","iopub.execute_input":"2026-07-07T19:49:08.211285Z","iopub.status.idle":"2026-07-07T19:49:10.468778Z","shell.execute_reply.started":"2026-07-07T19:49:08.211261Z","shell.execute_reply":"2026-07-07T19:49:10.468174Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from tensorflow.keras import mixed_precision\n\n# تفعيل سياسة الدقة المختلطة\npolicy = mixed_precision.Policy('mixed_float16')\nmixed_precision.set_global_policy(policy)\n\nprint('Compute dtype: %s' % policy.compute_dtype)\nprint('Variable dtype: %s' % policy.variable_dtype)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-07T19:49:10.469750Z","iopub.execute_input":"2026-07-07T19:49:10.470077Z","iopub.status.idle":"2026-07-07T19:49:10.474736Z","shell.execute_reply.started":"2026-07-07T19:49:10.470054Z","shell.execute_reply":"2026-07-07T19:49:10.473969Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 12. حساب أوزان الفئات (Class Weights) لحل مشكلة الـ Imbalance\nfrom sklearn.utils.class_weight import compute_class_weight\n\n# الحصول على الفئات بترتيبها\nlabels = train_data['diagnosis'].astype(int).values\n\n# حساب الأوزان برمجياً\nweights = compute_class_weight(\n    class_weight='balanced',\n    classes=np.unique(labels),\n    y=labels\n)\n\n# تحويل الأوزان إلى قاموس (Dictionary) ليفهمه Keras\nclass_weights_dict = dict(enumerate(weights))\n\nprint(\"Class Weights calculated:\")\nfor cls, weight in class_weights_dict.items():\n    print(f\"Class {cls}: Weight {weight:.2f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-07T19:49:10.475754Z","iopub.execute_input":"2026-07-07T19:49:10.476529Z","iopub.status.idle":"2026-07-07T19:49:10.492663Z","shell.execute_reply.started":"2026-07-07T19:49:10.476489Z","shell.execute_reply":"2026-07-07T19:49:10.491981Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 11. تجميع الموديل وتجهيز أدوات المراقبة (Compile & Callbacks)\nfrom tensorflow.keras.callbacks import ModelCheckpoint, ReduceLROnPlateau, EarlyStopping\n\n# 1. تجميع الموديل\n# استخدمنا Adam كـ Optimizer، و Categorical Crossentropy كدالة خسارة\nmodel.compile(\n    optimizer=Adam(learning_rate=0.0005), \n    loss='binary_crossentropy', # 🚀 التعديل هنا (بدلاً من categorical_crossentropy)\n    metrics=['accuracy']\n)\n# 2. أدوات المراقبة (Callbacks)\ncheckpoint = ModelCheckpoint(\n    'my_custom_cnn_model.h5', \n    monitor='val_accuracy', \n    save_best_only=True, \n    mode='max', \n    verbose=1\n)\n\nreduce_lr = ReduceLROnPlateau(\n    monitor='val_loss', \n    factor=0.3, \n    patience=3, \n    min_lr=0.000001, \n    verbose=1\n)\n\nearly_stop = EarlyStopping(\n    monitor='val_loss', \n    patience=8, # زدنا الصبر قليلاً لأن الموديلات من الصفر تحتاج وقت أطول للتعلم\n    restore_best_weights=True, \n    verbose=1\n)\n\ncallbacks_list = [checkpoint, reduce_lr, early_stop]\nprint(\"\\nCustom CNN Model is successfully compiled and ready for training!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-07T19:49:10.495274Z","iopub.execute_input":"2026-07-07T19:49:10.495590Z","iopub.status.idle":"2026-07-07T19:49:10.512618Z","shell.execute_reply.started":"2026-07-07T19:49:10.495569Z","shell.execute_reply":"2026-07-07T19:49:10.511810Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def load_and_preprocess_all_images(df, path, img_size=224):\n    all_images = []\n    for i, id_code in enumerate(df['id_code']):\n        img_path = os.path.join(path, id_code)\n        # استخدام دالة المعالجة التي كتبتها أنت سابقاً\n        img = preprocess_image(img_path, img_size) \n        all_images.append(img)\n        if i % 500 == 0: print(f\"Processed {i} images...\")\n    return np.array(all_images) / 255.0 # التطبيع\n\nprint(\"Processing Training Images...\")\nX_train = load_and_preprocess_all_images(train_data, TRAIN_IMG_PATH)\n# 🚀 التعديل هنا: نأخذ القيم 0 و 1 مباشرة كأرقام صحيحة\ny_train = train_data['diagnosis'].astype(int).values \n\nprint(\"Processing Validation Images...\")\nX_val = load_and_preprocess_all_images(val_data, TRAIN_IMG_PATH)\n# 🚀 التعديل هنا أيضا\ny_val = val_data['diagnosis'].astype(int).values","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-07T19:49:10.513372Z","iopub.execute_input":"2026-07-07T19:49:10.513687Z","iopub.status.idle":"2026-07-07T19:59:42.885530Z","shell.execute_reply.started":"2026-07-07T19:49:10.513667Z","shell.execute_reply":"2026-07-07T19:59:42.884881Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\n\n# الآن استخدم model.fit العادي بدون Generators (سيكون طلقة!)\nhistory = model.fit(\n    X_train, y_train,\n    batch_size=32,\n    epochs=50,\n    validation_data=(X_val, y_val),\n    class_weight=class_weights_dict,\n    callbacks=callbacks_list\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-07T19:59:42.886476Z","iopub.execute_input":"2026-07-07T19:59:42.886739Z","iopub.status.idle":"2026-07-07T20:01:47.966447Z","shell.execute_reply.started":"2026-07-07T19:59:42.886709Z","shell.execute_reply":"2026-07-07T20:01:47.965699Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# استخراج البيانات من متغير history\nacc = history.history['accuracy']\nval_acc = history.history['val_accuracy']\nloss = history.history['loss']\nval_loss = history.history['val_loss']\nepochs_range = range(len(acc))\n\n# رسم النتائج\nplt.figure(figsize=(15, 5))\n\n# رسم الدقة\nplt.subplot(1, 2, 1)\nplt.plot(epochs_range, acc, label='Training Accuracy')\nplt.plot(epochs_range, val_acc, label='Validation Accuracy')\nplt.title('Training and Validation Accuracy')\nplt.xlabel('Epochs')\nplt.ylabel('Accuracy')\nplt.legend(loc='lower right')\nplt.grid(True)\n\n# رسم الخسارة\nplt.subplot(1, 2, 2)\nplt.plot(epochs_range, loss, label='Training Loss')\nplt.plot(epochs_range, val_loss, label='Validation Loss')\nplt.title('Training and Validation Loss')\nplt.xlabel('Epochs')\nplt.ylabel('Loss')\nplt.legend(loc='upper right')\nplt.grid(True)\n\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-07T20:01:47.967564Z","iopub.execute_input":"2026-07-07T20:01:47.967903Z","iopub.status.idle":"2026-07-07T20:01:48.225612Z","shell.execute_reply.started":"2026-07-07T20:01:47.967879Z","shell.execute_reply":"2026-07-07T20:01:48.224823Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from sklearn.metrics import confusion_matrix, classification_report\nimport seaborn as sns\nval_preds = model.predict(X_val)\nval_preds_classes = (val_preds > 0.5).astype(int).reshape(-1)\ny_true = y_val\n\n# طباعة مصفوفة الارتباك والتقرير\ncm = confusion_matrix(y_true, val_preds_classes)\nsns.heatmap(cm, annot=True, fmt='d', cmap='Blues', \n            xticklabels=['Normal', 'Diabetic Retinopathy'],\n            yticklabels=['Normal', 'Diabetic Retinopathy'])\nplt.show()\n\nprint(classification_report(y_true, val_preds_classes, target_names=['Normal', 'DR']))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-07T20:01:48.226697Z","iopub.execute_input":"2026-07-07T20:01:48.227096Z","iopub.status.idle":"2026-07-07T20:01:51.155012Z","shell.execute_reply.started":"2026-07-07T20:01:48.227066Z","shell.execute_reply":"2026-07-07T20:01:51.154390Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import tensorflow as tf\nimport matplotlib.cm as cm\nimport matplotlib as mpl # أضفنا هذه لحل مشكلة التحذير\n\ndef get_img_array(img_path, size):\n    \"\"\"قراءة الصورة ومعالجتها وتطبيعها لتطابق بيانات التدريب\"\"\"\n    img = preprocess_image(img_path, img_size=size)\n    \n    # 🚀 التعديل الأهم: التطبيع (Normalization) ضروري جداً!\n    img_normalized = img.astype(np.float32) / 255.0 \n    \n    img_array = np.expand_dims(img_normalized, axis=0)\n    \n    # نرجع img (بدون تطبيع) للرسم، و img_array (المطبعة) للموديل\n    return img, img_array \n\ndef make_gradcam_heatmap(img_array, model, last_conv_layer_name):\n    \"\"\"بناء الخريطة الحرارية (Heatmap) بطريقة مضمونة لـ Sequential Models\"\"\"\n    inputs = tf.keras.Input(shape=(224, 224, 3))\n    x = inputs\n    conv_output = None\n    \n    for layer in model.layers:\n        x = layer(x)\n        if layer.name == last_conv_layer_name:\n            conv_output = x\n            \n    grad_model = tf.keras.models.Model(inputs, [conv_output, x])\n\n    with tf.GradientTape() as tape:\n        last_conv_layer_output, preds = grad_model(img_array)\n        class_channel = preds[0][0] \n\n    grads = tape.gradient(class_channel, last_conv_layer_output)\n    pooled_grads = tf.reduce_mean(grads, axis=(0, 1, 2))\n\n    last_conv_layer_output = last_conv_layer_output[0]\n    heatmap = last_conv_layer_output @ pooled_grads[..., tf.newaxis]\n    heatmap = tf.squeeze(heatmap)\n\n    heatmap = tf.maximum(heatmap, 0) / tf.math.reduce_max(heatmap)\n    return heatmap.numpy()\n\ndef display_gradcam(img_path, heatmap, alpha=0.4):\n    \"\"\"دمج الخريطة الحرارية مع الصورة الأصلية وعرضها\"\"\"\n    original_img = cv2.imread(img_path)\n    original_img = cv2.cvtColor(original_img, cv2.COLOR_BGR2RGB)\n    original_img = cv2.resize(original_img, (224, 224))\n\n    heatmap = np.uint8(255 * heatmap)\n    \n    # 🚀 التعديل الثاني: حل مشكلة التحذير الزهري (Deprecation Warning)\n    jet = mpl.colormaps['jet']\n    jet_colors = jet(np.arange(256))[:, :3]\n    jet_heatmap = jet_colors[heatmap]\n\n    jet_heatmap = tf.keras.preprocessing.image.array_to_img(jet_heatmap)\n    jet_heatmap = jet_heatmap.resize((original_img.shape[1], original_img.shape[0]))\n    jet_heatmap = tf.keras.preprocessing.image.img_to_array(jet_heatmap)\n\n    superimposed_img = jet_heatmap * alpha + original_img\n    superimposed_img = tf.keras.preprocessing.image.array_to_img(superimposed_img)\n\n    return original_img, superimposed_img\n\nprint(\"Grad-CAM functions updated successfully!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-07T20:01:51.156069Z","iopub.execute_input":"2026-07-07T20:01:51.156949Z","iopub.status.idle":"2026-07-07T20:01:51.166689Z","shell.execute_reply.started":"2026-07-07T20:01:51.156913Z","shell.execute_reply":"2026-07-07T20:01:51.165891Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 1. البحث عن اسم آخر طبقة Convolutional في الموديل الخاص بك ديناميكياً\nlast_conv_layer = [layer.name for layer in model.layers if isinstance(layer, Conv2D)][-1]\nprint(f\"Last Conv Layer used for Grad-CAM: {last_conv_layer}\")\n\n# 2. 🚀 التعديل هنا: استخدام train_data وتحويل النوع إلى int مؤقتاً للبحث\nsample_sick_patient = train_data[train_data['diagnosis'].astype(int) == 1].sample(1, random_state=42)\nsample_img_name = sample_sick_patient['id_code'].values[0]\nimg_path = os.path.join(TRAIN_IMG_PATH, sample_img_name)\n\n# 3. استخراج الصورة والتوقع والخريطة الحرارية\nimg_processed, img_array = get_img_array(img_path, size=224)\nprediction = model.predict(img_array, verbose=0)[0][0] # التنبؤ\nis_sick = \"Diabetic Retinopathy\" if prediction > 0.5 else \"Normal\"\nconfidence = prediction if prediction > 0.5 else (1 - prediction)\n\n# توليد الخريطة الحرارية\nheatmap = make_gradcam_heatmap(img_array, model, last_conv_layer)\n\n# دمج الصورة وعرضها\noriginal, overlay = display_gradcam(img_path, heatmap)\n\n# 4. الرسم المذهل للجنة التحكيم\nfig, axes = plt.subplots(1, 3, figsize=(18, 6))\n\naxes[0].imshow(original)\naxes[0].set_title(\"1. Original Image (Raw)\", fontsize=14)\naxes[0].axis('off')\n\naxes[1].imshow(heatmap, cmap='jet')\naxes[1].set_title(\"2. Grad-CAM Heatmap (AI Focus)\", fontsize=14)\naxes[1].axis('off')\n\naxes[2].imshow(overlay)\naxes[2].set_title(f\"3. AI Diagnosis: {is_sick} ({confidence:.1%} Confidence)\", fontsize=14, color='darkred')\naxes[2].axis('off')\n\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-07T20:01:51.167854Z","iopub.execute_input":"2026-07-07T20:01:51.168135Z","iopub.status.idle":"2026-07-07T20:01:53.957535Z","shell.execute_reply.started":"2026-07-07T20:01:51.168112Z","shell.execute_reply":"2026-07-07T20:01:53.956374Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# حفظ الموديل بالصيغة الحديثة والمدعومة\nmodel.save('diabetic_retinopathy_model.keras')\nprint(\"Model saved successfully! You can download it now.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-07T20:01:53.958885Z","iopub.execute_input":"2026-07-07T20:01:53.959303Z","iopub.status.idle":"2026-07-07T20:01:54.333787Z","shell.execute_reply.started":"2026-07-07T20:01:53.959278Z","shell.execute_reply":"2026-07-07T20:01:54.333116Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import gradio as gr\nimport cv2\nimport numpy as np\nimport tensorflow as tf\nimport matplotlib as mpl\n\n# 1. تهيئة النموذج الأساسي\n# سيتم استخدام هذا النموذج للتنبؤ ولتوليد الخريطة الحرارية (Grad-CAM)\nkeras_model = tf.keras.models.load_model('diabetic_retinopathy_model.keras')\n\n# البحث عن آخر طبقة التفافية ديناميكياً\nlast_conv_layer_name = [layer.name for layer in keras_model.layers if isinstance(layer, tf.keras.layers.Conv2D)][-1]\n\n# 2. دوال Grad-CAM\ndef make_gradcam_heatmap(img_array, model, last_conv_layer_name):\n    inputs = tf.keras.Input(shape=(224, 224, 3))\n    x = inputs\n    conv_output = None\n    \n    for layer in model.layers:\n        x = layer(x)\n        if layer.name == last_conv_layer_name:\n            conv_output = x\n            \n    grad_model = tf.keras.models.Model(inputs, [conv_output, x])\n\n    with tf.GradientTape() as tape:\n        last_conv_layer_output, preds = grad_model(img_array)\n        class_channel = preds[0][0] \n\n    grads = tape.gradient(class_channel, last_conv_layer_output)\n    pooled_grads = tf.reduce_mean(grads, axis=(0, 1, 2))\n\n    last_conv_layer_output = last_conv_layer_output[0]\n    heatmap = last_conv_layer_output @ pooled_grads[..., tf.newaxis]\n    heatmap = tf.squeeze(heatmap)\n    heatmap = tf.maximum(heatmap, 0) / tf.math.reduce_max(heatmap)\n    return heatmap.numpy()\n\ndef display_gradcam(processed_img, heatmap, alpha=0.4):\n    # نستخدم الصورة المعالجة بدلا من الأصلية ليكون التركيز في مكانه الدقيق\n    heatmap = np.uint8(255 * heatmap)\n    \n    jet = mpl.colormaps['jet']\n    jet_colors = jet(np.arange(256))[:, :3]\n    jet_heatmap = jet_colors[heatmap]\n\n    jet_heatmap = tf.keras.preprocessing.image.array_to_img(jet_heatmap)\n    jet_heatmap = jet_heatmap.resize((processed_img.shape[1], processed_img.shape[0]))\n    jet_heatmap = tf.keras.preprocessing.image.img_to_array(jet_heatmap)\n\n    superimposed_img = jet_heatmap * alpha + processed_img\n    superimposed_img = tf.keras.preprocessing.image.array_to_img(superimposed_img)\n    \n    return np.array(superimposed_img)\n\n# 3. دالة الواجهة الرئيسية\ndef gradio_predict_and_explain(image):\n    # أ. المعالجة المسبقة\n    tol = 7\n    gray_img = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)\n    mask = gray_img > tol\n    \n    check_shape = image[:,:,0][np.ix_(mask.any(1), mask.any(0))].shape[0]\n    if check_shape != 0:\n        img1 = image[:,:,0][np.ix_(mask.any(1), mask.any(0))]\n        img2 = image[:,:,1][np.ix_(mask.any(1), mask.any(0))]\n        img3 = image[:,:,2][np.ix_(mask.any(1), mask.any(0))]\n        processed_image = np.dstack([img1, img2, img3])\n    else:\n        processed_image = image\n        \n    processed_image = cv2.resize(processed_image, (224, 224))\n    processed_image_filtered = cv2.addWeighted(processed_image, 4, cv2.GaussianBlur(processed_image, (0,0), 10), -4, 128)\n    \n    img_normalized = processed_image_filtered.astype(np.float32) / 255.0\n    input_data = np.expand_dims(img_normalized, axis=0)\n    \n    # ب. التنبؤ عبر النموذج العادي (Keras Model)\n    prediction = keras_model.predict(input_data)\n    prediction_prob = prediction[0][0]\n    \n    probability_dr = float(prediction_prob)\n    probability_normal = float(1.0 - prediction_prob)\n    labels_dict = {\"Diabetic Retinopathy\": probability_dr, \"Normal (No DR)\": probability_normal}\n    \n    # ج. توليد الخريطة الحرارية عبر النموذج العادي\n    heatmap = make_gradcam_heatmap(input_data, keras_model, last_conv_layer_name)\n    overlay_img = display_gradcam(processed_image, heatmap)\n    \n    return labels_dict, overlay_img\n\n# 4. تشغيل الواجهة\ninterface = gr.Interface(\n    fn=gradio_predict_and_explain,\n    inputs=gr.Image(label=\"ارفع صورة شبكية العين هنا\"),\n    outputs=[\n        gr.Label(num_top_classes=2, label=\"نتيجة التشخيص\"),\n        gr.Image(label=\"التفسيرية (أماكن تركيز الذكاء الاصطناعي)\")\n    ],\n    title=\"نظام تشخيص اعتلال الشبكية السكري المفسر\",\n    description=\"يقوم النظام بالتنبؤ بحالة العين باستخدام النموذج الأساسي، ويعرض خريطة حرارية توضح الأجزاء التي استند إليها النموذج لاتخاذ قراره.\",\n    allow_flagging=\"never\"\n)\n\ninterface.launch(share=True)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-07T21:03:40.403496Z","iopub.execute_input":"2026-07-07T21:03:40.404110Z","iopub.status.idle":"2026-07-07T21:03:41.668101Z","shell.execute_reply.started":"2026-07-07T21:03:40.404081Z","shell.execute_reply":"2026-07-07T21:03:41.667510Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"","metadata":{}},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}