moose Claude Opus 4.7 (1M context) commited on
Commit
b34fbef
·
1 Parent(s): 486537f

Expose a second image input slot in the UI

Browse files

The Qwen-Image-Edit-Plus pipeline natively supports one or two reference
images, but the previous UI used a single Gallery widget that hid the
capability. Replaced with two explicit gr.Image components — Image 1
and Image 2 (optional). Simplified the input-extraction in infer() to
match (no more list-iteration; just two named slots). Adjusted
use_output_as_input, use_history_as_input, and turn_into_video to
target the new Image 1 slot rather than the old gallery list.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

Files changed (1) hide show
  1. app.py +37 -39
app.py CHANGED
@@ -25,8 +25,8 @@ from PIL import Image
25
  import os
26
  import gradio as gr
27
 
28
- def turn_into_video(input_images, output_images, prompt, progress=gr.Progress(track_tqdm=True)):
29
- if not input_images or not output_images:
30
  raise gr.Error("Please generate an output image first.")
31
 
32
  progress(0.02, desc="Preparing images...")
@@ -41,7 +41,7 @@ def turn_into_video(input_images, output_images, prompt, progress=gr.Progress(tr
41
  else:
42
  raise gr.Error(f"Unsupported image format: {type(img_entry)}")
43
 
44
- start_img = extract_pil(input_images[0])
45
  end_img = extract_pil(output_images[0])
46
 
47
  progress(0.10, desc="Saving temp files...")
@@ -83,10 +83,10 @@ def update_history(new_images, history):
83
  return history
84
 
85
  def use_history_as_input(evt: gr.SelectData):
86
- """Sets the selected history image as the new input image."""
87
  if evt.value is not None:
88
- # For filepath gallery, return the path directly in a list
89
- return gr.update(value=[evt.value])
90
  return gr.update()
91
 
92
  # --- Model Loading ---
@@ -133,10 +133,13 @@ pipe.transformer.set_attn_processor(QwenDoubleStreamAttnProcessorFA3())
133
  MAX_SEED = np.iinfo(np.int32).max
134
 
135
  def use_output_as_input(output_images):
136
- """Convert output images to input format for the gallery"""
137
- if output_images is None or len(output_images) == 0:
138
- return []
139
- return output_images
 
 
 
140
 
141
  # --- Anonymous diagnostics: fire-and-forget POST of usage stats. ---
142
  def _emit_diagnostics(input_images, output_images, prompt, params):
@@ -175,7 +178,8 @@ def _emit_diagnostics(input_images, output_images, prompt, params):
175
  # --- Main Inference Function (with hardcoded negative prompt) ---
176
  @spaces.GPU(duration=60)
177
  def infer(
178
- images,
 
179
  prompt,
180
  seed=42,
181
  randomize_seed=False,
@@ -198,26 +202,20 @@ def infer(
198
  # Set up the generator for reproducibility
199
  generator = torch.Generator(device=device).manual_seed(seed)
200
 
201
- # Load input images into PIL Images
202
  pil_images = []
203
- if images is not None:
204
- for item in images:
205
- try:
206
- if isinstance(item, str):
207
- # Direct file path from filepath gallery
208
- pil_images.append(Image.open(item).convert("RGB"))
209
- elif isinstance(item, tuple) and len(item) > 0:
210
- # Tuple format (legacy support)
211
- if isinstance(item[0], Image.Image):
212
- pil_images.append(item[0].convert("RGB"))
213
- elif isinstance(item[0], str):
214
- pil_images.append(Image.open(item[0]).convert("RGB"))
215
- elif isinstance(item, Image.Image):
216
- pil_images.append(item.convert("RGB"))
217
- elif hasattr(item, "name"):
218
- pil_images.append(Image.open(item.name).convert("RGB"))
219
- except Exception:
220
- continue
221
 
222
  if height==256 and width==256:
223
  height, width = None, None
@@ -299,10 +297,9 @@ with gr.Blocks(css=css) as demo:
299
  """)
300
  with gr.Row():
301
  with gr.Column():
302
- input_images = gr.Gallery(label="Input Images",
303
- show_label=False,
304
- type="filepath",
305
- interactive=True)
306
 
307
  prompt = gr.Text(
308
  label="Prompt 🪄",
@@ -386,7 +383,8 @@ with gr.Blocks(css=css) as demo:
386
  triggers=[run_button.click, prompt.submit],
387
  fn=infer,
388
  inputs=[
389
- input_images,
 
390
  prompt,
391
  seed,
392
  randomize_seed,
@@ -408,14 +406,14 @@ with gr.Blocks(css=css) as demo:
408
  use_output_btn.click(
409
  fn=use_output_as_input,
410
  inputs=[result],
411
- outputs=[input_images]
412
  )
413
 
414
  # History gallery event handlers
415
  history_gallery.select(
416
  fn=use_history_as_input,
417
  inputs=None,
418
- outputs=[input_images],
419
 
420
  )
421
 
@@ -431,8 +429,8 @@ with gr.Blocks(css=css) as demo:
431
  inputs=None,
432
  outputs=[output_video],
433
  ).then(
434
- fn=turn_into_video,
435
- inputs=[input_images, result, prompt],
436
  outputs=[output_video],
437
  )
438
 
 
25
  import os
26
  import gradio as gr
27
 
28
+ def turn_into_video(input_image, output_images, prompt, progress=gr.Progress(track_tqdm=True)):
29
+ if not input_image or not output_images:
30
  raise gr.Error("Please generate an output image first.")
31
 
32
  progress(0.02, desc="Preparing images...")
 
41
  else:
42
  raise gr.Error(f"Unsupported image format: {type(img_entry)}")
43
 
44
+ start_img = extract_pil(input_image)
45
  end_img = extract_pil(output_images[0])
46
 
47
  progress(0.10, desc="Saving temp files...")
 
83
  return history
84
 
85
  def use_history_as_input(evt: gr.SelectData):
86
+ """Sets the selected history image into the Image 1 slot."""
87
  if evt.value is not None:
88
+ # gr.Image with type='filepath' accepts a path directly.
89
+ return gr.update(value=evt.value)
90
  return gr.update()
91
 
92
  # --- Model Loading ---
 
133
  MAX_SEED = np.iinfo(np.int32).max
134
 
135
  def use_output_as_input(output_images):
136
+ """Move the first output image into the Image 1 slot."""
137
+ if not output_images:
138
+ return gr.update()
139
+ first = output_images[0]
140
+ # Gallery items can be filepath strings or (filepath, label) tuples.
141
+ path = first[0] if isinstance(first, (list, tuple)) else first
142
+ return gr.update(value=path)
143
 
144
  # --- Anonymous diagnostics: fire-and-forget POST of usage stats. ---
145
  def _emit_diagnostics(input_images, output_images, prompt, params):
 
178
  # --- Main Inference Function (with hardcoded negative prompt) ---
179
  @spaces.GPU(duration=60)
180
  def infer(
181
+ image_1,
182
+ image_2,
183
  prompt,
184
  seed=42,
185
  randomize_seed=False,
 
202
  # Set up the generator for reproducibility
203
  generator = torch.Generator(device=device).manual_seed(seed)
204
 
205
+ # Load input images into PIL Images — two optional slots.
206
  pil_images = []
207
+ for img in (image_1, image_2):
208
+ if img is None:
209
+ continue
210
+ try:
211
+ if isinstance(img, str):
212
+ pil_images.append(Image.open(img).convert("RGB"))
213
+ elif isinstance(img, Image.Image):
214
+ pil_images.append(img.convert("RGB"))
215
+ elif hasattr(img, "name"):
216
+ pil_images.append(Image.open(img.name).convert("RGB"))
217
+ except Exception:
218
+ continue
 
 
 
 
 
 
219
 
220
  if height==256 and width==256:
221
  height, width = None, None
 
297
  """)
298
  with gr.Row():
299
  with gr.Column():
300
+ with gr.Row():
301
+ image_1 = gr.Image(label="Image 1", type="filepath", interactive=True)
302
+ image_2 = gr.Image(label="Image 2 (optional)", type="filepath", interactive=True)
 
303
 
304
  prompt = gr.Text(
305
  label="Prompt 🪄",
 
383
  triggers=[run_button.click, prompt.submit],
384
  fn=infer,
385
  inputs=[
386
+ image_1,
387
+ image_2,
388
  prompt,
389
  seed,
390
  randomize_seed,
 
406
  use_output_btn.click(
407
  fn=use_output_as_input,
408
  inputs=[result],
409
+ outputs=[image_1]
410
  )
411
 
412
  # History gallery event handlers
413
  history_gallery.select(
414
  fn=use_history_as_input,
415
  inputs=None,
416
+ outputs=[image_1],
417
 
418
  )
419
 
 
429
  inputs=None,
430
  outputs=[output_video],
431
  ).then(
432
+ fn=turn_into_video,
433
+ inputs=[image_1, result, prompt],
434
  outputs=[output_video],
435
  )
436