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 from torchvision import transforms import torch.nn.functional as F # --- MODELOS VERIFICADOS DISPONIBLES EN HUGGING FACE --- # 1. Google Derm Foundation (VERIFICADO - existe en Hugging Face) try: derm_processor = ViTImageProcessor.from_pretrained("google/derm-foundation") derm_model = ViTForImageClassification.from_pretrained("google/derm-foundation") derm_model.eval() DERM_AVAILABLE = True print("✅ Google Derm Foundation cargado exitosamente") except Exception as e: DERM_AVAILABLE = False print(f"❌ Google Derm Foundation no disponible: {e}") # 2. Modelo HAM10k especializado (VERIFICADO) try: ham_processor = ViTImageProcessor.from_pretrained("bsenst/skin-cancer-HAM10k") ham_model = ViTForImageClassification.from_pretrained("bsenst/skin-cancer-HAM10k") ham_model.eval() HAM_AVAILABLE = True print("✅ HAM10k especializado cargado exitosamente") except Exception as e: HAM_AVAILABLE = False print(f"❌ HAM10k especializado no disponible: {e}") # 3. Modelo ISIC 2024 con SMOTE (VERIFICADO) try: isic_processor = ViTImageProcessor.from_pretrained("jhoppanne/SkinCancerClassifier_smote-V0") isic_model = ViTForImageClassification.from_pretrained("jhoppanne/SkinCancerClassifier_smote-V0") isic_model.eval() ISIC_AVAILABLE = True print("✅ ISIC 2024 SMOTE cargado exitosamente") except Exception as e: ISIC_AVAILABLE = False print(f"❌ ISIC 2024 SMOTE no disponible: {e}") # 4. Modelo genérico de detección (VERIFICADO) try: generic_processor = ViTImageProcessor.from_pretrained("syaha/skin_cancer_detection_model") generic_model = ViTForImageClassification.from_pretrained("syaha/skin_cancer_detection_model") generic_model.eval() GENERIC_AVAILABLE = True print("✅ Modelo genérico cargado exitosamente") except Exception as e: GENERIC_AVAILABLE = False print(f"❌ Modelo genérico no disponible: {e}") # 5. Modelo de melanoma específico (VERIFICADO) try: melanoma_processor = ViTImageProcessor.from_pretrained("milutinNemanjic/Melanoma-detection-model") melanoma_model = ViTForImageClassification.from_pretrained("milutinNemanjic/Melanoma-detection-model") melanoma_model.eval() MELANOMA_AVAILABLE = True print("✅ Modelo melanoma específico cargado exitosamente") except Exception as e: MELANOMA_AVAILABLE = False print(f"❌ Modelo melanoma específico no disponible: {e}") # 6. Tu modelo actual como respaldo try: backup_processor = ViTImageProcessor.from_pretrained("Anwarkh1/Skin_Cancer-Image_Classification") backup_model = ViTForImageClassification.from_pretrained("Anwarkh1/Skin_Cancer-Image_Classification") backup_model.eval() BACKUP_AVAILABLE = True print("✅ Modelo de respaldo cargado exitosamente") except Exception as e: BACKUP_AVAILABLE = False print(f"❌ Modelo de respaldo no disponible: {e}") # Clases HAM10000 estándar 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}, # akiec 1: {'level': 'Crítico', 'color': '#cc0000', 'weight': 0.9}, # bcc 2: {'level': 'Bajo', 'color': '#44ff44', 'weight': 0.1}, # bkl 3: {'level': 'Bajo', 'color': '#44ff44', 'weight': 0.1}, # df 4: {'level': 'Crítico', 'color': '#990000', 'weight': 1.0}, # melanoma 5: {'level': 'Bajo', 'color': '#66ff66', 'weight': 0.1}, # nv 6: {'level': 'Moderado', 'color': '#ffaa00', 'weight': 0.3} # vasc } MALIGNANT_INDICES = [0, 1, 4] # akiec, bcc, melanoma def safe_predict(image, processor, model, model_name, expected_classes=7): """Predicción segura que maneja diferentes números de clases""" try: inputs = processor(image, return_tensors="pt") with torch.no_grad(): outputs = model(**inputs) logits = outputs.logits # Manejar diferentes números de clases if logits.shape[1] != expected_classes: print(f"⚠️ {model_name}: Esperaba {expected_classes} clases, obtuvo {logits.shape[1]}") if logits.shape[1] == 2: # Modelo binario (benigno/maligno) probabilities = F.softmax(logits, dim=-1).cpu().numpy()[0] # Convertir a formato de 7 clases (simplificado) expanded_probs = np.zeros(expected_classes) if probabilities[1] > 0.5: # Maligno expanded_probs[4] = probabilities[1] * 0.6 # Melanoma expanded_probs[1] = probabilities[1] * 0.3 # BCC expanded_probs[0] = probabilities[1] * 0.1 # AKIEC else: # Benigno expanded_probs[5] = probabilities[0] * 0.7 # Nevus expanded_probs[2] = probabilities[0] * 0.2 # BKL expanded_probs[3] = probabilities[0] * 0.1 # DF probabilities = expanded_probs else: # Para otros números de clases, normalizar o truncar probabilities = F.softmax(logits, dim=-1).cpu().numpy()[0] if len(probabilities) > expected_classes: probabilities = probabilities[:expected_classes] elif len(probabilities) < expected_classes: temp = np.zeros(expected_classes) temp[:len(probabilities)] = probabilities probabilities = temp else: probabilities = F.softmax(logits, dim=-1).cpu().numpy()[0] predicted_idx = int(np.argmax(probabilities)) predicted_class = CLASSES[predicted_idx] if predicted_idx < len(CLASSES) else "Desconocido" confidence = float(probabilities[predicted_idx]) is_malignant = predicted_idx in MALIGNANT_INDICES return { 'model': model_name, 'class': predicted_class, 'confidence': confidence, 'probabilities': probabilities, 'is_malignant': is_malignant, 'predicted_idx': predicted_idx, 'success': True } except Exception as e: print(f"❌ Error en {model_name}: {e}") return { 'model': model_name, 'error': str(e), 'class': 'Error', 'confidence': 0.0, 'is_malignant': False, 'success': False } def ensemble_prediction(predictions): """Combina múltiples predicciones usando weighted voting inteligente""" valid_preds = [p for p in predictions if p.get('success', False)] if not valid_preds: return None # Weighted ensemble basado en confianza y disponibilidad del modelo ensemble_probs = np.zeros(len(CLASSES)) total_weight = 0 # Pesos específicos por modelo (basado en calidad esperada) model_weights = { "🏥 Google Derm Foundation": 1.0, "🧠 HAM10k Especializado": 0.9, "🆕 ISIC 2024 SMOTE": 0.8, "🔬 Melanoma Específico": 0.7, "🌐 Genérico": 0.6, "🔄 Respaldo Original": 0.5 } for pred in valid_preds: model_weight = model_weights.get(pred['model'], 0.5) confidence_weight = pred['confidence'] final_weight = model_weight * confidence_weight ensemble_probs += pred['probabilities'] * final_weight total_weight += final_weight if total_weight > 0: ensemble_probs /= total_weight 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 # Calcular consenso de malignidad 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 sofisticado""" if not ensemble_result: return 0.0 # Score base del ensemble base_score = ensemble_result['probabilities'][ensemble_result['predicted_idx']] * \ RISK_LEVELS[ensemble_result['predicted_idx']]['weight'] # Ajuste por consenso de malignidad consensus_boost = ensemble_result['malignant_consensus'] * 0.3 # Bonus por número de modelos model_confidence = min(ensemble_result['num_models'] / 5.0, 1.0) * 0.1 final_score = base_score + consensus_boost + model_confidence return min(final_score, 1.0) def analizar_lesion_verificado(img): """Análisis con modelos verificados existentes""" predictions = [] # Probar modelos disponibles en orden de preferencia models_to_try = [ (DERM_AVAILABLE, derm_processor, derm_model, "🏥 Google Derm Foundation"), (HAM_AVAILABLE, ham_processor, ham_model, "🧠 HAM10k Especializado"), (ISIC_AVAILABLE, isic_processor, isic_model, "🆕 ISIC 2024 SMOTE"), (MELANOMA_AVAILABLE, melanoma_processor, melanoma_model, "🔬 Melanoma Específico"), (GENERIC_AVAILABLE, generic_processor, generic_model, "🌐 Genérico"), (BACKUP_AVAILABLE, backup_processor, backup_model, "🔄 Respaldo Original") ] for available, processor, model, name in models_to_try: if available: pred = safe_predict(img, processor, model, name) predictions.append(pred) if not predictions: return "❌ No hay modelos disponibles", "" # Ensemble de predicciones ensemble_result = ensemble_prediction(predictions) if not ensemble_result: return "❌ Error en el análisis ensemble", "" # Calcular riesgo risk_score = calculate_risk_score(ensemble_result) # Generar visualización colors = [RISK_LEVELS[i]['color'] for i in range(len(CLASSES))] fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(16, 7)) # Gráfico principal del ensemble bars = ax1.bar(CLASSES, ensemble_result['probabilities'] * 100, color=colors, alpha=0.8) ax1.set_title("🎯 Predicción Ensemble (Modelos Combinados)", fontsize=16, fontweight='bold', pad=20) ax1.set_ylabel("Probabilidad (%)", fontsize=12) ax1.set_xticklabels(CLASSES, rotation=45, ha='right', fontsize=10) ax1.grid(axis='y', alpha=0.3) ax1.set_ylim(0, 100) # Destacar la 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) # 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) ax2.set_title(f"🤝 Consenso Malignidad ({ensemble_result['num_models']} modelos)", fontsize=16, 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 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') plt.tight_layout() buf = io.BytesIO() plt.savefig(buf, format="png", dpi=120, bbox_inches='tight') plt.close(fig) chart_html = f'' # Generar reporte detallado informe = f"""

