CarolineM5 commited on
Commit
98f01d7
·
verified ·
1 Parent(s): a6e0f3a

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +240 -217
app.py CHANGED
@@ -15,6 +15,12 @@ from transformers import CLIPTokenizer, CLIPTextModel, CLIPImageProcessor
15
  import torch.nn as nn
16
  import numpy as np
17
 
 
 
 
 
 
 
18
  class UNetNoCondWrapper(nn.Module):
19
  def __init__(self, base_unet: UNet2DModel):
20
  super().__init__()
@@ -71,243 +77,260 @@ class UNetNoCondWrapper(nn.Module):
71
  # pipe = pipe.to(torch.float32).to(device)
72
  # @spaces.GPU
73
 
74
- def pil_to_data_uri(img: Image.Image) -> str:
75
- buf = io.BytesIO()
76
- img.save(buf, format="PNG")
77
- b = base64.b64encode(buf.getvalue()).decode("utf-8")
78
- return f"data:image/png;base64,{b}"
79
-
80
- # # --- new gradio_generate that returns both gallery and 3D html ---
81
- # def gradio_generate(fibers: Image.Image, rings: Image.Image, num_steps: int):
82
- # """
83
- # fibers: PIL image containing 4 tiles in a 2x2 square (order 1,4,2,3)
84
- # rings: same
85
- # returns: (list_of_4_PIL_images, html_string_for_3d)
86
- # """
87
- # # call your inference function (unchanged)
88
- # preds = inference(pipe, fibers, rings, int(num_steps)) # preds order: [1,4,2,3] -> TL,TR,BL,BR
89
-
90
- # # convert preds to RGB for gallery
91
- # preds_rgb = [p.convert("RGB") if p.mode != "RGB" else p for p in preds]
92
-
93
- # # build data-urls in same order
94
- # data_urls = [pil_to_dataurl(im, fmt="PNG") for im in preds_rgb] # [TL,TR,BL,BR]
95
-
96
- # # safe JSON array literal for injection into HTML template
97
- # js_img_list = json.dumps(data_urls)
98
-
99
- # html = HTML_TEMPLATE.replace("___IMGS___", js_img_list)
100
-
101
- # # gr.Interface expects outputs matching signature; we return (gallery_images, html_string)
102
- # return preds_rgb, html
103
-
104
- # # replace the Interface outputs: first a Gallery, second a HTML component
105
- # iface = gr.Interface(
106
- # fn=gradio_generate,
107
- # inputs=[
108
- # gr.Image(type="pil", label="Fiber"),
109
- # gr.Image(type="pil", label="Ring"),
110
- # gr.Number(value=10, label="Number of inference steps"),
111
- # ],
112
- # outputs=[
113
- # gr.HTML(label="3D preview")
114
- # ],
115
- # title="Photorealistic wood generator (4 faces)",
116
- # description="""
117
- # Upload 2 images (one with four fiber maps and one with four ring maps) arranged as a 2x2 square.
118
- # The model returns four generated faces (one per tile) and a 3D preview with those faces textured on a box.
119
- # """
120
- # )
121
 
122
- def run(fibers: Image.Image, rings: Image.Image, num_steps: int):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
123
  try:
124
- # appelle la fonction d'inference de l'utilisateur
125
- # outputs = inference(pipe, fibers, rings, int(num_steps))
126
- outputs = inference(fibers, rings, int(num_steps))
127
- if not (isinstance(outputs, (list,tuple)) and len(outputs) >= 4):
128
- raise ValueError("La fonction d'inference doit renvoyer une liste/tuple de 4 images.")
129
- # Prendre les 4 premières images
130
- imgs = outputs[:4]
131
- # Convertir en PIL si nécessaire et assurer taille raisonnable
132
  pil_imgs = []
133
- for im in imgs:
134
  if isinstance(im, np.ndarray):
135
  im = Image.fromarray(im)
