Download app.py from Martinagg/DermaScan: direct link, hf CLI and curl.
- Browser
- Download file 13.6 kB
-
https://huggingface.co/spaces/Martinagg/DermaScan/resolve/main/app.py
- Command line
-
hf download hf://spaces/Martinagg/DermaScan/app.py
-
curl -L -o app.py https://huggingface.co/spaces/Martinagg/DermaScan/resolve/main/app.py
13.6 kB
| import gradio as gr | |
| import tensorflow as tf | |
| import numpy as np | |
| import cv2 | |
| from huggingface_hub import hf_hub_download | |
| from tensorflow.keras.preprocessing import image | |
| # === Importar funciones del pipeline === | |
| from preprocessing.zoom import apply_zoom | |
| from preprocessing.hair_removal import quitar_pelos | |
| from preprocessing.segmentation import segmentar_lesion | |
| from preprocessing.metrics import ( | |
| calcular_area, | |
| calcular_perimetro, | |
| calcular_circularidad, | |
| calcular_simetria | |
| ) | |
| # Tama帽o de entrada del modelo | |
| ROWS, COLS = 224, 224 | |
| # === Cargar modelo desde Hugging Face Hub === | |
| model_path = hf_hub_download(repo_id="Martinagg/simpleNet", filename="simpleNet.h5") | |
| model = tf.keras.models.load_model(model_path) | |
| # === Cargar modelo ZoomNet para Grad-CAM === | |
| zoom_path = hf_hub_download(repo_id="Martinagg/ZoomNet", filename="ZoomNet.keras") | |
| model_zoom = tf.keras.models.load_model(zoom_path) | |
| # === Funci贸n Grad-CAM === | |
| def make_gradcam_heatmap(img_array, model, last_conv_layer_name="conv4", pred_index=None): | |
| grad_model = tf.keras.models.Model( | |
| [model.inputs], | |
| [model.get_layer(last_conv_layer_name).output, model.output] | |
| ) | |
| with tf.GradientTape() as tape: | |
| conv_outputs, predictions = grad_model(img_array) | |
| if isinstance(predictions, list): | |
| predictions = predictions[0] | |
| if pred_index is None: | |
| pred_index = tf.argmax(predictions[0]) | |
| class_channel = predictions[:, pred_index] | |
| grads = tape.gradient(class_channel, conv_outputs) | |
| pooled_grads = tf.reduce_mean(grads, axis=(0, 1, 2)) | |
| conv_outputs = conv_outputs[0] | |
| heatmap = conv_outputs @ pooled_grads[..., tf.newaxis] | |
| heatmap = tf.squeeze(heatmap) | |
| heatmap = tf.maximum(heatmap, 0) / tf.math.reduce_max(heatmap) | |
| return heatmap.numpy() | |
| # === Funci贸n principal === | |
| def preprocess_and_predict(img_input): | |
| img = np.array(img_input)[:, :, ::-1] | |
| zoomed = apply_zoom(img, zoom_factor=0.9) | |
| rgb_clean = cv2.cvtColor(zoomed, cv2.COLOR_BGR2RGB) | |
| clean = quitar_pelos(rgb_clean) | |
| mask, lesion_rgb = segmentar_lesion(clean, size=(ROWS, COLS)) | |
| lesion_resized = cv2.resize(lesion_rgb, (ROWS, COLS)) | |
| img_array = image.img_to_array(lesion_resized) / 255.0 | |
| img_array = np.expand_dims(img_array, axis=0) | |
| probs = model.predict(img_array)[0] | |
| classes = ["Benign", "Malignant"] | |
| pred_idx = np.argmax(probs) | |
| pred_label = classes[pred_idx] | |
| prob_percent = int(probs[pred_idx] * 100) | |
| # Color din谩mico para la barra | |
| color = "green" if pred_label == "Benign" else "red" | |
| result_text_html = f"<h3 style='color:{color}; font-weight:bold;'>Predicci贸n: {pred_label}</h3>" | |
| result_bar_html = f""" | |
| <div style='width:100%; background:#eee; border-radius:8px;'> | |
| <div style='width:{prob_percent}%; background:{color}; padding:6px; | |
| border-radius:8px; color:white; text-align:center; font-weight:bold;'> | |
| {prob_percent}% | |
| </div> | |
| </div> | |
| """ | |
| # === M茅tricas geom茅tricas === | |
| area = calcular_area(mask) | |
| perim = calcular_perimetro(mask) | |
| circ = calcular_circularidad(mask) | |
| sim_v, sim_h = calcular_simetria(mask) | |
| metrics_data = [ | |
| ["脕rea (px虏)", area], | |
| ["Per铆metro (px)", perim], | |
| ["Circularidad", round(circ, 3)], | |
| ["Simetr铆a Vertical", round(sim_v, 3)], | |
| ["Simetr铆a Horizontal", round(sim_h, 3)] | |
| ] | |
| # === Grad-CAM === | |
| raw_resized = cv2.resize(np.array(img_input), (ROWS, COLS)) | |
| raw_array = image.img_to_array(raw_resized) / 255.0 | |
| raw_array = np.expand_dims(raw_array, axis=0) | |
| heatmap = make_gradcam_heatmap(raw_array, model_zoom, last_conv_layer_name="conv4") | |
| heatmap = cv2.resize(heatmap, (ROWS, COLS)) | |
| heatmap = np.uint8(255 * heatmap) | |
| heatmap_color = cv2.applyColorMap(heatmap, cv2.COLORMAP_JET) | |
| overlay = cv2.addWeighted(raw_resized.astype("uint8"), 0.6, heatmap_color, 0.4, 0) | |
| return mask, lesion_rgb, result_text_html, result_bar_html, metrics_data, overlay | |
| # === Interfaz con estilo === | |
| with gr.Blocks(css=""" | |
| body, .gradio-container { | |
| font-family: 'Inter', sans-serif; | |
| background: #ffffff !important; | |
| font-weight: bold !important; | |
| } | |
| h1, h2 { font-weight: 600; color: #111827; margin-bottom: 0.5rem; } | |
| .section { | |
| background: #f9fafb; /* gris muy claro */ | |
| border-radius: 0.75rem; | |
| padding: 1.5rem; | |
| margin: 1.5rem auto; | |
| box-shadow: 0 1px 3px rgba(0,0,0,0.08); | |
| max-width: 900px; | |
| } | |
| .gradio-container { max-width: 900px; margin: auto; } | |
| img { border-radius: 0.5rem; } | |
| /* === Bot贸n Analizar personalizado === */ | |
| #analyze-btn { | |
| display: block; | |
| margin: 1.5rem auto; | |
| width: 90%; | |
| padding: 0.75rem 1rem; | |
| background-color: #f97316; | |
| color: #7c2d12; | |
| border: 2px solid #f97316; | |
| border-radius: 0.75rem; | |
| font-weight: bold; | |
| font-size: 1rem; | |
| cursor: pointer; | |
| transition: background 0.3s ease, color 0.3s ease; | |
| } | |
| #analyze-btn:hover { | |
| background-color: #ea580c; | |
| color: white; | |
| } | |
| """) as demo: | |
| # === T铆tulo e introducci贸n === | |
| gr.HTML(""" | |
| <section style="text-align:center; padding: 2rem;"> | |
| <h1>DermaScan - Clasificaci贸n de Melanomas</h1> | |
| <!-- Caption breve --> | |
| <p style="color:#6b7280; font-size:1rem; font-style:italic; margin-top:0.5rem;"> | |
| Una herramienta de apoyo basada en IA para la detecci贸n temprana de c谩ncer de piel. | |
| </p> | |
| </section> | |
| <!-- Texto introductorio alineado a la izquierda --> | |
| <section style="text-align:justify; padding: 1.5rem; max-width: 800px; margin:auto; line-height:1.6;"> | |
| <p style="color:#111827; font-size:1.05rem;"> | |
| <span style='font-weight:bold; color:#000000;'>El melanoma es un c谩ncer de piel agresivo</span> que se origina en los melanocitos. | |
| Aunque poco frecuente, es el m谩s peligroso por su capacidad de generar met谩stasis si no se detecta a tiempo. | |
| </p> | |
| <p style="color:#111827; font-size:1.05rem; margin-top:1rem;"> | |
| En esta aplicaci贸n hemos implementado una <span style='font-weight:bold; color:#000000;'>red neuronal convolucional (CNN)</span> entrenada con im谩genes dermatosc贸picas | |
| para estimar la probabilidad de que una lesi贸n sea benigna o maligna. | |
| </p> | |
| <p style="color:#111827; font-size:1.05rem; margin-top:1rem;"> | |
| Adem谩s, incorporamos t茅cnicas de interpretabilidad como <span style='font-weight:bold; color:#000000;'>Grad-CAM </span> y <span style='font-weight:bold; color:#000000;'>m茅tricas geom茅tricas </span> basadas en el criterio cl铆nico ABCDE, | |
| que sirven como apoyo en la exploraci贸n m茅dica. | |
| </p> | |
| </section> | |
| """) | |
| # === Subir imagen === | |
| with gr.Column(elem_classes="section"): | |
| gr.HTML("<h2>Subir imagen</h2>") | |
| gr.HTML("<p style='color:#111827;'>Sube una imagen clara de tu lunar Aseg煤rate de que est茅 bien iluminada y centrada.</p>") | |
| img_input = gr.Image(type="pil", label="Imagen de la lesi贸n", elem_id="upload-img") | |
| run_btn = gr.Button("Analizar", elem_id="analyze-btn", scale=0) | |
| # === Segmentaci贸n === | |
| with gr.Column(elem_classes="section"): | |
| gr.HTML("<h2>Preprocesamiento y Segmentaci贸n</h2>") | |
| gr.HTML(""" | |
| <p style='color:#111827;'>En este apartado ver谩s c贸mo preparamos tu imagen para que la red neuronal se concentre en la lesi贸n:</p> | |
| <ul style='color:#111827;'> | |
| <li style="color:#111827; font-weight:bold;">Conversi贸n de canales de color a HSV.</li> | |
| <li style="color:#111827; font-weight:bold;">Eliminaci贸n de pelos y bordes para reducir ruido.</li> | |
| <li style="color:#111827; font-weight:bold;">Segmentaci贸n del lunar.</li> | |
| </ul> | |
| """) | |
| img_mask = gr.Image(type="numpy", label="M谩scara Binaria", elem_id="mask-img") | |
| img_segmented = gr.Image(type="numpy", label="Lesi贸n Segmentada", elem_id="seg-img") | |
| # === Grad-CAM === | |
| with gr.Column(elem_classes="section"): | |
| gr.HTML("<h2>Grad-CAM</h2>") | |
| gr.HTML(""" | |
| <p style='color:#111827;'> | |
| El Grad-CAM resalta las zonas en las que la red neuronal ha puesto mayor atenci贸n para clasificar la lesi贸n. | |
| Los colores c谩lidos indican mayor relevancia: rojo y amarillo muestran las 谩reas m谩s importantes. | |
| </p> | |
| """) | |
| gradcam_img = gr.Image(type="numpy", label="Mapa de activaci贸n", elem_id="gradcam-img") | |
| gr.HTML(""" | |
| <div style="display:flex; justify-content:center; margin-top:10px; gap:2rem;"> | |
| <div style="display:flex; align-items:center; gap:0.5rem;"> | |
| <div style="width:16px; height:16px; border-radius:50%; background:blue;"></div> | |
| <span style="color:#111827;">Baja relevancia</span> | |
| </div> | |
| <div style="display:flex; align-items:center; gap:0.5rem;"> | |
| <div style="width:16px; height:16px; border-radius:50%; background:gold;"></div> | |
| <span style="color:#111827;">Media relevancia</span> | |
| </div> | |
| <div style="display:flex; align-items:center; gap:0.5rem;"> | |
| <div style="width:16px; height:16px; border-radius:50%; background:red;"></div> | |
| <span style="color:#111827;">Alta relevancia</span> | |
| </div> | |
| </div> | |
| """) | |
| # === Resultados === | |
| with gr.Column(elem_classes="section"): | |
| gr.HTML("<h2>Resultados del modelo</h2>") | |
| result_text = gr.HTML() | |
| result_bar = gr.HTML() | |
| # === Cuadro amarillo estilo ABCDE === | |
| gr.HTML(""" | |
| <div style="background-color:#FFFBEB; border-radius:10px; padding:1.5rem; margin:1rem 0; | |
| border:1px solid #FDE68A;"> | |
| <h3 style="color:#111827; font-weight:600; margin-bottom:1rem;">Criterio ABCDE del melanoma</h3> | |
| <div style="display:flex; justify-content:space-around; text-align:center; margin-bottom:1rem;"> | |
| <div> | |
| <div style="background:#FDE68A; border-radius:50%; width:36px; height:36px; | |
| display:flex; align-items:center; justify-content:center; font-weight:bold; color:#92400E; margin:auto;"> | |
| A | |
| </div> | |
| <p style="margin-top:0.5rem; font-size:0.9rem; color:#111827;">Asimetr铆a</p> | |
| </div> | |
| <div> | |
| <div style="background:#FDE68A; border-radius:50%; width:36px; height:36px; | |
| display:flex; align-items:center; justify-content:center; font-weight:bold; color:#92400E; margin:auto;"> | |
| B | |
| </div> | |
| <p style="margin-top:0.5rem; font-size:0.9rem; color:#111827;">Bordes irregulares</p> | |
| </div> | |
| <div> | |
| <div style="background:#FDE68A; border-radius:50%; width:36px; height:36px; | |
| display:flex; align-items:center; justify-content:center; font-weight:bold; color:#92400E; margin:auto;"> | |
| C | |
| </div> | |
| <p style="margin-top:0.5rem; font-size:0.9rem; color:#111827;">Color variado</p> | |
| </div> | |
| <div> | |
| <div style="background:#FDE68A; border-radius:50%; width:36px; height:36px; | |
| display:flex; align-items:center; justify-content:center; font-weight:bold; color:#92400E; margin:auto;"> | |
| D | |
| </div> | |
| <p style="margin-top:0.5rem; font-size:0.9rem; color:#111827;">Di谩metro > 6mm</p> | |
| </div> | |
| <div> | |
| <div style="background:#FDE68A; border-radius:50%; width:36px; height:36px; | |
| display:flex; align-items:center; justify-content:center; font-weight:bold; color:#92400E; margin:auto;"> | |
| E | |
| </div> | |
| <p style="margin-top:0.5rem; font-size:0.9rem; color:#111827;">Evoluci贸n</p> | |
| </div> | |
| </div> | |
| <p style="color:#374151; font-size:0.9rem; text-align:center;"> | |
| Estas caracter铆sticas aumentan la probabilidad de melanoma. | |
| Consulte a un dermat贸logo si observa alguna. | |
| </p> | |
| </div> | |
| """) | |
| # === Tabla de m茅tricas debajo === | |
| gr.HTML(""" | |
| <p style='color:#111827;'>M茅tricas lunar para consultar en casos</p> | |
| """) | |
| metrics_table = gr.Dataframe( | |
| headers=["M茅trica", "Valor"], | |
| datatype=["str", "number"], | |
| interactive=False, | |
| label="M茅tricas calculadas", | |
| wrap=True, | |
| row_count=(5, "fixed"), | |
| col_count=(2, "fixed") | |
| ) | |
| # === Aviso final === | |
| gr.HTML(""" | |
| <section style="text-align:center; padding: 1rem; margin-top: 2rem;"> | |
| <p style="color:#b91c1c; font-weight:bold; font-size:1rem;"> | |
| Este sistema es solo de apoyo y nunca sustituye la valoraci贸n de un experto m茅dico. | |
| </p> | |
| </section> | |
| """) | |
| # === Conexi贸n bot贸n -> funci贸n === | |
| run_btn.click( | |
| fn=preprocess_and_predict, | |
| inputs=[img_input], | |
| outputs=[img_mask, img_segmented, result_text, result_bar, metrics_table, gradcam_img] | |
| ) | |
| # === Lanzar en tema claro === | |
| if __name__ == "__main__": | |
| demo.launch() | |