CarolineM5 commited on
Commit
da6818c
·
verified ·
1 Parent(s): 973f3eb

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +196 -90
app.py CHANGED
@@ -298,103 +298,209 @@ class UNetNoCondWrapper(nn.Module):
298
  # return (os.path.abspath(obj_path), tmpdir)
299
 
300
 
301
- def build_textured_cube(pil_imgs):
302
  """
303
- pil_imgs: [front, right, back, left]
304
- Renvoie : path absolu vers cube.obj
 
 
 
 
 
 
 
305
  """
306
-
307
- # -------------------------
308
- # 1. Création du répertoire temporaire
309
- # -------------------------
310
- tmpdir = tempfile.mkdtemp()
311
-
312
- # Créer les images noires pour top et bottom
313
- size_square = pil_imgs[0].size[0] # front est carré
314
- black = Image.new("RGB", (size_square, size_square), (0, 0, 0))
315
- textures = {
316
- "front": pil_imgs[0],
317
- "right": pil_imgs[1],
318
- "back": pil_imgs[2],
319
- "left": pil_imgs[3],
320
- "top": black,
321
- "bottom": black
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
322
  }
323
 
324
- # -------------------------
325
- # 2. Rotation des textures si nécessaire
326
- # -------------------------
327
- # Toutes les faces +90° pour être dans le même sens
328
- rotations = {"front": 0, "right": 270, "back": 180, "left": 90, "top": 0, "bottom": 0}
329
- for face, img in textures.items():
330
- if rotations[face] != 0:
331
- textures[face] = img.rotate(rotations[face], expand=True)
332
- # sauvegarder l'image
333
- textures[face].save(os.path.join(tmpdir, f"{face}.png"))
334
-
335
- # -------------------------
336
- # 3. Dimensions du parallélépipède
337
- # -------------------------
338
- # front/back = carré
339
- w = pil_imgs[0].width
340
- h = pil_imgs[0].height
341
- # left/right = rectangles
342
- d = pil_imgs[1].width # largeur du rectangle (X) correspond à profondeur
343
- # hauteur Z = front.height
344
- half_x = w / 2
345
- half_y = d / 2
346
- half_z = h / 2
347
-
348
- # -------------------------
349
- # 4. Définition des sommets
350
- # -------------------------
351
- verts = [
352
- (-half_x, -half_y, -half_z),
353
- ( half_x, -half_y, -half_z),
354
- ( half_x, half_y, -half_z),
355
- (-half_x, half_y, -half_z),
356
- (-half_x, -half_y, half_z),
357
- ( half_x, -half_y, half_z),
358
- ( half_x, half_y, half_z),
359
- (-half_x, half_y, half_z),
360
- ]
361
-
362
- # -------------------------
363
- # 5. Faces et textures
364
- # -------------------------
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
365
  quads = {
366
- "front": [4,5,6,7],
367
- "back": [0,1,2,3],
368
- "left": [0,4,7,3],
369
- "right": [1,5,6,2],
370
- "top": [3,7,6,2],
371
- "bottom": [0,4,5,1]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
372
  }
373
 
374
- # -------------------------
375
- # 6. Création du fichier OBJ et MTL
376
- # -------------------------
377
- obj_path = os.path.join(tmpdir, "cube.obj")
378
- mtl_path = os.path.join(tmpdir, "cube.mtl")
379
-
380
- with open(mtl_path, "w") as f:
381
- for face in quads:
382
- f.write(f"newmtl {face}\n")
383
- f.write(f"Ka 1.0 1.0 1.0\nKd 1.0 1.0 1.0\nKs 0.0 0.0 0.0\n")
384
- f.write(f"map_Kd {face}.png\n\n")
385
-
386
- with open(obj_path, "w") as f:
387
- f.write(f"mtllib cube.mtl\n")
388
- for v in verts:
389
- f.write(f"v {v[0]} {v[1]} {v[2]}\n")
390
- # coordonnées UV simples (coin-bas-gauche à coin-haut-droit)
391
- f.write("vt 0 0\nvt 1 0\nvt 1 1\nvt 0 1\n")
392
- f.write("vn 0 0 1\n")
393
- for face, idxs in quads.items():
394
- f.write(f"usemtl {face}\n")
395
- f.write(f"f {idxs[0]+1}/1 {idxs[1]+1}/2 {idxs[2]+1}/3 {idxs[3]+1}/4\n")
396
-
397
- return obj_path, tmpdir
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
398
 
399
  # def build_textured_cube(pil_imgs, face_rotations=None):
400
  # """
 
298
  # return (os.path.abspath(obj_path), tmpdir)
299
 
300
 
301
+ def build_textured_cube(pil_imgs, face_rotations=None):
302
  """
303
+ Crée un parallélépipède texturé (OBJ + MTL + textures).
304
+ - pil_imgs: liste/tuple de 4 PIL.Image dans l'ordre [front, right, back, left]
305
+ - Retour: (chemin_absolu_obj, tmpdir)
306
+ Defaults:
307
+ default_rots = {"front": 0, "right": 270, "back": 180, "left": 90, "top": 0, "bottom": 0}
308
+ face_order = ["top","right","bottom","left","front","back"]
309
+ Notes:
310
+ - Ecrit les fichiers dans /tmp/gradio si possible (HF Spaces).
311
+ - front/back utilisent la taille de pil_imgs[0] ; left/right utilisent leur propre largeur (rectangles).
312
  """
