Spaces:
Running on Zero
Running on Zero
Download app.py from akhaliq/Qwen-Image-2.1-workflow: direct link, hf CLI and curl.
- Browser
- Download file 2.94 kB
-
https://huggingface.co/spaces/akhaliq/Qwen-Image-2.1-workflow/resolve/d41bbfc581cc5134ff362e60aa7c62c88fcbe5ad/app.py
- Command line
-
hf download hf://spaces/akhaliq/Qwen-Image-2.1-workflow@d41bbfc581cc5134ff362e60aa7c62c88fcbe5ad/app.py
-
curl -L -o app.py https://huggingface.co/spaces/akhaliq/Qwen-Image-2.1-workflow/resolve/d41bbfc581cc5134ff362e60aa7c62c88fcbe5ad/app.py
2.94 kB
| """Qwen-Image 2.1 — Gradio Workflow on ZeroGPU. | |
| A node-based canvas (gr.Workflow) exposing the diffusers QwenImage21Pipeline: | |
| - text_to_image: prompt -> image | |
| - edit_image: condition image + instruction -> edited image (chained after | |
| text_to_image, or fed from an uploaded image reference) | |
| The pipeline is placed on `cuda` at module level: outside @spaces.GPU functions | |
| PyTorch runs in CUDA emulation mode, and a real ZeroGPU is attached only while | |
| a decorated function executes. | |
| """ | |
| import os | |
| import tempfile | |
| import gradio as gr | |
| import spaces | |
| import torch | |
| from PIL import Image | |
| from diffusers import QwenImage21Pipeline | |
| MODEL_ID = os.environ.get("QWEN_IMAGE_MODEL", "Qwen/Qwen-Image-2.1") | |
| # The model repo is private/gated. Per the Hub auth docs, the HF_TOKEN | |
| # environment variable is used implicitly for all Hub requests and takes | |
| # priority over any stored token — so set HF_TOKEN as a Space secret | |
| # (Settings -> Secrets) with a token whose account has access to the repo. | |
| if not os.environ.get("HF_TOKEN"): | |
| raise RuntimeError( | |
| "HF_TOKEN is not set. Add it as a Space secret (Settings -> Secrets) " | |
| f"with read access to {MODEL_ID}." | |
| ) | |
| pipe = QwenImage21Pipeline.from_pretrained(MODEL_ID, dtype=torch.bfloat16) | |
| pipe.to("cuda") | |
| def _to_pil(image) -> Image.Image: | |
| """Accept whatever the canvas hands a bound function for an image port: | |
| a PIL image, a file path, or Gradio's {path, url, is_file} file dict.""" | |
| if isinstance(image, Image.Image): | |
| return image | |
| if isinstance(image, dict): | |
| image = image.get("path") or image.get("url") | |
| if isinstance(image, str): | |
| return Image.open(image) | |
| raise ValueError(f"Unsupported image input: {type(image)!r}") | |
| def _save(image: Image.Image) -> str: | |
| path = tempfile.NamedTemporaryFile(suffix=".png", delete=False).name | |
| image.save(path) | |
| return path | |
| def _steps(value, default: int = 40) -> int: | |
| # Unconnected number ports arrive as None; function defaults are not | |
| # applied because the workflow executor passes arguments positionally. | |
| return default if value is None else int(value) | |
| def text_to_image(prompt: str, steps: int = 40) -> str: | |
| """Generate an image from a text prompt with Qwen-Image 2.1.""" | |
| image = pipe(prompt, num_inference_steps=_steps(steps)).images[0] | |
| return _save(image) | |
| def edit_image(image, instruction: str, steps: int = 40) -> str: | |
| """Edit a condition image following an instruction (image-conditioned | |
| generation with Qwen-Image 2.1).""" | |
| edited = pipe(instruction, image=_to_pil(image), num_inference_steps=_steps(steps)).images[0] | |
| return _save(edited) | |
| demo = gr.Workflow( | |
| graph=os.path.join(os.path.dirname(__file__), "workflow.json"), | |
| bind={"text_to_image": text_to_image, "edit_image": edit_image}, | |
| ) | |
| if __name__ == "__main__": | |
| demo.launch() | |