Download app.py from CEAIA/plagas: direct link, hf CLI and curl.
- Browser
- Download file 12.1 kB
-
https://huggingface.co/spaces/CEAIA/plagas/resolve/main/app.py
- Command line
-
hf download hf://spaces/CEAIA/plagas/app.py
-
curl -L -o app.py https://huggingface.co/spaces/CEAIA/plagas/resolve/main/app.py
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"] | |
| ) |