| import gradio as gr |
| import tensorflow as tf |
| import numpy as np |
| import time |
|
|
| |
| IMG_SIZE = (224, 224) |
| MODEL_PATH = "dental_classifier_model.keras" |
| CLASS_NAMES = ['no_valido', 'valido'] |
|
|
| |
| model = None |
| model_load_message = "Cargando modelo... por favor espera." |
|
|
| try: |
| |
| 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) |
|
|
| |
| def preprocess_image(img): |
| """Preprocesa la imagen de entrada al formato que espera el modelo.""" |
| if img is None: |
| return None |
| |
| if len(img.shape) == 2: |
| img = np.stack((img,)*3, axis=-1) |
| elif img.shape[2] == 4: |
| img = img[:, :, :3] |
|
|
| img = tf.image.resize(img, IMG_SIZE) |
| img_array = tf.expand_dims(img, 0) |
| img_array = img_array / 255.0 |
| 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: |
| 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 |
| |
| |
| color_borde = "#4CAF50" if predicted_class == "valido" else "#FF6B6B" |
| |
| |
| |
| 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 |
|
|
| |
| with gr.Blocks(theme=gr.themes.Soft(primary_hue=gr.themes.colors.emerald, secondary_hue=gr.themes.colors.slate)) as demo: |
| |
| 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>") |
|
|
| |
| gr.Textbox(value=model_load_message, interactive=False, container=False, |
| show_label=False, elem_id="model_status_message", |
| label="Estado del Modelo", |
| render=True, |
| 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, |
| ) |
| |
| |
| 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) |
|
|
| |
| boton_analizar.click(fn=predecir, inputs=rx_input, outputs=resultado) |
| boton_limpiar.click(lambda: (None, initial_result_html), inputs=[], outputs=[rx_input, resultado]) |
|
|
| |
| if __name__ == "__main__": |
| demo.launch(share=False) |
|
|
|
|