{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Is This Leaf Diseased?\n",
    "\n",
    "leaf_disease.py\n",
    "\n",
    "AI project on Computer Vision: spotting a diseased crop leaf from a photo,\n",
    "and saying how much of the leaf is affected.\n",
    "\n",
    "CBSE Class X, Artificial Intelligence (417), Part C project work.\n",
    "SDG 2 (Zero Hunger) and SDG 12 (Responsible Consumption and Production).\n",
    "\n",
    "The leaf is photographed on a plain sheet of paper. The program then:\n",
    "  1. finds which pixels are leaf and which are paper (segmentation),\n",
    "  2. counts how many leaf pixels are brown or yellow instead of green,\n",
    "  3. turns that into one FEATURE: the percentage of the leaf affected, and\n",
    "  4. learns, from the training photos, the cut-off percentage that best\n",
    "     separates healthy leaves from diseased ones.\n",
    "\n",
    "Libraries: numpy (pixel arithmetic), matplotlib (reading and showing\n",
    "images, and the charts), pandas (the table of features), os (folders)."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Stage 1 - Problem scoping"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(\"=\" * 60)\n",
    "print(\"IS THIS LEAF DISEASED?\")\n",
    "print(\"=\" * 60)\n",
    "print(\"Who   : farmers, especially those far from an expert\")\n",
    "print(\"What  : leaf disease noticed late spreads through the field\")\n",
    "print(\"Where : vegetable and crop fields, in the growing season\")\n",
    "print(\"Why   : early action saves the harvest and avoids extra spraying\")\n",
    "print()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Stage 2 - Data acquisition"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import os\n",
    "import numpy as np\n",
    "import pandas as pd\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "pd.set_option(\"display.width\", 120)\n",
    "\n",
    "classes = [\"healthy\", \"diseased\"]\n",
    "paths = []\n",
    "labels = []\n",
    "for c in classes:\n",
    "    for name in sorted(os.listdir(os.path.join(\"images\", c))):\n",
    "        paths.append(os.path.join(\"images\", c, name))\n",
    "        labels.append(c)\n",
    "\n",
    "print(\"--- Stage 2: Data acquisition ---\")\n",
    "print(\"Healthy leaves  :\", labels.count(\"healthy\"))\n",
    "print(\"Diseased leaves :\", labels.count(\"diseased\"))\n",
    "print(\"Total photos    :\", len(paths))\n",
    "print(\"Size of a photo :\", plt.imread(paths[0]).shape)\n",
    "print()\n",
    "\n",
    "healthy_paths = []\n",
    "diseased_paths = []\n",
    "for i in range(len(paths)):\n",
    "    if labels[i] == \"healthy\":\n",
    "        healthy_paths.append(paths[i])\n",
    "    else:\n",
    "        diseased_paths.append(paths[i])\n",
    "\n",
    "# Chart 1: six leaves of each kind.\n",
    "plt.figure(figsize=(9, 3.2))\n",
    "for i in range(6):\n",
    "    plt.subplot(2, 6, i + 1)\n",
    "    plt.imshow(plt.imread(healthy_paths[i * len(healthy_paths) // 6]))\n",
    "    plt.axis(\"off\")\n",
    "    if i == 0:\n",
    "        plt.title(\"healthy\", fontsize=9, loc=\"left\")\n",
    "    plt.subplot(2, 6, i + 7)\n",
    "    plt.imshow(plt.imread(diseased_paths[i * len(diseased_paths) // 6]))\n",
    "    plt.axis(\"off\")\n",
    "    if i == 0:\n",
    "        plt.title(\"diseased\", fontsize=9, loc=\"left\")\n",
    "plt.tight_layout()\n",
    "plt.savefig(\"chart1_sample_leaves.png\")\n",
    "plt.close()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Stage 3 - Data exploration"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(\"--- Stage 3: Data exploration ---\")\n",
    "\n",
    "# Feature extraction, done the same way for every photo:\n",
    "#   - Paper is grey: its red, green and blue are almost equal. A pixel whose\n",
    "#     largest and smallest colour differ by more than 35 is leaf.\n",
    "#   - Healthy leaf is green: much more green than red. A leaf pixel whose\n",
    "#     red is at least 0.75 of its green is brown or yellow - affected.\n",
    "rows = []\n",
    "for i in range(len(paths)):\n",
    "    img = plt.imread(paths[i])[:, :, :3].astype(float) * 255\n",
    "    spread = img.max(axis=2) - img.min(axis=2)\n",
    "    leaf = spread > 35\n",
    "    affected = leaf & (img[:, :, 0] >= 0.75 * img[:, :, 1])\n",
    "    percent = affected.sum() / leaf.sum() * 100\n",
    "    rows.append([paths[i], labels[i], leaf.sum(), affected.sum(), round(percent, 1)])\n",
    "\n",
    "data = pd.DataFrame(rows, columns=[\"photo\", \"label\", \"leaf_pixels\", \"affected_pixels\", \"affected_pct\"])\n",
    "print(\"Average of each kind:\")\n",
    "print(data.groupby(\"label\")[[\"leaf_pixels\", \"affected_pixels\", \"affected_pct\"]].mean().round(1))\n",
    "print()\n",
    "print(\"Healthy leaves  - most affected :\", data[data[\"label\"] == \"healthy\"][\"affected_pct\"].max(), \"%\")\n",
    "print(\"Diseased leaves - least affected:\", data[data[\"label\"] == \"diseased\"][\"affected_pct\"].min(), \"%\")\n",
    "print()\n",
    "\n",
    "# Chart 2: what the program sees on one diseased leaf.\n",
    "example = plt.imread(diseased_paths[0])[:, :, :3]\n",
    "img = example.astype(float) * 255\n",
    "leaf = (img.max(axis=2) - img.min(axis=2)) > 35\n",
    "affected = leaf & (img[:, :, 0] >= 0.75 * img[:, :, 1])\n",
    "marked = example.copy()\n",
    "marked[affected] = [1.0, 0.0, 0.0]\n",
    "plt.figure(figsize=(8, 3))\n",
    "plt.subplot(1, 3, 1)\n",
    "plt.imshow(example)\n",
    "plt.title(\"Photo\")\n",
    "plt.axis(\"off\")\n",
    "plt.subplot(1, 3, 2)\n",
    "plt.imshow(leaf, cmap=\"gray\")\n",
    "plt.title(\"Leaf pixels (white)\")\n",
    "plt.axis(\"off\")\n",
    "plt.subplot(1, 3, 3)\n",
    "plt.imshow(marked)\n",
    "plt.title(\"Affected pixels (red)\")\n",
    "plt.axis(\"off\")\n",
    "plt.tight_layout()\n",
    "plt.savefig(\"chart2_pixel_masks.png\")\n",
    "plt.close()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Stage 4 - Modelling"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(\"--- Stage 4: Modelling ---\")\n",
    "data = data.sample(frac=1, random_state=4)\n",
    "cut = int(len(data) * 0.8)\n",
    "train = data[:cut]\n",
    "test = data[cut:]\n",
    "print(\"Training photos :\", len(train))\n",
    "print(\"Testing photos  :\", len(test))\n",
    "\n",
    "# Learn the cut-off: try every value from 0% to 40% in steps of 0.5, count\n",
    "# the training mistakes of \"diseased if affected_pct is at least this\", and\n",
    "# keep the cut-off with the fewest.\n",
    "truth = train[\"label\"] == \"diseased\"\n",
    "best_cutoff = 0\n",
    "best_mistakes = len(train) + 1\n",
    "cutoffs = np.arange(0, 40.5, 0.5)\n",
    "for c in cutoffs:\n",
    "    mistakes = ((train[\"affected_pct\"] >= c) != truth).sum()\n",
    "    if mistakes < best_mistakes:\n",
    "        best_mistakes = mistakes\n",
    "        best_cutoff = c\n",
    "print(\"Learned cut-off            :\", best_cutoff, \"% of the leaf affected\")\n",
    "print(\"Mistakes on training data  :\", best_mistakes, \"of\", len(train))\n",
    "print()\n",
    "\n",
    "# Chart 3: the feature for both kinds, with the learned cut-off.\n",
    "plt.figure(figsize=(7, 4.3))\n",
    "plt.hist(train[train[\"label\"] == \"healthy\"][\"affected_pct\"], bins=20, alpha=0.65, color=\"#59a14f\", label=\"healthy\")\n",
    "plt.hist(train[train[\"label\"] == \"diseased\"][\"affected_pct\"], bins=20, alpha=0.65, color=\"#b07aa1\", label=\"diseased\")\n",
    "plt.axvline(best_cutoff, color=\"#e15759\", linestyle=\"--\", label=\"learned cut-off: \" + str(best_cutoff) + \"%\")\n",
    "plt.title(\"Share of the leaf affected (training photos)\")\n",
    "plt.xlabel(\"Affected pixels (% of the leaf)\")\n",
    "plt.ylabel(\"Number of leaves\")\n",
    "plt.legend()\n",
    "plt.tight_layout()\n",
    "plt.savefig(\"chart3_affected_share.png\")\n",
    "plt.close()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Stage 5 - Evaluation"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(\"--- Stage 5: Evaluation ---\")\n",
    "tp = fn = fp = tn = 0\n",
    "wrong = []\n",
    "for i in range(len(test)):\n",
    "    row = test.iloc[i]\n",
    "    said_diseased = row[\"affected_pct\"] >= best_cutoff\n",
    "    if row[\"label\"] == \"diseased\" and said_diseased:\n",
    "        tp = tp + 1\n",
    "    elif row[\"label\"] == \"diseased\":\n",
    "        fn = fn + 1\n",
    "        wrong.append(i)\n",
    "    elif said_diseased:\n",
    "        fp = fp + 1\n",
    "        wrong.append(i)\n",
    "    else:\n",
    "        tn = tn + 1\n",
    "accuracy = (tp + tn) / len(test) * 100\n",
    "precision = 0\n",
    "if tp + fp > 0:\n",
    "    precision = tp / (tp + fp) * 100\n",
    "recall = 0\n",
    "if tp + fn > 0:\n",
    "    recall = tp / (tp + fn) * 100\n",
    "f1 = 0\n",
    "if precision + recall > 0:\n",
    "    f1 = 2 * precision * recall / (precision + recall)\n",
    "print(\"TP, FN, FP, TN :\", tp, fn, fp, tn)\n",
    "print(\"Accuracy       :\", round(accuracy, 1), \"%\")\n",
    "print(\"Precision      :\", round(precision, 1), \"%\")\n",
    "print(\"Recall         :\", round(recall, 1), \"%\")\n",
    "print(\"F1 score       :\", round(f1, 1), \"%\")\n",
    "print(\"Photos wrong   :\", len(wrong))\n",
    "for i in wrong:\n",
    "    row = test.iloc[i]\n",
    "    print(\"  \" + os.path.basename(row[\"photo\"]), \"really\", row[\"label\"], \"-\", row[\"affected_pct\"], \"% affected\")\n",
    "print()\n",
    "\n",
    "# Chart 4: confusion matrix.\n",
    "grid = [[tp, fn], [fp, tn]]\n",
    "plt.figure(figsize=(5, 4.3))\n",
    "plt.imshow(grid, cmap=\"Greens\")\n",
    "names = [[\"TP\", \"FN\"], [\"FP\", \"TN\"]]\n",
    "for r in range(2):\n",
    "    for c in range(2):\n",
    "        if grid[r][c] > max(max(grid)) / 2:\n",
    "            ink = \"white\"\n",
    "        else:\n",
    "            ink = \"black\"\n",
    "        plt.text(c, r, names[r][c] + \"\\n\" + str(grid[r][c]), ha=\"center\", va=\"center\", fontsize=14, color=ink)\n",
    "plt.xticks([0, 1], [\"Said diseased\", \"Said healthy\"])\n",
    "plt.yticks([0, 1], [\"Diseased\", \"Healthy\"])\n",
    "plt.xlabel(\"What the model said\")\n",
    "plt.ylabel(\"The truth\")\n",
    "plt.title(\"Confusion matrix (testing photos)\")\n",
    "plt.tight_layout()\n",
    "plt.savefig(\"chart4_matrix.png\")\n",
    "plt.close()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Stage 6 - Deployment"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(\"--- Stage 6: Deployment ---\")\n",
    "new_names = sorted(os.listdir(\"new_photos\"))\n",
    "plt.figure(figsize=(8, 2.8))\n",
    "for i in range(len(new_names)):\n",
    "    photo = plt.imread(os.path.join(\"new_photos\", new_names[i]))[:, :, :3]\n",
    "    img = photo.astype(float) * 255\n",
    "    leaf = (img.max(axis=2) - img.min(axis=2)) > 35\n",
    "    affected = leaf & (img[:, :, 0] >= 0.75 * img[:, :, 1])\n",
    "    percent = affected.sum() / leaf.sum() * 100\n",
    "    if percent < best_cutoff:\n",
    "        verdict = \"healthy\"\n",
    "    elif percent < 15:\n",
    "        verdict = \"mild\"\n",
    "    elif percent < 30:\n",
    "        verdict = \"moderate\"\n",
    "    else:\n",
    "        verdict = \"severe\"\n",
    "    print(new_names[i], \"->\", round(percent, 1), \"% affected ->\", verdict.upper())\n",
    "    plt.subplot(1, len(new_names), i + 1)\n",
    "    plt.imshow(photo)\n",
    "    plt.title(verdict + \" (\" + str(round(percent)) + \"%)\", fontsize=10)\n",
    "    plt.axis(\"off\")\n",
    "plt.tight_layout()\n",
    "plt.savefig(\"chart5_new_leaves.png\")\n",
    "plt.close()\n",
    "print(\"Mild, moderate or severe: remove affected leaves and show the photo to\")\n",
    "print(\"the nearest Krishi Vigyan Kendra before spraying anything.\")"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "name": "python"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
