Spaces:
Running on Zero
Running on Zero
Upload folder using huggingface_hub
Browse files- README.md +33 -7
- __pycache__/app.cpython-313.pyc +0 -0
- app.py +193 -0
- requirements.txt +8 -0
README.md
CHANGED
|
@@ -1,13 +1,39 @@
|
|
| 1 |
---
|
| 2 |
-
title: Qwen Image 2
|
| 3 |
-
emoji:
|
| 4 |
-
colorFrom:
|
| 5 |
-
colorTo:
|
| 6 |
sdk: gradio
|
| 7 |
sdk_version: 6.28.0
|
| 8 |
-
python_version:
|
| 9 |
app_file: app.py
|
| 10 |
-
|
|
|
|
|
|
|
|
|
|
| 11 |
---
|
| 12 |
|
| 13 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
---
|
| 2 |
+
title: Qwen Image 2.1 Studio
|
| 3 |
+
emoji: 🎨
|
| 4 |
+
colorFrom: indigo
|
| 5 |
+
colorTo: purple
|
| 6 |
sdk: gradio
|
| 7 |
sdk_version: 6.28.0
|
| 8 |
+
python_version: "3.12"
|
| 9 |
app_file: app.py
|
| 10 |
+
short_description: Create, edit, and generate transparent images with Qwen
|
| 11 |
+
startup_duration_timeout: 1h
|
| 12 |
+
models:
|
| 13 |
+
- Qwen/Qwen-Image-2.1
|
| 14 |
---
|
| 15 |
|
| 16 |
+
# Qwen Image 2.1 Studio
|
| 17 |
+
|
| 18 |
+
A Gradio demo of [Qwen/Qwen-Image-2.1](https://huggingface.co/Qwen/Qwen-Image-2.1), running the model directly on Hugging Face ZeroGPU.
|
| 19 |
+
|
| 20 |
+
- **Create an image:** describe a scene or choose an example.
|
| 21 |
+
- **Edit an image:** upload one reference image and describe the change.
|
| 22 |
+
- **Transparent PNG:** describe a sticker, object, or other isolated asset. The model receives Qwen's recommended transparency prompt. Transparency depends on the generated result.
|
| 23 |
+
- Five aspect ratios around 1 megapixel, adjustable steps, reproducible seeds, and downloadable PNGs with embedded generation settings.
|
| 24 |
+
|
| 25 |
+
The default is 28 inference steps; use 40 for the model card's recommended final-quality setting. Example results are cached on first use. Disable **Use a new seed each time** to reproduce a result with the same settings.
|
| 26 |
+
|
| 27 |
+
## Hosting
|
| 28 |
+
|
| 29 |
+
This Space requires `zero-a10g` hardware. Configure hardware in the Space settings; README metadata does not select hardware. No external inference API key is needed. The model weights and Diffusers revision are pinned in the source. The initial download is approximately 33 GB and can take several minutes.
|
| 30 |
+
|
| 31 |
+
The model loads at startup and is registered with ZeroGPU. The Gradio queue runs one generation at a time. Visitors use Hugging Face's daily ZeroGPU quota. Uploaded and generated files are temporary; download results you want to keep. The app does not send images to an external API or publish a community gallery. Gradio cached files expire after 24 hours; cached examples can be reused across visitors.
|
| 32 |
+
|
| 33 |
+
## API
|
| 34 |
+
|
| 35 |
+
Use Gradio's **Use via API** link for the current `/generate` schema. An MCP server is also enabled. The handler returns the preview file, original PNG download, actual seed, and timing details.
|
| 36 |
+
|
| 37 |
+
## Model license
|
| 38 |
+
|
| 39 |
+
The model is governed by the [Qwen Research License Agreement](https://huggingface.co/Qwen/Qwen-Image-2.1/blob/main/LICENSE). See the model repository for its terms and intended use.
|
__pycache__/app.cpython-313.pyc
ADDED
|
Binary file (11.3 kB). View file
|
|
|
app.py
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
|
| 3 |
+
os.environ.setdefault("PYTORCH_CUDA_ALLOC_CONF", "expandable_segments:True")
|
| 4 |
+
|
| 5 |
+
import spaces
|
| 6 |
+
import gradio as gr
|
| 7 |
+
import torch
|
| 8 |
+
import json
|
| 9 |
+
import random
|
| 10 |
+
import tempfile
|
| 11 |
+
import time
|
| 12 |
+
from pathlib import Path
|
| 13 |
+
|
| 14 |
+
from diffusers import QwenImage21Pipeline
|
| 15 |
+
from PIL import Image, ImageOps, PngImagePlugin
|
| 16 |
+
|
| 17 |
+
MODEL_ID = "Qwen/Qwen-Image-2.1"
|
| 18 |
+
MODEL_REVISION = "790c92633540aa0cb11d9abf19eb46d861714758"
|
| 19 |
+
MODES = ["Create an image", "Edit an image", "Transparent PNG"]
|
| 20 |
+
SIZES = {
|
| 21 |
+
"Square · 1:1": (1024, 1024),
|
| 22 |
+
"Landscape · 16:9": (1344, 768),
|
| 23 |
+
"Portrait · 9:16": (768, 1344),
|
| 24 |
+
"Landscape · 4:3": (1152, 864),
|
| 25 |
+
"Portrait · 3:4": (864, 1152),
|
| 26 |
+
}
|
| 27 |
+
MAX_SEED = 2**31 - 1
|
| 28 |
+
|
| 29 |
+
print(f"Loading {MODEL_ID} at {MODEL_REVISION}", flush=True)
|
| 30 |
+
pipe = QwenImage21Pipeline.from_pretrained(
|
| 31 |
+
MODEL_ID, revision=MODEL_REVISION, torch_dtype=torch.bfloat16
|
| 32 |
+
).to("cuda")
|
| 33 |
+
print("Qwen-Image-2.1 pipeline loaded on CUDA via ZeroGPU.", flush=True)
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
@spaces.GPU(duration=180)
|
| 37 |
+
def generate(
|
| 38 |
+
prompt: str,
|
| 39 |
+
mode: str = "Create an image",
|
| 40 |
+
reference: Image.Image | None = None,
|
| 41 |
+
aspect_ratio: str = "Square · 1:1",
|
| 42 |
+
steps: int = 28,
|
| 43 |
+
seed: int = 42,
|
| 44 |
+
randomize_seed: bool = True,
|
| 45 |
+
progress: gr.Progress = gr.Progress(track_tqdm=True),
|
| 46 |
+
) -> tuple[str, str, int, str]:
|
| 47 |
+
"""Generate or edit an image with Qwen-Image-2.1 and return a PNG.
|
| 48 |
+
|
| 49 |
+
Choose Create an image, Edit an image (requires a reference), or
|
| 50 |
+
Transparent PNG. Disable randomize_seed to reuse a seed. The result
|
| 51 |
+
includes a PNG download, the actual seed, and generation details.
|
| 52 |
+
"""
|
| 53 |
+
prompt = prompt.strip()
|
| 54 |
+
if not prompt:
|
| 55 |
+
raise gr.Error("Describe the image you want to create or the change to make.")
|
| 56 |
+
if len(prompt) > 4000:
|
| 57 |
+
raise gr.Error("Keep your prompt under 4,000 characters.")
|
| 58 |
+
if mode not in MODES or aspect_ratio not in SIZES:
|
| 59 |
+
raise gr.Error("Choose one of the available modes and aspect ratios.")
|
| 60 |
+
if steps is None or int(steps) != steps or not 4 <= steps <= 40:
|
| 61 |
+
raise gr.Error("Steps must be a whole number between 4 and 40.")
|
| 62 |
+
if not randomize_seed and (
|
| 63 |
+
seed is None or int(seed) != seed or not 0 <= seed <= MAX_SEED
|
| 64 |
+
):
|
| 65 |
+
raise gr.Error(f"Seed must be a whole number between 0 and {MAX_SEED}.")
|
| 66 |
+
if mode == "Edit an image" and reference is None:
|
| 67 |
+
raise gr.Error("Upload a reference image before editing.")
|
| 68 |
+
|
| 69 |
+
actual_seed = random.randint(0, MAX_SEED) if randomize_seed else int(seed)
|
| 70 |
+
width, height = SIZES[aspect_ratio]
|
| 71 |
+
effective_prompt = prompt
|
| 72 |
+
if mode == "Transparent PNG":
|
| 73 |
+
effective_prompt = (
|
| 74 |
+
"This is an RGBA image with transparency. " + prompt
|
| 75 |
+
+ " The image has alpha channel and the background is transparent."
|
| 76 |
+
)
|
| 77 |
+
kwargs = {}
|
| 78 |
+
if mode == "Edit an image":
|
| 79 |
+
reference = ImageOps.exif_transpose(reference).convert("RGBA")
|
| 80 |
+
reference.thumbnail((2048, 2048))
|
| 81 |
+
kwargs["image"] = reference
|
| 82 |
+
|
| 83 |
+
start = time.perf_counter()
|
| 84 |
+
with torch.inference_mode():
|
| 85 |
+
result = pipe(
|
| 86 |
+
prompt=effective_prompt,
|
| 87 |
+
width=width,
|
| 88 |
+
height=height,
|
| 89 |
+
num_inference_steps=int(steps),
|
| 90 |
+
generator=torch.Generator("cuda").manual_seed(actual_seed),
|
| 91 |
+
**kwargs,
|
| 92 |
+
).images[0]
|
| 93 |
+
elapsed = time.perf_counter() - start
|
| 94 |
+
metadata = {
|
| 95 |
+
"model": MODEL_ID,
|
| 96 |
+
"revision": MODEL_REVISION,
|
| 97 |
+
"prompt": prompt,
|
| 98 |
+
"effective_prompt": effective_prompt,
|
| 99 |
+
"mode": mode,
|
| 100 |
+
"seed": actual_seed,
|
| 101 |
+
"steps": int(steps),
|
| 102 |
+
"width": result.width,
|
| 103 |
+
"height": result.height,
|
| 104 |
+
"seconds": round(elapsed, 2),
|
| 105 |
+
}
|
| 106 |
+
png_info = PngImagePlugin.PngInfo()
|
| 107 |
+
png_info.add_text("generation", json.dumps(metadata, ensure_ascii=False))
|
| 108 |
+
# Gradio owns its output cache and expires files after 24 hours.
|
| 109 |
+
output_dir = Path(tempfile.mkdtemp(prefix="qwen21-", dir=gr.utils.get_upload_folder()))
|
| 110 |
+
path = output_dir / f"qwen-image-2.1-{actual_seed}.png"
|
| 111 |
+
result.save(path, pnginfo=png_info)
|
| 112 |
+
details = (
|
| 113 |
+
f"{result.width} × {result.height} · {int(steps)} steps · "
|
| 114 |
+
f"{elapsed:.1f}s · seed {actual_seed} · {result.mode} PNG"
|
| 115 |
+
)
|
| 116 |
+
if mode == "Transparent PNG" and (
|
| 117 |
+
"A" not in result.getbands() or result.getchannel("A").getextrema()[0] == 255
|
| 118 |
+
):
|
| 119 |
+
details += " · The model returned an opaque image; try another seed or prompt."
|
| 120 |
+
print(f"Completed {mode}: {details}", flush=True)
|
| 121 |
+
return str(path), str(path), actual_seed, details
|
| 122 |
+
|
| 123 |
+
|
| 124 |
+
def show_reference(mode: str) -> gr.Image:
|
| 125 |
+
"""Show the reference upload for image editing."""
|
| 126 |
+
return gr.Image(visible=mode == "Edit an image")
|
| 127 |
+
|
| 128 |
+
|
| 129 |
+
CSS = """
|
| 130 |
+
.gradio-container { max-width: 1180px !important; margin: auto !important; }
|
| 131 |
+
.dark .gradio-container { color: var(--body-text-color); }
|
| 132 |
+
#intro { padding: 20px 0 12px; }
|
| 133 |
+
#intro h1 { font-size: clamp(28px, 4vw, 44px); letter-spacing: -1.5px; }
|
| 134 |
+
#result { min-height: 420px; }
|
| 135 |
+
#generate { min-height: 48px; }
|
| 136 |
+
"""
|
| 137 |
+
|
| 138 |
+
with gr.Blocks(title="Qwen Image 2.1 Studio", delete_cache=(3600, 86400)) as demo:
|
| 139 |
+
gr.Markdown(
|
| 140 |
+
"# Qwen Image 2.1 Studio\n"
|
| 141 |
+
"Turn an idea into an image. Reimagine a photo. Create a transparent asset.\n\n"
|
| 142 |
+
"[Model](https://huggingface.co/Qwen/Qwen-Image-2.1) · "
|
| 143 |
+
"[Qwen Research License](https://huggingface.co/Qwen/Qwen-Image-2.1/blob/main/LICENSE)",
|
| 144 |
+
elem_id="intro",
|
| 145 |
+
)
|
| 146 |
+
with gr.Row(equal_height=False):
|
| 147 |
+
with gr.Column(scale=5):
|
| 148 |
+
mode = gr.Radio(MODES, value=MODES[0], label="What would you like to do?")
|
| 149 |
+
prompt = gr.Textbox(
|
| 150 |
+
label="Your prompt",
|
| 151 |
+
placeholder="A tiny observatory on a floating island, warm evening light, detailed illustration…",
|
| 152 |
+
lines=5,
|
| 153 |
+
max_lines=10,
|
| 154 |
+
)
|
| 155 |
+
reference = gr.Image(
|
| 156 |
+
label="Reference image", type="pil", image_mode="RGBA",
|
| 157 |
+
sources=["upload"], visible=False, format="png",
|
| 158 |
+
)
|
| 159 |
+
aspect_ratio = gr.Dropdown(list(SIZES), value="Square · 1:1", label="Aspect ratio")
|
| 160 |
+
with gr.Accordion("Generation settings", open=False):
|
| 161 |
+
steps = gr.Slider(4, 40, value=28, step=1, label="Steps", info="More steps take longer. Qwen recommends 40 for final images.")
|
| 162 |
+
randomize_seed = gr.Checkbox(True, label="Use a new seed each time")
|
| 163 |
+
seed = gr.Number(value=42, precision=0, minimum=0, maximum=MAX_SEED, label="Seed", info="Turn off new seeds to reproduce a result.")
|
| 164 |
+
run = gr.Button("Generate image", variant="primary", elem_id="generate")
|
| 165 |
+
gr.Markdown("Shared ZeroGPU compute. Queue times and daily usage limits apply.")
|
| 166 |
+
with gr.Column(scale=6):
|
| 167 |
+
result = gr.Image(label="Your image", type="filepath", image_mode="RGBA", format="png", interactive=False, elem_id="result")
|
| 168 |
+
download = gr.File(label="Download original PNG", interactive=False)
|
| 169 |
+
details = gr.Textbox(label="Generation details", interactive=False)
|
| 170 |
+
|
| 171 |
+
mode.change(show_reference, inputs=mode, outputs=reference, api_visibility="private", queue=False)
|
| 172 |
+
run.click(
|
| 173 |
+
generate,
|
| 174 |
+
inputs=[prompt, mode, reference, aspect_ratio, steps, seed, randomize_seed],
|
| 175 |
+
outputs=[result, download, seed, details],
|
| 176 |
+
api_name="generate", concurrency_limit=1, concurrency_id="qwen-pipeline",
|
| 177 |
+
)
|
| 178 |
+
gr.Examples(
|
| 179 |
+
examples=[
|
| 180 |
+
['A neon shop sign that reads "QWEN IMAGE 2.1", rainy night, reflections on wet pavement', MODES[0]],
|
| 181 |
+
["A tiny observatory on a floating island above clouds, warm sunset, intricate storybook illustration", MODES[0]],
|
| 182 |
+
["A cute cartoon dragon sticker, jade green scales, a friendly expression, clean edges", MODES[2]],
|
| 183 |
+
],
|
| 184 |
+
inputs=[prompt, mode], outputs=[result, download, seed, details],
|
| 185 |
+
fn=generate, cache_examples=True, cache_mode="lazy", label="Try an idea",
|
| 186 |
+
)
|
| 187 |
+
gr.Markdown("For editing, upload an image and describe what should change. Downloaded PNGs include the prompt and settings.")
|
| 188 |
+
|
| 189 |
+
if __name__ == "__main__":
|
| 190 |
+
demo.queue(max_size=16, default_concurrency_limit=1).launch(
|
| 191 |
+
theme=gr.themes.Soft(primary_hue="indigo", neutral_hue="slate"),
|
| 192 |
+
css=CSS, mcp_server=True, max_file_size="10mb",
|
| 193 |
+
)
|
requirements.txt
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
diffusers @ git+https://github.com/huggingface/diffusers.git@9f1246971270c84dcbe71233edb7a519596a5d02
|
| 2 |
+
transformers==5.17.0
|
| 3 |
+
accelerate
|
| 4 |
+
torchvision
|
| 5 |
+
safetensors
|
| 6 |
+
Pillow
|
| 7 |
+
sentencepiece
|
| 8 |
+
mcp
|