Saltar a contenido

Prueba guiada de landmarks, calibración y referencia

Qué demuestra este protocolo

EPPA separa dos afirmaciones que no deben confundirse:

  1. 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.
  2. 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:

  1. Un operador entrenado localiza cada landmark indicado por la vista.
  2. Coloca un sticker verde pequeño centrado sobre el punto.
  3. La cámara se mantiene nivelada, fija y perpendicular al plano de análisis.
  4. El detector segmenta los stickers y propone una identidad según su geometría.
  5. Las estimaciones de pose, cuando existen, se muestran como aproximaciones y nunca reemplazan la identificación clínica.
  6. El operador revisa cada propuesta sobre la foto:
  7. desplaza un punto que no esté centrado;
  8. reemplaza manualmente una identidad incorrecta;
  9. usa Marcar siguiente faltante hasta completar la vista;
  10. pulsa Confirmar revisión.
  11. 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, reviewed y DetectionRun conservados;
  • 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 como Pendiente.

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.