RADIA: Radiología + IA Un Visor DICOM con IA en el Edge
Cómo construimos una plataforma de imagen médica completa — visor DICOM, reconstrucción 3D por ray marching, MPR multiplanar y análisis IA multi-modelo — sin un solo servidor, todo corriendo en Cloudflare Workers.
Las herramientas de imagen médica tradicionales son software de escritorio pesado, licencias caras y flujos de trabajo que no han cambiado en 20 años. Un radiólogo abre un visor PACS, revisa corte por corte manualmente, dicta su informe y lo envía al clínico. ¿Y si ese flujo pudiera vivir en un navegador, con IA analizando cada corte en paralelo, generando hallazgos con coordenadas y severidad, y permitiendo colaboración en tiempo real?
Eso es RADIA. No es un prototipo ni una demo. Es una plataforma en producción que procesa DICOM real — desde CBCT dentales de 300+ cortes hasta mamografías, TACs y fotografía clínica dermatológica — con tres modelos de IA trabajando en consenso.
16 modalidades, 4 modelos, 0 servidores
RADIA soporta 16 tipos de estudio en 6 sectores médicos, analizados por Gemini 2.5 Flash + MedGemma 4B-IT + Llama 4 Scout + DeepSeek Reasoner, desplegados íntegramente en Cloudflare Pages + Workers + D1 + R2. Sin EC2, sin Kubernetes, sin facturas de GPU.
Por qué el PACS actual no escala
El flujo de trabajo radiológico convencional tiene tres problemas fundamentales que llevan décadas sin resolver:
Software cautivo
Los visores PACS requieren instalación local, licencias por puesto y hardware específico. Un radiólogo no puede revisar un estudio desde su móvil camino al hospital.
Revisión manual
Un CBCT dental tiene 200-400 cortes. El doctor los revisa uno a uno buscando patologías. Un hallazgo sutil en el corte 247 puede pasar desapercibido por fatiga visual.
Colaboración imposible
Compartir un estudio entre profesionales implica CDs, USBs o portales hospitalarios con VPN. No hay forma simple de pedir una segunda opinión sobre un hallazgo.
Edge-first: todo en Cloudflare
La decisión más importante fue no levantar servidores. RADIA corre 100% en el edge de Cloudflare: las páginas son estáticas (Astro SSG), la lógica de negocio son Workers, la base de datos es D1 (SQLite distribuido) y los archivos DICOM viven en R2 (S3-compatible). El resultado: despliegues en 3 segundos, latencia global < 50ms, y cero mantenimiento de infraestructura.
Astro 4 + React
SSG → HTML estático con islas React interactivas
Workers
16 endpoints API con auth JWT, análisis IA y proxy DICOM
D1
SQLite distribuido: usuarios, estudios, hallazgos, chat, jobs
R2
Almacenamiento de archivos DICOM y thumbnails PNG
Parsing DICOM client-side con WASM
DICOM es el estándar universal de imagen médica, pero es un formato complejo: cabeceras binarias con tags anidados, codificaciones variadas (Little Endian, Big Endian, encapsulado) y compresión JPEG 2000 que los navegadores no entienden nativamente.
En lugar de parsear en el servidor (que consumiría recursos de Worker), RADIA hace todo el parsing en el cliente. La librería dicom-parser extrae los headers, y para los píxeles JPEG 2000 usamos el codec OpenJPEG compilado a WebAssembly. El resultado: el Worker nunca toca datos de píxeles, solo orquesta.
ZIP (drag & drop)
→ JSZip extrae archivos
→ dicom-parser lee headers (paciente, modalidad, geometría)
→ OpenJPEG WASM decodifica JPEG 2000 → PNG thumbnails
→ POST /api/studies/upload (metadata)
→ PUT /api/dicom/{key} × N (binarios en paralelo, 5 concurrentes)
→ POST /api/studies/{id} action=finalize
→ Redirect → visor con estudio listo El truco está en la doble capa de almacenamiento: los DICOM originales se guardan en R2 para análisis IA, y en paralelo generamos thumbnails PNG que pesan 10-50× menos para la navegación rápida. El visor precarga ±5 cortes en cada dirección usando la Cache API del navegador, logrando una experiencia de scroll instantánea incluso con estudios de 400 cortes.
Renderizado HQ Progresivo — 16-bit nativo
Cuando el usuario deja de hacer scroll y se detiene en un corte durante 800ms, RADIA descarga el DICOM original desde R2, lo parsea en el navegador (16-bit completo) y re-renderiza con el rango dinámico nativo del sensor. Un badge HD 16-bit aparece en el overlay PACS.
Scrolleando: PNG 8-bit instantáneo (~50ms). Pausado: DICOM 16-bit con W/L original (~200-500ms). Lo mejor de ambos mundos: fluidez + calidad diagnóstica.
Volume rendering con WebGL2 puro
Para reconstrucción 3D en imágenes volumétricas (CBCT dental, TAC), escribimos un ray marcher desde cero en GLSL. Sin Three.js (400KB+), sin dependencias 3D. Solo dos shaders, una textura 3D y matemáticas de matrices 4×4 inline.
Cómo funciona el ray marching
Los PNGs de cada corte se apilan en una textura 3D WebGL2 (256³ en móvil, 768³ en desktop)
Para cada píxel de pantalla, el fragment shader dispara un rayo a través del volumen
El rayo marcha en 160 pasos (móvil) o 384 pasos (desktop), muestreando densidad del tejido
Una transfer function mapea densidad → color: hueso beige, tejido blando translúcido, aire invisible
Tres modos de render: Bone (composición volumétrica), MIP (máxima intensidad), X-Ray (proyección inversa)
El volumen se cachea en IndexedDB con TTL de 7 días, así que al volver a abrir un estudio el 3D aparece instantáneamente sin reconstruir. En móvil, reducimos la resolución de la textura y los pasos de ray marching para mantener 30+ FPS con rotación táctil fluida.
MPR: Reconstrucción Multiplanar
Además del 3D, RADIA ofrece vista MPR en grid 2×2 — axial, coronal, sagital más un mini-3D — con crosshair sincronizado entre las cuatro vistas. Click en cualquier panel lo expande a pantalla completa. Todo recalculado en el cliente desde la misma textura 3D WebGL2.
5 modos de análisis, 4 modelos en cascada
RADIA no tiene un solo endpoint de "analizar". Tiene cinco modos de análisis distintos, cada uno diseñado para un momento diferente del flujo de trabajo del doctor. Antes de ejecutar cualquier análisis, un diálogo de pre-análisis permite al doctor elegir si complementar hallazgos existentes, revisar críticamente, o borrar y re-analizar desde cero — con toggle de Modo Asteroide y campo de razón para guiar el foco de la IA.
Scan 360°
Análisis automatizado del estudio completo por lotes. El frontend orquesta los batches para no superar el timeout de Workers (30s). Muestrea cada 8 cortes, genera hallazgos con título, severidad, confianza, bounding box normalizado y ubicación anatómica.
Deep Analysis
Profundización en un hallazgo específico. Lee ±10 cortes alrededor del hallazgo, genera diferencial clínico, significancia clínica, acciones recomendadas y revisión de confianza.
Point-Ask
El doctor hace click en cualquier punto de la imagen y pregunta "¿qué ves aquí?". Las coordenadas normalizadas (x, y) se envían junto con el PNG del corte. MedGemma tiene peso extra aquí por su especialización médica.
Viewport Capture
Captura la vista 3D o MPR actual como PNG, la guarda en R2 y la analiza. Útil para documentar hallazgos volumétricos que no existen en un solo corte 2D.
Sugerencias de Tratamiento
Sintetiza todos los hallazgos del estudio en un plan de tratamiento priorizado. Tiene en cuenta severidad, ubicación anatómica, análisis profundos previos y relaciones entre hallazgos.
Sugerencias de tratamiento generadas por IA: nivel de riesgo, hallazgos prioritarios y plan escalonado con urgencia y derivaciones.
Los cuatro modelos se encadenan en cascada de prioridad:
| Prioridad | Modelo | Proveedor | Rol |
|---|---|---|---|
| 1 | Gemini 2.5 Flash | Google AI | Primario — visión multimodal con contexto largo |
| 2 | Llama 4 Scout 17B | Groq | Fallback — si Gemini falla o está saturado |
| 3 | MedGemma 4B-IT | Vertex AI | Especialista médico — entrenado en radiología, dermatología, oftalmología |
| 4 | DeepSeek Reasoner | DeepSeek API | Consejo Clínico — razonamiento chain-of-thought sobre hallazgos (solo texto, no ve imágenes) |
Multi-modelo en paralelo con consenso cruzado
El Modo Asteroide es la artillería pesada. En lugar de usar un solo modelo que cae a fallback si falla, ejecuta Gemini y MedGemma en paralelo sobre cada corte, cruza los hallazgos y marca las coincidencias como consenso multi-modelo. Tras la detección visual, DeepSeek Reasoner actúa como "Consejo Clínico": recibe todos los hallazgos (solo texto, sin imágenes) y aplica razonamiento chain-of-thought para debatir consistencia clínica, detectar posibles falsos positivos y priorizar hallazgos.
Flujo del Modo Asteroide
Corte N del estudio
│
├─── Gemini 2.5 Flash ──────────┐
│ "3 hallazgos encontrados" │
│ ├── Promise.allSettled()
├─── MedGemma 4B-IT ───────────┘
│ "2 hallazgos encontrados"
│
▼
Cross-reference por (categoría + ubicación + corte)
│
├── Coincidencia → ☄️ Consenso
│ • confidence × 1.15
│ • ai_model = "gemini-2.5-flash+medgemma-4b-it"
│
└── Solo un modelo → hallazgo normal
• confidence sin boost
• ai_model = modelo individual
▼
DeepSeek Reasoner — Consejo Clínico (solo texto)
│
Input: todos los findings JSON + datos clínicos
│
Output:
├── Diferencial clínico expandido con reasoning
├── Detección de falsos positivos probables
├── Correlaciones entre hallazgos
└── Priorización y recomendaciones | Aspecto | Scan Normal | Modo Asteroide ☄️ |
|---|---|---|
| Muestreo | Cada 8 cortes | Cada 2 cortes |
| Modelos | 1 (con fallback) | 2 en paralelo |
| Deep analysis | Manual | Auto en top 5 hallazgos |
| Tiempo (~280 cortes) | ~30 segundos | ~2-3 minutos |
| Coste API | 1× | ~4× |
Un detalle importante: el consenso IA nunca se marca como confirmación del doctor. El campo user_confirmed siempre empieza en 0 independientemente de cuántos modelos coincidan. La validación humana es un paso separado e intencional — la IA asiste, no reemplaza.
Anatomía de un hallazgo
Cada hallazgo que la IA genera es una estructura rica con datos suficientes para localización precisa en la imagen, categorización clínica y seguimiento del flujo de validación. Los hallazgos en cortes adyacentes con misma categoría y ubicación se agrupan automáticamente (deduplicación), mostrando un rango de cortes en lugar de entradas repetidas. El doctor puede eliminar, ocultar del informe, o navegar directamente a cada hallazgo — tanto individual como en grupo.
Localización
- 📍 slice_number — corte donde se detectó
- 📐 bounding_box — [x, y, w, h] normalizado 0-1
- 🦷 location — texto anatómico ("zona molar inferior derecha")
Clasificación
- 🏷️ category — periapical, fractura, masa, quiste...
- 🎯 confidence — 0.0 a 1.0
- 📏 measurements — dimensiones en mm si aplica
La severidad se clasifica en 5 niveles con código de color consistente en todo el UI:
Marcadores interactivos en el volumen
Los hallazgos con bounding box y número de corte se proyectan como marcadores 3D sobre el volumen renderizado. La posición (x, y) del bounding box se mapea al sistema de coordenadas del volumen, el slice_number determina la profundidad Z, y la proyección MVP (Model-View-Projection) los lleva a coordenadas de pantalla en cada frame.
Al pulsar un marcador, aparece un popup con la información del hallazgo — título, severidad, descripción, ubicación — y tres acciones: ir a la lista de hallazgos, saltar al corte 2D donde está el hallazgo, o abrir la vista MPR centrada en esa posición. El doctor navega de 3D a 2D con un solo toque. Además, el Modo Navegar permite hacer click en cualquier punto del visor 2D para saltar automáticamente al hallazgo más cercano por distancia euclídea — ideal para explorar zonas de interés sin buscar en la lista.
Detalle técnico: proyección 3D → 2D
Los marcadores son divs HTML posicionados con position: absolute sobre el canvas WebGL. En cada frame de renderizado, multiplicamos las coordenadas 3D del hallazgo por la matriz MVP y convertimos NDC a porcentaje CSS. Si el marcador queda detrás de la cámara (clip.w ≤ 0) o fuera del frustum, se oculta con opacity: 0.
16 tipos de estudio, detección automática
RADIA no es solo un visor dental. Soporta 16 tipos de estudio organizados en 6 sectores médicos, cada uno con prompts de IA especializados y categorías de hallazgos específicas:
| Sector | Tipos | 3D/MPR |
|---|---|---|
| 🦷 Dental | CBCT, Panorámica, Periapical | CBCT ✓ |
| 🫁 Radiología | Rx Tórax, Rx Ósea, TAC, Mamografía | TAC ✓ |
| 🧠 RM | Cerebral, Musculoesquelética | Ambas ✓ |
| 📡 Ecografía | General | — |
| 📷 Foto Clínica | Dermatología, Podología, Oftalmología, Heridas, General | — |
| 🐾 Veterinaria | General | — |
La detección es automática: analizamos los headers DICOM (Modality, SeriesDescription, StudyDescription) con una cascada de reglas — primero keywords en la descripción, luego el código de modalidad DICOM, luego heurísticas por anatomía. El usuario siempre puede corregir manualmente, pero en la práctica la detección automática acierta en el 95%+ de los casos.
Labrador con dilatación-vólvulo gástrico (DVG): de la radiografía al informe firmado
El módulo veterinario no es un "añadido" sobre el motor médico — es una rama propia con prompts específicos para anatomía animal, terminología clínica veterinaria y un flujo pensado para urgencias 24h. La IA distingue entre proyecciones laterolateral y ventrodorsal, reconoce el patrón patognomónico de la "doble burbuja" o "gorro de pitufo", y prioriza los hallazgos por riesgo vital. En este caso real, una radiografía abdominal de un Labrador Retriever desencadenó 19 hallazgos automáticos en menos de 30 segundos, con 6 catalogados como malignos.
El Modo Asteroide activa el ensemble completo de modelos en paralelo y aplica un segundo pase de revisión crítica — pensado para casos urgentes donde no se puede pasar por alto un hallazgo. En la imagen verás también las cajas semánticas: cada color codifica severidad (rojo=maligno, naranja=sospechoso, amarillo=indeterminado, verde=normal) y cada caja se vincula a un hallazgo navegable en el panel inferior.
Modo propietario: la IA explica al humano sin perder rigor
En veterinaria el "paciente" no habla — y el propietario es quien firma la decisión clínica. Por eso RADIA genera, junto al informe técnico, una narrativa divulgativa escrita en castellano natural para el dueño del animal. Mismo contenido clínico, dos registros: el técnico para historial, y el del propietario explicando qué se ve, por qué importa y qué urgencia tiene. Todo trazable: cada explicación al propietario apunta al hallazgo técnico que la respalda.
Anexo · mapa de hallazgos numerados sobre la imagen original
El PDF que descarga el veterinario no es una captura de pantalla — es un informe estructurado generado en cliente con jsPDF, firmado con los datos del profesional (nº de colegiado multi-país: España, México, Colombia, Argentina, Chile, etc.), sello escaneado opcional y NIF/CIF de la clínica. La última hoja siempre es el anexo cartográfico: una vista panorámica con todos los hallazgos numerados sobre la radiografía original, replicando los colores de severidad y enlazando cada número con el hallazgo detallado de las páginas anteriores.
Esto cierra el ciclo: la IA detecta, el profesional valida y edita, el sistema genera la narrativa para el propietario, y el PDF firmado va al historial clínico. En el caso del DVG, RADIA convirtió una radiografía sospechosa en un informe accionable en menos de dos minutos — el tipo de tiempo que importa cuando hablas de una urgencia con ventana terapéutica de horas.
Conversación multimodal con contexto radiológico
El chat no es un chatbot genérico al que le pasas una imagen. Es un asistente con contexto completo del estudio: metadata DICOM, todos los hallazgos previos, historial de la conversación, y la imagen actual del visor (incluida la vista 3D si está activa). Si el doctor pregunta "¿qué opinas de la zona periapical del 46?", la IA sabe en qué corte está, qué hallazgos ya se encontraron allí, y puede generar un análisis contextualizado.
Si el visor 3D está activo, el chat captura automáticamente la vista actual como PNG y la incluye en la petición. El doctor rota el volumen a la perspectiva que le interesa, escribe su pregunta, y la IA ve exactamente lo que el doctor ve. Sin capturas manuales.
Compartir un estudio en un click
El doctor genera un enlace seguro con expiración opcional. El enlace da acceso de solo lectura al estudio completo — visor DICOM, 3D, MPR, todos los hallazgos — sin que el receptor necesite cuenta ni login. Para colaboración entre doctores (donde se necesita autenticación), existe un modo "collab" que requiere login con Google pero no necesita ser propietario del estudio.
El proxy DICOM verifica el token de compartición contra la base de datos en cada petición. No hay URLs firmadas que puedan filtrarse — si revocas el enlace, el acceso se corta inmediatamente.
Dos monitores, un estudio sincronizado
Los radiólogos trabajan con dos o más monitores: en uno revisan la imagen, en otro consultan el informe, los hallazgos y el historial. Los visores PACS de escritorio soportan esto de forma nativa, pero las aplicaciones web no — abrir dos pestañas del mismo estudio da dos instancias independientes sin conexión entre ellas.
RADIA resuelve esto con sincronización en tiempo real entre pestañas. Un click en "Multi-pantalla" abre una segunda ventana en modo visor — optimizada para la imagen, 3D y MPR — mientras la original se convierte en editor con la lista de hallazgos, chat IA y herramientas de análisis. Todo lo que el doctor hace en una ventana se refleja instantáneamente en la otra.
Multi-monitor real: análisis profundo con diferencial clínico en el editor (izquierda) y volumen 3D con marcadores sincronizados en el visor (derecha).
Visor 2D sincronizado: bounding boxes sobre el corte CBCT con hallazgos etiquetados.
Lista de hallazgos con filtros de severidad y 3D con marcadores de localización.
BroadcastChannel: sync sin servidor
La sincronización usa la BroadcastChannel API del navegador — un canal de mensajes entre pestañas del mismo origen. No hay servidor involucrado, no hay WebSockets, no hay latencia de red. Mensaje enviado = mensaje recibido en <1ms. Funciona en Chrome, Firefox, Edge y Safari — incluidas PWAs instaladas.
¿Qué se sincroniza?
- 🔄 Navegación de cortes — scroll en una ventana, ambas cambian
- 🎯 Selección de hallazgos — click en hallazgo → visor salta al corte
- 🧊 Vista 3D/MPR — cambiar modo de visualización sincronizado
- 💡 Highlight 3D — señalar hallazgo en el volumen
- 📊 Estado del scan — progreso de análisis visible en ambas
- 📝 Nuevos hallazgos — al completar scan o deep analysis
- 🗑️ Eliminaciones — borrar hallazgo actualiza ambas ventanas
- 💬 Hallazgos del chat — si la IA guarda un hallazgo desde el chat
Protocolo de sincronización
Editor (monitor 1) Viewer (monitor 2)
│ │
│── NAVIGATE_SLICE { slice: 147 } ─────→│ Salta al corte 147
│ │
│── SELECT_FINDING { id: "abc" } ──────→│ Muestra bounding box
│ │
│── VIEW_MODE { mode: "3d" } ──────────→│ Activa vista 3D
│ │
│── HIGHLIGHT_3D { id: "abc" } ────────→│ Marca en el volumen
│ │
│←── PING ──────────────────────────────│
│── PONG ──────────────────────────────→│ Heartbeat cada 3s
│ │
│── FINDINGS_CHANGED ──────────────────→│ Recarga hallazgos Un heartbeat PING/PONG cada 3 segundos detecta si la otra ventana sigue activa. Si no responde en 6 segundos, se muestra un indicador ámbar de desconexión. Al reconectarse, vuelve a verde. El doctor siempre sabe si sus ventanas están sincronizadas.
Ventana Editor
Lista de hallazgos, chat IA, barra de herramientas, botones de análisis, panel de sugerencias. El centro de control del radiólogo.
Ventana Visor
Imagen a pantalla completa con visor 2D, 3D y MPR. Sin distracciones — solo la imagen con bounding boxes y marcadores. Maximizada en el segundo monitor.
Próximamente: sync entre dispositivos
La Fase 2 llevará esta sincronización más allá del mismo navegador. Usando Cloudflare Durable Objects + WebSocket, dos dispositivos diferentes — por ejemplo el PC de la consulta y un iPad — podrán sincronizarse en tiempo real sobre el mismo estudio. Misma arquitectura de mensajes, pero con transporte de red en lugar de BroadcastChannel.
PDF radiológico generado en el cliente
El informe PDF se genera íntegramente en el navegador con jsPDF — datos del paciente, hallazgos seleccionados con códigos de severidad, impresión global, recomendaciones y notas del doctor. El Worker nunca genera PDFs; el cliente tiene toda la información necesaria. Esto evita la complejidad de renderizar documentos en un entorno serverless con 128MB de memoria.
Lo que aprendimos construyendo esto
Workers tienen timeout de 30s
No puedes analizar 280 cortes en una sola petición. La solución: el frontend orquesta los batches con un loop de POSTs. Cada batch procesa 8 cortes, el Worker responde en ~10s, y el frontend muestra progreso en tiempo real.
JPEG 2000 necesita WASM
La mitad de los DICOM médicos usan JPEG 2000, que ningún navegador decodifica nativamente. OpenJPEG compilado a WASM resuelve esto sin dependencia de servidor, pero pesa ~200KB. Lo cargamos lazy solo cuando detectamos Transfer Syntax 1.2.840.10008.1.2.4.90.
D1 es sorprendentemente rápido
SQLite distribuido en el edge. Las queries son ~1-3ms. Para un app con decenas de inserts por análisis y múltiples joins para obtener hallazgos, D1 ni se inmuta. La limitación real son las escrituras concurrentes, que evitamos con el patrón secuencial de batches.
WebGL2 es suficiente para esto
No necesitas Three.js para volume rendering médico. Dos shaders, texturas 3D nativas de WebGL2, y ~300 líneas de math de matrices 4×4. El resultado: 0 dependencias 3D, bundle ligero, y control total del pipeline de rendering.
La IA médica se equivoca (mucho)
Gemini y MedGemma son impresionantes pero no infalibles. Por eso el flujo de confirmación es explícito: el doctor revisa cada hallazgo y lo marca como confirmado o rechazado. El Modo Asteroide reduce errores con consenso multi-modelo, pero nunca pretende reemplazar al profesional.
R2 para archivos médicos escala perfecto
Un CBCT dental son ~150MB de DICOMs. R2 los sirve por prefijo con list() paginado y delete() en batch. Sin egress fees, sin cold starts. La clave: separar DICOM originales (para IA) y thumbnails PNG (para navegación rápida).
Un visor médico que no debería ser posible en el edge
RADIA demuestra que herramientas médicas profesionales pueden vivir íntegramente en el edge, sin servidores dedicados, sin GPUs alquiladas, sin infraestructura que mantener. Un doctor abre un navegador, sube su estudio, y tiene a tres modelos de IA analizando cada corte mientras el visor 3D se reconstruye en WebGL2 a 30fps.
El stack completo — Astro, React, Cloudflare Workers, D1, R2, Gemini 2.5 Flash, MedGemma, WebGL2 — se despliega con npm run deploy en 15 segundos. Sin containers, sin pipelines de CI/CD, sin clusters. El futuro de las herramientas médicas es un build estático con funciones serverless en el edge, y ya está aquí.
Stack completo
🩻
RADIA es parte de Cadences
Una plataforma, múltiples storefronts. RADIA comparte infraestructura (D1, auth, billing) con el resto del ecosistema Cadences. Cada storefront es independiente en código pero unificado en datos.