Prueba guiada de landmarks, calibración y referencia¶
Qué demuestra este protocolo¶
EPPA separa dos afirmaciones que no deben confundirse:
- Prueba funcional reproducible: demuestra que la aplicación puede cargar una imagen sin paciente, detectar stickers verdes con la API real, exigir revisión humana, guardar los puntos, calibrar 10 cm, construir la referencia, calcular regiones, restaurar el estado y exportar resultados.
- Validación clínica: demuestra que los puntos corresponden a landmarks anatómicos reales y que las mediciones concuerdan con un gold independiente. Esta segunda afirmación requiere imágenes aprobadas, consentimiento, anotadores calificados y un oracle que no haya sido producido por el detector.
El fixture sintético incluido sólo cubre la primera afirmación. No es una foto clínica, no contiene datos personales y no debe citarse como evidencia de precisión anatómica.
Método de identificación propuesto¶
El flujo es híbrido y siempre mantiene a una persona responsable en el circuito:
- Un operador entrenado localiza cada landmark indicado por la vista.
- Coloca un sticker verde pequeño centrado sobre el punto.
- La cámara se mantiene nivelada, fija y perpendicular al plano de análisis.
- El detector segmenta los stickers y propone una identidad según su geometría.
- Las estimaciones de pose, cuando existen, se muestran como aproximaciones y nunca reemplazan la identificación clínica.
- El operador revisa cada propuesta sobre la foto:
- desplaza un punto que no esté centrado;
- reemplaza manualmente una identidad incorrecta;
- usa Marcar siguiente faltante hasta completar la vista;
- pulsa Confirmar revisión.
- EPPA bloquea el guardado mientras quede algún punto automático sin confirmar.
La confianza del detector significa cercanía al modelo geométrico esperado. No es una probabilidad de que el sticker esté sobre el landmark anatómico correcto.
La sesión distingue tres procedencias de coordenadas: detected, manual y
clinician-corrected. Al desplazar una propuesta se conservan su fuente y
confianza como evidencia del detector, pero las coordenadas pasan a estar
protegidas como corrección humana; una redetección posterior no puede
sobrescribirlas. Cada vista guarda además el último DetectionRun, con
detector, vista, timestamp, conteos originales y nombres que no pudieron
asociarse al catálogo. Cambiar de imagen reinicia esa evidencia.
Preparación física¶
Persona y cámara¶
- Cuerpo entero y ambos pies visibles.
- Fondo blanco reglado/cuadriculado que contraste con el verde. Cada cuadrado pequeño debe medir 5 × 5 cm: dos cuadrados contiguos representan 10 cm.
- Iluminación uniforme y sin reflejos intensos.
- Cámara fija, nivelada y sin inclinación lateral.
- Resolución mínima: 800 × 600 px; se recomienda formato vertical.
- La persona y la grilla deben quedar completas en el mismo encuadre de referencia, sin perspectiva aparente que deforme el cuadrado usado para calibrar.
Calibración¶
La escala se toma sobre el fondo blanco reglado: hacer zoom y marcar dos cruces exactos de la grilla separados por dos cuadrados pequeños contiguos de 5 cm, es decir, 10 cm. El segmento debe ser horizontal, ubicarse a la izquierda de la pantalla, junto al cuerpo y cerca de la cadera. Esta regla de pantalla es la misma en anterior, posterior, lateral derecha y lateral izquierda; no se espeja al cambiar de vista. EPPA calcula:
centímetros por píxel = 10 cm / distancia entre extremos en píxeles
La calibración se realiza nuevamente para cada fotografía, aunque pertenezca a una vista ya calibrada. La decisión B fue confirmada por Luis el 2026-08-01 y coincide con la ubicación visible en el recorrido MATLAB histórico: izquierda de pantalla en las cuatro vistas. Por lo tanto, el mapeo de los perfiles ya no es candidato ni una pregunta clínica abierta.
Recta de referencia¶
La interfaz solicita dos anclajes después de calibrar. En vista anterior, esos
anclajes son ambos maléolos y se seleccionan de izquierda a derecha de la
pantalla. Ese mismo orden se conserva en los pares de superficie cuyo cálculo
usa x2 - x1 —cóndilos, gemelos y extremos poplíteos—; no define el lado ni el
sentido de la calibración. EPPA dibuja:
- el segmento horizontal entre ambos puntos;
- una vertical que pasa por el punto medio del segmento.
En posterior se aplica el mismo contrato de maléolos y vertical por el punto
medio. En los perfiles, la vertical pasa por el marcador importado Borde
anterior de Maléolo y el operador marca dos puntos de la horizontal. Estas
semánticas reproducen el MATLAB empaquetado y son ejecutables y persistentes;
siguen sin constituir, por sí solas, validación clínica sobre una persona.
Caso sintético integrado¶
El botón Caso de prueba sólo aparece si:
NEXT_PUBLIC_EPPA_ENABLE_SYNTHETIC_TEST_FIXTURES=1
La API sólo publica la imagen si EPPA_ENV=test o si uno de los entornos
permitidos (dev, development, staging o local) activa explícitamente:
EPPA_ENABLE_SYNTHETIC_TEST_FIXTURES=1
Un EPPA_ENV ausente, desconocido, prod o production siempre responde 404,
aunque el flag esté presente. El endpoint requiere la misma autenticación que
los endpoints de análisis:
Esta lista corresponde al endpoint autenticado del fixture. El mock navegable
aplica una política más estrecha y excluye también staging, como se detalla en
“Demo manual local sin autenticación real”.
GET /api/testing/synthetic-capture?view=anterior
GET /api/testing/synthetic-capture?view=posterior
GET /api/testing/synthetic-capture?view=lateral-derecha
GET /api/testing/synthetic-capture?view=lateral-izquierda
El parámetro stage=capture (default) entrega sólo la escena fotografiable:
maniquí, grilla y stickers físicos elegidos. stage=atlas agrega, para auditar
el contrato, calibración, puntos manuales, referencias y líneas de superficie.
Un stage desconocido se rechaza con HTTP 422.
Cada PNG renderizado es determinista, mide 1200 × 2200 px e incluye el
watermark SYNTHETIC TEST - NOT A PATIENT - NOT CLINICAL VALIDATION. Ambos
stages deben incluir el fondo blanco reglado de cuadrados pequeños de 5 × 5 cm;
dos cuadrados contiguos representan 10 cm. stage=capture conserva únicamente
cuerpo, grilla y stickers
físicos; no dibuja calibración, landmarks manuales, referencias ni superficie.
stage=atlas superpone el segmento horizontal de 10 cm definido por la
calibración de esa fotografía, a la izquierda de pantalla, junto al cuerpo y
cerca de la cadera, y luego las capas manuales y de referencia necesarias para
auditar el contrato.
El atlas, el oracle y los gates fueron regenerados y revalidados después de la decisión B. El fixture focal pasa 72/72 casos y confirma la grilla de 5 cm, el segmento a la izquierda de pantalla y la recalibración independiente.
El atlas schema 2 contiene 64 IDs canónicos y 18 endpoints/tangentes de
superficie. De los 64 IDs, 58 son stickers físicos elegidos, cuatro son ápex
manuales y dos son alternativas de lóbulo no seleccionadas porque el caso base
usa tragus. El maniquí anónimo, generado sin datos de pacientes, ocupa desde
y=420 hasta y=2110; cada vista declara el asset WebP lossless y su SHA-256.
La decodificación RGBA del WebP es idéntica píxel a píxel a la fuente de
generación, y la extensión queda fuera del filtro LFS del repositorio. La coincidencia
técnica entre renderer, detector y atlas no acredita precisión anatómica.
El detector de producción no usa esas coordenadas v2 por defecto. Una
llamada normal a /api/detect-markers, sin capture_profile, conserva
exactamente los priors del baseline productivo. El perfil
guided-synthetic-v2 sólo se envía al cargar este fixture y la API lo acepta
únicamente dentro del gate sintético no-productivo; fuera de él responde 404 y
un nombre desconocido responde 422. La procedencia queda ligada a la identidad
inmutable de esa imagen para permitir su redetección, pero es efímera: cámara,
upload, reemplazo o restore vuelven al perfil productivo. Los tests cubren
además traslación, escala uniforme y crop sin aflojar el umbral del detector.
| Vista | Stickers detectables | Landmarks manuales | Superficie/tangentes adicionales |
|---|---|---|---|
| Anterior | 17 | 0 | 4 |
| Posterior | 19 | 0 | 8 |
| Lateral derecha | 11 | 2 ápex | 3 tangentes; lóbulo alternativo no elegido |
| Lateral izquierda | 11 | 2 ápex | 3 tangentes; lóbulo alternativo no elegido |
El contrato confirmado exige una calibración independiente por fotografía: 10 cm equivalen a dos cuadrados pequeños y el segmento queda a la izquierda de pantalla en las cuatro vistas. El atlas regenerado implementa esas coordenadas y factores, verificados por el fixture focal y el oracle MATLAB real.
Demo manual local sin autenticación real¶
Este modo existe sólo para desarrollo sintético local. Exige todos los flags test-only, muestra el badge Sesión local de prueba y falla cerrado si la configuración está incompleta o el entorno es producción.
La ruta del mock educativo no requiere una sesión clínica: esa excepción es
deliberada para poder presentarlo directamente en localhost con material
sintético que no contiene pacientes. EPPA_ENABLE_GUIDED_DEMO=1 sólo lo
habilita si EPPA_ENV es test, dev, development o local; staging,
prod y production responden 404 aun con el flag. Las rutas clínicas reales
conservan su middleware de autenticación y el bypass sigue limitado a
EPPA_ENV=test con todos sus flags explícitos.
Terminal API:
EPPA_ENV=test \
EPPA_E2E_AUTH_BYPASS=1 \
DATABASE_URL='sqlite+pysqlite:///:memory:' \
EPPA_ENABLE_SYNTHETIC_TEST_FIXTURES=1 \
API_PORT=8100 \
CORS_ALLOW_ORIGINS=http://localhost:9002 \
PYTHONPATH=python \
python3.11 python/api.py
Terminal Next:
EPPA_ENV=test \
EPPA_E2E_AUTH_BYPASS=1 \
EPPA_ENABLE_GUIDED_DEMO=1 \
NEXT_PUBLIC_EPPA_ENABLE_SYNTHETIC_TEST_FIXTURES=1 \
NEXT_PUBLIC_API_URL=http://localhost:8100 \
npm run dev
Abrir:
- app real de prueba:
http://localhost:9002/es/marker-capture; - mock educativo:
http://localhost:9002/es/demo/guided-landmark-workflow.
El mock se limita explícitamente a español durante esta validación: las rutas EN, DE y PT-BR redirigen a ES y el encabezado lo identifica. Esta decisión evita publicar contenido español bajo un atributo de idioma falso; no modifica la autenticación de captura/análisis ni habilita el mock en staging o producción.
Ejecutar la prueba automatizada¶
Desde fronts/eppa, con Node 22 y Python 3.11:
npm ci
PYTHON=python3.11 npx playwright test playwright/e2e/guided-landmark-workflow.spec.ts
La configuración Playwright activa los flags sintéticos, construye Next.js,
levanta FastAPI y usa el bypass de autenticación exclusivo de EPPA_ENV=test.
Sin PLAYWRIGHT_BASE_URL, ambos servidores son gestionados y siempre nuevos:
Playwright no reutiliza procesos que ya ocupen los puertos. Si se define
PLAYWRIGHT_BASE_URL, la configuración no levanta servidores. El spec no
mockea /api/detect-markers.
Cobertura exigida:
- anterior: 17/17 stickers, sin fallback de pose;
- posterior: 19/19 stickers, sin fallback de pose;
- lateral derecha: 11/11 stickers con nombres canónicos
Der., 2 ápex manuales; - lateral izquierda: 11/11 stickers y 2 ápex manuales;
- tragus como opción preferida y lóbulo como fallback del mismo slot, nunca dos requisitos simultáneos;
- guardado bloqueado antes de la revisión;
- eliminación de una propuesta y reposición mediante Marcar siguiente faltante;
- corrección por teclado que sobrevive una nueva detección;
source,placement,confidence,reviewedyDetectionRunconservados;- restauración de imagen y puntos en análisis;
- calibración horizontal de 10 cm sobre dos cuadrados pequeños de 5 cm, a la izquierda de pantalla, junto al cuerpo y cerca de la cadera; recalibración por fotografía; tres líneas geométricas visibles en total: calibración, referencia horizontal y referencia vertical;
- las 61 métricas soportadas: 15 anteriores, 16 posteriores y 15 por perfil;
- cuatro clicks poplíteos, dos midpoints derivados y dos ángulos calcáneos;
- cuatro correcciones angulares aprobadas y ninguna excepción implícita;
- resultados y líneas conservados tras recargar;
- CSV por vista descargable dentro del workflow;
- workbook Excel consolidado de cuatro vistas validado separadamente por
consolidated-analysis-export.spec.ts, con faltantes u obsoletos comoPendiente.
Oracle MATLAB real¶
El expected versionado se genera con MATLAB R2026a real directamente desde el
atlas; no consume resultados de EPPA, Python, HTTP ni Playwright. El validador
liga exactamente imagen, calibración, referencias, landmarks, superficies,
derivadas y las 61 métricas al atlas y a las cuatro fuentes MATLAB. Para las 57
métricas sin excepción exige rawMatlab == displayedValue; para codo y rodilla
de ambos perfiles exige las cuatro transformaciones aprobadas exactas.
Como esta lane todavía no tiene un commit propio, el provenance no presenta el
HEAD base como si contuviera archivos que siguen en el working tree. Registra el
modelo base-commit-plus-canonical-input-snapshot-v1: commit base y siete
inputs controlantes con ruta, SHA-256 y Git blob OID, ligados por un digest raíz
que el validador reconstruye. El estado dirty se conserva como evidencia, no
como identidad reproducible del artefacto.
Una validación exitosa sólo demuestra fidelidad al atlas usado como entrada. Después de cualquier cambio de coordenadas o calibración se debe regenerar y validar nuevamente el expected con MATLAB real; no se acepta copiar resultados de EPPA o Python dentro del oracle.
JAVA_HOME="$HOME/Library/Java/JavaVirtualMachines/amazon-corretto-11.jdk/Contents/Home" \
/Applications/MATLAB_R2026a.app/bin/matlab -batch \
"addpath('matlab_validation'); guided_landmark_matlab_oracle_batch('src/fixtures/guided-landmark-atlas.v1.json', 'tests/fixtures/guided_landmark_matlab_oracle.expected.json')"
python3.11 -m unittest -v \
matlab_validation/test_validate_guided_landmark_matlab_oracle.py
python3.11 matlab_validation/validate_guided_landmark_matlab_oracle.py \
tests/fixtures/guided_landmark_matlab_oracle.expected.json
Identificadores vigentes del gate schema 2 regenerado con MATLAB R2026a Update 4 después de la confirmación de la decisión B:
- atlas SHA-256:
57ea00d7c5f357b98e4c5532cba59669eec03ff79fd8ae497315070414fc0fc4; - generador MATLAB:
58f48b6a43214862a206d33a302b9ab5ddad7389c5ae71e8bc0f841560cd374c; - expected SHA-256:
c04d37771590dfea2d3e10cd287c47a5509205da6415103de9101ccc12bb07b6; - snapshot de inputs:
3b8eb0b3da9a3ec6c607ad87a4324ecf91be77582dc37d0193e9f63c53d48726; - digest semántico canónico:
65edf4eae706f39db0807082538b63057fd34b78de06dcc580b99d3d59bc897d; - digest de las 61 métricas:
66ef95a990227875401fbbfe6f84078ab8eabbebf01775879b0f5e03efd0808f.
El validador directo confirma schema, provenance, 61 métricas y exactamente cuatro excepciones. Su suite pasa 12/12 casos: diez adulteraciones negativas y dos positivos. Estos resultados son técnicos sobre el fixture sintético y no constituyen validación clínica.
Tests focales complementarios:
npm run test:unit -- \
tests/unit/guided-marker-capture.test.ts \
tests/unit/marker-capture-session.test.ts
PYTHONPATH=python python3.11 -m pytest -q \
python/test_synthetic_capture_fixture.py
El pytest de fixture reemplaza temporalmente la sesión global por SQLite en
memoria y la restaura al terminar; no necesita DATABASE_URL ni PostgreSQL
externos.
Carreras de cámara y análisis¶
La regresión determinista de concurrencia se ejecuta sin cámara física, sin fotografías clínicas y sin archivos de Git LFS:
PYTHON=python3.11 npx playwright test \
playwright/e2e/marker-capture-races.spec.ts \
playwright/e2e/analysis-lines.spec.ts \
--project=chromium \
--grep 'stale toolbar|latest same-region' \
--workers=1
marker-capture-races.spec.ts simula el plumbing de cámara del navegador con
un stream generado por canvas, controla por separado las promesas de la
detección de toolbar y de la captura, y fuerza ambos órdenes de resolución. En
los dos casos comprueba que sólo la captura más nueva llega a la revisión,
IndexedDB y la recarga.
analysis-lines.spec.ts fuerza dos respuestas de la misma región lateral en
orden inverso, tanto a derecha como a izquierda. Comprueba que la última
solicitud gana en DOM, resultados, rectas persistidas y recarga. Para una
prueba de estabilidad focal se puede agregar --repeat-each=5.
Esta simulación demuestra aislamiento de operaciones, persistencia y restauración. No sustituye la validación de permisos, orientación, encuadre, resolución ni auto-captura con una cámara física en el dispositivo objetivo.
Lane clínica con una persona real¶
Para la sesión interna del 2026-08-01 Luis autorizó usar localmente las fotos enviadas por Cristina. No se copió, exportó ni versionó material de pacientes y no se modificó Git LFS. Los gates funcionales se repitieron sobre el fixture sintético schema 2 posterior a la decisión B y prueban el nuevo contrato técnico de grilla y calibración. No convierten el fixture en un gold clínico.
No agregar una nueva fotografía real al repositorio ni a Git LFS. El fixture clínico debe vivir en almacenamiento restringido y tener aprobación explícita. Antes de abrirlo, verificar un manifest equivalente a:
fixtureId: eppa-anterior-001
sha256: "<sha256 aprobado>"
width: 1200
height: 2200
view: anterior
metadataStripped: true
approvalRef: "<referencia sin PII>"
consentOrEthicsRef: "<referencia sin PII>"
calibration:
knownCm: 10
orientation: horizontal
screenSide: left
placement: near-hip
gridSmallSquareCm: 5
gridSquareSpan: 2
p1: {x: 0, y: 0}
p2: {x: 0, y: 0}
reference:
semantics: malleoli-midpoint-lrv
p1: {x: 0, y: 0}
p2: {x: 0, y: 0}
landmarks:
- id: "12"
name: Eminencia Frontal Media
x: 0
y: 0
tolerancePx: 0
oracle:
sourceVersion: "<versión independiente>"
variables: []
El gold debe provenir de dos evaluadores calificados independientes y una adjudicación. El detector bajo prueba no puede crear ni corregir ese gold.
Criterios de aceptación clínica¶
- SHA-256, dimensiones y metadata coinciden con el manifest aprobado.
- Cada identidad se compara con su tolerancia individual en píxeles.
- Se informa coverage, precisión, error por punto y fallback manual.
- Calibración, referencia, signos, unidades y resultados se comparan con el oracle independiente.
- Se repite el flujo en las cuatro vistas.
- La cámara real se valida separadamente en el dispositivo objetivo: permisos, orientación, encuadre y auto-captura no quedan demostrados por un upload.
Hasta completar esa lane, el estado correcto es:
Pipeline funcional y paridad numérica MATLAB verificables sobre el atlas sintético actualizado con la decisión B; no es validación clínica. La calibración está confirmada y revalidada técnicamente a la izquierda de pantalla en las cuatro vistas. Cualquier uso sobre personas requiere validación clínica independiente.