chore: auto-commit (5 archivos)
- CMakeLists.txt - app.md - appicon.ico - backend/ - main.cpp Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,17 @@
|
||||
"""Interfaz comun para backends image-to-3D.
|
||||
|
||||
Cada backend (triposr, hunyuan3d_2, trellis) expone:
|
||||
load() -> Handle
|
||||
donde Handle tiene metodo infer(image: PIL.Image, cfg: dict) -> bytes (GLB).
|
||||
|
||||
`cfg` recibe: seed, mc_resolution, foreground_ratio, texture.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Protocol, Any, Dict
|
||||
from PIL import Image
|
||||
|
||||
|
||||
class BackendHandle(Protocol):
|
||||
def infer(self, image: Image.Image, cfg: Dict[str, Any]) -> bytes: ...
|
||||
def close(self) -> None: ...
|
||||
@@ -0,0 +1,23 @@
|
||||
"""Backend Hunyuan3D-2 (Tencent, Community License).
|
||||
|
||||
STUB. Para implementar: clonar github.com/Tencent/Hunyuan3D-2 a sources/,
|
||||
instalar deps, cargar pipeline shape + texture.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict
|
||||
from PIL import Image
|
||||
|
||||
|
||||
class Handle:
|
||||
def infer(self, image: Image.Image, cfg: Dict[str, Any]) -> bytes:
|
||||
raise NotImplementedError(
|
||||
"hunyuan3d-2 backend pendiente. Ver notebook 03_smoke_hunyuan3d.ipynb"
|
||||
)
|
||||
|
||||
def close(self) -> None: # pragma: no cover
|
||||
return
|
||||
|
||||
|
||||
def load() -> Handle:
|
||||
return Handle()
|
||||
@@ -0,0 +1,23 @@
|
||||
"""Backend Trellis (Microsoft, MIT code).
|
||||
|
||||
STUB. Para implementar: clonar github.com/microsoft/TRELLIS a sources/,
|
||||
instalar deps (kaolin + custom CUDA), cargar pipeline structured latents.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict
|
||||
from PIL import Image
|
||||
|
||||
|
||||
class Handle:
|
||||
def infer(self, image: Image.Image, cfg: Dict[str, Any]) -> bytes:
|
||||
raise NotImplementedError(
|
||||
"trellis backend pendiente. Ver notebook 04_smoke_trellis.ipynb"
|
||||
)
|
||||
|
||||
def close(self) -> None: # pragma: no cover
|
||||
return
|
||||
|
||||
|
||||
def load() -> Handle:
|
||||
return Handle()
|
||||
@@ -0,0 +1,93 @@
|
||||
"""Backend TripoSR (Stability + Tripo, MIT).
|
||||
|
||||
Asume que `sources/TripoSR` esta clonado en el registry. Importa `tsr.system.TSR`.
|
||||
Descarga checkpoint desde HF en la primera carga (~1.2 GB).
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import io
|
||||
import os
|
||||
import pathlib
|
||||
import sys
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Dict
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
import trimesh
|
||||
from PIL import Image
|
||||
|
||||
|
||||
def _ensure_sources_on_path() -> pathlib.Path:
|
||||
root = pathlib.Path(os.environ.get("FN_REGISTRY_ROOT", "/home/lucas/fn_registry"))
|
||||
src = root / "sources" / "TripoSR"
|
||||
if not src.exists():
|
||||
raise RuntimeError(
|
||||
f"TripoSR no clonado en {src}. "
|
||||
"git clone --depth=1 https://github.com/VAST-AI-Research/TripoSR.git "
|
||||
f"{src}"
|
||||
)
|
||||
if str(src) not in sys.path:
|
||||
sys.path.insert(0, str(src))
|
||||
return src
|
||||
|
||||
|
||||
@dataclass
|
||||
class Handle:
|
||||
model: Any
|
||||
rembg_session: Any
|
||||
device: str
|
||||
|
||||
def infer(self, image: Image.Image, cfg: Dict[str, Any]) -> bytes:
|
||||
from tsr.utils import remove_background, resize_foreground
|
||||
|
||||
fg_ratio = float(cfg.get("foreground_ratio", 0.85))
|
||||
mc_res = int(cfg.get("mc_resolution", 256))
|
||||
|
||||
fg = remove_background(image, self.rembg_session)
|
||||
fg = resize_foreground(fg, fg_ratio)
|
||||
|
||||
# Composite RGBA -> RGB sobre gris 0.5 (preprocesado canonico TripoSR
|
||||
# run.py). Sin esto el tokenizer DINO recibe 4 canales y peta:
|
||||
# "The size of tensor a (4) must match tensor b (3) at dim 2".
|
||||
arr = np.asarray(fg).astype(np.float32) / 255.0
|
||||
if arr.shape[-1] == 4:
|
||||
arr = arr[:, :, :3] * arr[:, :, 3:4] + (1.0 - arr[:, :, 3:4]) * 0.5
|
||||
fg = Image.fromarray((arr * 255.0).astype(np.uint8))
|
||||
|
||||
with torch.no_grad():
|
||||
scene_codes = self.model([fg], device=self.device)
|
||||
meshes = self.model.extract_mesh(
|
||||
scene_codes, has_vertex_color=False, resolution=mc_res
|
||||
)
|
||||
m = meshes[0]
|
||||
tm = trimesh.Trimesh(
|
||||
vertices=np.asarray(m.vertices),
|
||||
faces=np.asarray(m.faces),
|
||||
process=True,
|
||||
)
|
||||
buf = io.BytesIO()
|
||||
tm.export(buf, file_type="glb")
|
||||
return buf.getvalue()
|
||||
|
||||
def close(self) -> None:
|
||||
del self.model
|
||||
del self.rembg_session
|
||||
if torch.cuda.is_available():
|
||||
torch.cuda.empty_cache()
|
||||
|
||||
|
||||
def load() -> Handle:
|
||||
_ensure_sources_on_path()
|
||||
from tsr.system import TSR
|
||||
from rembg import new_session
|
||||
|
||||
device = "cuda" if torch.cuda.is_available() else "cpu"
|
||||
model = TSR.from_pretrained(
|
||||
"stabilityai/TripoSR",
|
||||
config_name="config.yaml",
|
||||
weight_name="model.ckpt",
|
||||
)
|
||||
model.renderer.set_chunk_size(8192)
|
||||
model.to(device)
|
||||
return Handle(model=model, rembg_session=new_session(), device=device)
|
||||
Reference in New Issue
Block a user