Spaces:
Running
on
Zero
Running
on
Zero
| import os | |
| import random | |
| import uuid | |
| import json | |
| import gradio as gr | |
| import numpy as np | |
| from PIL import Image | |
| import spaces | |
| import torch | |
| from diffusers import StableDiffusionXLPipeline, EulerAncestralDiscreteScheduler | |
| # Use environment variables for flexibility | |
| MODEL_ID = os.getenv("MODEL_ID", "Corcelio/mobius") | |
| MAX_IMAGE_SIZE = int(os.getenv("MAX_IMAGE_SIZE", "4096")) | |
| USE_TORCH_COMPILE = os.getenv("USE_TORCH_COMPILE", "0") == "1" | |
| ENABLE_CPU_OFFLOAD = os.getenv("ENABLE_CPU_OFFLOAD", "0") == "1" | |
| BATCH_SIZE = int(os.getenv("BATCH_SIZE", "1")) # Allow generating multiple images at once | |
| # Determine device and load model outside of function for efficiency | |
| device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") | |
| pipe = StableDiffusionXLPipeline.from_pretrained( | |
| MODEL_ID, | |
| torch_dtype=torch.float16 if torch.cuda.is_available() else torch.float32, | |
| use_safetensors=True, | |
| add_watermarker=False, | |
| ).to(device) | |
| pipe.scheduler = EulerAncestralDiscreteScheduler.from_config(pipe.scheduler.config) | |
| # Torch compile for potential speedup (experimental) | |
| if USE_TORCH_COMPILE: | |
| pipe.compile() | |
| # CPU offloading for larger RAM capacity (experimental) | |
| if ENABLE_CPU_OFFLOAD: | |
| pipe.enable_model_cpu_offload() | |
| MAX_SEED = np.iinfo(np.int32).max | |
| def save_image(img): | |
| unique_name = str(uuid.uuid4()) + ".png" | |
| img.save(unique_name) | |
| return unique_name | |
| def randomize_seed_fn(seed: int, randomize_seed: bool) -> int: | |
| if randomize_seed: | |
| seed = random.randint(0, MAX_SEED) | |
| return seed | |
| def generate( | |
| prompt: str, | |
| negative_prompt: str = "", | |
| use_negative_prompt: bool = False, | |
| seed: int = 1, | |
| width: int = 1024, | |
| height: int = 1024, | |
| guidance_scale: float = 3, | |
| num_inference_steps: int = 30, | |
| randomize_seed: bool = False, | |
| use_resolution_binning: bool = True, | |
| num_images: int = 1, # Number of images to generate | |
| progress=gr.Progress(track_tqdm=True), | |
| ): | |
| seed = int(randomize_seed_fn(seed, randomize_seed)) | |
| generator = torch.Generator(device=device).manual_seed(seed) | |
| # Improved options handling | |
| options = { | |
| "prompt": [prompt] * num_images, | |
| "negative_prompt": [negative_prompt] * num_images if use_negative_prompt else None, | |
| "width": width, | |
| "height": height, | |
| "guidance_scale": guidance_scale, | |
| "num_inference_steps": num_inference_steps, | |
| "generator": generator, | |
| "output_type": "pil", | |
| } | |
| # Use resolution binning for faster generation with less VRAM usage | |
| if use_resolution_binning: | |
| options["use_resolution_binning"] = True | |
| # Generate images potentially in batches | |
| images = [] | |
| for i in range(0, num_images, BATCH_SIZE): | |
| batch_options = options.copy() | |
| batch_options["prompt"] = options["prompt"][i:i+BATCH_SIZE] | |
| if "negative_prompt" in batch_options: | |
| batch_options["negative_prompt"] = options["negative_prompt"][i:i+BATCH_SIZE] | |
| images.extend(pipe(**batch_options).images) | |
| image_paths = [save_image(img) for img in images] | |
| return image_paths, seed | |
| examples = [ | |
| "a cat eating a piece of cheese", | |
| "a ROBOT riding a BLUE horse on Mars, photorealistic, 4k", | |
| "Ironman VS Hulk, ultrarealistic", | |
| "Astronaut in a jungle, cold color palette, oil pastel, detailed, 8k", | |
| "An alien holding a sign board containing the word 'Flash', futuristic, neonpunk", | |
| "Kids going to school, Anime style" | |
| ] | |
| css = ''' | |
| .gradio-container{max-width: 700px !important} | |
| h1{text-align:center} | |
| footer { | |
| visibility: hidden | |
| } | |
| .wheel-and-hamster { | |
| --dur: 1s; | |
| position: relative; | |
| width: 12em; | |
| height: 12em; | |
| font-size: 14px; | |
| } | |
| .wheel, | |
| .hamster, | |
| .hamster div, | |
| .spoke { | |
| position: absolute; | |
| } | |
| .wheel, | |
| .spoke { | |
| border-radius: 50%; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| } | |
| .wheel { | |
| background: radial-gradient(100% 100% at center,hsla(0,0%,60%,0) 47.8%,hsl(0,0%,60%) 48%); | |
| z-index: 2; | |
| } | |
| .hamster { | |
| animation: hamster var(--dur) ease-in-out infinite; | |
| top: 50%; | |
| left: calc(50% - 3.5em); | |
| width: 7em; | |
| height: 3.75em; | |
| transform: rotate(4deg) translate(-0.8em,1.85em); | |
| transform-origin: 50% 0; | |
| z-index: 1; | |
| } | |
| .hamster__head { | |
| animation: hamsterHead var(--dur) ease-in-out infinite; | |
| background: hsl(30,90%,55%); | |
| border-radius: 70% 30% 0 100% / 40% 25% 25% 60%; | |
| box-shadow: 0 -0.25em 0 hsl(30,90%,80%) inset, | |
| 0.75em -1.55em 0 hsl(30,90%,90%) inset; | |
| top: 0; | |
| left: -2em; | |
| width: 2.75em; | |
| height: 2.5em; | |
| transform-origin: 100% 50%; | |
| } | |
| .hamster__ear { | |
| animation: hamsterEar var(--dur) ease-in-out infinite; | |
| background: hsl(0,90%,85%); | |
| border-radius: 50%; | |
| box-shadow: -0.25em 0 hsl(30,90%,55%) inset; | |
| top: -0.25em; | |
| right: -0.25em; | |
| width: 0.75em; | |
| height: 0.75em; | |
| transform-origin: 50% 75%; | |
| } | |
| .hamster__eye { | |
| animation: hamsterEye var(--dur) linear infinite; | |
| background-color: hsl(0,0%,0%); | |
| border-radius: 50%; | |
| top: 0.375em; | |
| left: 1.25em; | |
| width: 0.5em; | |
| height: 0.5em; | |
| } | |
| .hamster__nose { | |
| background: hsl(0,90%,75%); | |
| border-radius: 35% 65% 85% 15% / 70% 50% 50% 30%; | |
| top: 0.75em; | |
| left: 0; | |
| width: 0.2em; | |
| height: 0.25em; | |
| } | |
| .hamster__body { | |
| animation: hamsterBody var(--dur) ease-in-out infinite; | |
| background: hsl(30,90%,90%); | |
| border-radius: 50% 30% 50% 30% / 15% 60% 40% 40%; | |
| box-shadow: 0.1em 0.75em 0 hsl(30,90%,55%) inset, | |
| 0.15em -0.5em 0 hsl(30,90%,80%) inset; | |
| top: 0.25em; | |
| left: 2em; | |
| width: 4.5em; | |
| height: 3em; | |
| transform-origin: 17% 50%; | |
| transform-style: preserve-3d; | |
| } | |
| .hamster__limb--fr, | |
| .hamster__limb--fl { | |
| clip-path: polygon(0 0,100% 0,70% 80%,60% 100%,0% 100%,40% 80%); | |
| top: 2em; | |
| left: 0.5em; | |
| width: 1em; | |
| height: 1.5em; | |
| transform-origin: 50% 0; | |
| } | |
| .hamster__limb--fr { | |
| animation: hamsterFRLimb var(--dur) linear infinite; | |
| background: linear-gradient(hsl(30,90%,80%) 80%,hsl(0,90%,75%) 80%); | |
| transform: rotate(15deg) translateZ(-1px); | |
| } | |
| .hamster__limb--fl { | |
| animation: hamsterFLLimb var(--dur) linear infinite; | |
| background: linear-gradient(hsl(30,90%,80%) 80%,hsl(0,90%,75%) 80%); | |
| transform: rotate(-60deg) translateZ(-1px); | |
| } | |
| .hamster__limb--br, | |
| .hamster__limb--bl { | |
| clip-path: polygon(0 0,100% 0,100% 30%,70% 80%,60% 100%,40% 100%,30% 80%); | |
| top: 2.3em; | |
| left: 2.8em; | |
| width: 1.25em; | |
| height: 2.5em; | |
| transform-origin: 50% 10%; | |
| } | |
| .hamster__limb--br { | |
| animation: hamsterBRLimb var(--dur) linear infinite; | |
| background: linear-gradient(hsl(0,90%,75%) 40%,hsl(30,90%,80%) 40%); | |
| transform: rotate(45deg) translateZ(-1px); | |
| } | |
| .hamster__limb--bl { | |
| animation: hamsterBLLimb var(--dur) linear infinite; | |
| background: linear-gradient(hsl(0,90%,75%) 40%,hsl(30,90%,80%) 40%); | |
| transform: rotate(-30deg) translateZ(-1px); | |
| } | |
| .hamster__tail { | |
| animation: hamsterTail var(--dur) linear infinite; | |
| background: hsl(0,90%,85%); | |
| border-radius: 0.25em 50% 50% 0.25em; | |
| box-shadow: 0.1em 0.5em 0 hsl(30,90%,55%) inset, | |
| 0.1em -0.25em 0 hsl(30,90%,90%) inset; | |
| top: 3em; | |
| left: 6em; | |
| width: 0.75em; | |
| height: 0.75em; | |
| transform: rotate(30deg) translateZ(-1px); | |
| } | |
| .spoke { | |
| --s: 0.2; | |
| background: hsl(0,0%,100%); | |
| box-shadow: 0 0 0 0.2em hsl(0,0%,0%); | |
| left: calc(50% - var(--s)/2); | |
| width: var(--s); | |
| height: var(--s); | |
| } | |
| .spoke:nth-child(1) { | |
| --rotation: 15deg; | |
| transform: rotate(var(--rotation)); | |
| } | |
| .spoke:nth-child(2) { | |
| --rotation: 45deg; | |
| transform: rotate(var(--rotation)); | |
| } | |
| .spoke:nth-child(3) { | |
| --rotation: 75deg; | |
| transform: rotate(var(--rotation)); | |
| } | |
| .spoke:nth-child(4) { | |
| --rotation: 105deg; | |
| transform: rotate(var(--rotation)); | |
| } | |
| .spoke:nth-child(5) { | |
| --rotation: 135deg; | |
| transform: rotate(var(--rotation)); | |
| } | |
| .spoke:nth-child(6) { | |
| --rotation: 165deg; | |
| transform: rotate(var(--rotation)); | |
| } | |
| .spoke:nth-child(7) { | |
| --rotation: 195deg; | |
| transform: rotate(var(--rotation)); | |
| } | |
| .spoke:nth-child(8) { | |
| --rotation: 225deg; | |
| transform: rotate(var(--rotation)); | |
| } | |
| .spoke:nth-child(9) { | |
| --rotation: 255deg; | |
| transform: rotate(var(--rotation)); | |
| } | |
| .spoke:nth-child(10) { | |
| --rotation: 285deg; | |
| transform: rotate(var(--rotation)); | |
| } | |
| .spoke:nth-child(11) { | |
| --rotation: 315deg; | |
| transform: rotate(var(--rotation)); | |
| } | |
| .spoke:nth-child(12) { | |
| --rotation: 345deg; | |
| transform: rotate(var(--rotation)); | |
| } | |
| @keyframes hamster { | |
| 50% { | |
| transform: rotate(-4deg) translate(-0.8em,1.85em); | |
| } | |
| } | |
| @keyframes hamsterHead { | |
| 50% { | |
| transform: rotate(-8deg); | |
| } | |
| } | |
| @keyframes hamsterEye { | |
| 50% { | |
| transform: translateY(0.1em); | |
| } | |
| } | |
| @keyframes hamsterEar { | |
| 50% { | |
| transform: rotate(8deg); | |
| } | |
| } | |
| @keyframes hamsterBody { | |
| 50% { | |
| transform: rotate(2deg); | |
| } | |
| } | |
| @keyframes hamsterFRLimb { | |
| 8%,70% { | |
| transform: rotate(15deg) translateZ(-1px); | |
| } | |
| 33% { | |
| transform: rotate(-60deg) translateZ(-1px); | |
| } | |
| 83% { | |
| transform: rotate(45deg) translateZ(-1px); | |
| } | |
| } | |
| @keyframes hamsterFLLimb { | |
| 8%,70% { | |
| transform: rotate(-60deg) translateZ(-1px); | |
| } | |
| 33% { | |
| transform: rotate(15deg) translateZ(-1px); | |
| } | |
| 83% { | |
| transform: rotate(-45deg) translateZ(-1px); | |
| } | |
| } | |
| @keyframes hamsterBRLimb { | |
| 0%,50% { | |
| transform: rotate(45deg) translateZ(-1px); | |
| } | |
| 25% { | |
| transform: rotate(-30deg) translateZ(-1px); | |
| } | |
| 75% { | |
| transform: rotate(60deg) translateZ(-1px); | |
| } | |
| } | |
| @keyframes hamsterBLLimb { | |
| 0%,50% { | |
| transform: rotate(-30deg) translateZ(-1px); | |
| } | |
| 25% { | |
| transform: rotate(45deg) translateZ(-1px); | |
| } | |
| 75% { | |
| transform: rotate(-45deg) translateZ(-1px); | |
| } | |
| } | |
| @keyframes hamsterTail { | |
| 50% { | |
| transform: rotate(-30deg) translateZ(-1px); | |
| } | |
| } | |
| #wrapper { | |
| position: relative; | |
| height: 100vh; | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| } | |
| .loading-container { | |
| position: relative; | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| width: 100%; | |
| height: 100%; | |
| top: 50%; | |
| transform: translateY(-50%); | |
| } | |
| .hidden { | |
| display: none; | |
| } | |
| ''' | |
| html = ''' | |
| <div id="wrapper"> | |
| <div class="loading-container"> | |
| <div class="wheel-and-hamster"> | |
| <div class="wheel"> | |
| <div class="spoke"></div> | |
| <div class="spoke"></div> | |
| <div class="spoke"></div> | |
| <div class="spoke"></div> | |
| <div class="spoke"></div> | |
| <div class="spoke"></div> | |
| <div class="spoke"></div> | |
| <div class="spoke"></div> | |
| <div class="spoke"></div> | |
| <div class="spoke"></div> | |
| <div class="spoke"></div> | |
| <div class="spoke"></div> | |
| </div> | |
| <div class="hamster"> | |
| <div class="hamster__body"> | |
| <div class="hamster__head"> | |
| <div class="hamster__ear"></div> | |
| <div class="hamster__eye"></div> | |
| <div class="hamster__nose"></div> | |
| </div> | |
| <div class="hamster__limb hamster__limb--fr"></div> | |
| <div class="hamster__limb hamster__limb--fl"></div> | |
| <div class="hamster__limb hamster__limb--br"></div> | |
| <div class="hamster__limb hamster__limb--bl"></div> | |
| <div class="hamster__tail"></div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| // Wait for the Gradio app to load | |
| document.addEventListener("DOMContentLoaded", function() { | |
| const observer = new MutationObserver(function(mutationsList, observer) { | |
| for (const mutation of mutationsList) { | |
| if (mutation.type === "childList" && mutation.addedNodes.length > 0) { | |
| // Check if Gradio has loaded by looking for a specific element | |
| const gradioApp = document.querySelector("#root"); | |
| if (gradioApp) { | |
| // Hide the loading animation and observer | |
| document.querySelector(".loading-container").classList.add("hidden"); | |
| observer.disconnect(); | |
| } | |
| } | |
| } | |
| }); | |
| // Start observing the body for changes | |
| observer.observe(document.body, { childList: true, subtree: true }); | |
| }); | |
| </script> | |
| ''' | |
| block = gr.Blocks(css=css) | |
| with block as demo: | |
| gr.HTML(html) | |
| with gr.Column(elem_id="main-app"): | |
| gr.Markdown( | |
| """ | |
| # Generate images with Stable Diffusion XL | |
| """ | |
| ) | |
| with gr.Row(): | |
| with gr.Column(): | |
| text = gr.Textbox( | |
| label="Prompt", | |
| placeholder="Enter your prompt", | |
| lines=1, | |
| ) | |
| negative_text = gr.Textbox( | |
| label="Negative Prompt", | |
| placeholder="Enter negative prompt", | |
| lines=1, | |
| ) | |
| negative_prompt_chk = gr.Checkbox( | |
| label="Use Negative Prompt", | |
| value=True | |
| ) | |
| seed = gr.Number( | |
| label="Seed", | |
| value=1, | |
| precision=0 | |
| ) | |
| randomize_seed = gr.Checkbox( | |
| label="Randomize Seed", | |
| value=False | |
| ) | |
| width = gr.Slider( | |
| label="Width", | |
| value=1024, | |
| minimum=64, | |
| maximum=MAX_IMAGE_SIZE, | |
| step=8 | |
| ) | |
| height = gr.Slider( | |
| label="Height", | |
| value=1024, | |
| minimum=64, | |
| maximum=MAX_IMAGE_SIZE, | |
| step=8 | |
| ) | |
| guidance_scale = gr.Slider( | |
| label="Guidance Scale", | |
| value=3, | |
| minimum=0, | |
| maximum=50, | |
| step=0.5 | |
| ) | |
| num_inference_steps = gr.Slider( | |
| label="Number of Inference Steps", | |
| value=30, | |
| minimum=1, | |
| maximum=100, | |
| step=1 | |
| ) | |
| use_resolution_binning = gr.Checkbox( | |
| label="Use Resolution Binning", | |
| value=True | |
| ) | |
| num_images = gr.Number( | |
| label="Number of Images to Generate", | |
| value=1, | |
| minimum=1, | |
| maximum=10, | |
| step=1 | |
| ) | |
| generate_button = gr.Button(label="Generate Images") | |
| with gr.Column(): | |
| gr.Label(label="Examples:") | |
| for example in examples: | |
| gr.Label(label=f"- {example}") | |
| generated_images = gr.Image( | |
| label="Generated Images", | |
| type="PIL", # Display the PIL image | |
| source=None | |
| ) | |
| def generate_images_interface(): | |
| args = { | |
| "prompt": text.value, | |
| "negative_prompt": negative_text.value if negative_prompt_chk.value else "", | |
| "use_negative_prompt": negative_prompt_chk.value, | |
| "seed": seed.value, | |
| "width": int(width.value), | |
| "height": int(height.value), | |
| "guidance_scale": float(guidance_scale.value), | |
| "num_inference_steps": int(num_inference_steps.value), | |
| "randomize_seed": randomize_seed.value, | |
| "use_resolution_binning": use_resolution_binning.value, | |
| "num_images": int(num_images.value), | |
| "progress": gr.Progress() | |
| } | |
| image_paths, _ = generate(**args) | |
| images = [Image.open(image_path) for image_path in image_paths] | |
| return images | |
| def on_generate_click(): | |
| generated_images.set_value(generate_images_interface()) | |
| gr.Interface( | |
| fn=on_generate_click, | |
| live=True, | |
| title="Diffusion Generator", | |
| description="Generate images using Stable Diffusion XL.", | |
| layout="vertical", | |
| blocking=True | |
| ).launch() | |