136
- # for safety, ensure RGB
137
  if im.mode != "RGB":
138
  im = im.convert("RGB")
139
  pil_imgs.append(im)
140
 
141
- # Préparer miniatures (pour les 4 gr.Image)
142
- thumb_imgs = [im.copy().resize((256,256)) for im in pil_imgs]
143
-
144
- # Convertir en data-URIs pour Three.js
145
- data_uris = [pil_to_data_uri(im) for im in pil_imgs]
146
-
147
- # Construire le HTML/JS pour le visualiseur Three.js
148
- html = f"""
149
- <div id="viewer" style="width:100%;height:480px; border:1px solid #ddd; position:relative;background:#f6f6f6;">
150
- <div id="viewer-msg" style="position:absolute;left:8px;top:8px;padding:6px 8px;background:rgba(255,255,255,0.9);border-radius:6px;font-size:12px;color:#333;z-index:2;">
151
- Manipulez la souris pour tourner, molette pour zoomer.
152
- </div>
153
- </div>
154
- <script src="https://unpkg.com/three@0.152.2/build/three.min.js"></script>
155
- <script src="https://unpkg.com/three@0.152.2/examples/js/controls/OrbitControls.js"></script>
156
- <script>
157
- (function() {{
158
- const images = {data_uris!r};
159
- const container = document.getElementById('viewer');
160
-
161
- function safeLog(...args) {{ try{{console.log(...args)}}catch(e){{}} }}
162
-
163
- function initScene() {{
164
- const w = container.clientWidth || 800;
165
- const h = container.clientHeight || 480;
166
-
167
- const scene = new THREE.Scene();
168
- const camera = new THREE.PerspectiveCamera(45, w/h, 0.1, 1000);
169
- camera.position.set(2.5, 2.0, 3.5);
170
-
171
- const renderer = new THREE.WebGLRenderer({{antialias:true}});
172
- renderer.setSize(w, h);
173
- renderer.domElement.style.width = '100%';
174
- renderer.domElement.style.height = '100%';
175
- container.appendChild(renderer.domElement);
176
-
177
- // lights
178
- const ambient = new THREE.AmbientLight(0xffffff, 0.7);
179
- scene.add(ambient);
180
- const dir = new THREE.DirectionalLight(0xffffff, 0.6);
181
- dir.position.set(5,10,7);
182
- scene.add(dir);
183
-
184
- // fallback cube (visible immédiatement)
185
- const geometry = new THREE.BoxGeometry(1.6,1.6,1.6);
186
- const fallbackMats = [];
187
- for (let i=0;i<6;i++) fallbackMats.push(new THREE.MeshStandardMaterial({{color:0x999999}}));
188
- const cube = new THREE.Mesh(geometry, fallbackMats);
189
- scene.add(cube);
190
-
191
- // grid for context
192
- const grid = new THREE.GridHelper(6, 12, 0x888888, 0x444444);
193
- grid.position.y = -1.2;
194
- scene.add(grid);
195
-
196
- // controls
197
- const controls = new THREE.OrbitControls(camera, renderer.domElement);
198
- controls.enableDamping = true;
199
- controls.dampingFactor = 0.08;
200
- controls.target.set(0,0,0);
201
-
202
- // attempt to load textures, but keep fallback if it fails
203
- const loader = new THREE.TextureLoader();
204
- Promise.all(images.map(uri => new Promise((res, rej) => {{
205
- try {{
206
- loader.load(uri, tex => {{ tex.flipY = false; tex.generateMipmaps = true; res(tex); }}, undefined, err => rej(err));
207
- }} catch(e) {{ rej(e); }}
208
- }}))).then(textures => {{
209
- safeLog('Textures loaded', textures);
210
- const mats = [
211
- new THREE.MeshStandardMaterial({{map: textures[0]}}), // right
212
- new THREE.MeshStandardMaterial({{map: textures[1]}}), // left
213
- new THREE.MeshStandardMaterial({{color:0xcccccc}}), // top
214
- new THREE.MeshStandardMaterial({{color:0xcccccc}}), // bottom
215
- new THREE.MeshStandardMaterial({{map: textures[2]}}), // front
216
- new THREE.MeshStandardMaterial({{map: textures[3]}}) // back
217
- ];
218
- // set wrapping/filter
219
- mats.forEach(m => {{
220
- if (m.map) {{
221
- m.map.minFilter = THREE.LinearFilter;
222
- m.map.wrapS = THREE.ClampToEdgeWrapping;
223
- m.map.wrapT = THREE.ClampToEdgeWrapping;
224
- }}
225
- }});
226
- cube.material = mats;
227
- }}).catch(err => {{
228
- safeLog('Erreur chargement textures (on garde fallback):', err);
229
- // affiche un petit message utilisateur (optionnel)
230
- const msg = document.getElementById('viewer-msg');
231
- if (msg) msg.innerText = "Rendu 3D : textures non chargées, affichage fallback (voir console).";
232
- }});
233
-
234
- // animation loop
235
- function animate() {{
236
- requestAnimationFrame(animate);
237
- controls.update();
238
- renderer.render(scene, camera);
239
- }}
240
- animate();
241
-
242
- // responsive: observe container size
243
- if (typeof ResizeObserver !== 'undefined') {{
244
- const ro = new ResizeObserver(() => {{
245
- const ww = container.clientWidth || 800;
246
- const hh = container.clientHeight || 480;
247
- renderer.setSize(ww, hh);
248
- camera.aspect = ww / hh;
249
- camera.updateProjectionMatrix();
250
- }});
251
- ro.observe(container);
252
- }} else {{
253
- window.addEventListener('resize', () => {{
254
- const ww = container.clientWidth || 800;
255
- const hh = container.clientHeight || 480;
256
- renderer.setSize(ww, hh);
257
- camera.aspect = ww / hh;
258
- camera.updateProjectionMatrix();
259
- }});
260
- }}
261
- }}
262
-
263
- // wait until container has non-zero size and THREE is available
264
- let tries = 0;
265
- function waitForReady() {{
266
- tries++;
267
- if (container.clientWidth > 0 && typeof THREE !== 'undefined') {{
268
- try {{ initScene(); }} catch(e) {{ safeLog('Erreur initScene', e); container.innerHTML = "<div style='padding:10px;color:#900;'>Erreur initialisation rendu 3D (voir console)</div>"; }}
269
- }} else {{
270
- if (tries < 60) setTimeout(waitForReady, 100);
271
- else {{
272
- // give it one last try
273
- try {{ initScene(); }} catch(e) {{ safeLog('Derniere tentative échouée', e); container.innerHTML = "<div style='padding:10px;color:#900;'>Impossible d'initialiser le rendu 3D (voir console)</div>"; }}
274
- }}
275
- }}
276
- }}
277
- waitForReady();
278
- }})();
279
- </script>
280
- """
281
- # Retourner : 4 miniatures pour affichage + HTML viewer
282
- return (*thumb_imgs, html)
283
  except Exception as e:
