| import os |
| import cv2 |
| import numpy as np |
| import gradio as gr |
|
|
| |
| |
| |
| try: |
| import gradio_client.utils as client_utils |
| |
| 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}") |
|
|
| |
| |
| |
| 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()] |
|
|
| |
| |
| |
| 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() |
| |
| |
| 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) |
| |
| |
| predictions = model.predict(img_prep)[0] |
| top_indices = np.argsort(predictions)[::-1][:5] |
| indice_mejor = top_indices[0] |
| |
| |
| 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) |
|
|
| |
| |
| |
| 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) |
|
|
| |
| |
| |
| 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; |
| } |
| """ |
|
|
| |
| with gr.Blocks(css=theme_css, title="CEA - Identificación de Plagas") as demo: |
| |
| |
| 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, |
| type="numpy", |
| sources=["upload"], |
| elem_classes="camera-capture-box" |
| ) |
| |
| btn_identificar = gr.Button("IDENTIFICAR PLAGA", elem_classes="custom-btn-large", interactive=False) |
|
|
| |
| with gr.Column(scale=1, visible=False) as output_col: |
| |
| |
| output_tabla = gr.HTML() |
| |
| output_imagen = gr.Image(show_label=False) |
| |
| |
| output_info = gr.Markdown(elem_classes="info-box") |
|
|
| |
| input_fuente.change( |
| fn=controlar_estado, |
| inputs=input_fuente, |
| outputs=[btn_identificar, output_col] |
| ) |
|
|
| |
| btn_identificar.click( |
| fn=mostrar_procesando, |
| outputs=btn_identificar |
| ).then( |
| fn=predict_plaga, |
| inputs=input_fuente, |
| outputs=[output_tabla, output_imagen, output_info, output_col] |
| ).then( |
| fn=restaurar_boton, |
| inputs=input_fuente, |
| outputs=btn_identificar |
| ) |
|
|
| |
| 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"] |
| ) |