Download app.py from LabEndo/RxValidas: direct link, hf CLI and curl.
- Browser
- Download file 9.21 kB
-
https://huggingface.co/spaces/LabEndo/RxValidas/resolve/main/app.py
- Command line
-
hf download hf://spaces/LabEndo/RxValidas/app.py
-
curl -L -o app.py https://huggingface.co/spaces/LabEndo/RxValidas/resolve/main/app.py
9.21 kB
| import gradio as gr | |
| import tensorflow as tf | |
| import numpy as np | |
| import time # Para simular un retraso en la carga del modelo | |
| # --- Configuración --- | |
| IMG_SIZE = (224, 224) | |
| MODEL_PATH = "dental_classifier_model.keras" # Asegúrate de que esta ruta sea correcta | |
| CLASS_NAMES = ['no_valido', 'valido'] | |
| # --- Cargar Modelo con mensaje de carga --- | |
| model = None | |
| model_load_message = "Cargando modelo... por favor espera." | |
| try: | |
| # Simular una carga lenta para ver el mensaje (puedes quitar esto en producción) | |
| time.sleep(2) | |
| model = tf.keras.models.load_model(MODEL_PATH) | |
| model_load_message = "Modelo cargado exitosamente." | |
| print("Modelo cargado exitosamente.") | |
| except Exception as e: | |
| model_load_message = f"Error cargando el modelo: {e}. Asegúrate que 'dental_classifier_model.keras' existe." | |
| print(model_load_message) | |
| # --- Funciones de Procesamiento --- | |
| def preprocess_image(img): | |
| """Preprocesa la imagen de entrada al formato que espera el modelo.""" | |
| if img is None: | |
| return None | |
| # Asegurarse de que la imagen tiene 3 canales si es en escala de grises | |
| if len(img.shape) == 2: | |
| img = np.stack((img,)*3, axis=-1) | |
| elif img.shape[2] == 4: #RGBA a RGB | |
| img = img[:, :, :3] | |
| img = tf.image.resize(img, IMG_SIZE) | |
| img_array = tf.expand_dims(img, 0) | |
| img_array = img_array / 255.0 # Normalizar | |
| return img_array | |
| def predecir(rx_image): | |
| """Realiza la predicción y formatea la salida HTML.""" | |
| if model is None: | |
| return "<div class='result-box' style='color:#FF6B6B; font-weight: bold;'>Error: El modelo no se ha cargado.</div>" | |
| if rx_image is None: | |
| return "<div class='result-box' style='color:#FF6B6B; font-weight: bold;'>Por favor, sube una imagen RX para analizar.</div>" | |
| img_array = preprocess_image(rx_image) | |
| if img_array is None: # Si preprocess_image devuelve None por algún motivo | |
| return "<div class='result-box' style='color:#FF6B6B; font-weight: bold;'>Error: No se pudo procesar la imagen.</div>" | |
| try: | |
| preds = model.predict(img_array) | |
| except Exception as e: | |
| print(f"Error durante la predicción: {e}") | |
| return f"<div class='result-box' style='color:#FF6B6B; font-weight: bold;'>Error al realizar la predicción: {e}</div>" | |
| score = tf.nn.softmax(preds[0]) | |
| predicted_index = np.argmax(score) | |
| confidence = np.max(score) * 100 | |
| predicted_class = CLASS_NAMES[predicted_index] | |
| other_index = 1 - predicted_index | |
| other_class = CLASS_NAMES[other_index] | |
| other_confidence = score[other_index] * 100 | |
| # Colores para el borde. El color del texto se heredará del CSS. | |
| color_borde = "#4CAF50" if predicted_class == "valido" else "#FF6B6B" # Verde para válido, Rojo para no válido | |
| # HTML para el resultado con estilos mejorados | |
| # Eliminamos los 'style' de color de texto para que se hereden de la clase 'result-box' | |
| resultado_texto = f""" | |
| <div class='result-box' style=' | |
| border: 3px solid {color_borde}; | |
| box-shadow: 0 6px 20px rgba(0,0,0,0.15); /* Sombra más pronunciada */ | |
| '> | |
| <div style='font-size:42px; font-weight:bold;'> | |
| Resultado: <span style='color: {color_borde};'>{predicted_class.upper()}</span> | |
| </div> | |
| <div style='font-size:30px; margin-top:15px;'> | |
| Confianza: <span style='font-weight:bold;'>{confidence:.2f}%</span> | |
| </div> | |
| <div style='font-size:24px; margin-top:10px;'> | |
| (Probabilidad {other_class}: <span style='font-weight:bold;'>{other_confidence:.2f}%</span>) | |
| </div> | |
| </div> | |
| """ | |
| return resultado_texto | |
| # --- Interfaz de Gradio --- | |
| with gr.Blocks(theme=gr.themes.Soft(primary_hue=gr.themes.colors.emerald, secondary_hue=gr.themes.colors.slate)) as demo: | |
| # --- Estilos CSS personalizados (Adaptados a Dark Mode) --- | |
| gr.HTML(f""" | |
| <style> | |
| body {{ | |
| /* Se elimina el background-color y color fijos */ | |
| font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; | |
| }} | |
| .gradio-container {{ | |
| max-width: 1200px; | |
| margin: auto; | |
| padding: 20px; | |
| background-color: var(--background-fill-primary); /* Variable de Gradio para fondo */ | |
| border-radius: 15px; | |
| box-shadow: 0 10px 30px rgba(0,0,0,0.1); | |
| }} | |
| h2 {{ | |
| color: var(--text-color-primary); /* Variable de Gradio para texto */ | |
| font-size: 2.8em; | |
| font-weight: 700; | |
| text-align: center; | |
| margin-bottom: 30px; | |
| padding-bottom: 15px; | |
| border-bottom: 2px solid var(--border-color-accent); /* Variable de Gradio para bordes */ | |
| }} | |
| /* Estilos de botones (se mantienen, ya que definen su propio color y fondo) */ | |
| .gr-button.primary {{ | |
| background-color: #28a745 !important; | |
| border-color: #28a745 !important; | |
| color: white !important; | |
| font-weight: bold; | |
| padding: 12px 25px; | |
| font-size: 1.1em; | |
| border-radius: 8px; | |
| transition: all 0.3s ease; | |
| }} | |
| .gr-button.primary:hover {{ | |
| background-color: #218838 !important; | |
| border-color: #1e7e34 !important; | |
| transform: translateY(-2px); | |
| box-shadow: 0 4px 10px rgba(0,0,0,0.2); | |
| }} | |
| .gr-button.secondary {{ | |
| background-color: #6c757d !important; | |
| border-color: #6c757d !important; | |
| color: white !important; | |
| font-weight: bold; | |
| padding: 12px 25px; | |
| font-size: 1.1em; | |
| border-radius: 8px; | |
| transition: all 0.3s ease; | |
| }} | |
| .gr-button.secondary:hover {{ | |
| background-color: #5a6268 !important; | |
| border-color: #545b62 !important; | |
| transform: translateY(-2px); | |
| box-shadow: 0 4px 10px rgba(0,0,0,0.2); | |
| }} | |
| .result-box {{ | |
| font-size: 28px; | |
| text-align: center; | |
| padding: 50px; | |
| min-height: 380px; | |
| border-radius: 20px; | |
| background-color: var(--background-fill-secondary); /* Variable de Gradio */ | |
| display: flex; | |
| flex-direction: column; | |
| justify-content: center; | |
| color: var(--text-color-primary); /* Variable de Gradio */ | |
| transition: all 0.3s ease; | |
| }} | |
| .result-box:hover {{ | |
| transform: translateY(-5px); | |
| box-shadow: 0 8px 25px rgba(0,0,0,0.2); | |
| }} | |
| .gr-image {{ | |
| border-radius: 12px; | |
| border: 1px solid var(--border-color-primary); /* Variable de Gradio */ | |
| box-shadow: 0 2px 10px rgba(0,0,0,0.08); | |
| }} | |
| .gr-label {{ | |
| font-weight: 600; | |
| color: var(--text-color-secondary); /* Variable de Gradio */ | |
| font-size: 1.2em; | |
| margin-bottom: 8px; | |
| }} | |
| /* Clase para el subtítulo */ | |
| .subtitle {{ | |
| text-align:center; | |
| font-size:1.1em; | |
| color: var(--text-color-secondary); /* Variable de Gradio */ | |
| }} | |
| </style> | |
| """) | |
| gr.Markdown("## Clasificador RX LAB 🦷 V1(529NV-348V) TFG Marta B.") | |
| gr.Markdown("<p class='subtitle'>Sube una imagen de una radiografía dental para clasificarla como válida o no válida.</p>") | |
| # Mensaje de carga del modelo | |
| gr.Textbox(value=model_load_message, interactive=False, container=False, | |
| show_label=False, elem_id="model_status_message", | |
| label="Estado del Modelo", | |
| render=True, # Asegura que se renderiza inicialmente | |
| info="El modelo está cargando..." if "Cargando" in model_load_message else None, | |
| visible=True if "Cargando" in model_load_message or "Error" in model_load_message else False, | |
| ) | |
| # Texto de estado inicial para la caja de resultados | |
| initial_result_html = f"<div class='result-box'><div style='font-size:24px; color: var(--text-color-secondary);'>Esperando imagen...</div></div>" | |
| with gr.Row(variant="panel", scale=1): | |
| with gr.Column(scale=1, min_width=400): | |
| gr.Markdown("### Sube tu Radiografía") | |
| rx_input = gr.Image(type="numpy", label="Imagen de Radiografía Dental", show_label=True, height=450) | |
| with gr.Row(): | |
| boton_limpiar = gr.Button("Limpiar", variant="secondary", size="lg", ) | |
| boton_analizar = gr.Button("Analizar RX", variant="primary", size="lg",) | |
| with gr.Column(scale=1, min_width=400): | |
| gr.Markdown("### Resultado del Análisis") | |
| resultado = gr.HTML(label="Análisis de Radiografía", show_label=True, value=initial_result_html) | |
| # --- Conexiones de Eventos --- | |
| boton_analizar.click(fn=predecir, inputs=rx_input, outputs=resultado) | |
| boton_limpiar.click(lambda: (None, initial_result_html), inputs=[], outputs=[rx_input, resultado]) | |
| # --- Lanzar la App --- | |
| if __name__ == "__main__": | |
| demo.launch(share=False) | |