---
name: remove_background
kind: function
lang: py
domain: datascience
version: "1.0.0"
purity: impure
signature: "def remove_background(image_path: str, engine: str = 'auto') -> dict"
description: "Elimina el fondo de una imagen con cascada de motores (rembg/U2Net -> OpenCV GrabCut -> umbral NumPy), compone el objeto sobre fondo gris neutro y devuelve image+mask+engine. Paso de pre-proceso del flujo img->3D (grupo img-to-3d): su mask alimenta depth_to_relief_glb para recortar la malla de relieve al objeto."
tags: [img-to-3d, datascience, background-removal, segmentation, rembg, grabcut, opencv, computer-vision, mask]
uses_functions: []
uses_types: []
returns: []
returns_optional: false
error_type: "error_go_core"
imports: []
params:
  - name: image_path
    desc: "Ruta a la imagen de entrada. Cualquier formato que PIL.Image.open abra (jpg, png, webp, RGBA...). Si no existe o no es imagen valida, se devuelve status error. Un PNG RGBA ya recortado se reaprovecha en modo auto (passthrough:alpha)."
  - name: engine
    desc: "Motor de segmentacion. 'auto' (default) prueba en cascada rembg:u2net -> opencv:grabcut -> threshold:border y NUNCA falla (cae al umbral NumPy puro sin deps externas). Forzar uno: 'rembg' (red neuronal U2Net, mejor calidad, deps pesadas), 'grabcut' (OpenCV, rectangulo central), 'threshold' (distancia al color medio de los bordes, NumPy puro, objeto centrado). Si se fuerza un motor y no esta disponible/falla o produce mascara degenerada -> status error."
output: "dict. Exito: {status:'ok', image: PIL.Image RGB del objeto compuesto sobre fondo gris neutro (127,127,127), mask: ndarray HxW uint8 (0..255, 255=objeto), engine: str del motor usado ('rembg:u2net' | 'opencv:grabcut' | 'threshold:border' | 'passthrough:alpha'), height:int, width:int, fg_fraction: float (fraccion de pixeles objeto, redondeada a 4 decimales)}. Error: {status:'error', error:str} (ruta invalida, motor desconocido, motor forzado no disponible/fallido, o ningun motor produjo una mascara valida). No lanza nunca. El demo CLI (__main__) imprime un resumen JSON sin el ndarray ni la imagen y, si se pasa out_dir, guarda rgb.png + mask.png."
tested: false
tests: []
test_file_path: ""
file_path: "python/functions/datascience/remove_background.py"
source_file: "apps/img_to_3d_webapp/backend/bg_removal.py"
---

## Ejemplo

```python
# Requiere un venv con pillow + numpy (rembg/opencv solo si fuerzas esos motores; el umbral es NumPy puro).
# Import PLANO al modulo: el paquete datascience.__init__ arrastra deps de otros dominios
# (bs4, duckdb...) que no estan en ese venv. Ver Gotchas.
import sys
sys.path.insert(0, "python/functions/datascience")
from remove_background import remove_background

res = remove_background("apps/img_to_3d_webapp/samples/cats.jpg", engine="auto")
assert res["status"] == "ok"
print(res["engine"])                 # p.ej. "rembg:u2net" (o "opencv:grabcut" / "threshold:border")
print(res["height"], res["width"])   # p.ej. 1024 768
print(res["mask"].shape, res["mask"].dtype)   # (1024, 768) uint8 (255=objeto)
assert 0.0 < res["fg_fraction"] < 1.0
# res["mask"] (ndarray HxW uint8) alimenta depth_to_relief_glb para recortar la malla al objeto.
# res["image"] es el objeto compuesto sobre gris neutro, listo para estimar profundidad.
```

Lanzable como demo (imprime resumen JSON, sin serializar el ndarray; guarda PNGs si das out_dir):

```bash
./fn run remove_background_py_datascience apps/img_to_3d_webapp/samples/cats.jpg auto /tmp/cut
# {"status": "ok", "engine": "rembg:u2net", "height": 1024, "width": 768,
#  "fg_fraction": 0.4123, "rgb_path": "/tmp/cut/rgb.png", "mask_path": "/tmp/cut/mask.png"}
```

## Cuando usarla

Como pre-proceso ANTES de estimar profundidad en el flujo img->3D: aislar el objeto evita que el
modelo de profundidad estire el fondo plano, y la `mask` permite recortar la malla de relieve al
objeto (se pasa a `depth_to_relief_glb`). Tambien para segmentacion de primer plano generica
cuando necesitas separar un objeto de su fondo y componerlo sobre un color neutro (recortes para
catalogos, datasets, miniaturas).

## Gotchas

- **Impura**: segun el motor carga modelos neuronales y lee disco. `rembg`/`onnxruntime` (~170MB)
  DESCARGA el modelo U2Net la primera vez a su cache (`~/.u2net/`), requiere red en esa primera
  carga; `opencv-python` para GrabCut; el umbral (`threshold:border`) es NumPy puro sin deps externas.
- **Estado de proceso**: `_REMBG_SESSION` cachea la sesion rembg a nivel de modulo para no recargar
  los pesos en cada llamada. Es estado mutable compartido del proceso y ocupa RAM hasta que el
  interprete muere.
- **engine='auto' nunca lanza**: prueba rembg -> grabcut -> threshold y siempre cae al umbral NumPy
  puro si los anteriores no estan disponibles o fallan. Forzar un motor concreto SI puede devolver
  status error (motor no instalado, fallo, o mascara degenerada).
- **Mascara degenerada**: si la fraccion de objeto resulta `< 0.01` o `> 0.995` la mascara se
  descarta (casi todo fondo o casi todo objeto) y en modo auto se prueba el siguiente motor.
- **threshold:border es de baja calidad**: asume objeto centrado con los bordes de la imagen siendo
  fondo (calcula la distancia al color medio de los bordes). Es el fallback de ultimo recurso.
- **passthrough:alpha**: si la imagen ya viene recortada (PNG RGBA con alfa por debajo de 128) se
  reutiliza su canal alfa como mascara, SOLO en modo auto. Si fuerzas un motor concreto se respeta
  esa eleccion e ignora el alfa existente.
- **Import plano**: importa el modulo directo (`sys.path` a `python/functions/datascience` +
  `from remove_background import remove_background`), NO `from datascience import ...`. El
  `datascience.__init__` carga todo el dominio (scrapers con bs4, duckdb...) con deps ajenas a esta
  funcion que romperian el import del paquete en el venv de vision.
- Nunca lanza: errores (ruta invalida, motor forzado no disponible, OOM) vuelven como
  `{status:'error', error:str}`.
