---
name: depth_to_relief_glb
kind: function
lang: py
domain: datascience
version: "1.1.0"
purity: impure
signature: "def depth_to_relief_glb(image: Image.Image, depth: np.ndarray, out_glb_path: str, z_scale: float = 0.35, max_dim: int = 220, mask: np.ndarray | None = None) -> dict"
description: "Construye una malla de relieve (heightmap) texturizada a partir de un mapa de profundidad + la imagen original y la exporta como glTF binario (.glb). El depth se vuelve el eje Z de un grid regular de vertices y la imagen se mapea como textura UV. Con mask opcional recorta la malla al objeto (descarta las caras del fondo). Paso 2 del flujo img->3D (grupo img-to-3d): consume la salida de estimate_image_depth y, opcionalmente, la mask de remove_background."
tags: [img-to-3d, datascience, mesh, glb, gltf, relief, heightmap, trimesh, 3d, texture]
uses_functions: []
uses_types: []
returns: []
returns_optional: false
error_type: "error_go_core"
imports: []
params:
  - name: image
    desc: "PIL.Image RGB usada como textura de la malla. Tipicamente la 'image' devuelta por estimate_image_depth (la imagen original)."
  - name: depth
    desc: "ndarray HxW float32 en [0,1] (1=mas cerca). Tipicamente el 'depth' devuelto por estimate_image_depth. Si ndim != 2 se devuelve status error."
  - name: out_glb_path
    desc: "Ruta de salida del .glb. El directorio padre debe existir (si no, la exportacion de trimesh falla -> status error)."
  - name: z_scale
    desc: "Amplitud del relieve como fraccion del lado de la malla (default 0.35). Mayor = relieve mas pronunciado/exagerado."
  - name: max_dim
    desc: "Lado maximo del grid tras downsample bilineal (default 220, ~48k vertices / ~96k caras). Controla resolucion de la malla vs tamano del .glb. Imagenes mayores se reducen; menores se dejan igual."
  - name: mask
    desc: "Mascara opcional HxW (0..255, 255=objeto), tipicamente la 'mask' de remove_background. Si se pasa, se reescala al grid (NEAREST), el fondo se aplana a Z=0 y las caras cuyos tres vertices caen en el fondo se descartan: la malla queda recortada al objeto. None (default) = malla del frame completo (relieve incluido el fondo)."
output: "dict. Exito: {status:'ok', glb_path:str, vertices:int, faces:int, height:int, width:int}. Con mask, 'faces' es menor (solo caras del objeto); 'vertices' no cambia (el grid completo se conserva). Error: {status:'error', error:str} (depth con forma invalida, directorio de salida inexistente, fallo de trimesh.export). No lanza."
tested: false
tests: []
test_file_path: ""
file_path: "python/functions/datascience/depth_to_relief_glb.py"
---

## Ejemplo

```python
# Requiere un venv con torch + transformers + trimesh + pillow (el de apps/img_to_3d_webapp/backend/.venv).
# Import PLANO a los modulos (el paquete datascience.__init__ arrastra deps de otros dominios).
import sys
sys.path.insert(0, "python/functions/datascience")
from estimate_image_depth import estimate_image_depth
from depth_to_relief_glb import depth_to_relief_glb

est = estimate_image_depth("apps/img_to_3d_webapp/samples/cats.jpg")
assert est["status"] == "ok"

res = depth_to_relief_glb(est["image"], est["depth"], "/tmp/cats_relief.glb", z_scale=0.35, max_dim=220)
print(res["status"], res["vertices"], res["faces"])   # ok 48400 96114
print(res["glb_path"])                                 # /tmp/cats_relief.glb (cargable con useGLTF/GLTFLoader)
```

Lanzable end-to-end (el demo CLI encadena estimate_image_depth internamente):

```bash
./fn run depth_to_relief_glb_py_datascience apps/img_to_3d_webapp/samples/cats.jpg /tmp/cats_relief.glb
# {"status": "ok", "glb_path": "/tmp/cats_relief.glb", "vertices": ..., "faces": ..., ...}
```

## Cuando usarla

Tras `estimate_image_depth`, cuando quieras un modelo 3D real (no solo el mapa de profundidad):
visualizar una foto en relieve navegable, exportar a un visor web (three.js `useGLTF`/`GLTFLoader`,
Babylon, model-viewer) o a cualquier herramienta que lea glTF. Es el paso 2 (final) del grupo
`img-to-3d`. Usa `max_dim` para equilibrar detalle vs peso del .glb y `z_scale` para exagerar o
suavizar el relieve.

## Gotchas

- **Impura**: escribe el archivo .glb en `out_glb_path`. El directorio padre debe existir o
  `trimesh.export` falla (vuelve como status error, no crash).
- **Dep**: requiere `trimesh` (4.5.x) + `pillow` + `numpy`. `trimesh` se importa dentro de la
  funcion. No esta en el venv del registry; vive en el venv de la app `img_to_3d_webapp`.
- **No es reconstruccion real de geometria**: es un heightmap (relieve 2.5D). Solo deforma un
  plano segun la profundidad; no recupera las caras ocultas ni el volumen trasero del objeto.
- El downsample a `max_dim` usa interpolacion bilineal sobre el depth cuantizado a uint8 (0-255)
  para reescalar; introduce una ligera perdida de precision en la profundidad de la malla.
- UV con V invertido (`1 - v`) por convencion glTF; la textura es la imagen convertida a RGB.
- `process=False` en Trimesh: no se hace merge de vertices ni limpieza, para preservar la
  correspondencia 1:1 vertice<->pixel (necesaria para el mapeo UV del grid).
- **Import plano**: importa el modulo directo, NO `from datascience import ...` (el `__init__` del
  paquete arrastra deps de otros dominios ausentes en el venv de vision). Ver misma gotcha en
  `estimate_image_depth`.
- **mask opcional (v1.1.0)**: pasa la `mask` de `remove_background` para recortar la malla al
  objeto. Se reescala con NEAREST (sin interpolar, preserva el borde binario), el fondo se aplana
  a Z=0 y sus caras se eliminan. El nº de `vertices` no baja (el grid completo se conserva para no
  romper el mapeo UV 1:1); solo baja `faces`. Una mask degenerada (todo objeto) deja la malla
  intacta; una mask vacia (todo fondo) deja la malla sin caras (glb valido pero vacio).

## Capability growth log

- v1.1.0 (2026-06-21) — anade parametro opcional `mask` para recortar la malla al objeto
  (descarta las caras del fondo), cerrando la cadena con `remove_background` del grupo img-to-3d.
  Aditivo: `mask=None` mantiene el comportamiento previo. Fiel al original de `backend/depth.py`.
