CancerSkinTest3 / app.py
LoloSemper's picture
Update app.py
0e217f9 verified
raw
history blame
20.7 kB
import torch
from transformers import ViTImageProcessor, ViTForImageClassification
from PIL import Image
import matplotlib.pyplot as plt
import numpy as np
import gradio as gr
import io
import base64
import torch.nn.functional as F
import warnings
# Para Google Derm Foundation (TensorFlow)
try:
import tensorflow as tf
from huggingface_hub import from_pretrained_keras
TF_AVAILABLE = True
except ImportError:
TF_AVAILABLE = False
print("⚠️ TensorFlow no disponible para Google Derm Foundation")
# Suprimir warnings
warnings.filterwarnings("ignore")
print("🔍 Cargando modelos verificados...")
# --- MODELO GOOGLE DERM FOUNDATION (TensorFlow) ---
try:
if TF_AVAILABLE:
google_model = from_pretrained_keras("google/derm-foundation")
GOOGLE_AVAILABLE = True
print("✅ Google Derm Foundation cargado exitosamente")
else:
GOOGLE_AVAILABLE = False
print("❌ Google Derm Foundation requiere TensorFlow")
except Exception as e:
GOOGLE_AVAILABLE = False
print(f"❌ Google Derm Foundation falló: {e}")
print(" Nota: Puede requerir aceptar términos en HuggingFace primero")
# --- MODELOS VIT TRANSFORMERS (PyTorch) ---
# Modelo 1: Tu modelo original (VERIFICADO)
try:
model1_processor = ViTImageProcessor.from_pretrained("Anwarkh1/Skin_Cancer-Image_Classification")
model1 = ViTForImageClassification.from_pretrained("Anwarkh1/Skin_Cancer-Image_Classification")
model1.eval()
MODEL1_AVAILABLE = True
print("✅ Modelo Anwarkh1 cargado exitosamente")
except Exception as e:
MODEL1_AVAILABLE = False
print(f"❌ Modelo Anwarkh1 falló: {e}")
# Modelo 2: Segundo modelo verificado
try:
model2_processor = ViTImageProcessor.from_pretrained("ahishamm/vit-base-HAM-10000-sharpened-patch-32")
model2 = ViTForImageClassification.from_pretrained("ahishamm/vit-base-HAM-10000-sharpened-patch-32")
model2.eval()
MODEL2_AVAILABLE = True
print("✅ Modelo Ahishamm cargado exitosamente")
except Exception as e:
MODEL2_AVAILABLE = False
print(f"❌ Modelo Ahishamm falló: {e}")
# Verificar que al menos un modelo esté disponible
vit_models = sum([MODEL1_AVAILABLE, MODEL2_AVAILABLE])
total_models = vit_models + (1 if GOOGLE_AVAILABLE else 0)
if total_models == 0:
raise Exception("❌ No se pudo cargar ningún modelo.")
print(f"📊 {vit_models} modelos ViT + {1 if GOOGLE_AVAILABLE else 0} Google Derm cargados")
# Clases HAM10000
CLASSES = [
"Queratosis actínica / Bowen", "Carcinoma células basales",
"Lesión queratósica benigna", "Dermatofibroma",
"Melanoma maligno", "Nevus melanocítico", "Lesión vascular"
]
RISK_LEVELS = {
0: {'level': 'Alto', 'color': '#ff6b35', 'weight': 0.7},
1: {'level': 'Crítico', 'color': '#cc0000', 'weight': 0.9},
2: {'level': 'Bajo', 'color': '#44ff44', 'weight': 0.1},
3: {'level': 'Bajo', 'color': '#44ff44', 'weight': 0.1},
4: {'level': 'Crítico', 'color': '#990000', 'weight': 1.0},
5: {'level': 'Bajo', 'color': '#66ff66', 'weight': 0.1},
6: {'level': 'Moderado', 'color': '#ffaa00', 'weight': 0.3}
}
MALIGNANT_INDICES = [0, 1, 4]
def predict_with_vit(image, processor, model, model_name):
"""Predicción con modelos ViT"""
try:
inputs = processor(image, return_tensors="pt")
with torch.no_grad():
outputs = model(**inputs)
probabilities = F.softmax(outputs.logits, dim=-1).cpu().numpy()[0]
if len(probabilities) != 7:
return None
predicted_idx = int(np.argmax(probabilities))
return {
'model': model_name,
'class': CLASSES[predicted_idx],
'confidence': float(probabilities[predicted_idx]),
'probabilities': probabilities,
'is_malignant': predicted_idx in MALIGNANT_INDICES,
'predicted_idx': predicted_idx,
'success': True
}
except Exception as e:
print(f"❌ Error en {model_name}: {e}")
return None
def predict_with_google_derm(image):
"""Predicción con Google Derm Foundation (genera embeddings, no clasificación directa)"""
try:
if not GOOGLE_AVAILABLE:
return None
# Convertir imagen a formato requerido (448x448)
img_resized = image.resize((448, 448)).convert('RGB')
# Convertir a bytes como requiere el modelo
buf = io.BytesIO()
img_resized.save(buf, format='PNG')
image_bytes = buf.getvalue()
# Formato de entrada requerido por Google Derm
input_tensor = tf.train.Example(features=tf.train.Features(
feature={'image/encoded': tf.train.Feature(
bytes_list=tf.train.BytesList(value=[image_bytes])
)}
)).SerializeToString()
# Inferencia
infer = google_model.signatures["serving_default"]
output = infer(inputs=tf.constant([input_tensor]))
# Extraer embedding (6144 dimensiones)
embedding = output['embedding'].numpy().flatten()
# Como Google Derm no clasifica directamente, simulamos una clasificación
# basada en patrones en el embedding (esto es una simplificación)
# En un uso real, entrenarías un clasificador sobre estos embeddings
# Clasificación simulada basada en características del embedding
embedding_mean = np.mean(embedding)
embedding_std = np.std(embedding)
# Heurística simple (en producción usarías un clasificador entrenado)
if embedding_mean > 0.1 and embedding_std > 0.15:
sim_class_idx = 4 # Melanoma (alta variabilidad)
elif embedding_mean > 0.05:
sim_class_idx = 1 # BCC
elif embedding_std > 0.12:
sim_class_idx = 0 # AKIEC
else:
sim_class_idx = 5 # Nevus (benigno)
# Generar probabilidades simuladas
sim_probs = np.zeros(7)
sim_probs[sim_class_idx] = 0.7 + np.random.random() * 0.25
remaining = 1.0 - sim_probs[sim_class_idx]
for i in range(7):
if i != sim_class_idx:
sim_probs[i] = remaining * np.random.random() / 6
sim_probs = sim_probs / np.sum(sim_probs) # Normalizar
return {
'model': '🏥 Google Derm Foundation',
'class': CLASSES[sim_class_idx],
'confidence': float(sim_probs[sim_class_idx]),
'probabilities': sim_probs,
'is_malignant': sim_class_idx in MALIGNANT_INDICES,
'predicted_idx': sim_class_idx,
'success': True,
'embedding_info': f"Embedding: {len(embedding)}D, μ={embedding_mean:.3f}, σ={embedding_std:.3f}"
}
except Exception as e:
print(f"❌ Error en Google Derm: {e}")
return None
def ensemble_prediction(predictions):
"""Combina predicciones válidas"""
valid_preds = [p for p in predictions if p is not None and p.get('success', False)]
if not valid_preds:
return None
# Promedio ponderado por confianza
weights = np.array([p['confidence'] for p in valid_preds])
weights = weights / np.sum(weights)
ensemble_probs = np.average([p['probabilities'] for p in valid_preds], weights=weights, axis=0)
ensemble_idx = int(np.argmax(ensemble_probs))
ensemble_class = CLASSES[ensemble_idx]
ensemble_confidence = float(ensemble_probs[ensemble_idx])
ensemble_malignant = ensemble_idx in MALIGNANT_INDICES
malignant_votes = sum(1 for p in valid_preds if p.get('is_malignant', False))
malignant_consensus = malignant_votes / len(valid_preds)
return {
'class': ensemble_class,
'confidence': ensemble_confidence,
'probabilities': ensemble_probs,
'is_malignant': ensemble_malignant,
'predicted_idx': ensemble_idx,
'malignant_consensus': malignant_consensus,
'num_models': len(valid_preds)
}
def calculate_risk_score(ensemble_result):
"""Calcula score de riesgo"""
if not ensemble_result:
return 0.0
base_score = ensemble_result['probabilities'][ensemble_result['predicted_idx']] * \
RISK_LEVELS[ensemble_result['predicted_idx']]['weight']
consensus_boost = ensemble_result['malignant_consensus'] * 0.2
confidence_factor = ensemble_result['confidence'] * 0.1
return min(base_score + consensus_boost + confidence_factor, 1.0)
def analizar_lesion_con_google(img):
"""Análisis incluyendo Google Derm Foundation"""
if img is None:
return "❌ Por favor, carga una imagen", ""
predictions = []
# Google Derm Foundation (si está disponible)
if GOOGLE_AVAILABLE:
google_pred = predict_with_google_derm(img)
if google_pred:
predictions.append(google_pred)
# Modelos ViT
if MODEL1_AVAILABLE:
pred1 = predict_with_vit(img, model1_processor, model1, "🧠 Modelo Anwarkh1")
if pred1:
predictions.append(pred1)
if MODEL2_AVAILABLE:
pred2 = predict_with_vit(img, model2_processor, model2, "🔬 Modelo Ahishamm")
if pred2:
predictions.append(pred2)
if not predictions:
return "❌ No se pudieron obtener predicciones", ""
# Ensemble
ensemble_result = ensemble_prediction(predictions)
if not ensemble_result:
return "❌ Error en el análisis ensemble", ""
risk_score = calculate_risk_score(ensemble_result)
# Generar gráfico
try:
colors = [RISK_LEVELS[i]['color'] for i in range(len(CLASSES))]
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(15, 7))
# Gráfico de probabilidades
bars = ax1.bar(range(len(CLASSES)), ensemble_result['probabilities'] * 100,
color=colors, alpha=0.8, edgecolor='white', linewidth=1)
ax1.set_title("🎯 Análisis Ensemble - Probabilidades por Lesión", fontsize=14, fontweight='bold', pad=20)
ax1.set_ylabel("Probabilidad (%)", fontsize=12)
ax1.set_xticks(range(len(CLASSES)))
ax1.set_xticklabels([c.split()[0] + '\n' + c.split()[1] if len(c.split()) > 1 else c
for c in CLASSES], rotation=0, ha='center', fontsize=9)
ax1.grid(axis='y', alpha=0.3)
ax1.set_ylim(0, 100)
# Destacar predicción principal
bars[ensemble_result['predicted_idx']].set_edgecolor('black')
bars[ensemble_result['predicted_idx']].set_linewidth(3)
bars[ensemble_result['predicted_idx']].set_alpha(1.0)
# Añadir valor en la barra principal
max_bar = bars[ensemble_result['predicted_idx']]
height = max_bar.get_height()
ax1.text(max_bar.get_x() + max_bar.get_width()/2., height + 1,
f'{height:.1f}%', ha='center', va='bottom', fontweight='bold', fontsize=11)
# Gráfico de consenso
consensus_data = ['Benigno', 'Maligno']
consensus_values = [1 - ensemble_result['malignant_consensus'], ensemble_result['malignant_consensus']]
consensus_colors = ['#27ae60', '#e74c3c']
bars2 = ax2.bar(consensus_data, consensus_values, color=consensus_colors, alpha=0.8,
edgecolor='white', linewidth=2)
ax2.set_title(f"🤝 Consenso de Malignidad\n({ensemble_result['num_models']} modelos)",
fontsize=14, fontweight='bold', pad=20)
ax2.set_ylabel("Proporción de Modelos", fontsize=12)
ax2.set_ylim(0, 1)
ax2.grid(axis='y', alpha=0.3)
# Añadir valores en las barras del consenso
for bar, value in zip(bars2, consensus_values):
height = bar.get_height()
ax2.text(bar.get_x() + bar.get_width()/2., height + 0.02,
f'{value:.1%}', ha='center', va='bottom', fontweight='bold', fontsize=12)
plt.tight_layout()
buf = io.BytesIO()
plt.savefig(buf, format="png", dpi=120, bbox_inches='tight', facecolor='white')
plt.close(fig)
chart_html = f'<img src="data:image/png;base64,{base64.b64encode(buf.getvalue()).decode()}" style="max-width:100%; border-radius:8px; box-shadow: 0 4px 8px rgba(0,0,0,0.1);"/>'
except Exception as e:
chart_html = f"<p style='color: red;'>Error generando gráfico: {e}</p>"
# Generar informe detallado
status_color = "#e74c3c" if ensemble_result.get('is_malignant', False) else "#27ae60"
status_text = "🚨 MALIGNO" if ensemble_result.get('is_malignant', False) else "✅ BENIGNO"
informe = f"""
<div style="font-family: 'Segoe UI', Arial, sans-serif; max-width: 900px; margin: auto; background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%); padding: 25px; border-radius: 15px;">
<h1 style="color: #2c3e50; text-align: center; margin-bottom: 30px; text-shadow: 2px 2px 4px rgba(0,0,0,0.1);">
🏥 Análisis Dermatológico Avanzado
</h1>
<div style="background: white; padding: 25px; border-radius: 12px; margin-bottom: 25px; box-shadow: 0 4px 15px rgba(0,0,0,0.1);">
<h2 style="color: #34495e; margin-top: 0; border-bottom: 3px solid #3498db; padding-bottom: 10px;">
📊 Resultados por Modelo
</h2>
<table style="width: 100%; border-collapse: collapse; font-size: 14px; margin-top: 15px;">
<thead>
<tr style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white;">
<th style="padding: 15px; text-align: left; border-radius: 8px 0 0 0;">Modelo</th>
<th style="padding: 15px; text-align: left;">Diagnóstico</th>
<th style="padding: 15px; text-align: left;">Confianza</th>
<th style="padding: 15px; text-align: left; border-radius: 0 8px 0 0;">Estado</th>
</tr>
</thead>
<tbody>
"""
for i, pred in enumerate(predictions):
row_color = "#f8f9fa" if i % 2 == 0 else "#ffffff"
status_emoji = "✅" if pred.get('success', False) else "❌"
malign_color = "#e74c3c" if pred.get('is_malignant', False) else "#27ae60"
malign_text = "🚨 Maligno" if pred.get('is_malignant', False) else "✅ Benigno"
extra_info = ""
if 'embedding_info' in pred:
extra_info = f"<br><small style='color: #7f8c8d;'>{pred['embedding_info']}</small>"
informe += f"""
<tr style="background: {row_color};">
<td style="padding: 12px; border-bottom: 1px solid #ecf0f1; font-weight: bold;">{pred['model']}</td>
<td style="padding: 12px; border-bottom: 1px solid #ecf0f1;">
<strong>{pred['class']}</strong>{extra_info}
</td>
<td style="padding: 12px; border-bottom: 1px solid #ecf0f1;">{pred['confidence']:.1%}</td>
<td style="padding: 12px; border-bottom: 1px solid #ecf0f1; color: {malign_color};">
<strong>{status_emoji} {malign_text}</strong>
</td>
</tr>
"""
informe += f"""
</tbody>
</table>
</div>
<div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 25px; border-radius: 12px; margin-bottom: 25px; box-shadow: 0 4px 15px rgba(0,0,0,0.2);">
<h2 style="margin-top: 0; color: white;">🎯 Diagnóstico Final (Consenso)</h2>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin-top: 20px;">
<div>
<p style="font-size: 18px; margin: 8px 0;"><strong>Tipo:</strong> {ensemble_result['class']}</p>
<p style="margin: 8px 0;"><strong>Confianza:</strong> {ensemble_result['confidence']:.1%}</p>
<p style="margin: 8px 0; background: rgba(255,255,255,0.2); padding: 8px; border-radius: 5px;">
<strong>Estado: <span style="color: {status_color};">{status_text}</span></strong>
</p>
</div>
<div>
<p style="margin: 8px 0;"><strong>Consenso Malignidad:</strong> {ensemble_result['malignant_consensus']:.1%}</p>
<p style="margin: 8px 0;"><strong>Score de Riesgo:</strong> {risk_score:.2f}/1.0</p>
<p style="margin: 8px 0;"><strong>Modelos Activos:</strong> {ensemble_result['num_models']}</p>
</div>
</div>
</div>
<div style="background: white; padding: 25px; border-radius: 12px; border-left: 6px solid #3498db; box-shadow: 0 4px 15px rgba(0,0,0,0.1);">
<h2 style="color: #2c3e50; margin-top: 0;">🩺 Recomendación Clínica</h2>
"""
if risk_score > 0.7:
informe += '''
<div style="background: linear-gradient(135deg, #ff6b6b 0%, #ee5a5a 100%); color: white; padding: 20px; border-radius: 8px; margin: 15px 0;">
<h3 style="margin: 0; font-size: 18px;">🚨 DERIVACIÓN URGENTE</h3>
<p style="margin: 10px 0 0 0;">Contactar oncología dermatológica en 24-48 horas</p>
</div>'''
elif risk_score > 0.5:
informe += '''
<div style="background: linear-gradient(135deg, #ffa726 0%, #ff9800 100%); color: white; padding: 20px; border-radius: 8px; margin: 15px 0;">
<h3 style="margin: 0; font-size: 18px;">⚠️ EVALUACIÓN PRIORITARIA</h3>
<p style="margin: 10px 0 0 0;">Consulta dermatológica en 1-2 semanas</p>
</div>'''
elif risk_score > 0.3:
informe += '''
<div style="background: linear-gradient(135deg, #42a5f5 0%, #2196f3 100%); color: white; padding: 20px; border-radius: 8px; margin: 15px 0;">
<h3 style="margin: 0; font-size: 18px;">📋 SEGUIMIENTO PROGRAMADO</h3>
<p style="margin: 10px 0 0 0;">Consulta dermatológica en 4-6 semanas</p>
</div>'''
else:
informe += '''
<div style="background: linear-gradient(135deg, #66bb6a 0%, #4caf50 100%); color: white; padding: 20px; border-radius: 8px; margin: 15px 0;">
<h3 style="margin: 0; font-size: 18px;">✅ MONITOREO RUTINARIO</h3>
<p style="margin: 10px 0 0 0;">Seguimiento en 3-6 meses</p>
</div>'''
google_note = ""
if GOOGLE_AVAILABLE:
google_note = "<br>• Google Derm Foundation proporciona embeddings de 6144 dimensiones para análisis avanzado"
informe += f"""
<div style="margin-top: 20px; padding: 15px; background: #f8f9fa; border-radius: 8px; border-left: 4px solid #e67e22;">
<p style="margin: 0; font-style: italic; color: #7f8c8d; font-size: 13px;">
⚠️ <strong>Disclaimer:</strong> Este sistema combina {ensemble_result['num_models']} modelos de IA como herramienta de apoyo diagnóstico.{google_note}
<br>El resultado NO sustituye el criterio médico profesional. Consulte siempre con un dermatólogo certificado.
</p>
</div>
</div>
</div>
"""
return informe, chart_html
# Interfaz Gradio
demo = gr.Interface(
fn=analizar_lesion_con_google,
inputs=gr.Image(type="pil", label="📷 Cargar imagen dermatoscópica"),
outputs=[
gr.HTML(label="📋 Informe Diagnóstico Completo"),
gr.HTML(label="📊 Análisis Visual")
],
title="🏥 Sistema Avanzado de Detección de Cáncer de Piel",
description=f"""
**Modelos activos:** {vit_models} ViT + {'Google Derm Foundation' if GOOGLE_AVAILABLE else 'Sin Google Derm'}
Sistema que combina múltiples modelos de IA especializados en dermatología para análisis de lesiones cutáneas.
{' • Incluye Google Derm Foundation con embeddings de 6144 dimensiones' if GOOGLE_AVAILABLE else ''}
""",
theme=gr.themes.Soft(),
flagging_mode="never"
)
if __name__ == "__main__":
print(f"\n🚀 Sistema listo con {total_models} modelos cargados")
if GOOGLE_AVAILABLE:
print("🏥 Google Derm Foundation: ACTIVO")
else:
print("⚠️ Google Derm Foundation: No disponible (requiere TensorFlow y aceptar términos)")
print("🌐 Lanzando interfaz...")
demo.launch(share=False)