rusandy/TRELLIS
0
1import gradio as gr2import spaces3from gradio_litmodel3d import LitModel3D4 5import os6import shutil7os.environ['SPCONV_ALGO'] = 'native'8from typing import *9import torch10import numpy as np11import imageio12import uuid13from easydict import EasyDict as edict14from PIL import Image15from trellis.pipelines import TrellisImageTo3DPipeline16from trellis.representations import Gaussian, MeshExtractResult17from trellis.utils import render_utils, postprocessing_utils18 19 20MAX_SEED = np.iinfo(np.int32).max21TMP_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'tmp')22os.makedirs(TMP_DIR, exist_ok=True)23 24 25def start_session(req: gr.Request):26 user_dir = os.path.join(TMP_DIR, str(req.session_hash))27 print(f'Creating user directory: {user_dir}')28 os.makedirs(user_dir, exist_ok=True)29 30 31def end_session(req: gr.Request):32 user_dir = os.path.join(TMP_DIR, str(req.session_hash))33 print(f'Removing user directory: {user_dir}')34 shutil.rmtree(user_dir)35 36 37def preprocess_image(image: Image.Image) -> Tuple[str, Image.Image]:38 """39 Preprocess the input image.40 41 Args:42 image (Image.Image): The input image.43 44 Returns:45 str: uuid of the trial.46 Image.Image: The preprocessed image.47 """48 processed_image = pipeline.preprocess_image(image)49 return processed_image50 51 52def pack_state(gs: Gaussian, mesh: MeshExtractResult, trial_id: str) -> dict:53 return {54 'gaussian': {55 **gs.init_params,56 '_xyz': gs._xyz.cpu().numpy(),57 '_features_dc': gs._features_dc.cpu().numpy(),58 '_scaling': gs._scaling.cpu().numpy(),59 '_rotation': gs._rotation.cpu().numpy(),60 '_opacity': gs._opacity.cpu().numpy(),61 },62 'mesh': {63 'vertices': mesh.vertices.cpu().numpy(),64 'faces': mesh.faces.cpu().numpy(),65 },66 'trial_id': trial_id,67 }68 69 70def unpack_state(state: dict) -> Tuple[Gaussian, edict, str]:71 gs = Gaussian(72 aabb=state['gaussian']['aabb'],73 sh_degree=state['gaussian']['sh_degree'],74 mininum_kernel_size=state['gaussian']['mininum_kernel_size'],75 scaling_bias=state['gaussian']['scaling_bias'],76 opacity_bias=state['gaussian']['opacity_bias'],77 scaling_activation=state['gaussian']['scaling_activation'],78 )79 gs._xyz = torch.tensor(state['gaussian']['_xyz'], device='cuda')80 gs._features_dc = torch.tensor(state['gaussian']['_features_dc'], device='cuda')81 gs._scaling = torch.tensor(state['gaussian']['_scaling'], device='cuda')82 gs._rotation = torch.tensor(state['gaussian']['_rotation'], device='cuda')83 gs._opacity = torch.tensor(state['gaussian']['_opacity'], device='cuda')84 85 mesh = edict(86 vertices=torch.tensor(state['mesh']['vertices'], device='cuda'),87 faces=torch.tensor(state['mesh']['faces'], device='cuda'),88 )89 90 return gs, mesh, state['trial_id']91 92 93def get_seed(randomize_seed: bool, seed: int) -> int:94 """95 Get the random seed.96 """97 return np.random.randint(0, MAX_SEED) if randomize_seed else seed98 99 100@spaces.GPU101def image_to_3d(102 image: Image.Image,103 seed: int,104 ss_guidance_strength: float,105 ss_sampling_steps: int,106 slat_guidance_strength: float,107 slat_sampling_steps: int,108 req: gr.Request,109) -> Tuple[dict, str]:110 """111 Convert an image to a 3D model.112 113 Args:114 image (Image.Image): The input image.115 seed (int): The random seed.116 ss_guidance_strength (float): The guidance strength for sparse structure generation.117 ss_sampling_steps (int): The number of sampling steps for sparse structure generation.118 slat_guidance_strength (float): The guidance strength for structured latent generation.119 slat_sampling_steps (int): The number of sampling steps for structured latent generation.120 121 Returns:122 dict: The information of the generated 3D model.123 str: The path to the video of the 3D model.124 """125 user_dir = os.path.join(TMP_DIR, str(req.session_hash))126 outputs = pipeline.run(127 image,128 seed=seed,129 formats=["gaussian", "mesh"],130 preprocess_image=False,131 sparse_structure_sampler_params={132 "steps": ss_sampling_steps,133 "cfg_strength": ss_guidance_strength,134 },135 slat_sampler_params={136 "steps": slat_sampling_steps,137 "cfg_strength": slat_guidance_strength,138 },139 )140 video = render_utils.render_video(outputs['gaussian'][0], num_frames=120)['color']141 video_geo = render_utils.render_video(outputs['mesh'][0], num_frames=120)['normal']142 video = [np.concatenate([video[i], video_geo[i]], axis=1) for i in range(len(video))]143 trial_id = uuid.uuid4()144 video_path = os.path.join(user_dir, f"{trial_id}.mp4")145 imageio.mimsave(video_path, video, fps=15)146 state = pack_state(outputs['gaussian'][0], outputs['mesh'][0], trial_id)147 torch.cuda.empty_cache()148 return state, video_path149 150 151@spaces.GPU152def extract_glb(153 state: dict,154 mesh_simplify: float,155 texture_size: int,156 req: gr.Request,157) -> Tuple[str, str]:158 """159 Extract a GLB file from the 3D model.160 161 Args:162 state (dict): The state of the generated 3D model.163 mesh_simplify (float): The mesh simplification factor.164 texture_size (int): The texture resolution.165 166 Returns:167 str: The path to the extracted GLB file.168 """169 user_dir = os.path.join(TMP_DIR, str(req.session_hash))170 gs, mesh, trial_id = unpack_state(state)171 glb = postprocessing_utils.to_glb(gs, mesh, simplify=mesh_simplify, texture_size=texture_size, verbose=False)172 glb_path = os.path.join(user_dir, f"{trial_id}.glb")173 glb.export(glb_path)174 torch.cuda.empty_cache()175 return glb_path, glb_path176 177 178with gr.Blocks(delete_cache=(600, 600)) as demo:179 gr.Markdown("""180 ## Image to 3D Asset with [TRELLIS](https://trellis3d.github.io/)181 * Upload an image and click "Generate" to create a 3D asset. If the image has alpha channel, it be used as the mask. Otherwise, we use `rembg` to remove the background.182 * If you find the generated 3D asset satisfactory, click "Extract GLB" to extract the GLB file and download it.183 """)184 185 with gr.Row():186 with gr.Column():187 image_prompt = gr.Image(label="Image Prompt", format="png", image_mode="RGBA", type="pil", height=300)188 189 with gr.Accordion(label="Generation Settings", open=False):190 seed = gr.Slider(0, MAX_SEED, label="Seed", value=0, step=1)191 randomize_seed = gr.Checkbox(label="Randomize Seed", value=True)192 gr.Markdown("Stage 1: Sparse Structure Generation")193 with gr.Row():194 ss_guidance_strength = gr.Slider(0.0, 10.0, label="Guidance Strength", value=7.5, step=0.1)195 ss_sampling_steps = gr.Slider(1, 50, label="Sampling Steps", value=12, step=1)196 gr.Markdown("Stage 2: Structured Latent Generation")197 with gr.Row():198 slat_guidance_strength = gr.Slider(0.0, 10.0, label="Guidance Strength", value=3.0, step=0.1)199 slat_sampling_steps = gr.Slider(1, 50, label="Sampling Steps", value=12, step=1)200 201 generate_btn = gr.Button("Generate")202 203 with gr.Accordion(label="GLB Extraction Settings", open=False):204 mesh_simplify = gr.Slider(0.9, 0.98, label="Simplify", value=0.95, step=0.01)205 texture_size = gr.Slider(512, 2048, label="Texture Size", value=1024, step=512)206 207 extract_glb_btn = gr.Button("Extract GLB", interactive=False)208 209 with gr.Column():210 video_output = gr.Video(label="Generated 3D Asset", autoplay=True, loop=True, height=300)211 model_output = LitModel3D(label="Extracted GLB", exposure=20.0, height=300)212 download_glb = gr.DownloadButton(label="Download GLB", interactive=False)213 214 output_buf = gr.State()215 216 # Example images at the bottom of the page217 with gr.Row():218 examples = gr.Examples(219 examples=[220 f'assets/example_image/{image}'221 for image in os.listdir("assets/example_image")222 ],223 inputs=[image_prompt],224 fn=preprocess_image,225 outputs=[image_prompt],226 run_on_click=True,227 examples_per_page=64,228 )229 230 # Handlers231 demo.load(start_session)232 demo.unload(end_session)233 234 image_prompt.upload(235 preprocess_image,236 inputs=[image_prompt],237 outputs=[image_prompt],238 )239 240 generate_btn.click(241 get_seed,242 inputs=[randomize_seed, seed],243 outputs=[seed],244 ).then(245 image_to_3d,246 inputs=[image_prompt, seed, ss_guidance_strength, ss_sampling_steps, slat_guidance_strength, slat_sampling_steps],247 outputs=[output_buf, video_output],248 ).then(249 lambda: gr.Button(interactive=True),250 outputs=[extract_glb_btn],251 )252 253 video_output.clear(254 lambda: gr.Button(interactive=False),255 outputs=[extract_glb_btn],256 )257 258 extract_glb_btn.click(259 extract_glb,260 inputs=[output_buf, mesh_simplify, texture_size],261 outputs=[model_output, download_glb],262 ).then(263 lambda: gr.Button(interactive=True),264 outputs=[download_glb],265 )266 267 model_output.clear(268 lambda: gr.Button(interactive=False),269 outputs=[download_glb],270 )271 272 273# Launch the Gradio app274if __name__ == "__main__":275 pipeline = TrellisImageTo3DPipeline.from_pretrained("JeffreyXiang/TRELLIS-image-large")276 pipeline.cuda()277 try:278 pipeline.preprocess_image(Image.fromarray(np.zeros((512, 512, 3), dtype=np.uint8))) # Preload rembg279 except:280 pass281 demo.launch()282 