OLIVE2403 commited on
Commit
5e8abf1
·
verified ·
1 Parent(s): 7514034

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +130 -99
app.py CHANGED
@@ -1,66 +1,67 @@
1
- # app.py - MangaMorph (Gradio) CPU-friendly template
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
- # ---- CONFIG ----
12
- # Change this model id if you prefer another (see note about license/access above)
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.float32 if device == "cpu" else torch.float16
20
 
21
- # Limits / defaults for CPU-friendly runs
22
- DEFAULT_WIDTH = 512
23
- DEFAULT_HEIGHT = 512
24
- DEFAULT_STEPS = 20
25
- DEFAULT_GUIDANCE = 7.5
26
  MAX_SEED = np.iinfo(np.int32).max
27
 
28
- # Load pipeline (wrapped in try/except so error messages are shown in app log)
 
29
  def load_pipeline():
 
 
 
30
  try:
31
- scheduler = EulerDiscreteScheduler.from_pretrained(MODEL_ID, subfolder="scheduler") if os.getenv("USE_EULER", "1") == "1" else None
32
- pipe = DiffusionPipeline.from_pretrained(
33
- MODEL_ID,
34
- torch_dtype=torch_dtype,
35
- use_auth_token=HF_TOKEN,
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
- # For CPU: disable safety checker to avoid long CPU runs (optional)
42
  try:
43
  pipe.safety_checker = None
44
  except Exception:
45
  pass
46
- return pipe
 
47
  except Exception as e:
48
- raise RuntimeError(f"Failed to load model '{MODEL_ID}': {e}")
49
 
50
- # lazy load
51
- PIPE = None
52
- def get_pipe():
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
- if not prompt:
75
- return None, "Please enter a prompt."
 
76
 
77
- if randomize_seed:
78
  seed = random.randint(0, MAX_SEED)
 
 
79
 
80
- gen = torch.Generator(device=device)
81
- gen = gen.manual_seed(seed)
 
 
 
82
 
83
- pipe = get_pipe()
 
 
 
84
 
85
- # Cap size to avoid OOM on CPU
86
- width = min(width, 768)
87
- height = min(height, 768)
88
 
89
  try:
90
- output = pipe(
91
  prompt=prompt,
92
- negative_prompt=(negative_prompt or DEFAULT_NEGATIVE_PROMPT),
93
  width=width,
94
  height=height,
95
  guidance_scale=float(guidance_scale),
96
- num_inference_steps=int(num_inference_steps),
97
  generator=gen,
98
  )
99
- image = output.images[0]
100
- # simple postprocessing: convert to RGB and return
101
- if isinstance(image, Image.Image):
102
- image = image.convert("RGB")
103
- return image, f"Seed: {seed}"
104
  except Exception as e:
105
- # retry logic: try again with smaller steps/guidance if CPU fails
106
  try:
107
- output = pipe(
108
  prompt=prompt,
109
- negative_prompt=(negative_prompt or DEFAULT_NEGATIVE_PROMPT),
110
  width=width,
111
  height=height,
112
  guidance_scale=max(3.0, float(guidance_scale) - 1.0),
113
- num_inference_steps=max(5, int(num_inference_steps) - 5),
114
  generator=gen,
115
  )
116
- image = output.images[0]
117
- if isinstance(image, Image.Image):
118
- image = image.convert("RGB")
119
- return image, f"Recovered (retry) — Seed: {seed}"
120
  except Exception as e2:
121
  return None, f"Generation failed: {e2}"
122
 
123
- # ---- UI ----
124
  css = """
125
- #main { max-width: 880px; margin: auto; }
126
- .header { text-align: center; }
127
- .small { font-size: 0.9rem; color: #666; }
 
 
 
 
 
 
 
128
  """
129
 
130
  examples = [
131
- "A young anime girl standing in a rain-soaked neon street, detailed lighting, cinematic",
132
- "A samurai in traditional armor on a cliff at sunset, dramatic lighting, anime style",
133
- "Cozy room with anime character by window reading, soft warm light"
134
  ]
135
 
136
- with gr.Blocks(css=css, theme=gr.themes.Default()) as demo:
137
- with gr.Column(elem_id="main"):
138
- gr.Markdown("<div class='header'><h2>MangaMorph — Anime Scene Generator</h2>"
139
- "<div class='small'>Text → Anime image | CPU-optimized | Use Model ID or set HF token in Secrets</div></div>")
140
-
141
- with gr.Row():
142
- prompt = gr.Textbox(label="Prompt", placeholder="Describe the anime scene you want...", lines=2)
143
- run_btn = gr.Button("Generate", variant="primary")
144
-
145
- with gr.Row():
146
- gallery = gr.Image(label="Result")
147
-
148
- with gr.Accordion("Advanced settings", open=False):
149
- negative = gr.Textbox(label="Negative prompt (optional)", placeholder=DEFAULT_NEGATIVE_PROMPT, lines=2, value=DEFAULT_NEGATIVE_PROMPT)
150
- seed = gr.Number(label="Seed (0 = randomize)", value=0)
151
- randomize = gr.Checkbox(label="Randomize seed", value=True)
152
- with gr.Row():
153
- width = gr.Slider(label="Width", minimum=256, maximum=768, step=64, value=DEFAULT_WIDTH)
154
- height = gr.Slider(label="Height", minimum=256, maximum=768, step=64, value=DEFAULT_HEIGHT)
155
- with gr.Row():
156
- guidance = gr.Slider(label="Guidance scale", minimum=1.0, maximum=15.0, step=0.1, value=DEFAULT_GUIDANCE)
157
- steps = gr.Slider(label="Steps", minimum=5, maximum=50, step=1, value=DEFAULT_STEPS)
158
-
159
- gr.Examples(examples=examples, inputs=[prompt])
160
-
161
- status = gr.Textbox(label="Status / Seed", interactive=False)
 
 
 
 
 
 
 
 
162
 
163
  run_btn.click(
164
- fn=infer,
165
  inputs=[prompt, negative, seed, randomize, width, height, guidance, steps],
166
- outputs=[gallery, status],
 
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()