Microservice for server-side rendering of dental charts (odontograms) to PNG images Microservicio de renderizado de odontogramas a imágenes PNG en el servidor
odontograma-render is a dedicated microservice that converts structured dental chart data (odontograms) into high-fidelity PNG images. It was built to serve as a rendering backend for a healthcare management system, enabling the generation of dental chart images for use in medical history reports and clinical documents.
The service exposes a simple REST API that accepts JSON payloads describing tooth conditions, damage types, and prosthetics, and returns pixel-perfect PNG images — either a single image or a batch — rendered by a headless Chromium browser via Puppeteer.
| Feature | Description |
|---|---|
| Server-side rendering | Uses Puppeteer + Headless Chromium to render complex SVG dental charts with pixel precision |
| Single & batch rendering | Supports rendering one or multiple odontograms in a single request with configurable concurrency |
| Adult & pediatric dentition | Automatically infers dentition type (adult 32 teeth / pediatric 20 teeth) from the incoming data |
| Rich dental markings | Supports partial fillings, caries circles, extractions, crowns, bridges, removable prosthetics, full prosthetics, and more |
| Color-coded annotations | Marks can be rendered in blue (planned/current treatment), red (past treatment), or mixed |
| Dockerized | Fully containerized with all Chromium dependencies pre-installed for reliable deployment |
| Health check endpoint | Built-in /health endpoint for container orchestration readiness probes |
Consumer (PHP / any HTTP client)
│
│ HTTP POST JSON
▼
┌─────────────────────────────┐
│ Express REST API │
│ server.js │
│ /render /render-multiple │
└────────────┬────────────────┘
│
▼
┌─────────────────────────────┐
│ Render Engine │
│ render.js │
│ Puppeteer + Chromium │
│ (singleton browser instance)│
└────────────┬────────────────┘
│ loads
▼
┌─────────────────────────────┐
│ template.html │
│ SVG Dental Chart + jQuery │
│ Applies damage markings │
│ Signals render completion │
└─────────────────────────────┘
│ screenshot
▼
PNG Image
- Node.js 20+
- npm
- (Optional) Docker & Docker Compose
# Clone the repository
git clone https://github.com/Gusthere/odontograma-render.git
cd odontograma-render
# Install dependencies (Puppeteer will download Chromium automatically)
npm install
# Start the service
node server.jsThe service will be available at http://127.0.0.1:3001
# Build and start the container
docker compose up -d
# Check health status
docker inspect --format='{{json .State.Health}}' odontograma_renderThe container exposes port 3001 and includes a built-in health check that automatically restarts the service if it becomes unresponsive.
Returns the service status. Used by Docker health checks and load balancers.
Response:
{ "ok": true }Renders a single odontogram and returns a PNG image binary.
Request Body:
{
"data": [
{
"coordenada": "11",
"ubicacion": "sup",
"dano": "parcial_3",
"color": "blue"
},
{
"coordenada": "21",
"ubicacion": "inf",
"dano": "extraccion",
"color": "red"
}
]
}Field Reference:
| Field | Description | Example Values |
|---|---|---|
coordenada |
Tooth number (FDI notation) | "11" to "48" (adult), "51" to "85" (pediatric) |
ubicacion |
Surface location | "sup", "inf", "izq", "der", "cen" |
dano |
Damage or condition type | "parcial_3", "caries_2", "extraccion", "corona_tot", "puente_ini", "outline" |
color |
Annotation color | "blue" (current), "red" (past), "mixed" |
Response: Binary PNG (Content-Type: image/png)
Renders multiple odontograms in parallel (concurrency: 3) and returns base64-encoded images.
Request Body:
[
[
{ "coordenada": "11", "ubicacion": "sup", "dano": "parcial_3", "color": "blue" }
],
[
{ "coordenada": "21", "ubicacion": "inf", "dano": "outline", "color": "red" }
]
]Response:
{
"success": true,
"images": ["<base64_png_1>", "<base64_png_2>"]
}PowerShell:
$body = @{
data = @(
@{ coordenada = "11"; ubicacion = "sup"; dano = "parcial_3"; color = "blue" }
)
} | ConvertTo-Json -Depth 5
Invoke-WebRequest `
-Uri http://127.0.0.1:3001/render `
-Method POST `
-ContentType "application/json" `
-Body $body `
-OutFile .\test_odontogram.pngcURL:
curl -X POST http://127.0.0.1:3001/render \
-H "Content-Type: application/json" \
-d '{"data":[{"coordenada":"11","ubicacion":"sup","dano":"parcial_3","color":"blue"}]}' \
--output test_odontogram.png- Singleton browser: Puppeteer launches a single Chromium instance that persists for the lifetime of the service, avoiding the overhead of spawning a new browser per request.
- Concurrent batch rendering:
renderOdontogramasBatchuses a worker pool pattern with configurable concurrency (default: 3 parallel renders) to efficiently process multiple odontograms. - Render signaling: The
template.htmlsetswindow.__RENDER_DONE__ = truewhen the dental chart has been fully painted, andrender.jspolls for this flag before taking the screenshot — ensuring no partial renders are captured. - Viewport & scale: Pages are rendered at
1400×900withdeviceScaleFactor: 2(2× retina), producing crisp, high-resolution output. - Data injection: Patient dental data is injected into the page via
page.evaluateOnNewDocument, making it available before any script runs.
The Dockerfile is based on node:20-bookworm-slim and includes all system libraries required by Chromium (GTK, NSS, X11 libs, fonts, etc.) making it fully self-contained and portable.
This service is consumed by a PHP-based EMR (Electronic Medical Records) system. The PHP backend makes HTTP POST requests to /render or /render-multiple, passing the dental condition data retrieved from the database, and saves the resulting PNG images for embedding in patient reports and clinical documents.
# Install PM2 globally
npm install -g pm2
# Start the service
pm2 start server.js --name odontograma-render
# Enable auto-start on system boot
pm2 startup
pm2 save
# Useful commands
pm2 status # View running processes
pm2 logs odontograma-render # Stream logs
pm2 restart odontograma-render # Restart
pm2 stop odontograma-render # Stop
pm2 delete odontograma-render # Remove from PM2| Symptom | Likely Cause | Solution |
|---|---|---|
| Empty or blank PNG returned | Missing CSS files or template load failure | Ensure odontograma.css and sb-admin-2.css are present alongside template.html |
| Chromium fails to launch | Missing system libraries | Use the Docker image, which includes all dependencies |
ECONNREFUSED from consumer |
Service not running | Check pm2 status or docker ps |
| Slow first render | Browser cold start | Normal behavior; subsequent renders reuse the singleton browser instance |
odontograma-render es un microservicio dedicado que convierte datos estructurados de odontogramas en imágenes PNG de alta fidelidad. Fue construido como backend de renderizado para un sistema de gestión de salud, permitiendo la generación de imágenes de odontogramas para su uso en reportes de historias médicas y documentos clínicos.
El servicio expone una API REST simple que acepta payloads JSON describiendo condiciones dentales, tipos de daño y prótesis, y devuelve imágenes PNG con precisión de píxel — ya sea una sola imagen o un lote — renderizadas por un navegador Chromium headless mediante Puppeteer.
| Característica | Descripción |
|---|---|
| Renderizado en servidor | Usa Puppeteer + Chromium headless para renderizar gráficos SVG dentales con precisión de píxel |
| Renderizado individual y por lotes | Soporta renderizar uno o múltiples odontogramas en una sola request con concurrencia configurable |
| Dentición adulta y pediátrica | Infiere automáticamente el tipo de dentición (adulto 32 piezas / niño 20 piezas) de los datos recibidos |
| Marcas dentales completas | Soporta rellenos parciales, círculos de caries, extracciones, coronas, puentes, prótesis removibles, prótesis totales y más |
| Anotaciones con colores | Las marcas pueden ser azules (tratamiento actual/planeado), rojas (tratamiento previo) o mixtas |
| Dockerizado | Completamente contenedorizado con todas las dependencias de Chromium pre-instaladas |
| Health check integrado | Endpoint /health incorporado para sondas de disponibilidad en orquestadores de contenedores |
- Node.js 20+
- npm
- (Opcional) Docker y Docker Compose
# Clonar el repositorio
git clone https://github.com/Gusthere/odontograma-render.git
cd odontograma-render
# Instalar dependencias (Puppeteer descargará Chromium automáticamente)
npm install
# Iniciar el servicio
node server.jsEl servicio estará disponible en http://127.0.0.1:3001
# Construir e iniciar el contenedor
docker compose up -d
# Verificar estado de salud
docker inspect --format='{{json .State.Health}}' odontograma_renderDevuelve el estado del servicio.
Respuesta:
{ "ok": true }Renderiza un odontograma individual y devuelve el binario PNG.
Body:
{
"data": [
{
"coordenada": "11",
"ubicacion": "sup",
"dano": "parcial_3",
"color": "blue"
}
]
}Referencia de campos:
| Campo | Descripción | Valores de ejemplo |
|---|---|---|
coordenada |
Número de pieza (notación FDI) | "11" al "48" (adulto), "51" al "85" (niño) |
ubicacion |
Superficie de la pieza | "sup", "inf", "izq", "der", "cen" |
dano |
Tipo de daño o condición | "parcial_3", "caries_2", "extraccion", "corona_tot", "puente_ini", "outline" |
color |
Color de la anotación | "blue" (actual), "red" (pasado), "mixed" |
Respuesta: PNG binario (Content-Type: image/png)
Renderiza múltiples odontogramas en paralelo y devuelve imágenes en base64.
Respuesta:
{
"success": true,
"images": ["<base64_png_1>", "<base64_png_2>"]
}- Navegador singleton: Puppeteer lanza una única instancia de Chromium que persiste durante toda la vida del servicio, evitando el overhead de iniciar un nuevo navegador por cada request.
- Renderizado concurrente:
renderOdontogramasBatchusa un patrón de worker pool con concurrencia configurable (por defecto: 3 renders paralelos) para procesar múltiples odontogramas eficientemente. - Señalización de render completo: El
template.htmlestablecewindow.__RENDER_DONE__ = truecuando el gráfico dental ha sido completamente pintado, yrender.jsespera esta señal antes de capturar la pantalla, garantizando que nunca se capturen renders incompletos. - Viewport y escala: Las páginas se renderizan a
1400×900condeviceScaleFactor: 2(resolución 2× retina), produciendo imágenes nítidas y de alta resolución. - Inyección de datos: Los datos dentales del paciente se inyectan en la página mediante
page.evaluateOnNewDocument, disponibles antes de que cualquier script se ejecute.
Este servicio es consumido por un sistema de Historia Clínica Electrónica (HCE) basado en PHP. El backend PHP realiza peticiones HTTP POST a /render o /render-multiple, enviando los datos de condiciones dentales obtenidos de la base de datos, y guarda las imágenes PNG resultantes para incrustarlas en los reportes y documentos clínicos de los pacientes.
# Instalar PM2 globalmente
npm install -g pm2
# Iniciar el servicio
pm2 start server.js --name odontograma-render
# Habilitar inicio automático al arrancar el sistema
pm2 startup
pm2 save
# Comandos útiles
pm2 status # Ver procesos activos
pm2 logs odontograma-render # Ver logs en tiempo real
pm2 restart odontograma-render # Reiniciar
pm2 stop odontograma-render # Detener
pm2 delete odontograma-render # Eliminar de PM2| Síntoma | Causa probable | Solución |
|---|---|---|
| PNG vacío o en blanco | Archivos CSS faltantes o fallo al cargar la plantilla | Asegúrate de que odontograma.css y sb-admin-2.css estén junto a template.html |
| Chromium no inicia | Librerías de sistema faltantes | Usar la imagen Docker que incluye todas las dependencias |
ECONNREFUSED desde el consumidor |
Servicio no está corriendo | Verificar con pm2 status o docker ps |
| Primer render lento | Arranque en frío del navegador | Comportamiento normal; los renders siguientes reutilizan la instancia singleton |
ISC
Built for clinical dental documentation
Construido para documentación clínica odontológica