ruslanmv commited on
Commit
e3f244a
1 Parent(s): 31e0235

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +144 -56
app.py CHANGED
@@ -2,15 +2,19 @@ import gradio as gr
2
  import plotly.graph_objs as go
3
  import trimesh
4
  import numpy as np
5
- from PIL import Image
6
  import torch
7
- from diffusers import StableDiffusionPipeline
8
  import os
9
  import matplotlib.pyplot as plt
10
 
11
- # Load the Stable Diffusion model for text-to-image generation
12
  device = "cuda" if torch.cuda.is_available() else "cpu"
13
  pipeline = StableDiffusionPipeline.from_pretrained("CompVis/stable-diffusion-v1-4").to(device)
 
 
 
 
14
 
15
  # Get the current directory
16
  current_dir = os.getcwd()
@@ -20,23 +24,101 @@ DEFAULT_OBJ_FILE = os.path.join(current_dir, "female.obj")
20
  TEMP_TEXTURE_FILE = os.path.join(current_dir, "generated_texture.png")
21
  # File path to save the 2D image
22
  OUTPUT_IMAGE_FILE = os.path.join(current_dir, "output_image.png")
23
- DEFAULT_GLB_FILE= os.path.join(current_dir, "vroid_girl1.glb")
24
 
25
- def apply_texture(mesh, texture_file):
 
 
 
 
26
  texture_image = Image.open(texture_file)
27
  uv_coords = mesh.visual.uv
28
- uv_coords = np.clip(uv_coords, 0, 1)
29
- texture_colors = np.array([
30
- texture_image.getpixel((
31
- int(u * (texture_image.width - 1)),
32
- int(v * (texture_image.height - 1))
33
- )) for u, v in uv_coords
34
- ])
35
- texture_colors = texture_colors / 255.0
36
- return texture_colors
37
 
38
- def display_3d_object(obj_file, texture_file, light_intensity, ambient_intensity, color):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
  file_extension = obj_file.split('.')[-1].lower()
 
 
40
  if file_extension == 'obj':
41
  mesh = trimesh.load(obj_file)
42
  elif file_extension == 'glb':
