multimodalart HF Staff commited on
Commit
8c3537a
·
verified ·
1 Parent(s): e095348

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +133 -33
app.py CHANGED
@@ -6,7 +6,8 @@ import spaces
6
 
7
  from PIL import Image
8
  from diffusers import FlowMatchEulerDiscreteScheduler, QwenImageEditPlusPipeline
9
-
 
10
 
11
  MAX_SEED = np.iinfo(np.int32).max
12
 
@@ -165,14 +166,19 @@ class CameraControl3D(gr.HTML):
165
  """
166
  A 3D camera control component using Three.js.
167
  Outputs: { azimuth: number, elevation: number, distance: number }
 
168
  """
169
- def __init__(self, value=None, **kwargs):
170
  if value is None:
171
  value = {"azimuth": 0, "elevation": 0, "distance": 1.0}
172
 
173
  html_template = """
174
  <div id="camera-control-wrapper" style="width: 100%; height: 450px; position: relative; background: #1a1a1a; border-radius: 12px; overflow: hidden;">
175
  <div id="prompt-overlay" style="position: absolute; bottom: 10px; left: 50%; transform: translateX(-50%); background: rgba(0,0,0,0.8); padding: 8px 16px; border-radius: 8px; font-family: monospace; font-size: 12px; color: #00ff88; white-space: nowrap; z-index: 10;"></div>
 
 
 
 
176
  </div>
177
  """
178
 
@@ -180,6 +186,7 @@ class CameraControl3D(gr.HTML):
180
  (() => {
181
  const wrapper = element.querySelector('#camera-control-wrapper');
182
  const promptOverlay = element.querySelector('#prompt-overlay');
 
183
 
184
  // Wait for THREE to load
185
  const initScene = () => {
@@ -202,8 +209,8 @@ class CameraControl3D(gr.HTML):
202
  wrapper.insertBefore(renderer.domElement, promptOverlay);
203
 
204
  // Lighting
205
- scene.add(new THREE.AmbientLight(0xffffff, 0.4));
206
- const dirLight = new THREE.DirectionalLight(0xffffff, 0.8);
207
  dirLight.position.set(5, 10, 5);
208
  scene.add(dirLight);
209
 
@@ -238,36 +245,94 @@ class CameraControl3D(gr.HTML):
238
  return steps.reduce((prev, curr) => Math.abs(curr - value) < Math.abs(prev - value) ? curr : prev);
239
  }
240
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
241
  // Target image plane
242
- const imgCanvas = document.createElement('canvas');
243
- imgCanvas.width = 256;
244
- imgCanvas.height = 256;
245
- const ctx = imgCanvas.getContext('2d');
246
- ctx.fillStyle = '#3a3a4a';
247
- ctx.fillRect(0, 0, 256, 256);
248
- ctx.fillStyle = '#ffcc99';
249
- ctx.beginPath();
250
- ctx.arc(128, 128, 80, 0, Math.PI * 2);
251
- ctx.fill();
252
- ctx.fillStyle = '#333';
253
- ctx.beginPath();
254
- ctx.arc(100, 110, 10, 0, Math.PI * 2);
255
- ctx.arc(156, 110, 10, 0, Math.PI * 2);
256
- ctx.fill();
257
- ctx.strokeStyle = '#333';
258
- ctx.lineWidth = 3;
259
- ctx.beginPath();
260
- ctx.arc(128, 130, 35, 0.2, Math.PI - 0.2);
261
- ctx.stroke();
262
-
263
- const texture = new THREE.CanvasTexture(imgCanvas);
264
- const targetPlane = new THREE.Mesh(
265
- new THREE.PlaneGeometry(1.2, 1.2),
266
- new THREE.MeshBasicMaterial({ map: texture, side: THREE.DoubleSide })
267
- );
268
  targetPlane.position.copy(CENTER);
269
  scene.add(targetPlane);
270
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
271
  // Camera model
272
  const cameraGroup = new THREE.Group();
273
  const bodyMat = new THREE.MeshStandardMaterial({ color: 0x333333, metalness: 0.8, roughness: 0.2 });
@@ -502,7 +567,7 @@ class CameraControl3D(gr.HTML):
502
  renderer.setSize(wrapper.clientWidth, wrapper.clientHeight);
503
  }).observe(wrapper);
504
 
505
- // Store update function for external calls
506
  wrapper._updateFromProps = (newVal) => {
507
  if (newVal && typeof newVal === 'object') {
508
  azimuthAngle = newVal.azimuth ?? azimuthAngle;
@@ -511,6 +576,17 @@ class CameraControl3D(gr.HTML):
511
  updatePositions();
512
  }
513
  };
 
 
 
 
 
 
 
 
 
 
 
514
  };
515
 
516
  initScene();
@@ -521,6 +597,7 @@ class CameraControl3D(gr.HTML):
521
  value=value,
522
  html_template=html_template,
523
  js_on_load=js_on_load,
 
524
  **kwargs
525
  )
526
 
@@ -624,6 +701,19 @@ with gr.Blocks(css=css, theme=gr.themes.Soft()) as demo:
624
  """Sync slider changes to 3D control."""
