{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.10.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceId":25563,"databundleVersionId":2094376,"sourceType":"competition"}],"dockerImageVersionId":30822,"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-03-14T07:12:45.640983Z","iopub.execute_input":"2025-03-14T07:12:45.641322Z","iopub.status.idle":"2025-03-14T07:13:44.348135Z","shell.execute_reply.started":"2025-03-14T07:12:45.641288Z","shell.execute_reply":"2025-03-14T07:13:44.347241Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"test dataset\n/kaggle/input/plant-pathology-2021-fgvc8/test_images/ad8770db05586b59.jpg\n/kaggle/input/plant-pathology-2021-fgvc8/test_images/c7b03e718489f3ca.jpg\n/kaggle/input/plant-pathology-2021-fgvc8/test_images/85f8cb619c66b863.jpg","metadata":{}},{"cell_type":"markdown","source":"จำแนกโรคพืชจากใบแอปเปิ้ล 18.6k files\nวัตถุประสงค์คือการพัฒนาโมเดลที่ใช้การเรียนรู้ของ machine learning เพื่อจำแนกภาพใบไม้จาก test dataset ว่าเป็นโรคที่เฉพาะเจาะจงประเภทใด และเพื่อระบุโรคแต่ละโรคจากอาการโรคต่างๆ มากมายในภาพใบไม้ใบเดียว","metadata":{}},{"cell_type":"code","source":"# รีเซ็ตตัวแปรที่อาจขัดแย้ง\nimport sys\nfor var in ['csv', 'read_csv']:\n    if var in dir():\n        del globals()[var]\n    if var in dir(sys.modules[__name__]):\n        delattr(sys.modules[__name__], var)\n\n# ล้าง pandas และ import ใหม่\nif 'pandas' in sys.modules:\n    del sys.modules['pandas']\nimport pandas as pd\n\n# ทดสอบอ่านไฟล์\ntest_df = pd.read_csv('/kaggle/input/plant-pathology-2021-fgvc8/train.csv')\nprint(\"สามารถอ่านไฟล์ CSV ได้\")\nprint(f\"จำนวนแถว: {len(test_df)}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-14T07:13:44.349389Z","iopub.execute_input":"2025-03-14T07:13:44.349762Z","iopub.status.idle":"2025-03-14T07:13:44.401479Z","shell.execute_reply.started":"2025-03-14T07:13:44.349737Z","shell.execute_reply":"2025-03-14T07:13:44.400663Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ตรวจสอบว่ามีตัวแปรที่ขัดแย้งหรือไม่\ncurrent_vars = dir()\nprint(\"ตัวแปรทั้งหมดในปัจจุบัน:\")\nfor var in current_vars:\n    if not var.startswith('_'):  # ไม่แสดงตัวแปรระบบ\n        print(f\"{var}: {type(eval(var))}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-14T07:13:44.403272Z","iopub.execute_input":"2025-03-14T07:13:44.403486Z","iopub.status.idle":"2025-03-14T07:13:44.409464Z","shell.execute_reply.started":"2025-03-14T07:13:44.403468Z","shell.execute_reply":"2025-03-14T07:13:44.408795Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\nimport tensorflow as tf\nfrom tensorflow.keras import layers, models\nimport numpy as np\nimport os\nfrom sklearn.preprocessing import LabelEncoder\n\n# กำหนดพาธที่ถูกต้อง\nBASE_PATH = '/kaggle/input/plant-pathology-2021-fgvc8'\nTRAIN_IMAGES_DIR = os.path.join(BASE_PATH, 'train_images')\nTEST_IMAGES_DIR = os.path.join(BASE_PATH, 'test_images')\nTRAIN_CSV = os.path.join(BASE_PATH, 'train.csv')\nTEST_CSV = os.path.join(BASE_PATH, 'sample_submission.csv')\n\ndef process_path(file_path, label=None):\n    \"\"\"\n    ฟังก์ชันสำหรับประมวลผลรูปภาพ\n    \"\"\"\n    img = tf.io.read_file(file_path)\n    img = tf.image.decode_jpeg(img, channels=3)\n    # ลดเวลาทำงานโดยการ ลดขนาดรูปภาพลงเหลือ 128x128\n    img = tf.image.resize(img, (128, 128))\n    img = tf.cast(img, tf.float32) / 255.0\n    \n    if label is not None:\n        return img, label\n    return img\n\ndef create_dataset(dataframe, images_dir, batch_size=32, is_training=True):\n    \"\"\"\n    สร้าง tf.data.Dataset เพื่อโหลดข้อมูลแบบ batch\n    \"\"\"\n    # สร้าง full path สำหรับรูปภาพ\n    full_paths = [os.path.join(images_dir, fname) for fname in dataframe['image']]\n    \n    # ใช้ AUTOTUNE สำหรับการ prefetch และ parallel calls\n    AUTOTUNE = tf.data.AUTOTUNE\n    \n    if is_training:\n        label_encoder = LabelEncoder()\n        labels = label_encoder.fit_transform(dataframe['labels'])\n        \n        # สร้าง dataset จาก paths และ labels\n        dataset = tf.data.Dataset.from_tensor_slices((full_paths, labels))\n        dataset = dataset.map(process_path, num_parallel_calls=AUTOTUNE)\n        dataset = dataset.cache()  # เพิ่ม cache\n        \n        if is_training:\n            dataset = dataset.shuffle(buffer_size=1000)\n            \n            # ลดเวลาการทำงานโดย ลดความซับซ้อนของ data augmentation\n            data_augmentation = tf.keras.Sequential([\n                layers.RandomFlip(\"horizontal\"),\n                layers.RandomRotation(0.1),  # ลดการหมุนลง\n            ])\n            \n            dataset = dataset.map(\n                lambda x, y: (data_augmentation(x, training=True), y),\n                num_parallel_calls=AUTOTUNE\n            )\n    else:\n        dataset = tf.data.Dataset.from_tensor_slices(full_paths)\n        dataset = dataset.map(process_path, num_parallel_calls=AUTOTUNE)\n        dataset = dataset.cache()  # เพิ่ม cache\n\n    # เพิ่ม batch size และใช้ prefetch\n    dataset = dataset.batch(batch_size).prefetch(AUTOTUNE)\n    \n    if is_training:\n        return dataset, label_encoder\n    return dataset\n\ndef create_model(num_classes, img_size=(128, 128)):\n    # ใช้ MobileNetV2 แทน ResNet50\n    base_model = tf.keras.applications.MobileNetV2(\n        include_top=False,\n        weights='imagenet',\n        input_shape=(img_size[0], img_size[1], 3)\n    )\n    \n    base_model.trainable = False\n    \n    model = models.Sequential([\n        base_model,\n        layers.GlobalAveragePooling2D(),\n        layers.Dense(256, activation='relu'),  # ลดจำนวน nodes\n        layers.Dropout(0.3),  # ลด dropout\n        layers.Dense(num_classes, activation='softmax')\n    ])\n    \n    return model\n\ndef main():\n    try:\n        print(\"เริ่มต้นการโหลดข้อมูล...\")\n        \n        # ตั้งค่า mixed precision\n        tf.keras.mixed_precision.set_global_policy('mixed_float16')\n        \n        # อ่านข้อมูลจาก CSV\n        train_df = pd.read_csv(TRAIN_CSV)\n        \n        # แบ่งข้อมูลสำหรับ validation\n        val_size = int(len(train_df) * 0.2)\n        train_df_split = train_df.iloc[:-val_size]\n        val_df_split = train_df.iloc[-val_size:]\n        \n        # เพิ่ม batch size\n        batch_size = 64\n        train_dataset, label_encoder = create_dataset(\n            train_df_split, \n            TRAIN_IMAGES_DIR, \n            batch_size=batch_size\n        )\n        val_dataset, _ = create_dataset(\n            val_df_split, \n            TRAIN_IMAGES_DIR, \n            batch_size=batch_size\n        )\n        \n        num_classes = len(label_encoder.classes_)\n        print(f\"จำนวน classes: {num_classes}\")\n        \n        model = create_model(num_classes)\n        \n        # ใช้ compile ที่เร็วขึ้น\n        model.compile(\n            optimizer=tf.keras.optimizers.Adam(learning_rate=0.001),\n            loss='sparse_categorical_crossentropy',\n            metrics=['accuracy']\n        )\n        \n        print(\"เริ่มการเทรนโมเดล...\")\n        history = model.fit(\n            train_dataset,\n            epochs=1, #ลดจำนวนจาก20epochs เหลือ1เพื่อความเร็ว\n            validation_data=val_dataset,\n            callbacks=[\n                tf.keras.callbacks.EarlyStopping(\n                    monitor='val_loss',\n                    patience=3,\n                    restore_best_weights=True\n                )\n            ]\n        )\n        \n        model.save_weights('plant_disease_model.weights.h5')\n        print(\"บันทึกโมเดลเรียบร้อย\")\n        \n    except Exception as e:\n        print(f\"เกิดข้อผิดพลาดในการทำงาน: {str(e)}\")\n\nif __name__ == \"__main__\":\n    main()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-14T07:13:44.410616Z","iopub.execute_input":"2025-03-14T07:13:44.410890Z","iopub.status.idle":"2025-03-14T07:21:12.536485Z","shell.execute_reply.started":"2025-03-14T07:13:44.410867Z","shell.execute_reply":"2025-03-14T07:21:12.535620Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import tensorflow as tf\nfrom tensorflow.keras import layers, models\nimport numpy as np\nfrom tensorflow.keras.mixed_precision import set_global_policy\nimport os\n\n# ตั้งค่า mixed precision\nset_global_policy('mixed_float16')\n\ndef create_model(num_classes, img_size=(128, 128)):\n    base_model = tf.keras.applications.MobileNetV2(\n        include_top=False,\n        weights='imagenet',\n        input_shape=(img_size[0], img_size[1], 3)\n    )\n    \n    base_model.trainable = False\n    \n    model = models.Sequential([\n        base_model,\n        layers.GlobalAveragePooling2D(),\n        layers.Dense(256, activation='relu'),\n        layers.Dropout(0.3),\n        layers.Dense(num_classes, activation='softmax')\n    ])\n    \n    # Build the model with a dummy input\n    dummy_input = tf.zeros((1, img_size[0], img_size[1], 3))\n    model(dummy_input)\n    \n    model.compile(\n        optimizer=tf.keras.optimizers.Adam(learning_rate=0.001),\n        loss='sparse_categorical_crossentropy',\n        metrics=['accuracy']\n    )\n    \n    return model\n\ndef predict_image(image_path, model, class_names):\n    descriptions = {\n        'frog_eye_leaf_spot': (\n            \"โรค Frogeye Leaf Spot (โรคจุดตากบ): เกิดจากเชื้อรา Cercospora sojina\\n\"\n            \"ลักษณะเด่น:\\n\"\n            \"- เป็นจุดแผลกลมหรือเกือบกลม\\n\"\n            \"- ตรงกลางแผลมีสีเทาหรือน้ำตาลอ่อน\\n\"\n            \"- ขอบแผลมีสีน้ำตาลแดงถึงน้ำตาลเข้ม\\n\"\n            \"- แผลมีขนาดประมาณ 1-5 มิลลิเมตร\"\n        ),\n        'powdery_mildew': (\n            \"โรค Powdery Mildew (โรคราแป้ง): เกิดจากเชื้อรา Erysiphales\\n\"\n            \"ลักษณะเด่น:\\n\"\n            \"- ใบมีลักษณะเป็นผงสีขาวคล้ายแป้ง\\n\"\n            \"- ผงขาวนี้จะปกคลุมผิวใบ, ก้านใบ หรือกิ่งไม้\\n\"\n            \"- ใบอาจเปลี่ยนสีและหลุดร่วง\"\n        ),\n        'rust': (\n            \"โรค Rust (โรคราสนิม): เกิดจากเชื้อรา Puccinia spp.\\n\"\n            \"ลักษณะเด่น:\\n\"\n            \"- มีจุดสีส้มหรือสีน้ำตาลขึ้นบนใบ\\n\"\n            \"- จุดนี้มักมีลักษณะนูนขึ้นมาเล็กน้อย\\n\"\n            \"- ใบที่ติดโรคอาจเปลี่ยนสีและร่วงหล่น\"\n        ),\n        'scab': (\n            \"โรค Scab (โรคสแคบ): เกิดจากเชื้อรา Venturia inaequalis\\n\"\n            \"ลักษณะเด่น:\\n\"\n            \"- มีแผลนูนขึ้นมาบนใบ\\n\"\n            \"- แผลมีลักษณะคล้ายเปลือกที่หยาบกระด้าง\\n\"\n            \"- ใบที่ติดโรคอาจหงิกงอและเปลี่ยนสี\"\n        ),\n        'complex': (\n            \"Complex: หมายถึงการพบโรคหลายชนิดร่วมกัน\\n\"\n            \"- ลักษณะอาการอาจผสมผสานระหว่างโรคต่าง ๆ ที่กล่าวมาข้างต้น\"\n        )\n    }\n    \n    try:\n        img = tf.io.read_file(image_path)\n        img = tf.image.decode_jpeg(img, channels=3)\n        img = tf.image.resize(img, (128, 128))\n        img = tf.cast(img, tf.float32) / 255.0\n        img = tf.expand_dims(img, axis=0)\n        \n        predictions = model.predict(img)\n        predicted_class_idx = np.argmax(predictions[0])\n        confidence = float(predictions[0][predicted_class_idx])\n        \n        predicted_class_name = class_names[predicted_class_idx]\n        description = descriptions.get(predicted_class_name, \"ไม่มีคำอธิบายเพิ่มเติมสำหรับโรคนี้\")\n        \n        return {\n            'class_name': predicted_class_name,\n            'confidence': confidence,\n            'description': description,\n            'class_index': int(predicted_class_idx)\n        }\n        \n    except Exception as e:\n        print(f\"เกิดข้อผิดพลาดในการทำนาย: {str(e)}\")\n        raise\n\n# รายชื่อคลาส\nclass_names = [\n    'complex', 'frog_eye_leaf_spot', 'frog_eye_leaf_spot complex',\n    'healthy', 'powdery_mildew', 'powdery_mildew complex', \n    'rust', 'rust complex', 'rust frog_eye_leaf_spot',\n    'scab', 'scab frog_eye_leaf_spot', 'scab frog_eye_leaf_spot complex'\n]\n\ntry:\n    # สร้างและ build โมเดล\n    model = create_model(num_classes=len(class_names))\n    \n    # โหลด weights\n    model.load_weights('plant_disease_model.weights.h5')\n    print(\"โหลดโมเดลเรียบร้อย\")\n    \n    # ทดสอบกับรูปภาพ\n    image_path = '/kaggle/input/plant-pathology-2021-fgvc8/test_images/85f8cb619c66b863.jpg'\n    if os.path.exists(image_path):\n        result = predict_image(image_path, model, class_names)\n        print(f\"\\nผลการทำนาย:\")\n        print(f\"โรคที่พบ: {result['class_name']}\")\n        print(f\"ความมั่นใจ: {result['confidence']:.2%}\")\n        print(f\"คำอธิบาย: {result['description']}\")\n    else:\n        print(f\"ไม่พบไฟล์รูปภาพที่: {image_path}\")\n    \nexcept Exception as e:\n    print(f\"เกิดข้อผิดพลาด: {str(e)}\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-14T07:21:12.538890Z","iopub.execute_input":"2025-03-14T07:21:12.539237Z","iopub.status.idle":"2025-03-14T07:21:18.853678Z","shell.execute_reply.started":"2025-03-14T07:21:12.539198Z","shell.execute_reply":"2025-03-14T07:21:18.852990Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"test image ซึ่งทั้ง2ภาพเป็นภาพของโรคFrogeye Leaf Spot (โรคจุดตากบ)\n/kaggle/input/plant-pathology-2021-fgvc8/test_images/ad8770db05586b59.jpg\n/kaggle/input/plant-pathology-2021-fgvc8/test_images/c7b03e718489f3ca.jpg\n\nภาพนี้เป็นComplex: หมายถึงการพบโรคหลายชนิดร่วมกัน\n/kaggle/input/plant-pathology-2021-fgvc8/test_images/85f8cb619c66b863.jpg","metadata":{}},{"cell_type":"code","source":"#ตรวจสอบว่าไฟล์ Weights มีอยู่จริง\nimport os\n\nweights_path = \"plant_disease_model.weights.h5\"\nif os.path.exists(weights_path):\n    print(\"พบไฟล์ weights:\", weights_path)\nelse:\n    print(\"❌ ไม่พบไฟล์ weights:\", weights_path)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-14T07:21:18.854552Z","iopub.execute_input":"2025-03-14T07:21:18.854861Z","iopub.status.idle":"2025-03-14T07:21:18.859800Z","shell.execute_reply.started":"2025-03-14T07:21:18.854826Z","shell.execute_reply":"2025-03-14T07:21:18.859031Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#สร้างโมเดลให้สมบูรณ์ก่อนโหลด weights\ndef create_model(num_classes, img_size=(128, 128)):\n    base_model = tf.keras.applications.MobileNetV2(\n        include_top=False,\n        weights='imagenet',\n        input_shape=(img_size[0], img_size[1], 3)\n    )\n    \n    base_model.trainable = False\n    \n    model = models.Sequential([\n        base_model,\n        layers.GlobalAveragePooling2D(),\n        layers.Dense(256, activation='relu'),\n        layers.Dropout(0.3),\n        layers.Dense(num_classes, activation='softmax')\n    ])\n    \n    # สร้างโมเดลให้สมบูรณ์ด้วย dummy input\n    dummy_input = tf.zeros((1, img_size[0], img_size[1], 3))\n    model(dummy_input)\n    \n    model.compile(\n        optimizer=tf.keras.optimizers.Adam(learning_rate=0.001),\n        loss='sparse_categorical_crossentropy',\n        metrics=['accuracy']\n    )\n    \n    return model","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-14T07:21:18.860611Z","iopub.execute_input":"2025-03-14T07:21:18.860836Z","iopub.status.idle":"2025-03-14T07:21:18.880307Z","shell.execute_reply.started":"2025-03-14T07:21:18.860812Z","shell.execute_reply":"2025-03-14T07:21:18.879557Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#โหลด weights หลังจากสร้างโมเดลให้สมบูรณ์\ntry:\n    # สร้างโมเดล\n    model = create_model(num_classes=len(class_names))\n    \n    # โหลด weights\n    model.load_weights('plant_disease_model.weights.h5')\n    print(\"✅ โหลดโมเดลเรียบร้อย!\")\n    \n    # ทดสอบกับรูปภาพ\n    image_path = '/kaggle/input/plant-pathology-2021-fgvc8/test_images/85f8cb619c66b863.jpg'\n    if os.path.exists(image_path):\n        result = predict_image(image_path, model, class_names)\n        print(f\"\\nผลการทำนาย:\")\n        print(f\"โรคที่พบ: {result['class_name']}\")\n        print(f\"ความมั่นใจ: {result['confidence']:.2%}\")\n        print(f\"คำอธิบาย: {result['description']}\")\n    else:\n        print(f\"❌ ไม่พบไฟล์รูปภาพที่: {image_path}\")\n    \nexcept Exception as e:\n    print(f\"❌ เกิดข้อผิดพลาด: {str(e)}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-14T07:21:18.881059Z","iopub.execute_input":"2025-03-14T07:21:18.881302Z","iopub.status.idle":"2025-03-14T07:21:22.857117Z","shell.execute_reply.started":"2025-03-14T07:21:18.881270Z","shell.execute_reply":"2025-03-14T07:21:22.856128Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#ตรวจสอบว่าโมเดลถูกสร้างอย่างสมบูรณ์\nmodel.summary()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-14T07:21:22.859627Z","iopub.execute_input":"2025-03-14T07:21:22.859859Z","iopub.status.idle":"2025-03-14T07:21:22.880801Z","shell.execute_reply.started":"2025-03-14T07:21:22.859839Z","shell.execute_reply":"2025-03-14T07:21:22.880036Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"ใช้ Grad-CAM กับ MobileNetV2 เพื่ออธิบายว่าโมเดลพิจารณาตรงไหนในภาพ\nGrad-CAM (Gradient-weighted Class Activation Mapping) สามารถ ไฮไลต์บริเวณที่โมเดลใช้ตัดสินใจ\nแสดงเป็น Heatmap Overlay บนภาพ โดยสีแดงหรือเหลืองแสดงบริเวณที่โมเดลสนใจมากที่สุด","metadata":{}},{"cell_type":"code","source":"import tensorflow as tf\nimport numpy as np\nimport cv2\nimport matplotlib.pyplot as plt\nfrom tensorflow.keras import models, layers\n\ndef make_gradcam_heatmap(img_array, model, base_model, pred_index=None):\n    \"\"\"Generate Grad-CAM heatmap with corrected implementation\"\"\"\n    # First, we create a model that maps the input image to the activations\n    # of the last conv layer and the output predictions\n    last_conv_layer = base_model.get_layer('Conv_1')\n    \n    # Create a model that maps the input image to:\n    # 1. The last conv layer activation\n    # 2. The final class predictions\n    grad_model = tf.keras.models.Model(\n        inputs=[base_model.inputs],\n        outputs=[\n            last_conv_layer.output,\n            model.output\n        ]\n    )\n\n    # Then, we compute the gradient of the top predicted class for our input image\n    # with respect to the activations of the last conv layer\n    with tf.GradientTape() as tape:\n        last_conv_layer_output, predictions = grad_model(img_array)\n        if pred_index is None:\n            pred_index = tf.argmax(predictions[0])\n        class_channel = predictions[:, pred_index]\n\n    # This is the gradient of the predicted class with respect to\n    # the output feature map of the last conv layer\n    grads = tape.gradient(class_channel, last_conv_layer_output)\n\n    # This is the channel-wise mean of the resulting feature map\n    pooled_grads = tf.reduce_mean(grads, axis=(0, 1, 2))\n\n    # We multiply each channel in the feature map array\n    # by \"how important this channel is\" with regard to the predicted class\n    last_conv_layer_output = last_conv_layer_output.numpy()[0]\n    pooled_grads = pooled_grads.numpy()\n    for i in range(pooled_grads.shape[-1]):\n        last_conv_layer_output[:, :, i] *= pooled_grads[i]\n\n    # The channel-wise mean of the resulting feature map\n    # is our heatmap of class activation\n    heatmap = np.mean(last_conv_layer_output, axis=-1)\n\n    # For visualization purpose, we will also normalize the heatmap between 0 & 1\n    heatmap = np.maximum(heatmap, 0) / np.maximum(np.max(heatmap), 1e-10)\n    \n    return heatmap\n\n# Main execution code\ndef process_image_with_gradcam(image_path, model, base_model, class_names):\n    \"\"\"Process image and generate Grad-CAM visualization\"\"\"\n    # Load and process image\n    img = tf.io.read_file(image_path)\n    img = tf.image.decode_jpeg(img, channels=3)\n    original_img = tf.image.resize(img, (256, 256)).numpy().astype(np.uint8)\n    processed_img = tf.image.resize(img, (128, 128))\n    processed_img = tf.expand_dims(processed_img / 255.0, axis=0)\n\n    # Make prediction\n    preds = model.predict(processed_img, verbose=0)\n    class_idx = np.argmax(preds[0])\n\n    # Generate heatmap\n    heatmap = make_gradcam_heatmap(\n        processed_img,\n        model,\n        base_model,\n        class_idx\n    )\n\n    # Create overlay\n    heatmap_resized = cv2.resize(heatmap, (original_img.shape[1], original_img.shape[0]))\n    heatmap_resized = np.uint8(255 * heatmap_resized)\n    heatmap_colored = cv2.applyColorMap(heatmap_resized, cv2.COLORMAP_JET)\n    \n    original_img_bgr = cv2.cvtColor(original_img, cv2.COLOR_RGB2BGR)\n    overlay = cv2.addWeighted(original_img_bgr, 0.7, heatmap_colored, 0.3, 0)\n\n    return original_img, heatmap, overlay, class_names[class_idx], preds[0][class_idx]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-14T07:33:14.220472Z","iopub.execute_input":"2025-03-14T07:33:14.220805Z","iopub.status.idle":"2025-03-14T07:33:14.230407Z","shell.execute_reply.started":"2025-03-14T07:33:14.220782Z","shell.execute_reply":"2025-03-14T07:33:14.229621Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"อธิบายเกี่ยวกับการทำงานของโมเดลและโรคพืชที่ตรวจพบครับ:\n\n1. ฟีเจอร์ที่โมเดลใช้ในการตรวจจับ:\n- โมเดลใช้ MobileNetV2 เป็น base model ซึ่งถูกออกแบบมาเพื่อสกัดฟีเจอร์จากรูปภาพโดยเฉพาะ\n- ฟีเจอร์หลักที่ใช้ในการตรวจจับประกอบด้วย:\n  - รูปแบบของจุด/รอยแผลบนใบ (Pattern recognition)\n  - สี และการเปลี่ยนแปลงของสีบนใบ\n  - ลักษณะขอบของแผล\n  - ขนาดและการกระจายตัวของรอยโรค\n  - พื้นผิวของใบที่เป็นโรค\n\n2. การจำแนกประเภทของโรคที่ตรวจพบ:\n\nโรค Frogeye Leaf Spot (โรคจุดตากบ): เกิดจากเชื้อรา Cercospora sojina\nลักษณะเด่น:\n- เป็นจุดแผลกลมหรือเกือบกลม\n- ตรงกลางแผลมีสีเทาหรือน้ำตาลอ่อน\n- ขอบแผลมีสีน้ำตาลแดงถึงน้ำตาลเข้ม\n- แผลมีขนาดประมาณ 1-5 มิลลิเมตร\n\nโรค Powdery Mildew (โรคราแป้ง): เกิดจากเชื้อรา Erysiphales\nลักษณะเด่น:\n- ใบมีลักษณะเป็นผงสีขาวคล้ายแป้ง\n- ผงขาวนี้จะปกคลุมผิวใบ, ก้านใบ หรือกิ่งไม้\n- ใบอาจเปลี่ยนสีและหลุดร่วง\"\n\nโรค Rust (โรคราสนิม): เกิดจากเชื้อรา Puccinia spp.\nลักษณะเด่น:\n- มีจุดสีส้มหรือสีน้ำตาลขึ้นบนใบ\n- จุดนี้มักมีลักษณะนูนขึ้นมาเล็กน้อย\n- ใบที่ติดโรคอาจเปลี่ยนสีและร่วงหล่น\n\nโรค Scab (โรคสแคบ): เกิดจากเชื้อรา Venturia inaequalis\nลักษณะเด่น:\n- มีแผลนูนขึ้นมาบนใบ\n- แผลมีลักษณะคล้ายเปลือกที่หยาบกระด้าง\n- ใบที่ติดโรคอาจหงิกงอและเปลี่ยนสี\n\nComplex: หมายถึงการพบโรคหลายชนิดร่วมกัน\n- ลักษณะอาการอาจผสมผสานระหว่างโรคต่าง ๆ ที่กล่าวมาข้างต้น\"\n\n\nอย่างไรก็ตาม โมเดลนี้ถูกเทรนมาให้ระบุลักษณะอาการที่เห็น ไม่ได้ถูกเทรนมาให้แยกประเภทตามสาเหตุของโรค (แบคทีเรีย/ไวรัส/เชื้อรา) โดยตรง หากต้องการระบุสาเหตุของโรค จำเป็นต้อง:\n1. เพิ่มข้อมูลการจำแนกประเภทตามสาเหตุในชุดข้อมูลที่ใช้เทรน\n2. ปรับโมเดลให้สามารถทำนายทั้งลักษณะอาการและสาเหตุของโรค\n3. อาจต้องเพิ่มข้อมูลทางกายภาพอื่นๆ นอกเหนือจากรูปภาพ เช่น อุณหภูมิ ความชื้น ฤดูกาล เป็นต้น\n\n","metadata":{}},{"cell_type":"code","source":"import tensorflow as tf\nimport numpy as np\nimport cv2\nimport matplotlib.pyplot as plt\nfrom tensorflow.keras import models, layers\nimport os\nimport logging\n\n# Set up logging\nlogging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')\nlogger = logging.getLogger(__name__)\n\n# Set mixed precision\ntf.keras.mixed_precision.set_global_policy('mixed_float16')\n\n# Class names\nclass_names = [\n    'complex', 'frog_eye_leaf_spot', 'frog_eye_leaf_spot complex',\n    'healthy', 'powdery_mildew', 'powdery_mildew complex', \n    'rust', 'rust complex', 'rust frog_eye_leaf_spot',\n    'scab', 'scab frog_eye_leaf_spot', 'scab frog_eye_leaf_spot complex'\n]\n\ndef create_model(num_classes, img_size=(128, 128)):\n    \"\"\"\n    Create a model from scratch using MobileNetV2 as base\n    \"\"\"\n    # Create input with fixed shape\n    inputs = tf.keras.Input(shape=(img_size[0], img_size[1], 3))\n    \n    # Create a base model with proper input shape\n    base_model = tf.keras.applications.MobileNetV2(\n        include_top=False,\n        weights='imagenet',\n        input_tensor=inputs\n    )\n    \n    # Make base model non-trainable initially\n    base_model.trainable = False\n    \n    # Create the model with a functional API to ensure shapes are defined\n    x = base_model.output\n    x = layers.GlobalAveragePooling2D()(x)\n    x = layers.Dense(256, activation='relu')(x)\n    x = layers.Dropout(0.3)(x)\n    outputs = layers.Dense(num_classes, activation='softmax')(x)\n    \n    # Create model with explicit inputs and outputs\n    model = tf.keras.Model(inputs=inputs, outputs=outputs)\n    \n    # Compile the model\n    model.compile(\n        optimizer=tf.keras.optimizers.Adam(learning_rate=0.001),\n        loss='categorical_crossentropy',\n        metrics=['accuracy']\n    )\n    \n    return model, base_model\n\ndef get_last_conv_layer_name(model):\n    \"\"\"\n    Automatically find the name of the last convolutional layer in the model\n    \"\"\"\n    for layer in reversed(model.layers[0].layers):  # Traverse base model layers in reverse\n        if isinstance(layer, tf.keras.layers.Conv2D):\n            return layer.name\n    \n    # Fallback to a common layer name in MobileNetV2\n    return \"Conv_1\"\n\ndef get_grad_cam(model, img_array, layer_name=None, pred_index=None):\n    \"\"\"\n    Calculate Grad-CAM with automatic layer detection if not specified\n    \"\"\"\n    # For MobileNetV2, we want to target the \"out_relu\" layer which is the last activation layer\n    # before the global average pooling in the standard MobileNetV2 architecture\n    target_layer_name = \"out_relu\"\n    \n    # Find the target layer\n    target_layer = None\n    \n    # First, try to find the layer directly\n    try:\n        target_layer = model.get_layer(target_layer_name)\n        logger.info(f\"Found target layer directly: {target_layer_name}\")\n    except ValueError:\n        logger.warning(f\"Layer '{target_layer_name}' not found directly in model\")\n        \n        # Try to find the layer in base_model if it exists\n        for layer in model.layers:\n            if hasattr(layer, 'layers'):  # This is likely the base model\n                try:\n                    target_layer = layer.get_layer(target_layer_name)\n                    logger.info(f\"Found target layer in base model: {target_layer_name}\")\n                    break\n                except ValueError:\n                    continue\n    \n    # If we still haven't found the target layer, look for the last ReLU activation before GlobalAveragePooling2D\n    if target_layer is None:\n        logger.info(\"Searching for appropriate target layer...\")\n        for layer in reversed(model.layers):\n            if isinstance(layer, tf.keras.layers.ReLU):\n                target_layer = layer\n                logger.info(f\"Using last ReLU layer: {target_layer.name}\")\n                break\n            elif hasattr(layer, 'layers'):\n                for sublayer in reversed(layer.layers):\n                    if isinstance(sublayer, tf.keras.layers.ReLU):\n                        target_layer = sublayer\n                        logger.info(f\"Using last ReLU sublayer: {target_layer.name}\")\n                        break\n                if target_layer is not None:\n                    break\n    \n    # If we still can't find the target layer, just use the last Conv2D layer\n    if target_layer is None:\n        for layer in reversed(model.layers):\n            if isinstance(layer, tf.keras.layers.Conv2D):\n                target_layer = layer\n                logger.info(f\"Falling back to Conv2D layer: {target_layer.name}\")\n                break\n            elif hasattr(layer, 'layers'):\n                for sublayer in reversed(layer.layers):\n                    if isinstance(sublayer, tf.keras.layers.Conv2D):\n                        target_layer = sublayer\n                        logger.info(f\"Falling back to Conv2D sublayer: {target_layer.name}\")\n                        break\n                if target_layer is not None:\n                    break\n    \n    if target_layer is None:\n        raise ValueError(\"Could not find an appropriate layer for Grad-CAM\")\n    \n    # Create a model that outputs both the target layer and the final predictions\n    try:\n        grad_model = tf.keras.models.Model(\n            inputs=model.inputs,\n            outputs=[target_layer.output, model.output]\n        )\n    except Exception as e:\n        logger.error(f\"Failed to create grad model: {e}\")\n        return fallback_gradcam(model, img_array, pred_index)\n    \n    # Calculate gradients\n    with tf.GradientTape() as tape:\n        conv_outputs, predictions = grad_model(img_array)\n        if pred_index is None:\n            pred_index = tf.argmax(predictions[0])\n        class_channel = predictions[:, pred_index]\n    \n    # Gradient of the prediction with respect to the feature maps\n    grads = tape.gradient(class_channel, conv_outputs)\n    \n    # Global average pooling of the gradients\n    pooled_grads = tf.reduce_mean(grads, axis=(0, 1, 2))\n    \n    # Weight the feature maps by their importance (gradients)\n    conv_outputs = conv_outputs[0]\n    heatmap = tf.reduce_sum(tf.multiply(pooled_grads, conv_outputs), axis=-1)\n    \n    # ReLU to keep only positive contributions\n    heatmap = tf.maximum(heatmap, 0)\n    \n    # Normalize the heatmap\n    heatmap = heatmap / (tf.reduce_max(heatmap) + tf.keras.backend.epsilon())\n    \n    return heatmap.numpy(), pred_index\n\ndef fallback_gradcam(model, img_array, pred_index=None):\n    \"\"\"\n    Alternative Grad-CAM approach for when the normal approach fails\n    \"\"\"\n    logger.info(\"Using fallback Grad-CAM implementation\")\n    \n    # Define a simpler model specific to MobileNetV2\n    base_model = tf.keras.applications.MobileNetV2(\n        include_top=False,\n        weights='imagenet',\n        input_shape=(128, 128, 3)\n    )\n    \n    # Get the last convolutional layer\n    last_conv_layer = base_model.get_layer('out_relu')\n    \n    # Create a model that maps to the last conv layer output\n    feature_model = tf.keras.models.Model(\n        inputs=base_model.inputs,\n        outputs=last_conv_layer.output\n    )\n    \n    # Get feature maps\n    feature_maps = feature_model.predict(img_array)\n    \n    # Use channel averages as a simple heatmap\n    heatmap = np.mean(feature_maps[0], axis=-1)\n    \n    # Normalize\n    heatmap = np.maximum(heatmap, 0) / np.max(heatmap)\n    \n    return heatmap, pred_index if pred_index is not None else 0\n\ndef process_heatmap(original_img, heatmap):\n    \"\"\"Process heatmap and create multiple visualizations\"\"\"\n    # Ensure original_img is the correct data type\n    if isinstance(original_img, tf.Tensor):\n        original_img = original_img.numpy()\n    \n    # Convert to uint8 if needed\n    if original_img.dtype != np.uint8:\n        original_img = np.clip(original_img * 255, 0, 255).astype(np.uint8)\n    \n    # Make sure dimensions are correct - OpenCV expects (height, width)\n    h, w = original_img.shape[:2]\n    \n    # 1. Raw heatmap (small grid) - keep as is\n    raw_heatmap = heatmap.copy()\n    \n    # 2. Normalized and interpolated heatmap (same size as original)\n    # Use scipy's zoom instead of OpenCV resize to avoid potential errors\n    from scipy.ndimage import zoom\n    zoom_factor = np.array(original_img.shape[:2]) / np.array(heatmap.shape)\n    norm_heatmap = zoom(heatmap, zoom_factor, order=1)\n    \n    # Convert to uint8\n    norm_heatmap = np.clip(norm_heatmap * 255, 0, 255).astype(np.uint8)\n    \n    # 3. Apply colormap to get colored heatmap\n    colored_heatmap = cv2.applyColorMap(norm_heatmap, cv2.COLORMAP_JET)\n    \n    # 4. Overlay heatmap on original image\n    # Ensure both images have the same shape\n    if colored_heatmap.shape[:2] != original_img.shape[:2]:\n        logger.warning(f\"Shape mismatch: colored_heatmap {colored_heatmap.shape}, original_img {original_img.shape}\")\n        # Resize colored_heatmap to match original_img\n        colored_heatmap = zoom(colored_heatmap, (original_img.shape[0]/colored_heatmap.shape[0], \n                                               original_img.shape[1]/colored_heatmap.shape[1], 1), order=1)\n        colored_heatmap = np.clip(colored_heatmap, 0, 255).astype(np.uint8)\n    \n    # Ensure original_img is 3-channel for overlay\n    if len(original_img.shape) == 2:\n        original_img = cv2.cvtColor(original_img, cv2.COLOR_GRAY2BGR)\n    elif original_img.shape[2] == 4:  # With alpha channel\n        original_img = original_img[:, :, :3]\n    \n    # Create overlay\n    try:\n        superimposed_img = cv2.addWeighted(\n            original_img, \n            0.6,  # Transparency of original image \n            colored_heatmap, \n            0.4,  # Transparency of heatmap\n            0\n        )\n    except Exception as e:\n        logger.error(f\"Error in addWeighted: {e}\")\n        # Fallback to simple overlay\n        superimposed_img = np.copy(original_img)\n        mask = norm_heatmap > 50  # Use threshold to create a simple mask\n        superimposed_img[mask] = np.clip(original_img[mask] * 0.6 + colored_heatmap[mask] * 0.4, 0, 255).astype(np.uint8)\n    \n    return raw_heatmap, norm_heatmap, colored_heatmap, superimposed_img\n\ndef load_and_preprocess_image(image_path, target_size=(128, 128)):\n    \"\"\"Load and preprocess an image for inference\"\"\"\n    try:\n        # Check if the file exists\n        if not os.path.exists(image_path):\n            logger.error(f\"Image file not found: {image_path}\")\n            raise FileNotFoundError(f\"Image not found: {image_path}\")\n        \n        # Load the image using OpenCV for more reliable loading\n        try:\n            img = cv2.imread(image_path)\n            if img is None:\n                raise ValueError(f\"Failed to read image with OpenCV: {image_path}\")\n            img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)  # Convert BGR to RGB\n        except Exception as cv_error:\n            logger.warning(f\"OpenCV image loading failed: {cv_error}, trying TensorFlow\")\n            # Fallback to TensorFlow\n            img = tf.io.read_file(image_path)\n            img = tf.image.decode_image(img, channels=3, expand_animations=False)\n            img = img.numpy()\n        \n        # Store original image for display\n        display_size = (256, 256)\n        original_img = cv2.resize(img, display_size)\n        \n        # Preprocess for model - ensure consistent shapes\n        processed_img = cv2.resize(img, target_size)\n        processed_img = processed_img.astype(np.float32) / 255.0\n        processed_img = np.expand_dims(processed_img, axis=0)\n        \n        logger.info(f\"Preprocessed image shape: {processed_img.shape}\")\n        \n        return original_img, processed_img\n    \n    except Exception as e:\n        logger.error(f\"Error loading image: {e}\")\n        # Create a dummy image as fallback\n        logger.info(\"Creating dummy image as fallback\")\n        dummy_img = np.zeros((256, 256, 3), dtype=np.uint8)\n        dummy_img_processed = np.zeros((1, target_size[0], target_size[1], 3), dtype=np.float32)\n        return dummy_img, dummy_img_processed\n\ndef simple_model_approach():\n    \"\"\"Fallback to a simpler approach using a standard pre-trained model\"\"\"\n    logger.info(\"Using a simplified approach with standard MobileNetV2\")\n    \n    # Use a standard ImageNet model for demonstration\n    model = tf.keras.applications.MobileNetV2(weights='imagenet', include_top=True)\n    \n    # Load some sample image\n    image_path = None\n    for root, dirs, files in os.walk('.'):\n        for file in files:\n            if file.lower().endswith(('.jpg', '.jpeg', '.png')):\n                image_path = os.path.join(root, file)\n                break\n        if image_path:\n            break\n    \n    if not image_path:\n        # Create a dummy colored image\n        dummy_img = np.random.randint(0, 255, (224, 224, 3), dtype=np.uint8)\n        cv2.imwrite('dummy_image.jpg', dummy_img)\n        image_path = 'dummy_image.jpg'\n    \n    # Process image\n    logger.info(f\"Loading image: {image_path}\")\n    img = tf.keras.preprocessing.image.load_img(image_path, target_size=(224, 224))\n    img_array = tf.keras.preprocessing.image.img_to_array(img)\n    img_array = np.expand_dims(img_array, axis=0)\n    img_array = tf.keras.applications.mobilenet_v2.preprocess_input(img_array)\n    \n    # Predict\n    preds = model.predict(img_array)\n    decoded_preds = tf.keras.applications.mobilenet_v2.decode_predictions(preds, top=1)[0]\n    \n    # Display\n    plt.figure(figsize=(6, 6))\n    plt.imshow(img)\n    plt.title(f\"Prediction: {decoded_preds[0][1]}\")\n    plt.axis('off')\n    plt.show()\n    \n    print(f\"Prediction: {decoded_preds[0][1]}\")\n    print(f\"Confidence: {decoded_preds[0][2]:.2%}\")\n\ndef main():\n    try:\n        # Create model with fixed input shape\n        logger.info(\"Creating model...\")\n        model, base_model = create_model(len(class_names))\n        \n        # Model summary for debugging\n        model.summary()\n        \n        # Try to load weights with different strategies\n        weights_path = 'plant_disease_model.weights.h5'\n        weights_loaded = False\n        \n        if os.path.exists(weights_path):\n            logger.info(f\"Found weights file: {weights_path}\")\n            try:\n                # Try normal loading\n                model.load_weights(weights_path)\n                logger.info(\"Weights loaded successfully\")\n                weights_loaded = True\n            except Exception as e:\n                logger.warning(f\"Could not load weights normally: {e}\")\n                try:\n                    # Try loading with skip_mismatch\n                    model.load_weights(weights_path, by_name=True, skip_mismatch=True)\n                    logger.info(\"Weights loaded with skip_mismatch=True\")\n                    weights_loaded = True\n                except Exception as e2:\n                    logger.warning(f\"Could not load weights with skip_mismatch: {e2}\")\n                    logger.info(\"Using pre-trained ImageNet weights instead\")\n        else:\n            logger.warning(f\"Weights file not found: {weights_path}\")\n            logger.info(\"Using pre-trained ImageNet weights instead\")\n        \n        # Load and process image\n        image_path = '/kaggle/input/plant-pathology-2021-fgvc8/test_images/85f8cb619c66b863.jpg'\n        found_image = False\n        \n        try:\n            if os.path.exists(image_path):\n                logger.info(f\"Loading specified image: {image_path}\")\n                original_img, processed_img = load_and_preprocess_image(image_path)\n                found_image = True\n            else:\n                # Try to find any image in the current directory\n                logger.warning(f\"Image not found at {image_path}, searching for alternatives...\")\n                for root, dirs, files in os.walk('.'):\n                    for file in files:\n                        if file.lower().endswith(('.jpg', '.jpeg', '.png')):\n                            alt_image_path = os.path.join(root, file)\n                            logger.info(f\"Found alternative image: {alt_image_path}\")\n                            original_img, processed_img = load_and_preprocess_image(alt_image_path)\n                            found_image = True\n                            break\n                    if found_image:\n                        break\n        except Exception as img_error:\n            logger.error(f\"Error loading image: {img_error}\")\n            # Create a dummy image\n            logger.info(\"Creating a dummy image\")\n            original_img = np.random.randint(0, 255, (256, 256, 3), dtype=np.uint8)\n            processed_img = np.random.random((1, 128, 128, 3)).astype(np.float32)\n            found_image = True\n        \n        if not found_image:\n            logger.error(\"No suitable image found\")\n            raise FileNotFoundError(\"No image files found\")\n        \n        # Check the processed image shape\n        logger.info(f\"Processed image shape: {processed_img.shape}\")\n        \n        # Make prediction with error handling\n        try:\n            logger.info(\"Making prediction...\")\n            preds = model.predict(processed_img)\n            class_idx = np.argmax(preds[0])\n            confidence = preds[0][class_idx]\n            logger.info(f\"Prediction: {class_names[class_idx]}, Confidence: {confidence:.2%}\")\n        except Exception as pred_error:\n            logger.error(f\"Prediction error: {pred_error}\")\n            # Fallback to direct class assignment for demonstration\n            class_idx = 0  # Assume \"complex\" class as fallback\n            confidence = 0.0\n            logger.warning(f\"Using fallback prediction: {class_names[class_idx]}\")\n        \n        # Generate heatmap with detailed error handling\n        try:\n            logger.info(\"Generating Grad-CAM heatmap...\")\n            heatmap, _ = get_grad_cam(model, processed_img, pred_index=class_idx)\n            logger.info(f\"Heatmap shape: {heatmap.shape}\")\n        except Exception as hm_error:\n            logger.error(f\"Heatmap generation error: {hm_error}\")\n            # Create a dummy heatmap\n            logger.info(\"Creating dummy heatmap\")\n            heatmap = np.zeros((128, 128))\n        \n        # Check if original_img is a tensor and convert if needed\n        if isinstance(original_img, tf.Tensor):\n            original_img = original_img.numpy()\n        \n        # Create the overlay with error handling\n        try:\n            # Process heatmap to get different visualizations\n            raw_heatmap, norm_heatmap, colored_heatmap, grad_cam_overlay = process_heatmap(original_img, heatmap)\n        except Exception as overlay_error:\n            logger.error(f\"Overlay error: {overlay_error}\")\n            # Use original image as fallback\n            raw_heatmap = heatmap.copy()\n            norm_heatmap = np.zeros_like(original_img[:,:,0])\n            colored_heatmap = np.zeros_like(original_img)\n            grad_cam_overlay = original_img.copy()\n        \n        # Display results in a 2x2 grid like in the Medium article\n        fig, axes = plt.subplots(2, 2, figsize=(14, 12))\n        \n        # Original image\n        axes[0, 0].imshow(cv2.cvtColor(original_img, cv2.COLOR_BGR2RGB) if len(original_img.shape) == 3 else original_img, \n                         interpolation='nearest')\n        axes[0, 0].set_title('Original', fontsize=14)\n        axes[0, 0].set_ylabel('Pixels', fontsize=12)\n        axes[0, 0].set_xlabel('Pixels', fontsize=12)\n        fig.colorbar(axes[0, 0].imshow(cv2.cvtColor(original_img, cv2.COLOR_BGR2RGB) if len(original_img.shape) == 3 else original_img), \n                    ax=axes[0, 0], fraction=0.046, pad=0.04)\n        \n        # Raw Grad-CAM (small grid)\n        raw_im = axes[0, 1].imshow(raw_heatmap, cmap='jet', interpolation='nearest')\n        axes[0, 1].set_title('Raw Grad-CAM', fontsize=14)\n        axes[0, 1].set_xlabel('Pixels', fontsize=12)\n        fig.colorbar(raw_im, ax=axes[0, 1], fraction=0.046, pad=0.04)\n        \n        # Normalized Grad-CAM (interpolated)\n        norm_im = axes[1, 0].imshow(norm_heatmap, cmap='jet', interpolation='nearest')\n        axes[1, 0].set_title('Normalized Grad-CAM', fontsize=14)\n        axes[1, 0].set_ylabel('Pixels', fontsize=12)\n        axes[1, 0].set_xlabel('Pixels', fontsize=12)\n        fig.colorbar(norm_im, ax=axes[1, 0], fraction=0.046, pad=0.04)\n        \n        # Applied Grad-CAM overlay\n        overlay_im = axes[1, 1].imshow(cv2.cvtColor(grad_cam_overlay, cv2.COLOR_BGR2RGB) if len(grad_cam_overlay.shape) == 3 else grad_cam_overlay, \n                                     interpolation='nearest')\n        axes[1, 1].set_title('Applied Grad-CAM', fontsize=14)\n        axes[1, 1].set_xlabel('Pixels', fontsize=12)\n        fig.colorbar(overlay_im, ax=axes[1, 1], fraction=0.046, pad=0.04)\n        \n        # Add overall title with prediction info\n        plt.suptitle(f\"Disease Diagnosis: {class_names[class_idx]} (Confidence: {confidence:.2%})\", \n                    fontsize=16, y=0.98)\n        \n        plt.tight_layout()\n        plt.subplots_adjust(top=0.93)  # Adjust to make room for the suptitle\n        \n        plt.tight_layout()\n        plt.show()\n        \n        # Print results\n        print(f'\\n🔍 Disease Diagnosis:')\n        print(f'- Disease: {class_names[class_idx]}')\n        print(f'- Confidence: {confidence:.2%}')\n        print(f'- Affected Areas: Red/yellow regions in heatmap')\n    \n    except Exception as e:\n        logger.error(f\"An error occurred in main: {e}\", exc_info=True)\n        print(f\"Error: {e}\")\n        \n        # Fallback to simple approach\n        logger.info(\"Falling back to simple model approach\")\n        try:\n            simple_model_approach()\n        except Exception as fallback_error:\n            logger.error(f\"Fallback approach also failed: {fallback_error}\")\n            print(\"Both approaches failed. Please check logs for details.\")\n\nif __name__ == \"__main__\":\n    main()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-14T07:57:16.610232Z","iopub.execute_input":"2025-03-14T07:57:16.610607Z","iopub.status.idle":"2025-03-14T07:57:21.537020Z","shell.execute_reply.started":"2025-03-14T07:57:16.610576Z","shell.execute_reply":"2025-03-14T07:57:21.535896Z"},"collapsed":true,"jupyter":{"outputs_hidden":true}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import tensorflow as tf\nimport numpy as np\nimport cv2\nimport matplotlib.pyplot as plt\nfrom tensorflow.keras import models, layers\nimport os\n\n# Disable mixed precision to avoid float16 problems\ntf.keras.mixed_precision.set_global_policy('float32')\n\n# Class names\nclass_names = [\n    'complex', 'frog_eye_leaf_spot', 'frog_eye_leaf_spot complex',\n    'healthy', 'powdery_mildew', 'powdery_mildew complex', \n    'rust', 'rust complex', 'rust frog_eye_leaf_spot',\n    'scab', 'scab frog_eye_leaf_spot', 'scab frog_eye_leaf_spot complex'\n]\n\ndef create_model(num_classes, img_size=(128, 128)):\n    \"\"\"\n    Create a model using MobileNetV2 as base\n    \"\"\"\n    # Create input with fixed shape\n    inputs = tf.keras.Input(shape=(img_size[0], img_size[1], 3))\n    \n    # Create base model with proper input shape\n    base_model = tf.keras.applications.MobileNetV2(\n        include_top=False,\n        weights='imagenet',\n        input_tensor=inputs\n    )\n    \n    # Make base model non-trainable\n    base_model.trainable = False\n    \n    # Create model with functional API to ensure shapes are defined\n    x = base_model.output\n    x = layers.GlobalAveragePooling2D()(x)\n    x = layers.Dense(256, activation='relu')(x)\n    x = layers.Dropout(0.3)(x)\n    outputs = layers.Dense(num_classes, activation='softmax')(x)\n    \n    # Create model with explicit inputs and outputs\n    model = tf.keras.Model(inputs=inputs, outputs=outputs)\n    \n    # Compile model\n    model.compile(\n        optimizer=tf.keras.optimizers.Adam(learning_rate=0.001),\n        loss='categorical_crossentropy',\n        metrics=['accuracy']\n    )\n    \n    return model, base_model\n\ndef get_grad_cam(model, img_array, layer_name=None, pred_index=None):\n    \"\"\"\n    Calculate Grad-CAM with automatic layer detection if not specified\n    \"\"\"\n    # For MobileNetV2, we want to target the \"out_relu\" layer which is the last activation layer\n    target_layer_name = \"out_relu\"\n    \n    # Find the target layer\n    target_layer = None\n    \n    # Method 1: Find layer directly\n    try:\n        target_layer = model.get_layer(target_layer_name)\n        print(f\"Found target layer directly: {target_layer_name}\")\n    except:\n        print(f\"Layer '{target_layer_name}' not found directly in model\")\n        \n        # Method 2: Search in base model\n        for layer in model.layers:\n            if hasattr(layer, 'layers'):  # This is likely the base model\n                try:\n                    target_layer = layer.get_layer(target_layer_name)\n                    print(f\"Found target layer in base model: {target_layer_name}\")\n                    break\n                except:\n                    continue\n    \n    # Method 3: Find the last ReLU layer before GlobalAveragePooling2D\n    if target_layer is None:\n        print(\"Searching for appropriate target layer...\")\n        for layer in reversed(model.layers):\n            if isinstance(layer, tf.keras.layers.ReLU):\n                target_layer = layer\n                print(f\"Using last ReLU layer: {target_layer.name}\")\n                break\n            elif hasattr(layer, 'layers'):\n                for sublayer in reversed(layer.layers):\n                    if isinstance(sublayer, tf.keras.layers.ReLU):\n                        target_layer = sublayer\n                        print(f\"Using last ReLU sublayer: {target_layer.name}\")\n                        break\n                if target_layer is not None:\n                    break\n    \n    # Method 4: Use the last Conv2D layer\n    if target_layer is None:\n        for layer in reversed(model.layers):\n            if isinstance(layer, tf.keras.layers.Conv2D):\n                target_layer = layer\n                print(f\"Using Conv2D layer: {target_layer.name}\")\n                break\n            elif hasattr(layer, 'layers'):\n                for sublayer in reversed(layer.layers):\n                    if isinstance(sublayer, tf.keras.layers.Conv2D):\n                        target_layer = sublayer\n                        print(f\"Using Conv2D sublayer: {target_layer.name}\")\n                        break\n                if target_layer is not None:\n                    break\n    \n    if target_layer is None:\n        raise ValueError(\"Could not find appropriate layer for Grad-CAM\")\n    \n    # Create a model that outputs both the target layer and the final predictions\n    try:\n        grad_model = tf.keras.models.Model(\n            inputs=model.inputs,\n            outputs=[target_layer.output, model.output]\n        )\n    except Exception as e:\n        print(f\"Failed to create grad model: {e}\")\n        # Create dummy heatmap\n        dummy_heatmap = np.ones((4, 4), dtype=np.float32)\n        return dummy_heatmap, pred_index if pred_index is not None else 0\n    \n    # Calculate gradients\n    with tf.GradientTape() as tape:\n        conv_outputs, predictions = grad_model(img_array)\n        if pred_index is None:\n            pred_index = tf.argmax(predictions[0])\n        class_channel = predictions[:, pred_index]\n    \n    # Gradient of the prediction with respect to the feature maps\n    grads = tape.gradient(class_channel, conv_outputs)\n    \n    # Global average pooling of gradients\n    pooled_grads = tf.reduce_mean(grads, axis=(0, 1, 2))\n    \n    # Weight feature maps by importance (gradients)\n    conv_outputs = conv_outputs[0]\n    heatmap = tf.reduce_sum(tf.multiply(pooled_grads, conv_outputs), axis=-1)\n    \n    # ReLU to keep only positive contributions\n    heatmap = tf.maximum(heatmap, 0)\n    \n    # Normalize the heatmap\n    heatmap = heatmap / (tf.reduce_max(heatmap) + tf.keras.backend.epsilon())\n    \n    # Convert to NumPy and ensure float32\n    heatmap_np = heatmap.numpy().astype(np.float32)\n    \n    return heatmap_np, pred_index\n\ndef process_heatmap_v2(original_img, heatmap, alpha=0.6, threshold=0.5, enhance_factor=2.0):\n    \"\"\"\n    Process heatmap and create various visualizations - fixed version\n    \"\"\"\n    # Convert data to correct types\n    if isinstance(original_img, tf.Tensor):\n        original_img = original_img.numpy()\n    \n    # Convert to uint8 if needed\n    if original_img.dtype != np.uint8:\n        original_img = np.clip(original_img * 255, 0, 255).astype(np.uint8)\n    \n    # Check dimensions\n    h, w = original_img.shape[:2]\n    \n    # 1. Raw heatmap - keep as is\n    raw_heatmap = heatmap.copy()\n    \n    # Print debug info\n    print(f\"Heatmap shape: {heatmap.shape}\")\n    print(f\"Original image shape: {original_img.shape}\")\n    print(f\"Heatmap data type: {heatmap.dtype}\")\n    \n    # 2. Resize heatmap to match original image using manual resizing\n    # Create norm_heatmap same size as original image\n    norm_heatmap = np.zeros((h, w), dtype=np.float32)\n    \n    # Resize with simple method (nearest neighbor)\n    heatmap_h, heatmap_w = heatmap.shape\n    h_ratio = h / heatmap_h\n    w_ratio = w / heatmap_w\n    \n    for i in range(h):\n        for j in range(w):\n            src_i = min(int(i / h_ratio), heatmap_h - 1)\n            src_j = min(int(j / w_ratio), heatmap_w - 1)\n            norm_heatmap[i, j] = heatmap[src_i, src_j]\n    \n    # Enhance heatmap intensity\n    enhanced_heatmap = np.power(norm_heatmap, 1.0/enhance_factor)\n    \n    # Convert to uint8\n    norm_heatmap_uint8 = np.clip(norm_heatmap * 255, 0, 255).astype(np.uint8)\n    enhanced_heatmap_uint8 = np.clip(enhanced_heatmap * 255, 0, 255).astype(np.uint8)\n    \n    # Apply colormap to get colored heatmaps\n    colored_heatmap = cv2.applyColorMap(norm_heatmap_uint8, cv2.COLORMAP_JET)\n    enhanced_colored_heatmap = cv2.applyColorMap(enhanced_heatmap_uint8, cv2.COLORMAP_JET)\n    \n    # Ensure original_img is 3-channel\n    if len(original_img.shape) == 2:\n        original_img = cv2.cvtColor(original_img, cv2.COLOR_GRAY2BGR)\n    elif original_img.shape[2] == 4:  # With alpha channel\n        original_img = original_img[:, :, :3]\n    \n    # Create normal overlay\n    superimposed_img = cv2.addWeighted(\n        original_img, \n        1 - alpha,  # Transparency of original image \n        colored_heatmap, \n        alpha,  # Transparency of heatmap \n        0\n    )\n    \n    # Create enhanced overlay\n    enhanced_overlay = cv2.addWeighted(\n        original_img, \n        1 - alpha,  # Transparency of original image \n        enhanced_colored_heatmap, \n        alpha,  # Transparency of enhanced heatmap\n        0\n    )\n    \n    # 5. Create custom overlay to clearly show disease areas\n    disease_highlight = np.copy(original_img)\n    \n    # Create mask for disease areas\n    disease_mask = enhanced_heatmap > threshold\n    \n    # In disease areas, increase red intensity\n    disease_overlay = np.zeros_like(original_img)\n    disease_overlay[disease_mask, 0] = 0      # Blue channel\n    disease_overlay[disease_mask, 1] = 0      # Green channel\n    disease_overlay[disease_mask, 2] = 255    # Red channel\n    \n    # Blend original image with red overlay\n    disease_highlight = cv2.addWeighted(disease_highlight, 0.7, disease_overlay, 0.3, 0)\n    \n    # Add contours around disease areas\n    # Convert mask to uint8 for findContours\n    mask_uint8 = disease_mask.astype(np.uint8) * 255\n    contours, _ = cv2.findContours(mask_uint8, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)\n    cv2.drawContours(disease_highlight, contours, -1, (0, 255, 0), 2)  # Draw green contours\n    \n    return raw_heatmap, norm_heatmap, superimposed_img, disease_highlight, enhanced_overlay\n\ndef load_and_preprocess_image(image_path, target_size=(128, 128)):\n    \"\"\"Load and preprocess image for inference\"\"\"\n    try:\n        # Check if file exists\n        if not os.path.exists(image_path):\n            print(f\"Image file not found: {image_path}\")\n            # Create dummy image\n            dummy_img = np.zeros((256, 256, 3), dtype=np.uint8)\n            dummy_img_processed = np.zeros((1, target_size[0], target_size[1], 3), dtype=np.float32)\n            return dummy_img, dummy_img_processed\n        \n        # Load image with OpenCV\n        try:\n            img = cv2.imread(image_path)\n            if img is not None:\n                img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n                print(\"Successfully loaded image with OpenCV\")\n            else:\n                # If OpenCV can't load, create dummy image\n                print(f\"OpenCV cannot read image: {image_path}\")\n                img = np.zeros((256, 256, 3), dtype=np.uint8)\n        except Exception as cv_error:\n            print(f\"Failed to load image with OpenCV: {cv_error}\")\n            img = np.zeros((256, 256, 3), dtype=np.uint8)\n        \n        # Store original image for display\n        display_size = (256, 256)\n        original_img = cv2.resize(img, display_size)\n        \n        # Preprocess for model\n        processed_img = cv2.resize(img, target_size)\n        processed_img = processed_img.astype(np.float32) / 255.0\n        processed_img = np.expand_dims(processed_img, axis=0)\n        \n        print(f\"Processed image shape: {processed_img.shape}\")\n        \n        return original_img, processed_img\n    \n    except Exception as e:\n        print(f\"Error loading image: {e}\")\n        # Create dummy image\n        dummy_img = np.zeros((256, 256, 3), dtype=np.uint8)\n        dummy_img_processed = np.zeros((1, target_size[0], target_size[1], 3), dtype=np.float32)\n        return dummy_img, dummy_img_processed\n\ndef visualize_plant_disease(image_path, model_path=None, alpha=0.7, threshold=0.5, enhance_factor=1.5):\n    \"\"\"\n    Main function to load image and model, then visualize disease locations\n    \"\"\"\n    # Create and load model\n    model, base_model = create_model(len(class_names))\n    \n    # Load weights if specified\n    if model_path and os.path.exists(model_path):\n        try:\n            model.load_weights(model_path, by_name=True, skip_mismatch=True)\n            print(f\"Loaded weights from {model_path}\")\n        except Exception as e:\n            print(f\"Could not load weights: {e}\")\n    \n    # Load and preprocess image\n    original_img, processed_img = load_and_preprocess_image(image_path)\n    \n    # Make prediction\n    preds = model.predict(processed_img)\n    class_idx = np.argmax(preds[0])\n    confidence = preds[0][class_idx]\n    \n    # Generate Grad-CAM heatmap\n    heatmap, _ = get_grad_cam(model, processed_img, pred_index=class_idx)\n    \n    # Process heatmap to create visualizations - use fixed version\n    raw_heatmap, norm_heatmap, grad_cam_overlay, disease_highlight, enhanced_overlay = process_heatmap_v2(\n        original_img, heatmap, alpha=alpha, threshold=threshold, enhance_factor=enhance_factor\n    )\n    \n    # Display results\n    fig, axes = plt.subplots(2, 2, figsize=(15, 13))\n    \n    # Original image\n    axes[0, 0].imshow(original_img)\n    axes[0, 0].set_title('Original Image', fontsize=14)\n    axes[0, 0].set_ylabel('Pixels', fontsize=12)\n    axes[0, 0].set_xlabel('Pixels', fontsize=12)\n    \n    # Raw Grad-CAM\n    raw_im = axes[0, 1].imshow(raw_heatmap, cmap='jet', interpolation='nearest')\n    axes[0, 1].set_title('Raw Grad-CAM', fontsize=14)\n    axes[0, 1].set_xlabel('Pixels', fontsize=12)\n    fig.colorbar(raw_im, ax=axes[0, 1], fraction=0.046, pad=0.04)\n    \n    # Disease location highlight\n    axes[1, 0].imshow(disease_highlight)\n    axes[1, 0].set_title('Disease Location (Green Outline)', fontsize=14)\n    axes[1, 0].set_ylabel('Pixels', fontsize=12)\n    axes[1, 0].set_xlabel('Pixels', fontsize=12)\n    \n    # Enhanced Grad-CAM\n    axes[1, 1].imshow(enhanced_overlay)\n    axes[1, 1].set_title('Enhanced Grad-CAM', fontsize=14)\n    axes[1, 1].set_xlabel('Pixels', fontsize=12)\n    \n    # Add title with diagnosis info\n    plt.suptitle(f\"Disease Diagnosis: {class_names[class_idx]} (Confidence: {confidence:.2%})\", \n                fontsize=16, y=0.98)\n    \n    plt.tight_layout()\n    plt.subplots_adjust(top=0.93)  # Adjust to make room for title\n    plt.show()\n    \n    # Show additional diagnosis info\n    print(f'\\n🔍 Plant Disease Diagnosis:')\n    print(f'- Disease: {class_names[class_idx]}')\n    print(f'- Confidence: {confidence:.2%}')\n    print(f'- Affected Areas: Red/yellow regions in heatmap with green outlines')\n    \n    return {\n        'class': class_names[class_idx],\n        'confidence': confidence,\n        'heatmap': heatmap,\n        'disease_highlight': disease_highlight,\n        'enhanced_overlay': enhanced_overlay\n    }\n\n# Example usage\nif __name__ == \"__main__\":\n    # Set the path to the leaf image to analyze\n    leaf_image_path = '/kaggle/input/plant-pathology-2021-fgvc8/test_images/85f8cb619c66b863.jpg'\n    \n    # Call the function\n    try:\n        results = visualize_plant_disease(\n            image_path=leaf_image_path,\n            alpha=0.7,\n            threshold=0.4,\n            enhance_factor=2.0\n        )\n        print(\"Successfully completed!\")\n    except Exception as e:\n        print(f\"An error occurred: {e}\")\n        import traceback\n        traceback.print_exc()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-14T08:31:19.036949Z","iopub.execute_input":"2025-03-14T08:31:19.037308Z","iopub.status.idle":"2025-03-14T08:31:23.471462Z","shell.execute_reply.started":"2025-03-14T08:31:19.037280Z","shell.execute_reply":"2025-03-14T08:31:23.470400Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}