284
  traceback.print_exc()
285
- # en cas d'erreur, renvoyer 4 images vides et message html d'erreur
286
  blank = Image.new("RGB", (256,256), (220,220,220))
287
- err_html = f"<div style='color:#900;padding:12px;'>Erreur serveur: {str(e)}</div>"
288
- return (blank, blank, blank, blank, err_html)
289
 
 
290
  # Interface Gradio
291
- with gr.Blocks(title="3D Cube Viewer - 4 outputs on cube") as demo:
292
- gr.Markdown("### App 3D : assemble 4 images produites par votre code d'inference sur les 4 faces d'un cube.")
 
293
  with gr.Row():
294
  with gr.Column(scale=1):
295
- inp1 = gr.Image(label="Image 1 (entrée)", type="numpy")
296
- inp2 = gr.Image(label="Image 2 (entrée)", type="numpy")
297
- inp3 = gr.Number(value=10, label="Number of inference steps")
298
  run_btn = gr.Button("Lancer l'inference")
299
- gr.Markdown("Remplace `user_inference` dans `app.py` par ton code d'inférence existant.")
300
  with gr.Column(scale=2):
301
- viewer = gr.HTML("<div style='padding:12px;color:#666;'>Le rendu 3D s'affichera ici après inference.</div>", label="Visu 3D")
302
- # gr.Row().style(mobile_collapse=False)
303
  with gr.Row():
