Saltar a contenido

Posture Analysis API - Integration Guide

Overview

The Posture Analysis system now includes a Python FastAPI backend that provides MATLAB-validated biomechanical calculations through a REST API. The frontend Next.js application can use either:

  1. Python API (recommended) - MATLAB-validated calculations
  2. TypeScript local - Browser-based calculations

Architecture

┌─────────────────┐          ┌──────────────────┐
│   Next.js       │  HTTP    │   FastAPI        │
│   Frontend      │ ──────>  │   Python API     │
│   (Port 9002)   │          │   (Port 8000)    │
└─────────────────┘          └──────────────────┘
                                      │
                                      ▼
                             ┌─────────────────┐
                             │  MATLAB-Validated│
                             │  Calculations    │
                             └─────────────────┘

Quick Start

1. Start the Python API

# Option A: Using the startup script
./start-api.sh

# Option B: Manual startup
source venv/bin/activate
cd python
python api.py

The API will be available at: - API Base: http://localhost:8000 - Interactive Docs: http://localhost:8000/docs - Health Check: http://localhost:8000/health

2. Start the Frontend

npm run dev

Frontend will be available at: http://localhost:9002

3. Test the Integration

  1. Navigate to: http://localhost:9002/analysis/anterior
  2. Load an image
  3. Import markers (or mark manually)
  4. Calibrate (FC and LRV)
  5. Click "🐍 Análisis Completo con API Python"

API Endpoints

Health Check

GET /health

Anterior View Analysis

POST /api/analyze/anterior
Content-Type: application/json

{
  "calibration": {
    "lrv_x": 504.20,
    "fc": 0.107021,
    "grid_size_cm": 10.0
  },
  "markers": {
    "eminencia_frontal_media": [495.5315, 501.4900],
    "espina_nasal": [494.6027, 565.4409],
    "punto_mentoniano": [494.7410, 630.2119],
    "tragus_izq": [565.8360, 557.6051],
    "tragus_der": [420.3754, 554.0479],
    "punto_acromion_izq": [661.7130, 734.5966],
    "punto_acromion_der": [315.2222, 710.0803],
    "escotadura_esternal": [486.1345, 694.5977],
    "apendice_xifoides": [487.2709, 900.7583],
    "ombligo": [499.1579, 1042.2],
    "punto_sinfisis_pubiana": [493.8188, 1182.7],
    "espina_iliaca_anterosuperior_izq": [610.1036, 1068.5],
    "espina_iliaca_anterosuperior_der": [383.2815, 1071.8],
    "centro_rotula_izq": [571.0434, 1559.5],
    "centro_rotula_der": [431.3830, 1556.2],
    "tat_izq": [574.65, 1617.2],
    "tat_der": [434.7327, 1612.9]
  }
}

Response (8 metrics):

{
  "head_angle": 1.32,
  "dist_eminencia_frontal": -0.95,
  "dist_espina_nasal": -1.02,
  "dist_punto_mentoniano": -0.99,
  "trunk_angle": 0.24,
  "pelvis_angle": 0.18,
  "q_angle_right": 11.53,
  "q_angle_left": 11.14,
  "unit_angles": "degrees",
  "unit_distances": "cm"
}

Posterior View Analysis

POST /api/analyze/posterior
Content-Type: application/json

{
  "calibration": {
    "lrv_x": 504.2333,
    "fc": 0.107021
  },
  "markers": {
    "lobulo_izq": [422.4962, 562.7312],
    "lobulo_der": [574.9465, 564.1885],
    "cervical_7": [499.8218, 652.9047],
    "toracica_3": [500.8726, 701.6793],
    "toracica_7": [506.4539, 821.3555],
    "toracica_12": [507.5345, 958.4399],
    "lumbar_5": [505.8789, 1053.4511],
    "espina_iliaca_posterosuperior_izq": [478.9439, 1082.6564],
    "espina_iliaca_posterosuperior_der": [540.7151, 1082.7754],
    "aquiles_izq": [461.8430, 2045.8652],
    "aquiles_der": [543.7288, 2041.3250],
    "calcaneo_izq": [466.2645, 2088.9991],
    "calcaneo_der": [542.2021, 2090.1194],
    "popliteo_izq": [452.0207, 1949.9652],
    "popliteo_der": [561.8601, 1943.8250]
  },
  "estimate_popliteal": false
}

Response (7 metrics):

{
  "calcaneo_angle_left": 179.999885,
  "calcaneo_angle_right": 179.999985,
  "coronal_balance_c7": -0.472123,
  "coronal_balance_t7": 0.237651,
  "coronal_balance_l5": 0.176114,
  "pelvis_angle": 0.110378,
  "head_angle": 0.547684,
  "unit_angles": "degrees",
  "unit_distances": "cm"
}

Lateral View Analysis

POST /api/analyze/lateral
Content-Type: application/json