625
  return {"azimuth": azimuth, "elevation": elevation, "distance": distance}
626
 
 
 
 
 
 
 
 
 
 
 
 
 
 
627
  # Slider -> Prompt preview
628
  for slider in [azimuth_slider, elevation_slider, distance_slider]:
629
  slider.change(
@@ -654,11 +744,21 @@ with gr.Blocks(css=css, theme=gr.themes.Soft()) as demo:
654
  outputs=[result, seed, prompt_preview]
655
  )
656
 
657
- # Image upload -> update dimensions
658
  image.upload(
659
  fn=update_dimensions_on_upload,
660
  inputs=[image],
661
  outputs=[width, height]
 
 
 
 
 
 
 
 
 
 
662
  )
663
 
664
  # Examples
@@ -676,4 +776,4 @@ with gr.Blocks(css=css, theme=gr.themes.Soft()) as demo:
676
 
677
  if __name__ == "__main__":
678
  head = '<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>'
679
- demo.launch(head=head)
 
6
 
7
  from PIL import Image
8
  from diffusers import FlowMatchEulerDiscreteScheduler, QwenImageEditPlusPipeline
9
+ #from qwenimage.pipeline_qwenimage_edit_plus import QwenImageEditPlusPipeline
10
+ #from qwenimage.transformer_qwenimage import QwenImageTransformer2DModel
11
 
12
  MAX_SEED = np.iinfo(np.int32).max
13
 
 
166
  """
167
  A 3D camera control component using Three.js.
168
  Outputs: { azimuth: number, elevation: number, distance: number }
169
+ Accepts imageUrl prop to display user's uploaded image on the plane.
170
  """
171
+ def __init__(self, value=None, imageUrl=None, **kwargs):
172
  if value is None:
173
  value = {"azimuth": 0, "elevation": 0, "distance": 1.0}
174
 
175
  html_template = """
176
  <div id="camera-control-wrapper" style="width: 100%; height: 450px; position: relative; background: #1a1a1a; border-radius: 12px; overflow: hidden;">
177
  <div id="prompt-overlay" style="position: absolute; bottom: 10px; left: 50%; transform: translateX(-50%); background: rgba(0,0,0,0.8); padding: 8px 16px; border-radius: 8px; font-family: monospace; font-size: 12px; color: #00ff88; white-space: nowrap; z-index: 10;"></div>
178
+ <div id="upload-hint" style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); color: #666; font-family: sans-serif; font-size: 14px; text-align: center; pointer-events: none; z-index: 5;">
179
+ <div style="font-size: 32px; margin-bottom: 8px;">📷</div>
180
+ Upload an image to preview
181
+ </div>
182
  </div>
183
  """
