plagas / app.py
JHPerera's picture
Mejora cuadro imagen
aaa9024
Raw
History Blame Contribute Delete
12.1 kB
import os
import cv2
import numpy as np
import gradio as gr
# =====================================================================
# PARCHE DE SEGURIDAD CRÍTICO PARA EL BUG DE GRADIO 4.X
# =====================================================================
try:
import gradio_client.utils as client_utils
# Neutraliza el bucle recursivo del generador de API que causa el crash
client_utils.json_schema_to_python_type = lambda *args, **kwargs: "str"
print("[SISTEMA] Parche anti-bugs de API aplicado con éxito.")
except Exception as e:
print(f"[SISTEMA] No se pudo aplicar el parche: {e}")
# =====================================================================
# 1. CONFIGURACIÓN BÁSICA Y CARGA LAZY DEL MODELO
# =====================================================================
os.environ["TF_CPP_MIN_LOG_LEVEL"] = "2"
MODEL_PATH = "assets/model"
LABELS_PATH = "assets/model/labels.txt"
NAMES_PATH = "assets/model/names.txt"
IMAGES_DIR = "assets/images"
DATA_DIR = "assets/data"
model = None
labels = []
names = []
def cargar_modelo_aislado():
"""Carga TensorFlow de manera aislada para evitar conflictos con Gradio"""
global model, labels, names
if model is None:
print("Cargando TensorFlow y el modelo de IA de forma aislada...")
import tensorflow as tf
model = tf.keras.models.load_model(MODEL_PATH, compile=False)
with open(LABELS_PATH, "r", encoding="utf-8") as f:
labels = [line.strip() for line in f.readlines()]
with open(NAMES_PATH, "r", encoding="utf-8") as f:
names = [line.strip() for line in f.readlines()]
# =====================================================================
# 2. LÓGICA DE PROCESAMIENTO E INFERENCIA
# =====================================================================
def limpiar_nombre_plaga(nombre_etiqueta):
"""Limpia etiquetas del tipo '0 Mosca Blanca' a 'mosca_blanca'"""
partes = nombre_etiqueta.split(maxsplit=1)
if len(partes) > 1 and partes[0].isdigit():
nombre_real = partes[1]
else:
nombre_real = nombre_etiqueta
return nombre_real.strip().lower().replace(" ", "_")
def get_image_path(nombre_etiqueta):
nombre_limpio = limpiar_nombre_plaga(nombre_etiqueta)
filename = nombre_limpio + ".jpg"
filepath = os.path.join(IMAGES_DIR, filename)
return filepath if os.path.exists(filepath) else None
def get_info_text(nombre_etiqueta):
nombre_limpio = limpiar_nombre_plaga(nombre_etiqueta)
filename = nombre_limpio + ".txt"
filepath = os.path.join(DATA_DIR, filename)
if os.path.exists(filepath):
with open(filepath, "r", encoding="utf-8") as f:
return f.read()
return f"Información técnica no disponible para la plaga: {nombre_etiqueta}"
def predict_plaga(img):
if img is None:
return "<p style='color:red;'>Por favor, capture o suba una imagen.</p>", None, "No se suministró ninguna imagen.", gr.update(visible=True)
try:
cargar_modelo_aislado()
# Preprocesamiento para Teachable Machine
img_prep = cv2.resize(img, (224, 224))
img_prep = img_prep.astype(np.float32) / 255.0
img_prep = np.expand_dims(img_prep, axis=0)
# Predicción
predictions = model.predict(img_prep)[0]
top_indices = np.argsort(predictions)[::-1][:5]
indice_mejor = top_indices[0]
# Formatear la tabla HTML (2 columnas, verdes alternados)
tabla_html = """
<table style='width:100%; border-collapse: collapse; font-family: Arial, sans-serif; background-color: #F1F8E9; color: #000000; border-radius: 8px; overflow: hidden; box-shadow: 0 2px 4px rgba(0,0,0,0.1);'>
<thead>
<tr style='background-color: #2E7D32; color: #FFFFFF; font-weight: bold !important;'>
<th style='padding: 12px 15px; text-align: left; border-bottom: 2px solid #1B5E20; color: #FFFFFF; font-size: 1rem;'>Nombre de la Plaga</th>
<th style='padding: 12px 15px; text-align: right; border-bottom: 2px solid #1B5E20; color: #FFFFFF; font-size: 1rem;'>Probabilidad</th>
</tr>
</thead>
<tbody>
"""
for idx, i in enumerate(top_indices):
porcentaje = predictions[i] * 100
bg_color = "#E8F5E9" if idx % 2 == 0 else "#C8E6C9"
tabla_html += f"""
<tr style='background-color: {bg_color}; border-bottom: 1px solid #A5D6A7;'>
<td style='padding: 10px 15px; text-align: left; font-weight: bold; color: #000000; font-size: 1rem;'>{labels[i]}</td>
<td style='padding: 10px 15px; text-align: right; font-weight: bold; color: #000000; font-size: 1rem;'>{porcentaje:.2f}%</td>
</tr>
"""
tabla_html += "</tbody></table>"
mejor_plaga = labels[indice_mejor]
ruta_imagen_ganadora = get_image_path(mejor_plaga)
texto_informativo = get_info_text(mejor_plaga)
return tabla_html, ruta_imagen_ganadora, texto_informativo, gr.update(visible=True)
except Exception as e:
return f"<p style='color:red;'>Error en el diagnóstico: {str(e)}</p>", None, "Ocurrió un error interno.", gr.update(visible=True)
# =====================================================================
# 3. CONTROLADORES DE ESTADO (UI Dinámica)
# =====================================================================
def controlar_estado(img):
"""Maneja qué sucede cuando se carga o borra una foto"""
if img is None:
return gr.update(interactive=False), gr.update(visible=False)
else:
return gr.update(interactive=True), gr.update(visible=False)
def mostrar_procesando():
"""Cambia el botón a estado de carga"""
return gr.update(value="⏳ PROCESANDO IMAGEN...", interactive=False)
def restaurar_boton(img):
"""Restaura el botón luego de que la IA termina"""
estado_activo = True if img is not None else False
return gr.update(value="IDENTIFICAR PLAGA", interactive=estado_activo)
# =====================================================================
# 4. INTERFAZ GRÁFICA PERSONALIZADA (CSS Gradio 4)
# =====================================================================
theme_css = """
body, .gradio-container { background-color: #1B5E20 !important; color: white !important; font-family: 'Arial', sans-serif; }
h1, h2, h3, p { text-align: center !important; color: white !important; }
/* Botón activo con verde más claro solicitado */
.custom-btn-large {
background-color: #81C784 !important;
color: black !important;
font-weight: bold !important;
font-size: 1.3rem !important;
border-radius: 12px !important;
padding: 15px !important;
margin-top: 15px !important;
width: 100% !important;
box-shadow: 0 4px 6px rgba(0,0,0,0.3) !important;
border: none !important;
cursor: pointer !important;
transition: background-color 0.3s, transform 0.1s;
}
.custom-btn-large:hover:not(:disabled) { background-color: #A5D6A7 !important; }
.custom-btn-large:active:not(:disabled) { transform: scale(0.98); }
/* Botón inhabilitado / procesando */
.custom-btn-large:disabled {
background-color: #3E6B40 !important;
color: #A5D6A7 !important;
cursor: not-allowed !important;
box-shadow: none !important;
opacity: 0.8;
}
.camera-capture-box {
background-color: #2E7D32 !important;
border: 5px #E8F5E9 !important;
border-radius: 16px !important;
min-height: 250px !important;
transition: background-color 0.3s;
}
.camera-capture-box:hover { background-color: #43A047 !important; }
.camera-capture-box .xl { color: white !important; }
.camera-capture-box * { color: white !important; }
/* Texto en negro sobre verde claro */
.info-box {
background-color: #E8F5E9 !important;
color: #000000 !important;
padding: 18px !important;
border-radius: 10px !important;
border: 2px solid #A5D6A7 !important;
box-shadow: 0 2px 4px rgba(0,0,0,0.1) !important;
}
.info-box * {
color: #000000 !important;
text-align: left !important;
}
/* Cuadro de alumnos */
.credits-box {
background-color: #2E7D32 !important;
color: white !important;
padding: 5px !important;
border-radius: 12px !important;
text-align: center !important;
margin-top: 20px !important;
border: 1px solid #A5D6A7 !important;
box-shadow: 0 4px 8px rgba(0,0,0,0.2) !important;
width: 100% !important;
}
.credits-box h3 {
margin-top: 0 !important;
margin-bottom: 10px !important;
color: #A5D6A7 !important;
font-size: 1.0rem !important;
font-weight: bold !important;
}
.credits-box p {
font-size: 0.7rem !important;
margin: 0 !important;
color: #FFFFFF !important;
}
"""
# Interfaz GRADIO
with gr.Blocks(css=theme_css, title="CEA - Identificación de Plagas") as demo:
# Encabezado institucional
gr.HTML("""
<div style="display: flex; align-items: center; justify-content: center; gap: 20px; margin-top: 15px; margin-bottom: 20px; width: 100%;">
<div style="flex-shrink: 0;">
<img src="/file=assets/images/logocea.png" alt="Logo CEA" style="height: 80px; width: auto; object-fit: contain;">
</div>
<div style="text-align: left;">
<h3 style="font-family: 'Stencil', 'Arial Black', 'Arial', sans-serif; font-size: 1.3rem;
letter-spacing: 1px; margin: 0; color: white; line-height: 1.1; text-align: left !important;">
CENTRO DE<br>EDUCACIÓN AGRÍCOLA
</h3>
<p style="font-size: 1.1rem; font-weight: bold; margin-top: 5px; margin-bottom: 0; color: #A5D6A7;
text-align: left !important;">
IDENTIFICACIÓN DE PLAGAS
</p>
</div>
</div>
""")
with gr.Row():
with gr.Column(scale=1):
input_fuente = gr.Image(
show_label=False, #label="TOMAR FOTO DE LA PLAGA",
type="numpy",
sources=["upload"],
elem_classes="camera-capture-box"
)
# Inicia deshabilitado
btn_identificar = gr.Button("IDENTIFICAR PLAGA", elem_classes="custom-btn-large", interactive=False)
# Columna de resultados inicialmente invisible
with gr.Column(scale=1, visible=False) as output_col:
# Título actualizado
# gr.Markdown("### PLAGAS MÁS PROBABLES")
output_tabla = gr.HTML()
output_imagen = gr.Image(show_label=False) # label="COINCIDENCIA VISUAL"
# Título eliminado. Componente Markdown estilizado
output_info = gr.Markdown(elem_classes="info-box")
# Evento: Al cambiar la foto
input_fuente.change(
fn=controlar_estado,
inputs=input_fuente,
outputs=[btn_identificar, output_col]
)
# SECUENCIA DE EVENTOS AL HACER CLIC (Manejo de estados de carga)
btn_identificar.click(
fn=mostrar_procesando, # 1. Cambia el botón a "Procesando..."
outputs=btn_identificar
).then(
fn=predict_plaga, # 2. Ejecuta la IA (bloqueante y pesado)
inputs=input_fuente,
outputs=[output_tabla, output_imagen, output_info, output_col]
).then(
fn=restaurar_boton, # 3. Devuelve el botón a la normalidad
inputs=input_fuente,
outputs=btn_identificar
)
# Cuadro de alumnos desarrolladores
with gr.Row():
gr.HTML("""
<div class="credits-box">
<p>CEA (C) 2026 - Tucumán, Argentina</p>
<p>Figueroa, Julieta; Gutierrez, Brisa; Hardoy, Milagros;</p>
<p>Medina, Molly; Pettorossi, Franco; Rojas, Ayelén; Trejo, Rocío</p>
</div>
""")
if __name__ == "__main__":
demo.launch(
allowed_paths=["assets/images"]
)