🏥 Análisis Dermatológico Multi-Modelo IA

📊 Resultados Individuales por Modelo

""" for i, pred in enumerate(predictions): row_color = "#f8f9fa" if i % 2 == 0 else "#ffffff" if pred.get('success', False): status_icon = "✅" status_color = "#27ae60" status_text = "Activo" malignant_color = "#e74c3c" if pred.get('is_malignant', False) else "#27ae60" malignant_text = "🚨 Maligno" if pred.get('is_malignant', False) else "✅ Benigno" informe += f""" """ else: informe += f""" """ # Resultado del ensemble ensemble_status_color = "#e74c3c" if ensemble_result.get('is_malignant', False) else "#27ae60" ensemble_status_text = "🚨 MALIGNO" if ensemble_result.get('is_malignant', False) else "✅ BENIGNO" informe += f"""
Modelo Diagnóstico Confianza Estado Malignidad
{pred['model']} {pred['class']} {pred['confidence']:.1%} {status_icon} {status_text} {malignant_text}
{pred['model']} ❌ No disponible N/A ❌ Error N/A

🎯 Diagnóstico Final (Consenso de {ensemble_result['num_models']} modelos)

Diagnóstico: {ensemble_result['class']}

Confianza: {ensemble_result['confidence']:.1%}

