Spaces:
Paused
Paused
Upload 6 files
Browse files- docs/DEPLOYMENT.md +313 -0
- docs/WORKFLOW.md +455 -0
- test/images/AAA0119DNBSPD01.jpg +0 -0
- test/images/AAA0119DNBSPD02.jpg +0 -0
- test/images/AAA0119DNBSPD03.jpg +0 -0
- test/test_api_local.py +71 -0
docs/DEPLOYMENT.md
ADDED
|
@@ -0,0 +1,313 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Clasificación de Uso de Suelo de Bogotá - Guía de Despliegue
|
| 2 |
+
|
| 3 |
+
Esta guía proporciona instrucciones completas para desplegar la aplicación de Clasificación de Uso de Suelo de Bogotá en varias plataformas, con enfoque en el despliegue en Hugging Face Spaces usando aceleración GPU.
|
| 4 |
+
|
| 5 |
+
## Tabla de Contenidos
|
| 6 |
+
- [Resumen](#resumen)
|
| 7 |
+
- [Despliegue en Hugging Face Spaces](#despliegue-en-hugging-face-spaces)
|
| 8 |
+
- [Desarrollo Local](#desarrollo-local)
|
| 9 |
+
- [Despliegue con Docker](#despliegue-con-docker)
|
| 10 |
+
- [Variables de Entorno](#variables-de-entorno)
|
| 11 |
+
- [Solución de Problemas](#solución-de-problemas)
|
| 12 |
+
|
| 13 |
+
## Resumen
|
| 14 |
+
|
| 15 |
+
La aplicación de Clasificación de Uso de Suelo de Bogotá es un servicio web basado en FastAPI que clasifica el uso del suelo en imágenes satelitales de Bogotá utilizando modelos de aprendizaje profundo. La aplicación está diseñada para ejecutarse eficientemente en entornos acelerados por GPU.
|
| 16 |
+
|
| 17 |
+
## Despliegue en Hugging Face Spaces
|
| 18 |
+
|
| 19 |
+
### Prerrequisitos
|
| 20 |
+
- Cuenta de Hugging Face con acceso a Spaces
|
| 21 |
+
- Acceso a hardware GPU (recomendado: L40S o similar)
|
| 22 |
+
- Conocimiento básico de Docker y contenedorización
|
| 23 |
+
|
| 24 |
+
### Paso 1: Crear un Nuevo Space
|
| 25 |
+
|
| 26 |
+
1. Ve a [Hugging Face Spaces](https://huggingface.co/spaces)
|
| 27 |
+
2. Haz clic en "Create new Space"
|
| 28 |
+
3. Configura tu space:
|
| 29 |
+
- **Nombre del Space**: `clasificador-uso-suelo-bogota` (o tu nombre preferido)
|
| 30 |
+
- **Licencia**: MIT
|
| 31 |
+
- **SDK**: Docker
|
| 32 |
+
- **Hardware**: **GPU L40S** (48GB VRAM, alto rendimiento para aprendizaje profundo)
|
| 33 |
+
- **Visibilidad**: Público o Privado (según necesidad)
|
| 34 |
+
|
| 35 |
+
### Paso 2: Configurar Ajustes del Space
|
| 36 |
+
|
| 37 |
+
#### Configuración de Hardware
|
| 38 |
+
Selecciona la **GPU L40S** para rendimiento óptimo:
|
| 39 |
+
- **GPU**: NVIDIA L40S
|
| 40 |
+
- **VRAM**: 48GB
|
| 41 |
+
- **Núcleos CUDA**: 18,176
|
| 42 |
+
- **Ancho de Banda de Memoria**: 864 GB/s
|
| 43 |
+
- **Ideal para**: Modelos transformer grandes y tareas de visión por computadora
|
| 44 |
+
|
| 45 |
+
#### Archivo de Configuración del Space
|
| 46 |
+
Asegúrate de que tu `README.md` (en la raíz del space) contenga:
|
| 47 |
+
|
| 48 |
+
```yaml
|
| 49 |
+
---
|
| 50 |
+
title: Clasificador Uso de Suelo Bogotá
|
| 51 |
+
emoji: 🏙️
|
| 52 |
+
colorFrom: blue
|
| 53 |
+
colorTo: green
|
| 54 |
+
sdk: docker
|
| 55 |
+
pinned: false
|
| 56 |
+
license: mit
|
| 57 |
+
hardware: l40s
|
| 58 |
+
---
|
| 59 |
+
```
|
| 60 |
+
|
| 61 |
+
### Paso 3: Subir Archivos de la Aplicación
|
| 62 |
+
|
| 63 |
+
Sube los siguientes archivos a tu Hugging Face Space:
|
| 64 |
+
|
| 65 |
+
```
|
| 66 |
+
├── app.py # Aplicación principal FastAPI
|
| 67 |
+
├── classifier.py # Lógica de clasificación
|
| 68 |
+
├── inference.py # Pipeline de inferencia
|
| 69 |
+
├── model.py # Definiciones del modelo
|
| 70 |
+
├── types_io.py # Definiciones de tipos
|
| 71 |
+
├── requirements.txt # Dependencias de Python
|
| 72 |
+
├── Dockerfile # Configuración del contenedor
|
| 73 |
+
└── README.md # Configuración del space
|
| 74 |
+
```
|
| 75 |
+
|
| 76 |
+
### Paso 4: Configuración del Dockerfile
|
| 77 |
+
|
| 78 |
+
La aplicación usa Docker para contenedorización. El Dockerfile debe estar optimizado para uso de GPU:
|
| 79 |
+
|
| 80 |
+
```dockerfile
|
| 81 |
+
FROM python:3.10-slim
|
| 82 |
+
|
| 83 |
+
# Instalar dependencias del sistema
|
| 84 |
+
RUN apt-get update && apt-get install -y \
|
| 85 |
+
build-essential \
|
| 86 |
+
&& rm -rf /var/lib/apt/lists/*
|
| 87 |
+
|
| 88 |
+
# Establecer directorio de trabajo
|
| 89 |
+
WORKDIR /app
|
| 90 |
+
|
| 91 |
+
# Copiar requisitos e instalar dependencias
|
| 92 |
+
COPY requirements.txt .
|
| 93 |
+
RUN pip install --no-cache-dir -r requirements.txt
|
| 94 |
+
|
| 95 |
+
# Copiar código de la aplicación
|
| 96 |
+
COPY . .
|
| 97 |
+
|
| 98 |
+
# Exponer puerto
|
| 99 |
+
EXPOSE 7860
|
| 100 |
+
|
| 101 |
+
# Ejecutar la aplicación
|
| 102 |
+
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
|
| 103 |
+
```
|
| 104 |
+
|
| 105 |
+
### Paso 5: Configuración del Entorno
|
| 106 |
+
|
| 107 |
+
#### Optimización GPU
|
| 108 |
+
La GPU L40S proporciona excelente rendimiento para esta aplicación:
|
| 109 |
+
- **CUDA**: Detectado automáticamente y utilizado por PyTorch
|
| 110 |
+
- **Memoria**: 48GB VRAM permite procesamiento en lotes grandes
|
| 111 |
+
- **Capacidad de Cómputo**: 8.9 (arquitectura Ampere)
|
| 112 |
+
|
| 113 |
+
#### Dependencias
|
| 114 |
+
Dependencias clave optimizadas para aceleración GPU:
|
| 115 |
+
```
|
| 116 |
+
torch>=2.7.0 # PyTorch con soporte CUDA
|
| 117 |
+
torchvision>=0.22.0 # Utilidades de visión por computadora
|
| 118 |
+
transformers>=4.52.3 # Transformers de Hugging Face
|
| 119 |
+
accelerate>=1.7.0 # Utilidades de entrenamiento distribuido
|
| 120 |
+
```
|
| 121 |
+
|
| 122 |
+
### Paso 6: Desplegar y Monitorear
|
| 123 |
+
|
| 124 |
+
1. **Subir al Space**: Sube todos los archivos a tu Hugging Face Space
|
| 125 |
+
2. **Proceso de Construcción**: El space construirá automáticamente el contenedor Docker
|
| 126 |
+
3. **Asignación de GPU**: La GPU L40S será asignada durante el inicio
|
| 127 |
+
4. **Monitorear Logs**: Revisa los logs del space para cualquier problema de despliegue
|
| 128 |
+
|
| 129 |
+
#### Tiempo de Inicio Esperado
|
| 130 |
+
- **Construcción del Contenedor**: 5-10 minutos
|
| 131 |
+
- **Carga del Modelo**: 2-3 minutos en L40S
|
| 132 |
+
- **API Lista**: Total ~7-13 minutos
|
| 133 |
+
|
| 134 |
+
### Paso 7: Uso de la API
|
| 135 |
+
|
| 136 |
+
Una vez desplegada, tu aplicación estará disponible en:
|
| 137 |
+
```
|
| 138 |
+
https://huggingface.co/spaces/[USUARIO]/[NOMBRE_SPACE]
|
| 139 |
+
```
|
| 140 |
+
|
| 141 |
+
#### Endpoints de la API
|
| 142 |
+
- `POST /classify`: Endpoint principal de clasificación
|
| 143 |
+
- `GET /health`: Endpoint de verificación de salud
|
| 144 |
+
- `GET /docs`: Documentación interactiva de la API
|
| 145 |
+
|
| 146 |
+
#### Ejemplo de Uso
|
| 147 |
+
```python
|
| 148 |
+
import requests
|
| 149 |
+
import base64
|
| 150 |
+
|
| 151 |
+
# Codificar imagen a base64
|
| 152 |
+
with open("imagen_satelital.jpg", "rb") as f:
|
| 153 |
+
imagen_b64 = base64.b64encode(f.read()).decode()
|
| 154 |
+
|
| 155 |
+
# Hacer petición a la API
|
| 156 |
+
response = requests.post(
|
| 157 |
+
"https://huggingface.co/spaces/[USUARIO]/[NOMBRE_SPACE]/classify",
|
| 158 |
+
json={
|
| 159 |
+
"images": [
|
| 160 |
+
{
|
| 161 |
+
"data": imagen_b64,
|
| 162 |
+
"format": "base64"
|
| 163 |
+
}
|
| 164 |
+
]
|
| 165 |
+
}
|
| 166 |
+
)
|
| 167 |
+
|
| 168 |
+
resultados = response.json()
|
| 169 |
+
print(f"Clasificación: {resultados}")
|
| 170 |
+
```
|
| 171 |
+
|
| 172 |
+
### Beneficios de Rendimiento GPU
|
| 173 |
+
|
| 174 |
+
Usar la GPU L40S proporciona ventajas significativas:
|
| 175 |
+
|
| 176 |
+
#### Métricas de Rendimiento
|
| 177 |
+
- **Velocidad de Inferencia**: ~40-50seg por imagen (vs 40-50min en CPU)
|
| 178 |
+
- **Procesamiento en Lotes**: Puede procesar múltiples imágenes simultáneamente
|
| 179 |
+
- **Memoria**: 48GB permite modelos grandes y tamaños de lote grandes
|
| 180 |
+
- **Rendimiento**: ~60-80 imágenes/hora dependiendo de la complejidad del modelo.
|
| 181 |
+
|
| 182 |
+
#### Optimización de Costos
|
| 183 |
+
- **Procesamiento Eficiente**: Inferencia más rápida reduce tiempo de cómputo
|
| 184 |
+
- **Operaciones en Lotes**: Procesa múltiples imágenes en paralelo
|
| 185 |
+
- **Auto-escalado**: Hugging Face gestiona la asignación de recursos
|
| 186 |
+
|
| 187 |
+
## Desarrollo Local
|
| 188 |
+
|
| 189 |
+
### Configuración
|
| 190 |
+
```bash
|
| 191 |
+
# Clonar repositorio
|
| 192 |
+
git clone <url-repositorio>
|
| 193 |
+
cd bogota_land_use_final
|
| 194 |
+
|
| 195 |
+
# Instalar dependencias
|
| 196 |
+
pip install -r requirements.txt
|
| 197 |
+
|
| 198 |
+
# Ejecutar aplicación
|
| 199 |
+
uvicorn app:app --reload --host 0.0.0.0 --port 8000
|
| 200 |
+
```
|
| 201 |
+
|
| 202 |
+
### Requisitos GPU (Local)
|
| 203 |
+
- GPU NVIDIA con soporte CUDA
|
| 204 |
+
- CUDA 11.8 o posterior
|
| 205 |
+
- cuDNN 8.7 o posterior
|
| 206 |
+
- Al menos 8GB VRAM (16GB+ recomendado)
|
| 207 |
+
|
| 208 |
+
## Despliegue con Docker
|
| 209 |
+
|
| 210 |
+
### Construir y Ejecutar
|
| 211 |
+
```bash
|
| 212 |
+
# Construir imagen
|
| 213 |
+
docker build -t bogota-land-use .
|
| 214 |
+
|
| 215 |
+
# Ejecutar contenedor (soporte GPU)
|
| 216 |
+
docker run --gpus all -p 7860:7860 bogota-land-use
|
| 217 |
+
|
| 218 |
+
# Ejecutar contenedor (solo CPU)
|
| 219 |
+
docker run -p 7860:7860 bogota-land-use
|
| 220 |
+
```
|
| 221 |
+
|
| 222 |
+
### Docker Compose
|
| 223 |
+
```yaml
|
| 224 |
+
version: '3.8'
|
| 225 |
+
services:
|
| 226 |
+
app:
|
| 227 |
+
build: .
|
| 228 |
+
ports:
|
| 229 |
+
- "7860:7860"
|
| 230 |
+
deploy:
|
| 231 |
+
resources:
|
| 232 |
+
reservations:
|
| 233 |
+
devices:
|
| 234 |
+
- driver: nvidia
|
| 235 |
+
count: 1
|
| 236 |
+
capabilities: [gpu]
|
| 237 |
+
```
|
| 238 |
+
|
| 239 |
+
## Variables de Entorno
|
| 240 |
+
|
| 241 |
+
Configura estas variables de entorno para rendimiento óptimo:
|
| 242 |
+
|
| 243 |
+
```bash
|
| 244 |
+
# Configuración GPU
|
| 245 |
+
CUDA_VISIBLE_DEVICES=0
|
| 246 |
+
TORCH_CUDA_ARCH_LIST="8.9" # Arquitectura L40S
|
| 247 |
+
|
| 248 |
+
# Configuraciones de la Aplicación
|
| 249 |
+
MODEL_CACHE_DIR="/tmp/model_cache"
|
| 250 |
+
MAX_BATCH_SIZE=16
|
| 251 |
+
INFERENCE_TIMEOUT=30
|
| 252 |
+
|
| 253 |
+
# Ajuste de Rendimiento
|
| 254 |
+
OMP_NUM_THREADS=4
|
| 255 |
+
TORCH_NUM_THREADS=4
|
| 256 |
+
```
|
| 257 |
+
|
| 258 |
+
## Solución de Problemas
|
| 259 |
+
|
| 260 |
+
### Problemas Comunes
|
| 261 |
+
|
| 262 |
+
#### GPU No Detectada
|
| 263 |
+
```bash
|
| 264 |
+
# Verificar disponibilidad de GPU
|
| 265 |
+
python -c "import torch; print(torch.cuda.is_available())"
|
| 266 |
+
python -c "import torch; print(torch.cuda.device_count())"
|
| 267 |
+
```
|
| 268 |
+
|
| 269 |
+
#### Problemas de Memoria
|
| 270 |
+
- Reducir tamaño de lote en inferencia
|
| 271 |
+
- Habilitar gradient checkpointing
|
| 272 |
+
- Usar entrenamiento de precisión mixta
|
| 273 |
+
|
| 274 |
+
#### Inferencia Lenta
|
| 275 |
+
- Verificar utilización de GPU
|
| 276 |
+
- Revisar cuantización del modelo
|
| 277 |
+
- Optimizar preprocesamiento de imágenes
|
| 278 |
+
|
| 279 |
+
### Monitoreo de Rendimiento
|
| 280 |
+
|
| 281 |
+
#### Monitoreo de GPU
|
| 282 |
+
```bash
|
| 283 |
+
# Monitorear uso de GPU
|
| 284 |
+
nvidia-smi -l 1
|
| 285 |
+
|
| 286 |
+
# Verificar versión de CUDA
|
| 287 |
+
nvcc --version
|
| 288 |
+
```
|
| 289 |
+
|
| 290 |
+
#### Logs de la Aplicación
|
| 291 |
+
Revisar logs del Hugging Face Space para:
|
| 292 |
+
- Tiempo de carga del modelo
|
| 293 |
+
- Rendimiento de inferencia
|
| 294 |
+
- Uso de memoria
|
| 295 |
+
- Mensajes de error
|
| 296 |
+
|
| 297 |
+
### Soporte
|
| 298 |
+
|
| 299 |
+
Para problemas con:
|
| 300 |
+
- **Hugging Face Spaces**: Consulta [documentación de Hugging Face](https://huggingface.co/docs/hub/spaces)
|
| 301 |
+
- **Configuración GPU**: Verifica instalación y compatibilidad de CUDA
|
| 302 |
+
- **Lógica de la Aplicación**: Revisa logs de la aplicación y mensajes de error
|
| 303 |
+
|
| 304 |
+
## Recursos Adicionales
|
| 305 |
+
|
| 306 |
+
- [Documentación de Hugging Face Spaces](https://huggingface.co/docs/hub/spaces)
|
| 307 |
+
- [Especificaciones GPU L40S](https://www.nvidia.com/en-us/data-center/l40s/)
|
| 308 |
+
- [Documentación GPU PyTorch](https://pytorch.org/docs/stable/notes/cuda.html)
|
| 309 |
+
- [Documentación FastAPI](https://fastapi.tiangolo.com/)
|
| 310 |
+
|
| 311 |
+
---
|
| 312 |
+
|
| 313 |
+
*Última actualización: 13 de julio de 2025*
|
docs/WORKFLOW.md
ADDED
|
@@ -0,0 +1,455 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Clasificación de Uso de Suelo de Bogotá - Flujo de Código
|
| 2 |
+
|
| 3 |
+
Este documento explica la arquitectura técnica y el flujo de código de la aplicación de Clasificación de Uso de Suelo de Bogotá, proporcionando una visión detallada de cómo interactúan los diferentes componentes del sistema.
|
| 4 |
+
|
| 5 |
+
## Tabla de Contenidos
|
| 6 |
+
- [Resumen de Arquitectura](#resumen-de-arquitectura)
|
| 7 |
+
- [Diagrama de Flujo del Sistema](#diagrama-de-flujo-del-sistema)
|
| 8 |
+
- [Componentes Principales](#componentes-principales)
|
| 9 |
+
- [Flujo de Datos Detallado](#flujo-de-datos-detallado)
|
| 10 |
+
- [Modelo de Datos](#modelo-de-datos)
|
| 11 |
+
- [Manejo de Errores](#manejo-de-errores)
|
| 12 |
+
- [Optimizaciones de Rendimiento](#optimizaciones-de-rendimiento)
|
| 13 |
+
- [Patrones de Diseño](#patrones-de-diseño)
|
| 14 |
+
|
| 15 |
+
## Resumen de Arquitectura
|
| 16 |
+
|
| 17 |
+
La aplicación sigue una arquitectura de microservicios basada en FastAPI con separación clara de responsabilidades:
|
| 18 |
+
|
| 19 |
+
- **Capa de API**: Manejo de peticiones HTTP y validación de entrada
|
| 20 |
+
- **Capa de Procesamiento**: Preprocesamiento de imágenes y orquestación
|
| 21 |
+
- **Capa de Inferencia**: Clasificación usando modelos de aprendizaje profundo
|
| 22 |
+
- **Capa de Modelo**: Gestión del modelo de transformers y procesador
|
| 23 |
+
|
| 24 |
+
## Diagrama de Flujo del Sistema
|
| 25 |
+
|
| 26 |
+
```mermaid
|
| 27 |
+
graph TD
|
| 28 |
+
A[Cliente HTTP] -->|POST /classify| B[FastAPI App]
|
| 29 |
+
B --> C{Validación de Request}
|
| 30 |
+
C -->|Error| D[HTTP 400 Error]
|
| 31 |
+
C -->|Válido| E[decode_base64_image]
|
| 32 |
+
|
| 33 |
+
E --> F[save_images_to_disk]
|
| 34 |
+
F --> G[Inference.classify_building]
|
| 35 |
+
G --> H[Classifier.get_response]
|
| 36 |
+
|
| 37 |
+
H --> I[get_input_tensor]
|
| 38 |
+
I --> J[resize_image]
|
| 39 |
+
J --> K[prepare_messages]
|
| 40 |
+
|
| 41 |
+
K --> L[Model.load_model]
|
| 42 |
+
L --> M[Model.load_processor]
|
| 43 |
+
|
| 44 |
+
M --> N[generate_model_response]
|
| 45 |
+
N --> O[processor.apply_chat_template]
|
| 46 |
+
O --> P[model.generate]
|
| 47 |
+
P --> Q[processor.batch_decode]
|
| 48 |
+
|
| 49 |
+
Q --> R[Respuesta JSON]
|
| 50 |
+
R --> S[Cliente HTTP]
|
| 51 |
+
|
| 52 |
+
style B fill:#e1f5fe
|
| 53 |
+
style H fill:#f3e5f5
|
| 54 |
+
style L fill:#e8f5e8
|
| 55 |
+
style N fill:#fff3e0
|
| 56 |
+
```
|
| 57 |
+
|
| 58 |
+
### Flujo Alternativo de Errores
|
| 59 |
+
|
| 60 |
+
```mermaid
|
| 61 |
+
graph TD
|
| 62 |
+
A[Error en cualquier etapa] --> B{Tipo de Error}
|
| 63 |
+
B -->|Validación| C[HTTP 400 - Bad Request]
|
| 64 |
+
B -->|Procesamiento| D[HTTP 500 - Internal Error]
|
| 65 |
+
B -->|Modelo| E[HTTP 500 - Model Error]
|
| 66 |
+
|
| 67 |
+
C --> F[Log de Error]
|
| 68 |
+
D --> F
|
| 69 |
+
E --> F
|
| 70 |
+
F --> G[Respuesta de Error al Cliente]
|
| 71 |
+
```
|
| 72 |
+
|
| 73 |
+
## Componentes Principales
|
| 74 |
+
|
| 75 |
+
### 1. app.py - Capa de API (Entrada del Sistema)
|
| 76 |
+
|
| 77 |
+
**Responsabilidades:**
|
| 78 |
+
- Manejo de peticiones HTTP
|
| 79 |
+
- Validación de entrada
|
| 80 |
+
- Decodificación de imágenes base64
|
| 81 |
+
- Guardado temporal de imágenes
|
| 82 |
+
- Orquestación del flujo principal
|
| 83 |
+
|
| 84 |
+
**Funciones clave:**
|
| 85 |
+
```python
|
| 86 |
+
def decode_base64_image(base64_str: str) -> Optional[Image.Image]
|
| 87 |
+
def save_images_to_disk(images: List[Image.Image], output_dir: str) -> List[str]
|
| 88 |
+
async def classify(request: ClassificationRequest) -> dict
|
| 89 |
+
```
|
| 90 |
+
|
| 91 |
+
**Flujo de datos:**
|
| 92 |
+
1. Recibe request con imágenes en base64
|
| 93 |
+
2. Valida formato usando Pydantic
|
| 94 |
+
3. Decodifica cada imagen base64 a PIL.Image
|
| 95 |
+
4. Guarda imágenes temporalmente en disco
|
| 96 |
+
5. Delega clasificación a la capa de inferencia
|
| 97 |
+
6. Retorna respuesta JSON estructurada
|
| 98 |
+
|
| 99 |
+
### 2. inference.py - Capa de Procesamiento
|
| 100 |
+
|
| 101 |
+
**Responsabilidades:**
|
| 102 |
+
- Orquestación entre API y clasificador
|
| 103 |
+
- Preprocesamiento de imágenes
|
| 104 |
+
- Conversión de formatos de imagen
|
| 105 |
+
- Logging de operaciones
|
| 106 |
+
|
| 107 |
+
**Funciones clave:**
|
| 108 |
+
```python
|
| 109 |
+
def _prepare_images(self, images: List[Image.Image]) -> List[Image.Image]
|
| 110 |
+
def classify_building(self, images: List[Image.Image], saved_image_paths: List[str]) -> dict
|
| 111 |
+
```
|
| 112 |
+
|
| 113 |
+
**Flujo de datos:**
|
| 114 |
+
1. Recibe lista de objetos PIL.Image
|
| 115 |
+
2. Convierte todas las imágenes a formato RGB
|
| 116 |
+
3. Valida integridad de cada imagen
|
| 117 |
+
4. Delega clasificación al componente Classifier
|
| 118 |
+
5. Retorna respuesta estructurada
|
| 119 |
+
|
| 120 |
+
### 3. classifier.py - Lógica de Clasificación
|
| 121 |
+
|
| 122 |
+
**Responsabilidades:**
|
| 123 |
+
- Gestión del pipeline de clasificación
|
| 124 |
+
- Redimensionamiento de imágenes
|
| 125 |
+
- Preparación de mensajes para el modelo
|
| 126 |
+
- Generación de respuestas del modelo
|
| 127 |
+
- Aplicación de plantillas de chat
|
| 128 |
+
|
| 129 |
+
**Funciones clave:**
|
| 130 |
+
```python
|
| 131 |
+
def get_response(self, images: List[Image.Image], saved_image_paths: List[str]) -> dict
|
| 132 |
+
def get_input_tensor(self, images: List[Image.Image]) -> List[Image.Image]
|
| 133 |
+
def generate_model_response(self, images: List[Image.Image], messages: List[dict]) -> str
|
| 134 |
+
def resize_image(image: Image.Image, max_size: int = 224) -> Image.Image
|
| 135 |
+
def prepare_messages(saved_image_paths: List[str]) -> List[dict]
|
| 136 |
+
```
|
| 137 |
+
|
| 138 |
+
**Prompt del sistema:**
|
| 139 |
+
- Define 20 categorías de uso de suelo específicas para Bogotá
|
| 140 |
+
- Requiere mínimo 3 categorías por clasificación
|
| 141 |
+
- Incluye scores de confianza (0-1)
|
| 142 |
+
- Formato de salida JSON estructurado
|
| 143 |
+
|
| 144 |
+
### 4. model.py - Gestión del Modelo
|
| 145 |
+
|
| 146 |
+
**Responsabilidades:**
|
| 147 |
+
- Carga única del modelo y procesador (Singleton)
|
| 148 |
+
- Configuración de parámetros del modelo
|
| 149 |
+
- Gestión de memoria y recursos GPU
|
| 150 |
+
- Abstracción del modelo Kimi-VL-A3B-Thinking
|
| 151 |
+
|
| 152 |
+
**Funciones clave:**
|
| 153 |
+
```python
|
| 154 |
+
@classmethod
|
| 155 |
+
def load(cls) -> Tuple[AutoModelForCausalLM, AutoProcessor]
|
| 156 |
+
@classmethod
|
| 157 |
+
def load_model(cls) -> AutoModelForCausalLM
|
| 158 |
+
@classmethod
|
| 159 |
+
def load_processor(cls) -> AutoProcessor
|
| 160 |
+
```
|
| 161 |
+
|
| 162 |
+
**Configuración del modelo:**
|
| 163 |
+
```python
|
| 164 |
+
MODEL_PATH = "moonshotai/Kimi-VL-A3B-Thinking-2506"
|
| 165 |
+
model_kwargs = {
|
| 166 |
+
"device_map": "auto",
|
| 167 |
+
"torch_dtype": "auto",
|
| 168 |
+
"trust_remote_code": True
|
| 169 |
+
}
|
| 170 |
+
```
|
| 171 |
+
|
| 172 |
+
### 5. types_io.py - Definiciones de Tipos
|
| 173 |
+
|
| 174 |
+
**Responsabilidades:**
|
| 175 |
+
- Validación de datos de entrada y salida
|
| 176 |
+
- Definición de esquemas JSON
|
| 177 |
+
- Enumeración de categorías de clasificación
|
| 178 |
+
- Restricciones de formato y rangos
|
| 179 |
+
|
| 180 |
+
**Modelos principales:**
|
| 181 |
+
```python
|
| 182 |
+
class ClassificationRequest(BaseModel) # Entrada de la API
|
| 183 |
+
class ImageData(BaseModel) # Salida de la clasificación
|
| 184 |
+
class ImageTag(BaseModel) # Tags individuales
|
| 185 |
+
class TagType(Enum) # Categorías de uso de suelo
|
| 186 |
+
```
|
| 187 |
+
|
| 188 |
+
## Flujo de Datos Detallado
|
| 189 |
+
|
| 190 |
+
### Fase 1: Recepción y Validación (app.py)
|
| 191 |
+
```
|
| 192 |
+
Cliente → POST /classify → FastAPI → Pydantic Validation
|
| 193 |
+
↓
|
| 194 |
+
ClassificationRequest {
|
| 195 |
+
images: List[str] # base64 strings
|
| 196 |
+
}
|
| 197 |
+
```
|
| 198 |
+
|
| 199 |
+
### Fase 2: Decodificación y Almacenamiento (app.py)
|
| 200 |
+
```
|
| 201 |
+
List[str] → decode_base64_image() → List[PIL.Image]
|
| 202 |
+
↓
|
| 203 |
+
save_images_to_disk() → List[str] # file paths
|
| 204 |
+
```
|
| 205 |
+
|
| 206 |
+
### Fase 3: Preprocesamiento (inference.py)
|
| 207 |
+
```
|
| 208 |
+
List[PIL.Image] → _prepare_images() → List[PIL.Image] # RGB converted
|
| 209 |
+
↓
|
| 210 |
+
Validación de integridad
|
| 211 |
+
```
|
| 212 |
+
|
| 213 |
+
### Fase 4: Clasificación (classifier.py)
|
| 214 |
+
```
|
| 215 |
+
List[PIL.Image] → get_input_tensor() → resize_image() → List[PIL.Image] # 224x224
|
| 216 |
+
↓
|
| 217 |
+
List[str] # paths → prepare_messages() → List[dict] # chat format
|
| 218 |
+
↓
|
| 219 |
+
generate_model_response() → modelo transformer → str # JSON response
|
| 220 |
+
```
|
| 221 |
+
|
| 222 |
+
### Fase 5: Carga del Modelo (model.py)
|
| 223 |
+
```
|
| 224 |
+
Kimi-VL-A3B-Thinking-2506 → AutoModelForCausalLM.from_pretrained()
|
| 225 |
+
↓
|
| 226 |
+
device_map="auto" # GPU allocation
|
| 227 |
+
↓
|
| 228 |
+
torch_dtype="auto" # Mixed precision
|
| 229 |
+
```
|
| 230 |
+
|
| 231 |
+
### Fase 6: Generación de Respuesta
|
| 232 |
+
```
|
| 233 |
+
Imágenes + Prompt → processor.apply_chat_template() → model.generate()
|
| 234 |
+
↓
|
| 235 |
+
processor.batch_decode() → JSON string → ImageData
|
| 236 |
+
```
|
| 237 |
+
|
| 238 |
+
## Modelo de Datos
|
| 239 |
+
|
| 240 |
+
### Estructura de Entrada
|
| 241 |
+
```json
|
| 242 |
+
{
|
| 243 |
+
"images": [
|
| 244 |
+
"data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD...",
|
| 245 |
+
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB..."
|
| 246 |
+
]
|
| 247 |
+
}
|
| 248 |
+
```
|
| 249 |
+
|
| 250 |
+
### Estructura de Salida
|
| 251 |
+
```json
|
| 252 |
+
{
|
| 253 |
+
"output": {
|
| 254 |
+
"classification": [
|
| 255 |
+
{
|
| 256 |
+
"category": "Residenciales",
|
| 257 |
+
"confidence": 0.92
|
| 258 |
+
},
|
| 259 |
+
{
|
| 260 |
+
"category": "Comerciales1",
|
| 261 |
+
"confidence": 0.65
|
| 262 |
+
},
|
| 263 |
+
{
|
| 264 |
+
"category": "Moles",
|
| 265 |
+
"confidence": 0.33
|
| 266 |
+
}
|
| 267 |
+
],
|
| 268 |
+
"think": "Esta edificación presenta características principalmente residenciales..."
|
| 269 |
+
}
|
| 270 |
+
}
|
| 271 |
+
```
|
| 272 |
+
|
| 273 |
+
### Categorías de Clasificación
|
| 274 |
+
|
| 275 |
+
| Categoría | Descripción | Ejemplo |
|
| 276 |
+
|-----------|-------------|---------|
|
| 277 |
+
| **Residenciales** | Edificios para vivienda | Casas, edificios PH, condominios |
|
| 278 |
+
| **Comerciales1-5** | Diferentes tipos comerciales | Tiendas, oficinas, hoteles, talleres |
|
| 279 |
+
| **Centros_Comerciales** | Complejos comerciales | Centros comerciales, plazas |
|
| 280 |
+
| **Bodegas** | Almacenamiento | Bodegas industriales y comerciales |
|
| 281 |
+
| **Parqueaderos** | Estacionamientos | Edificios de parqueo |
|
| 282 |
+
| **Dotacionales1-5** | Servicios públicos | Escuelas, hospitales, iglesias |
|
| 283 |
+
| **Especiales** | Usos especiales | Áreas militares, cementerios |
|
| 284 |
+
| **Moles** | Grandes edificios | >4 pisos o >10,000 m² |
|
| 285 |
+
| **Rurales** | Construcciones rurales | Galpones, silos, establos |
|
| 286 |
+
| **Mixto1-3** | Usos combinados | Residencial+Comercial |
|
| 287 |
+
|
| 288 |
+
## Manejo de Errores
|
| 289 |
+
|
| 290 |
+
### Tipos de Errores y Estrategias
|
| 291 |
+
|
| 292 |
+
#### 1. Errores de Validación (HTTP 400)
|
| 293 |
+
```python
|
| 294 |
+
# En app.py
|
| 295 |
+
try:
|
| 296 |
+
images = []
|
| 297 |
+
for img_str in request.images:
|
| 298 |
+
img = decode_base64_image(img_str)
|
| 299 |
+
if img is None:
|
| 300 |
+
raise ValueError("Invalid base64 image")
|
| 301 |
+
images.append(img)
|
| 302 |
+
except ValueError as ve:
|
| 303 |
+
raise HTTPException(status_code=400, detail=str(ve))
|
| 304 |
+
```
|
| 305 |
+
|
| 306 |
+
#### 2. Errores de Procesamiento (HTTP 500)
|
| 307 |
+
```python
|
| 308 |
+
# En classifier.py
|
| 309 |
+
try:
|
| 310 |
+
img = self.resize_image(img)
|
| 311 |
+
processed_images.append(img)
|
| 312 |
+
except Exception as e:
|
| 313 |
+
logger.error(f"Error processing image at index {idx}: {str(e)}")
|
| 314 |
+
raise
|
| 315 |
+
```
|
| 316 |
+
|
| 317 |
+
#### 3. Errores del Modelo (HTTP 500)
|
| 318 |
+
```python
|
| 319 |
+
# En model.py
|
| 320 |
+
try:
|
| 321 |
+
cls._model = cls.model_class.from_pretrained(
|
| 322 |
+
cls.MODEL_PATH, **cls.model_kwargs
|
| 323 |
+
)
|
| 324 |
+
except Exception as e:
|
| 325 |
+
logger.error(f"Failed to load model: {str(e)}")
|
| 326 |
+
raise
|
| 327 |
+
```
|
| 328 |
+
|
| 329 |
+
### Sistema de Logging
|
| 330 |
+
```python
|
| 331 |
+
logging.basicConfig(
|
| 332 |
+
level=logging.INFO,
|
| 333 |
+
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
| 334 |
+
)
|
| 335 |
+
```
|
| 336 |
+
|
| 337 |
+
**Niveles de logging utilizados:**
|
| 338 |
+
- `INFO`: Operaciones principales y flujo normal
|
| 339 |
+
- `DEBUG`: Información detallada para debugging
|
| 340 |
+
- `ERROR`: Errores que requieren atención
|
| 341 |
+
- `WARNING`: Situaciones anómalas pero no críticas
|
| 342 |
+
|
| 343 |
+
## Optimizaciones de Rendimiento
|
| 344 |
+
|
| 345 |
+
### 1. Gestión de Memoria
|
| 346 |
+
- **Singleton para modelo**: Carga única en `model.py`
|
| 347 |
+
- **Auto device mapping**: `device_map="auto"` para distribución GPU óptima
|
| 348 |
+
- **Torch dtype automático**: `torch_dtype="auto"` para precisión mixta
|
| 349 |
+
|
| 350 |
+
### 2. Procesamiento de Imágenes
|
| 351 |
+
- **Redimensionamiento inteligente**: Mantiene aspect ratio, reduce a 224x224
|
| 352 |
+
- **Conversión RGB**: Garantiza compatibilidad del formato
|
| 353 |
+
- **Guardado temporal**: Optimiza memoria usando rutas de archivo
|
| 354 |
+
|
| 355 |
+
### 3. Batch Processing
|
| 356 |
+
```python
|
| 357 |
+
# En classifier.py
|
| 358 |
+
inputs = self.processor(
|
| 359 |
+
images=images,
|
| 360 |
+
text=text,
|
| 361 |
+
return_tensors="pt",
|
| 362 |
+
padding=True, # Padding para batch processing
|
| 363 |
+
truncation=True # Truncación para consistencia
|
| 364 |
+
).to(self.model.device)
|
| 365 |
+
```
|
| 366 |
+
|
| 367 |
+
### 4. Configuración GPU
|
| 368 |
+
```python
|
| 369 |
+
# Parámetros optimizados para L40S
|
| 370 |
+
model_kwargs = {
|
| 371 |
+
"device_map": "auto", # Distribución automática en GPU
|
| 372 |
+
"torch_dtype": "auto", # Precision mixta FP16/FP32
|
| 373 |
+
"trust_remote_code": True
|
| 374 |
+
}
|
| 375 |
+
```
|
| 376 |
+
|
| 377 |
+
## Patrones de Diseño
|
| 378 |
+
|
| 379 |
+
### 1. Singleton Pattern (model.py)
|
| 380 |
+
```python
|
| 381 |
+
class Model:
|
| 382 |
+
_model = None
|
| 383 |
+
_processor = None
|
| 384 |
+
|
| 385 |
+
@classmethod
|
| 386 |
+
def load(cls):
|
| 387 |
+
if cls._model is None:
|
| 388 |
+
# Cargar solo una vez
|
| 389 |
+
cls._model = cls.model_class.from_pretrained(...)
|
| 390 |
+
```
|
| 391 |
+
|
| 392 |
+
### 2. Factory Pattern (types_io.py)
|
| 393 |
+
```python
|
| 394 |
+
class ImageData(BaseModel):
|
| 395 |
+
"""Factory para crear respuestas validadas"""
|
| 396 |
+
classification: List[ImageTag]
|
| 397 |
+
think: str
|
| 398 |
+
```
|
| 399 |
+
|
| 400 |
+
### 3. Strategy Pattern (classifier.py)
|
| 401 |
+
```python
|
| 402 |
+
def resize_image(image: Image.Image, max_size: int = 224):
|
| 403 |
+
"""Estrategia configurable de redimensionamiento"""
|
| 404 |
+
scale = min(max_size / width, max_size / height)
|
| 405 |
+
# Aplicar estrategia de redimensionamiento
|
| 406 |
+
```
|
| 407 |
+
|
| 408 |
+
### 4. Dependency Injection (app.py → inference.py → classifier.py)
|
| 409 |
+
```python
|
| 410 |
+
# app.py
|
| 411 |
+
inference = Inference()
|
| 412 |
+
|
| 413 |
+
# inference.py
|
| 414 |
+
def __init__(self):
|
| 415 |
+
self.classifier = classifier # Inyección de dependencia
|
| 416 |
+
|
| 417 |
+
# classifier.py
|
| 418 |
+
def __init__(self):
|
| 419 |
+
self.model = Model.load_model() # Inyección lazy
|
| 420 |
+
self.processor = Model.load_processor()
|
| 421 |
+
```
|
| 422 |
+
|
| 423 |
+
### 5. Pipeline Pattern
|
| 424 |
+
```
|
| 425 |
+
Cliente → API → Inference → Classifier → Model → Respuesta
|
| 426 |
+
```
|
| 427 |
+
|
| 428 |
+
Cada etapa transforma los datos y los pasa a la siguiente, permitiendo:
|
| 429 |
+
- **Separación de responsabilidades**
|
| 430 |
+
- **Facilidad de testing**
|
| 431 |
+
- **Escalabilidad independiente**
|
| 432 |
+
- **Mantenimiento modular**
|
| 433 |
+
|
| 434 |
+
## Consideraciones de Escalabilidad
|
| 435 |
+
|
| 436 |
+
### 1. Arquitectura Horizontal
|
| 437 |
+
- Cada componente puede escalarse independientemente
|
| 438 |
+
- API stateless permite múltiples instancias
|
| 439 |
+
- Modelo singleton optimiza uso de memoria
|
| 440 |
+
|
| 441 |
+
### 2. Gestión de Recursos
|
| 442 |
+
- Auto device mapping para múltiples GPUs
|
| 443 |
+
- Batch processing para eficiencia
|
| 444 |
+
- Cleanup automático de archivos temporales
|
| 445 |
+
|
| 446 |
+
### 3. Monitoring y Observabilidad
|
| 447 |
+
- Logging estructurado en cada capa
|
| 448 |
+
- Métricas de tiempo de respuesta
|
| 449 |
+
- Seguimiento de uso de memoria GPU
|
| 450 |
+
|
| 451 |
+
---
|
| 452 |
+
|
| 453 |
+
*Documento técnico actualizado: 13 de julio de 2025*
|
| 454 |
+
|
| 455 |
+
*Para más información sobre el despliegue, consulta [DEPLOYMENT.md](DEPLOYMENT.md)*
|
test/images/AAA0119DNBSPD01.jpg
ADDED
|
test/images/AAA0119DNBSPD02.jpg
ADDED
|
test/images/AAA0119DNBSPD03.jpg
ADDED
|
test/test_api_local.py
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import requests
|
| 2 |
+
import base64
|
| 3 |
+
from pathlib import Path
|
| 4 |
+
from PIL import Image
|
| 5 |
+
import io
|
| 6 |
+
|
| 7 |
+
def resize_image(image: Image.Image, max_size: int = 224) -> Image.Image:
|
| 8 |
+
"""
|
| 9 |
+
Resize an image while maintaining aspect ratio.
|
| 10 |
+
|
| 11 |
+
Args:
|
| 12 |
+
image: PIL Image object to resize
|
| 13 |
+
max_size: Maximum dimension (width or height) of the output image
|
| 14 |
+
|
| 15 |
+
Returns:
|
| 16 |
+
PIL Image: Resized image with maintained aspect ratio
|
| 17 |
+
"""
|
| 18 |
+
# Get current dimensions
|
| 19 |
+
width, height = image.size
|
| 20 |
+
|
| 21 |
+
# Calculate scaling factor to fit within max_size
|
| 22 |
+
scale = min(max_size / width, max_size / height)
|
| 23 |
+
|
| 24 |
+
# Only resize if image is larger than max_size
|
| 25 |
+
if scale < 1:
|
| 26 |
+
new_width = int(width * scale)
|
| 27 |
+
new_height = int(height * scale)
|
| 28 |
+
image = image.resize(
|
| 29 |
+
(new_width, new_height),
|
| 30 |
+
Image.LANCZOS
|
| 31 |
+
)
|
| 32 |
+
|
| 33 |
+
return image
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
# Define your desired size
|
| 37 |
+
TARGET_SIZE = 16
|
| 38 |
+
|
| 39 |
+
# Define the image paths
|
| 40 |
+
image_paths = [
|
| 41 |
+
"images/AAA0119DNBSPD01.jpg",
|
| 42 |
+
"images/AAA0119DNBSPD02.jpg"
|
| 43 |
+
]
|
| 44 |
+
|
| 45 |
+
# Read and encode images
|
| 46 |
+
images = []
|
| 47 |
+
for path in image_paths:
|
| 48 |
+
# Open the image
|
| 49 |
+
img = Image.open(path)
|
| 50 |
+
|
| 51 |
+
# Resize the image (using LANCZOS for high-quality downsampling)
|
| 52 |
+
img = resize_image(img, max_size=TARGET_SIZE)
|
| 53 |
+
|
| 54 |
+
# Convert to bytes
|
| 55 |
+
buffered = io.BytesIO()
|
| 56 |
+
img.save(buffered, format="JPEG") # You can change format to PNG if needed
|
| 57 |
+
|
| 58 |
+
# Encode to base64
|
| 59 |
+
base64_image = base64.b64encode(buffered.getvalue()).decode('utf-8')
|
| 60 |
+
images.append(base64_image)
|
| 61 |
+
|
| 62 |
+
# Make the request
|
| 63 |
+
print(images[0])
|
| 64 |
+
url = "http://localhost:8000/classify"
|
| 65 |
+
payload = {"images": [images[0]]}
|
| 66 |
+
headers = {"Content-Type": "application/json"}
|
| 67 |
+
|
| 68 |
+
response = requests.post(url, json=payload, headers=headers)
|
| 69 |
+
print(f"Status Code: {response.status_code}")
|
| 70 |
+
print("Response Text:")
|
| 71 |
+
print(response.text)
|