Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
|
@@ -1,66 +1,67 @@
|
|
| 1 |
-
# app.py
|
| 2 |
import os
|
| 3 |
import random
|
|
|
|
| 4 |
import numpy as np
|
| 5 |
-
from PIL import Image
|
| 6 |
import gradio as gr
|
| 7 |
import torch
|
| 8 |
-
from diffusers import DiffusionPipeline
|
| 9 |
-
from diffusers import EulerDiscreteScheduler # scheduler choice
|
| 10 |
|
| 11 |
-
#
|
| 12 |
-
|
| 13 |
-
MODEL_ID = os.getenv("MODEL_ID", "hakurei/waifu-diffusion")
|
| 14 |
-
|
| 15 |
-
# If your model requires a token, set HUGGINGFACE_HUB_TOKEN in Space secrets
|
| 16 |
HF_TOKEN = os.getenv("HUGGINGFACE_HUB_TOKEN", None)
|
| 17 |
|
| 18 |
device = "cuda" if torch.cuda.is_available() else "cpu"
|
| 19 |
-
torch_dtype = torch.
|
| 20 |
|
| 21 |
-
#
|
| 22 |
-
DEFAULT_WIDTH =
|
| 23 |
-
DEFAULT_HEIGHT =
|
| 24 |
-
DEFAULT_STEPS =
|
| 25 |
-
DEFAULT_GUIDANCE =
|
| 26 |
MAX_SEED = np.iinfo(np.int32).max
|
| 27 |
|
| 28 |
-
# Load pipeline (
|
|
|
|
| 29 |
def load_pipeline():
|
|
|
|
|
|
|
|
|
|
| 30 |
try:
|
| 31 |
-
|
| 32 |
-
pipe = DiffusionPipeline.from_pretrained(
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
)
|
| 37 |
-
# attach scheduler only if available and desired
|
| 38 |
-
if isinstance(pipe.scheduler, type(None)) and scheduler is not None:
|
| 39 |
pipe.scheduler = scheduler
|
|
|
|
|
|
|
|
|
|
| 40 |
pipe = pipe.to(device)
|
| 41 |
-
#
|
| 42 |
try:
|
| 43 |
pipe.safety_checker = None
|
| 44 |
except Exception:
|
| 45 |
pass
|
| 46 |
-
|
|
|
|
| 47 |
except Exception as e:
|
| 48 |
-
raise RuntimeError(f"
|
| 49 |
|
| 50 |
-
#
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
global PIPE
|
| 54 |
-
if PIPE is None:
|
| 55 |
-
PIPE = load_pipeline()
|
| 56 |
-
return PIPE
|
| 57 |
-
|
| 58 |
-
# Default negative prompt tuned to reduce common artifacts
|
| 59 |
-
DEFAULT_NEGATIVE_PROMPT = (
|
| 60 |
-
"low quality, bad anatomy, blurry, deformed, extra limbs, mutated hands, "
|
| 61 |
-
"poorly drawn face, watermark, text, signature, lowres, oversaturated"
|
| 62 |
)
|
| 63 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 64 |
def infer(
|
| 65 |
prompt: str,
|
| 66 |
negative_prompt: str,
|
|
@@ -71,100 +72,130 @@ def infer(
|
|
| 71 |
guidance_scale: float,
|
| 72 |
num_inference_steps: int,
|
| 73 |
):
|
| 74 |
-
|
| 75 |
-
|
|
|
|
| 76 |
|
| 77 |
-
if randomize_seed:
|
| 78 |
seed = random.randint(0, MAX_SEED)
|
|
|
|
|
|
|
| 79 |
|
| 80 |
-
|
| 81 |
-
|
|
|
|
|
|
|
|
|
|
| 82 |
|
| 83 |
-
|
|
|
|
|
|
|
|
|
|
| 84 |
|
| 85 |
-
|
| 86 |
-
width = min(width, 768)
|
| 87 |
-
height = min(height, 768)
|
| 88 |
|
| 89 |
try:
|
| 90 |
-
|
| 91 |
prompt=prompt,
|
| 92 |
-
negative_prompt=(negative_prompt or
|
| 93 |
width=width,
|
| 94 |
height=height,
|
| 95 |
guidance_scale=float(guidance_scale),
|
| 96 |
-
num_inference_steps=
|
| 97 |
generator=gen,
|
| 98 |
)
|
| 99 |
-
image =
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
return image, f"Seed: {seed}"
|
| 104 |
except Exception as e:
|
| 105 |
-
#
|
| 106 |
try:
|
| 107 |
-
|
| 108 |
prompt=prompt,
|
| 109 |
-
negative_prompt=(negative_prompt or
|
| 110 |
width=width,
|
| 111 |
height=height,
|
| 112 |
guidance_scale=max(3.0, float(guidance_scale) - 1.0),
|
| 113 |
-
num_inference_steps=max(
|
| 114 |
generator=gen,
|
| 115 |
)
|
| 116 |
-
image =
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
return image, f"Recovered (retry) — Seed: {seed}"
|
| 120 |
except Exception as e2:
|
| 121 |
return None, f"Generation failed: {e2}"
|
| 122 |
|
| 123 |
-
#
|
| 124 |
css = """
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 128 |
"""
|
| 129 |
|
| 130 |
examples = [
|
| 131 |
-
"
|
| 132 |
-
"
|
| 133 |
-
"
|
| 134 |
]
|
| 135 |
|
| 136 |
-
with gr.Blocks(css=css,
|
| 137 |
-
with gr.
|
| 138 |
-
gr.
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
gr.
|
| 160 |
-
|
| 161 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 162 |
|
| 163 |
run_btn.click(
|
| 164 |
-
fn=
|
| 165 |
inputs=[prompt, negative, seed, randomize, width, height, guidance, steps],
|
| 166 |
-
outputs=[gallery,
|
|
|
|
| 167 |
)
|
| 168 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 169 |
if __name__ == "__main__":
|
| 170 |
demo.launch()
|
|
|
|
| 1 |
+
# app.py — MangaMorph (Gradio) — colorful & polished CPU-friendly UI
|
| 2 |
import os
|
| 3 |
import random
|
| 4 |
+
import time
|
| 5 |
import numpy as np
|
| 6 |
+
from PIL import Image, ImageOps
|
| 7 |
import gradio as gr
|
| 8 |
import torch
|
| 9 |
+
from diffusers import DiffusionPipeline, EulerDiscreteScheduler
|
|
|
|
| 10 |
|
| 11 |
+
# ---------- CONFIG ----------
|
| 12 |
+
MODEL_ID = os.getenv("MODEL_ID", "hakurei/waifu-diffusion") # change if needed
|
|
|
|
|
|
|
|
|
|
| 13 |
HF_TOKEN = os.getenv("HUGGINGFACE_HUB_TOKEN", None)
|
| 14 |
|
| 15 |
device = "cuda" if torch.cuda.is_available() else "cpu"
|
| 16 |
+
torch_dtype = torch.float16 if device == "cuda" else torch.float32
|
| 17 |
|
| 18 |
+
# CPU-friendly defaults & limits
|
| 19 |
+
DEFAULT_WIDTH = 384
|
| 20 |
+
DEFAULT_HEIGHT = 384
|
| 21 |
+
DEFAULT_STEPS = 10
|
| 22 |
+
DEFAULT_GUIDANCE = 5.5
|
| 23 |
MAX_SEED = np.iinfo(np.int32).max
|
| 24 |
|
| 25 |
+
# ---------- Load pipeline (lazy) ----------
|
| 26 |
+
PIPE = None
|
| 27 |
def load_pipeline():
|
| 28 |
+
global PIPE
|
| 29 |
+
if PIPE is not None:
|
| 30 |
+
return PIPE
|
| 31 |
try:
|
| 32 |
+
# Try to load model; if scheduler not present we keep default
|
| 33 |
+
pipe = DiffusionPipeline.from_pretrained(MODEL_ID, torch_dtype=torch_dtype, use_auth_token=HF_TOKEN)
|
| 34 |
+
# Try to set a faster scheduler if available
|
| 35 |
+
try:
|
| 36 |
+
scheduler = EulerDiscreteScheduler.from_pretrained(MODEL_ID, subfolder="scheduler")
|
|
|
|
|
|
|
|
|
|
| 37 |
pipe.scheduler = scheduler
|
| 38 |
+
except Exception:
|
| 39 |
+
pass
|
| 40 |
+
# Move to device
|
| 41 |
pipe = pipe.to(device)
|
| 42 |
+
# Disable safety checker on CPU for speed (optional)
|
| 43 |
try:
|
| 44 |
pipe.safety_checker = None
|
| 45 |
except Exception:
|
| 46 |
pass
|
| 47 |
+
PIPE = pipe
|
| 48 |
+
return PIPE
|
| 49 |
except Exception as e:
|
| 50 |
+
raise RuntimeError(f"Model load failed: {e}")
|
| 51 |
|
| 52 |
+
# ---------- Helpers ----------
|
| 53 |
+
DEFAULT_NEG = (
|
| 54 |
+
"low quality, bad anatomy, blurry, extra limbs, malformed, deformed, watermark, text, signature, lowres"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 55 |
)
|
| 56 |
|
| 57 |
+
def tidy_image(img: Image.Image, max_side=1024):
|
| 58 |
+
# ensure RGB and a consistent max size (safety)
|
| 59 |
+
img = img.convert("RGB")
|
| 60 |
+
if max(img.size) > max_side:
|
| 61 |
+
img = ImageOps.contain(img, (max_side, max_side))
|
| 62 |
+
return img
|
| 63 |
+
|
| 64 |
+
# ---------- Inference function ----------
|
| 65 |
def infer(
|
| 66 |
prompt: str,
|
| 67 |
negative_prompt: str,
|
|
|
|
| 72 |
guidance_scale: float,
|
| 73 |
num_inference_steps: int,
|
| 74 |
):
|
| 75 |
+
start = time.time()
|
| 76 |
+
if not prompt or prompt.strip() == "":
|
| 77 |
+
return None, "Enter a prompt first."
|
| 78 |
|
| 79 |
+
if randomize_seed or int(seed) == 0:
|
| 80 |
seed = random.randint(0, MAX_SEED)
|
| 81 |
+
else:
|
| 82 |
+
seed = int(seed) % MAX_SEED
|
| 83 |
|
| 84 |
+
# load model (may download on first run)
|
| 85 |
+
try:
|
| 86 |
+
pipe = load_pipeline()
|
| 87 |
+
except Exception as e:
|
| 88 |
+
return None, f"Model load error: {e}"
|
| 89 |
|
| 90 |
+
# Cap sizes for CPU safety
|
| 91 |
+
width = int(min(max(256, width), 512))
|
| 92 |
+
height = int(min(max(256, height), 512))
|
| 93 |
+
steps = int(min(max(4, num_inference_steps), 20))
|
| 94 |
|
| 95 |
+
gen = torch.Generator(device=device).manual_seed(seed)
|
|
|
|
|
|
|
| 96 |
|
| 97 |
try:
|
| 98 |
+
out = pipe(
|
| 99 |
prompt=prompt,
|
| 100 |
+
negative_prompt=(negative_prompt or DEFAULT_NEG),
|
| 101 |
width=width,
|
| 102 |
height=height,
|
| 103 |
guidance_scale=float(guidance_scale),
|
| 104 |
+
num_inference_steps=steps,
|
| 105 |
generator=gen,
|
| 106 |
)
|
| 107 |
+
image = out.images[0]
|
| 108 |
+
image = tidy_image(image, max_side=1024)
|
| 109 |
+
elapsed = time.time() - start
|
| 110 |
+
return image, f"✅ Done — Seed: {seed} • {int(elapsed)}s"
|
|
|
|
| 111 |
except Exception as e:
|
| 112 |
+
# try a lighter retry
|
| 113 |
try:
|
| 114 |
+
out = pipe(
|
| 115 |
prompt=prompt,
|
| 116 |
+
negative_prompt=(negative_prompt or DEFAULT_NEG),
|
| 117 |
width=width,
|
| 118 |
height=height,
|
| 119 |
guidance_scale=max(3.0, float(guidance_scale) - 1.0),
|
| 120 |
+
num_inference_steps=max(4, steps - 4),
|
| 121 |
generator=gen,
|
| 122 |
)
|
| 123 |
+
image = tidy_image(out.images[0], max_side=1024)
|
| 124 |
+
elapsed = time.time() - start
|
| 125 |
+
return image, f"⚠ Recovered (retry) — Seed: {seed} • {int(elapsed)}s"
|
|
|
|
| 126 |
except Exception as e2:
|
| 127 |
return None, f"Generation failed: {e2}"
|
| 128 |
|
| 129 |
+
# ---------- UI (Gradio Blocks) ----------
|
| 130 |
css = """
|
| 131 |
+
/* Gradient page background */
|
| 132 |
+
body { background: linear-gradient(120deg,#f6f0ff 0%, #fff9f0 100%); }
|
| 133 |
+
/* Card styling */
|
| 134 |
+
.header { text-align: left; padding: 12px 18px; border-radius: 12px; background: linear-gradient(90deg,#ffd6e0,#ffe8a1); box-shadow: 0 6px 20px rgba(0,0,0,0.06); }
|
| 135 |
+
.brand { font-weight: 800; font-size: 20px; letter-spacing: 0.2px; color: #5b1e72; }
|
| 136 |
+
.subtitle { color:#333333; font-size:13px; margin-top:4px; }
|
| 137 |
+
.controls { background: white; padding: 12px; border-radius: 10px; box-shadow: 0 4px 18px rgba(0,0,0,0.04); }
|
| 138 |
+
.small { font-size:12px; color:#666; }
|
| 139 |
+
.btn-primary { background: linear-gradient(90deg,#ff7ab6,#ffb86b); color: white; font-weight:700; }
|
| 140 |
+
.footer { font-size:12px; color:#666; text-align:center; margin-top:8px; }
|
| 141 |
"""
|
| 142 |
|
| 143 |
examples = [
|
| 144 |
+
"anime girl standing on a cherry-blossom bridge at sunset, cinematic lighting, detailed eyes",
|
| 145 |
+
"young samurai on a misty mountain path, dramatic clouds, anime style",
|
| 146 |
+
"cozy studio apartment with anime character reading by window, warm lighting"
|
| 147 |
]
|
| 148 |
|
| 149 |
+
with gr.Blocks(css=css, title="MangaMorph — Anime Scene Generator") as demo:
|
| 150 |
+
with gr.Row():
|
| 151 |
+
with gr.Column(scale=2):
|
| 152 |
+
gr.HTML("<div class='header'><div class='brand'>MangaMorph</div>"
|
| 153 |
+
"<div class='subtitle'>Text → Anime image • CPU-optimized • Try 384×384 & 10 steps for speed</div></div>")
|
| 154 |
+
with gr.Box(elem_id="controls", visible=True):
|
| 155 |
+
prompt = gr.Textbox(label="Describe your anime scene", placeholder="e.g. A cyberpunk anime girl on a rainy street, neon lights...", lines=3)
|
| 156 |
+
with gr.Row():
|
| 157 |
+
run_btn = gr.Button("Generate", elem_id="run", variant="primary")
|
| 158 |
+
download_btn = gr.Button("Download", elem_id="dl", variant="secondary")
|
| 159 |
+
with gr.Accordion("Advanced settings", open=False):
|
| 160 |
+
negative = gr.Textbox(label="Negative prompt (optional)", placeholder="e.g. blurry, deformed, watermark", value=DEFAULT_NEG, lines=2)
|
| 161 |
+
with gr.Row():
|
| 162 |
+
seed = gr.Number(label="Seed (0 = random)", value=0)
|
| 163 |
+
randomize = gr.Checkbox(label="Randomize seed", value=True)
|
| 164 |
+
with gr.Row():
|
| 165 |
+
width = gr.Slider(label="Width", minimum=256, maximum=512, step=64, value=DEFAULT_WIDTH)
|
| 166 |
+
height = gr.Slider(label="Height", minimum=256, maximum=512, step=64, value=DEFAULT_HEIGHT)
|
| 167 |
+
with gr.Row():
|
| 168 |
+
guidance = gr.Slider(label="Guidance scale", minimum=1.0, maximum=12.0, step=0.1, value=DEFAULT_GUIDANCE)
|
| 169 |
+
steps = gr.Slider(label="Steps", minimum=4, maximum=20, step=1, value=DEFAULT_STEPS)
|
| 170 |
+
gr.Examples(examples=examples, inputs=[prompt], label="Try examples")
|
| 171 |
+
status = gr.Textbox(label="Status", value="Ready", interactive=False)
|
| 172 |
+
with gr.Column(scale=1):
|
| 173 |
+
gr.HTML("<div style='padding:8px;text-align:center;'><b>Preview</b></div>")
|
| 174 |
+
result = gr.Image(label="Generated image", shape=(384,384))
|
| 175 |
+
gallery = gr.Gallery(label="History (latest first)", columns=1).style(height="auto")
|
| 176 |
+
gr.HTML("<div class='footer'>Tip: Use lower resolution & fewer steps for much faster results on CPU</div>")
|
| 177 |
+
|
| 178 |
+
# Click behavior
|
| 179 |
+
def generate_and_update(*args):
|
| 180 |
+
img, msg = infer(*args)
|
| 181 |
+
# manage gallery: return [img] to gallery; status msg to status
|
| 182 |
+
return img, msg, [img] if img is not None else [], img
|
| 183 |
|
| 184 |
run_btn.click(
|
| 185 |
+
fn=generate_and_update,
|
| 186 |
inputs=[prompt, negative, seed, randomize, width, height, guidance, steps],
|
| 187 |
+
outputs=[result, status, gallery, result],
|
| 188 |
+
show_progress=True,
|
| 189 |
)
|
| 190 |
|
| 191 |
+
# Download button behaviour: downloads currently previewed image
|
| 192 |
+
def download_current(img):
|
| 193 |
+
if img is None:
|
| 194 |
+
return gr.update(value=None)
|
| 195 |
+
# return PIL image to trigger download
|
| 196 |
+
return img
|
| 197 |
+
|
| 198 |
+
download_btn.click(fn=download_current, inputs=[result], outputs=[result])
|
| 199 |
+
|
| 200 |
if __name__ == "__main__":
|
| 201 |
demo.launch()
|