184
 
 
186
  (() => {
187
  const wrapper = element.querySelector('#camera-control-wrapper');
188
  const promptOverlay = element.querySelector('#prompt-overlay');
189
+ const uploadHint = element.querySelector('#upload-hint');
190
 
191
  // Wait for THREE to load
192
  const initScene = () => {
 
209
  wrapper.insertBefore(renderer.domElement, promptOverlay);
210
 
211
  // Lighting
212
+ scene.add(new THREE.AmbientLight(0xffffff, 0.6));
213
+ const dirLight = new THREE.DirectionalLight(0xffffff, 0.6);
214
  dirLight.position.set(5, 10, 5);
215
  scene.add(dirLight);
216
 
 
245
  return steps.reduce((prev, curr) => Math.abs(curr - value) < Math.abs(prev - value) ? curr : prev);
246
  }
247
 
248
+ // Create placeholder texture (smiley face)
249
+ function createPlaceholderTexture() {
250
+ const canvas = document.createElement('canvas');
251
+ canvas.width = 256;
252
+ canvas.height = 256;
253
+ const ctx = canvas.getContext('2d');
254
+ ctx.fillStyle = '#3a3a4a';
255
+ ctx.fillRect(0, 0, 256, 256);
256
+ ctx.fillStyle = '#ffcc99';
257
+ ctx.beginPath();
258
+ ctx.arc(128, 128, 80, 0, Math.PI * 2);
259
+ ctx.fill();
260
+ ctx.fillStyle = '#333';
261
+ ctx.beginPath();
262
+ ctx.arc(100, 110, 10, 0, Math.PI * 2);
263
+ ctx.arc(156, 110, 10, 0, Math.PI * 2);
264
+ ctx.fill();
265
+ ctx.strokeStyle = '#333';
266
+ ctx.lineWidth = 3;
267
+ ctx.beginPath();
268
+ ctx.arc(128, 130, 35, 0.2, Math.PI - 0.2);
269
+ ctx.stroke();
270
+ return new THREE.CanvasTexture(canvas);
271
+ }
272
+
273
  // Target image plane
274
+ let currentTexture = createPlaceholderTexture();
275
+ const planeMaterial = new THREE.MeshBasicMaterial({ map: currentTexture, side: THREE.DoubleSide });
276
+ let targetPlane = new THREE.Mesh(new THREE.PlaneGeometry(1.2, 1.2), planeMaterial);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
277
  targetPlane.position.copy(CENTER);
278
  scene.add(targetPlane);
279
 
280
+ // Function to update texture from image URL
281
+ function updateTextureFromUrl(url) {
282
+ if (!url) {
283
+ // Reset to placeholder
284
+ planeMaterial.map = createPlaceholderTexture();
285
+ planeMaterial.needsUpdate = true;
286
+ uploadHint.style.display = 'block';
287
+ // Reset plane to square
288
+ scene.remove(targetPlane);
289
+ targetPlane = new THREE.Mesh(new THREE.PlaneGeometry(1.2, 1.2), planeMaterial);
290
+ targetPlane.position.copy(CENTER);
291
+ scene.add(targetPlane);
292
+ return;
293
+ }
294
+
295
+ uploadHint.style.display = 'none';
296
+
297
+ const loader = new THREE.TextureLoader();
298
+ loader.crossOrigin = 'anonymous';
299
+ loader.load(url, (texture) => {
300
+ texture.minFilter = THREE.LinearFilter;
301
+ texture.magFilter = THREE.LinearFilter;
302
+ planeMaterial.map = texture;
303
+ planeMaterial.needsUpdate = true;
304
+
305
+ // Adjust plane aspect ratio to match image
306
+ const img = texture.image;
307
+ if (img && img.width && img.height) {
308
+ const aspect = img.width / img.height;
309
+ const maxSize = 1.5;
310
+ let planeWidth, planeHeight;
311
+ if (aspect > 1) {
312
+ planeWidth = maxSize;
313
+ planeHeight = maxSize / aspect;
314
+ } else {
315
+ planeHeight = maxSize;
316
+ planeWidth = maxSize * aspect;
317
+ }
318
+ scene.remove(targetPlane);
319
+ targetPlane = new THREE.Mesh(
320
+ new THREE.PlaneGeometry(planeWidth, planeHeight),
321
+ planeMaterial
322
+ );
323
+ targetPlane.position.copy(CENTER);
324
+ scene.add(targetPlane);
325
+ }
326
+ }, undefined, (err) => {
327
+ console.error('Failed to load texture:', err);
328
+ });
329
+ }
330
+
331
+ // Check for initial imageUrl
332
+ if (props.imageUrl) {
333
+ updateTextureFromUrl(props.imageUrl);
334
+ }
335
+
336
  // Camera model
337
  const cameraGroup = new THREE.Group();
338
  const bodyMat = new THREE.MeshStandardMaterial({ color: 0x333333, metalness: 0.8, roughness: 0.2 });
 
567
  renderer.setSize(wrapper.clientWidth, wrapper.clientHeight);
568
  }).observe(wrapper);
569
 
570
+ // Store update functions for external calls
571
  wrapper._updateFromProps = (newVal) => {
572
  if (newVal && typeof newVal === 'object') {
573
  azimuthAngle = newVal.azimuth ?? azimuthAngle;
 
576
  updatePositions();
577
  }
578
  };
579
+
580
+ wrapper._updateTexture = updateTextureFromUrl;
581
+
582
+ // Watch for imageUrl prop changes
583
+ let lastImageUrl = props.imageUrl;
584
+ setInterval(() => {
585
+ if (props.imageUrl !== lastImageUrl) {
586
+ lastImageUrl = props.imageUrl;
587
+ updateTextureFromUrl(props.imageUrl);
588
+ }
589
+ }, 100);
590
  };
591
 
592
  initScene();
 
597
  value=value,
598
  html_template=html_template,
599
  js_on_load=js_on_load,
600
+ imageUrl=imageUrl,
601
  **kwargs
602
  )
603
 
 
701
  """Sync slider changes to 3D control."""
702
  return {"azimuth": azimuth, "elevation": elevation, "distance": distance}
703
 
704
+ def update_3d_image(image):
705
+ """Update the 3D component with the uploaded image."""
706
+ if image is None:
707
+ return gr.update(imageUrl=None)
708
+ # Convert PIL image to base64 data URL
709
+ import base64
710
+ from io import BytesIO
711
+ buffered = BytesIO()
712
+ image.save(buffered, format="PNG")
713
+ img_str = base64.b64encode(buffered.getvalue()).decode()
714
+ data_url = f"data:image/png;base64,{img_str}"
715
+ return gr.update(imageUrl=data_url)
716
+
717
  # Slider -> Prompt preview
718
  for slider in [azimuth_slider, elevation_slider, distance_slider]:
719
  slider.change(
 
744
  outputs=[result, seed, prompt_preview]
745
  )
746
 
747
+ # Image upload -> update dimensions AND update 3D preview
748
  image.upload(
749
  fn=update_dimensions_on_upload,
750
  inputs=[image],
751
  outputs=[width, height]
752
+ ).then(
753
+ fn=update_3d_image,
754
+ inputs=[image],
755
+ outputs=[camera_3d]
756
+ )
757
+
758
+ # Also handle image clear
759
+ image.clear(
760
+ fn=lambda: gr.update(imageUrl=None),
761
+ outputs=[camera_3d]
762
  )
763
 
764
  # Examples
 
776
 
777
  if __name__ == "__main__":
778
  head = '<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>'
779
+ demo.launch()