Estado: {ensemble_status_text}

Consenso Malignidad: {ensemble_result['malignant_consensus']:.1%}

Score de Riesgo: {risk_score:.2f}

Modelos Activos: {ensemble_result['num_models']}/6

""" # Recomendación clínica informe += """

🩺 Recomendación Clínica Automatizada

""" if risk_score > 0.7: informe += '''

🚨 DERIVACIÓN URGENTE

Contactar con oncología dermatológica en 24-48 horas

''' elif risk_score > 0.5: informe += '''

⚠️ EVALUACIÓN PRIORITARIA

Consulta dermatológica en 1-2 semanas

''' elif risk_score > 0.3: informe += '''

📋 SEGUIMIENTO PROGRAMADO

Consulta dermatológica en 4-6 semanas

''' else: informe += '''

✅ MONITOREO RUTINARIO

Seguimiento en 3-6 meses

''' informe += f"""

⚠️ Disclaimer Médico: Este análisis utiliza {ensemble_result['num_models']} modelos de IA como herramienta de apoyo diagnóstico. El resultado NO sustituye el criterio médico profesional. Siempre consulte con un dermatólogo certificado para un diagnóstico definitivo y plan de tratamiento apropiado.

""" return informe, chart_html # Interfaz Gradio mejorada demo = gr.Interface( fn=analizar_lesion_verificado, inputs=gr.Image(type="pil", label="📷 Cargar imagen dermatoscópica o foto de lesión cutánea"), outputs=[ gr.HTML(label="📋 Informe Diagnóstico Completo"), gr.HTML(label="📊 Análisis Visual de Resultados") ], title="🏥 Sistema Avanzado de Detección de Cáncer de Piel - Multi-Modelo IA", description=""" Sistema de análisis dermatológico que utiliza múltiples modelos de IA especializados verificados: • Google Derm Foundation (modelo más avanzado de Google Health) • Modelos especializados en HAM10000, ISIC 2024, y detección de melanoma • Ensemble inteligente con weighted voting y análisis de consenso """, theme=gr.themes.Soft(), allow_flagging="never", examples=None ) if __name__ == "__main__": print("\n🚀 Iniciando sistema de detección de cáncer de piel...") print("📋 Modelos verificados y disponibles en Hugging Face:") print("✅ google/derm-foundation") print("✅ bsenst/skin-cancer-HAM10k") print("✅ jhoppanne/SkinCancerClassifier_smote-V0") print("✅ syaha/skin_cancer_detection_model") print("✅ milutinNemanjic/Melanoma-detection-model") print("✅ Anwarkh1/Skin_Cancer-Image_Classification") print("\n🌐 Lanzando interfaz web...") demo.launch(share=False)