Robledo Gularte Gonçalves commited on
Commit
f292ce3
·
1 Parent(s): 69ff606

back to old front

Browse files
Files changed (3) hide show
  1. app-new-front.py +1124 -0
  2. app-old-front.py +454 -0
  3. app.py +46 -716
app-new-front.py ADDED
@@ -0,0 +1,1124 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import spaces
2
+ import os
3
+ import gradio as gr
4
+ import numpy as np
5
+ import torch
6
+ from PIL import Image
7
+ import trimesh
8
+ import random
9
+ from transformers import AutoModelForImageSegmentation
10
+ from torchvision import transforms
11
+ from huggingface_hub import hf_hub_download, snapshot_download
12
+ import subprocess
13
+ import shutil
14
+
15
+ # install others
16
+ subprocess.run("pip install spandrel==0.4.1 --no-deps", shell=True, check=True)
17
+
18
+ DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
19
+ DTYPE = torch.float16
20
+
21
+ print("DEVICE: ", DEVICE)
22
+ print("CUDA DEVICE NAME: ", torch.cuda.get_device_name(torch.cuda.current_device()))
23
+
24
+ DEFAULT_FACE_NUMBER = 100000
25
+ MAX_SEED = np.iinfo(np.int32).max
26
+ TRIPOSG_REPO_URL = "https://github.com/VAST-AI-Research/TripoSG.git"
27
+ MV_ADAPTER_REPO_URL = "https://github.com/huanngzh/MV-Adapter.git"
28
+
29
+ RMBG_PRETRAINED_MODEL = "checkpoints/RMBG-1.4"
30
+ TRIPOSG_PRETRAINED_MODEL = "checkpoints/TripoSG"
31
+
32
+ TMP_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "tmp")
33
+ os.makedirs(TMP_DIR, exist_ok=True)
34
+
35
+ TRIPOSG_CODE_DIR = "./triposg"
36
+ if not os.path.exists(TRIPOSG_CODE_DIR):
37
+ os.system(f"git clone {TRIPOSG_REPO_URL} {TRIPOSG_CODE_DIR}")
38
+
39
+ MV_ADAPTER_CODE_DIR = "./mv_adapter"
40
+ if not os.path.exists(MV_ADAPTER_CODE_DIR):
41
+ os.system(f"git clone {MV_ADAPTER_REPO_URL} {MV_ADAPTER_CODE_DIR} && cd {MV_ADAPTER_CODE_DIR} && git checkout 7d37a97e9bc223cdb8fd26a76bd8dd46504c7c3d")
42
+
43
+ import sys
44
+ sys.path.append(TRIPOSG_CODE_DIR)
45
+ sys.path.append(os.path.join(TRIPOSG_CODE_DIR, "scripts"))
46
+ sys.path.append(MV_ADAPTER_CODE_DIR)
47
+ sys.path.append(os.path.join(MV_ADAPTER_CODE_DIR, "scripts"))
48
+
49
+ # Custom styling constants
50
+ NESTLE_BLUE = "#0066b1"
51
+ NESTLE_BLUE_DARK = "#004a82"
52
+ ACCENT_COLOR = "#10b981"
53
+
54
+ # # triposg
55
+ from image_process import prepare_image
56
+ from briarmbg import BriaRMBG
57
+ snapshot_download("briaai/RMBG-1.4", local_dir=RMBG_PRETRAINED_MODEL)
58
+ rmbg_net = BriaRMBG.from_pretrained(RMBG_PRETRAINED_MODEL).to(DEVICE)
59
+ rmbg_net.eval()
60
+ from triposg.pipelines.pipeline_triposg import TripoSGPipeline
61
+ snapshot_download("VAST-AI/TripoSG", local_dir=TRIPOSG_PRETRAINED_MODEL)
62
+ triposg_pipe = TripoSGPipeline.from_pretrained(TRIPOSG_PRETRAINED_MODEL).to(DEVICE, DTYPE)
63
+
64
+ # mv adapter
65
+ NUM_VIEWS = 6
66
+ from inference_ig2mv_sdxl import prepare_pipeline, preprocess_image, remove_bg
67
+ from mvadapter.utils import get_orthogonal_camera, tensor_to_image, make_image_grid
68
+ from mvadapter.utils.render import NVDiffRastContextWrapper, load_mesh, render
69
+ mv_adapter_pipe = prepare_pipeline(
70
+ base_model="stabilityai/stable-diffusion-xl-base-1.0",
71
+ vae_model="madebyollin/sdxl-vae-fp16-fix",
72
+ unet_model=None,
73
+ lora_model=None,
74
+ adapter_path="huanngzh/mv-adapter",
75
+ scheduler=None,
76
+ num_views=NUM_VIEWS,
77
+ device=DEVICE,
78
+ dtype=torch.float16,
79
+ )
80
+ birefnet = AutoModelForImageSegmentation.from_pretrained(
81
+ "ZhengPeng7/BiRefNet", trust_remote_code=True
82
+ )
83
+ birefnet.to(DEVICE)
84
+ transform_image = transforms.Compose(
85
+ [
86
+ transforms.Resize((1024, 1024)),
87
+ transforms.ToTensor(),
88
+ transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]),
89
+ ]
90
+ )
91
+ remove_bg_fn = lambda x: remove_bg(x, birefnet, transform_image, DEVICE)
92
+
93
+ if not os.path.exists("checkpoints/RealESRGAN_x2plus.pth"):
94
+ hf_hub_download("dtarnow/UPscaler", filename="RealESRGAN_x2plus.pth", local_dir="checkpoints")
95
+ if not os.path.exists("checkpoints/big-lama.pt"):
96
+ subprocess.run("wget -P checkpoints/ https://github.com/Sanster/models/releases/download/add_big_lama/big-lama.pt", shell=True, check=True)
97
+
98
+ def start_session(req: gr.Request):
99
+ save_dir = os.path.join(TMP_DIR, str(req.session_hash))
100
+ os.makedirs(save_dir, exist_ok=True)
101
+ print("start session, mkdir", save_dir)
102
+
103
+ def end_session(req: gr.Request):
104
+ save_dir = os.path.join(TMP_DIR, str(req.session_hash))
105
+ shutil.rmtree(save_dir)
106
+
107
+ def get_random_hex():
108
+ random_bytes = os.urandom(8)
109
+ random_hex = random_bytes.hex()
110
+ return random_hex
111
+
112
+ def get_random_seed(randomize_seed, seed):
113
+ if randomize_seed:
114
+ seed = random.randint(0, MAX_SEED)
115
+ return seed
116
+
117
+ @spaces.GPU(duration=180)
118
+ def run_full(image: str, req: gr.Request):
119
+ seed = 0
120
+ num_inference_steps = 50
121
+ guidance_scale = 7.5
122
+ simplify = True
123
+ target_face_num = DEFAULT_FACE_NUMBER
124
+
125
+ image_seg = prepare_image(image, bg_color=np.array([1.0, 1.0, 1.0]), rmbg_net=rmbg_net)
126
+
127
+ outputs = triposg_pipe(
128
+ image=image_seg,
129
+ generator=torch.Generator(device=triposg_pipe.device).manual_seed(seed),
130
+ num_inference_steps=num_inference_steps,
131
+ guidance_scale=guidance_scale
132
+ ).samples[0]
133
+ print("mesh extraction done")
134
+ mesh = trimesh.Trimesh(outputs[0].astype(np.float32), np.ascontiguousarray(outputs[1]))
135
+
136
+ if simplify:
137
+ print("start simplify")
138
+ from utils import simplify_mesh
139
+ mesh = simplify_mesh(mesh, target_face_num)
140
+
141
+ save_dir = os.path.join(TMP_DIR, "examples")
142
+ os.makedirs(save_dir, exist_ok=True)
143
+ mesh_path = os.path.join(save_dir, f"triposg_{get_random_hex()}.glb")
144
+ mesh.export(mesh_path)
145
+ print("save to ", mesh_path)
146
+
147
+ torch.cuda.empty_cache()
148
+
149
+ height, width = 768, 768
150
+ # Prepare cameras
151
+ cameras = get_orthogonal_camera(
152
+ elevation_deg=[0, 0, 0, 0, 89.99, -89.99],
153
+ distance=[1.8] * NUM_VIEWS,
154
+ left=-0.55,
155
+ right=0.55,
156
+ bottom=-0.55,
157
+ top=0.55,
158
+ azimuth_deg=[x - 90 for x in [0, 90, 180, 270, 180, 180]],
159
+ device=DEVICE,
160
+ )
161
+ ctx = NVDiffRastContextWrapper(device=DEVICE, context_type="cuda")
162
+
163
+ mesh = load_mesh(mesh_path, rescale=True, device=DEVICE)
164
+ render_out = render(
165
+ ctx,
166
+ mesh,
167
+ cameras,
168
+ height=height,
169
+ width=width,
170
+ render_attr=False,
171
+ normal_background=0.0,
172
+ )
173
+ control_images = (
174
+ torch.cat(
175
+ [
176
+ (render_out.pos + 0.5).clamp(0, 1),
177
+ (render_out.normal / 2 + 0.5).clamp(0, 1),
178
+ ],
179
+ dim=-1,
180
+ )
181
+ .permute(0, 3, 1, 2)
182
+ .to(DEVICE)
183
+ )
184
+
185
+ image = Image.open(image)
186
+ image = remove_bg_fn(image)
187
+ image = preprocess_image(image, height, width)
188
+
189
+ pipe_kwargs = {}
190
+ if seed != -1 and isinstance(seed, int):
191
+ pipe_kwargs["generator"] = torch.Generator(device=DEVICE).manual_seed(seed)
192
+
193
+ images = mv_adapter_pipe(
194
+ "high quality",
195
+ height=height,
196
+ width=width,
197
+ num_inference_steps=15,
198
+ guidance_scale=3.0,
199
+ num_images_per_prompt=NUM_VIEWS,
200
+ control_image=control_images,
201
+ control_conditioning_scale=1.0,
202
+ reference_image=image,
203
+ reference_conditioning_scale=1.0,
204
+ negative_prompt="watermark, ugly, deformed, noisy, blurry, low contrast",
205
+ cross_attention_kwargs={"scale": 1.0},
206
+ **pipe_kwargs,
207
+ ).images
208
+
209
+ torch.cuda.empty_cache()
210
+
211
+ mv_image_path = os.path.join(save_dir, f"mv_adapter_{get_random_hex()}.png")
212
+ make_image_grid(images, rows=1).save(mv_image_path)
213
+
214
+ from texture import TexturePipeline, ModProcessConfig
215
+ texture_pipe = TexturePipeline(
216
+ upscaler_ckpt_path="checkpoints/RealESRGAN_x2plus.pth",
217
+ inpaint_ckpt_path="checkpoints/big-lama.pt",
218
+ device=DEVICE,
219
+ )
220
+
221
+ textured_glb_path = texture_pipe(
222
+ mesh_path=mesh_path,
223
+ save_dir=save_dir,
224
+ save_name=f"texture_mesh_{get_random_hex()}.glb",
225
+ uv_unwarp=True,
226
+ uv_size=4096,
227
+ rgb_path=mv_image_path,
228
+ rgb_process_config=ModProcessConfig(view_upscale=True, inpaint_mode="view"),
229
+ camera_azimuth_deg=[x - 90 for x in [0, 90, 180, 270, 180, 180]],
230
+ )
231
+
232
+ return image_seg, mesh_path, textured_glb_path
233
+
234
+
235
+ @spaces.GPU()
236
+ @torch.no_grad()
237
+ def run_segmentation(image: str):
238
+ print("run_segmentation pre image str path: ", image)
239
+ image = prepare_image(image, bg_color=np.array([1.0, 1.0, 1.0]), rmbg_net=rmbg_net)
240
+ print("run_segmentation pos image: ", image)
241
+ return image
242
+
243
+ @spaces.GPU(duration=90)
244
+ @torch.no_grad()
245
+ def image_to_3d(
246
+ image: Image.Image,
247
+ seed: int,
248
+ num_inference_steps: int,
249
+ guidance_scale: float,
250
+ simplify: bool,
251
+ target_face_num: int,
252
+ req: gr.Request
253
+ ):
254
+ outputs = triposg_pipe(
255
+ image=image,
256
+ generator=torch.Generator(device=triposg_pipe.device).manual_seed(seed),
257
+ num_inference_steps=num_inference_steps,
258
+ guidance_scale=guidance_scale
259
+ ).samples[0]
260
+ print("mesh extraction done")
261
+ mesh = trimesh.Trimesh(outputs[0].astype(np.float32), np.ascontiguousarray(outputs[1]))
262
+
263
+ if simplify:
264
+ print("start simplify")
265
+ from utils import simplify_mesh
266
+ mesh = simplify_mesh(mesh, target_face_num)
267
+
268
+ save_dir = os.path.join(TMP_DIR, str(req.session_hash))
269
+ mesh_path = os.path.join(save_dir, f"triposg_{get_random_hex()}.glb")
270
+ mesh.export(mesh_path)
271
+ print("save to ", mesh_path)
272
+
273
+ torch.cuda.empty_cache()
274
+
275
+ return mesh_path
276
+
277
+ @spaces.GPU(duration=120)
278
+ @torch.no_grad()
279
+ def run_texture(image: Image, mesh_path: str, seed: int, text_prompt: str, req: gr.Request):
280
+ height, width = 768, 768
281
+ # Prepare cameras
282
+ cameras = get_orthogonal_camera(
283
+ elevation_deg=[0, 0, 0, 0, 89.99, -89.99],
284
+ distance=[1.8] * NUM_VIEWS,
285
+ left=-0.55,
286
+ right=0.55,
287
+ bottom=-0.55,
288
+ top=0.55,
289
+ azimuth_deg=[x - 90 for x in [0, 90, 180, 270, 180, 180]],
290
+ device=DEVICE,
291
+ )
292
+ ctx = NVDiffRastContextWrapper(device=DEVICE, context_type="cuda")
293
+
294
+ mesh = load_mesh(mesh_path, rescale=True, device=DEVICE)
295
+ render_out = render(
296
+ ctx,
297
+ mesh,
298
+ cameras,
299
+ height=height,
300
+ width=width,
301
+ render_attr=False,
302
+ normal_background=0.0,
303
+ )
304
+ control_images = (
305
+ torch.cat(
306
+ [
307
+ (render_out.pos + 0.5).clamp(0, 1),
308
+ (render_out.normal / 2 + 0.5).clamp(0, 1),
309
+ ],
310
+ dim=-1,
311
+ )
312
+ .permute(0, 3, 1, 2)
313
+ .to(DEVICE)
314
+ )
315
+
316
+ image = Image.open(image)
317
+ image = remove_bg_fn(image)
318
+ image = preprocess_image(image, height, width)
319
+
320
+ pipe_kwargs = {}
321
+ if seed != -1 and isinstance(seed, int):
322
+ pipe_kwargs["generator"] = torch.Generator(device=DEVICE).manual_seed(seed)
323
+
324
+ images = mv_adapter_pipe(
325
+ text_prompt,
326
+ height=height,
327
+ width=width,
328
+ num_inference_steps=15,
329
+ guidance_scale=3.0,
330
+ num_images_per_prompt=NUM_VIEWS,
331
+ control_image=control_images,
332
+ control_conditioning_scale=1.0,
333
+ reference_image=image,
334
+ reference_conditioning_scale=1.0,
335
+ negative_prompt="watermark, ugly, deformed, noisy, blurry, low contrast",
336
+ cross_attention_kwargs={"scale": 1.0},
337
+ **pipe_kwargs,
338
+ ).images
339
+
340
+ torch.cuda.empty_cache()
341
+
342
+ save_dir = os.path.join(TMP_DIR, str(req.session_hash))
343
+ mv_image_path = os.path.join(save_dir, f"mv_adapter_{get_random_hex()}.png")
344
+ make_image_grid(images, rows=1).save(mv_image_path)
345
+
346
+ from texture import TexturePipeline, ModProcessConfig
347
+ texture_pipe = TexturePipeline(
348
+ upscaler_ckpt_path="checkpoints/RealESRGAN_x2plus.pth",
349
+ inpaint_ckpt_path="checkpoints/big-lama.pt",
350
+ device=DEVICE,
351
+ )
352
+
353
+ textured_glb_path = texture_pipe(
354
+ mesh_path=mesh_path,
355
+ save_dir=save_dir,
356
+ save_name=f"texture_mesh_{get_random_hex()}.glb",
357
+ uv_unwarp=True,
358
+ uv_size=4096,
359
+ rgb_path=mv_image_path,
360
+ rgb_process_config=ModProcessConfig(view_upscale=True, inpaint_mode="view"),
361
+ camera_azimuth_deg=[x - 90 for x in [0, 90, 180, 270, 180, 180]],
362
+ )
363
+
364
+ return textured_glb_path
365
+
366
+ # Custom UI components
367
+ def create_header():
368
+ return f"""
369
+ <div class="card" style="background: linear-gradient(135deg, {NESTLE_BLUE} 0%, {NESTLE_BLUE_DARK} 100%); color: white; border: none;">
370
+ <div style="display: flex; align-items: center; gap: 20px;">
371
+ <div style="background: white; padding: 12px; border-radius: 12px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);">
372
+ <img src="https://logodownload.org/wp-content/uploads/2016/11/nestle-logo-1.png"
373
+ alt="Nestlé Logo" style="height: 48px; width: auto;">
374
+ </div>
375
+ <div style="flex: 1;">
376
+ <h1 style="margin: 0; font-size: 2.5rem; font-weight: 700; letter-spacing: -0.025em;">
377
+ Nestlé 3D Generator
378
+ </h1>
379
+ <p style="margin: 0.5rem 0 0 0; opacity: 0.9; font-size: 1.1rem;">
380
+ Transform your product images into stunning 3D models with AI
381
+ </p>
382
+ </div>
383
+ <div class="badge primary">Beta v2.0</div>
384
+ </div>
385
+ </div>
386
+ """
387
+
388
+ def create_tabs():
389
+ return """
390
+ <div class="tabs-container">
391
+ <div class="tabs-list">
392
+ <button class="tab-button active" onclick="switchTab('segmentation')">
393
+ 🔍 Segmentation
394
+ </button>
395
+ <button class="tab-button" onclick="switchTab('model')">
396
+ 🎨 3D Model
397
+ </button>
398
+ <button class="tab-button" onclick="switchTab('textured')">
399
+ ✨ Textured Model
400
+ </button>
401
+ </div>
402
+
403
+ <div id="segmentation-tab" class="tab-content active">
404
+ <div style="text-align: center; color: #1e293b;">
405
+ <div style="font-size: 4rem; margin-bottom: 1rem;">📤</div>
406
+ <p>Upload an image to see segmentation results</p>
407
+ </div>
408
+ </div>
409
+
410
+ <div id="model-tab" class="tab-content">
411
+ <div style="text-align: center; color: #1e293b;">
412
+ <div style="font-size: 4rem; margin-bottom: 1rem;">🎯</div>
413
+ <p>3D model will appear here after generation</p>
414
+ </div>
415
+ </div>
416
+
417
+ <div id="textured-tab" class="tab-content">
418
+ <div style="text-align: center; color: #1e293b;">
419
+ <div style="font-size: 4rem; margin-bottom: 1rem;">🎨</div>
420
+ <p>Textured model will appear here</p>
421
+ </div>
422
+ </div>
423
+ </div>
424
+ """
425
+
426
+ def create_progress_bar():
427
+ return """
428
+ <div class="progress-container" style="display: none;" id="progress-container">
429
+ <div class="progress-header">
430
+ <span>Generating 3D model...</span>
431
+ <span id="progress-text">0%</span>
432
+ </div>
433
+ <div class="progress-bar-container">
434
+ <div class="progress-bar" id="progress-bar"></div>
435
+ </div>
436
+ </div>
437
+ """
438
+
439
+ # JavaScript
440
+ ADVANCED_JS = """
441
+ <script>
442
+ // React-like state management simulation
443
+ window.appState = {
444
+ currentTab: 'segmentation',
445
+ isGenerating: false,
446
+ progress: 0
447
+ };
448
+
449
+ // Tab switching functionality
450
+ function switchTab(tabName) {
451
+ window.appState.currentTab = tabName;
452
+
453
+ // Hide all tab contents
454
+ document.querySelectorAll('.tab-content').forEach(el => {
455
+ el.style.display = 'none';
456
+ });
457
+
458
+ // Show selected tab
459
+ const selectedTab = document.getElementById(tabName + '-tab');
460
+ if (selectedTab) {
461
+ selectedTab.style.display = 'block';
462
+ }
463
+
464
+ // Update tab buttons
465
+ document.querySelectorAll('.tab-button').forEach(btn => {
466
+ btn.classList.remove('active');
467
+ });
468
+
469
+ const activeBtn = document.querySelector(`[onclick="switchTab('${tabName}')"]`);
470
+ if (activeBtn) {
471
+ activeBtn.classList.add('active');
472
+ }
473
+ }
474
+
475
+ // Progress simulation
476
+ function simulateProgress() {
477
+ window.appState.isGenerating = true;
478
+ window.appState.progress = 0;
479
+
480
+ const progressBar = document.getElementById('progress-bar');
481
+ const progressText = document.getElementById('progress-text');
482
+
483
+ const interval = setInterval(() => {
484
+ window.appState.progress += 10;
485
+
486
+ if (progressBar) {
487
+ progressBar.style.width = window.appState.progress + '%';
488
+ }
489
+
490
+ if (progressText) {
491
+ progressText.textContent = window.appState.progress + '%';
492
+ }
493
+
494
+ if (window.appState.progress >= 100) {
495
+ clearInterval(interval);
496
+ window.appState.isGenerating = false;
497
+ }
498
+ }, 300);
499
+ }
500
+
501
+ // Drag and drop simulation
502
+ function setupDragDrop() {
503
+ const uploadArea = document.querySelector('.upload-area');
504
+ if (uploadArea) {
505
+ uploadArea.addEventListener('dragover', (e) => {
506
+ e.preventDefault();
507
+ uploadArea.classList.add('drag-over');
508
+ });
509
+
510
+ uploadArea.addEventListener('dragleave', () => {
511
+ uploadArea.classList.remove('drag-over');
512
+ });
513
+
514
+ uploadArea.addEventListener('drop', (e) => {
515
+ e.preventDefault();
516
+ uploadArea.classList.remove('drag-over');
517
+ // Handle file drop
518
+ });
519
+ }
520
+ }
521
+
522
+ // Initialize when DOM is ready
523
+ document.addEventListener('DOMContentLoaded', function() {
524
+ setupDragDrop();
525
+ switchTab('segmentation');
526
+ });
527
+ </script>
528
+ """
529
+
530
+ # CSS
531
+ ADVANCED_CSS = f"""
532
+ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
533
+
534
+ :root {{
535
+ --nestle-blue: {NESTLE_BLUE};
536
+ --nestle-blue-dark: {NESTLE_BLUE_DARK};
537
+ --accent: {ACCENT_COLOR};
538
+ --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.1);
539
+ --shadow-md: 0 4px 12px rgba(0, 0, 0, 0.1);
540
+ --shadow-lg: 0 10px 25px rgba(0, 0, 0, 0.1);
541
+ --border-radius: 12px;
542
+ }}
543
+
544
+ * {{
545
+ font-family: 'Inter', sans-serif !important;
546
+ }}
547
+
548
+ body, .gradio-container {{
549
+ background: linear-gradient(135deg, #f8fafc 0%, #e2e8f0 100%) !important;
550
+ margin: 0 !important;
551
+ padding: 0 !important;
552
+ min-height: 100vh !important;
553
+ color: #ffffff !important;
554
+ font-size: 1rem !important;
555
+ }}
556
+
557
+ /* AGGRESSIVE TEXT COLOR FIXES - Higher specificity */
558
+ .gradio-container *,
559
+ .gradio-container div,
560
+ .gradio-container span,
561
+ .gradio-container p,
562
+ .gradio-container label,
563
+ .gradio-container h1,
564
+ .gradio-container h2,
565
+ .gradio-container h3,
566
+ .gradio-container h4,
567
+ .gradio-container h5,
568
+ .gradio-container h6 {{
569
+ color: #ffffff !important;
570
+ text-shadow: 0 1px 3px rgba(0, 0, 0, 0.8) !important;
571
+ }}
572
+
573
+ /* Force white text on all Gradio components */
574
+ .gr-group *,
575
+ .gr-form *,
576
+ .gr-block *,
577
+ .gr-box *,
578
+ div[class*="gr-"] *,
579
+ div[class*="svelte-"] *,
580
+ span[class*="svelte-"] *,
581
+ label[class*="svelte-"] *,
582
+ p[class*="svelte-"] * {{
583
+ color: #ffffff !important;
584
+ text-shadow: 0 1px 3px rgba(0, 0, 0, 0.8) !important;
585
+ font-weight: 600 !important;
586
+ }}
587
+
588
+ /* Specific targeting for card descriptions and titles */
589
+ .card-description,
590
+ .card-title,
591
+ div.card-description,
592
+ div.card-title,
593
+ p.card-description,
594
+ h3.card-title {{
595
+ color: #ffffff !important;
596
+ text-shadow: 0 1px 3px rgba(0, 0, 0, 0.8) !important;
597
+ font-weight: 700 !important;
598
+ background: rgba(0, 0, 0, 0.3) !important;
599
+ padding: 4px 8px !important;
600
+ border-radius: 6px !important;
601
+ margin: 0.5rem 0 !important;
602
+ display: inline-block !important;
603
+ }}
604
+
605
+ /* Card Components */
606
+ .card {{
607
+ background: white;
608
+ border: 1px solid #e2e8f0;
609
+ border-radius: var(--border-radius);
610
+ box-shadow: var(--shadow-md);
611
+ padding: 1.5rem;
612
+ transition: all 0.2s ease;
613
+ margin-bottom: 1rem;
614
+ }}
615
+
616
+ .card:hover {{
617
+ box-shadow: var(--shadow-lg);
618
+ transform: translateY(-2px);
619
+ }}
620
+
621
+ .card-header {{
622
+ margin-bottom: 1rem;
623
+ padding-bottom: 1rem;
624
+ border-bottom: 1px solid #e2e8f0;
625
+ }}
626
+
627
+ /* Tabs */
628
+ .tabs-container {{
629
+ background: white;
630
+ border-radius: var(--border-radius);
631
+ box-shadow: var(--shadow-md);
632
+ overflow: hidden;
633
+ }}
634
+
635
+ .tabs-list {{
636
+ display: flex;
637
+ background: #f8fafc;
638
+ border-bottom: 1px solid #e2e8f0;
639
+ }}
640
+
641
+ .tab-button {{
642
+ flex: 1;
643
+ padding: 1rem;
644
+ background: none;
645
+ border: none;
646
+ cursor: pointer;
647
+ font-weight: 600;
648
+ color: #334155 !important;
649
+ font-size: 1rem;
650
+ transition: all 0.2s ease;
651
+ position: relative;
652
+ }}
653
+
654
+ .tab-button:hover {{
655
+ background: #f1f5f9;
656
+ color: #1e293b !important;
657
+ }}
658
+
659
+ .tab-button.active {{
660
+ color: var(--nestle-blue) !important;
661
+ background: white;
662
+ font-weight: 800;
663
+ }}
664
+
665
+ .tab-button.active::after {{
666
+ content: '';
667
+ position: absolute;
668
+ bottom: 0;
669
+ left: 0;
670
+ right: 0;
671
+ height: 2px;
672
+ background: var(--nestle-blue);
673
+ }}
674
+
675
+ .tab-content {{
676
+ padding: 2rem;
677
+ min-height: 400px;
678
+ display: none;
679
+ }}
680
+
681
+ .tab-content.active {{
682
+ display: block;
683
+ }}
684
+
685
+ .tab-content * {{
686
+ color: #1e293b !important;
687
+ text-shadow: none !important;
688
+ }}
689
+
690
+ /* Progress Component */
691
+ .progress-container {{
692
+ margin: 1rem 0;
693
+ padding: 1rem;
694
+ background: #f8fafc;
695
+ border-radius: var(--border-radius);
696
+ border: 1px solid #e2e8f0;
697
+ }}
698
+
699
+ .progress-header {{
700
+ display: flex;
701
+ justify-content: space-between;
702
+ margin-bottom: 0.5rem;
703
+ font-size: 1rem;
704
+ color: #334155 !important;
705
+ font-weight: 600;
706
+ }}
707
+
708
+ .progress-bar-container {{
709
+ width: 100%;
710
+ height: 8px;
711
+ background: #e2e8f0;
712
+ border-radius: 4px;
713
+ overflow: hidden;
714
+ }}
715
+
716
+ .progress-bar {{
717
+ height: 100%;
718
+ background: linear-gradient(90deg, var(--nestle-blue) 0%, var(--accent) 100%);
719
+ width: 0%;
720
+ transition: width 0.3s ease;
721
+ border-radius: 4px;
722
+ }}
723
+
724
+ /* Badge */
725
+ .badge {{
726
+ display: inline-flex;
727
+ align-items: center;
728
+ padding: 0.25rem 0.75rem;
729
+ background: #e2e8f0;
730
+ color: #1e293b !important;
731
+ border-radius: 9999px;
732
+ font-size: 0.85rem;
733
+ font-weight: 600;
734
+ }}
735
+
736
+ .badge.primary {{
737
+ background: var(--nestle-blue);
738
+ color: #fff !important;
739
+ }}
740
+
741
+ /* Button variants */
742
+ .btn, .btn-primary, .btn-secondary, .gr-button {{
743
+ display: inline-flex;
744
+ align-items: center;
745
+ justify-content: center;
746
+ gap: 0.5rem;
747
+ padding: 0.75rem 1.5rem;
748
+ border-radius: var(--border-radius);
749
+ font-weight: 700 !important;
750
+ font-size: 1rem !important;
751
+ border: none;
752
+ cursor: pointer;
753
+ transition: all 0.2s ease;
754
+ text-decoration: none;
755
+ letter-spacing: -0.01em;
756
+ }}
757
+
758
+ .btn-primary, .gr-button {{
759
+ background: linear-gradient(135deg, var(--nestle-blue) 0%, var(--nestle-blue-dark) 100%) !important;
760
+ color: white !important;
761
+ box-shadow: var(--shadow-sm) !important;
762
+ }}
763
+
764
+ .btn-primary:hover, .gr-button:hover {{
765
+ transform: translateY(-1px) !important;
766
+ box-shadow: var(--shadow-md) !important;
767
+ }}
768
+
769
+ .btn-secondary {{
770
+ background: white !important;
771
+ color: #374151 !important;
772
+ border: 1px solid #d1d5db !important;
773
+ }}
774
+
775
+ .btn-secondary:hover {{
776
+ background: #f9fafb !important;
777
+ }}
778
+
779
+ /* Enhanced Gradio component styling */
780
+ .gr-image, .gr-model3d {{
781
+ border: 2px solid #e2e8f0 !important;
782
+ border-radius: var(--border-radius) !important;
783
+ box-shadow: var(--shadow-sm) !important;
784
+ transition: all 0.2s ease !important;
785
+ }}
786
+
787
+ .gr-slider .noUi-connect {{
788
+ background: linear-gradient(90deg, var(--nestle-blue) 0%, var(--accent) 100%) !important;
789
+ }}
790
+
791
+ .gr-slider .noUi-handle {{
792
+ background: white !important;
793
+ border: 3px solid var(--nestle-blue) !important;
794
+ border-radius: 50% !important;
795
+ box-shadow: var(--shadow-md) !important;
796
+ }}
797
+
798
+ /* Responsive design */
799
+ @media (max-width: 768px) {{
800
+ .tabs-list {{
801
+ flex-direction: column;
802
+ }}
803
+
804
+ .card {{
805
+ padding: 1rem;
806
+ }}
807
+ }}
808
+
809
+ /* SUPER AGGRESSIVE TEXT FIXES */
810
+ /* Target every possible Gradio text element */
811
+ .gradio-container .gr-group .gr-form label,
812
+ .gradio-container .gr-group .gr-form span,
813
+ .gradio-container .gr-group .gr-form div,
814
+ .gradio-container .gr-group .gr-form p,
815
+ .gradio-container .gr-block label,
816
+ .gradio-container .gr-block span,
817
+ .gradio-container .gr-block div,
818
+ .gradio-container .gr-block p,
819
+ .gradio-container .gr-box label,
820
+ .gradio-container .gr-box span,
821
+ .gradio-container .gr-box div,
822
+ .gradio-container .gr-box p {{
823
+ color: #ffffff !important;
824
+ text-shadow: 0 1px 3px rgba(0, 0, 0, 0.8) !important;
825
+ font-weight: 600 !important;
826
+ opacity: 1 !important;
827
+ }}
828
+
829
+ /* Target Svelte components specifically */
830
+ [class*="svelte-"] {{
831
+ color: #ffffff !important;
832
+ text-shadow: 0 1px 3px rgba(0, 0, 0, 0.8) !important;
833
+ }}
834
+
835
+ /* Target slider labels and info text */
836
+ .gr-slider label,
837
+ .gr-slider .gr-text,
838
+ .gr-slider span,
839
+ .gr-checkbox label,
840
+ .gr-checkbox span {{
841
+ color: #ffffff !important;
842
+ text-shadow: 0 1px 3px rgba(0, 0, 0, 0.8) !important;
843
+ font-weight: 600 !important;
844
+ }}
845
+
846
+ /* Target info text specifically */
847
+ .gr-info,
848
+ [class*="info"],
849
+ .info {{
850
+ color: #ffffff !important;
851
+ text-shadow: 0 1px 3px rgba(0, 0, 0, 0.8) !important;
852
+ font-weight: 500 !important;
853
+ background: rgba(0, 0, 0, 0.2) !important;
854
+ padding: 2px 6px !important;
855
+ border-radius: 4px !important;
856
+ }}
857
+
858
+ /* Fix for image action icons */
859
+ .gr-image .image-button,
860
+ .gr-image button,
861
+ .gr-image .icon-button,
862
+ .gr-image [role="button"],
863
+ .gr-image .svelte-1pijsyv,
864
+ .gr-image .svelte-1pijsyv button {{
865
+ background: rgba(255, 255, 255, 0.95) !important;
866
+ border: 1px solid #e2e8f0 !important;
867
+ border-radius: 8px !important;
868
+ padding: 8px !important;
869
+ margin: 2px !important;
870
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15) !important;
871
+ transition: all 0.2s ease !important;
872
+ color: #374151 !important;
873
+ font-size: 16px !important;
874
+ min-width: 36px !important;
875
+ min-height: 36px !important;
876
+ display: flex !important;
877
+ align-items: center !important;
878
+ justify-content: center !important;
879
+ }}
880
+
881
+ .gr-image .image-button:hover,
882
+ .gr-image button:hover,
883
+ .gr-image .icon-button:hover,
884
+ .gr-image [role="button"]:hover,
885
+ .gr-image .svelte-1pijsyv:hover,
886
+ .gr-image .svelte-1pijsyv button:hover {{
887
+ background: rgba(255, 255, 255, 1) !important;
888
+ transform: translateY(-1px) !important;
889
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2) !important;
890
+ color: var(--nestle-blue) !important;
891
+ }}
892
+
893
+ /* Upload area text */
894
+ .gr-image .upload-text,
895
+ .gr-image .drag-text,
896
+ .gr-image .svelte-1ipelgc {{
897
+ color: #1e293b !important;
898
+ font-weight: 600 !important;
899
+ text-shadow: 0 0 4px white !important;
900
+ background: rgba(255, 255, 255, 0.9) !important;
901
+ padding: 8px 12px !important;
902
+ border-radius: 8px !important;
903
+ margin: 4px !important;
904
+ }}
905
+
906
+ /* Nuclear option - force all text to be white with shadow */
907
+ * {{
908
+ color: #ffffff !important;
909
+ text-shadow: 0 1px 3px rgba(0, 0, 0, 0.8) !important;
910
+ }}
911
+
912
+ /* But override for specific areas that should be dark */
913
+ .tabs-container *,
914
+ .tab-content *,
915
+ .badge *,
916
+ .btn *,
917
+ .gr-button *,
918
+ .upload-area *,
919
+ .gr-image .upload-text *,
920
+ .gr-image .drag-text *,
921
+ .gr-image .svelte-1ipelgc *,
922
+ .progress-container * {{
923
+ color: #1e293b !important;
924
+ text-shadow: 0 0 2px white !important;
925
+ }}
926
+
927
+ /* Header text should remain white */
928
+ .card[style*="linear-gradient"] *,
929
+ .card[style*="linear-gradient"] h1,
930
+ .card[style*="linear-gradient"] p {{
931
+ color: #ffffff !important;
932
+ text-shadow: 0 1px 3px rgba(0, 0, 0, 0.5) !important;
933
+ }}
934
+ """
935
+
936
+ # interface
937
+ with gr.Blocks(
938
+ title="Nestlé 3D Generator",
939
+ css=ADVANCED_CSS,
940
+ head=ADVANCED_JS,
941
+ theme=gr.themes.Soft(
942
+ primary_hue="blue",
943
+ secondary_hue="slate",
944
+ neutral_hue="slate",
945
+ font=gr.themes.GoogleFont("Inter")
946
+ )
947
+ ) as demo:
948
+
949
+ # Header
950
+ gr.HTML(create_header())
951
+
952
+ with gr.Row():
953
+ with gr.Column(scale=1):
954
+ with gr.Group():
955
+ gr.HTML("""
956
+ <div class="card-header">
957
+ <h3 class="card-title">📤 Product Image Upload</h3>
958
+ <p class="card-description">Upload a clear image of your Nestlé product</p>
959
+ </div>
960
+ """)
961
+
962
+ image_prompts = gr.Image(
963
+ label="",
964
+ type="filepath",
965
+ show_label=False,
966
+ height=350,
967
+ elem_classes=["upload-area"]
968
+ )
969
+
970
+ # Settings Card
971
+ with gr.Group():
972
+ gr.HTML("""
973
+ <div class="card-header">
974
+ <h3 class="card-title">⚙️ Generation Settings</h3>
975
+ <p class="card-description">Configure your 3D model generation</p>
976
+ </div>
977
+ """)
978
+
979
+ text_prompt = gr.Textbox(label="Prompt", placeholder="Enter your prompt", value="high quality")
980
+
981
+ with gr.Row():
982
+ randomize_seed = gr.Checkbox(
983
+ label="🎲 Randomize Seed",
984
+ value=True
985
+ )
986
+ seed = gr.Slider(
987
+ label="Seed Value",
988
+ minimum=0,
989
+ maximum=MAX_SEED,
990
+ step=1,
991
+ value=0
992
+ )
993
+
994
+ num_inference_steps = gr.Slider(
995
+ label="🔄 Inference Steps",
996
+ minimum=8,
997
+ maximum=50,
998
+ step=1,
999
+ value=50,
1000
+ info="Higher values = better quality, slower generation"
1001
+ )
1002
+
1003
+ guidance_scale = gr.Slider(
1004
+ label="🎯 Guidance Scale",
1005
+ minimum=0.0,
1006
+ maximum=20.0,
1007
+ step=0.1,
1008
+ value=7.0,
1009
+ info="Controls how closely the model follows the input"
1010
+ )
1011
+
1012
+ with gr.Row():
1013
+ reduce_face = gr.Checkbox(
1014
+ label="🔧 Optimize Mesh",
1015
+ value=True,
1016
+ info="Reduce polygon count for better performance"
1017
+ )
1018
+ target_face_num = gr.Slider(
1019
+ label="Target Faces",
1020
+ maximum=1_000_000,
1021
+ minimum=10_000,
1022
+ value=DEFAULT_FACE_NUMBER,
1023
+ step=1000
1024
+ )
1025
+
1026
+ with gr.Column(scale=2):
1027
+ gr.HTML("""
1028
+ <div class="card-header">
1029
+ <h3 class="card-title">3D Model Generation</h3>
1030
+ <p class="card-description">View your generated 3D models and apply textures</p>
1031
+ </div>
1032
+ """)
1033
+
1034
+ # CT React-like
1035
+ gr.HTML(create_tabs())
1036
+
1037
+ # PB
1038
+ gr.HTML(create_progress_bar())
1039
+
1040
+ # Hidden Gradio components for actual functionality
1041
+ with gr.Row(visible=False):
1042
+ seg_image = gr.Image(type="pil", format="png", interactive=False)
1043
+ model_output = gr.Model3D(interactive=False)
1044
+ textured_model_output = gr.Model3D(interactive=False)
1045
+
1046
+ # Action Buttons
1047
+ with gr.Row():
1048
+ gen_button = gr.Button(
1049
+ "🚀 Generate 3D Model",
1050
+ variant="primary",
1051
+ size="lg",
1052
+ elem_classes=["btn", "btn-primary"]
1053
+ )
1054
+ gen_texture_button = gr.Button(
1055
+ "🎨 Apply Texture",
1056
+ variant="secondary",
1057
+ size="lg",
1058
+ interactive=False,
1059
+ elem_classes=["btn", "btn-secondary"]
1060
+ )
1061
+ download_button = gr.Button(
1062
+ "💾 Download Model",
1063
+ variant="secondary",
1064
+ size="lg",
1065
+ elem_classes=["btn", "btn-secondary"]
1066
+ )
1067
+
1068
+ status_display = gr.HTML(
1069
+ """<div style='text-align: center; padding: 1rem; color: #1e293b;'>
1070
+ <span style='display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: #10b981; margin-right: 8px;'></span>
1071
+ Ready to generate your 3D model
1072
+ </div>"""
1073
+ )
1074
+
1075
+ # Event Handlers with JavaScript integration
1076
+ gen_button.click(
1077
+ fn=run_segmentation,
1078
+ inputs=[image_prompts],
1079
+ outputs=[seg_image],
1080
+ # js="() => { simulateProgress(); document.getElementById('progress-container').style.display = 'block'; }",
1081
+ ).then(
1082
+ get_random_seed,
1083
+ inputs=[randomize_seed, seed],
1084
+ outputs=[seed],
1085
+ ).then(
1086
+ image_to_3d,
1087
+ inputs=[
1088
+ seg_image,
1089
+ seed,
1090
+ num_inference_steps,
1091
+ guidance_scale,
1092
+ reduce_face,
1093
+ target_face_num
1094
+ ],
1095
+ outputs=[model_output]
1096
+ ).then(
1097
+ fn=lambda: gr.Button(interactive=True),
1098
+ outputs=[gen_texture_button]
1099
+ )
1100
+
1101
+ gen_texture_button.click(
1102
+ run_texture,
1103
+ inputs=[image_prompts, model_output, seed, text_prompt],
1104
+ outputs=[textured_model_output]
1105
+ )
1106
+
1107
+ # with gr.Row():
1108
+ # examples = gr.Examples(
1109
+ # examples=[
1110
+ # f"./examples/{image}"
1111
+ # for image in os.listdir(f"./examples/")
1112
+ # ],
1113
+ # fn=run_full,
1114
+ # inputs=[image_prompts],
1115
+ # outputs=[seg_image, model_output, textured_model_output],
1116
+ # cache_examples=False,
1117
+ # )
1118
+
1119
+ demo.load(start_session)
1120
+ demo.unload(end_session)
1121
+
1122
+
1123
+ if __name__ == "__main__":
1124
+ demo.launch(share=False, show_error=True)
app-old-front.py ADDED
@@ -0,0 +1,454 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import spaces
2
+ import os
3
+ import gradio as gr
4
+ import numpy as np
5
+ import torch
6
+ from PIL import Image
7
+ import trimesh
8
+ import random
9
+ from transformers import AutoModelForImageSegmentation
10
+ from torchvision import transforms
11
+ from huggingface_hub import hf_hub_download, snapshot_download
12
+ import subprocess
13
+ import shutil
14
+
15
+ # install others
16
+ subprocess.run("pip install spandrel==0.4.1 --no-deps", shell=True, check=True)
17
+
18
+ DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
19
+ DTYPE = torch.float16
20
+
21
+ print("DEVICE: ", DEVICE)
22
+ print("CUDA DEVICE NAME: ", torch.cuda.get_device_name(torch.cuda.current_device()))
23
+
24
+ DEFAULT_FACE_NUMBER = 100000
25
+ MAX_SEED = np.iinfo(np.int32).max
26
+ TRIPOSG_REPO_URL = "https://github.com/VAST-AI-Research/TripoSG.git"
27
+ MV_ADAPTER_REPO_URL = "https://github.com/huanngzh/MV-Adapter.git"
28
+
29
+ RMBG_PRETRAINED_MODEL = "checkpoints/RMBG-1.4"
30
+ TRIPOSG_PRETRAINED_MODEL = "checkpoints/TripoSG"
31
+
32
+ TMP_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "tmp")
33
+ os.makedirs(TMP_DIR, exist_ok=True)
34
+
35
+ TRIPOSG_CODE_DIR = "./triposg"
36
+ if not os.path.exists(TRIPOSG_CODE_DIR):
37
+ os.system(f"git clone {TRIPOSG_REPO_URL} {TRIPOSG_CODE_DIR}")
38
+
39
+ MV_ADAPTER_CODE_DIR = "./mv_adapter"
40
+ if not os.path.exists(MV_ADAPTER_CODE_DIR):
41
+ os.system(f"git clone {MV_ADAPTER_REPO_URL} {MV_ADAPTER_CODE_DIR} && cd {MV_ADAPTER_CODE_DIR} && git checkout 7d37a97e9bc223cdb8fd26a76bd8dd46504c7c3d")
42
+
43
+ import sys
44
+ sys.path.append(TRIPOSG_CODE_DIR)
45
+ sys.path.append(os.path.join(TRIPOSG_CODE_DIR, "scripts"))
46
+ sys.path.append(MV_ADAPTER_CODE_DIR)
47
+ sys.path.append(os.path.join(MV_ADAPTER_CODE_DIR, "scripts"))
48
+
49
+ HEADER = """
50
+ # <img src="https://compass.uol/content/dam/aircompanycompass/header/logo-desktop.png" alt="Compass.UOL">
51
+
52
+ # Compass.UOL | Nestlé| Image to 3D | Proof of Concept
53
+
54
+ ## State-of-the-art 3D Generation Using Large-Scale Rectified Flow Transformers
55
+
56
+ ## 📋 Quick Start Guide:
57
+ 1. **Upload an image** (single object works best)
58
+ 2. Click **Generate Shape** to create the 3D mesh
59
+ 3. Click **Apply Texture** to add textures
60
+ 4. Use **Download GLB** to save the 3D model
61
+ 5. Adjust parameters under **Generation Settings** for fine-tuning
62
+
63
+ Best results come from clean, well-lit images with clear subject isolation.
64
+ """
65
+
66
+ # # triposg
67
+ from image_process import prepare_image
68
+ from briarmbg import BriaRMBG
69
+ snapshot_download("briaai/RMBG-1.4", local_dir=RMBG_PRETRAINED_MODEL)
70
+ rmbg_net = BriaRMBG.from_pretrained(RMBG_PRETRAINED_MODEL).to(DEVICE)
71
+ rmbg_net.eval()
72
+ from triposg.pipelines.pipeline_triposg import TripoSGPipeline
73
+ snapshot_download("VAST-AI/TripoSG", local_dir=TRIPOSG_PRETRAINED_MODEL)
74
+ triposg_pipe = TripoSGPipeline.from_pretrained(TRIPOSG_PRETRAINED_MODEL).to(DEVICE, DTYPE)
75
+
76
+ # mv adapter
77
+ NUM_VIEWS = 6
78
+ from inference_ig2mv_sdxl import prepare_pipeline, preprocess_image, remove_bg
79
+ from mvadapter.utils import get_orthogonal_camera, tensor_to_image, make_image_grid
80
+ from mvadapter.utils.render import NVDiffRastContextWrapper, load_mesh, render
81
+ mv_adapter_pipe = prepare_pipeline(
82
+ base_model="stabilityai/stable-diffusion-xl-base-1.0",
83
+ vae_model="madebyollin/sdxl-vae-fp16-fix",
84
+ unet_model=None,
85
+ lora_model=None,
86
+ adapter_path="huanngzh/mv-adapter",
87
+ scheduler=None,
88
+ num_views=NUM_VIEWS,
89
+ device=DEVICE,
90
+ dtype=torch.float16,
91
+ )
92
+ birefnet = AutoModelForImageSegmentation.from_pretrained(
93
+ "ZhengPeng7/BiRefNet", trust_remote_code=True
94
+ )
95
+ birefnet.to(DEVICE)
96
+ transform_image = transforms.Compose(
97
+ [
98
+ transforms.Resize((1024, 1024)),
99
+ transforms.ToTensor(),
100
+ transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]),
101
+ ]
102
+ )
103
+ remove_bg_fn = lambda x: remove_bg(x, birefnet, transform_image, DEVICE)
104
+
105
+ if not os.path.exists("checkpoints/RealESRGAN_x2plus.pth"):
106
+ hf_hub_download("dtarnow/UPscaler", filename="RealESRGAN_x2plus.pth", local_dir="checkpoints")
107
+ if not os.path.exists("checkpoints/big-lama.pt"):
108
+ subprocess.run("wget -P checkpoints/ https://github.com/Sanster/models/releases/download/add_big_lama/big-lama.pt", shell=True, check=True)
109
+
110
+ def start_session(req: gr.Request):
111
+ save_dir = os.path.join(TMP_DIR, str(req.session_hash))
112
+ os.makedirs(save_dir, exist_ok=True)
113
+ print("start session, mkdir", save_dir)
114
+
115
+ def end_session(req: gr.Request):
116
+ save_dir = os.path.join(TMP_DIR, str(req.session_hash))
117
+ shutil.rmtree(save_dir)
118
+
119
+ def get_random_hex():
120
+ random_bytes = os.urandom(8)
121
+ random_hex = random_bytes.hex()
122
+ return random_hex
123
+
124
+ def get_random_seed(randomize_seed, seed):
125
+ if randomize_seed:
126
+ seed = random.randint(0, MAX_SEED)
127
+ return seed
128
+
129
+ @spaces.GPU(duration=180)
130
+ def run_full(image: str, req: gr.Request):
131
+ seed = 0
132
+ num_inference_steps = 50
133
+ guidance_scale = 7.5
134
+ simplify = True
135
+ target_face_num = DEFAULT_FACE_NUMBER
136
+
137
+ image_seg = prepare_image(image, bg_color=np.array([1.0, 1.0, 1.0]), rmbg_net=rmbg_net)
138
+
139
+ outputs = triposg_pipe(
140
+ image=image_seg,
141
+ generator=torch.Generator(device=triposg_pipe.device).manual_seed(seed),
142
+ num_inference_steps=num_inference_steps,
143
+ guidance_scale=guidance_scale
144
+ ).samples[0]
145
+ print("mesh extraction done")
146
+ mesh = trimesh.Trimesh(outputs[0].astype(np.float32), np.ascontiguousarray(outputs[1]))
147
+
148
+ if simplify:
149
+ print("start simplify")
150
+ from utils import simplify_mesh
151
+ mesh = simplify_mesh(mesh, target_face_num)
152
+
153
+ save_dir = os.path.join(TMP_DIR, "examples")
154
+ os.makedirs(save_dir, exist_ok=True)
155
+ mesh_path = os.path.join(save_dir, f"triposg_{get_random_hex()}.glb")
156
+ mesh.export(mesh_path)
157
+ print("save to ", mesh_path)
158
+
159
+ torch.cuda.empty_cache()
160
+
161
+ height, width = 768, 768
162
+ # Prepare cameras
163
+ cameras = get_orthogonal_camera(
164
+ elevation_deg=[0, 0, 0, 0, 89.99, -89.99],
165
+ distance=[1.8] * NUM_VIEWS,
166
+ left=-0.55,
167
+ right=0.55,
168
+ bottom=-0.55,
169
+ top=0.55,
170
+ azimuth_deg=[x - 90 for x in [0, 90, 180, 270, 180, 180]],
171
+ device=DEVICE,
172
+ )
173
+ ctx = NVDiffRastContextWrapper(device=DEVICE, context_type="cuda")
174
+
175
+ mesh = load_mesh(mesh_path, rescale=True, device=DEVICE)
176
+ render_out = render(
177
+ ctx,
178
+ mesh,
179
+ cameras,
180
+ height=height,
181
+ width=width,
182
+ render_attr=False,
183
+ normal_background=0.0,
184
+ )
185
+ control_images = (
186
+ torch.cat(
187
+ [
188
+ (render_out.pos + 0.5).clamp(0, 1),
189
+ (render_out.normal / 2 + 0.5).clamp(0, 1),
190
+ ],
191
+ dim=-1,
192
+ )
193
+ .permute(0, 3, 1, 2)
194
+ .to(DEVICE)
195
+ )
196
+
197
+ image = Image.open(image)
198
+ image = remove_bg_fn(image)
199
+ image = preprocess_image(image, height, width)
200
+
201
+ pipe_kwargs = {}
202
+ if seed != -1 and isinstance(seed, int):
203
+ pipe_kwargs["generator"] = torch.Generator(device=DEVICE).manual_seed(seed)
204
+
205
+ images = mv_adapter_pipe(
206
+ "high quality",
207
+ height=height,
208
+ width=width,
209
+ num_inference_steps=15,
210
+ guidance_scale=3.0,
211
+ num_images_per_prompt=NUM_VIEWS,
212
+ control_image=control_images,
213
+ control_conditioning_scale=1.0,
214
+ reference_image=image,
215
+ reference_conditioning_scale=1.0,
216
+ negative_prompt="watermark, ugly, deformed, noisy, blurry, low contrast",
217
+ cross_attention_kwargs={"scale": 1.0},
218
+ **pipe_kwargs,
219
+ ).images
220
+
221
+ torch.cuda.empty_cache()
222
+
223
+ mv_image_path = os.path.join(save_dir, f"mv_adapter_{get_random_hex()}.png")
224
+ make_image_grid(images, rows=1).save(mv_image_path)
225
+
226
+ from texture import TexturePipeline, ModProcessConfig
227
+ texture_pipe = TexturePipeline(
228
+ upscaler_ckpt_path="checkpoints/RealESRGAN_x2plus.pth",
229
+ inpaint_ckpt_path="checkpoints/big-lama.pt",
230
+ device=DEVICE,
231
+ )
232
+
233
+ textured_glb_path = texture_pipe(
234
+ mesh_path=mesh_path,
235
+ save_dir=save_dir,
236
+ save_name=f"texture_mesh_{get_random_hex()}.glb",
237
+ uv_unwarp=True,
238
+ uv_size=4096,
239
+ rgb_path=mv_image_path,
240
+ rgb_process_config=ModProcessConfig(view_upscale=True, inpaint_mode="view"),
241
+ camera_azimuth_deg=[x - 90 for x in [0, 90, 180, 270, 180, 180]],
242
+ )
243
+
244
+ return image_seg, mesh_path, textured_glb_path
245
+
246
+
247
+ @spaces.GPU()
248
+ @torch.no_grad()
249
+ def run_segmentation(image: str):
250
+ image = prepare_image(image, bg_color=np.array([1.0, 1.0, 1.0]), rmbg_net=rmbg_net)
251
+ return image
252
+
253
+ @spaces.GPU(duration=90)
254
+ @torch.no_grad()
255
+ def image_to_3d(
256
+ image: Image.Image,
257
+ seed: int,
258
+ num_inference_steps: int,
259
+ guidance_scale: float,
260
+ simplify: bool,
261
+ target_face_num: int,
262
+ req: gr.Request
263
+ ):
264
+ outputs = triposg_pipe(
265
+ image=image,
266
+ generator=torch.Generator(device=triposg_pipe.device).manual_seed(seed),
267
+ num_inference_steps=num_inference_steps,
268
+ guidance_scale=guidance_scale
269
+ ).samples[0]
270
+ print("mesh extraction done")
271
+ mesh = trimesh.Trimesh(outputs[0].astype(np.float32), np.ascontiguousarray(outputs[1]))
272
+
273
+ if simplify:
274
+ print("start simplify")
275
+ from utils import simplify_mesh
276
+ mesh = simplify_mesh(mesh, target_face_num)
277
+
278
+ save_dir = os.path.join(TMP_DIR, str(req.session_hash))
279
+ mesh_path = os.path.join(save_dir, f"triposg_{get_random_hex()}.glb")
280
+ mesh.export(mesh_path)
281
+ print("save to ", mesh_path)
282
+
283
+ torch.cuda.empty_cache()
284
+
285
+ return mesh_path
286
+
287
+ @spaces.GPU(duration=120)
288
+ @torch.no_grad()
289
+ def run_texture(image: Image, mesh_path: str, seed: int, text_prompt: str, req: gr.Request):
290
+ height, width = 768, 768
291
+ # Prepare cameras
292
+ cameras = get_orthogonal_camera(
293
+ elevation_deg=[0, 0, 0, 0, 89.99, -89.99],
294
+ distance=[1.8] * NUM_VIEWS,
295
+ left=-0.55,
296
+ right=0.55,
297
+ bottom=-0.55,
298
+ top=0.55,
299
+ azimuth_deg=[x - 90 for x in [0, 90, 180, 270, 180, 180]],
300
+ device=DEVICE,
301
+ )
302
+ ctx = NVDiffRastContextWrapper(device=DEVICE, context_type="cuda")
303
+
304
+ mesh = load_mesh(mesh_path, rescale=True, device=DEVICE)
305
+ render_out = render(
306
+ ctx,
307
+ mesh,
308
+ cameras,
309
+ height=height,
310
+ width=width,
311
+ render_attr=False,
312
+ normal_background=0.0,
313
+ )
314
+ control_images = (
315
+ torch.cat(
316
+ [
317
+ (render_out.pos + 0.5).clamp(0, 1),
318
+ (render_out.normal / 2 + 0.5).clamp(0, 1),
319
+ ],
320
+ dim=-1,
321
+ )
322
+ .permute(0, 3, 1, 2)
323
+ .to(DEVICE)
324
+ )
325
+
326
+ image = Image.open(image)
327
+ image = remove_bg_fn(image)
328
+ image = preprocess_image(image, height, width)
329
+
330
+ pipe_kwargs = {}
331
+ if seed != -1 and isinstance(seed, int):
332
+ pipe_kwargs["generator"] = torch.Generator(device=DEVICE).manual_seed(seed)
333
+
334
+ images = mv_adapter_pipe(
335
+ text_prompt,
336
+ height=height,
337
+ width=width,
338
+ num_inference_steps=15,
339
+ guidance_scale=3.0,
340
+ num_images_per_prompt=NUM_VIEWS,
341
+ control_image=control_images,
342
+ control_conditioning_scale=1.0,
343
+ reference_image=image,
344
+ reference_conditioning_scale=1.0,
345
+ negative_prompt="watermark, ugly, deformed, noisy, blurry, low contrast",
346
+ cross_attention_kwargs={"scale": 1.0},
347
+ **pipe_kwargs,
348
+ ).images
349
+
350
+ torch.cuda.empty_cache()
351
+
352
+ save_dir = os.path.join(TMP_DIR, str(req.session_hash))
353
+ mv_image_path = os.path.join(save_dir, f"mv_adapter_{get_random_hex()}.png")
354
+ make_image_grid(images, rows=1).save(mv_image_path)
355
+
356
+ from texture import TexturePipeline, ModProcessConfig
357
+ texture_pipe = TexturePipeline(
358
+ upscaler_ckpt_path="checkpoints/RealESRGAN_x2plus.pth",
359
+ inpaint_ckpt_path="checkpoints/big-lama.pt",
360
+ device=DEVICE,
361
+ )
362
+
363
+ textured_glb_path = texture_pipe(
364
+ mesh_path=mesh_path,
365
+ save_dir=save_dir,
366
+ save_name=f"texture_mesh_{get_random_hex()}.glb",
367
+ uv_unwarp=True,
368
+ uv_size=4096,
369
+ rgb_path=mv_image_path,
370
+ rgb_process_config=ModProcessConfig(view_upscale=True, inpaint_mode="view"),
371
+ camera_azimuth_deg=[x - 90 for x in [0, 90, 180, 270, 180, 180]],
372
+ )
373
+
374
+ return textured_glb_path
375
+
376
+
377
+ with gr.Blocks(title="Nestlé | Proof of Concept") as demo:
378
+ gr.Markdown(HEADER)
379
+
380
+ with gr.Row():
381
+ with gr.Column():
382
+ with gr.Row():
383
+ image_prompts = gr.Image(label="Input Image", type="filepath")
384
+ seg_image = gr.Image(
385
+ label="Segmentation Result", type="pil", format="png", interactive=False
386
+ )
387
+
388
+ with gr.Accordion("Generation Settings", open=True):
389
+ text_prompt = gr.Textbox(label="Prompt", placeholder="Enter your prompt", value="high quality")
390
+ seed = gr.Slider(
391
+ label="Seed",
392
+ minimum=0,
393
+ maximum=MAX_SEED,
394
+ step=0,
395
+ value=0
396
+ )
397
+ randomize_seed = gr.Checkbox(label="Randomize seed", value=True)
398
+ num_inference_steps = gr.Slider(
399
+ label="Number of inference steps",
400
+ minimum=8,
401
+ maximum=50,
402
+ step=1,
403
+ value=50,
404
+ )
405
+ guidance_scale = gr.Slider(
406
+ label="CFG scale",
407
+ minimum=0.0,
408
+ maximum=20.0,
409
+ step=0.1,
410
+ value=7.0,
411
+ )
412
+
413
+ with gr.Row():
414
+ reduce_face = gr.Checkbox(label="Simplify Mesh", value=True)
415
+ target_face_num = gr.Slider(maximum=1000000, minimum=10000, value=DEFAULT_FACE_NUMBER, label="Target Face Number")
416
+
417
+ gen_button = gr.Button("Generate Shape", variant="primary")
418
+ gen_texture_button = gr.Button("Apply Texture", interactive=False)
419
+
420
+ with gr.Column():
421
+ model_output = gr.Model3D(label="Generated GLB", interactive=False)
422
+ textured_model_output = gr.Model3D(label="Textured GLB", interactive=False)
423
+
424
+ gen_button.click(
425
+ run_segmentation,
426
+ inputs=[image_prompts],
427
+ outputs=[seg_image]
428
+ ).then(
429
+ get_random_seed,
430
+ inputs=[randomize_seed, seed],
431
+ outputs=[seed],
432
+ ).then(
433
+ image_to_3d,
434
+ inputs=[
435
+ seg_image,
436
+ seed,
437
+ num_inference_steps,
438
+ guidance_scale,
439
+ reduce_face,
440
+ target_face_num
441
+ ],
442
+ outputs=[model_output]
443
+ ).then(lambda: gr.Button(interactive=True), outputs=[gen_texture_button])
444
+
445
+ gen_texture_button.click(
446
+ run_texture,
447
+ inputs=[image_prompts, model_output, seed, text_prompt],
448
+ outputs=[textured_model_output]
449
+ )
450
+
451
+ demo.load(start_session)
452
+ demo.unload(end_session)
453
+
454
+ demo.launch()
app.py CHANGED
@@ -46,10 +46,22 @@ sys.path.append(os.path.join(TRIPOSG_CODE_DIR, "scripts"))
46
  sys.path.append(MV_ADAPTER_CODE_DIR)