313
+ import os
314
+ import tempfile
315
+ from PIL import Image
316
+
317
+ # validation
318
+ if not (isinstance(pil_imgs, (list, tuple)) and len(pil_imgs) >= 4):
319
+ raise ValueError("build_textured_cube attend une liste/tuple de 4 images PIL (front, right, back, left).")
320
+
321
+ # defaults rotation & ordre
322
+ default_rots = {"front": 0, "right": 270, "back": 180, "left": 90, "top": 0, "bottom": 0}
323
+ if face_rotations is None:
324
+ face_rotations = default_rots
325
+ else:
326
+ for k, v in default_rots.items():
327
+ face_rotations.setdefault(k, v)
328
+
329
+ # dossier temporaire (préférer /tmp/gradio sur HF)
330
+ base_dir = "/tmp/gradio"
331
+ if os.path.isdir(base_dir) and os.access(base_dir, os.W_OK):
332
+ tmpdir = tempfile.mkdtemp(prefix="parallelep_", dir=base_dir)
333
+ else:
334
+ tmpdir = tempfile.mkdtemp(prefix="parallelep_")
335
+
336
+ # noms relatifs pour textures (mtl utilisera ces noms)
337
+ tex_names = {
338
+ "front": "tex_front.png",
339
+ "right": "tex_right.png",
340
+ "back": "tex_back.png",
341
+ "left": "tex_left.png",
342
+ "top": "tex_top.png",
343
+ "bottom": "tex_bottom.png",
344
  }
345
 
346
+ # récupérer tailles (on suppose que inference a déjà redimensionné left/right si besoin)
347
+ front_w, front_h = pil_imgs[0].size
348
+ right_w, right_h = pil_imgs[1].size
349
+ back_w, back_h = pil_imgs[2].size
350
+ left_w, left_h = pil_imgs[3].size
351
+
352
+ # définir dimensions physiques du parallélépipède (en "px" puis on normalise)
353
+ width_px = float(front_w) # largeur X (front width)
354
+ height_px = float(front_h) # hauteur Z
355
+ # profondeur Y : on prend la largeur des faces latérales (moyenne left/right)
356
+ depth_px = float((right_w + left_w) / 2.0)
357
+
358
+ # normalisation pour garder des coordonnées de l'ordre de ±0.5
359
+ max_dim = max(width_px, depth_px, height_px, 1.0)
360
+ scale = 1.0 / max_dim
361
+ half_x = (width_px * 0.5) * scale # demi-largeur en X
362
+ half_y = (depth_px * 0.5) * scale # demi-profondeur en Y
363
+ half_z = (height_px * 0.5) * scale # demi-hauteur en Z
364
+
365
+ # mapping attendu pour pil_imgs
366
+ mapping_order = ["front", "right", "back", "left"]
367
+ # sauvegarder textures (avec rotation demandée) dans tmpdir
368
+ for img, face_name in zip(pil_imgs[:4], mapping_order):
369
+ im = img.convert("RGB")
370
+ angle = face_rotations.get(face_name, 0)
371
+ if angle % 360 != 0:
372
+ # PIL rotate: angle en degrés, positif = CCW
373
+ im = im.rotate(angle, resample=Image.BICUBIC, expand=False)
374
+ path = os.path.join(tmpdir, tex_names[face_name])
375
+ im.save(path, format="PNG")
376
+ try:
377
+ os.chmod(path, 0o644)
378
+ except Exception:
379
+ pass
380
+
381
+ # top/bottom textures noires (même taille que front pour cohérence)
382
+ black = Image.new("RGB", (front_w, front_h), (0, 0, 0))
383
+ for face_name in ("top", "bottom"):
384
+ im = black
385
+ angle = face_rotations.get(face_name, 0)
386
+ if angle % 360 != 0:
387
+ im = im.rotate(angle, resample=Image.BICUBIC, expand=False)
388
+ p = os.path.join(tmpdir, tex_names[face_name])
389
+ im.save(p, format="PNG")
390
+ try:
391
+ os.chmod(p, 0o644)
392
+ except Exception:
393
+ pass
394
+
395
+ # --- écrire le .mtl (références relatives) ---
396
+ mtl_path = os.path.join(tmpdir, "parallelep.mtl")
397
+ with open(mtl_path, "w", encoding="utf-8") as f:
398
+ f.write("# Material file for parallelepiped\n")
399
+ for mat_name, tex_file in tex_names.items():
400
+ f.write(f"newmtl m_{mat_name}\n")
401
+ f.write("Ka 1.000 1.000 1.000\n")
402
+ f.write("Kd 1.000 1.000 1.000\n")
403
+ f.write("Ks 0.000 0.000 0.000\n")
404
+ f.write("Ns 10.000\n")
405
+ f.write("illum 2\n")
406
+ f.write(f"map_Kd {tex_file}\n\n")
407
+ try:
408
+ os.chmod(mtl_path, 0o644)
409
+ except Exception:
410
+ pass
411
+
412
+ # --- géométrie : définir quads par face (CCW en regardant la face de l'extérieur)
413
+ # Convention: +X = right, +Y = front, +Z = up
414
+ # Les 8 coins (pour référence)
415
+ # (-x,-y,-z), ( x,-y,-z), ( x, y,-z), (-x, y,-z),
416
+ # (-x,-y, z), ( x,-y, z), ( x, y, z), (-x, y, z)
417
  quads = {
418
+ # top (+Z) : regarder depuis +Z (au-dessus)
419
+ "top": [
420
+ (-half_x, -half_y, half_z),
421
+ ( half_x, -half_y, half_z),
422
+ ( half_x, half_y, half_z),
423
+ (-half_x, half_y, half_z),
424
+ ],
425
+ # right (+X) : regarder depuis +X (côté droit)
426
+ "right": [
427
+ ( half_x, -half_y, -half_z),
428
+ ( half_x, half_y, -half_z),
429
+ ( half_x, half_y, half_z),
430
+ ( half_x, -half_y, half_z),
431
+ ],
432
+ # bottom (-Z) : regarder depuis -Z (dessous)
433
+ "bottom": [
434
+ (-half_x, half_y, -half_z),
435
+ ( half_x, half_y, -half_z),
436
+ ( half_x, -half_y, -half_z),
437
+ (-half_x, -half_y, -half_z),
438
+ ],
439
+ # left (-X) : regarder depuis -X (côté gauche)
440
+ "left": [
441
+ (-half_x, -half_y, half_z),
442
+ (-half_x, half_y, half_z),
443
+ (-half_x, half_y, -half_z),
444
+ (-half_x, -half_y, -half_z),
445
+ ],
446
+ # front (+Y) : regarder depuis +Y (face avant)
447
+ "front": [
448
+ (-half_x, half_y, -half_z),
449
+ (-half_x, half_y, half_z),
450
+ ( half_x, half_y, half_z),
451
+ ( half_x, half_y, -half_z),
452
+ ],
453
+ # back (-Y) : regarder depuis -Y (face arrière)
454
+ "back": [
455
+ ( half_x, -half_y, -half_z),
456
+ ( half_x, -half_y, half_z),
457
+ (-half_x, -half_y, half_z),
458
+ (-half_x, -half_y, -half_z),
459
+ ],
460
  }
