Files changed (1) hide show
  1. app.py +136 -38
app.py CHANGED
@@ -2,11 +2,22 @@ import os
2
  import shlex
3
  import spaces
4
  import subprocess
 
 
 
 
 
 
5
  def install_cuda_toolkit():
 
6
  CUDA_TOOLKIT_URL = "https://developer.download.nvidia.com/compute/cuda/12.4.0/local_installers/cuda_12.4.0_550.54.14_linux.run"
7
  CUDA_TOOLKIT_FILE = "/tmp/%s" % os.path.basename(CUDA_TOOLKIT_URL)
 
 
8
  subprocess.call(["wget", "-q", CUDA_TOOLKIT_URL, "-O", CUDA_TOOLKIT_FILE])
9
  subprocess.call(["chmod", "+x", CUDA_TOOLKIT_FILE])
 
 
10
  subprocess.call([CUDA_TOOLKIT_FILE, "--silent", "--toolkit"])
11
 
12
  os.environ["CUDA_HOME"] = "/usr/local/cuda"
@@ -16,14 +27,21 @@ def install_cuda_toolkit():
16
  "" if "LD_LIBRARY_PATH" not in os.environ else os.environ["LD_LIBRARY_PATH"],
17
  )
18
  os.environ["TORCH_CUDA_ARCH_LIST"] = "8.0;8.6"
 
 
19
  install_cuda_toolkit()
 
 
20
  os.system("pip list | grep torch")
21
  os.system('nvcc -V')
22
- print("cd /home/user/app/step1x3d_texture/differentiable_renderer/ && python setup.py install")
 
23
  os.system("cd /home/user/app/step1x3d_texture/differentiable_renderer/ && python setup.py install")
24
 
 
25
  subprocess.run(shlex.split("pip install custom_rasterizer-0.1-cp310-cp310-linux_x86_64.whl"), check=True)
26
- import time
 
27
  import uuid
28
  import torch
29
  import trimesh
@@ -36,7 +54,10 @@ from step1x3d_texture.pipelines.step1x_3d_texture_synthesis_pipeline import (
36
  )
37
  from step1x3d_geometry.models.pipelines.pipeline_utils import reduce_face, remove_degenerate_face
38
 
39
-
 
 
 
40
  parser = argparse.ArgumentParser()
