Spaces:
Sleeping
Sleeping
Update app.py
Browse files
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 |
-
|
|
|
|
|
|
|
|
|
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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
49 |
else:
|
50 |
-
|
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=
|
64 |
color=color if not texture_file else None,
|
65 |
-
opacity=
|
66 |
lighting=dict(
|
67 |
ambient=ambient_intensity,
|
68 |
-
diffuse=light_intensity,
|
69 |
-
specular=0.
|
70 |
-
roughness=0.
|
71 |
-
fresnel=0.
|
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
|
92 |
-
|
93 |
-
|
94 |
-
|
95 |
-
|
96 |
-
|
97 |
-
return
|
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
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
105 |
if prompt:
|
106 |
-
|
|
|
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 |
-
|
|
|
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 |
-
|
|
|
|
|
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]],
|