File size: 6,864 Bytes
678e8a8 246be56 266d613 cb122bc b0fb27c 04440d5 130cda8 6190974 130cda8 db2a085 266d613 04440d5 b0fb27c 04440d5 b0fb27c 04440d5 246be56 ed40b20 db2a085 276211d ed40b20 db2a085 ed40b20 9ce6c4e 04440d5 9ce6c4e 04440d5 6267098 cb122bc 25c4e8b cb122bc 04440d5 266d613 ddc8207 266d613 04440d5 6267098 04440d5 6267098 04440d5 db2a085 1c42818 670e5e9 06be701 7a93b87 670e5e9 b0fb27c 276211d 670e5e9 04440d5 b0fb27c e35f986 db2a085 109ba73 04440d5 b0fb27c 3fbfc98 04440d5 e642790 266d613 b0fb27c ddc8207 04440d5 3fbfc98 04440d5 3fbfc98 04440d5 109ba73 3fbfc98 04440d5 cb122bc 04440d5 cb122bc 109ba73 04440d5 b0fb27c cb122bc ddc8207 04440d5 266d613 04440d5 8d4283d 9ce6c4e b0fb27c 9ce6c4e 04440d5 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 | import streamlit as st
import numpy as np
from PIL import Image
import json
import tensorflow as tf
import keras
import cv2
import os
from transformers import AutoModelForCausalLM
from peft import PeftModel
# ======================
# CONFIG
# ======================
st.set_page_config(page_title="Tomato AI 🍅", layout="wide")
st.markdown("""
<style>
.big-title { font-size:42px; font-weight:bold; color:#2E8B57; }
.card { padding:20px; border-radius:15px; background-color:#f5f5f5; margin-bottom:15px; }
.result { font-size:26px; font-weight:bold; color:#ff4b4b; }
.sub { font-size:18px; color:#555; }
</style>
""", unsafe_allow_html=True)
st.markdown('<div class="big-title">🍅 AI Tomato Disease Detection System</div>', unsafe_allow_html=True)
# ======================
# PATHS
# ======================
GEMMA_PATH = "/app/gemma-tomato-lora"
MODEL_PATH = "/app/src/best_model.h5"
JSON_PATH = "/app/src/class_indices.json"
# ======================
# TOKEN — BİR DƏFƏ OXUNUR, SİLİNMİR
# ======================
hf_token = os.environ.get("HF_TOKEN")
if not hf_token:
st.error("HF_TOKEN tapılmadı!")
st.stop()
# ======================
# LOAD CNN
# ======================
@st.cache_resource
def load_cnn():
model = keras.models.load_model(MODEL_PATH, compile=False)
return model
cnn_model = load_cnn()
# ======================
# LOAD CLASSES
# ======================
with open(JSON_PATH) as f:
class_indices = json.load(f)
class_names = [None] * len(class_indices)
for k, v in class_indices.items():
class_names[v] = k
# ======================
# PREPROCESS
# ======================
IMG_SIZE = 224
def preprocess(img):
img = img.convert("RGB")
img = img.resize((IMG_SIZE, IMG_SIZE))
arr = np.array(img, dtype=np.float32)
return np.expand_dims(arr, axis=0)
# ======================
# GEMMA EXPLANATION
# ======================
@st.cache_resource
def load_llm(_token):
from transformers import GemmaTokenizer
tok = GemmaTokenizer(
vocab_file=f"{GEMMA_PATH}/tokenizer.model",
add_bos_token=True,
add_eos_token=False,
)
model = AutoModelForCausalLM.from_pretrained(
"google/gemma-2b-it",
device_map="auto",
torch_dtype="auto",
token=_token
)
model = PeftModel.from_pretrained(model, GEMMA_PATH)
return tok, model
tokenizer, gemma = load_llm(hf_token)
def explain(label):
prompt = f"""You are a plant disease expert. Explain the following tomato disease clearly and concisely in English.
Disease: {label}
Provide:
1. Cause
2. Symptoms
3. Prevention
4. Treatment advice for farmers
Answer:"""
inputs = tokenizer(prompt, return_tensors="pt").to(gemma.device)
out = gemma.generate(
**inputs,
max_new_tokens=200,
do_sample=True,
temperature=0.7,
repetition_penalty=1.3
)
full = tokenizer.decode(out[0], skip_special_tokens=True)
return full[len(prompt):]
# ======================
# GRADCAM
# ======================
def make_gradcam(img_array, model):
last_conv_name = None
search_model = model
for layer in model.layers:
if isinstance(layer, keras.Model):
search_model = layer
for inner_layer in layer.layers:
if isinstance(inner_layer, keras.Model):
search_model = inner_layer
break
break
for layer in reversed(search_model.layers):
layer_type = type(layer).__name__
if "Conv2D" in layer_type or "conv" in layer.name.lower():
last_conv_name = layer.name
break
if last_conv_name is None:
layer_names = [f"{l.name} ({type(l).__name__})" for l in search_model.layers[-20:]]
raise ValueError("Conv2D tapılmadı:\n" + "\n".join(layer_names))
try:
grad_model = keras.models.Model(
inputs=search_model.inputs,
outputs=[search_model.get_layer(last_conv_name).output, search_model.output]
)
except Exception:
grad_model = keras.models.Model(
inputs=model.inputs,
outputs=[search_model.get_layer(last_conv_name).output, model.output]
)
_ = grad_model(img_array)
with tf.GradientTape() as tape:
conv_outputs, preds = grad_model(img_array)
loss = tf.reduce_max(preds)
grads = tape.gradient(loss, conv_outputs)
pooled = tf.reduce_mean(grads, axis=(0, 1, 2))
heatmap = conv_outputs[0] @ pooled[..., tf.newaxis]
heatmap = tf.squeeze(heatmap)
heatmap = np.maximum(heatmap.numpy(), 0)
max_val = np.max(heatmap)
if max_val > 0:
heatmap /= max_val
return heatmap, last_conv_name
# ======================
# UI
# ======================
uploaded_file = st.file_uploader("Upload tomato leaf image 🍅", type=["jpg", "jpeg", "png"])
if uploaded_file:
image = Image.open(uploaded_file)
col1, col2 = st.columns(2)
with col1:
st.image(image, caption="Input Image", use_column_width=True)
img = preprocess(image)
preds = cnn_model.predict(img, verbose=0)
idx = np.argmax(preds[0])
label = class_names[idx]
conf = float(preds[0][idx])
with col2:
st.markdown('<div class="card">', unsafe_allow_html=True)
st.markdown(f"<div class='result'>🔍 Prediction: {label}</div>", unsafe_allow_html=True)
st.markdown(f"<div class='sub'>Confidence: {conf:.2%}</div>", unsafe_allow_html=True)
st.progress(conf)
st.markdown("#### Top 3 Predictions")
top3_idx = np.argsort(preds[0])[::-1][:3]
for i in top3_idx:
st.markdown(f"- **{class_names[i]}**: {preds[0][i]:.2%}")
st.markdown('</div>', unsafe_allow_html=True)
st.markdown("### 🧠 AI Explanation (Gemma)")
with st.spinner("Generating explanation..."):
try:
explanation = explain(label)
st.info(explanation)
except Exception as e:
st.warning(f"Gemma explanation unavailable: {e}")
st.markdown("### 🔥 Model Attention (GradCAM)")
with st.spinner("Generating GradCAM..."):
try:
heatmap, conv_name = make_gradcam(img, cnn_model)
st.caption(f"Layer: {conv_name}")
heatmap_resized = cv2.resize(heatmap, (IMG_SIZE, IMG_SIZE))
heatmap_uint8 = np.uint8(255 * heatmap_resized)
heatmap_colored = cv2.applyColorMap(heatmap_uint8, cv2.COLORMAP_JET)
img_np = np.array(image.resize((IMG_SIZE, IMG_SIZE)))
superimposed = cv2.addWeighted(img_np, 0.6, heatmap_colored, 0.4, 0)
st.image(superimposed, caption=f"GradCAM ({conv_name})", use_column_width=True)
except Exception as e:
st.warning(f"GradCAM unavailable: {e}")
else:
st.info("📤 Upload a tomato leaf image to start prediction") |