Spaces:
Sleeping
Sleeping
Upload app.py
Browse files
app.py
CHANGED
|
@@ -200,46 +200,46 @@ def build_textured_cube(pil_imgs, face_rotations=None):
|
|
| 200 |
|
| 201 |
# --- géométrie : définir quads par face (CCW en regardant la face de l'extérieur)
|
| 202 |
# Convention: +X = right, +Y = front, +Z = up
|
| 203 |
-
#
|
| 204 |
# (-x,-y,-z), ( x,-y,-z), ( x, y,-z), (-x, y,-z),
|
| 205 |
# (-x,-y, z), ( x,-y, z), ( x, y, z), (-x, y, z)
|
| 206 |
quads = {
|
| 207 |
-
# top (+Z) :
|
| 208 |
"front": [
|
| 209 |
(-half_x, -half_y, half_z),
|
| 210 |
( half_x, -half_y, half_z),
|
| 211 |
( half_x, half_y, half_z),
|
| 212 |
(-half_x, half_y, half_z),
|
| 213 |
],
|
| 214 |
-
# right (+X) :
|
| 215 |
"right": [
|
| 216 |
( half_x, -half_y, -half_z),
|
| 217 |
( half_x, half_y, -half_z),
|
| 218 |
( half_x, half_y, half_z),
|
| 219 |
( half_x, -half_y, half_z),
|
| 220 |
],
|
| 221 |
-
# bottom (-Z) :
|
| 222 |
"back": [
|
| 223 |
(-half_x, half_y, -half_z),
|
| 224 |
( half_x, half_y, -half_z),
|
| 225 |
( half_x, -half_y, -half_z),
|
| 226 |
(-half_x, -half_y, -half_z),
|
| 227 |
],
|
| 228 |
-
# left (-X) :
|
| 229 |
"left": [
|
| 230 |
(-half_x, -half_y, half_z),
|
| 231 |
(-half_x, half_y, half_z),
|
| 232 |
(-half_x, half_y, -half_z),
|
| 233 |
(-half_x, -half_y, -half_z),
|
| 234 |
],
|
| 235 |
-
# front (+Y) :
|
| 236 |
"top": [
|
| 237 |
(-half_x, half_y, -half_z),
|
| 238 |
(-half_x, half_y, half_z),
|
| 239 |
( half_x, half_y, half_z),
|
| 240 |
( half_x, half_y, -half_z),
|
| 241 |
],
|
| 242 |
-
# back (-Y) :
|
| 243 |
"bottom": [
|
| 244 |
( half_x, -half_y, -half_z),
|
| 245 |
( half_x, -half_y, half_z),
|
|
@@ -248,28 +248,24 @@ def build_textured_cube(pil_imgs, face_rotations=None):
|
|
| 248 |
],
|
| 249 |
}
|
| 250 |
|
| 251 |
-
|
| 252 |
face_order = ["top", "right", "bottom", "left", "front", "back"]
|
| 253 |
-
# face_order = ["back", "left", "top", "front", "right", "bottom"]
|
| 254 |
obj_path = os.path.join(tmpdir, "parallelep.obj")
|
| 255 |
with open(obj_path, "w", encoding="utf-8") as f:
|
| 256 |
f.write("# Parallelepiped OBJ generated by build_textured_cube\n")
|
| 257 |
f.write("mtllib parallelep.mtl\n\n")
|
| 258 |
|
| 259 |
-
# écrire vertices : 4 par face, dans l'ordre face_order
|
| 260 |
for face_name in face_order:
|
| 261 |
for v in quads[face_name]:
|
| 262 |
f.write("v {:.6f} {:.6f} {:.6f}\n".format(*v))
|
| 263 |
f.write("\n")
|
| 264 |
|
| 265 |
-
# écrire UVs : 4 UVs par face (0..1). On garde origine UV bas-gauche.
|
| 266 |
uvs = [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)]
|
| 267 |
for _ in range(6):
|
| 268 |
for (u, v) in uvs:
|
| 269 |
f.write("vt {:.6f} {:.6f}\n".format(u, v))
|
| 270 |
f.write("\n")
|
| 271 |
|
| 272 |
-
# faces (2 triangles par face), en s'assurant que les matériaux correspondent
|
| 273 |
for i, face_name in enumerate(face_order):
|
| 274 |
f.write(f"usemtl m_{face_name}\n")
|
| 275 |
v_base = i * 4 + 1
|
|
@@ -284,7 +280,6 @@ def build_textured_cube(pil_imgs, face_rotations=None):
|
|
| 284 |
except Exception:
|
| 285 |
pass
|
| 286 |
|
| 287 |
-
# vérifications rapides
|
| 288 |
for fname in ["parallelep.obj", "parallelep.mtl"] + list(tex_names.values()):
|
| 289 |
p = os.path.join(tmpdir, fname)
|
| 290 |
if not os.path.exists(p):
|
|
@@ -295,15 +290,14 @@ def build_textured_cube(pil_imgs, face_rotations=None):
|
|
| 295 |
|
| 296 |
|
| 297 |
# -------------------------
|
| 298 |
-
#
|
| 299 |
-
# retourne : 4 miniatures (PIL) + chemin vers le .obj (str)
|
| 300 |
# -------------------------
|
| 301 |
def run(fibers: Image.Image, rings: Image.Image, num_steps: int):
|
| 302 |
try:
|
| 303 |
outputs = inference(fibers, rings, num_steps)
|
| 304 |
if not (isinstance(outputs, (list, tuple)) and len(outputs) >= 4):
|
| 305 |
raise ValueError("user_inference doit renvoyer une liste/tuple de 4 images.")
|
| 306 |
-
|
| 307 |
pil_imgs = []
|
| 308 |
for im in outputs[:4]:
|
| 309 |
if isinstance(im, np.ndarray):
|
|
@@ -313,19 +307,15 @@ def run(fibers: Image.Image, rings: Image.Image, num_steps: int):
|
|
| 313 |
print(im.size)
|
| 314 |
pil_imgs.append(im)
|
| 315 |
|
| 316 |
-
|
| 317 |
thumbs = [im.copy() for im in pil_imgs]
|
| 318 |
|
| 319 |
-
|
| 320 |
obj_path, tmpdir = build_textured_cube(pil_imgs)
|
| 321 |
|
| 322 |
-
# IMPORTANT: on renvoie le chemin absolu vers le fichier .obj
|
| 323 |
-
# Gradio/Spaces chargera le modèle 3D depuis ce fichier et les textures
|
| 324 |
-
# doivent être dans le même dossier que le .obj (ce qui est le cas).
|
| 325 |
return (*thumbs, obj_path)
|
| 326 |
except Exception as e:
|
| 327 |
traceback.print_exc()
|
| 328 |
-
# en cas d'erreur, renvoyer 4 images grises et None pour le modèle
|
| 329 |
blank = Image.new("RGB", (256,256), (220,220,220))
|
| 330 |
return (blank, blank, blank, blank, None)
|
| 331 |
|
|
@@ -333,6 +323,7 @@ def run(fibers: Image.Image, rings: Image.Image, num_steps: int):
|
|
| 333 |
# Interface Gradio
|
| 334 |
# -------------------------
|
| 335 |
with gr.Blocks(title="Photorealistic wood generator (4 faces)") as demo:
|
|
|
|
| 336 |
gr.Markdown("""Upload 2 images (four fiber maps and four ring maps) corresponding to the board faces. The model will return four generated images (one per face), produced in a single coherent pass.
|
| 337 |
Set the number of inference steps. Higher values can improve quality but increase processing time.""")
|
| 338 |
with gr.Row():
|
|
@@ -344,10 +335,10 @@ with gr.Blocks(title="Photorealistic wood generator (4 faces)") as demo:
|
|
| 344 |
with gr.Column(scale=2):
|
| 345 |
model3d_out = gr.Model3D(label="3D board")
|
| 346 |
with gr.Row():
|
| 347 |
-
out1 = gr.Image(
|
| 348 |
-
out2 = gr.Image(
|
| 349 |
-
out3 = gr.Image(
|
| 350 |
-
out4 = gr.Image(
|
| 351 |
|
| 352 |
run_btn.click(fn=run, inputs=[inp1, inp2, inp3], outputs=[out1, out2, out3, out4, model3d_out])
|
| 353 |
|
|
|
|
| 200 |
|
| 201 |
# --- géométrie : définir quads par face (CCW en regardant la face de l'extérieur)
|
| 202 |
# Convention: +X = right, +Y = front, +Z = up
|
| 203 |
+
# 8 corners:
|
| 204 |
# (-x,-y,-z), ( x,-y,-z), ( x, y,-z), (-x, y,-z),
|
| 205 |
# (-x,-y, z), ( x,-y, z), ( x, y, z), (-x, y, z)
|
| 206 |
quads = {
|
| 207 |
+
# top (+Z) : look from +Z
|
| 208 |
"front": [
|
| 209 |
(-half_x, -half_y, half_z),
|
| 210 |
( half_x, -half_y, half_z),
|
| 211 |
( half_x, half_y, half_z),
|
| 212 |
(-half_x, half_y, half_z),
|
| 213 |
],
|
| 214 |
+
# right (+X) : look from +X
|
| 215 |
"right": [
|
| 216 |
( half_x, -half_y, -half_z),
|
| 217 |
( half_x, half_y, -half_z),
|
| 218 |
( half_x, half_y, half_z),
|
| 219 |
( half_x, -half_y, half_z),
|
| 220 |
],
|
| 221 |
+
# bottom (-Z) : look from -Z
|
| 222 |
"back": [
|
| 223 |
(-half_x, half_y, -half_z),
|
| 224 |
( half_x, half_y, -half_z),
|
| 225 |
( half_x, -half_y, -half_z),
|
| 226 |
(-half_x, -half_y, -half_z),
|
| 227 |
],
|
| 228 |
+
# left (-X) : look from -X
|
| 229 |
"left": [
|
| 230 |
(-half_x, -half_y, half_z),
|
| 231 |
(-half_x, half_y, half_z),
|
| 232 |
(-half_x, half_y, -half_z),
|
| 233 |
(-half_x, -half_y, -half_z),
|
| 234 |
],
|
| 235 |
+
# front (+Y) : look from +Y
|
| 236 |
"top": [
|
| 237 |
(-half_x, half_y, -half_z),
|
| 238 |
(-half_x, half_y, half_z),
|
| 239 |
( half_x, half_y, half_z),
|
| 240 |
( half_x, half_y, -half_z),
|
| 241 |
],
|
| 242 |
+
# back (-Y) : look from -Y
|
| 243 |
"bottom": [
|
| 244 |
( half_x, -half_y, -half_z),
|
| 245 |
( half_x, -half_y, half_z),
|
|
|
|
| 248 |
],
|
| 249 |
}
|
| 250 |
|
| 251 |
+
|
| 252 |
face_order = ["top", "right", "bottom", "left", "front", "back"]
|
|
|
|
| 253 |
obj_path = os.path.join(tmpdir, "parallelep.obj")
|
| 254 |
with open(obj_path, "w", encoding="utf-8") as f:
|
| 255 |
f.write("# Parallelepiped OBJ generated by build_textured_cube\n")
|
| 256 |
f.write("mtllib parallelep.mtl\n\n")
|
| 257 |
|
|
|
|
| 258 |
for face_name in face_order:
|
| 259 |
for v in quads[face_name]:
|
| 260 |
f.write("v {:.6f} {:.6f} {:.6f}\n".format(*v))
|
| 261 |
f.write("\n")
|
| 262 |
|
|
|
|
| 263 |
uvs = [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)]
|
| 264 |
for _ in range(6):
|
| 265 |
for (u, v) in uvs:
|
| 266 |
f.write("vt {:.6f} {:.6f}\n".format(u, v))
|
| 267 |
f.write("\n")
|
| 268 |
|
|
|
|
| 269 |
for i, face_name in enumerate(face_order):
|
| 270 |
f.write(f"usemtl m_{face_name}\n")
|
| 271 |
v_base = i * 4 + 1
|
|
|
|
| 280 |
except Exception:
|
| 281 |
pass
|
| 282 |
|
|
|
|
| 283 |
for fname in ["parallelep.obj", "parallelep.mtl"] + list(tex_names.values()):
|
| 284 |
p = os.path.join(tmpdir, fname)
|
| 285 |
if not os.path.exists(p):
|
|
|
|
| 290 |
|
| 291 |
|
| 292 |
# -------------------------
|
| 293 |
+
# return : 4 img (PIL) + path to .obj (str)
|
|
|
|
| 294 |
# -------------------------
|
| 295 |
def run(fibers: Image.Image, rings: Image.Image, num_steps: int):
|
| 296 |
try:
|
| 297 |
outputs = inference(fibers, rings, num_steps)
|
| 298 |
if not (isinstance(outputs, (list, tuple)) and len(outputs) >= 4):
|
| 299 |
raise ValueError("user_inference doit renvoyer une liste/tuple de 4 images.")
|
| 300 |
+
|
| 301 |
pil_imgs = []
|
| 302 |
for im in outputs[:4]:
|
| 303 |
if isinstance(im, np.ndarray):
|
|
|
|
| 307 |
print(im.size)
|
| 308 |
pil_imgs.append(im)
|
| 309 |
|
| 310 |
+
|
| 311 |
thumbs = [im.copy() for im in pil_imgs]
|
| 312 |
|
| 313 |
+
|
| 314 |
obj_path, tmpdir = build_textured_cube(pil_imgs)
|
| 315 |
|
|
|
|
|
|
|
|
|
|
| 316 |
return (*thumbs, obj_path)
|
| 317 |
except Exception as e:
|
| 318 |
traceback.print_exc()
|
|
|
|
| 319 |
blank = Image.new("RGB", (256,256), (220,220,220))
|
| 320 |
return (blank, blank, blank, blank, None)
|
| 321 |
|
|
|
|
| 323 |
# Interface Gradio
|
| 324 |
# -------------------------
|
| 325 |
with gr.Blocks(title="Photorealistic wood generator (4 faces)") as demo:
|
| 326 |
+
gr.HTML("<h1 style='text-align:center; margin-bottom:8px;'>Photorealistic wood generator (4 faces)</h1>")
|
| 327 |
gr.Markdown("""Upload 2 images (four fiber maps and four ring maps) corresponding to the board faces. The model will return four generated images (one per face), produced in a single coherent pass.
|
| 328 |
Set the number of inference steps. Higher values can improve quality but increase processing time.""")
|
| 329 |
with gr.Row():
|
|
|
|
| 335 |
with gr.Column(scale=2):
|
| 336 |
model3d_out = gr.Model3D(label="3D board")
|
| 337 |
with gr.Row():
|
| 338 |
+
out1 = gr.Image(label="Front")
|
| 339 |
+
out2 = gr.Image(label="Right")
|
| 340 |
+
out3 = gr.Image(label="Back")
|
| 341 |
+
out4 = gr.Image(label="Left")
|
| 342 |
|
| 343 |
run_btn.click(fn=run, inputs=[inp1, inp2, inp3], outputs=[out1, out2, out3, out4, model3d_out])
|
| 344 |
|