feat(datascience): promover remove_background al registry + mask en depth_to_relief_glb (grupo img-to-3d)

Completa la promoción del flujo imagen->3D al registry (grupo de capacidad
img-to-3d), extraído de la app img_to_3d_webapp.

- remove_background_py_datascience (nueva): elimina el fondo con cascada
  rembg/U2Net -> OpenCV GrabCut -> umbral NumPy, compone el objeto sobre gris
  neutro y devuelve image + mask + engine. Impura, nunca lanza. Adaptada de
  backend/bg_removal.py con firma de ruta (image_path) y salida dict, demo CLI
  JSON-serializable.
- depth_to_relief_glb_py_datascience (v1.1.0): añade el parámetro opcional mask
  para recortar la malla de relieve al objeto (descarta las caras del fondo),
  cerrando la cadena con remove_background. Aditivo (mask=None = comportamiento
  previo), fiel al original de backend/depth.py.
- docs/capabilities/img-to-3d.md: incorpora remove_background como paso 0
  (pre-proceso), actualiza el flujo a 3 pasos encadenados, la tabla de funciones
  (4), el ejemplo end-to-end con mask y las deps (rembg/opencv).
- docs/capabilities/INDEX.md: conteo del grupo 3 -> 4.

Las dos funciones ya presentes (estimate_image_depth, depth_to_relief_glb) y el
pipeline build_relief_glb_from_image fueron promovidas en una ronda previa.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-21 21:43:08 +02:00
parent cbefc82c02
commit 3cf8b21fea
6 changed files with 368 additions and 21 deletions
+30 -16
View File
@@ -10,24 +10,27 @@ partir de una sola foto se estima un mapa de profundidad monocular con un modelo
reconstruye una malla de relieve (heightmap) texturizada con la imagen original, exportada como
`.glb` cargable por cualquier visor glTF (three.js `useGLTF`/`GLTFLoader`, Babylon, model-viewer).
Promovido desde la app `img_to_3d_webapp` (su backend incrustaba estas dos funciones; ver su
`backend/depth.py`). El flujo canonico es de **dos pasos encadenados**:
Promovido desde la app `img_to_3d_webapp` (su backend incrustaba estas funciones; ver
`backend/depth.py` y `backend/bg_removal.py`). El flujo canonico encadena un pre-proceso opcional
de fondo con los dos pasos de reconstruccion:
```
estimate_image_depth (imagen -> depth+image) -> depth_to_relief_glb (depth+image -> .glb)
[remove_background (imagen -> rgb+mask)] -> estimate_image_depth (imagen -> depth+image) -> depth_to_relief_glb (depth+image[+mask] -> .glb)
```
## Funciones
| ID | Firma corta | Que hace |
|---|---|---|
| `remove_background_py_datascience` | `remove_background(image_path, engine?) -> dict` | **Pre-proceso (paso 0).** Elimina el fondo en cascada rembg -> GrabCut -> umbral y compone el objeto sobre gris neutro. Devuelve `image` PIL + `mask` ndarray. La `mask` se pasa a `depth_to_relief_glb` para recortar la malla al objeto. |
| `estimate_image_depth_py_datascience` | `estimate_image_depth(image_path, model_name?, device?, use_cache?) -> dict` | Estima profundidad monocular con Depth-Anything-V2 (GPU/CPU). Devuelve `depth` ndarray [0,1] + `image` PIL. Paso 1. |
| `depth_to_relief_glb_py_datascience` | `depth_to_relief_glb(image, depth, out_glb_path, z_scale?, max_dim?) -> dict` | Convierte `depth`+`image` en una malla de relieve texturizada y la exporta a `.glb`. Paso 2. |
| `build_relief_glb_from_image_py_pipelines` | `build_relief_glb_from_image(image_path, out_glb_path, model_name?, device?, z_scale?, max_dim?) -> dict` | **Pipeline one-shot**: compone los dos pasos en una sola llamada (imagen -> .glb). Salida JSON-serializable, apta para `fn run`. |
| `depth_to_relief_glb_py_datascience` | `depth_to_relief_glb(image, depth, out_glb_path, z_scale?, max_dim?, mask?) -> dict` | Convierte `depth`+`image` en una malla de relieve texturizada y la exporta a `.glb`. Con `mask` opcional recorta las caras del fondo. Paso 2. |
| `build_relief_glb_from_image_py_pipelines` | `build_relief_glb_from_image(image_path, out_glb_path, model_name?, device?, z_scale?, max_dim?) -> dict` | **Pipeline one-shot**: compone estimacion + relieve en una sola llamada (imagen -> .glb). Salida JSON-serializable, apta para `fn run`. |
Las tres son **impuras** (cargan modelo / GPU / escriben archivo), devuelven `dict` con `status`
Las cuatro son **impuras** (cargan modelo / GPU / escriben archivo), devuelven `dict` con `status`
(`ok`/`error`) y **nunca lanzan**: los fallos vuelven como `{status:'error', error:str}`. El
pipeline ademas marca `stage` (`estimate`/`relief`) en el error.
pipeline ademas marca `stage` (`estimate`/`relief`) en el error. `remove_background` en
`engine="auto"` nunca falla (cae al umbral NumPy puro sin deps externas).
## Ejemplo canonico (end-to-end imagen → glb)
@@ -37,17 +40,24 @@ pipeline ademas marca `stage` (`estimate`/`relief`) en el error.
# ausentes en el venv de vision. Ver "Fronteras / gotchas".
import sys
sys.path.insert(0, "python/functions/datascience")
from remove_background import remove_background
from estimate_image_depth import estimate_image_depth
from depth_to_relief_glb import depth_to_relief_glb
IMG = "apps/img_to_3d_webapp/samples/cats.jpg"
OUT = "/tmp/cats_relief.glb"
# Paso 0 (opcional pero recomendado): aislar el objeto del fondo. La mask recorta la malla.
cut = remove_background(IMG) # engine='auto' -> rembg -> grabcut -> umbral
assert cut["status"] == "ok"
print(cut["engine"], cut["fg_fraction"]) # p.ej. rembg:u2net 0.42
est = estimate_image_depth(IMG) # device='auto' -> GPU si hay
assert est["status"] == "ok"
# est["depth"]: ndarray HxW float32 [0,1] (1=mas cerca) | est["image"]: PIL.Image RGB
res = depth_to_relief_glb(est["image"], est["depth"], OUT, z_scale=0.35, max_dim=220)
# Pasando la mask del paso 0, las caras del fondo se descartan: malla solo del objeto.
res = depth_to_relief_glb(est["image"], est["depth"], OUT, z_scale=0.35, max_dim=220, mask=cut["mask"])
assert res["status"] == "ok"
print(res["glb_path"], res["vertices"], res["faces"]) # /tmp/cats_relief.glb 36300 71832
# OUT es un glTF binario valido: trimesh.load(OUT) devuelve una Scene texturizada.
@@ -70,15 +80,19 @@ O en una sola llamada con el pipeline (recomendado para fn run / Launcher TUI):
- **No cubre el render/visualizacion.** Producir el `.glb` es el limite del grupo. Cargarlo y
subirlo a GPU (OpenGL) en una app C++/ImGui es el grupo **`mesh-3d`** (`gltf_load_mesh_cpp_gfx`
carga justamente este tipo de `.glb`). img-to-3d **produce**; mesh-3d **consume/renderiza**.
- **Deps pesadas y de dos mundos.** Requiere `torch`+`transformers` (vision) y `trimesh` (mesh),
que hoy viven en el venv de `img_to_3d_webapp`, NO en el venv del registry. Ademas el
`datascience.__init__` arrastra deps de scrapers (`bs4`...) que no estan en el venv de vision,
por eso el import es **plano** (al modulo) y no via el paquete. `fn run` de estas funciones
exige un venv que combine ambos mundos (torch + transformers + trimesh + las deps del dominio
datascience). Ver gotchas en cada `.md`.
- **Deps pesadas y de dos mundos.** Requiere `torch`+`transformers` (vision), `trimesh` (mesh) y,
para `remove_background`, `rembg`+`onnxruntime` (segmentacion) y `opencv-python` (GrabCut) —
todas opcionales: el umbral de `remove_background` es NumPy puro. Hoy viven en el venv de
`img_to_3d_webapp`, NO en el venv del registry. Ademas el `datascience.__init__` arrastra deps
de scrapers (`bs4`...) que no estan en el venv de vision, por eso el import es **plano** (al
modulo) y no via el paquete. `fn run` de estas funciones exige un venv que combine ambos mundos
(torch + transformers + trimesh + rembg/opencv + las deps del dominio datascience). Ver gotchas
en cada `.md`.
## Prerequisitos
- GPU NVIDIA + CUDA recomendada (corre en CPU pero lento). Primera ejecucion descarga los pesos
del modelo a `~/.cache/huggingface/` (cientos de MB segun la variante).
- Paquetes: `torch`, `transformers`, `trimesh`, `pillow`, `numpy`.
del modelo de profundidad a `~/.cache/huggingface/` y el de `rembg` (U2Net ~170 MB) a su cache.
- Paquetes: `torch`, `transformers`, `trimesh`, `pillow`, `numpy`. Para el recorte de fondo de
mayor calidad: `rembg` (+`onnxruntime`) y `opencv-python` (ambos opcionales; sin ellos
`remove_background` cae al umbral NumPy).