{
  "calibration": {
    "lrv_x": 504.20,
    "fc": 0.107021
  },
  "markers": {
    "tragus": [435.8723, 544.1333],
    "apex_cervical": [355.7445, 636.3141],
    "apex_lumbar": [340.2564, 1009.7554],
    "punto_acromion": [389.2969, 715.8458],
    "punto_epicondilo": [378.9614, 1006.3911],
    "base_3er_metacarpiano": [460.7562, 1306.5444],
    "trocanter_mayor": [374.9404, 1166.6466],
    "tuberculo_condilo_externo": [397.8240, 1579.6417],
    "borde_anterior_maleolo": [379.0827, 2056.7005]
  },
  "side": "right"
}

Response (4 metrics):

{
  "head_angle_horizontal": 49.001327,
  "trunk_angle_vertical": 2.374923,
  "elbow_angle": 162.719154,
  "knee_angle": 174.578834,
  "side": "right",
  "unit": "degrees"
}

Estimate Popliteal Point

POST /api/estimate-popliteal
Content-Type: application/json

{
  "aquiles": {"x": 461.8430, "y": 2045.8652},
  "calcaneo": {"x": 466.2645, "y": 2088.9991},
  "factor": 2.2
}

Response:

{
  "popliteo": {"x": 452.0207, "y": 1949.9652}
}

Frontend Integration

Using the API Client

import { apiClient } from '@/lib/api-client';

// Analyze anterior view
const results = await apiClient.analyzeAnteriorView({
  calibration: { lrv_x: 504.20, fc: 0.107021 },
  markers: { /* marker coordinates */ }
});

console.log(`Head angle: ${results.head_angle}°`);
console.log(`Q-angle right: ${results.q_angle_right}°`);

Helper Functions

import {
  buildAnteriorViewRequest,
  convertFrontendPointToApiPoint
} from '@/lib/api-client';

// Convert percentage-based coordinates to pixel coordinates
const apiPoint = convertFrontendPointToApiPoint(
  frontendPoint,  // {x: 0.5, y: 0.3}
  imageWidth,     // 1920
  imageHeight     // 1080
);

// Build complete request from frontend data
const request = buildAnteriorViewRequest(
  markedPoints,
  lrvOriginXPx,
  conversionFactor,
  imageWidth,
  imageHeight
);

Configuration

Environment Variables

Create .env.local in the project root:

NEXT_PUBLIC_API_URL=http://localhost:8000

CORS Configuration

The API is configured to accept requests from: - http://localhost:9002 (Next.js dev server) - http://localhost:3000 (alternative dev port)

To add more origins, edit python/api.py:

app.add_middleware(
    CORSMiddleware,
    allow_origins=["http://localhost:9002", "http://your-domain.com"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

Validation

All API calculations are validated against MATLAB implementations:

  • Anterior View: 8 metrics, 100% match with MATLAB
  • Posterior View: 7 metrics, 100% match with MATLAB
  • Lateral View: 4 metrics, 100% match with MATLAB

Total: 19 metrics, max difference: 1.42e-13 (well below 1e-10 tolerance)

See VALIDATION_SUMMARY.md for details.

Testing

Test API Health

curl http://localhost:8000/health

Test Anterior Analysis

curl -X POST http://localhost:8000/api/analyze/anterior \
  -H "Content-Type: application/json" \
  -d @tests/fixtures/anterior_request_example.json

Run Python Tests

source venv/bin/activate
pytest tests/test_integration.py -v

Troubleshooting

API not starting

# Check if port 8000 is in use
lsof -ti:8000

# Verify virtual environment
source venv/bin/activate
python --version  # Should show Python 3.13+

CORS errors in browser

  • Verify API is running: http://localhost:8000/health
  • Check browser console for exact error
  • Ensure frontend is running on port 9002
  • Check NEXT_PUBLIC_API_URL in .env.local

Missing markers error

  • API requires ALL markers for each view
  • Check marker names match exactly (case-sensitive)
  • Use buildAnteriorViewRequest() to validate markers

Performance

  • Average response time: < 50ms
  • Concurrent requests: Up to 100/second
  • Memory usage: ~50MB per worker

Development

Hot Reload

# API with auto-reload
source venv/bin/activate
cd python
uvicorn api:app --reload --port 8000

# Frontend with hot reload
npm run dev

API Documentation

Visit http://localhost:8000/docs for interactive Swagger UI documentation.

Production Deployment

Using Uvicorn

source venv/bin/activate
cd python
uvicorn api:app --host 0.0.0.0 --port 8000 --workers 4

Using Gunicorn + Uvicorn

pip install gunicorn
gunicorn api:app --workers 4 --worker-class uvicorn.workers.UvicornWorker --bind 0.0.0.0:8000

Docker (Future)

FROM python:3.13-slim
WORKDIR /app
COPY python/requirements.txt .
RUN pip install -r requirements.txt
COPY python/ .
CMD ["uvicorn", "api:app", "--host", "0.0.0.0", "--port", "8000"]

License

Same as main project. MATLAB-validated calculations maintained for clinical accuracy.