Carlos012 commited on
Commit
08bea24
·
1 Parent(s): 4e13311

feat (interfaz): Actualizar interfaz gradio

Browse files
Files changed (5) hide show
  1. Dockerfile +10 -0
  2. README.md +272 -10
  3. app.py +533 -0
  4. configuracion.py +173 -0
  5. requirements.txt +22 -0
Dockerfile ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.12-slim
2
+
3
+ WORKDIR /app
4
+ COPY requirements.txt .
5
+ RUN pip install -r requirements.txt
6
+
7
+ COPY app.py .
8
+
9
+ EXPOSE 7860
10
+ CMD ["python", "app.py"]
README.md CHANGED
@@ -1,12 +1,274 @@
1
- ---
2
- title: App
3
- emoji: 🐠
4
- colorFrom: indigo
5
- colorTo: gray
6
- sdk: gradio
7
- sdk_version: 6.5.1
8
- app_file: app.py
9
- pinned: false
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
  ---
11
 
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
+ # 🥔 Clasificador de Calidad de Papas - Gradio
2
+
3
+ Aplicación de clasificación de calidad de tubérculos de papa (Chaucha y Chola) usando Vision Transformer (ViT-Base/16) con interfaz mejorada en Gradio.
4
+
5
+ ## 🌟 Características
6
+
7
+ - **Modelo de última generación**: Vision Transformer (ViT-Base/16) afinado para clasificación de papa chaucha y chola.
8
+ - **Interfaz moderna**: Gradio con diseño responsive y visualizaciones interactivas
9
+ - **Visualización explicable**: Mapas de atención Grad-CAM para interpretar las decisiones del modelo
10
+ - **Alto rendimiento**: Optimizado para CPU y GPU
11
+ - **Métricas en tiempo real**: Tiempo de inferencia y confianza por clase
12
+ - **Validación robusta**: Manejo de errores y validación de entrada
13
+
14
+ ## 📋 Requisitos
15
+
16
+ - Python 3.8 o superior
17
+ - CUDA (opcional, para aceleración GPU)
18
+ - 4GB RAM mínimo (8GB recomendado)
19
+
20
+ ## 🚀 Instalación
21
+
22
+ ### 1. Clonar o descargar el repositorio
23
+
24
+ ```bash
25
+ cd streamlit/demo
26
+ ```
27
+
28
+ ### 2. Crear entorno virtual (recomendado)
29
+
30
+ **Windows:**
31
+
32
+ ```bash
33
+ python -m venv venv
34
+ .\venv\Scripts\activate
35
+ ```
36
+
37
+ **Linux/Mac:**
38
+
39
+ ```bash
40
+ python3 -m venv venv
41
+ source venv/bin/activate
42
+ ```
43
+
44
+ ### 3. Instalar dependencias
45
+
46
+ ```bash
47
+ pip install -r requirements_gradio.txt
48
+ ```
49
+
50
+ ### 4. Configurar token de Hugging Face (opcional)
51
+
52
+ Si el modelo no se descarga automáticamente, crea un archivo `.env` o configura la variable de entorno:
53
+
54
+ **Windows:**
55
+
56
+ ```bash
57
+ set HF_TOKEN=tu_token_aqui
58
+ ```
59
+
60
+ **Linux/Mac:**
61
+
62
+ ```bash
63
+ export HF_TOKEN=tu_token_aqui
64
+ ```
65
+
66
+ Obtén tu token en: https://huggingface.co/settings/tokens
67
+
68
+ ## 🎯 Uso
69
+
70
+ ### Ejecución local
71
+
72
+ ```bash
73
+ python app.py
74
+ ```
75
+
76
+ La aplicación se abrirá automáticamente en tu navegador en:
77
+
78
+ ```
79
+ http://localhost:7860
80
+ ```
81
+
82
+ ### Ejecución con compartir (público temporalmente)
83
+
84
+ Modifica `demo.launch()` en `app.py`:
85
+
86
+ ```python
87
+ demo.launch(share=True) # Genera una URL pública temporal
88
+ ```
89
+
90
+ ## 📊 Clases de Clasificación
91
+
92
+ | Clase | Descripción |
93
+ | --------------- | ------------------------------------------------------ |
94
+ | **Buen estado** | Papa sin defectos visibles, apta para comercialización |
95
+ | **Defectuoso** | Papa con daños, manchas o deformaciones |
96
+
97
+ ## 🎨 Características de la Interfaz
98
+
99
+ ### Panel de Entrada
100
+
101
+ - Arrastrar y soltar imágenes
102
+ - Formatos soportados: PNG, JPG, JPEG
103
+ - Tamaño máximo: 20 MB
104
+ - Vista previa en tiempo real
105
+
106
+ ### Panel de Resultados
107
+
108
+ - **Predicción principal**: Clase y confianza
109
+ - **Gráficos de confianza**: Barras de progreso por cada clase
110
+ - **Mapa de atención**: Visualización Grad-CAM de regiones importantes
111
+ - **Métricas de rendimiento**: Tiempo de procesamiento y dispositivo usado
112
+
113
+ ## 🧠 Arquitectura del Modelo
114
+
115
+ ```
116
+ Input Image (224×224×3)
117
+
118
+ Vision Transformer Base
119
+ ├─ Patch Embedding (16×16)
120
+ ├─ 12 Transformer Blocks
121
+ └─ Classification Head
122
+
123
+ Output (2 clases)
124
+ ```
125
+
126
+ ### Especificaciones Técnicas
127
+
128
+ - **Arquitectura**: ViT-Base/16
129
+ - **Parámetros**: ~86M
130
+ - **Input**: 224×224 RGB
131
+ - **Normalización**: ImageNet stats
132
+ - **Framework**: PyTorch + timm
133
+
134
+ ## 📈 Interpretación del Mapa de Atención
135
+
136
+ El mapa Grad-CAM muestra en **rojo intenso** las áreas que más influyeron en la decisión:
137
+
138
+ - 🔴 **Rojo**: Alta importancia (defectos detectados)
139
+ - 🟡 **Amarillo**: Importancia media
140
+ - 🔵 **Azul**: Baja importancia
141
+
142
+ ## 🔧 Configuración Avanzada
143
+
144
+ ### Cambiar puerto
145
+
146
+ En `app.py`:
147
+
148
+ ```python
149
+ demo.launch(server_port=8080) # Cambiar puerto
150
+ ```
151
+
152
+ ### Modo desarrollo
153
+
154
+ ```python
155
+ demo.launch(debug=True) # Habilitar modo debug
156
+ ```
157
+
158
+ ### Recursos compartidos
159
+
160
+ ```python
161
+ demo.launch(
162
+ server_name="0.0.0.0", # Accesible desde la red local
163
+ share=True, # Generar link público
164
+ auth=("usuario", "contraseña") # Agregar autenticación
165
+ )
166
+ ```
167
+
168
+ ## 🐛 Solución de Problemas
169
+
170
+ ### Error: "No module named 'gradio'"
171
+
172
+ ```bash
173
+ pip install gradio --upgrade
174
+ ```
175
+
176
+ ### Error: Modelo no descarga
177
+
178
+ 1. Verifica conexión a internet
179
+ 2. Configura `HF_TOKEN` en variables de entorno
180
+ 3. Intenta descargar manualmente:
181
+
182
+ ```python
183
+ from huggingface_hub import hf_hub_download
184
+ hf_hub_download("Carlos012/vit_papas", "vit_papas_ft.pt")
185
+ ```
186
+
187
+ ### Error: CUDA out of memory
188
+
189
+ - Usa CPU en lugar de GPU
190
+ - Reduce el tamaño de las imágenes antes de subirlas
191
+
192
+ ### La interfaz no carga
193
+
194
+ ```bash
195
+ # Verificar puerto en uso
196
+ netstat -an | findstr :7860
197
+
198
+ # Cambiar puerto en app_gradio.py
199
+ demo.launch(server_port=7861)
200
+ ```
201
+
202
+ ## 📦 Estructura del Proyecto
203
+
204
+ ```
205
+ streamlit/demo/
206
+ ├── app.py # Aplicación principal Gradio
207
+ ├── requirements.txt # Dependencias Gradio
208
+ └── README.md # Este archivo
209
+ ```
210
+
211
+ ## 🚀 Despliegue en Producción
212
+
213
+ ### Hugging Face Spaces (Recomendado)
214
+
215
+ 1. Crear un nuevo Space en https://huggingface.co/spaces
216
+ 2. Seleccionar "Gradio" como SDK
217
+ 3. Subir `app.py` y `requirements.txt`
218
+ 4. El Space se desplegará automáticamente
219
+
220
+ ### Docker
221
+
222
+ ```dockerfile
223
+ FROM python:3.9-slim
224
+
225
+ WORKDIR /app
226
+ COPY requirements.txt .
227
+ RUN pip install -r requirements.txt
228
+
229
+ COPY app.py .
230
+
231
+ EXPOSE 7860
232
+ CMD ["python", "app.py"]
233
+ ```
234
+
235
+ ```bash
236
+ docker build -t clasificador-papas .
237
+ docker run -p 7860:7860 clasificador-papas
238
+ ```
239
+
240
+ ## 🎓 Mejoras Implementadas
241
+
242
+ ### Respecto a la versión Streamlit:
243
+
244
+ 1. **✅ Interfaz más limpia**: Diseño moderno con Gradio Blocks
245
+ 2. **✅ Mejor rendimiento**: Carga más rápida y menos overhead
246
+ 3. **✅ Visualizaciones mejoradas**: HTML/CSS personalizado
247
+ 4. **✅ Arquitectura modular**: Clases para gestor y procesador
248
+ 5. **✅ Validación robusta**: Manejo de errores completo
249
+ 6. **✅ Métricas expandidas**: Más información técnica
250
+ 7. **✅ Documentación inline**: Docstrings y comentarios
251
+ 8. **✅ Configuración flexible**: Dataclasses para config
252
+
253
+ ## 📝 Licencia
254
+
255
+ Este proyecto es parte de un trabajo de investigación académica.
256
+
257
+ ## 👨‍💻 Autor
258
+
259
+ Desarrollado para clasificación de tubérculos de papa usando técnicas de Deep Learning.
260
+
261
+ ## 🙏 Agradecimientos
262
+
263
+ - **timm** (PyTorch Image Models): Implementación de ViT
264
+ - **Gradio**: Framework de interfaz
265
+ - **Hugging Face Hub**: Almacenamiento del modelo
266
+ - **pytorch-grad-cam**: Visualización explicable
267
+
268
+ ## 📧 Contacto
269
+
270
+ Para preguntas, sugerencias o reportar problemas, abre un issue en el repositorio.
271
+
272
  ---
