Spaces:
Runtime error
Runtime error
Upload hy3dgen/texgen/pipelines.py with huggingface_hub
Browse files- hy3dgen/texgen/pipelines.py +191 -0
hy3dgen/texgen/pipelines.py
ADDED
@@ -0,0 +1,191 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Open Source Model Licensed under the Apache License Version 2.0
|
2 |
+
# and Other Licenses of the Third-Party Components therein:
|
3 |
+
# The below Model in this distribution may have been modified by THL A29 Limited
|
4 |
+
# ("Tencent Modifications"). All Tencent Modifications are Copyright (C) 2024 THL A29 Limited.
|
5 |
+
|
6 |
+
# Copyright (C) 2024 THL A29 Limited, a Tencent company. All rights reserved.
|
7 |
+
# The below software and/or models in this distribution may have been
|
8 |
+
# modified by THL A29 Limited ("Tencent Modifications").
|
9 |
+
# All Tencent Modifications are Copyright (C) THL A29 Limited.
|
10 |
+
|
11 |
+
# Hunyuan 3D is licensed under the TENCENT HUNYUAN NON-COMMERCIAL LICENSE AGREEMENT
|
12 |
+
# except for the third-party components listed below.
|
13 |
+
# Hunyuan 3D does not impose any additional limitations beyond what is outlined
|
14 |
+
# in the repsective licenses of these third-party components.
|
15 |
+
# Users must comply with all terms and conditions of original licenses of these third-party
|
16 |
+
# components and must ensure that the usage of the third party components adheres to
|
17 |
+
# all relevant laws and regulations.
|
18 |
+
|
19 |
+
# For avoidance of doubts, Hunyuan 3D means the large language models and
|
20 |
+
# their software and algorithms, including trained model weights, parameters (including
|
21 |
+
# optimizer states), machine-learning model code, inference-enabling code, training-enabling code,
|
22 |
+
# fine-tuning enabling code and other elements of the foregoing made publicly available
|
23 |
+
# by Tencent in accordance with TENCENT HUNYUAN COMMUNITY LICENSE AGREEMENT.
|
24 |
+
|
25 |
+
|
26 |
+
import logging
|
27 |
+
import os
|
28 |
+
|
29 |
+
import numpy as np
|
30 |
+
import torch
|
31 |
+
from PIL import Image
|
32 |
+
|
33 |
+
from .differentiable_renderer.mesh_render import MeshRender
|
34 |
+
from .utils.dehighlight_utils import Light_Shadow_Remover
|
35 |
+
from .utils.multiview_utils import Multiview_Diffusion_Net
|
36 |
+
from .utils.uv_warp_utils import mesh_uv_wrap
|
37 |
+
|
38 |
+
logger = logging.getLogger(__name__)
|
39 |
+
|
40 |
+
|
41 |
+
class Hunyuan3DTexGenConfig:
|
42 |
+
|
43 |
+
def __init__(self, light_remover_ckpt_path, multiview_ckpt_path):
|
44 |
+
self.device = 'cuda'
|
45 |
+
self.light_remover_ckpt_path = light_remover_ckpt_path
|
46 |
+
self.multiview_ckpt_path = multiview_ckpt_path
|
47 |
+
|
48 |
+
self.candidate_camera_azims = [0, 90, 180, 270, 0, 180]
|
49 |
+
self.candidate_camera_elevs = [0, 0, 0, 0, 90, -90]
|
50 |
+
self.candidate_view_weights = [1, 0.1, 0.5, 0.1, 0.05, 0.05]
|
51 |
+
|
52 |
+
self.render_size = 2048
|
53 |
+
self.texture_size = 1024
|
54 |
+
self.bake_exp = 4
|
55 |
+
self.merge_method = 'fast'
|
56 |
+
|
57 |
+
|
58 |
+
class Hunyuan3DPaintPipeline:
|
59 |
+
@classmethod
|
60 |
+
def from_pretrained(cls, model_path):
|
61 |
+
original_model_path = model_path
|
62 |
+
if not os.path.exists(model_path):
|
63 |
+
# try local path
|
64 |
+
base_dir = os.environ.get('HY3DGEN_MODELS', '~/.cache/hy3dgen')
|
65 |
+
model_path = os.path.expanduser(os.path.join(base_dir, model_path))
|
66 |
+
|
67 |
+
delight_model_path = os.path.join(model_path, 'hunyuan3d-delight-v2-0')
|
68 |
+
multiview_model_path = os.path.join(model_path, 'hunyuan3d-paint-v2-0')
|
69 |
+
|
70 |
+
if not os.path.exists(delight_model_path) or not os.path.exists(multiview_model_path):
|
71 |
+
try:
|
72 |
+
import huggingface_hub
|
73 |
+
# download from huggingface
|
74 |
+
model_path = huggingface_hub.snapshot_download(repo_id=original_model_path)
|
75 |
+
delight_model_path = os.path.join(model_path, 'hunyuan3d-delight-v2-0')
|
76 |
+
multiview_model_path = os.path.join(model_path, 'hunyuan3d-paint-v2-0')
|
77 |
+
return cls(Hunyuan3DTexGenConfig(delight_model_path, multiview_model_path))
|
78 |
+
except ImportError:
|
79 |
+
logger.warning(
|
80 |
+
"You need to install HuggingFace Hub to load models from the hub."
|
81 |
+
)
|
82 |
+
raise RuntimeError(f"Model path {model_path} not found")
|
83 |
+
else:
|
84 |
+
return cls(Hunyuan3DTexGenConfig(delight_model_path, multiview_model_path))
|
85 |
+
|
86 |
+
raise FileNotFoundError(f"Model path {original_model_path} not found and we could not find it at huggingface")
|
87 |
+
|
88 |
+
def __init__(self, config):
|
89 |
+
self.config = config
|
90 |
+
self.models = {}
|
91 |
+
self.render = MeshRender(
|
92 |
+
default_resolution=self.config.render_size,
|
93 |
+
texture_size=self.config.texture_size)
|
94 |
+
|
95 |
+
self.load_models()
|
96 |
+
|
97 |
+
def load_models(self):
|
98 |
+
# empty cude cache
|
99 |
+
torch.cuda.empty_cache()
|
100 |
+
# Load model
|
101 |
+
self.models['delight_model'] = Light_Shadow_Remover(self.config)
|
102 |
+
self.models['multiview_model'] = Multiview_Diffusion_Net(self.config)
|
103 |
+
|
104 |
+
def render_normal_multiview(self, camera_elevs, camera_azims, use_abs_coor=True):
|
105 |
+
normal_maps = []
|
106 |
+
for elev, azim in zip(camera_elevs, camera_azims):
|
107 |
+
normal_map = self.render.render_normal(
|
108 |
+
elev, azim, use_abs_coor=use_abs_coor, return_type='pl')
|
109 |
+
normal_maps.append(normal_map)
|
110 |
+
|
111 |
+
return normal_maps
|
112 |
+
|
113 |
+
def render_position_multiview(self, camera_elevs, camera_azims):
|
114 |
+
position_maps = []
|
115 |
+
for elev, azim in zip(camera_elevs, camera_azims):
|
116 |
+
position_map = self.render.render_position(
|
117 |
+
elev, azim, return_type='pl')
|
118 |
+
position_maps.append(position_map)
|
119 |
+
|
120 |
+
return position_maps
|
121 |
+
|
122 |
+
def bake_from_multiview(self, views, camera_elevs,
|
123 |
+
camera_azims, view_weights, method='graphcut'):
|
124 |
+
project_textures, project_weighted_cos_maps = [], []
|
125 |
+
project_boundary_maps = []
|
126 |
+
for view, camera_elev, camera_azim, weight in zip(
|
127 |
+
views, camera_elevs, camera_azims, view_weights):
|
128 |
+
project_texture, project_cos_map, project_boundary_map = self.render.back_project(
|
129 |
+
view, camera_elev, camera_azim)
|
130 |
+
project_cos_map = weight * (project_cos_map ** self.config.bake_exp)
|
131 |
+
project_textures.append(project_texture)
|
132 |
+
project_weighted_cos_maps.append(project_cos_map)
|
133 |
+
project_boundary_maps.append(project_boundary_map)
|
134 |
+
|
135 |
+
if method == 'fast':
|
136 |
+
texture, ori_trust_map = self.render.fast_bake_texture(
|
137 |
+
project_textures, project_weighted_cos_maps)
|
138 |
+
else:
|
139 |
+
raise f'no method {method}'
|
140 |
+
return texture, ori_trust_map > 1E-8
|
141 |
+
|
142 |
+
def texture_inpaint(self, texture, mask):
|
143 |
+
|
144 |
+
texture_np = self.render.uv_inpaint(texture, mask)
|
145 |
+
texture = torch.tensor(texture_np / 255).float().to(texture.device)
|
146 |
+
|
147 |
+
return texture
|
148 |
+
|
149 |
+
@torch.no_grad()
|
150 |
+
def __call__(self, mesh, image):
|
151 |
+
|
152 |
+
if isinstance(image, str):
|
153 |
+
image_prompt = Image.open(image)
|
154 |
+
else:
|
155 |
+
image_prompt = image
|
156 |
+
|
157 |
+
image_prompt = self.models['delight_model'](image_prompt)
|
158 |
+
|
159 |
+
mesh = mesh_uv_wrap(mesh)
|
160 |
+
|
161 |
+
self.render.load_mesh(mesh)
|
162 |
+
|
163 |
+
selected_camera_elevs, selected_camera_azims, selected_view_weights = \
|
164 |
+
self.config.candidate_camera_elevs, self.config.candidate_camera_azims, self.config.candidate_view_weights
|
165 |
+
|
166 |
+
normal_maps = self.render_normal_multiview(
|
167 |
+
selected_camera_elevs, selected_camera_azims, use_abs_coor=True)
|
168 |
+
position_maps = self.render_position_multiview(
|
169 |
+
selected_camera_elevs, selected_camera_azims)
|
170 |
+
|
171 |
+
camera_info = [(((azim // 30) + 9) % 12) // {-20: 1, 0: 1, 20: 1, -90: 3, 90: 3}[
|
172 |
+
elev] + {-20: 0, 0: 12, 20: 24, -90: 36, 90: 40}[elev] for azim, elev in
|
173 |
+
zip(selected_camera_azims, selected_camera_elevs)]
|
174 |
+
multiviews = self.models['multiview_model'](image_prompt, normal_maps + position_maps, camera_info)
|
175 |
+
|
176 |
+
for i in range(len(multiviews)):
|
177 |
+
multiviews[i] = multiviews[i].resize(
|
178 |
+
(self.config.render_size, self.config.render_size))
|
179 |
+
|
180 |
+
texture, mask = self.bake_from_multiview(multiviews,
|
181 |
+
selected_camera_elevs, selected_camera_azims, selected_view_weights,
|
182 |
+
method=self.config.merge_method)
|
183 |
+
|
184 |
+
mask_np = (mask.squeeze(-1).cpu().numpy() * 255).astype(np.uint8)
|
185 |
+
|
186 |
+
texture = self.texture_inpaint(texture, mask_np)
|
187 |
+
|
188 |
+
self.render.set_texture(texture)
|
189 |
+
textured_mesh = self.render.save_mesh()
|
190 |
+
|
191 |
+
return textured_mesh
|