Spaces:
Runtime error
Runtime error
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 filesThe 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>
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(
|
| 29 |
-
if not
|
| 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(
|
| 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
|
| 87 |
if evt.value is not None:
|
| 88 |
-
#
|
| 89 |
-
return gr.update(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 |
-
"""
|
| 137 |
-
if
|
| 138 |
-
return
|
| 139 |
-
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
|
|
|
| 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 |
-
|
| 204 |
-
|
| 205 |
-
|
| 206 |
-
|
| 207 |
-
|
| 208 |
-
|
| 209 |
-
|
| 210 |
-
|
| 211 |
-
|
| 212 |
-
|
| 213 |
-
|
| 214 |
-
|
| 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 |
-
|
| 303 |
-
|
| 304 |
-
|
| 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 |
-
|
|
|
|
| 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=[
|
| 412 |
)
|
| 413 |
|
| 414 |
# History gallery event handlers
|
| 415 |
history_gallery.select(
|
| 416 |
fn=use_history_as_input,
|
| 417 |
inputs=None,
|
| 418 |
-
outputs=[
|
| 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=[
|
| 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 |
|