41
  parser.add_argument(
42
  "--geometry_model", type=str, default="Step1X-3D-Geometry-Label-1300m"
@@ -48,19 +69,37 @@ parser.add_argument("--cache_dir", type=str, default="cache")
48
  args = parser.parse_args()
49
 
50
  os.makedirs(args.cache_dir, exist_ok=True)
 
51
 
 
52
  geometry_model = Step1X3DGeometryPipeline.from_pretrained(
53
  "stepfun-ai/Step1X-3D", subfolder=args.geometry_model
54
- ).to("cuda")
 
55
 
 
56
  texture_model = Step1X3DTexturePipeline.from_pretrained("stepfun-ai/Step1X-3D", subfolder=args.texture_model)
 
 
57
 
 
 
 
 
58
 
59
- @spaces.GPU(duration=240)
60
- def generate_func(
61
- input_image_path, guidance_scale, inference_steps, max_facenum, symmetry, edge_type
62
  ):
63
- # geometry_model = geometry_model.to("cuda")
 
 
 
 
 
 
 
 
64
  if "Label" in args.geometry_model:
65
  symmetry_values = ["x", "asymmetry"]
66
  out = geometry_model(
@@ -79,49 +118,90 @@ def generate_func(
79
  max_facenum=int(max_facenum),
80
  )
81
 
 
82
  save_name = str(uuid.uuid4())
83
- print(save_name)
84
  geometry_save_path = f"{args.cache_dir}/{save_name}.glb"
85
  geometry_mesh = out.mesh[0]
86
  geometry_mesh.export(geometry_save_path)
87
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
88
  geometry_mesh = remove_degenerate_face(geometry_mesh)
89
  geometry_mesh = reduce_face(geometry_mesh)
 
 
90
  textured_mesh = texture_model(input_image_path, geometry_mesh)
 
 
 
91
  textured_save_path = f"{args.cache_dir}/{save_name}-textured.glb"
92
  textured_mesh.export(textured_save_path)
93
-
94
  torch.cuda.empty_cache()
95
- print("Generate finish")
96
- return geometry_save_path, textured_save_path
 
 
97
 
 
 
 
 
98
 
99
  with gr.Blocks(title="Step1X-3D demo") as demo:
100
  gr.Markdown("# Step1X-3D")
 
 
 
 
 
101
  with gr.Row():
102
  with gr.Column(scale=2):
103
- input_image = gr.Image(label="Image", type="filepath")
104
- guidance_scale = gr.Number(label="Guidance Scale", value="7.5")
105
- inference_steps = gr.Slider(
106
- label="Inferece Steps", minimum=1, maximum=100, value=50
107
- )
108
- max_facenum = gr.Number(label="Max Face Num", value="400000")
109
- symmetry = gr.Radio(
110
- choices=["symmetry", "asymmetry"],
111
- label="Symmetry Type",
112
- value="symmetry",
113
- type="index",
114
- )
115
- edge_type = gr.Radio(
116
- choices=["sharp", "normal", "smooth"],
117
- label="Edge Type",
118
- value="sharp",
119
- type="value",
120
- )
121
- btn = gr.Button("Start")
 
 
 
 
 
 
122
  with gr.Column(scale=4):
123
- textured_preview = gr.Model3D(label="Textured", height=380)
124
- geometry_preview = gr.Model3D(label="Geometry", height=380)
 
125
  with gr.Column(scale=1):
126
  gr.Examples(
127
  examples=[
@@ -138,8 +218,11 @@ with gr.Blocks(title="Step1X-3D demo") as demo:
138
  cache_examples=False,
139
  )
140
 
141
- btn.click(
142
- generate_func,
 
 
 
143
  inputs=[
144
  input_image,
145
  guidance_scale,
@@ -148,7 +231,22 @@ with gr.Blocks(title="Step1X-3D demo") as demo:
148
  symmetry,
149
  edge_type,
150
  ],
151
- outputs=[geometry_preview, textured_preview],
 
 
 
 
 
 
 
 
 
 
 
 
 
 
152
  )
153
 
154
- demo.launch(ssr_mode=False)
 
 
2
  import shlex
3
  import spaces
4
  import subprocess
5
+
6
+ # --------------------------------------------------------------------------
7
+ # 1. ENVIRONMENT AND DEPENDENCY INSTALLATION
8
+ # This section is crucial for the Hugging Face Space to work.
9
+ # Installs the CUDA toolkit and compiles the necessary C++/CUDA extensions.
10
+ # --------------------------------------------------------------------------
11
  def install_cuda_toolkit():
12
+ """Installs the CUDA toolkit required to compile extensions."""
13
  CUDA_TOOLKIT_URL = "https://developer.download.nvidia.com/compute/cuda/12.4.0/local_installers/cuda_12.4.0_550.54.14_linux.run"
14
  CUDA_TOOLKIT_FILE = "/tmp/%s" % os.path.basename(CUDA_TOOLKIT_URL)
15
+
16
+ print("Downloading CUDA Toolkit...")
17
  subprocess.call(["wget", "-q", CUDA_TOOLKIT_URL, "-O", CUDA_TOOLKIT_FILE])
18
  subprocess.call(["chmod", "+x", CUDA_TOOLKIT_FILE])
19
+
20
+ print("Installing CUDA Toolkit...")
21
  subprocess.call([CUDA_TOOLKIT_FILE, "--silent", "--toolkit"])
22
 
23
  os.environ["CUDA_HOME"] = "/usr/local/cuda"
 
27
  "" if "LD_LIBRARY_PATH" not in os.environ else os.environ["LD_LIBRARY_PATH"],
28
  )
29
  os.environ["TORCH_CUDA_ARCH_LIST"] = "8.0;8.6"
30
+ print("CUDA environment configuration completed.")
31
+
32
  install_cuda_toolkit()
33
+
34
+ print("Verifying PyTorch installation and NVCC version:")
35
  os.system("pip list | grep torch")
36
  os.system('nvcc -V')
37
+
38
+ print("Compiling differentiable renderer extension...")
39
  os.system("cd /home/user/app/step1x3d_texture/differentiable_renderer/ && python setup.py install")
40
 
41
+ print("Installing custom rasterizer...")
42
  subprocess.run(shlex.split("pip install custom_rasterizer-0.1-cp310-cp310-linux_x86_64.whl"), check=True)
43
+ print("Installation and compilation completed.")
44
+
45
  import uuid
46
  import torch
47
  import trimesh
 
54
  )
55
  from step1x3d_geometry.models.pipelines.pipeline_utils import reduce_face, remove_degenerate_face
56
 
57
+ # --------------------------------------------------------------------------
58
+ # 2. MODEL CONFIGURATION AND LOADING
59
+ # Here we define the models to be used and load them into memory.
60
+ # --------------------------------------------------------------------------
61
  parser = argparse.ArgumentParser()
62
  parser.add_argument(
63
  "--geometry_model", type=str, default="Step1X-3D-Geometry-Label-1300m"
 
69
  args = parser.parse_args()
70
 
71
  os.makedirs(args.cache_dir, exist_ok=True)
72
+ device = "cuda" if torch.cuda.is_available() else "cpu"
73
 
74
+ print(f"Loading geometry model: {args.geometry_model}...")
75
  geometry_model = Step1X3DGeometryPipeline.from_pretrained(
76
  "stepfun-ai/Step1X-3D", subfolder=args.geometry_model
77
+ ).to(device)
78
+ print("Geometry model loaded.")
79
 
80
+ print(f"Loading texture model: {args.texture_model}...")
81
  texture_model = Step1X3DTexturePipeline.from_pretrained("stepfun-ai/Step1X-3D", subfolder=args.texture_model)
82
+ print("Texture model loaded.")
83
+
84
 
85
+ # --------------------------------------------------------------------------
86
+ # 3. SEPARATE GENERATION FUNCTIONS
87
+ # The logic is split into two functions: one for geometry and one for textures.
88
+ # --------------------------------------------------------------------------
89
 
90
+ @spaces.GPU(duration=180)
91
+ def generate_geometry(
92
+ input_image_path, guidance_scale, inference_steps, max_facenum, symmetry, edge_type, progress=gr.Progress(track_tqdm=True)
93
  ):
94
+ """
95
+ Function that generates geometry only from the input image.
96
+ """
97
+ if input_image_path is None:
98
+ raise gr.Error("Please upload an image to start.")
99
+
100
+ print("Starting geometry generation...")
101
+
102
+ # Choose the appropriate pipeline based on the model name
103
  if "Label" in args.geometry_model:
104
  symmetry_values = ["x", "asymmetry"]
105
  out = geometry_model(
 
118
  max_facenum=int(max_facenum),
119
  )
120
 
121
+ # Save the result to a temporary file
122
  save_name = str(uuid.uuid4())
 
123
  geometry_save_path = f"{args.cache_dir}/{save_name}.glb"
124
  geometry_mesh = out.mesh[0]
125
  geometry_mesh.export(geometry_save_path)
126
+
127
+ torch.cuda.empty_cache()
128
+ print(f"Geometry saved at: {geometry_save_path}")
129
+
130
+ # Return the path for display in the viewer and to store in state
131
+ return geometry_save_path, geometry_save_path
132
+
133
+ @spaces.GPU(duration=120)
134
+ def generate_texture(input_image_path, geometry_path, progress=gr.Progress(track_tqdm=True)):
135
+ """
136
+ Function that applies texture to an already generated geometry.
137
+ """
138
+ if not geometry_path or not os.path.exists(geometry_path):
139
+ raise gr.Error("Please generate the geometry first before texturing.")
140
+
141
+ print(f"Starting texturing for mesh: {geometry_path}")
142
+ geometry_mesh = trimesh.load(geometry_path)
143
+
144
+ # Optional post-processing of the mesh before texturing
145
  geometry_mesh = remove_degenerate_face(geometry_mesh)
146
  geometry_mesh = reduce_face(geometry_mesh)
147
+
148
+ # Call the texturing pipeline
149
  textured_mesh = texture_model(input_image_path, geometry_mesh)
150
+
151
+ # Save the final result
152
+ save_name = os.path.basename(geometry_path).replace(".glb", "")
153
  textured_save_path = f"{args.cache_dir}/{save_name}-textured.glb"
154
  textured_mesh.export(textured_save_path)
155
+
156
  torch.cuda.empty_cache()
157
+ print(f"Textured mesh saved at: {textured_save_path}")
158
+
159
+ return textured_save_path
160
+
161
 
162
+ # --------------------------------------------------------------------------
163
+ # 4. GRADIO USER INTERFACE
164
+ # Defines the look and behavior of the web app.
165
+ # --------------------------------------------------------------------------
166
 
167
  with gr.Blocks(title="Step1X-3D demo") as demo:
168
  gr.Markdown("# Step1X-3D")
169
+ gr.Markdown("### Demo for generating 3D models from a single image")
170
+
171
+ # State component: stores the geometry path between steps
172
+ geometry_path_state = gr.State()
173
+
174
  with gr.Row():
175
  with gr.Column(scale=2):
176
+ input_image = gr.Image(label="Input Image", type="filepath")
177
+
178
+ with gr.Accordion(label="Generation Parameters", open=True):
179
+ guidance_scale = gr.Number(label="Guidance Scale", value="7.5")
180
+ inference_steps = gr.Slider(
181
+ label="Inference Steps", minimum=1, maximum=100, value=50, step=1
182
+ )
183
+ max_facenum = gr.Number(label="Max. Number of Faces", value="400000")
184
+ symmetry = gr.Radio(
185
+ choices=["symmetry", "asymmetry"],
186
+ label="Symmetry Type",
187
+ value="symmetry",
188
+ type="index",
189
+ )
190
+ edge_type = gr.Radio(
191
+ choices=["sharp", "normal", "smooth"],
192
+ label="Edge Type",
193
+ value="sharp",
194
+ type="value",
195
+ )
196
+
197
+ with gr.Row():
198
+ btn_geo = gr.Button("1. Generate Geometry", variant="primary")
199
+ btn_tex = gr.Button("2. Generate Texture", visible=False, variant="primary")
200
+
201
  with gr.Column(scale=4):
202
+ textured_preview = gr.Model3D(label="Textured Model", height=380, clear_color=[0.0, 0.0, 0.0, 0.0])
203
+ geometry_preview = gr.Model3D(label="Model (geometry only)", height=380, clear_color=[0.0, 0.0, 0.0, 0.0])
204
+
205
  with gr.Column(scale=1):
206
  gr.Examples(
207
  examples=[
 
218
  cache_examples=False,
219
  )
220
 
221
+ # --- Button logic and interface flow ---
222
+
223
+ # 1. When the user clicks "Generate Geometry"
224
+ btn_geo.click(
225
+ fn=generate_geometry,
226
  inputs=[
227
  input_image,
228
  guidance_scale,
 
231
  symmetry,
232
  edge_type,
233
  ],
234
+ outputs=[geometry_preview, geometry_path_state]
235
+ ).then(
236
+ # 2. When geometry is done, run this part
237
+ fn=lambda: {
238
+ btn_tex: gr.update(visible=True), # Show texture button
239
+ textured_preview: gr.update(value=None) # Clear previous texture preview
240
+ },
241
+ outputs=[btn_tex, textured_preview]
242
+ )
243
+
244
+ # 3. When the user clicks "Generate Texture"
245
+ btn_tex.click(
246
+ fn=generate_texture,
247
+ inputs=[input_image, geometry_path_state],
248
+ outputs=[textured_preview],
249
  )
250
 
251
+ # Launch the app
252
+ demo.launch(ssr_mode=False)