{
  "nbformat": 4,
  "nbformat_minor": 0,
  "metadata": {
    "colab": {
      "provenance": []
    },
    "kernelspec": {
      "name": "python3",
      "display_name": "Python 3"
    },
    "language_info": {
      "name": "python"
    }
  },
  "cells": [
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "rcBJE8BE2WE1"
      },
      "outputs": [],
      "source": [
        "# ================================================================\n",
        "#   Hybrid Multi-Stage Classification (HMSC)\n",
        "#   Early Fusion + Late Fusion + VGG Classifier + LIME\n",
        "# ================================================================\n",
        "\n",
        "import numpy as np\n",
        "import pandas as pd\n",
        "import os\n",
        "import cv2\n",
        "import glob\n",
        "import matplotlib.pyplot as plt\n",
        "from sklearn.metrics import classification_report, confusion_matrix, roc_auc_score\n",
        "from sklearn.metrics import matthews_corrcoef, precision_score, recall_score, f1_score, accuracy_score\n",
        "from tensorflow.keras.preprocessing.image import ImageDataGenerator\n",
        "from tensorflow.keras.applications import VGG16, InceptionV3, EfficientNetB0, MobileNetV3Large\n",
        "from tensorflow.keras.models import Model\n",
        "from tensorflow.keras.layers import GlobalAveragePooling2D, Dense, Dropout, Input, Concatenate\n",
        "from tensorflow.keras.optimizers import Adam\n",
        "from tensorflow.keras.utils import to_categorical\n",
        "from lime import lime_image\n",
        "from skimage.segmentation import mark_boundaries\n",
        "from tensorflow.keras.preprocessing import image\n",
        "from sklearn.model_selection import train_test_split\n",
        "from tqdm import tqdm\n",
        "\n",
        "# ================================================================\n",
        "#   1. Load Dataset Function\n",
        "#   (DermNet as major, and HAM10000, ISIC-2019 for reviewer validation)\n",
        "# ================================================================\n",
        "\n",
        "IMG_SIZE = 224\n",
        "\n",
        "def load_images_from_folder(path, label):\n",
        "    images = []\n",
        "    labels = []\n",
        "    for img in glob.glob(path + \"/*.jpg\"):\n",
        "        im = cv2.imread(img)\n",
        "        if im is None:\n",
        "            continue\n",
        "        im = cv2.cvtColor(im, cv2.COLOR_BGR2RGB)\n",
        "        im = cv2.resize(im, (IMG_SIZE, IMG_SIZE))\n",
        "        images.append(im)\n",
        "        labels.append(label)\n",
        "    return images, labels\n",
        "\n",
        "\n",
        "# ================================================================\n",
        "#  DermNet Dataset Loading (5 classes)\n",
        "# ================================================================\n",
        "\n",
        "root_dir = \"Dermnet_dataset_path\"\n",
        "\n",
        "classes = [\"acne\", \"eczema\", \"psoriasis\", \"scabies\", \"urticaria\"]\n",
        "\n",
        "X, y = [], []\n",
        "\n",
        "for idx, cls in enumerate(classes):\n",
        "    imgs, labels_ = load_images_from_folder(os.path.join(root_dir, cls), idx)\n",
        "    X.extend(imgs)\n",
        "    y.extend(labels_)\n",
        "\n",
        "X = np.array(X)\n",
        "y = np.array(y)\n",
        "\n",
        "print(\"DermNet Loaded:\", X.shape)\n",
        "\n",
        "\n",
        "# ================================================================\n",
        "#  Preprocessing & Augmentation\n",
        "# ================================================================\n",
        "\n",
        "datagen = ImageDataGenerator(\n",
        "    rotation_range=25,\n",
        "    width_shift_range=0.15,\n",
        "    height_shift_range=0.15,\n",
        "    brightness_range=[0.8, 1.2],\n",
        "    zoom_range=0.2,\n",
        "    horizontal_flip=True,\n",
        "    vertical_flip=True,\n",
        "    validation_split=0.2,\n",
        "    rescale=1./255\n",
        ")\n",
        "\n",
        "train_gen = datagen.flow(\n",
        "    X, to_categorical(y, 5),\n",
        "    batch_size=32, subset='training'\n",
        ")\n",
        "\n",
        "test_gen = datagen.flow(\n",
        "    X, to_categorical(y, 5),\n",
        "    batch_size=32, subset='validation'\n",
        ")\n",
        "\n",
        "\n",
        "# ================================================================\n",
        "#  2. Feature Extraction Models\n",
        "# ================================================================\n",
        "\n",
        "def build_feature_extractor(model_class, input_shape=(224,224,3)):\n",
        "    base = model_class(weights='imagenet', include_top=False, input_shape=input_shape)\n",
        "    x = GlobalAveragePooling2D()(base.output)\n",
        "    return Model(base.input, x)\n",
        "\n",
        "feat_vgg = build_feature_extractor(VGG16)\n",
        "feat_inc = build_feature_extractor(InceptionV3)\n",
        "feat_eff = build_feature_extractor(EfficientNetB0)\n",
        "feat_mob = build_feature_extractor(MobileNetV3Large)\n",
        "\n",
        "extractors = [feat_vgg, feat_inc, feat_eff, feat_mob]\n",
        "\n",
        "\n",
        "# ================================================================\n",
        "#  Early Fusion (Feature Concatenation)\n",
        "# ================================================================\n",
        "\n",
        "def extract_features(model, X):\n",
        "    feats = []\n",
        "    for i in tqdm(range(len(X)), desc=\"Extracting\"):\n",
        "        img = X[i] / 255.0\n",
        "        img = np.expand_dims(img, axis=0)\n",
        "        feat = model.predict(img, verbose=0)\n",
        "        feats.append(feat.flatten())\n",
        "    return np.array(feats)\n",
        "\n",
        "F_vgg = extract_features(feat_vgg, X)\n",
        "F_inc = extract_features(feat_inc, X)\n",
        "F_eff = extract_features(feat_eff, X)\n",
        "F_mob = extract_features(feat_mob, X)\n",
        "\n",
        "F_early = np.concatenate([F_vgg, F_inc, F_eff, F_mob], axis=1)\n",
        "print(\"Early Fusion Shape:\", F_early.shape)\n",
        "\n",
        "\n",
        "# ================================================================\n",
        "#  Late Fusion (Probability Weighted Averaging)\n",
        "# ================================================================\n",
        "\n",
        "def build_softmax_model(model_class):\n",
        "    base = model_class(weights='imagenet', include_top=False, input_shape=(224,224,3))\n",
        "    x = GlobalAveragePooling2D()(base.output)\n",
        "    x = Dense(128, activation=\"relu\")(x)\n",
        "    out = Dense(5, activation=\"softmax\")(x)\n",
        "    model = Model(base.input, out)\n",
        "    model.compile(optimizer=Adam(1e-4), loss=\"categorical_crossentropy\", metrics=[\"accuracy\"])\n",
        "    return model\n",
        "\n",
        "\n",
        "m1 = build_softmax_model(VGG16)\n",
        "m2 = build_softmax_model(InceptionV3)\n",
        "m3 = build_softmax_model(EfficientNetB0)\n",
        "m4 = build_softmax_model(MobileNetV3Large)\n",
        "\n",
        "# Train (for demonstration small epochs)\n",
        "m1.fit(train_gen, epochs=3, verbose=1)\n",
        "m2.fit(train_gen, epochs=3, verbose=1)\n",
        "m3.fit(train_gen, epochs=3, verbose=1)\n",
        "m4.fit(train_gen, epochs=3, verbose=1)\n",
        "\n",
        "P1 = m1.predict(X/255.0)\n",
        "P2 = m2.predict(X/255.0)\n",
        "P3 = m3.predict(X/255.0)\n",
        "P4 = m4.predict(X/255.0)\n",
        "\n",
        "weights = [0.4, 0.2, 0.2, 0.2]  # VGG highest\n",
        "\n",
        "P_late = weights[0]*P1 + weights[1]*P2 + weights[2]*P3 + weights[3]*P4\n",
        "\n",
        "\n",
        "# ================================================================\n",
        "#  Hybrid Fusion = Early Fusion + Late Fusion Probabilities\n",
        "# ================================================================\n",
        "\n",
        "F_hybrid = np.concatenate([F_early, P_late], axis=1)\n",
        "\n",
        "X_train, X_test, y_train, y_test = train_test_split(\n",
        "    F_hybrid, to_categorical(y,5), test_size=0.2, stratify=y\n",
        ")\n",
        "\n",
        "\n",
        "# ================================================================\n",
        "#  Final VGG16 Classifier on Hybrid Features\n",
        "# ================================================================\n",
        "\n",
        "inp = Input(shape=(F_hybrid.shape[1],))\n",
        "x = Dense(512, activation=\"relu\")(inp)\n",
        "x = Dropout(0.4)(x)\n",
        "x = Dense(256, activation=\"relu\")(x)\n",
        "x = Dropout(0.3)(x)\n",
        "out = Dense(5, activation=\"softmax\")(x)\n",
        "\n",
        "clf = Model(inp, out)\n",
        "clf.compile(optimizer=Adam(1e-4), loss=\"categorical_crossentropy\", metrics=[\"accuracy\"])\n",
        "\n",
        "clf.fit(X_train, y_train, epochs=50, batch_size=32, validation_split=0.2)\n",
        "\n",
        "# epochs will be [50:100:150]\n",
        "# ================================================================\n",
        "#  Evaluation Metrics\n",
        "# ================================================================\n",
        "\n",
        "y_pred = clf.predict(X_test)\n",
        "y_true_cls = np.argmax(y_test, axis=1)\n",
        "y_pred_cls = np.argmax(y_pred, axis=1)\n",
        "\n",
        "print(\"Accuracy:\", accuracy_score(y_true_cls, y_pred_cls))\n",
        "print(\"Precision:\", precision_score(y_true_cls, y_pred_cls, average='macro'))\n",
        "print(\"Recall:\", recall_score(y_true_cls, y_pred_cls, average='macro'))\n",
        "print(\"F1:\", f1_score(y_true_cls, y_pred_cls, average='macro'))\n",
        "print(\"MCC:\", matthews_corrcoef(y_true_cls, y_pred_cls))\n",
        "\n",
        "\n",
        "# ================================================================\n",
        "#  LIME Explainability\n",
        "# ================================================================\n",
        "\n",
        "explainer = lime_image.LimeImageExplainer()\n",
        "\n",
        "def explain_image(img_index):\n",
        "    img = X[img_index] / 255.0\n",
        "\n",
        "    def predict_fn(images):\n",
        "        images = np.array(images)\n",
        "        feats = np.concatenate([\n",
        "            extract_features(feat_vgg, images),\n",
        "            extract_features(feat_inc, images),\n",
        "            extract_features(feat_eff, images),\n",
        "            extract_features(feat_mob, images)\n",
        "        ], axis=1)\n",
        "        return clf.predict(feats)\n",
        "\n",
        "    explanation = explainer.explain_instance(\n",
        "        img, predict_fn, top_labels=1, hide_color=0, num_samples=1000\n",
        "    )\n",
        "\n",
        "    temp, mask = explanation.get_image_and_mask(\n",
        "        explanation.top_labels[0], positive_only=True, hide_rest=False\n",
        "    )\n",
        "\n",
        "    plt.imshow(mark_boundaries(temp, mask))\n",
        "    plt.title(\"LIME Explanation\")\n",
        "    plt.show()\n",
        "\n",
        "\n",
        "# ================================================================\n",
        "#  Load HAM10000 Dataset\n",
        "# ================================================================\n",
        "\n",
        "ham_dir = \"HAM10000_path\"\n",
        "ham_imgs, ham_labels = load_images_from_folder(ham_dir, 0)\n",
        "ham_imgs = np.array(ham_imgs) / 255.0\n",
        "\n",
        "# Test using hybrid pipeline\n",
        "F_v = extract_features(feat_vgg, ham_imgs)\n",
        "F_i = extract_features(feat_inc, ham_imgs)\n",
        "F_e = extract_features(feat_eff, ham_imgs)\n",
        "F_m = extract_features(feat_mob, ham_imgs)\n",
        "\n",
        "F_early_ham = np.concatenate([F_v, F_i, F_e, F_m], axis=1)\n",
        "P_late_ham = clf.predict(F_early_ham)\n",
        "\n",
        "\n",
        "# ================================================================\n",
        "#  Load ISIC-2019 Dataset\n",
        "# ================================================================\n",
        "\n",
        "isic_dir = \"ISIC_2019_path\"\n",
        "isic_imgs, isic_labels = load_images_from_folder(isic_dir, 0)\n",
        "isic_imgs = np.array(isic_imgs)/255.0\n",
        "\n",
        "F_v2 = extract_features(feat_vgg, isic_imgs)\n",
        "F_i2 = extract_features(feat_inc, isic_imgs)\n",
        "F_e2 = extract_features(feat_eff, isic_imgs)\n",
        "F_m2 = extract_features(feat_mob, isic_imgs)\n",
        "\n",
        "F_early_isic = np.concatenate([F_v2, F_i2, F_e2, F_m2], axis=1)\n",
        "P_late_isic = clf.predict(F_early_isic)\n",
        "\n"
      ]
    }
  ]
}