Spaces:
Running
on
Zero
Running
on
Zero
Tanut
commited on
Commit
Β·
6ae079b
1
Parent(s):
e20d060
upload from local
Browse files- README.md +6 -11
- app.py +272 -75
- requirements.txt +1 -1
README.md
CHANGED
@@ -1,17 +1,12 @@
|
|
1 |
---
|
2 |
-
title:
|
3 |
-
emoji:
|
4 |
-
colorFrom: green
|
5 |
-
colorTo: green
|
6 |
sdk: gradio
|
7 |
-
sdk_version:
|
8 |
-
|
9 |
-
pinned: false
|
10 |
-
license: openrail
|
11 |
-
short_description: Testing generate Images from Controlnet
|
12 |
preload_from_hub:
|
13 |
- runwayml/stable-diffusion-v1-5
|
14 |
- monster-labs/control_v1p_sd15_qrcode_monster
|
|
|
|
|
15 |
---
|
16 |
-
|
17 |
-
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
|
|
1 |
---
|
2 |
+
title: QRBlend ZeroGPU
|
3 |
+
emoji: π§©
|
|
|
|
|
4 |
sdk: gradio
|
5 |
+
sdk_version: 4.44.0
|
6 |
+
python_version: 3.10.13
|
|
|
|
|
|
|
7 |
preload_from_hub:
|
8 |
- runwayml/stable-diffusion-v1-5
|
9 |
- monster-labs/control_v1p_sd15_qrcode_monster
|
10 |
+
- latentcat/control_v1p_sd15_brightness
|
11 |
+
license: openrail++
|
12 |
---
|
|
|
|
app.py
CHANGED
@@ -1,94 +1,291 @@
|
|
1 |
-
import
|
|
|
|
|
|
|
|
|
2 |
import gradio as gr
|
3 |
-
from PIL import Image
|
4 |
import qrcode
|
5 |
from qrcode.constants import ERROR_CORRECT_H
|
6 |
|
7 |
import torch
|
8 |
-
from diffusers import
|
9 |
-
|
10 |
-
|
11 |
-
|
12 |
-
|
13 |
-
|
14 |
-
|
15 |
-
|
16 |
-
|
17 |
-
|
18 |
-
|
19 |
-
|
20 |
-
|
21 |
-
|
22 |
-
|
23 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
24 |
)
|
25 |
-
|
26 |
-
|
27 |
-
|
28 |
-
|
29 |
-
|
30 |
-
|
31 |
-
|
32 |
-
|
33 |
-
|
34 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
35 |
)
|
36 |
-
|
37 |
-
|
38 |
-
|
39 |
-
|
|
|
|
|
|
|
|
|
|
|
40 |
return img
|
41 |
|
42 |
-
|
43 |
-
|
44 |
-
|
45 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
46 |
):
|
47 |
-
|
48 |
-
if seed is None or seed < 0:
|
49 |
-
seed = random.randint(0, 2**31 - 1)
|
50 |
-
generator = torch.Generator(device="cuda").manual_seed(seed)
|
51 |
|
52 |
-
|
|
|
|
|
53 |
|
54 |
-
|
55 |
-
|
56 |
-
|
57 |
-
|
58 |
-
|
59 |
-
|
60 |
-
|
61 |
-
|
62 |
-
|
63 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
64 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
65 |
img = out.images[0]
|
66 |
-
return img, seed
|
67 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
68 |
with gr.Blocks() as demo:
|
69 |
-
gr.Markdown("
|
70 |
-
|
71 |
-
with gr.
|
72 |
-
with gr.
|
73 |
-
|
74 |
-
|
75 |
-
|
76 |
-
|
77 |
-
|
78 |
-
|
79 |
-
|
80 |
-
|
81 |
-
|
82 |
-
|
83 |
-
|
84 |
-
|
85 |
-
|
86 |
-
|
87 |
-
|
88 |
-
|
89 |
-
|
90 |
-
|
91 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
92 |
|
93 |
if __name__ == "__main__":
|
94 |
demo.launch()
|
|
|
1 |
+
import os, re, gc, random
|
2 |
+
import numpy as np
|
3 |
+
from contextlib import nullcontext
|
4 |
+
from typing import Tuple
|
5 |
+
|
6 |
import gradio as gr
|
7 |
+
from PIL import Image, ImageFilter
|
8 |
import qrcode
|
9 |
from qrcode.constants import ERROR_CORRECT_H
|
10 |
|
11 |
import torch
|
12 |
+
from diffusers import (
|
13 |
+
StableDiffusionPipeline,
|
14 |
+
StableDiffusionControlNetImg2ImgPipeline,
|
15 |
+
ControlNetModel,
|
16 |
+
DPMSolverMultistepScheduler,
|
17 |
+
)
|
18 |
+
import spaces # ZeroGPU decorator
|
19 |
+
|
20 |
+
# =========================================================
|
21 |
+
# Auth (optional for private models)
|
22 |
+
# =========================================================
|
23 |
+
hf_token = os.getenv("HF_TOKEN")
|
24 |
+
AUTH_KW = {"token": hf_token} if hf_token else {}
|
25 |
+
|
26 |
+
# =========================================================
|
27 |
+
# Helpers (untouched logic)
|
28 |
+
# =========================================================
|
29 |
+
def normalize_color(c):
|
30 |
+
if c is None: return "white"
|
31 |
+
if isinstance(c, (tuple, list)):
|
32 |
+
r, g, b = (int(max(0, min(255, round(float(x))))) for x in c[:3]); return (r, g, b)
|
33 |
+
if isinstance(c, str):
|
34 |
+
s = c.strip()
|
35 |
+
if s.startswith("#"): return s
|
36 |
+
m = re.match(r"rgba?\(\s*([0-9.]+)\s*,\s*([0-9.]+)\s*,\s*([0-9.]+)", s, re.IGNORECASE)
|
37 |
+
if m:
|
38 |
+
r = int(max(0, min(255, round(float(m.group(1))))))
|
39 |
+
g = int(max(0, min(255, round(float(m.group(2))))))
|
40 |
+
b = int(max(0, min(255, round(float(m.group(3))))))
|
41 |
+
return (r, g, b)
|
42 |
+
return s
|
43 |
+
return "white"
|
44 |
+
|
45 |
+
def strengthen_qr_prompts(pos: str, neg: str) -> Tuple[str, str]:
|
46 |
+
# DONβT say βQR codeβ here β let ControlNet impose it
|
47 |
+
pos = (pos or "").strip()
|
48 |
+
neg = (neg or "").strip()
|
49 |
+
pos2 = f"{pos}, high contrast lighting, clean details, cohesive composition".strip(", ")
|
50 |
+
add_neg = "frame, border, ornate frame, watermark, text, numbers, checkerboard, mosaic, halftone, repeated pattern, glitch"
|
51 |
+
neg2 = (neg + (", " if neg else "") + add_neg).strip(", ").strip()
|
52 |
+
return pos2, neg2
|
53 |
+
|
54 |
+
def enforce_qr_contrast(stylized: Image.Image, qr_img: Image.Image, strength: float = 0.6, feather: float = 1.0) -> Image.Image:
|
55 |
+
if strength <= 0: return stylized
|
56 |
+
q = qr_img.convert("L")
|
57 |
+
black_mask = q.point(lambda p: 255 if p < 128 else 0).filter(ImageFilter.GaussianBlur(radius=float(feather)))
|
58 |
+
black = np.asarray(black_mask, dtype=np.float32) / 255.0
|
59 |
+
white = 1.0 - black
|
60 |
+
s = np.asarray(stylized.convert("RGB"), dtype=np.float32) / 255.0
|
61 |
+
s = s * (1.0 - float(strength) * black[..., None])
|
62 |
+
s = s + (1.0 - s) * (float(strength) * 0.85 * white[..., None])
|
63 |
+
s = np.clip(s, 0.0, 1.0)
|
64 |
+
return Image.fromarray((s * 255.0).astype(np.uint8), mode="RGB")
|
65 |
+
|
66 |
+
# =========================================================
|
67 |
+
# Models & loading (ZeroGPU-friendly lazy load)
|
68 |
+
# =========================================================
|
69 |
+
BASE_15 = "runwayml/stable-diffusion-v1-5"
|
70 |
+
QR_MONSTER_15 = "monster-labs/control_v1p_sd15_qrcode_monster" # v2 subfolder is handled by authors; base path is fine
|
71 |
+
BRIGHTNESS_15 = "latentcat/control_v1p_sd15_brightness" # optional helper
|
72 |
+
|
73 |
+
_sd = {"pipe": None}
|
74 |
+
_cn = {"pipe": None}
|
75 |
+
|
76 |
+
def _setup_scheduler(pipe):
|
77 |
+
pipe.scheduler = DPMSolverMultistepScheduler.from_config(
|
78 |
+
pipe.scheduler.config,
|
79 |
+
use_karras_sigmas=True,
|
80 |
+
algorithm_type="dpmsolver++"
|
81 |
)
|
82 |
+
|
83 |
+
def _enable_memory_savers(pipe):
|
84 |
+
# Good defaults for Spaces/ZeroGPU
|
85 |
+
pipe.enable_attention_slicing()
|
86 |
+
pipe.enable_vae_slicing()
|
87 |
+
pipe.enable_vae_tiling()
|
88 |
+
pipe.enable_model_cpu_offload()
|
89 |
+
|
90 |
+
def _load_sd_txt2img():
|
91 |
+
if _sd["pipe"] is None:
|
92 |
+
pipe = StableDiffusionPipeline.from_pretrained(
|
93 |
+
BASE_15,
|
94 |
+
torch_dtype=torch.float16,
|
95 |
+
safety_checker=None,
|
96 |
+
use_safetensors=True,
|
97 |
+
low_cpu_mem_usage=True,
|
98 |
+
**AUTH_KW
|
99 |
+
)
|
100 |
+
_setup_scheduler(pipe)
|
101 |
+
_enable_memory_savers(pipe)
|
102 |
+
_sd["pipe"] = pipe
|
103 |
+
return _sd["pipe"]
|
104 |
+
|
105 |
+
def _load_cn_img2img():
|
106 |
+
if _cn["pipe"] is None:
|
107 |
+
qrnet = ControlNetModel.from_pretrained(
|
108 |
+
QR_MONSTER_15, torch_dtype=torch.float16, use_safetensors=True, **AUTH_KW
|
109 |
+
)
|
110 |
+
bright = ControlNetModel.from_pretrained(
|
111 |
+
BRIGHTNESS_15, torch_dtype=torch.float16, use_safetensors=True, **AUTH_KW
|
112 |
+
)
|
113 |
+
pipe = StableDiffusionControlNetImg2ImgPipeline.from_pretrained(
|
114 |
+
BASE_15,
|
115 |
+
controlnet=[qrnet, bright],
|
116 |
+
torch_dtype=torch.float16,
|
117 |
+
safety_checker=None,
|
118 |
+
use_safetensors=True,
|
119 |
+
low_cpu_mem_usage=True,
|
120 |
+
**AUTH_KW
|
121 |
+
)
|
122 |
+
_setup_scheduler(pipe)
|
123 |
+
_enable_memory_savers(pipe)
|
124 |
+
_cn["pipe"] = pipe
|
125 |
+
return _cn["pipe"]
|
126 |
+
|
127 |
+
# =========================================================
|
128 |
+
# Generation utilities (use inside @spaces.GPU)
|
129 |
+
# =========================================================
|
130 |
+
def sd_generate(prompt, negative, steps, guidance, seed, size=512):
|
131 |
+
pipe = _load_sd_txt2img()
|
132 |
+
# Reproducible generator β on GPU if available
|
133 |
+
gen = torch.Generator(device="cuda" if torch.cuda.is_available() else "cpu")
|
134 |
+
if int(seed) != 0:
|
135 |
+
gen = gen.manual_seed(int(seed))
|
136 |
+
else:
|
137 |
+
gen = gen.manual_seed(random.randint(0, 2**31 - 1))
|
138 |
+
|
139 |
+
if torch.cuda.is_available():
|
140 |
+
torch.cuda.empty_cache()
|
141 |
+
gc.collect()
|
142 |
+
|
143 |
+
out = pipe(
|
144 |
+
prompt=prompt,
|
145 |
+
negative_prompt=negative or "",
|
146 |
+
num_inference_steps=int(steps),
|
147 |
+
guidance_scale=float(guidance),
|
148 |
+
width=int(size), height=int(size),
|
149 |
+
generator=gen
|
150 |
)
|
151 |
+
return out.images[0]
|
152 |
+
|
153 |
+
def make_qr(url="http://www.mybirdfire.com", size=512, border=10, back_color="#808080", blur_radius=0.0):
|
154 |
+
qr = qrcode.QRCode(version=None, error_correction=ERROR_CORRECT_H, box_size=10, border=int(border))
|
155 |
+
qr.add_data(url.strip()); qr.make(fit=True)
|
156 |
+
bg = normalize_color(back_color)
|
157 |
+
img = qr.make_image(fill_color="black", back_color=bg).convert("RGB").resize((size, size), Image.NEAREST)
|
158 |
+
if blur_radius and blur_radius > 0:
|
159 |
+
img = img.filter(ImageFilter.GaussianBlur(radius=float(blur_radius)))
|
160 |
return img
|
161 |
|
162 |
+
NEG_DEFAULT = "lowres, low contrast, blurry, jpeg artifacts, worst quality, bad anatomy, extra digits"
|
163 |
+
|
164 |
+
# =========================================================
|
165 |
+
# Main two-stage generator (ZeroGPU-guarded)
|
166 |
+
# =========================================================
|
167 |
+
@spaces.GPU(duration=120) # allocate GPU only while generating
|
168 |
+
def qr_art_two_stage(
|
169 |
+
prompt, negative,
|
170 |
+
base_steps, base_cfg, base_seed,
|
171 |
+
stylize_steps, stylize_cfg, stylize_seed,
|
172 |
+
size, url, border, back_color,
|
173 |
+
denoise, qr_weight, bright_weight,
|
174 |
+
qr_start, qr_end, bright_start, bright_end,
|
175 |
+
control_blur, repair_strength, feather_px
|
176 |
):
|
177 |
+
size = max(384, int(size) // 8 * 8)
|
|
|
|
|
|
|
178 |
|
179 |
+
# Stage A: base art (txt2img)
|
180 |
+
p_pos, p_neg = strengthen_qr_prompts(prompt, negative)
|
181 |
+
base_img = sd_generate(p_pos, p_neg, base_steps, base_cfg, base_seed, size=size)
|
182 |
|
183 |
+
# Stage B: img2img + ControlNet
|
184 |
+
qr_img = make_qr(url=url, size=size, border=border, back_color=back_color, blur_radius=control_blur)
|
185 |
+
pipe = _load_cn_img2img()
|
186 |
+
|
187 |
+
if torch.cuda.is_available():
|
188 |
+
torch.cuda.empty_cache()
|
189 |
+
gc.collect()
|
190 |
+
|
191 |
+
gen = torch.Generator(device="cuda" if torch.cuda.is_available() else "cpu")
|
192 |
+
if int(stylize_seed) != 0:
|
193 |
+
gen = gen.manual_seed(int(stylize_seed))
|
194 |
+
else:
|
195 |
+
gen = gen.manual_seed(random.randint(0, 2**31 - 1))
|
196 |
+
|
197 |
+
kwargs = dict(
|
198 |
+
prompt=p_pos,
|
199 |
+
negative_prompt=p_neg or NEG_DEFAULT,
|
200 |
+
image=base_img, # init image for img2img
|
201 |
+
control_image=[qr_img, qr_img], # Monster + Brightness
|
202 |
+
strength=float(denoise), # how much we allow change
|
203 |
+
num_inference_steps=int(stylize_steps),
|
204 |
+
guidance_scale=float(stylize_cfg),
|
205 |
+
generator=gen,
|
206 |
+
controlnet_conditioning_scale=[float(qr_weight), float(bright_weight)],
|
207 |
+
width=size, height=size, # (diffusers uses init image size; harmless here)
|
208 |
)
|
209 |
+
|
210 |
+
try:
|
211 |
+
out = pipe(
|
212 |
+
**kwargs,
|
213 |
+
control_guidance_start=[float(qr_start), float(bright_start)],
|
214 |
+
control_guidance_end=[float(qr_end), float(bright_end)],
|
215 |
+
)
|
216 |
+
except TypeError:
|
217 |
+
out = pipe(
|
218 |
+
**kwargs,
|
219 |
+
controlnet_start=[float(qr_start), float(bright_start)],
|
220 |
+
controlnet_end=[float(qr_end), float(bright_end)],
|
221 |
+
)
|
222 |
+
|
223 |
img = out.images[0]
|
|
|
224 |
|
225 |
+
# Optional post repair to push blacks/whites where modules demand
|
226 |
+
img = enforce_qr_contrast(img, qr_img, strength=float(repair_strength), feather=float(feather_px))
|
227 |
+
return img, base_img, qr_img
|
228 |
+
|
229 |
+
# =========================================================
|
230 |
+
# UI (Gradio Space)
|
231 |
+
# =========================================================
|
232 |
with gr.Blocks() as demo:
|
233 |
+
gr.Markdown("## π§© QR-Code Monster β Two-Stage (txt2img β img2img + ControlNet) β ZeroGPU")
|
234 |
+
|
235 |
+
with gr.Tab("Two-Stage QR Art"):
|
236 |
+
with gr.Row():
|
237 |
+
with gr.Column():
|
238 |
+
url = gr.Textbox(label="URL/Text", value="http://www.mybirdfire.com")
|
239 |
+
prompt = gr.Textbox(
|
240 |
+
label="Style prompt (no 'QR code' here)",
|
241 |
+
value="baroque palace interior with intricate roots, cinematic, dramatic lighting, ultra detailed"
|
242 |
+
)
|
243 |
+
negative = gr.Textbox(label="Negative", value="")
|
244 |
+
size = gr.Slider(512, 1024, value=768, step=64, label="Canvas (px)")
|
245 |
+
|
246 |
+
gr.Markdown("**Stage A β Base art (txt2img)**")
|
247 |
+
base_steps = gr.Slider(10, 60, value=26, step=1, label="Base steps")
|
248 |
+
base_cfg = gr.Slider(1.0, 12.0, value=6.0, step=0.1, label="Base CFG")
|
249 |
+
base_seed = gr.Number(value=0, precision=0, label="Base seed (0=random)")
|
250 |
+
|
251 |
+
gr.Markdown("**Stage B β ControlNet img2img**")
|
252 |
+
stylize_steps = gr.Slider(10, 60, value=28, step=1, label="Stylize steps")
|
253 |
+
stylize_cfg = gr.Slider(1.0, 12.0, value=6.0, step=0.1, label="Stylize CFG")
|
254 |
+
stylize_seed = gr.Number(value=0, precision=0, label="Stylize seed (0=random)")
|
255 |
+
denoise = gr.Slider(0.1, 0.8, value=0.48, step=0.01, label="Denoising strength (keep composition lower)")
|
256 |
+
|
257 |
+
qr_weight = gr.Slider(0.5, 1.7, value=1.2, step=0.05, label="QR Monster weight")
|
258 |
+
bright_weight = gr.Slider(0.0, 1.0, value=0.20, step=0.05, label="Brightness weight")
|
259 |
+
|
260 |
+
qr_start = gr.Slider(0.0, 1.0, value=0.05, step=0.01, label="QR start")
|
261 |
+
qr_end = gr.Slider(0.0, 1.0, value=0.95, step=0.01, label="QR end")
|
262 |
+
bright_start = gr.Slider(0.0, 1.0, value=0.40, step=0.01, label="Brightness start")
|
263 |
+
bright_end = gr.Slider(0.0, 1.0, value=0.85, step=0.01, label="Brightness end")
|
264 |
+
|
265 |
+
border = gr.Slider(4, 20, value=12, step=1, label="QR border (quiet zone)")
|
266 |
+
back_color = gr.ColorPicker(value="#808080", label="QR background (mid-gray blends better)")
|
267 |
+
control_blur = gr.Slider(0.0, 3.0, value=1.2, step=0.1, label="Soften control (Gaussian blur radius)")
|
268 |
+
repair_strength = gr.Slider(0.0, 1.0, value=0.65, step=0.05, label="Post repair strength")
|
269 |
+
feather_px = gr.Slider(0.0, 3.0, value=1.0, step=0.1, label="Repair feather (px)")
|
270 |
+
|
271 |
+
go = gr.Button("Generate QR Art", variant="primary")
|
272 |
+
|
273 |
+
with gr.Column():
|
274 |
+
final_img = gr.Image(label="Final stylized QR")
|
275 |
+
base_img = gr.Image(label="Base art (Stage A)")
|
276 |
+
ctrl_img = gr.Image(label="Control image (QR used)")
|
277 |
+
|
278 |
+
go.click(
|
279 |
+
qr_art_two_stage,
|
280 |
+
inputs=[prompt, negative,
|
281 |
+
base_steps, base_cfg, base_seed,
|
282 |
+
stylize_steps, stylize_cfg, stylize_seed,
|
283 |
+
size, url, border, back_color,
|
284 |
+
denoise, qr_weight, bright_weight,
|
285 |
+
qr_start, qr_end, bright_start, bright_end,
|
286 |
+
control_blur, repair_strength, feather_px],
|
287 |
+
outputs=[final_img, base_img, ctrl_img]
|
288 |
+
)
|
289 |
|
290 |
if __name__ == "__main__":
|
291 |
demo.launch()
|
requirements.txt
CHANGED
@@ -3,9 +3,9 @@ diffusers>=0.27.2
|
|
3 |
transformers>=4.42.0
|
4 |
accelerate>=0.31.0
|
5 |
safetensors
|
6 |
-
xformers
|
7 |
gradio>=4.29.0
|
8 |
qrcode[pil]
|
9 |
Pillow
|
10 |
huggingface-hub
|
11 |
spaces
|
|
|
|
3 |
transformers>=4.42.0
|
4 |
accelerate>=0.31.0
|
5 |
safetensors
|
|
|
6 |
gradio>=4.29.0
|
7 |
qrcode[pil]
|
8 |
Pillow
|
9 |
huggingface-hub
|
10 |
spaces
|
11 |
+
numpy
|