273
 
274
+ **⚠️ Nota Importante**: Este sistema es una herramienta de apoyo para la clasificación de papas. Para decisiones críticas de calidad en contextos comerciales, se recomienda consultar con especialistas en agronomía e inspección de calidad.
app.py ADDED
@@ -0,0 +1,533 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import time
3
+ import warnings
4
+ from dataclasses import dataclass
5
+ from pathlib import Path
6
+ from typing import Dict, Optional, Tuple
7
+ import gradio as gr
8
+ import numpy as np
9
+ import timm
10
+ import torch
11
+ import torch.nn as nn
12
+ import torchvision.transforms as transforms
13
+ from huggingface_hub import hf_hub_download
14
+ from PIL import Image
15
+ from pytorch_grad_cam import EigenCAM
16
+ from pytorch_grad_cam.utils.image import show_cam_on_image
17
+
18
+ # Configuración
19
+ warnings.filterwarnings("ignore", message=".*HF Hub.*")
20
+ warnings.filterwarnings("ignore", category=FutureWarning)
21
+
22
+ # Constantes globales
23
+ CLASES = {0: "Buen estado", 1: "Defectuoso"}
24
+ COLORES_CLASES = {0: "#28a745", 1: "#dc3545"}
25
+ EXTENSIONES_VALIDAS = [".png", ".jpg", ".jpeg"]
26
+ TAMANIO_MAXIMO_MB = 20
27
+ RESOLUCION = (224, 224)
28
+ DISPOSITIVO = "cuda" if torch.cuda.is_available() else "cpu"
29
+
30
+ # Transformaciones de imagen
31
+ TRANSFORMACION = transforms.Compose(
32
+ [
33
+ transforms.Resize(RESOLUCION),
34
+ transforms.ToTensor(),
35
+ transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),
36
+ ]
37
+ )
38
+
39
+
40
+ @dataclass
41
+ class ConfigModelo:
42
+ repo_id: str = "Carlos012/vit_papas"
43
+ nombre_archivo: str = "vit_papas_ft.pt"
44
+ num_clases: int = 2
45
+ modelo_base: str = "vit_base_patch16_224"
46
+
47
+
48
+ class ClasificadorPapasViT(nn.Module):
49
+ def __init__(self, num_clases: int = 2, modelo_base: str = "vit_base_patch16_224"):
50
+ super().__init__()
51
+ self.backbone = timm.create_model(
52
+ modelo_base, pretrained=False, num_classes=num_clases
53
+ )
54
+ # Inicialización Xavier para mejor convergencia
55
+ nn.init.xavier_uniform_(self.backbone.head.weight)
56
+ nn.init.zeros_(self.backbone.head.bias)
57
+
58
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
59
+ return self.backbone(x)
60
+
61
+
62
+ class GestorModelo:
63
+ def __init__(self, config: ConfigModelo):
64
+ self.config = config
65
+ self.modelo: Optional[ClasificadorPapasViT] = None
66
+ self.cam: Optional[EigenCAM] = None
67
+
68
+ def descargar_pesos(self) -> Optional[str]:
69
+ try:
70
+ # Intentar usar token de variable de entorno
71
+ token = os.getenv("HF_TOKEN")
72
+
73
+ ruta_modelo = hf_hub_download(
74
+ repo_id=self.config.repo_id,
75
+ filename=self.config.nombre_archivo,
76
+ token=token,
77
+ repo_type="model",
78
+ )
79
+ return ruta_modelo
80
+ except Exception as e:
81
+ print(f"Error al descargar modelo: {e}")
82
+ return None
83
+
84
+ def cargar_modelo(self) -> bool:
85
+ if self.modelo is not None:
86
+ return True
87
+
88
+ ruta_pesos = self.descargar_pesos()
89
+ if ruta_pesos is None:
90
+ return False
91
+
92
+ try:
93
+ # Inicializar modelo
94
+ self.modelo = ClasificadorPapasViT(
95
+ num_clases=self.config.num_clases, modelo_base=self.config.modelo_base
96
+ )
97
+
98
+ # Cargar pesos
99
+ checkpoint = torch.load(
100
+ ruta_pesos, map_location=DISPOSITIVO, weights_only=False
101
+ )
102
+
103
+ state_dict = checkpoint.get("modelo_state_dict", checkpoint)
104
+
105
+ # Limpiar prefijos si existen
106
+ if any(k.startswith("_orig_mod.") for k in state_dict.keys()):
107
+ state_dict = {
108
+ k.replace("_orig_mod.", ""): v for k, v in state_dict.items()
109
+ }
110
+
111
+ self.modelo.load_state_dict(state_dict, strict=False)
112
+ self.modelo.to(DISPOSITIVO)
113
+ self.modelo.eval()
114
+
115
+ # Configurar Grad-CAM
116
+ capas_objetivo = [self.modelo.backbone.blocks[-1].norm1]
117
+ self.cam = EigenCAM(
118
+ model=self.modelo.backbone,
119
+ target_layers=capas_objetivo,
120
+ reshape_transform=self._reshape_transform,
121
+ )
122
+
123
+ print(f"Modelo cargado exitosamente en {DISPOSITIVO}")
124
+ return True
125
+
126
+ except Exception as e:
127
+ print(f"Error al cargar modelo: {e}")
128
+ return False
129
+
130
+ @staticmethod
131
+ def _reshape_transform(tensor: torch.Tensor) -> torch.Tensor:
132
+ # Eliminar token CLS
133
+ resultado = tensor[:, 1:, :]
134
+
135
+ # Calcular tamaño de malla
136
+ tamanio_malla = int(np.sqrt(resultado.size(1)))
137
+
138
+ # Reorganizar: [Batch, Grid, Grid, Canales]
139
+ resultado = resultado.reshape(
140
+ tensor.size(0), tamanio_malla, tamanio_malla, tensor.size(2)
141
+ )
142
+
143
+ # Transponer para formato imagen: [Batch, Canales, Altura, Anchura]
144
+ resultado = resultado.transpose(2, 3).transpose(1, 2)
145
+ return resultado
146
+
147
+
148
+ class ProcesadorImagen:
149
+ def __init__(self, gestor: GestorModelo):
150
+ self.gestor = gestor
151
+
152
+ def validar_imagen(self, imagen_path: str) -> Tuple[bool, str]:
153
+ if imagen_path is None:
154
+ return False, "No se ha subido ninguna imagen"
155
+
156
+ # Validar extensión
157
+ extension = Path(imagen_path).suffix.lower()
158
+ if extension not in EXTENSIONES_VALIDAS:
159
+ return False, f"Formato no válido. Use: {', '.join(EXTENSIONES_VALIDAS)}"
160
+
161
+ # Validar tamaño
162
+ tamanio_mb = Path(imagen_path).stat().st_size / (1024 * 1024)
163
+ if tamanio_mb > TAMANIO_MAXIMO_MB:
164
+ return (
165
+ False,
166
+ f"Imagen muy grande ({tamanio_mb:.2f}MB). Máximo: {TAMANIO_MAXIMO_MB}MB",
167
+ )
168
+
169
+ return True, "OK"
170
+
171
+ def procesar(self, imagen_path: str) -> Dict:
172
+ tiempo_inicio = time.time()
173
+
174
+ # Validar imagen
175
+ valido, mensaje = self.validar_imagen(imagen_path)
176
+ if not valido:
177
+ return {"error": mensaje}
178
+
179
+ # Cargar modelo si es necesario
180
+ if self.gestor.modelo is None:
181
+ if not self.gestor.cargar_modelo():
182
+ return {"error": "No se pudo cargar el modelo"}
183
+
184
+ try:
185
+ # Cargar y preparar imagen
186
+ imagen = Image.open(imagen_path).convert("RGB")
187
+ imagen_redimensionada = imagen.resize(RESOLUCION)
188
+ tensor_imagen = TRANSFORMACION(imagen).unsqueeze(0).to(DISPOSITIVO)
189
+
190
+ # Predicción
191
+ with torch.no_grad():
192
+ salida = self.gestor.modelo(tensor_imagen)
193
+ probabilidades = torch.softmax(salida, dim=1)[0].cpu().numpy()
194
+
195
+ id_prediccion = int(np.argmax(probabilidades))
196
+ confianza = float(probabilidades[id_prediccion])
197
+
198
+ # Generar Grad-CAM
199
+ mapa_atencion = self.gestor.cam(input_tensor=tensor_imagen, targets=None)[
200
+ 0, :
201
+ ]
202
+
203
+ # Crear visualización
204
+ imagen_normalizada = np.float32(imagen_redimensionada) / 255.0
205
+ visualizacion_cam = show_cam_on_image(
206
+ imagen_normalizada, mapa_atencion, use_rgb=True
207
+ )
208
+
209
+ tiempo_total = time.time() - tiempo_inicio
210
+
211
+ return {
212
+ "clase_id": id_prediccion,
213
+ "clase_nombre": CLASES[id_prediccion],
214
+ "confianza": confianza,
215
+ "probabilidades": probabilidades,
216
+ "imagen_original": imagen_redimensionada,
217
+ "mapa_atencion": mapa_atencion,
218
+ "visualizacion_cam": visualizacion_cam,
219
+ "tiempo_procesamiento": tiempo_total,
220
+ }
221
+
222
+ except Exception as e:
223
+ return {"error": f"Error al procesar imagen: {str(e)}"}
224
+
225
+
226
+ # Instancia global del gestor
227
+ config = ConfigModelo()
228
+ gestor_modelo = GestorModelo(config)
229
+ procesador = ProcesadorImagen(gestor_modelo)
230
+
231
+
232
+ def predecir(imagen) -> Tuple:
233
+ if imagen is None:
234
+ return (
235
+ "⚠️ Por favor, sube una imagen",
236
+ None,
237
+ crear_html_error("No hay imagen para procesar"),
238
+ "",
239
+ )
240
+
241
+ # Procesar imagen
242
+ resultado = procesador.procesar(imagen)
243
+
244
+ # Manejar errores
245
+ if "error" in resultado:
246
+ return (
247
+ f"❌ Error: {resultado['error']}",
248
+ None,
249
+ crear_html_error(resultado["error"]),
250
+ "",
251
+ )
252
+
253
+ # Preparar resultados
254
+ clase_id = resultado["clase_id"]
255
+ clase_nombre = resultado["clase_nombre"]
256
+ confianza = resultado["confianza"]
257
+ probabilidades = resultado["probabilidades"]
258
+ vis_cam = resultado["visualizacion_cam"]
259
+ tiempo = resultado["tiempo_procesamiento"]
260
+
261
+ # Texto de predicción
262
+ emoji = "✅" if clase_id == 0 else "⚠️"
263
+ prediccion_texto = f"{emoji} **{clase_nombre}** (Confianza: {confianza*100:.2f}%)"
264
+
265
+ # HTML de confianzas por clase
266
+ html_confianzas = crear_html_confianzas(probabilidades)
267
+
268
+ # HTML de métricas
269
+ html_metricas = crear_html_metricas(tiempo, DISPOSITIVO)
270
+
271
+ return (prediccion_texto, vis_cam, html_confianzas, html_metricas)
272
+
273
+
274
+ def crear_html_confianzas(probabilidades: np.ndarray) -> str:
275
+ """Crea HTML para mostrar confianzas por clase"""
276
+ prob_buen_estado = probabilidades[0] * 100
277
+ prob_defectuoso = probabilidades[1] * 100
278
+
279
+ html = f"""
280
+ <div style="display: flex; gap: 20px; margin-top: 20px;">
281
+ <div style="flex: 1; background: linear-gradient(135deg, #d4edda 0%, #c3e6cb 100%);
282
+ padding: 24px; border-radius: 12px; border-left: 5px solid #28a745;
283
+ box-shadow: 0 4px 6px rgba(0,0,0,0.1);">
284
+ <div style="color: #155724; font-size: 14px; font-weight: 600;
285
+ text-transform: uppercase; letter-spacing: 1px; margin-bottom: 8px;">
286
+ Buen Estado
287
+ </div>
288
+ <div style="color: #28a745; font-size: 42px; font-weight: bold;
289
+ font-family: 'Segoe UI', sans-serif;">
290
+ {prob_buen_estado:.1f}%
291
+ </div>
292
+ <div style="background-color: rgba(40, 167, 69, 0.2); height: 8px;
293
+ border-radius: 4px; margin-top: 12px; overflow: hidden;">
294
+ <div style="background-color: #28a745; height: 100%;
295
+ width: {prob_buen_estado}%; transition: width 0.3s ease;"></div>
296
+ </div>
297
+ </div>
298
+
299
+ <div style="flex: 1; background: linear-gradient(135deg, #f8d7da 0%, #f5c6cb 100%);
300
+ padding: 24px; border-radius: 12px; border-left: 5px solid #dc3545;
301
+ box-shadow: 0 4px 6px rgba(0,0,0,0.1);">
302
+ <div style="color: #721c24; font-size: 14px; font-weight: 600;
303
+ text-transform: uppercase; letter-spacing: 1px; margin-bottom: 8px;">
304
+ Defectuoso
305
+ </div>
306
+ <div style="color: #dc3545; font-size: 42px; font-weight: bold;
307
+ font-family: 'Segoe UI', sans-serif;">
308
+ {prob_defectuoso:.1f}%
309
+ </div>
310
+ <div style="background-color: rgba(220, 53, 69, 0.2); height: 8px;
311
+ border-radius: 4px; margin-top: 12px; overflow: hidden;">
312
+ <div style="background-color: #dc3545; height: 100%;
313
+ width: {prob_defectuoso}%; transition: width 0.3s ease;"></div>
314
+ </div>
315
+ </div>
316
+ </div>
317
+ """
318
+ return html
319
+
320
+
321
+ def crear_html_metricas(tiempo: float, dispositivo: str) -> str:
322
+ icono_dispositivo = "🖥️" if dispositivo == "cpu" else "⚡"
323
+
324
+ html = f"""
325
+ <div style="display: flex; gap: 15px; margin-top: 15px; flex-wrap: wrap;">
326
+ <div style="background: linear-gradient(135deg, #e3f2fd 0%, #bbdefb 100%);
327
+ padding: 15px 20px; border-radius: 10px; flex: 1; min-width: 150px;
328
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
329
+ <div style="color: #1565c0; font-size: 12px; font-weight: 600;
330
+ margin-bottom: 5px;">⏱️ TIEMPO</div>
331
+ <div style="color: #0d47a1; font-size: 24px; font-weight: bold;">
332
+ {tiempo:.3f}s
333
+ </div>
334
+ </div>
335
+
336
+ <div style="background: linear-gradient(135deg, #f3e5f5 0%, #e1bee7 100%);
337
+ padding: 15px 20px; border-radius: 10px; flex: 1; min-width: 150px;
338
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
339
+ <div style="color: #6a1b9a; font-size: 12px; font-weight: 600;
340
+ margin-bottom: 5px;">{icono_dispositivo} DISPOSITIVO</div>
341
+ <div style="color: #4a148c; font-size: 24px; font-weight: bold;
342
+ text-transform: uppercase;">
343
+ {dispositivo}
344
+ </div>
345
+ </div>
346
+
347
+ <div style="background: linear-gradient(135deg, #fff3e0 0%, #ffe0b2 100%);
348
+ padding: 15px 20px; border-radius: 10px; flex: 1; min-width: 150px;
349
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
350
+ <div style="color: #e65100; font-size: 12px; font-weight: 600;
351
+ margin-bottom: 5px;">🧠 MODELO</div>
352
+ <div style="color: #bf360c; font-size: 20px; font-weight: bold;">
353
+ ViT-Base/16
354
+ </div>
355
+ </div>
356
+ </div>
357
+ """
358
+ return html
359
+
360
+
361
+ def crear_html_error(mensaje: str) -> str:
362
+ """Crea HTML para mostrar errores"""
363
+ html = f"""
364
+ <div style="background-color: #fff3cd; border: 2px solid #ffc107;
365
+ border-radius: 10px; padding: 20px; margin-top: 20px;">
366
+ <div style="color: #856404; font-size: 16px; font-weight: 600; margin-bottom: 10px;">
367
+ ⚠️ Advertencia
368
+ </div>
369
+ <div style="color: #856404; font-size: 14px;">
370
+ {mensaje}
371
+ </div>
372
+ </div>
373
+ """
374
+ return html
375
+
376
+
377
+ def crear_interfaz() -> gr.Blocks:
378
+ # CSS personalizado
379
+ css_personalizado = """
380
+ .gradio-container {
381
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif !important;
382
+ }
383
+ .resultado-principal {
384
+ font-size: 24px !important;
385
+ font-weight: bold !important;
386
+ padding: 20px !important;
387
+ border-radius: 10px !important;
388
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
389
+ color: white !important;
390
+ text-align: center !important;
391
+ }
392
+ footer {
393
+ display: none !important;
394
+ }
395
+ """
396
+
397
+ with gr.Blocks(
398
+ title="Clasificador de Papas (Chaucha y Chola) - ViT",
399
+ theme=gr.themes.Soft(primary_hue="blue", secondary_hue="green"),
400
+ css=css_personalizado,
401
+ ) as demo:
402
+
403
+ # Encabezado
404
+ gr.Markdown(
405
+ """
406
+ # 🥔 Clasificador de Calidad de Papas
407
+ ### Powered by Vision Transformer (ViT-Base/16)
408
+
409
+ Sube una imagen de papa (Chaucha o Chola) para clasificar su calidad en tiempo real.
410
+ El sistema utiliza un modelo de inteligencia artificial entrenado específicamente
411
+ para detectar defectos en tubérculos de papa.
412
+ """
413
+ )
414
+
415
+ with gr.Row():
416
+ with gr.Column(scale=1):
417
+ gr.Markdown("### 📤 Imagen de Entrada")
418
+ imagen_entrada = gr.Image(
419
+ label="Subir imagen de papa", type="filepath", height=400
420
+ )
421
+
422
+ gr.Markdown(
423
+ f"""
424
+ **Especificaciones:**
425
+ - Formatos: PNG, JPG, JPEG
426
+ - Tamaño máximo: {TAMANIO_MAXIMO_MB} MB
427
+ - Resolución: Se redimensiona a {RESOLUCION[0]}×{RESOLUCION[1]}
428
+ """
429
+ )
430
+
431
+ boton_predecir = gr.Button(
432
+ "🔍 Analizar Calidad", variant="primary", size="lg"
433
+ )
434
+ boton_limpiar = gr.ClearButton(
435
+ components=[imagen_entrada], value="🗑️ Limpiar", size="sm"
436
+ )
437
+
438
+ with gr.Column(scale=1):
439
+ gr.Markdown("### 📊 Resultados del Análisis")
440
+
441
+ resultado_texto = gr.Markdown(
442
+ value="Esperando imagen...", elem_classes=["resultado-principal"]
443
+ )
444
+
445
+ html_confianzas = gr.HTML(label="Probabilidades por Clase")
446
+
447
+ gr.Markdown("### 🔥 Mapa de Atención (Grad-CAM)")
448
+ imagen_gradcam = gr.Image(
449
+ label="Visualización de áreas de interés del modelo", height=400
450
+ )
451
+
452
+ html_metricas = gr.HTML(label="Métricas de Rendimiento")
453
+
454
+ # Info adicional
455
+ with gr.Accordion("ℹ️ Información del Sistema", open=False):
456
+ gr.Markdown(
457
+ f"""
458
+ ### Detalles Técnicos
459
+
460
+ - **Modelo**: Vision Transformer Base (ViT-Base/16)
461
+ - **Framework**: PyTorch + timm
462
+ - **Dispositivo**: {DISPOSITIVO.upper()}
463
+ - **Visualización**: EigenCAM (Grad-CAM para Transformers)
464
+ - **Clases**: Buen estado / Defectuoso
465
+
466
+ ### Interpretación del Mapa de Atención
467
+
468
+ El mapa de atención (Grad-CAM) muestra en **rojo** las regiones de la imagen
469
+ que más influyeron en la decisión del modelo. Esto ayuda a entender qué partes
470
+ del tubérculo está evaluando la red neuronal.
471
+
472
+ ### Sobre el Modelo
473
+
474
+ El clasificador ha sido entrenado con un dataset especializado de papas
475
+ Chaucha y Chola, capturando diversos tipos de defectos y condiciones de iluminación.
476
+ """
477
+ )
478
+
479
+ # Conectar eventos
480
+ boton_predecir.click(
481
+ fn=predecir,
482
+ inputs=[imagen_entrada],
483
+ outputs=[resultado_texto, imagen_gradcam, html_confianzas, html_metricas],
484
+ )
485
+
486
+ # También predecir al subir imagen
487
+ imagen_entrada.change(
488
+ fn=predecir,
489
+ inputs=[imagen_entrada],
490
+ outputs=[resultado_texto, imagen_gradcam, html_confianzas, html_metricas],
491
+ )
492
+
493
+ # Footer
494
+ gr.Markdown(
495
+ """
496
+ ---
497
+ **Nota**: Este sistema es una herramienta de apoyo. Para decisiones críticas
498
+ de calidad, consulte con un especialista en agronomía.
499
+ """
500
+ )
501
+
502
+ return demo
503
+
504
+
505
+ def main():
506
+ print("=" * 60)
507
+ print("🥔 Clasificador de Papas con Transformadores de Visión (ViT)")
508
+ print("=" * 60)
509
+ print(f"Dispositivo: {DISPOSITIVO.upper()}")
510
+ print(f"Resolución: {RESOLUCION}")
511
+ print("=" * 60)
512
+
513
+ # Pre-cargar modelo
514
+ print("\n📦 Cargando modelo...")
515
+ if gestor_modelo.cargar_modelo():
516
+ print("✅ Modelo cargado exitosamente\n")
517
+ else:
518
+ print("⚠️ El modelo se cargará cuando sea necesario\n")
519
+
520
+ # Crear y lanzar interfaz
521
+ demo = crear_interfaz()
522
+
523
+ demo.launch(
524
+ server_name="0.0.0.0",
525
+ server_port=7860,
526
+ share=False,
527
+ show_error=True,
528
+ quiet=False,
529
+ )
530
+
531
+
532
+ if __name__ == "__main__":
533
+ main()
configuracion.py ADDED
@@ -0,0 +1,173 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ===========================
2
+ # CONFIGURACIÓN DEL MODELO
3
+ # ===========================
4
+
5
+ HF_REPO_ID = "Carlos012/vit_papas"
6
+ HF_ARCHIVO_MODELO = "vit_papas_ft.pt"
7
+
8
+ # Arquitectura del modelo
9
+ ARQUITECTURA_MODELO = "vit_base_patch16_224"
10
+ NUM_CLASES = 2
11
+
12
+ # Etiquetas de las clases
13
+ ETIQUETAS_CLASE = {0: "Buen estado", 1: "Defectuoso"}
14
+
15
+ # Colores para visualización (formato hexadecimal)
16
+ COLORES_CLASES = {
17
+ 0: "#28a745", # Verde para buen estado
18
+ 1: "#dc3545", # Rojo para defectuoso
19
+ }
20
+
21
+ # ===========================
22
+ # CONFIGURACIÓN DE IMAGEN
23
+ # ===========================
24
+
25
+ # Resolución de entrada del modelo
26
+ RESOLUCION_IMAGEN = (224, 224)
27
+
28
+ # Tamaño máximo de archivo (en MB)
29
+ TAMANIO_MAXIMO_MB = 20
30
+
31
+ # Extensiones permitidas
32
+ EXTENSIONES_PERMITIDAS = [".png", ".jpg", ".jpeg"]
33
+
34
+ # Normalización (estadísticas de ImageNet)
35
+ MEDIA = [0.485, 0.456, 0.406]
36
+ DESVIACION_ESTANDAR = [0.229, 0.224, 0.225]
37
+
38
+ # ===========================
39
+ # CONFIGURACIÓN DE GRADIO
40
+ # ===========================
41
+
42
+ # Configuración del servidor
43
+ CONFIGURACION_SERVIDOR = {
44
+ "server_name": "0.0.0.0", # "0.0.0.0" para acceso en red, "127.0.0.1" solo local
45
+ "server_port": 7860, # Puerto del servidor
46
+ "share": False, # True para generar link público temporal
47
+ "debug": False, # True para modo debug
48
+ "show_error": True, # Mostrar errores detallados
49
+ "quiet": False, # True para menos logs
50
+ }
51
+
52
+ # Autenticación (opcional)
53
+ # Descomenta y configura para habilitar
54
+ # AUTH_CREDENTIALS = [
55
+ # ("usuario1", "contraseña1"),
56
+ # ("usuario2", "contraseña2")
57
+ # ]
58
+ CREDENCIALES_AUTENTICACION = None
59
+
60
+ # ===========================
61
+ # CONFIGURACIÓN DE RENDIMIENTO
62
+ # ===========================
63
+
64
+ # Dispositivo preferido ("cuda", "cpu", o "auto")
65
+ DISPOSITIVO_PREFERIDO = "auto"
66
+
67
+ # Usar modelo compilado (PyTorch 2.0+)
68
+ MODELO_COMPILADO = False
69
+
70
+ # Pre-cargar modelo al inicio
71
+ PRECARGAR_MODELO = True
72
+
73
+ # Número de threads para CPU
74
+ # None = detección automática
75
+ HILOS_CPU = None
76
+
77
+ # ===========================
78
+ # CONFIGURACIÓN DE GRAD-CAM
79
+ # ===========================
80
+
81
+ # Capa objetivo para Grad-CAM (None = última capa)
82
+ ETIQUETA_GRADCAM = None # Se usa automáticamente la última
83
+
84
+ # Transparencia de la superposición (0.0 a 1.0)
85
+ GRADCAM_ALPHA = 0.5
86
+
87
+ # Mapa de colores para Grad-CAM
88
+ # Opciones: COLORMAP_JET, COLORMAP_HOT, COLORMAP_VIRIDIS, etc.
89
+ COLORMAP_GRADCAM = "COLORMAP_JET"
90
+
91
+ # ===========================
92
+ # CONFIGURACIÓN DE UI
93
+ # ===========================
94
+
95
+ # Título de la aplicación
96
+ TITULO_APP = "🥔 Clasificador de Calidad de Papas"
97
+
98
+ # Descripción
99
+ DESCRIPCION_APP = """
100
+ ### Powered by Vision Transformer (ViT-Base/16)
101
+
102
+ Sube una imagen de papa (Chaucha o Chola) para clasificar su calidad en tiempo real.
103
+ El sistema utiliza un modelo de inteligencia artificial entrenado específicamente
104
+ para detectar defectos en tubérculos de papa.
105
+ """
106
+
107
+ # Tema de Gradio
108
+ # Opciones: "default", "soft", "monochrome", "glass"
109
+ TEMA_GRADIO = "soft"
110
+
111
+ # Color principal del tema (hue)
112
+ # Opciones: "red", "orange", "amber", "yellow", "lime", "green",
113
+ # "emerald", "teal", "cyan", "sky", "blue", "indigo",
114
+ # "violet", "purple", "fuchsia", "pink", "rose"
115
+ PRIMARIO_HUE = "blue"
116
+ SECUNDARIO_HUE = "green"
117
+
118
+ # Altura de las imágenes en la interfaz
119
+ ALTURA_IMAGEN = 400
120
+
121
+ # ===========================
122
+ # CONFIGURACIÓN DE LOGGING
123
+ # ===========================
124
+
125
+ # Nivel de logging
126
+ # Opciones: "DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"
127
+ NIVEL_REGISTRO = "INFO"
128
+
129
+ # Guardar logs en archivo
130
+ GUARDAR_REGISTROS = False
131
+ ARCHIVO_REGISTRO = "app_gradio.log"
132
+
133
+ # ===========================
134
+ # CONFIGURACIÓN AVANZADA
135
+ # ===========================
136
+
137
+ # Caché de resultados
138
+ ACTIVAR_CACHE = True
139
+
140
+ # Timeout para carga del modelo (segundos)
141
+ TIEMPO_CARGA_MODELO = 300
142
+
143
+ # Mostrar información técnica expandida
144
+ INFO_EXTENDIDA = True
145
+
146
+ # Habilitar ejemplos pre-cargados
147
+ # Proporciona una lista de rutas a imágenes de ejemplo
148
+ IMAGENES_EJEMPLO = []
149
+ # EXAMPLE_IMAGES = [
150
+ # "ejemplos/papa_buena_1.jpg",
151
+ # "ejemplos/papa_defectuosa_1.jpg",
152
+ # ]
153
+
154
+ # ===========================
155
+ # MENSAJES PERSONALIZADOS
156
+ # ===========================
157
+
158
+ MENSAJES = {
159
+ "esperando": "⏳ Esperando imagen...",
160
+ "procesando": "🔄 Procesando imagen...",
161
+ "error_en_imagen": "⚠️ Por favor, sube una imagen",
162
+ "error_cargar_modelo": "❌ Error al cargar el modelo",
163
+ "buen_estado": "✅ Tubérculo en buen estado",
164
+ "defectuoso": "⚠️ Tubérculo defectuoso",
165
+ }
166
+
167
+ # ===========================
168
+ # METADATOS
169
+ # ===========================
170
+
171
+ VERSION = "1.0.0"
172
+ AUTOR = "CEAS - Clasificador de Papas"
173
+ LISCNCIA = "MIT"
requirements.txt ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Framework de interfaz
2
+ gradio>=4.19.0
3
+
4
+ # Deep Learning
5
+ torch>=2.0.0
6
+ torchvision>=0.15.0
7
+ timm>=0.9.12
8
+
9
+ # Procesamiento de imágenes
10
+ opencv-python>=4.8.0
11
+ Pillow>=10.0.0
12
+ numpy>=1.24.0
13
+
14
+ # Grad-CAM para visualización
15
+ pytorch-grad-cam>=1.4.8
16
+
17
+ # Hugging Face Hub
18
+ huggingface-hub>=0.20.0
19
+
20
+ # Utilidades
21
+ typing-extensions>=4.8.0
22
+ dataclasses>=0.6