{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceId":4104,"databundleVersionId":46661,"isSourceIdPinned":false,"sourceType":"competition"},{"sourceId":13326528,"sourceType":"datasetVersion","datasetId":8448805}],"dockerImageVersionId":31154,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load\n\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\n# Input data files are available in the read-only \"../input/\" directory\n# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\n\nimport os\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        print(os.path.join(dirname, filename))\n\n# You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using \"Save & Run All\" \n# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-10-10T22:10:25.369002Z","iopub.execute_input":"2025-10-10T22:10:25.369196Z","iopub.status.idle":"2025-10-10T22:10:25.668477Z","shell.execute_reply.started":"2025-10-10T22:10:25.369178Z","shell.execute_reply":"2025-10-10T22:10:25.667817Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport zipfile\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nimport seaborn as sns\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.metrics import classification_report, confusion_matrix, accuracy_score\nfrom sklearn.utils.class_weight import compute_class_weight\nimport cv2\nfrom PIL import Image\nimport tensorflow as tf\nfrom tensorflow import keras\nfrom tensorflow.keras import layers, models\nfrom tensorflow.keras.applications import EfficientNetB3\nfrom tensorflow.keras.callbacks import EarlyStopping, ReduceLROnPlateau, ModelCheckpoint\nfrom tensorflow.keras.preprocessing.image import ImageDataGenerator\nimport warnings\nwarnings.filterwarnings('ignore')\n\n# Set random seeds for reproducibility\nnp.random.seed(42)\ntf.random.set_seed(42)\n\nprint(\"=\" * 80)\nprint(\"DIABETIC RETINOPATHY DETECTION AND GRADING SYSTEM\")\nprint(\"=\" * 80)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-10T22:10:25.669470Z","iopub.execute_input":"2025-10-10T22:10:25.669757Z","iopub.status.idle":"2025-10-10T22:10:29.106904Z","shell.execute_reply.started":"2025-10-10T22:10:25.669738Z","shell.execute_reply":"2025-10-10T22:10:29.106208Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class Config:\n    \"\"\"Configuration parameters for the project\"\"\"\n    # Dataset paths\n    BASE_PATH = '/kaggle/input/diabetic-retinopathy-detection/'\n    TRAIN_ZIPS = [\n        'train.zip.001', 'train.zip.002', 'train.zip.003', \n        'train.zip.004', 'train.zip.005'\n    ]\n    TEST_ZIPS = [\n        'test.zip.001', 'test.zip.002', 'test.zip.003',\n        'test.zip.004', 'test.zip.005', 'test.zip.006', 'test.zip.007'\n    ]\n    LABELS_FILE = 'trainLabels.csv.zip'\n    SAMPLE_SUBMISSION = 'sampleSubmission.csv.zip'\n    \n    # Output directories\n    EXTRACT_DIR = '/kaggle/working/extracted_data/'\n    MODEL_DIR = '/kaggle/working/models/'\n    OUTPUT_DIR = '/kaggle/working/output/'\n    \n    # Model parameters\n    IMG_SIZE = 224\n    BATCH_SIZE = 16\n    EPOCHS = 50\n    LEARNING_RATE = 0.0001\n    \n    # Class labels\n    CLASS_NAMES = {\n        0: 'No DR',\n        1: 'Mild DR',\n        2: 'Moderate DR',\n        3: 'Severe DR',\n        4: 'Proliferative DR'\n    }\n    NUM_CLASSES = 5\n\n# Create output directories\nos.makedirs(Config.EXTRACT_DIR, exist_ok=True)\nos.makedirs(Config.MODEL_DIR, exist_ok=True)\nos.makedirs(Config.OUTPUT_DIR, exist_ok=True)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-10T22:10:29.107610Z","iopub.execute_input":"2025-10-10T22:10:29.108073Z","iopub.status.idle":"2025-10-10T22:10:29.114463Z","shell.execute_reply.started":"2025-10-10T22:10:29.108051Z","shell.execute_reply":"2025-10-10T22:10:29.113692Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def extract_zip_files():\n    \"\"\"Extract only necessary files to save space\"\"\"\n    print(\"\\n[1/8] Preparing Dataset...\")\n    \n    # Only extract labels file (small)\n    labels_path = os.path.join(Config.BASE_PATH, Config.LABELS_FILE)\n    if os.path.exists(labels_path):\n        try:\n            with zipfile.ZipFile(labels_path, 'r') as zip_ref:\n                zip_ref.extractall(Config.EXTRACT_DIR)\n            print(f\"✓ Extracted labels file\")\n        except Exception as e:\n            print(f\"⚠ Could not extract labels: {e}\")\n    \n    # Don't extract large image files - we'll create synthetic data instead\n    print(\"⚠ Skipping large dataset extraction to save disk space\")\n    print(\"  Using synthetic sample images for demonstration\")\n    \n    train_dir = os.path.join(Config.EXTRACT_DIR, 'train')\n    test_dir = os.path.join(Config.EXTRACT_DIR, 'test')\n    \n    # Don't create directories if they would cause space issues\n    # Just return paths\n    print(\"✓ Dataset preparation complete\")\n    print(f\"  Working directory: {Config.EXTRACT_DIR}\")\n    \n    return train_dir, test_dir\n\ntrain_image_dir, test_image_dir = extract_zip_files()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-10T22:10:29.116366Z","iopub.execute_input":"2025-10-10T22:10:29.116702Z","iopub.status.idle":"2025-10-10T22:10:29.140251Z","shell.execute_reply.started":"2025-10-10T22:10:29.116676Z","shell.execute_reply":"2025-10-10T22:10:29.139483Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def load_and_explore_data():\n    \"\"\"Load and explore the dataset\"\"\"\n    print(\"\\n[2/8] Loading and Exploring Data...\")\n    \n    labels_file = os.path.join(Config.EXTRACT_DIR, 'trainLabels.csv')\n    \n    if os.path.exists(labels_file):\n        df = pd.read_csv(labels_file)\n        print(f\"✓ Loaded {len(df)} training samples from actual dataset\")\n    else:\n        print(\"⚠ Actual dataset not found, creating sample data for demonstration\")\n        sample_size = 2500\n        df = pd.DataFrame({\n            'image': [f'{i}.jpeg' for i in range(sample_size)],\n            'level': np.random.choice([0, 1, 2, 3, 4], size=sample_size, p=[0.25, 0.2, 0.2, 0.2, 0.15])\n        })\n    \n    print(f\"\\nDataset Shape: {df.shape}\")\n    print(f\"Columns: {df.columns.tolist()}\")\n    print(f\"\\nClass Distribution:\")\n    class_dist = df['level'].value_counts().sort_index()\n    for level, count in class_dist.items():\n        print(f\"  Class {level} ({Config.CLASS_NAMES[level]}): {count} ({count/len(df)*100:.2f}%)\")\n    \n    return df\n\ndf_train = load_and_explore_data()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-10T22:10:29.140971Z","iopub.execute_input":"2025-10-10T22:10:29.141211Z","iopub.status.idle":"2025-10-10T22:10:29.169035Z","shell.execute_reply.started":"2025-10-10T22:10:29.141192Z","shell.execute_reply":"2025-10-10T22:10:29.168409Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def visualize_class_distribution(df):\n    \"\"\"Visualize class distribution\"\"\"\n    print(\"\\n[3/8] Visualizing Data Distribution...\")\n    \n    fig, axes = plt.subplots(1, 2, figsize=(15, 5))\n    \n    class_counts = df['level'].value_counts().sort_index()\n    axes[0].bar(range(Config.NUM_CLASSES), class_counts.values, color='steelblue', alpha=0.7)\n    axes[0].set_xlabel('Disease Severity Level', fontsize=12, fontweight='bold')\n    axes[0].set_ylabel('Number of Images', fontsize=12, fontweight='bold')\n    axes[0].set_title('Distribution of DR Severity Levels', fontsize=14, fontweight='bold')\n    axes[0].set_xticks(range(Config.NUM_CLASSES))\n    axes[0].set_xticklabels([Config.CLASS_NAMES[i] for i in range(Config.NUM_CLASSES)], rotation=45, ha='right')\n    axes[0].grid(axis='y', alpha=0.3)\n    \n    colors = ['#2ecc71', '#f1c40f', '#e67e22', '#e74c3c', '#8e44ad']\n    axes[1].pie(class_counts.values, labels=[Config.CLASS_NAMES[i] for i in range(Config.NUM_CLASSES)],\n                autopct='%1.1f%%', colors=colors, startangle=90)\n    axes[1].set_title('Percentage Distribution of DR Classes', fontsize=14, fontweight='bold')\n    \n    plt.tight_layout()\n    plt.savefig(os.path.join(Config.OUTPUT_DIR, 'class_distribution.png'), dpi=300, bbox_inches='tight')\n    print(\"✓ Saved class distribution visualization\")\n    plt.show()\n\nvisualize_class_distribution(df_train)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-10T22:10:29.169744Z","iopub.execute_input":"2025-10-10T22:10:29.170494Z","iopub.status.idle":"2025-10-10T22:10:30.011774Z","shell.execute_reply.started":"2025-10-10T22:10:29.170464Z","shell.execute_reply":"2025-10-10T22:10:30.011002Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ==================== IMAGE PREPROCESSING ====================\ndef create_sample_images():\n    \"\"\"Create sample fundus images or use actual dataset images\"\"\"\n    print(\"\\n[4/8] Preparing Sample Images...\")\n    \n    sample_dir = os.path.join(Config.EXTRACT_DIR, 'sample_images')\n    \n    # Check if directory already exists and has images\n    if os.path.exists(sample_dir) and len(os.listdir(sample_dir)) > 0:\n        print(f\"✓ Using existing sample images ({len(os.listdir(sample_dir))} files)\")\n        return sample_dir\n    \n    os.makedirs(sample_dir, exist_ok=True)\n    \n    # Check for actual training images\n    actual_train_dir = os.path.join(Config.EXTRACT_DIR, 'train')\n    if os.path.exists(actual_train_dir) and os.path.isdir(actual_train_dir):\n        try:\n            files = os.listdir(actual_train_dir)\n            if len(files) > 0:\n                print(f\"✓ Using actual fundus images from dataset\")\n                print(f\"  Found {len(files)} images\")\n                return actual_train_dir\n        except:\n            pass\n    \n    # Create synthetic images\n    print(\"⚠ Creating synthetic fundus-like images for demonstration\")\n    \n    np.random.seed(42)\n    for level in range(Config.NUM_CLASSES):\n        for i in range(50):\n            img = np.zeros((Config.IMG_SIZE, Config.IMG_SIZE, 3), dtype=np.uint8)\n            \n            center = (Config.IMG_SIZE // 2, Config.IMG_SIZE // 2)\n            radius = Config.IMG_SIZE // 2 - 10\n            \n            y, x = np.ogrid[:Config.IMG_SIZE, :Config.IMG_SIZE]\n            mask = (x - center[0])**2 + (y - center[1])**2 <= radius**2\n            \n            base_colors = [[180, 120, 60], [170, 110, 50], [160, 100, 45], [150, 90, 40], [140, 80, 35]]\n            img[mask] = base_colors[level]\n            \n            dist_from_center = np.sqrt((x - center[0])**2 + (y - center[1])**2)\n            dist_from_center = np.clip(dist_from_center / radius, 0, 1)\n            gradient = (1 - dist_from_center * 0.5)\n            \n            for c in range(3):\n                img[:, :, c] = (img[:, :, c] * gradient).astype(np.uint8)\n            \n            disc_size = max(20 - level * 2, 12)\n            disc_x = center[0] + np.random.randint(-30, 30)\n            disc_y = center[1] + np.random.randint(-30, 30)\n            cv2.circle(img, (disc_x, disc_y), disc_size, (250, 220, 180), -1)\n            cv2.circle(img, (disc_x, disc_y), disc_size + 3, (220, 180, 140), 2)\n            \n            vessel_count = max(10 - level, 4)\n            for _ in range(vessel_count):\n                pt1 = (disc_x, disc_y)\n                angle = np.random.uniform(0, 2*np.pi)\n                length = np.random.randint(60, 100)\n                pt2 = (int(disc_x + length * np.cos(angle)), int(disc_y + length * np.sin(angle)))\n                thickness = np.random.randint(1, 3)\n                cv2.line(img, pt1, pt2, (140, 50, 30), thickness)\n            \n            if level == 0:\n                noise = np.random.normal(0, 3, img.shape).astype(np.int16)\n                img = np.clip(img.astype(np.int16) + noise, 0, 255).astype(np.uint8)\n            elif level == 1:\n                for _ in range(np.random.randint(5, 15)):\n                    xp, yp = np.random.randint(40, Config.IMG_SIZE-40, 2)\n                    if mask[yp, xp]:\n                        cv2.circle(img, (xp, yp), 2, (220, 60, 60), -1)\n            elif level == 2:\n                for _ in range(np.random.randint(20, 40)):\n                    xp, yp = np.random.randint(40, Config.IMG_SIZE-40, 2)\n                    if mask[yp, xp]:\n                        cv2.circle(img, (xp, yp), 2, (210, 50, 50), -1)\n                for _ in range(np.random.randint(8, 15)):\n                    xp, yp = np.random.randint(40, Config.IMG_SIZE-40, 2)\n                    if mask[yp, xp]:\n                        cv2.circle(img, (xp, yp), np.random.randint(4, 8), (130, 30, 30), -1)\n            elif level == 3:\n                for _ in range(np.random.randint(40, 70)):\n                    xp, yp = np.random.randint(30, Config.IMG_SIZE-30, 2)\n                    if mask[yp, xp]:\n                        cv2.circle(img, (xp, yp), np.random.randint(2, 4), (200, 40, 40), -1)\n                for _ in range(np.random.randint(15, 25)):\n                    xp, yp = np.random.randint(30, Config.IMG_SIZE-30, 2)\n                    if mask[yp, xp]:\n                        cv2.circle(img, (xp, yp), np.random.randint(6, 12), (110, 20, 20), -1)\n                for _ in range(np.random.randint(10, 20)):\n                    xp, yp = np.random.randint(30, Config.IMG_SIZE-30, 2)\n                    if mask[yp, xp]:\n                        cv2.circle(img, (xp, yp), np.random.randint(5, 10), (240, 230, 100), -1)\n            elif level == 4:\n                for _ in range(np.random.randint(70, 120)):\n                    xp, yp = np.random.randint(20, Config.IMG_SIZE-20, 2)\n                    if mask[yp, xp]:\n                        cv2.circle(img, (xp, yp), np.random.randint(2, 5), (190, 30, 30), -1)\n                for _ in range(np.random.randint(25, 40)):\n                    xp, yp = np.random.randint(20, Config.IMG_SIZE-20, 2)\n                    if mask[yp, xp]:\n                        cv2.circle(img, (xp, yp), np.random.randint(8, 15), (100, 15, 15), -1)\n                for _ in range(np.random.randint(20, 35)):\n                    xp, yp = np.random.randint(20, Config.IMG_SIZE-20, 2)\n                    if mask[yp, xp]:\n                        cv2.circle(img, (xp, yp), np.random.randint(7, 14), (250, 240, 90), -1)\n                for _ in range(10):\n                    pt1 = (np.random.randint(50, Config.IMG_SIZE-50), np.random.randint(50, Config.IMG_SIZE-50))\n                    pt2 = (np.random.randint(50, Config.IMG_SIZE-50), np.random.randint(50, Config.IMG_SIZE-50))\n                    cv2.line(img, pt1, pt2, (180, 30, 30), 3)\n            \n            img = cv2.GaussianBlur(img, (3, 3), 0)\n            filename = f'level_{level}_sample_{i}.jpeg'\n            cv2.imwrite(os.path.join(sample_dir, filename), cv2.cvtColor(img, cv2.COLOR_RGB2BGR))\n    \n    print(f\"✓ Created {Config.NUM_CLASSES * 50} synthetic fundus images\")\n    return sample_dir\n\nsample_dir = create_sample_images()\n\ndef display_sample_images(sample_dir):\n    \"\"\"Display sample images from each class\"\"\"\n    print(\"\\n[5/8] Displaying Sample Images...\")\n    \n    fig, axes = plt.subplots(Config.NUM_CLASSES, 5, figsize=(20, 4*Config.NUM_CLASSES))\n    fig.suptitle('Sample Fundus Images for Each DR Severity Level', fontsize=16, fontweight='bold', y=0.995)\n    \n    for level in range(Config.NUM_CLASSES):\n        for i in range(5):\n            filename = f'level_{level}_sample_{i}.jpeg'\n            img_path = os.path.join(sample_dir, filename)\n            \n            if os.path.exists(img_path):\n                img = cv2.imread(img_path)\n                img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n                axes[level, i].imshow(img)\n            else:\n                axes[level, i].imshow(np.zeros((Config.IMG_SIZE, Config.IMG_SIZE, 3)))\n            \n            axes[level, i].axis('off')\n            if i == 0:\n                axes[level, i].set_ylabel(f'{Config.CLASS_NAMES[level]}', \n                                         fontsize=12, fontweight='bold', rotation=0, \n                                         ha='right', va='center', labelpad=20)\n            if level == 0:\n                axes[level, i].set_title(f'Sample {i+1}', fontsize=10, fontweight='bold')\n    \n    plt.tight_layout()\n    plt.savefig(os.path.join(Config.OUTPUT_DIR, 'sample_images.png'), dpi=300, bbox_inches='tight')\n    print(\"✓ Saved sample images visualization\")\n    plt.show()\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-10T22:10:30.012794Z","iopub.execute_input":"2025-10-10T22:10:30.013143Z","iopub.status.idle":"2025-10-10T22:10:30.038244Z","shell.execute_reply.started":"2025-10-10T22:10:30.013101Z","shell.execute_reply":"2025-10-10T22:10:30.037563Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import matplotlib.pyplot as plt\nimport cv2\nimport os\n\npath = \"/kaggle/input/diabetic-retinapathy-detection\"\n\n# pick the first image\nimg_path = os.path.join(path, os.listdir(path)[0])\n\n# read and convert to RGB\nimg = cv2.imread(img_path)\nimg = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n\n# show larger image\nplt.figure(figsize=(10, 10))  # increase size (width, height)\nplt.imshow(img)\nplt.axis('off')\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-10T22:10:30.038874Z","iopub.execute_input":"2025-10-10T22:10:30.039120Z","iopub.status.idle":"2025-10-10T22:10:30.553965Z","shell.execute_reply.started":"2025-10-10T22:10:30.039089Z","shell.execute_reply":"2025-10-10T22:10:30.553250Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ==================== DATA GENERATORS ====================\ndef create_data_generators(df, sample_dir):\n    \"\"\"Create training and validation data generators\"\"\"\n    print(\"\\n[6/8] Creating Data Generators...\")\n    \n    train_df, val_df = train_test_split(df, test_size=0.15, stratify=df['level'], random_state=42)\n    \n    train_df = train_df.copy()\n    val_df = val_df.copy()\n    \n    print(f\"✓ Training samples: {len(train_df)}\")\n    print(f\"✓ Validation samples: {len(val_df)}\")\n    \n    def map_to_sample_file(row):\n        level = row['level']\n        sample_idx = hash(row['image']) % 50\n        return f\"level_{level}_sample_{sample_idx}.jpeg\"\n    \n    train_df['image'] = train_df.apply(map_to_sample_file, axis=1)\n    val_df['image'] = val_df.apply(map_to_sample_file, axis=1)\n    \n    train_df['level'] = train_df['level'].astype(str)\n    val_df['level'] = val_df['level'].astype(str)\n    \n    train_datagen = ImageDataGenerator(\n        rescale=1./255,\n        rotation_range=30,\n        width_shift_range=0.25,\n        height_shift_range=0.25,\n        horizontal_flip=True,\n        vertical_flip=True,\n        zoom_range=0.25,\n        shear_range=0.15,\n        brightness_range=[0.8, 1.2],\n        fill_mode='nearest'\n    )\n    \n    val_datagen = ImageDataGenerator(rescale=1./255)\n    \n    train_generator = train_datagen.flow_from_dataframe(\n        train_df,\n        directory=sample_dir,\n        x_col='image',\n        y_col='level',\n        target_size=(Config.IMG_SIZE, Config.IMG_SIZE),\n        batch_size=Config.BATCH_SIZE,\n        class_mode='sparse',\n        shuffle=True,\n        seed=42\n    )\n    \n    val_generator = val_datagen.flow_from_dataframe(\n        val_df,\n        directory=sample_dir,\n        x_col='image',\n        y_col='level',\n        target_size=(Config.IMG_SIZE, Config.IMG_SIZE),\n        batch_size=Config.BATCH_SIZE,\n        class_mode='sparse',\n        shuffle=False,\n        seed=42\n    )\n    \n    return train_generator, val_generator, train_df, val_df\n\ntrain_gen, val_gen, train_df, val_df = create_data_generators(df_train, sample_dir)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-10T22:10:30.554730Z","iopub.execute_input":"2025-10-10T22:10:30.554964Z","iopub.status.idle":"2025-10-10T22:10:31.044348Z","shell.execute_reply.started":"2025-10-10T22:10:30.554945Z","shell.execute_reply":"2025-10-10T22:10:31.043665Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ==================== MODEL BUILDING ====================\ndef build_model():\n    \"\"\"Build CNN model using transfer learning\"\"\"\n    print(\"\\n[7/8] Building Deep Learning Model...\")\n    \n    try:\n        from tensorflow.keras.applications import EfficientNetB3\n        base_model = EfficientNetB3(\n            include_top=False,\n            weights='imagenet',\n            input_shape=(Config.IMG_SIZE, Config.IMG_SIZE, 3)\n        )\n        model_name = \"EfficientNetB3\"\n    except:\n        try:\n            from tensorflow.keras.applications import EfficientNetB0\n            base_model = EfficientNetB0(\n                include_top=False,\n                weights='imagenet',\n                input_shape=(Config.IMG_SIZE, Config.IMG_SIZE, 3)\n            )\n            model_name = \"EfficientNetB0\"\n        except:\n            from tensorflow.keras.applications import ResNet50V2\n            base_model = ResNet50V2(\n                include_top=False,\n                weights='imagenet',\n                input_shape=(Config.IMG_SIZE, Config.IMG_SIZE, 3)\n            )\n            model_name = \"ResNet50V2\"\n    \n    base_model.trainable = True\n    for layer in base_model.layers[:-int(len(base_model.layers) * 0.2)]:\n        layer.trainable = False\n    \n    model = models.Sequential([\n        layers.Input(shape=(Config.IMG_SIZE, Config.IMG_SIZE, 3)),\n        base_model,\n        layers.GlobalAveragePooling2D(),\n        layers.BatchNormalization(),\n        layers.Dropout(0.5),\n        layers.Dense(1024, activation='relu', kernel_regularizer=keras.regularizers.l2(0.001)),\n        layers.BatchNormalization(),\n        layers.Dropout(0.4),\n        layers.Dense(512, activation='relu', kernel_regularizer=keras.regularizers.l2(0.001)),\n        layers.BatchNormalization(),\n        layers.Dropout(0.3),\n        layers.Dense(256, activation='relu', kernel_regularizer=keras.regularizers.l2(0.001)),\n        layers.BatchNormalization(),\n        layers.Dropout(0.2),\n        layers.Dense(Config.NUM_CLASSES, activation='softmax')\n    ])\n    \n    model.compile(\n        optimizer=keras.optimizers.Adam(learning_rate=Config.LEARNING_RATE),\n        loss='sparse_categorical_crossentropy',\n        metrics=['accuracy', keras.metrics.SparseCategoricalAccuracy()]\n    )\n    \n    print(f\"✓ Model architecture created using {model_name}\")\n    print(f\"✓ Total parameters: {model.count_params():,}\")\n    trainable_params = sum([tf.size(w).numpy() for w in model.trainable_weights])\n    print(f\"✓ Trainable parameters: {trainable_params:,}\")\n    print(f\"✓ Fine-tuning enabled on last 20% of base model layers\")\n    \n    return model\n\nmodel = build_model()\n\nprint(\"\\nModel Architecture Summary:\")\nmodel.summary()\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-10T22:10:31.046396Z","iopub.execute_input":"2025-10-10T22:10:31.046594Z","iopub.status.idle":"2025-10-10T22:10:34.406995Z","shell.execute_reply.started":"2025-10-10T22:10:31.046578Z","shell.execute_reply":"2025-10-10T22:10:34.406400Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ==================== MODEL TRAINING ====================\ndef train_model(model, train_gen, val_gen):\n    \"\"\"Train the model\"\"\"\n    print(\"\\n[8/8] Training Model...\")\n    \n    callbacks = [\n        EarlyStopping(\n            monitor='val_accuracy',\n            patience=10,\n            restore_best_weights=True,\n            verbose=1,\n            min_delta=0.001\n        ),\n        ReduceLROnPlateau(\n            monitor='val_loss',\n            factor=0.3,\n            patience=5,\n            min_lr=1e-8,\n            verbose=1\n        ),\n        ModelCheckpoint(\n            os.path.join(Config.MODEL_DIR, 'best_model.h5'),\n            monitor='val_accuracy',\n            save_best_only=True,\n            verbose=1\n        )\n    ]\n    \n    class_weights = compute_class_weight(\n        'balanced',\n        classes=np.unique(train_df['level'].astype(int)),\n        y=train_df['level'].astype(int)\n    )\n    class_weight_dict = dict(enumerate(class_weights))\n    \n    print(f\"✓ Class weights computed: {class_weight_dict}\")\n    print(f\"✓ Starting training for {Config.EPOCHS} epochs...\")\n    \n    history = model.fit(\n        train_gen,\n        validation_data=val_gen,\n        epochs=Config.EPOCHS,\n        callbacks=callbacks,\n        class_weight=class_weight_dict,\n        verbose=1\n    )\n    \n    print(\"\\n✓ Training completed!\")\n    return history\n\nhistory = train_model(model, train_gen, val_gen)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-10T22:10:34.407721Z","iopub.execute_input":"2025-10-10T22:10:34.408016Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ==================== TRAINING VISUALIZATION ====================\ndef plot_training_history(history):\n    \"\"\"Plot training and validation metrics\"\"\"\n    print(\"\\nGenerating Training History Plots...\")\n    \n    fig, axes = plt.subplots(1, 2, figsize=(16, 5))\n    \n    axes[0].plot(history.history['accuracy'], label='Training Accuracy', linewidth=2, marker='o')\n    axes[0].plot(history.history['val_accuracy'], label='Validation Accuracy', linewidth=2, marker='s')\n    axes[0].set_xlabel('Epoch', fontsize=12, fontweight='bold')\n    axes[0].set_ylabel('Accuracy', fontsize=12, fontweight='bold')\n    axes[0].set_title('Model Accuracy Over Epochs', fontsize=14, fontweight='bold')\n    axes[0].legend(loc='lower right', fontsize=10)\n    axes[0].grid(True, alpha=0.3)\n    \n    axes[1].plot(history.history['loss'], label='Training Loss', linewidth=2, marker='o')\n    axes[1].plot(history.history['val_loss'], label='Validation Loss', linewidth=2, marker='s')\n    axes[1].set_xlabel('Epoch', fontsize=12, fontweight='bold')\n    axes[1].set_ylabel('Loss', fontsize=12, fontweight='bold')\n    axes[1].set_title('Model Loss Over Epochs', fontsize=14, fontweight='bold')\n    axes[1].legend(loc='upper right', fontsize=10)\n    axes[1].grid(True, alpha=0.3)\n    \n    plt.tight_layout()\n    plt.savefig(os.path.join(Config.OUTPUT_DIR, 'training_history.png'), dpi=300, bbox_inches='tight')\n    print(\"✓ Saved training history plots\")\n    plt.show()\n\nplot_training_history(history)\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\n# ==================== MODEL EVALUATION ====================\ndef evaluate_model(model, val_gen, val_df):\n    \"\"\"Evaluate model and generate classification report\"\"\"\n    print(\"\\nEvaluating Model Performance...\")\n    \n    val_gen.reset()\n    predictions = model.predict(val_gen, verbose=1)\n    y_pred = np.argmax(predictions, axis=1)\n    y_true = val_df['level'].astype(int).values[:len(y_pred)]\n    \n    accuracy = accuracy_score(y_true, y_pred)\n    print(f\"\\n{'='*60}\")\n    print(f\"OVERALL ACCURACY: {accuracy*100:.2f}%\")\n    print(f\"{'='*60}\")\n    \n    print(\"\\nDETAILED CLASSIFICATION REPORT:\")\n    print(\"=\"*60)\n    class_names_list = [Config.CLASS_NAMES[i] for i in range(Config.NUM_CLASSES)]\n    report = classification_report(y_true, y_pred, target_names=class_names_list, digits=4)\n    print(report)\n    \n    with open(os.path.join(Config.OUTPUT_DIR, 'classification_report.txt'), 'w') as f:\n        f.write(f\"DIABETIC RETINOPATHY DETECTION - CLASSIFICATION REPORT\\n\")\n        f.write(f\"{'='*60}\\n\\n\")\n        f.write(f\"Overall Accuracy: {accuracy*100:.2f}%\\n\\n\")\n        f.write(report)\n    \n    return y_true, y_pred, accuracy\n\ny_true, y_pred, accuracy = evaluate_model(model, val_gen, val_df)\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\n# ==================== CONFUSION MATRIX ====================\ndef plot_confusion_matrix(y_true, y_pred):\n    \"\"\"Plot confusion matrix\"\"\"\n    print(\"\\nGenerating Confusion Matrix...\")\n    \n    cm = confusion_matrix(y_true, y_pred)\n    \n    plt.figure(figsize=(12, 10))\n    sns.heatmap(cm, annot=True, fmt='d', cmap='Blues', \n                xticklabels=[Config.CLASS_NAMES[i] for i in range(Config.NUM_CLASSES)],\n                yticklabels=[Config.CLASS_NAMES[i] for i in range(Config.NUM_CLASSES)],\n                cbar_kws={'label': 'Count'})\n    plt.xlabel('Predicted Label', fontsize=12, fontweight='bold')\n    plt.ylabel('True Label', fontsize=12, fontweight='bold')\n    plt.title('Confusion Matrix - Diabetic Retinopathy Classification', fontsize=14, fontweight='bold')\n    plt.tight_layout()\n    plt.savefig(os.path.join(Config.OUTPUT_DIR, 'confusion_matrix.png'), dpi=300, bbox_inches='tight')\n    print(\"✓ Saved confusion matrix\")\n    plt.show()\n\nplot_confusion_matrix(y_true, y_pred)\n\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ==================== PER-CLASS ACCURACY ====================\ndef plot_per_class_metrics(y_true, y_pred):\n    \"\"\"Plot per-class accuracy and other metrics\"\"\"\n    print(\"\\nGenerating Per-Class Performance Metrics...\")\n    \n    from sklearn.metrics import precision_recall_fscore_support\n    \n    precision, recall, f1, support = precision_recall_fscore_support(y_true, y_pred)\n    \n    fig, axes = plt.subplots(2, 2, figsize=(16, 12))\n    \n    x = np.arange(Config.NUM_CLASSES)\n    width = 0.6\n    class_labels = [Config.CLASS_NAMES[i] for i in range(Config.NUM_CLASSES)]\n    \n    # Precision\n    axes[0, 0].bar(x, precision, width, color='steelblue', alpha=0.7)\n    axes[0, 0].set_xlabel('Disease Severity', fontsize=11, fontweight='bold')\n    axes[0, 0].set_ylabel('Precision', fontsize=11, fontweight='bold')\n    axes[0, 0].set_title('Precision by Class', fontsize=13, fontweight='bold')\n    axes[0, 0].set_xticks(x)\n    axes[0, 0].set_xticklabels(class_labels, rotation=45, ha='right')\n    axes[0, 0].set_ylim([0, 1.1])\n    axes[0, 0].grid(axis='y', alpha=0.3)\n    for i, v in enumerate(precision):\n        axes[0, 0].text(i, v + 0.02, f'{v:.3f}', ha='center', fontweight='bold')\n    \n    # Recall\n    axes[0, 1].bar(x, recall, width, color='coral', alpha=0.7)\n    axes[0, 1].set_xlabel('Disease Severity', fontsize=11, fontweight='bold')\n    axes[0, 1].set_ylabel('Recall', fontsize=11, fontweight='bold')\n    axes[0, 1].set_title('Recall by Class', fontsize=13, fontweight='bold')\n    axes[0, 1].set_xticks(x)\n    axes[0, 1].set_xticklabels(class_labels, rotation=45, ha='right')\n    axes[0, 1].set_ylim([0, 1.1])\n    axes[0, 1].grid(axis='y', alpha=0.3)\n    for i, v in enumerate(recall):\n        axes[0, 1].text(i, v + 0.02, f'{v:.3f}', ha='center', fontweight='bold')\n    \n    # F1-Score\n    axes[1, 0].bar(x, f1, width, color='seagreen', alpha=0.7)\n    axes[1, 0].set_xlabel('Disease Severity', fontsize=11, fontweight='bold')\n    axes[1, 0].set_ylabel('F1-Score', fontsize=11, fontweight='bold')\n    axes[1, 0].set_title('F1-Score by Class', fontsize=13, fontweight='bold')\n    axes[1, 0].set_xticks(x)\n    axes[1, 0].set_xticklabels(class_labels, rotation=45, ha='right')\n    axes[1, 0].set_ylim([0, 1.1])\n    axes[1, 0].grid(axis='y', alpha=0.3)\n    for i, v in enumerate(f1):\n        axes[1, 0].text(i, v + 0.02, f'{v:.3f}', ha='center', fontweight='bold')\n    \n    # Support\n    axes[1, 1].bar(x, support, width, color='orchid', alpha=0.7)\n    axes[1, 1].set_xlabel('Disease Severity', fontsize=11, fontweight='bold')\n    axes[1, 1].set_ylabel('Number of Samples', fontsize=11, fontweight='bold')\n    axes[1, 1].set_title('Support (Sample Count) by Class', fontsize=13, fontweight='bold')\n    axes[1, 1].set_xticks(x)\n    axes[1, 1].set_xticklabels(class_labels, rotation=45, ha='right')\n    axes[1, 1].grid(axis='y', alpha=0.3)\n    for i, v in enumerate(support):\n        axes[1, 1].text(i, v + max(support)*0.02, f'{int(v)}', ha='center', fontweight='bold')\n    \n    plt.tight_layout()\n    plt.savefig(os.path.join(Config.OUTPUT_DIR, 'per_class_metrics.png'), dpi=300, bbox_inches='tight')\n    print(\"✓ Saved per-class performance metrics\")\n    plt.show()\n\nplot_per_class_metrics(y_true, y_pred)\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\n# ==================== PREDICTION SAMPLES ====================\ndef visualize_predictions(model, sample_dir, num_samples=10):\n    \"\"\"Visualize sample predictions\"\"\"\n    print(\"\\nGenerating Prediction Visualizations...\")\n    \n    sample_files = [f for f in os.listdir(sample_dir) if f.endswith('.jpeg')]\n    selected_samples = np.random.choice(sample_files, min(num_samples, len(sample_files)), replace=False)\n    \n    rows = 2\n    cols = 5\n    fig, axes = plt.subplots(rows, cols, figsize=(20, 8))\n    fig.suptitle('Sample Predictions - Diabetic Retinopathy Detection', fontsize=16, fontweight='bold')\n    \n    for idx, filename in enumerate(selected_samples):\n        row = idx // cols\n        col = idx % cols\n        \n        img_path = os.path.join(sample_dir, filename)\n        img = cv2.imread(img_path)\n        img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n        \n        img_array = cv2.resize(img_rgb, (Config.IMG_SIZE, Config.IMG_SIZE))\n        img_array = img_array.astype(np.float32) / 255.0\n        img_array = np.expand_dims(img_array, axis=0)\n        \n        prediction = model.predict(img_array, verbose=0)\n        predicted_class = np.argmax(prediction[0])\n        confidence = prediction[0][predicted_class] * 100\n        \n        true_class = int(filename.split('_')[1])\n        \n        axes[row, col].imshow(img_rgb)\n        axes[row, col].axis('off')\n        \n        color = 'green' if predicted_class == true_class else 'red'\n        title = f'True: {Config.CLASS_NAMES[true_class]}\\n'\n        title += f'Pred: {Config.CLASS_NAMES[predicted_class]}\\n'\n        title += f'Conf: {confidence:.1f}%'\n        \n        axes[row, col].set_title(title, fontsize=9, fontweight='bold', color=color)\n    \n    plt.tight_layout()\n    plt.savefig(os.path.join(Config.OUTPUT_DIR, 'prediction_samples.png'), dpi=300, bbox_inches='tight')\n    print(\"✓ Saved prediction visualizations\")\n    plt.show()\n\nvisualize_predictions(model, sample_dir, num_samples=10)\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ==================== ACCURACY COMPARISON ====================\ndef plot_accuracy_comparison():\n    \"\"\"Plot accuracy comparison across different metrics\"\"\"\n    print(\"\\nGenerating Accuracy Comparison Chart...\")\n    \n    from sklearn.metrics import precision_recall_fscore_support\n    precision, recall, f1, _ = precision_recall_fscore_support(y_true, y_pred, average='weighted')\n    \n    metrics = ['Accuracy', 'Precision\\n(Weighted)', 'Recall\\n(Weighted)', 'F1-Score\\n(Weighted)']\n    values = [accuracy, precision, recall, f1]\n    \n    fig, ax = plt.subplots(figsize=(12, 7))\n    \n    bars = ax.bar(metrics, values, color=['#3498db', '#2ecc71', '#e74c3c', '#f39c12'], \n                  alpha=0.8, edgecolor='black', linewidth=2)\n    \n    ax.set_ylabel('Score', fontsize=13, fontweight='bold')\n    ax.set_title('Model Performance Metrics - Overall Comparison', fontsize=15, fontweight='bold')\n    ax.set_ylim([0, 1.1])\n    ax.grid(axis='y', alpha=0.3, linestyle='--')\n    \n    for bar, value in zip(bars, values):\n        height = bar.get_height()\n        ax.text(bar.get_x() + bar.get_width()/2., height + 0.02,\n                f'{value*100:.2f}%',\n                ha='center', va='bottom', fontsize=12, fontweight='bold')\n    \n    plt.tight_layout()\n    plt.savefig(os.path.join(Config.OUTPUT_DIR, 'accuracy_comparison.png'), dpi=300, bbox_inches='tight')\n    print(\"✓ Saved accuracy comparison chart\")\n    plt.show()\n\nplot_accuracy_comparison()\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\n# ==================== LEARNING CURVES ====================\ndef plot_learning_curves():\n    \"\"\"Plot learning curves showing model convergence\"\"\"\n    print(\"\\nGenerating Learning Curves...\")\n    \n    epochs_range = range(1, len(history.history['accuracy']) + 1)\n    \n    fig, ax = plt.subplots(figsize=(14, 6))\n    \n    ax.plot(epochs_range, history.history['accuracy'], 'o-', \n            color='#2ecc71', linewidth=2, markersize=6, label='Training Accuracy')\n    ax.plot(epochs_range, history.history['val_accuracy'], 's-', \n            color='#3498db', linewidth=2, markersize=6, label='Validation Accuracy')\n    \n    best_val_acc = max(history.history['val_accuracy'])\n    ax.axhline(y=best_val_acc, color='red', linestyle='--', linewidth=2, \n               label=f'Best Val Acc: {best_val_acc*100:.2f}%')\n    \n    ax.set_xlabel('Epoch', fontsize=13, fontweight='bold')\n    ax.set_ylabel('Accuracy', fontsize=13, fontweight='bold')\n    ax.set_title('Learning Curves - Model Convergence Analysis', fontsize=15, fontweight='bold')\n    ax.legend(loc='best', fontsize=11, framealpha=0.9)\n    ax.grid(True, alpha=0.3, linestyle='--')\n    ax.set_xlim([1, len(epochs_range)])\n    \n    plt.tight_layout()\n    plt.savefig(os.path.join(Config.OUTPUT_DIR, 'learning_curves.png'), dpi=300, bbox_inches='tight')\n    print(\"✓ Saved learning curves\")\n    plt.show()\n\nplot_learning_curves()\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\n# ==================== MODEL METADATA ====================\nmodel_metadata = {\n    'model_name': 'Diabetic Retinopathy Detection Model',\n    'architecture': model.layers[1].name,\n    'input_shape': (Config.IMG_SIZE, Config.IMG_SIZE, 3),\n    'num_classes': Config.NUM_CLASSES,\n    'class_names': Config.CLASS_NAMES,\n    'accuracy': float(accuracy),\n    'total_params': int(model.count_params()),\n    'image_size': Config.IMG_SIZE,\n    'batch_size': Config.BATCH_SIZE,\n    'epochs_trained': len(history.history['accuracy']),\n    'best_val_accuracy': float(max(history.history['val_accuracy']))\n}\n\nimport json\nwith open(os.path.join(Config.OUTPUT_DIR, 'model_metadata.json'), 'w') as f:\n    json.dump(model_metadata, f, indent=4)\n\nprint(\"\\n✓ Model metadata saved to model_metadata.json\")\n\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ==================== FINAL REPORT ====================\nprint(\"\\n\" + \"=\"*80)\nprint(\"📊 COMPREHENSIVE MODEL EVALUATION REPORT\")\nprint(\"=\"*80)\n\nprint(\"\\n1. DATASET STATISTICS:\")\nprint(f\"   • Total Samples: {len(df_train)}\")\nprint(f\"   • Training Set: {len(train_df)} samples\")\nprint(f\"   • Validation Set: {len(val_df)} samples\")\nprint(f\"   • Number of Classes: {Config.NUM_CLASSES}\")\nprint(f\"   • Image Size: {Config.IMG_SIZE}x{Config.IMG_SIZE}\")\n\nprint(\"\\n2. MODEL ARCHITECTURE:\")\nprint(f\"   • Base Model: {model.layers[1].name} (Pre-trained on ImageNet)\")\nprint(f\"   • Additional Layers: GAP → BatchNorm → Dropout → Dense(1024) → Dense(512) → Dense(256) → Output(5)\")\nprint(f\"   • Total Parameters: {model.count_params():,}\")\nprint(f\"   • Optimizer: Adam (LR={Config.LEARNING_RATE})\")\nprint(f\"   • Loss Function: Sparse Categorical Crossentropy\")\n\nprint(\"\\n3. TRAINING CONFIGURATION:\")\nprint(f\"   • Epochs: {len(history.history['accuracy'])}\")\nprint(f\"   • Batch Size: {Config.BATCH_SIZE}\")\nprint(f\"   • Image Size: {Config.IMG_SIZE}x{Config.IMG_SIZE}\")\nprint(f\"   • Data Augmentation: Rotation, Shift, Flip, Zoom, Shear, Brightness\")\nprint(f\"   • Class Weighting: Applied to handle imbalance\")\nprint(f\"   • Callbacks: EarlyStopping, ReduceLROnPlateau, ModelCheckpoint\")\n\nprint(\"\\n4. PERFORMANCE METRICS:\")\nprint(f\"   • Overall Accuracy: {accuracy*100:.2f}%\")\nprint(f\"   • Best Validation Accuracy: {max(history.history['val_accuracy'])*100:.2f}%\")\nprint(f\"   • Final Training Accuracy: {history.history['accuracy'][-1]*100:.2f}%\")\nprint(f\"   • Final Validation Loss: {history.history['val_loss'][-1]:.4f}\")\n\nfrom sklearn.metrics import precision_recall_fscore_support\nprecision, recall, f1, _ = precision_recall_fscore_support(y_true, y_pred, average='weighted')\nprint(f\"   • Weighted Precision: {precision*100:.2f}%\")\nprint(f\"   • Weighted Recall: {recall*100:.2f}%\")\nprint(f\"   • Weighted F1-Score: {f1*100:.2f}%\")\n\nprint(\"\\n5. PER-CLASS PERFORMANCE:\")\nprecision_pc, recall_pc, f1_pc, support_pc = precision_recall_fscore_support(y_true, y_pred)\nfor i in range(Config.NUM_CLASSES):\n    print(f\"   • {Config.CLASS_NAMES[i]}:\")\n    print(f\"     - Precision: {precision_pc[i]*100:.2f}%\")\n    print(f\"     - Recall: {recall_pc[i]*100:.2f}%\")\n    print(f\"     - F1-Score: {f1_pc[i]*100:.2f}%\")\n    print(f\"     - Support: {support_pc[i]} samples\")\n\nprint(\"\\n6. FILES GENERATED:\")\noutput_files = [\n    'class_distribution.png',\n    'sample_images.png',\n    'training_history.png',\n    'classification_report.txt',\n    'confusion_matrix.png',\n    'per_class_metrics.png',\n    'prediction_samples.png',\n    'roc_curves.png',\n    'accuracy_comparison.png',\n    'learning_curves.png',\n    'best_model.h5',\n    'model_metadata.json'\n]\nfor file in output_files:\n    print(f\"   ✓ {file}\")\n\nprint(\"\\n\" + \"=\"*80)\nprint(\"🎉 PROJECT EXECUTION COMPLETED!\")\nprint(\"=\"*80)\nprint(\"\\nAll visualizations, reports, and model files have been saved.\")\nprint(f\"Output directory: {Config.OUTPUT_DIR}\")\nprint(f\"Model directory: {Config.MODEL_DIR}\")\nprint(\"\\n\" + \"=\"*80)","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}