304
- out1 = gr.Image(label="Output 1")
305
- out2 = gr.Image(label="Output 2")
306
- out3 = gr.Image(label="Output 3")
307
- out4 = gr.Image(label="Output 4")
308
 
309
- run_btn.click(fn=run, inputs=[inp1, inp2, inp3], outputs=[out1, out2, out3, out4, viewer])
310
 
311
  if __name__ == "__main__":
312
- demo.launch(server_name="0.0.0.0", server_port=7860, share=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
313
 
 
15
  import torch.nn as nn
16
  import numpy as np
17
 
18
+ import tempfile
19
+ import os
20
+ import shutil
21
+ import uuid
22
+
23
+
24
  class UNetNoCondWrapper(nn.Module):
25
  def __init__(self, base_unet: UNet2DModel):
26
  super().__init__()
 
77
  # pipe = pipe.to(torch.float32).to(device)
78
  # @spaces.GPU
79
 
80
+ # def pil_to_data_uri(img: Image.Image) -> str:
81
+ # buf = io.BytesIO()
82
+ # img.save(buf, format="PNG")
83
+ # b = base64.b64encode(buf.getvalue()).decode("utf-8")
84
+ # return f"data:image/png;base64,{b}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
85
 
86
+ def user_inference(img1, img2):
87
+ try:
88
+ # si numpy array -> PIL
89
+ if isinstance(img1, np.ndarray):
90
+ img1 = Image.fromarray(img1)
91
+ if isinstance(img2, np.ndarray):
92
+ img2 = Image.fromarray(img2)
93
+ # 4 outputs tests
94
+ o1 = img1.copy().resize((512,512))
95
+ o2 = img2.copy().resize((512,512))
96
+ o3 = img1.transpose(Image.FLIP_LEFT_RIGHT).resize((512,512))
97
+ o4 = img2.rotate(90, expand=True).resize((512,512))
98
+ return [o1, o2, o3, o4]
99
+ except Exception:
100
+ blank = Image.new("RGB", (512,512), (200,200,200))
101
+ return [blank, blank, blank, blank]
102
+
103
+ def build_textured_cube(textures, out_dir=None):
104
+ """
105
+ textures: list de 4 PIL.Image (RGB)
106
+ out_dir: optionnel dossier de sortie ; si None, un tmpdir est créé
107
+ retourne: chemin absolu vers le fichier .obj
108
+ """
109
+ if out_dir is None:
110
+ out_dir = tempfile.mkdtemp(prefix="cube_")
111
+ else:
112
+ os.makedirs(out_dir, exist_ok=True)
113
+
114
+ # sauvegarde textures
115
+ tex_filenames = []
116
+ for i, im in enumerate(textures):
117
+ fn = f"tex_{i}.png"
118
+ path = os.path.join(out_dir, fn)
119
+ im.save(path, format="PNG")
120
+ tex_filenames.append(fn)
121
+
122
+ # mtl content (référence les textures sauvegardées)
123
+ mtl_name = "cube.mtl"
124
+ mtl_path = os.path.join(out_dir, mtl_name)
125
+ with open(mtl_path, "w") as f:
126
+ # materials mapping: mat_right, mat_left, mat_top, mat_bottom, mat_front, mat_back
127
+ f.write("# materials for cube\n")
128
+ # right -> textures[0]
129
+ f.write("newmtl mat_right\n")
130
+ f.write(f" Ns 10\n")
131
+ f.write(f" Ka 1.000 1.000 1.000\n")
132
+ f.write(f" Kd 1.000 1.000 1.000\n")
133
+ f.write(f" map_Kd {tex_filenames[0]}\n\n")
134
+ # left -> textures[1]
135
+ f.write("newmtl mat_left\n")
136
+ f.write(" Ns 10\n")
137
+ f.write(" Ka 1.000 1.000 1.000\n")
138
+ f.write(" Kd 1.000 1.000 1.000\n")
139
+ f.write(f" map_Kd {tex_filenames[1]}\n\n")
140
+ # top neutral
141
+ f.write("newmtl mat_top\n")
142
+ f.write(" Ns 10\n")
143
+ f.write(" Ka 1.000 1.000 1.000\n")
144
+ f.write(" Kd 0.8 0.8 0.8\n\n")
145
+ # bottom neutral
146
+ f.write("newmtl mat_bottom\n")
147
+ f.write(" Ns 10\n")
148
+ f.write(" Ka 1.000 1.000 1.000\n")
149
+ f.write(" Kd 0.7 0.7 0.7\n\n")
150
+ # front -> textures[2]
151
+ f.write("newmtl mat_front\n")
152
+ f.write(" Ns 10\n")
153
+ f.write(" Ka 1.000 1.000 1.000\n")
154
+ f.write(" Kd 1.000 1.000 1.000\n")
155
+ f.write(f" map_Kd {tex_filenames[2]}\n\n")
156
+ # back -> textures[3]
157
+ f.write("newmtl mat_back\n")
158
+ f.write(" Ns 10\n")
159
+ f.write(" Ka 1.000 1.000 1.000\n")
160
+ f.write(" Kd 1.000 1.000 1.000\n")
161
+ f.write(f" map_Kd {tex_filenames[3]}\n\n")
162
+
163
+ # OBJ content
164
+ obj_name = "cube.obj"
165
+ obj_path = os.path.join(out_dir, obj_name)
166
+ with open(obj_path, "w") as f:
167
+ f.write(f"mtllib {mtl_name}\n")
168
+ f.write("o CubeTextured\n")
169
+
170
+ # 8 vertices
171
+ verts = [
172
+ (-1.0, -1.0, -1.0), # v1
173
+ ( 1.0, -1.0, -1.0), # v2
174
+ ( 1.0, 1.0, -1.0), # v3
175
+ (-1.0, 1.0, -1.0), # v4
176
+ (-1.0, -1.0, 1.0), # v5
177
+ ( 1.0, -1.0, 1.0), # v6
178
+ ( 1.0, 1.0, 1.0), # v7
179
+ (-1.0, 1.0, 1.0), # v8
180
+ ]
181
+ for v in verts:
182
+ f.write(f"v {v[0]} {v[1]} {v[2]}\n")
183
+
184
+ # Pour chaque face on va définir 4 UVs (vt) — on crée 4 vt par face (24 au total)
185
+ # uv order: (0,0),(1,0),(1,1),(0,1)
186
+ uv_coords = [(0.0,0.0),(1.0,0.0),(1.0,1.0),(0.0,1.0)]
187
+ for _ in range(6):
188
+ for uv in uv_coords:
189
+ f.write(f"vt {uv[0]} {uv[1]}\n")
190
+
191
+ # Faces:
192
+ # We'll use materials in this order: right, left, top, bottom, front, back
193
+ # Note: vt indices are 1..24
194
+ # indices for convenience
195
+ # vertices indices: v1..v8 (1-based)
196
+ # define faces as quads (OBJ supports quads)
197
+ # right (x = +1): v2, v6, v7, v3
198
+ f.write("usemtl mat_right\n")
199
+ f.write("f 2/1 6/2 7/3 3/4\n")
200
+ # left (x = -1): v5, v1, v4, v8
201
+ f.write("usemtl mat_left\n")
202
+ f.write("f 5/5 1/6 4/7 8/8\n")
203
+ # top (y = +1): v4, v3, v7, v8
204
+ f.write("usemtl mat_top\n")
205
+ f.write("f 4/9 3/10 7/11 8/12\n")
206
+ # bottom (y = -1): v1, v5, v6, v2
207
+ f.write("usemtl mat_bottom\n")
208
+ f.write("f 1/13 5/14 6/15 2/16\n")
209
+ # front (z = +1): v5, v6, v7, v8
210
+ f.write("usemtl mat_front\n")
211
+ f.write("f 5/17 6/18 7/19 8/20\n")
212
+ # back (z = -1): v1, v2, v3, v4
213
+ f.write("usemtl mat_back\n")
214
+ f.write("f 1/21 2/22 3/23 4/24\n")
215
+
216
+ # return absolute path to .obj
217
+ return obj_path, out_dir
218
+
219
+ # -------------------------
220
+ # Fonction appelée par Gradio
221
+ # retourne : 4 miniatures (PIL) + chemin vers le .obj (str)
222
+ # -------------------------
223
+ def run(img1, img2):
224
  try:
225
+ outputs = user_inference(img1, img2)
226
+ if not (isinstance(outputs, (list, tuple)) and len(outputs) >= 4):
227
+ raise ValueError("user_inference doit renvoyer une liste/tuple de 4 images.")
228
+ # Prendre les 4 premières images et convertir en PIL RGB
 
 
 
 
229
  pil_imgs = []
230
+ for im in outputs[:4]:
231
  if isinstance(im, np.ndarray):
232
  im = Image.fromarray(im)
 
233
  if im.mode != "RGB":
234
  im = im.convert("RGB")
235
  pil_imgs.append(im)
236
 
237
+ # miniatures affichées dans l'UI
238
+ thumbs = [im.copy().resize((256,256)) for im in pil_imgs]
239
+
240
+ # construire le .obj + textures
241
+ obj_path, tmpdir = build_textured_cube(pil_imgs)
242
+
243
+ # IMPORTANT: on renvoie le chemin absolu vers le fichier .obj
244
+ # Gradio/Spaces chargera le modèle 3D depuis ce fichier et les textures
245
+ # doivent être dans le même dossier que le .obj (ce qui est le cas).
246
+ return (*thumbs, obj_path)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
247
  except Exception as e:
248
  traceback.print_exc()
249
+ # en cas d'erreur, renvoyer 4 images grises et None pour le modèle
250
  blank = Image.new("RGB", (256,256), (220,220,220))
251
+ return (blank, blank, blank, blank, None)
 
252
 
253
+ # -------------------------
254
  # Interface Gradio
255
+ # -------------------------
256
+ with gr.Blocks(title="Cube 3D coller 4 images sur les faces") as demo:
257
+ gr.Markdown("**Chargez 2 images** → votre code d'inference doit produire 4 images → les 4 images seront collées sur les faces latérales d'un cube (.obj).")
258
  with gr.Row():
259
  with gr.Column(scale=1):
260
+ inp1 = gr.Image(label="Image 1 (entrée)", type="pil")
261
+ inp2 = gr.Image(label="Image 2 (entrée)", type="pil")
 
262
  run_btn = gr.Button("Lancer l'inference")
263
+ gr.Markdown("Remplace `user_inference` dans `app.py` par ta fonction d'inference réelle.")
264
  with gr.Column(scale=2):
265
+ model3d_out = gr.Model3D(label="Cube 3D (obj + mtl + textures)")
 
266
  with gr.Row():
267
+ out1 = gr.Image(label="Output 1 (right)")
268
+ out2 = gr.Image(label="Output 2 (left)")
269
+ out3 = gr.Image(label="Output 3 (front)")
270
+ out4 = gr.Image(label="Output 4 (back)")
271
 
272
+ run_btn.click(fn=run, inputs=[inp1, inp2], outputs=[out1, out2, out3, out4, model3d_out])
273
 
274
  if __name__ == "__main__":
275
+ demo.launch(server_name="0.0.0.0", server_port=7860)
276
+
277
+ # def run(fibers: Image.Image, rings: Image.Image, num_steps: int):
278
+ # try:
279
+ # # appelle la fonction d'inference de l'utilisateur
280
+ # # outputs = inference(pipe, fibers, rings, int(num_steps))
281
+ # outputs = inference(fibers, rings, int(num_steps))
282
+ # if not (isinstance(outputs, (list,tuple)) and len(outputs) >= 4):
283
+ # raise ValueError("La fonction d'inference doit renvoyer une liste/tuple de 4 images.")
284
+ # # Prendre les 4 premières images
285
+ # imgs = outputs[:4]
286
+ # # Convertir en PIL si nécessaire et assurer taille raisonnable
287
+ # pil_imgs = []
288
+ # for im in imgs:
289
+ # if isinstance(im, np.ndarray):
290
+ # im = Image.fromarray(im)
291
+ # # for safety, ensure RGB
292
+ # if im.mode != "RGB":
293
+ # im = im.convert("RGB")
294
+ # pil_imgs.append(im)
295
+
296
+ # # Préparer miniatures (pour les 4 gr.Image)
297
+ # thumb_imgs = [im.copy().resize((256,256)) for im in pil_imgs]
298
+
299
+ # # Convertir en data-URIs pour Three.js
300
+ # data_uris = [pil_to_data_uri(im) for im in pil_imgs]
301
+
302
+ # # Construire le HTML/JS pour le visualiseur Three.j
303
+ # html = 0
304
+ # # Retourner : 4 miniatures pour affichage + HTML viewer
305
+ # return (*thumb_imgs, html)
306
+ # except Exception as e:
307
+ # traceback.print_exc()
308
+ # # en cas d'erreur, renvoyer 4 images vides et message html d'erreur
309
+ # blank = Image.new("RGB", (256,256), (220,220,220))
310
+ # err_html = f"<div style='color:#900;padding:12px;'>Erreur serveur: {str(e)}</div>"
311
+ # return (blank, blank, blank, blank, err_html)
312
+
313
+ # # Interface Gradio
314
+ # with gr.Blocks(title="3D Cube Viewer - 4 outputs on cube") as demo:
315
+ # gr.Markdown("### App 3D : assemble 4 images produites par votre code d'inference sur les 4 faces d'un cube.")
316
+ # with gr.Row():
317
+ # with gr.Column(scale=1):
318
+ # inp1 = gr.Image(label="Image 1 (entrée)", type="numpy")
319
+ # inp2 = gr.Image(label="Image 2 (entrée)", type="numpy")
320
+ # inp3 = gr.Number(value=10, label="Number of inference steps")
321
+ # run_btn = gr.Button("Lancer l'inference")
322
+ # gr.Markdown("Remplace `user_inference` dans `app.py` par ton code d'inférence existant.")
323
+ # with gr.Column(scale=2):
324
+ # viewer = gr.HTML("<div style='padding:12px;color:#666;'>Le rendu 3D s'affichera ici après inference.</div>", label="Visu 3D")
325
+ # # gr.Row().style(mobile_collapse=False)
326
+ # with gr.Row():
327
+ # out1 = gr.Image(label="Output 1")
328
+ # out2 = gr.Image(label="Output 2")
329
+ # out3 = gr.Image(label="Output 3")
330
+ # out4 = gr.Image(label="Output 4")
331
+
332
+ # run_btn.click(fn=run, inputs=[inp1, inp2, inp3], outputs=[out1, out2, out3, out4, viewer])
333
+
334
+ # if __name__ == "__main__":
335
+ # demo.launch(server_name="0.0.0.0", server_port=7860, share=True)
336