461
 
462
+ # --- écrire l'OBJ (24 vertices, 24 vt, triangulé) selon face_order demandé ---
463
+ face_order = ["top", "right", "bottom", "left", "front", "back"]
464
+ obj_path = os.path.join(tmpdir, "parallelep.obj")
465
+ with open(obj_path, "w", encoding="utf-8") as f:
466
+ f.write("# Parallelepiped OBJ generated by build_textured_cube\n")
467
+ f.write("mtllib parallelep.mtl\n\n")
468
+
469
+ # écrire vertices : 4 par face, dans l'ordre face_order
470
+ for face_name in face_order:
471
+ for v in quads[face_name]:
472
+ f.write("v {:.6f} {:.6f} {:.6f}\n".format(*v))
473
+ f.write("\n")
474
+
475
+ # écrire UVs : 4 UVs par face (0..1). On garde origine UV bas-gauche.
476
+ uvs = [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)]
477
+ for _ in range(6):
478
+ for (u, v) in uvs:
479
+ f.write("vt {:.6f} {:.6f}\n".format(u, v))
480
+ f.write("\n")
481
+
482
+ # faces (2 triangles par face), en s'assurant que les matériaux correspondent
483
+ for i, face_name in enumerate(face_order):
484
+ f.write(f"usemtl m_{face_name}\n")
485
+ v_base = i * 4 + 1
486
+ t_base = i * 4 + 1
487
+ v1, v2, v3, v4 = v_base, v_base + 1, v_base + 2, v_base + 3
488
+ t1, t2, t3, t4 = t_base, t_base + 1, t_base + 2, t_base + 3
489
+ # deux triangles (v/vt)
490
+ f.write(f"f {v1}/{t1} {v2}/{t2} {v3}/{t3}\n")
491
+ f.write(f"f {v1}/{t1} {v3}/{t3} {v4}/{t4}\n\n")
492
+ try:
493
+ os.chmod(obj_path, 0o644)
494
+ except Exception:
495
+ pass
496
+
497
+ # vérifications rapides
498
+ for fname in ["parallelep.obj", "parallelep.mtl"] + list(tex_names.values()):
499
+ p = os.path.join(tmpdir, fname)
500
+ if not os.path.exists(p):
501
+ raise FileNotFoundError(f"Fichier attendu introuvable : {p}")
502
+
503
+ return (os.path.abspath(obj_path), tmpdir)
504
 
505
  # def build_textured_cube(pil_imgs, face_rotations=None):
506
  # """