47
  sys.path.append(os.path.join(MV_ADAPTER_CODE_DIR, "scripts"))
48
 
49
- # Custom styling constants
50
- NESTLE_BLUE = "#0066b1"
51
- NESTLE_BLUE_DARK = "#004a82"
52
- ACCENT_COLOR = "#10b981"
 
 
 
 
 
 
 
 
 
 
 
 
53
 
54
  # # triposg
55
  from image_process import prepare_image
@@ -235,9 +247,7 @@ def run_full(image: str, req: gr.Request):
235
  @spaces.GPU()
236
  @torch.no_grad()
237
  def run_segmentation(image: str):
238
- print("run_segmentation pre image str path: ", image)
239
  image = prepare_image(image, bg_color=np.array([1.0, 1.0, 1.0]), rmbg_net=rmbg_net)
240
- print("run_segmentation pos image: ", image)
241
  return image
242
 
243
  @spaces.GPU(duration=90)
@@ -363,721 +373,58 @@ def run_texture(image: Image, mesh_path: str, seed: int, text_prompt: str, req:
363
 
364
  return textured_glb_path
365
 
366
- # Custom UI components
367
- def create_header():
368
- return f"""
369
- <div class="card" style="background: linear-gradient(135deg, {NESTLE_BLUE} 0%, {NESTLE_BLUE_DARK} 100%); color: white; border: none;">
370
- <div style="display: flex; align-items: center; gap: 20px;">
371
- <div style="background: white; padding: 12px; border-radius: 12px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);">
372
- <img src="https://logodownload.org/wp-content/uploads/2016/11/nestle-logo-1.png"
373
- alt="Nestlé Logo" style="height: 48px; width: auto;">
374
- </div>
375
- <div style="flex: 1;">
376
- <h1 style="margin: 0; font-size: 2.5rem; font-weight: 700; letter-spacing: -0.025em;">
377
- Nestlé 3D Generator
378
- </h1>
379
- <p style="margin: 0.5rem 0 0 0; opacity: 0.9; font-size: 1.1rem;">
380
- Transform your product images into stunning 3D models with AI
381
- </p>
382
- </div>
383
- <div class="badge primary">Beta v2.0</div>
384
- </div>
385
- </div>
386
- """
387
-
388
- def create_tabs():
389
- return """
390
- <div class="tabs-container">
391
- <div class="tabs-list">
392
- <button class="tab-button active" onclick="switchTab('segmentation')">
393
- 🔍 Segmentation
394
- </button>
395
- <button class="tab-button" onclick="switchTab('model')">
396
- 🎨 3D Model
397
- </button>
398
- <button class="tab-button" onclick="switchTab('textured')">
399
- ✨ Textured Model
400
- </button>
401
- </div>
402
-
403
- <div id="segmentation-tab" class="tab-content active">
404
- <div style="text-align: center; color: #1e293b;">
405
- <div style="font-size: 4rem; margin-bottom: 1rem;">📤</div>
406
- <p>Upload an image to see segmentation results</p>
407
- </div>
408
- </div>
409
-
410
- <div id="model-tab" class="tab-content">
411
- <div style="text-align: center; color: #1e293b;">
412
- <div style="font-size: 4rem; margin-bottom: 1rem;">🎯</div>
413
- <p>3D model will appear here after generation</p>
414
- </div>
415
- </div>
416
-
417
- <div id="textured-tab" class="tab-content">
418
- <div style="text-align: center; color: #1e293b;">
419
- <div style="font-size: 4rem; margin-bottom: 1rem;">🎨</div>
420
- <p>Textured model will appear here</p>
421
- </div>
422
- </div>
423
- </div>
424
- """
425
-
426
- def create_progress_bar():
427
- return """
428
- <div class="progress-container" style="display: none;" id="progress-container">
429
- <div class="progress-header">
430
- <span>Generating 3D model...</span>
431
- <span id="progress-text">0%</span>
432
- </div>
433
- <div class="progress-bar-container">
434
- <div class="progress-bar" id="progress-bar"></div>
435
- </div>
436
- </div>
437
- """
438
-
439
- # JavaScript
440
- ADVANCED_JS = """
441
- <script>
442
- // React-like state management simulation
443
- window.appState = {
444
- currentTab: 'segmentation',
445
- isGenerating: false,
446
- progress: 0
447
- };
448
-
449
- // Tab switching functionality
450
- function switchTab(tabName) {
451
- window.appState.currentTab = tabName;
452
-
453
- // Hide all tab contents
454
- document.querySelectorAll('.tab-content').forEach(el => {
455
- el.style.display = 'none';
456
- });
457
-
458
- // Show selected tab
459
- const selectedTab = document.getElementById(tabName + '-tab');
460
- if (selectedTab) {
461
- selectedTab.style.display = 'block';
462
- }
463
-
464
- // Update tab buttons
465
- document.querySelectorAll('.tab-button').forEach(btn => {
466
- btn.classList.remove('active');
467
- });
468
-
469
- const activeBtn = document.querySelector(`[onclick="switchTab('${tabName}')"]`);
470
- if (activeBtn) {
471
- activeBtn.classList.add('active');
472
- }
473
- }
474
-
475
- // Progress simulation
476
- function simulateProgress() {
477
- window.appState.isGenerating = true;
478
- window.appState.progress = 0;
479
-
480
- const progressBar = document.getElementById('progress-bar');
481
- const progressText = document.getElementById('progress-text');
482
-
483
- const interval = setInterval(() => {
484
- window.appState.progress += 10;
485
-
486
- if (progressBar) {
487
- progressBar.style.width = window.appState.progress + '%';
488
- }
489
-
490
- if (progressText) {
491
- progressText.textContent = window.appState.progress + '%';
492
- }
493
-
494
- if (window.appState.progress >= 100) {
495
- clearInterval(interval);
496
- window.appState.isGenerating = false;
497
- }
498
- }, 300);
499
- }
500
-
501
- // Drag and drop simulation
502
- function setupDragDrop() {
503
- const uploadArea = document.querySelector('.upload-area');
504
- if (uploadArea) {
505
- uploadArea.addEventListener('dragover', (e) => {
506
- e.preventDefault();
507
- uploadArea.classList.add('drag-over');
508
- });
509
-
510
- uploadArea.addEventListener('dragleave', () => {
511
- uploadArea.classList.remove('drag-over');
512
- });
513
-
514
- uploadArea.addEventListener('drop', (e) => {
515
- e.preventDefault();
516
- uploadArea.classList.remove('drag-over');
517
- // Handle file drop
518
- });
519
- }
520
- }
521
-
522
- // Initialize when DOM is ready
523
- document.addEventListener('DOMContentLoaded', function() {
524
- setupDragDrop();
525
- switchTab('segmentation');
526
- });
527
- </script>
528
- """
529
-
530
- # CSS
531
- ADVANCED_CSS = f"""
532
- @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
533
-
534
- :root {{
535
- --nestle-blue: {NESTLE_BLUE};
536
- --nestle-blue-dark: {NESTLE_BLUE_DARK};
537
- --accent: {ACCENT_COLOR};
538
- --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.1);
539
- --shadow-md: 0 4px 12px rgba(0, 0, 0, 0.1);
540
- --shadow-lg: 0 10px 25px rgba(0, 0, 0, 0.1);
541
- --border-radius: 12px;
542
- }}
543
-
544
- * {{
545
- font-family: 'Inter', sans-serif !important;
546
- }}
547
-
548
- body, .gradio-container {{
549
- background: linear-gradient(135deg, #f8fafc 0%, #e2e8f0 100%) !important;
550
- margin: 0 !important;
551
- padding: 0 !important;
552
- min-height: 100vh !important;
553
- color: #ffffff !important;
554
- font-size: 1rem !important;
555
- }}
556
-
557
- /* AGGRESSIVE TEXT COLOR FIXES - Higher specificity */
558
- .gradio-container *,
559
- .gradio-container div,
560
- .gradio-container span,
561
- .gradio-container p,
562
- .gradio-container label,
563
- .gradio-container h1,
564
- .gradio-container h2,
565
- .gradio-container h3,
566
- .gradio-container h4,
567
- .gradio-container h5,
568
- .gradio-container h6 {{
569
- color: #ffffff !important;
570
- text-shadow: 0 1px 3px rgba(0, 0, 0, 0.8) !important;
571
- }}
572
-
573
- /* Force white text on all Gradio components */
574
- .gr-group *,
575
- .gr-form *,
576
- .gr-block *,
577
- .gr-box *,
578
- div[class*="gr-"] *,
579
- div[class*="svelte-"] *,
580
- span[class*="svelte-"] *,
581
- label[class*="svelte-"] *,
582
- p[class*="svelte-"] * {{
583
- color: #ffffff !important;
584
- text-shadow: 0 1px 3px rgba(0, 0, 0, 0.8) !important;
585
- font-weight: 600 !important;
586
- }}
587
-
588
- /* Specific targeting for card descriptions and titles */
589
- .card-description,
590
- .card-title,
591
- div.card-description,
592
- div.card-title,
593
- p.card-description,
594
- h3.card-title {{
595
- color: #ffffff !important;
596
- text-shadow: 0 1px 3px rgba(0, 0, 0, 0.8) !important;
597
- font-weight: 700 !important;
598
- background: rgba(0, 0, 0, 0.3) !important;
599
- padding: 4px 8px !important;
600
- border-radius: 6px !important;
601
- margin: 0.5rem 0 !important;
602
- display: inline-block !important;
603
- }}
604
-
605
- /* Card Components */
606
- .card {{
607
- background: white;
608
- border: 1px solid #e2e8f0;
609
- border-radius: var(--border-radius);
610
- box-shadow: var(--shadow-md);
611
- padding: 1.5rem;
612
- transition: all 0.2s ease;
613
- margin-bottom: 1rem;
614
- }}
615
-
616
- .card:hover {{
617
- box-shadow: var(--shadow-lg);
618
- transform: translateY(-2px);
619
- }}
620
-
621
- .card-header {{
622
- margin-bottom: 1rem;
623
- padding-bottom: 1rem;
624
- border-bottom: 1px solid #e2e8f0;
625
- }}
626
-
627
- /* Tabs */
628
- .tabs-container {{
629
- background: white;
630
- border-radius: var(--border-radius);
631
- box-shadow: var(--shadow-md);
632
- overflow: hidden;
633
- }}
634
-
635
- .tabs-list {{
636
- display: flex;
637
- background: #f8fafc;
638
- border-bottom: 1px solid #e2e8f0;
639
- }}
640
-
641
- .tab-button {{
642
- flex: 1;
643
- padding: 1rem;
644
- background: none;
645
- border: none;
646
- cursor: pointer;
647
- font-weight: 600;
648
- color: #334155 !important;
649
- font-size: 1rem;
650
- transition: all 0.2s ease;
651
- position: relative;
652
- }}
653
-
654
- .tab-button:hover {{
655
- background: #f1f5f9;
656
- color: #1e293b !important;
657
- }}
658
-
659
- .tab-button.active {{
660
- color: var(--nestle-blue) !important;
661
- background: white;
662
- font-weight: 800;
663
- }}
664
-
665
- .tab-button.active::after {{
666
- content: '';
667
- position: absolute;
668
- bottom: 0;
669
- left: 0;
670
- right: 0;
671
- height: 2px;
672
- background: var(--nestle-blue);
673
- }}
674
-
675
- .tab-content {{
676
- padding: 2rem;
677
- min-height: 400px;
678
- display: none;
679
- }}
680
-
681
- .tab-content.active {{
682
- display: block;
683
- }}
684
-
685
- .tab-content * {{
686
- color: #1e293b !important;
687
- text-shadow: none !important;
688
- }}
689
-
690
- /* Progress Component */
691
- .progress-container {{
692
- margin: 1rem 0;
693
- padding: 1rem;
694
- background: #f8fafc;
695
- border-radius: var(--border-radius);
696
- border: 1px solid #e2e8f0;
697
- }}
698
-
699
- .progress-header {{
700
- display: flex;
701
- justify-content: space-between;
702
- margin-bottom: 0.5rem;
703
- font-size: 1rem;
704
- color: #334155 !important;
705
- font-weight: 600;
706
- }}
707
-
708
- .progress-bar-container {{
709
- width: 100%;
710
- height: 8px;
711
- background: #e2e8f0;
712
- border-radius: 4px;
713
- overflow: hidden;
714
- }}
715
 
716
- .progress-bar {{
717
- height: 100%;
718
- background: linear-gradient(90deg, var(--nestle-blue) 0%, var(--accent) 100%);
719
- width: 0%;
720
- transition: width 0.3s ease;
721
- border-radius: 4px;
722
- }}
723
 
724
- /* Badge */
725
- .badge {{
726
- display: inline-flex;
727
- align-items: center;
728
- padding: 0.25rem 0.75rem;
729
- background: #e2e8f0;
730
- color: #1e293b !important;
731
- border-radius: 9999px;
732
- font-size: 0.85rem;
733
- font-weight: 600;
734
- }}
735
-
736
- .badge.primary {{
737
- background: var(--nestle-blue);
738
- color: #fff !important;
739
- }}
740
-
741
- /* Button variants */
742
- .btn, .btn-primary, .btn-secondary, .gr-button {{
743
- display: inline-flex;
744
- align-items: center;
745
- justify-content: center;
746
- gap: 0.5rem;
747
- padding: 0.75rem 1.5rem;
748
- border-radius: var(--border-radius);
749
- font-weight: 700 !important;
750
- font-size: 1rem !important;
751
- border: none;
752
- cursor: pointer;
753
- transition: all 0.2s ease;
754
- text-decoration: none;
755
- letter-spacing: -0.01em;
756
- }}
757
-
758
- .btn-primary, .gr-button {{
759
- background: linear-gradient(135deg, var(--nestle-blue) 0%, var(--nestle-blue-dark) 100%) !important;
760
- color: white !important;
761
- box-shadow: var(--shadow-sm) !important;
762
- }}
763
-
764
- .btn-primary:hover, .gr-button:hover {{
765
- transform: translateY(-1px) !important;
766
- box-shadow: var(--shadow-md) !important;
767
- }}
768
-
769
- .btn-secondary {{
770
- background: white !important;
771
- color: #374151 !important;
772
- border: 1px solid #d1d5db !important;
773
- }}
774
-
775
- .btn-secondary:hover {{
776
- background: #f9fafb !important;
777
- }}
778
-
779
- /* Enhanced Gradio component styling */
780
- .gr-image, .gr-model3d {{
781
- border: 2px solid #e2e8f0 !important;
782
- border-radius: var(--border-radius) !important;
783
- box-shadow: var(--shadow-sm) !important;
784
- transition: all 0.2s ease !important;
785
- }}
786
-
787
- .gr-slider .noUi-connect {{
788
- background: linear-gradient(90deg, var(--nestle-blue) 0%, var(--accent) 100%) !important;
789
- }}
790
-
791
- .gr-slider .noUi-handle {{
792
- background: white !important;
793
- border: 3px solid var(--nestle-blue) !important;
794
- border-radius: 50% !important;
795
- box-shadow: var(--shadow-md) !important;
796
- }}
797
-
798
- /* Responsive design */
799
- @media (max-width: 768px) {{
800
- .tabs-list {{
801
- flex-direction: column;
802
- }}
803
-
804
- .card {{
805
- padding: 1rem;
806
- }}
807
- }}
808
-
809
- /* SUPER AGGRESSIVE TEXT FIXES */
810
- /* Target every possible Gradio text element */
811
- .gradio-container .gr-group .gr-form label,
812
- .gradio-container .gr-group .gr-form span,
813
- .gradio-container .gr-group .gr-form div,
814
- .gradio-container .gr-group .gr-form p,
815
- .gradio-container .gr-block label,
816
- .gradio-container .gr-block span,
817
- .gradio-container .gr-block div,
818
- .gradio-container .gr-block p,
819
- .gradio-container .gr-box label,
820
- .gradio-container .gr-box span,
821
- .gradio-container .gr-box div,
822
- .gradio-container .gr-box p {{
823
- color: #ffffff !important;
824
- text-shadow: 0 1px 3px rgba(0, 0, 0, 0.8) !important;
825
- font-weight: 600 !important;
826
- opacity: 1 !important;
827
- }}
828
-
829
- /* Target Svelte components specifically */
830
- [class*="svelte-"] {{
831
- color: #ffffff !important;
832
- text-shadow: 0 1px 3px rgba(0, 0, 0, 0.8) !important;
833
- }}
834
-
835
- /* Target slider labels and info text */
836
- .gr-slider label,
837
- .gr-slider .gr-text,
838
- .gr-slider span,
839
- .gr-checkbox label,
840
- .gr-checkbox span {{
841
- color: #ffffff !important;
842
- text-shadow: 0 1px 3px rgba(0, 0, 0, 0.8) !important;
843
- font-weight: 600 !important;
844
- }}
845
-
846
- /* Target info text specifically */
847
- .gr-info,
848
- [class*="info"],
849
- .info {{
850
- color: #ffffff !important;
851
- text-shadow: 0 1px 3px rgba(0, 0, 0, 0.8) !important;
852
- font-weight: 500 !important;
853
- background: rgba(0, 0, 0, 0.2) !important;
854
- padding: 2px 6px !important;
855
- border-radius: 4px !important;
856
- }}
857
-
858
- /* Fix for image action icons */
859
- .gr-image .image-button,
860
- .gr-image button,
861
- .gr-image .icon-button,
862
- .gr-image [role="button"],
863
- .gr-image .svelte-1pijsyv,
864
- .gr-image .svelte-1pijsyv button {{
865
- background: rgba(255, 255, 255, 0.95) !important;
866
- border: 1px solid #e2e8f0 !important;
867
- border-radius: 8px !important;
868
- padding: 8px !important;
869
- margin: 2px !important;
870
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15) !important;
871
- transition: all 0.2s ease !important;
872
- color: #374151 !important;
873
- font-size: 16px !important;
874
- min-width: 36px !important;
875
- min-height: 36px !important;
876
- display: flex !important;
877
- align-items: center !important;
878
- justify-content: center !important;
879
- }}
880
-
881
- .gr-image .image-button:hover,
882
- .gr-image button:hover,
883
- .gr-image .icon-button:hover,
884
- .gr-image [role="button"]:hover,
885
- .gr-image .svelte-1pijsyv:hover,
886
- .gr-image .svelte-1pijsyv button:hover {{
887
- background: rgba(255, 255, 255, 1) !important;
888
- transform: translateY(-1px) !important;
889
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2) !important;
890
- color: var(--nestle-blue) !important;
891
- }}
892
-
893
- /* Upload area text */
894
- .gr-image .upload-text,
895
- .gr-image .drag-text,
896
- .gr-image .svelte-1ipelgc {{
897
- color: #1e293b !important;
898
- font-weight: 600 !important;
899
- text-shadow: 0 0 4px white !important;
900
- background: rgba(255, 255, 255, 0.9) !important;
901
- padding: 8px 12px !important;
902
- border-radius: 8px !important;
903
- margin: 4px !important;
904
- }}
905
-
906
- /* Nuclear option - force all text to be white with shadow */
907
- * {{
908
- color: #ffffff !important;
909
- text-shadow: 0 1px 3px rgba(0, 0, 0, 0.8) !important;
910
- }}
911
-
912
- /* But override for specific areas that should be dark */
913
- .tabs-container *,
914
- .tab-content *,
915
- .badge *,
916
- .btn *,
917
- .gr-button *,
918
- .upload-area *,
919
- .gr-image .upload-text *,
920
- .gr-image .drag-text *,
921
- .gr-image .svelte-1ipelgc *,
922
- .progress-container * {{
923
- color: #1e293b !important;
924
- text-shadow: 0 0 2px white !important;
925
- }}
926
-
927
- /* Header text should remain white */
928
- .card[style*="linear-gradient"] *,
929
- .card[style*="linear-gradient"] h1,
930
- .card[style*="linear-gradient"] p {{
931
- color: #ffffff !important;
932
- text-shadow: 0 1px 3px rgba(0, 0, 0, 0.5) !important;
933
- }}
934
- """
935
-
936
- # interface
937
- with gr.Blocks(
938
- title="Nestlé 3D Generator",
939
- css=ADVANCED_CSS,
940
- head=ADVANCED_JS,
941
- theme=gr.themes.Soft(
942
- primary_hue="blue",
943
- secondary_hue="slate",
944
- neutral_hue="slate",
945
- font=gr.themes.GoogleFont("Inter")
946
- )
947
- ) as demo:
948
-
949
- # Header
950
- gr.HTML(create_header())
951
-
952
  with gr.Row():
953
- with gr.Column(scale=1):
954
- with gr.Group():
955
- gr.HTML("""
956
- <div class="card-header">
957
- <h3 class="card-title">📤 Product Image Upload</h3>
958
- <p class="card-description">Upload a clear image of your Nestlé product</p>
959
- </div>
960
- """)
961
-
962
- image_prompts = gr.Image(
963
- label="",
964
- type="filepath",
965
- show_label=False,
966
- height=350,
967
- elem_classes=["upload-area"]
968
  )
969
-
970
- # Settings Card
971
- with gr.Group():
972
- gr.HTML("""
973
- <div class="card-header">
974
- <h3 class="card-title">⚙️ Generation Settings</h3>
975
- <p class="card-description">Configure your 3D model generation</p>
976
- </div>
977
- """)
978
-
979
- text_prompt = gr.Textbox(label="Prompt", placeholder="Enter your prompt", value="high quality")
980
 
981
- with gr.Row():
982
- randomize_seed = gr.Checkbox(
983
- label="🎲 Randomize Seed",
984
- value=True
985
- )
986
- seed = gr.Slider(
987
- label="Seed Value",
988
- minimum=0,
989
- maximum=MAX_SEED,
990
- step=1,
991
- value=0
992
- )
993
-
994
  num_inference_steps = gr.Slider(
995
- label="🔄 Inference Steps",
996
  minimum=8,
997
  maximum=50,
998
  step=1,
999
  value=50,
1000
- info="Higher values = better quality, slower generation"
1001
  )
1002
-
1003
  guidance_scale = gr.Slider(
1004
- label="🎯 Guidance Scale",
1005
  minimum=0.0,
1006
  maximum=20.0,
1007
  step=0.1,
1008
  value=7.0,
1009
- info="Controls how closely the model follows the input"
1010
  )
1011
 
1012
  with gr.Row():
1013
- reduce_face = gr.Checkbox(
1014
- label="🔧 Optimize Mesh",
1015
- value=True,
1016
- info="Reduce polygon count for better performance"
1017
- )
1018
- target_face_num = gr.Slider(
1019
- label="Target Faces",
1020
- maximum=1_000_000,
1021
- minimum=10_000,
1022
- value=DEFAULT_FACE_NUMBER,
1023
- step=1000
1024
- )
1025
 
1026
- with gr.Column(scale=2):
1027
- gr.HTML("""
1028
- <div class="card-header">
1029
- <h3 class="card-title">3D Model Generation</h3>
1030
- <p class="card-description">View your generated 3D models and apply textures</p>
1031
- </div>
1032
- """)
1033
-
1034
- # CT React-like
1035
- gr.HTML(create_tabs())
1036
-
1037
- # PB
1038
- gr.HTML(create_progress_bar())
1039
-
1040
- # Hidden Gradio components for actual functionality
1041
- with gr.Row(visible=False):
1042
- seg_image = gr.Image(type="pil", format="png", interactive=False)
1043
- model_output = gr.Model3D(interactive=False)
1044
- textured_model_output = gr.Model3D(interactive=False)
1045
 
1046
- # Action Buttons
1047
- with gr.Row():
1048
- gen_button = gr.Button(
1049
- "🚀 Generate 3D Model",
1050
- variant="primary",
1051
- size="lg",
1052
- elem_classes=["btn", "btn-primary"]
1053
- )
1054
- gen_texture_button = gr.Button(
1055
- "🎨 Apply Texture",
1056
- variant="secondary",
1057
- size="lg",
1058
- interactive=False,
1059
- elem_classes=["btn", "btn-secondary"]
1060
- )
1061
- download_button = gr.Button(
1062
- "💾 Download Model",
1063
- variant="secondary",
1064
- size="lg",
1065
- elem_classes=["btn", "btn-secondary"]
1066
- )
1067
-
1068
- status_display = gr.HTML(
1069
- """<div style='text-align: center; padding: 1rem; color: #1e293b;'>
1070
- <span style='display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: #10b981; margin-right: 8px;'></span>
1071
- Ready to generate your 3D model
1072
- </div>"""
1073
- )
1074
 
1075
- # Event Handlers with JavaScript integration
1076
  gen_button.click(
1077
- fn=run_segmentation,
1078
  inputs=[image_prompts],
1079
- outputs=[seg_image],
1080
- # js="() => { simulateProgress(); document.getElementById('progress-container').style.display = 'block'; }",
1081
  ).then(
1082
  get_random_seed,
1083
  inputs=[randomize_seed, seed],
@@ -1093,32 +440,15 @@ with gr.Blocks(
1093
  target_face_num
1094
  ],
1095
  outputs=[model_output]
1096
- ).then(
1097
- fn=lambda: gr.Button(interactive=True),
1098
- outputs=[gen_texture_button]
1099
- )
1100
 
1101
  gen_texture_button.click(
1102
  run_texture,
1103
  inputs=[image_prompts, model_output, seed, text_prompt],
1104
  outputs=[textured_model_output]
1105
  )
1106
-
1107
- # with gr.Row():
1108
- # examples = gr.Examples(
1109
- # examples=[
1110
- # f"./examples/{image}"
1111
- # for image in os.listdir(f"./examples/")
1112
- # ],
1113
- # fn=run_full,
1114
- # inputs=[image_prompts],
1115
- # outputs=[seg_image, model_output, textured_model_output],
1116
- # cache_examples=False,
1117
- # )
1118
 
1119
  demo.load(start_session)
1120
  demo.unload(end_session)
1121
 
1122
-
1123
- if __name__ == "__main__":
1124
- demo.launch(share=False, show_error=True)
 
46
  sys.path.append(MV_ADAPTER_CODE_DIR)
47
  sys.path.append(os.path.join(MV_ADAPTER_CODE_DIR, "scripts"))
48
 
49
+ HEADER = """
50
+ # <img src="https://compass.uol/content/dam/aircompanycompass/header/logo-desktop.png" alt="Compass.UOL">
51
+
52
+ # Compass.UOL | Nestlé| Image to 3D | Proof of Concept
53
+
54
+ ## State-of-the-art 3D Generation Using Large-Scale Rectified Flow Transformers
55
+
56
+ ## 📋 Quick Start Guide:
57
+ 1. **Upload an image** (single object works best)
58
+ 2. Click **Generate Shape** to create the 3D mesh
59
+ 3. Click **Apply Texture** to add textures
60
+ 4. Use **Download GLB** to save the 3D model
61
+ 5. Adjust parameters under **Generation Settings** for fine-tuning
62
+
63
+ Best results come from clean, well-lit images with clear subject isolation.
64
+ """
65
 
66
  # # triposg
67
  from image_process import prepare_image
 
247
  @spaces.GPU()
248
  @torch.no_grad()
249
  def run_segmentation(image: str):
 
250
  image = prepare_image(image, bg_color=np.array([1.0, 1.0, 1.0]), rmbg_net=rmbg_net)
 
251
  return image
252
 
253
  @spaces.GPU(duration=90)
 
373
 
374
  return textured_glb_path
375
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
376
 
377
+ with gr.Blocks(title="Nestlé | Proof of Concept") as demo:
378
+ gr.Markdown(HEADER)
 
 
 
 
 
379
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
380
  with gr.Row():
381
+ with gr.Column():
382
+ with gr.Row():
383
+ image_prompts = gr.Image(label="Input Image", type="filepath")
384
+ seg_image = gr.Image(
385
+ label="Segmentation Result", type="pil", format="png", interactive=False
 
 
 
 
 
 
 
 
 
 
386
  )
 
 
 
 
 
 
 
 
 
 
 
387
 
388
+ with gr.Accordion("Generation Settings", open=True):
389
+ text_prompt = gr.Textbox(label="Prompt", placeholder="Enter your prompt", value="high quality")
390
+ seed = gr.Slider(
391
+ label="Seed",
392
+ minimum=0,
393
+ maximum=MAX_SEED,
394
+ step=0,
395
+ value=0
396
+ )
397
+ randomize_seed = gr.Checkbox(label="Randomize seed", value=True)
 
 
 
398
  num_inference_steps = gr.Slider(
399
+ label="Number of inference steps",
400
  minimum=8,
401
  maximum=50,
402
  step=1,
403
  value=50,
 
404
  )
 
405
  guidance_scale = gr.Slider(
406
+ label="CFG scale",
407
  minimum=0.0,
408
  maximum=20.0,
409
  step=0.1,
410
  value=7.0,
 
411
  )
412
 
413
  with gr.Row():
414
+ reduce_face = gr.Checkbox(label="Simplify Mesh", value=True)
415
+ target_face_num = gr.Slider(maximum=1000000, minimum=10000, value=DEFAULT_FACE_NUMBER, label="Target Face Number")
 
 
 
 
 
 
 
 
 
 
416
 
417
+ gen_button = gr.Button("Generate Shape", variant="primary")
418
+ gen_texture_button = gr.Button("Apply Texture", interactive=False)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
419
 
420
+ with gr.Column():
421
+ model_output = gr.Model3D(label="Generated GLB", interactive=False)
422
+ textured_model_output = gr.Model3D(label="Textured GLB", interactive=False)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
423
 
 
424
  gen_button.click(
425
+ run_segmentation,
426
  inputs=[image_prompts],
427
+ outputs=[seg_image]
 
428
  ).then(
429
  get_random_seed,
430
  inputs=[randomize_seed, seed],
 
440
  target_face_num
441
  ],
442
  outputs=[model_output]
443
+ ).then(lambda: gr.Button(interactive=True), outputs=[gen_texture_button])
 
 
 
444
 
445
  gen_texture_button.click(
446
  run_texture,
447
  inputs=[image_prompts, model_output, seed, text_prompt],
448
  outputs=[textured_model_output]
449
  )
 
 
 
 
 
 
 
 
 
 
 
 
450
 
451
  demo.load(start_session)
452
  demo.unload(end_session)
453
 
454
+ demo.launch()