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 "
Por favor, capture o suba una imagen.
", 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 = """ " 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"Error en el diagnóstico: {str(e)}
", 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("""
IDENTIFICACIÓN DE PLAGAS
CEA (C) 2026 - Tucumán, Argentina
Figueroa, Julieta; Gutierrez, Brisa; Hardoy, Milagros;
Medina, Molly; Pettorossi, Franco; Rojas, Ayelén; Trejo, Rocío