@@ -44,14 +126,13 @@ def display_3d_object(obj_file, texture_file, light_intensity, ambient_intensity
44
  else:
45
  raise ValueError("Unsupported file format. Please upload a .obj or .glb file.")
46
 
 
47
  if texture_file:
48
- colors = apply_texture(mesh, texture_file)
49
  else:
50
- colors = color
51
-
52
- ambient_intensity = max(0, min(ambient_intensity, 1))
53
- light_intensity = max(0, min(light_intensity, 1)) # Ensure light_intensity is within the valid range
54
 
 
55
  fig = go.Figure(data=[
56
  go.Mesh3d(
57
  x=mesh.vertices[:, 0],
@@ -60,15 +141,15 @@ def display_3d_object(obj_file, texture_file, light_intensity, ambient_intensity
60
  i=mesh.faces[:, 0],
61
  j=mesh.faces[:, 1],
62
  k=mesh.faces[:, 2],
63
- facecolor=colors if texture_file else None,
64
  color=color if not texture_file else None,
65
- opacity=0.50,
66
  lighting=dict(
67
  ambient=ambient_intensity,
68
- diffuse=light_intensity, # Corrected to ensure it's within [0, 1]
69
- specular=0.5,
70
- roughness=0.1,
71
- fresnel=0.2
72
  ),
73
  lightposition=dict(
74
  x=100,
@@ -79,66 +160,73 @@ def display_3d_object(obj_file, texture_file, light_intensity, ambient_intensity
79
  ])
80
  fig.update_layout(scene=dict(aspectmode='data'))
81
 
82
- # Cleaning Temp file
83
- if os.path.exists(TEMP_TEXTURE_FILE):
84
- os.remove(TEMP_TEXTURE_FILE)
85
- print(f"Deleted existing file: {TEMP_TEXTURE_FILE}")
86
- else:
87
- print(f"File not found: {TEMP_TEXTURE_FILE}")
88
-
89
  return fig
90
 
91
- def load_glb_file(filename):
92
- trimesh_scene = trimesh.load(filename)
93
- if isinstance(trimesh_scene, trimesh.Scene):
94
- mesh = trimesh_scene.dump(concatenate=True)
95
- else:
96
- mesh = trimesh_scene
97
- return mesh
98
-
99
- def generate_clothing_image(prompt):
100
- image = pipeline(prompt).images[0]
101
- image.save(TEMP_TEXTURE_FILE)
102
- return TEMP_TEXTURE_FILE, image
103
 
104
- def update_texture_display(prompt, texture_file):
 
 
 
 
 
 
 
 
 
 
105
  if prompt:
106
- texture_path, image = generate_clothing_image(prompt)
 
107
  return image
108
  elif texture_file:
 
109
  return Image.open(texture_file)
110
  return None
111
 
112
  with gr.Blocks() as demo:
113
- gr.Markdown("## 3D Object Viewer with Custom Texture, Color, and Adjustable Lighting")
114
 
115
  with gr.Row():
116
  with gr.Column(scale=1):
117
  gr.Markdown("### Texture Options")
118
  prompt_input = gr.Textbox(label="Enter a Prompt to Generate Texture", placeholder="Type a prompt...")
 
119
  generate_button = gr.Button("Generate Texture")
120
  texture_file = gr.File(label="Upload Texture file (PNG or JPG, optional)", type="filepath")
121
  texture_preview = gr.Image(label="Texture Preview", visible=True)
122
 
123
- gr.Markdown("### Lighting & Color Settings")
124
- light_intensity_slider = gr.Slider(minimum=0, maximum=2, step=0.1, value=0.8, label="Light Intensity")
 
125
  ambient_intensity_slider = gr.Slider(minimum=0, maximum=1, step=0.1, value=0.5, label="Ambient Intensity")
 
126
  color_picker = gr.ColorPicker(value="#D3D3D3", label="Object Color")
127
  submit_button = gr.Button("Submit")
 
 
128
  obj_file = gr.File(label="Upload OBJ or GLB file", value=DEFAULT_OBJ_FILE, type='filepath')
129
 
130
  with gr.Column(scale=2):
131
  display = gr.Plot(label="3D Viewer")
132
 
133
- def update_display(file, texture, light_intensity, ambient_intensity, color):
134
  texture_to_use = TEMP_TEXTURE_FILE if os.path.exists(TEMP_TEXTURE_FILE) else texture
135
- return display_3d_object(file, texture_to_use, light_intensity, ambient_intensity, color)
136
 
137
- submit_button.click(fn=update_display, inputs=[obj_file, texture_file, light_intensity_slider, ambient_intensity_slider, color_picker], outputs=display)
138
- generate_button.click(fn=update_texture_display, inputs=[prompt_input, texture_file], outputs=texture_preview)
139
- texture_file.change(fn=update_texture_display, inputs=[prompt_input, texture_file], outputs=texture_preview)
 
 
140
 
141
- demo.load(fn=update_display, inputs=[obj_file, texture_file, light_intensity_slider, ambient_intensity_slider, color_picker], outputs=display)
142
 
143
  gr.Examples(
144
  examples=[[DEFAULT_OBJ_FILE, None],[DEFAULT_GLB_FILE, None]],
 
2
  import plotly.graph_objs as go
3
  import trimesh
4
  import numpy as np
5
+ from PIL import Image, ImageDraw
6
  import torch
7
+ from diffusers import StableDiffusionPipeline, StableDiffusionInpaintPipeline
8
  import os
9
  import matplotlib.pyplot as plt
10
 
11
+ # Load the Stable Diffusion model for text-to-image generation and inpainting
12
  device = "cuda" if torch.cuda.is_available() else "cpu"
13
  pipeline = StableDiffusionPipeline.from_pretrained("CompVis/stable-diffusion-v1-4").to(device)
14
+ pipeline_inpaint = StableDiffusionInpaintPipeline.from_pretrained(
15
+ "runwayml/stable-diffusion-inpainting",
16
+ torch_dtype=torch.float16
17
+ ).to(device)
18
 
19
  # Get the current directory
20
  current_dir = os.getcwd()
 
24
  TEMP_TEXTURE_FILE = os.path.join(current_dir, "generated_texture.png")
25
  # File path to save the 2D image
26
  OUTPUT_IMAGE_FILE = os.path.join(current_dir, "output_image.png")
27
+ DEFAULT_GLB_FILE = os.path.join(current_dir, "vroid_girl1.glb")
28
 
29
+
30
+ def apply_texture(mesh, texture_file, uv_scale):
31
+ """
32
+ Applies the texture to the mesh with UV scaling to make triangles/rectangles smaller or larger.
33
+ """
34
  texture_image = Image.open(texture_file)
35
  uv_coords = mesh.visual.uv
 
 
 
 
 
 
 
 
 
36
 
37
+ # Apply scaling to UV coordinates to make the mapping finer or coarser
38
+ uv_coords = np.clip(uv_coords * uv_scale, 0, 1)
39
+
40
+ # Get the size of the texture image
41
+ img_width, img_height = texture_image.size
42
+ texture_array = np.array(texture_image)
43
+
44
+ # Prepare to store the colors per face
45
+ face_colors = []
46
+
47
+ for face in mesh.faces:
48
+ uv_face = uv_coords[face]
49
+ pixel_coords = np.round(uv_face * np.array([img_width - 1, img_height - 1])).astype(int)
50
+
51
+ # Ensure the UV coordinates are within the bounds of the texture image
52
+ valid_coords = np.all((pixel_coords[:, 0] >= 0) & (pixel_coords[:, 0] < img_width) &
53
+ (pixel_coords[:, 1] >= 0) & (pixel_coords[:, 1] < img_height))
54
+
55
+ if valid_coords:
56
+ # Get the average color for the face from the corresponding UV points in the texture
57
+ face_color = np.mean(texture_array[pixel_coords[:, 1], pixel_coords[:, 0]], axis=0)
58
+ face_colors.append(face_color / 255.0) # Normalize to [0, 1]
59
+ else:
60
+ # Assign a default color (e.g., gray) if UV coordinates are not valid
61
+ face_colors.append([0.5, 0.5, 0.5]) # Default to gray if no texture is applied
62
+
63
+ # Ensure no face is left unpainted
64
+ face_colors = np.array(face_colors)
65
+ if len(face_colors) < len(mesh.faces):
66
+ face_colors = np.pad(face_colors, ((0, len(mesh.faces) - len(face_colors)), (0, 0)), 'constant', constant_values=0.5)
67
+
68
+ return face_colors
69
+
70
+
71
+ def load_glb_file(filename):
72
+ trimesh_scene = trimesh.load(filename)
73
+ if isinstance(trimesh_scene, trimesh.Scene):
74
+ mesh = trimesh_scene.dump(concatenate=True)
75
+ else:
76
+ mesh = trimesh_scene
77
+ return mesh
78
+
79
+ def generate_clothing_image(prompt, num_inference_steps):
80
+ """
81
+ Generates the clothing texture based on the provided prompt and number of inference steps.
82
+ """
83
+ image = pipeline(prompt, num_inference_steps=num_inference_steps).images[0]
84
+ image.save(TEMP_TEXTURE_FILE)
85
+ return TEMP_TEXTURE_FILE, image
86
+
87
+ def generate_uv_specific_texture(prompt, uv_map_file, num_inference_steps=50):
88
+ """
89
+ Generates a texture for the 3D model using a given prompt and UV map.
90
+
91
+ Args:
92
+ prompt (str): The prompt for the diffusion model to generate the texture.
93
+ uv_map_file (str): Path to the UV map file.
94
+ num_inference_steps (int): The number of iterations/steps for the diffusion process.
95
+
96
+ Returns:
97
+ (str, PIL.Image): The path to the generated texture file and the generated texture image.
98
+ """
99
+ # Load UV map as a mask
100
+ uv_map = Image.open(uv_map_file)
101
+
102
+ # Generate texture based on UV map and the provided prompt
103
+ image = pipeline_inpaint(
104
+ prompt=prompt,
105
+ image=uv_map,
106
+ mask_image=uv_map,
107
+ num_inference_steps=num_inference_steps # Set custom number of inference steps
108
+ ).images[0]
109
+
110
+ # Save the generated texture
111
+ image.save(TEMP_TEXTURE_FILE)
112
+
113
+ return TEMP_TEXTURE_FILE, image
114
+
115
+ def display_3d_object(obj_file, texture_file, light_intensity, ambient_intensity, color, uv_scale, transparency):
116
+ """
117
+ Displays the 3D object with applied texture or color, with support for UV scaling and transparency.
118
+ """
119
  file_extension = obj_file.split('.')[-1].lower()
120
+
121
+ # Load mesh
122
  if file_extension == 'obj':
123
  mesh = trimesh.load(obj_file)
124
  elif file_extension == 'glb':
 
126
  else:
127
  raise ValueError("Unsupported file format. Please upload a .obj or .glb file.")
128
 
129
+ # Apply texture or color
130
  if texture_file:
131
+ face_colors = apply_texture(mesh, texture_file, uv_scale)
132
  else:
133
+ face_colors = np.array([color] * len(mesh.faces)) # Use a single color for all faces if no texture
 
 
 
134
 
135
+ # Define lighting settings
136
  fig = go.Figure(data=[
137
  go.Mesh3d(
138
  x=mesh.vertices[:, 0],
 
141
  i=mesh.faces[:, 0],
142
  j=mesh.faces[:, 1],
143
  k=mesh.faces[:, 2],
144
+ facecolor=face_colors if texture_file else None,
145
  color=color if not texture_file else None,
146
+ opacity=transparency, # Adjustable transparency
147
  lighting=dict(
148
  ambient=ambient_intensity,
149
+ diffuse=light_intensity,
150
+ specular=0.8, # Fine-tuned specular to avoid excessive shininess
151
+ roughness=0.3,
152
+ fresnel=0.1
153
  ),
154
  lightposition=dict(
155
  x=100,
 
160
  ])
161
  fig.update_layout(scene=dict(aspectmode='data'))
162
 
 
 
 
 
 
 
 
163
  return fig
164
 
165
+ def clear_texture():
166
+ """
167
+ Clears the texture preview and removes the texture file, allowing for a reset.
168
+ """
169
+ if os.path.exists(TEMP_TEXTURE_FILE):
170
+ os.remove(TEMP_TEXTURE_FILE)
171
+ return None
 
 
 
 
 
172
 
173
+ def restore_original(obj_file):
174
+ """
175
+ Restores the original 3D object without any applied texture.
176
+ """
177
+ return display_3d_object(obj_file, None, 0.8, 0.5, "#D3D3D3", 1.0, 1.0) # Default settings for restoration
178
+
179
+ def update_texture_display(prompt, texture_file, num_inference_steps):
180
+ """
181
+ Update the texture display either by generating a texture from the prompt
182
+ or by displaying an uploaded texture.
183
+ """
184
  if prompt:
185
+ # Generate new texture based on the prompt using the Stable Diffusion pipeline
186
+ texture_path, image = generate_clothing_image(prompt, num_inference_steps)
187
  return image
188
  elif texture_file:
189
+ # Display the uploaded texture file
190
  return Image.open(texture_file)
191
  return None
192
 
193
  with gr.Blocks() as demo:
194
+ gr.Markdown("## 3D Object Viewer with Custom Texture, UV Scale, Transparency, Color, and Adjustable Lighting")
195
 
196
  with gr.Row():
197
  with gr.Column(scale=1):
198
  gr.Markdown("### Texture Options")
199
  prompt_input = gr.Textbox(label="Enter a Prompt to Generate Texture", placeholder="Type a prompt...")
200
+ num_inference_steps_slider = gr.Slider(minimum=5, maximum=100, step=1, value=10, label="Num Inference Steps")
201
  generate_button = gr.Button("Generate Texture")
202
  texture_file = gr.File(label="Upload Texture file (PNG or JPG, optional)", type="filepath")
203
  texture_preview = gr.Image(label="Texture Preview", visible=True)
204
 
205
+ gr.Markdown("### Mapping, Lighting & Color Settings")
206
+ uv_scale_slider = gr.Slider(minimum=0.1, maximum=5, step=0.1, value=1.0, label="UV Mapping Scale (Make smaller/bigger)")
207
+ light_intensity_slider = gr.Slider(minimum=0, maximum=1, step=0.1, value=0.8, label="Light Intensity")
208
  ambient_intensity_slider = gr.Slider(minimum=0, maximum=1, step=0.1, value=0.5, label="Ambient Intensity")
209
+ transparency_slider = gr.Slider(minimum=0.1, maximum=1.0, step=0.1, value=1.0, label="Transparency (1.0 is fully opaque)")
210
  color_picker = gr.ColorPicker(value="#D3D3D3", label="Object Color")
211
  submit_button = gr.Button("Submit")
212
+ restore_button = gr.Button("Restore")
213
+ clear_button = gr.Button("Clear")
214
  obj_file = gr.File(label="Upload OBJ or GLB file", value=DEFAULT_OBJ_FILE, type='filepath')
215
 
216
  with gr.Column(scale=2):
217
  display = gr.Plot(label="3D Viewer")
218
 
219
+ def update_display(file, texture, uv_scale, light_intensity, ambient_intensity, transparency, color, num_inference_steps):
220
  texture_to_use = TEMP_TEXTURE_FILE if os.path.exists(TEMP_TEXTURE_FILE) else texture
221
+ return display_3d_object(file, texture_to_use, light_intensity, ambient_intensity, color, uv_scale, transparency)
222
 
223
+ submit_button.click(fn=update_display, inputs=[obj_file, texture_file, uv_scale_slider, light_intensity_slider, ambient_intensity_slider, transparency_slider, color_picker, num_inference_steps_slider], outputs=display)
224
+ generate_button.click(fn=update_texture_display, inputs=[prompt_input, texture_file, num_inference_steps_slider], outputs=texture_preview)
225
+ restore_button.click(fn=restore_original, inputs=[obj_file], outputs=display)
226
+ clear_button.click(fn=clear_texture, outputs=texture_preview)
227
+ texture_file.change(fn=update_texture_display, inputs=[prompt_input, texture_file, num_inference_steps_slider], outputs=texture_preview)
228
 
229
+ demo.load(fn=update_display, inputs=[obj_file, texture_file, uv_scale_slider, light_intensity_slider, ambient_intensity_slider, transparency_slider, color_picker, num_inference_steps_slider], outputs=display)
230
 
231
  gr.Examples(
232
  examples=[[DEFAULT_OBJ_FILE, None],[DEFAULT_GLB_FILE, None]],