Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -1,9 +1,8 @@
|
|
| 1 |
-
# app.py (CPU-optimized
|
| 2 |
-
# Neurale netwerken als tekenaars β ControlNet (scribble)
|
| 3 |
-
#
|
| 4 |
-
# -
|
| 5 |
-
# -
|
| 6 |
-
# - CPU optimalisaties: 512px, DPMSolver, 12 steps, attention_slicing, single-queue
|
| 7 |
|
| 8 |
import os, random, numpy as np
|
| 9 |
from typing import Optional
|
|
@@ -16,12 +15,14 @@ from diffusers import (
|
|
| 16 |
DPMSolverMultistepScheduler,
|
| 17 |
)
|
| 18 |
|
|
|
|
| 19 |
MODEL_BASE = os.environ.get("SD_BASE_MODEL", "runwayml/stable-diffusion-v1-5")
|
| 20 |
MODEL_CN = os.environ.get("CN_SCRIBBLE_MODEL", "lllyasviel/sd-controlnet-scribble")
|
| 21 |
|
| 22 |
-
DEVICE = "cpu"
|
| 23 |
DTYPE = torch.float32
|
| 24 |
-
TARGET_SIZE = 512
|
|
|
|
| 25 |
|
| 26 |
pipe: Optional[StableDiffusionControlNetPipeline] = None
|
| 27 |
|
|
@@ -33,9 +34,8 @@ def _lazy_load_pipeline():
|
|
| 33 |
pipe = StableDiffusionControlNetPipeline.from_pretrained(
|
| 34 |
MODEL_BASE, controlnet=controlnet, torch_dtype=DTYPE, safety_checker=None
|
| 35 |
)
|
| 36 |
-
# Snellere scheduler
|
| 37 |
pipe.scheduler = DPMSolverMultistepScheduler.from_config(pipe.scheduler.config)
|
| 38 |
-
# CPU-optimalisaties
|
| 39 |
pipe = pipe.to(DEVICE)
|
| 40 |
pipe.enable_attention_slicing()
|
| 41 |
return pipe
|
|
@@ -45,11 +45,9 @@ def to_rgba(img):
|
|
| 45 |
if img is None:
|
| 46 |
return None
|
| 47 |
if isinstance(img, np.ndarray):
|
| 48 |
-
# Sketchpad geeft HxWx4 (RGBA) of HxWx3 terug
|
| 49 |
if img.dtype != np.uint8:
|
| 50 |
img = img.astype(np.uint8)
|
| 51 |
-
|
| 52 |
-
return pil.convert("RGBA")
|
| 53 |
if isinstance(img, Image.Image):
|
| 54 |
return img.convert("RGBA")
|
| 55 |
try:
|
|
@@ -58,28 +56,29 @@ def to_rgba(img):
|
|
| 58 |
return None
|
| 59 |
|
| 60 |
def rgba_to_scribble(img) -> Image.Image:
|
| 61 |
-
"""Zet RGBA schets om naar zwart-op-wit
|
| 62 |
img = to_rgba(img)
|
| 63 |
if img is None:
|
| 64 |
return None
|
| 65 |
# transparant β wit
|
| 66 |
-
bg = Image.new("RGBA", img.size, (255,255,255,255))
|
| 67 |
img = Image.alpha_composite(bg, img)
|
| 68 |
# grijs + drempel
|
| 69 |
arr = np.array(img.convert("L"))
|
| 70 |
-
thr = max(25, int(arr.mean()*0.9))
|
| 71 |
lines = arr < thr
|
| 72 |
-
out = np.full((*arr.shape,3), 255, dtype=np.uint8)
|
| 73 |
-
out[lines] = (0,0,0)
|
| 74 |
scribble = Image.fromarray(out, "RGB")
|
| 75 |
# vierkant + resize
|
| 76 |
m = max(scribble.width, scribble.height)
|
| 77 |
-
sq = Image.new("RGB", (m,m), (255,255,255))
|
| 78 |
-
sq.paste(scribble, ((m-scribble.width)//2, (m-scribble.height)//2))
|
| 79 |
return sq.resize((TARGET_SIZE, TARGET_SIZE), Image.BICUBIC)
|
| 80 |
|
| 81 |
-
def run(drawing, prompt, negative_prompt,
|
| 82 |
-
|
|
|
|
| 83 |
if drawing is None:
|
| 84 |
raise gr.Error("Teken eerst iets of gebruik de voorbeeld-schets.")
|
| 85 |
cn_image = rgba_to_scribble(drawing)
|
|
@@ -89,12 +88,14 @@ def run(drawing, prompt, negative_prompt, guidance_scale=7.0,
|
|
| 89 |
if not prompt or not str(prompt).strip():
|
| 90 |
prompt = "clean pencil sketch, coherent completion, subtle shading, high quality"
|
| 91 |
if seed is None or int(seed) < 0:
|
| 92 |
-
seed = random.randint(0, 2**31-1)
|
| 93 |
gen = torch.Generator(device=DEVICE).manual_seed(int(seed))
|
| 94 |
|
| 95 |
p = _lazy_load_pipeline()
|
| 96 |
-
full_prompt = (
|
| 97 |
-
|
|
|
|
|
|
|
| 98 |
result = p(
|
| 99 |
prompt=full_prompt,
|
| 100 |
negative_prompt=negative_prompt or "text, watermark, extra limbs, low quality, distorted face, nsfw",
|
|
@@ -106,14 +107,18 @@ def run(drawing, prompt, negative_prompt, guidance_scale=7.0,
|
|
| 106 |
)
|
| 107 |
image = result.images[0]
|
| 108 |
pad = 12
|
| 109 |
-
combo = Image.new("RGB", (cn_image.width*2+pad, cn_image.height), (245,245,245))
|
| 110 |
-
combo.paste(cn_image, (0,
|
|
|
|
| 111 |
return image, cn_image, combo, f"Seed: {seed}, steps: {num_inference_steps}"
|
| 112 |
|
| 113 |
def make_example_scribble() -> Image.Image:
|
|
|
|
| 114 |
w, h = 360, 360
|
| 115 |
-
img = Image.new("RGBA", (w,h), (0,0,0,0))
|
| 116 |
-
d.
|
|
|
|
|
|
|
| 117 |
fill=(0,0,0,255), width=12)
|
| 118 |
d.line([(200,140),(210,110)], fill=(0,0,0,255), width=10)
|
| 119 |
d.line([(210,110),(250,120),(265,150)], fill=(0,0,0,255), width=9)
|
|
@@ -121,8 +126,9 @@ def make_example_scribble() -> Image.Image:
|
|
| 121 |
|
| 122 |
CSS = ".gradio-container{max-width:1100px} #combo img{border-radius:10px}"
|
| 123 |
|
| 124 |
-
with gr.Blocks(title="ποΈ Neurale netwerken als tekenaars (CPU
|
| 125 |
gr.Markdown("## ποΈ Neurale netwerken als tekenaars (CPU)\nTeken links of klik **Voorbeeld-schets**; AI vult aan met Stable Diffusion + ControlNet (scribble).")
|
|
|
|
| 126 |
with gr.Row():
|
| 127 |
with gr.Column():
|
| 128 |
canvas = gr.Sketchpad(label="Schets (teken in zwart op transparant/wit)", height=420)
|
|
@@ -131,20 +137,21 @@ with gr.Blocks(title="ποΈ Neurale netwerken als tekenaars (CPU v3)", css=CSS
|
|
| 131 |
with gr.Accordion("Geavanceerd", open=False):
|
| 132 |
guidance_scale = gr.Slider(5.0, 10.0, value=7.0, step=0.5, label="Guidance scale")
|
| 133 |
cn_strength = gr.Slider(0.6, 1.6, value=1.0, step=0.05, label="ControlNet conditioning scale")
|
| 134 |
-
steps = gr.Slider(6, 25, value=
|
| 135 |
seed = gr.Number(value=-1, precision=0, label="Seed (-1 = random)")
|
| 136 |
with gr.Row():
|
| 137 |
run_btn = gr.Button("Vervolledig β¨", variant="primary")
|
| 138 |
example_btn = gr.Button("Voorbeeld-schets", variant="secondary")
|
|
|
|
| 139 |
with gr.Column():
|
| 140 |
-
out_img
|
| 141 |
-
cn_prev
|
| 142 |
-
combo
|
| 143 |
-
meta
|
| 144 |
|
| 145 |
def fill_and_run(p, n, gs, cs, st, sd):
|
| 146 |
img = make_example_scribble()
|
| 147 |
-
a,b,c,d = run(img, p, n, gs, cs, st, sd)
|
| 148 |
return img, a, b, c, d
|
| 149 |
|
| 150 |
run_btn.click(run, inputs=[canvas, prompt, negative_prompt, guidance_scale, cn_strength, steps, seed],
|
|
@@ -152,7 +159,7 @@ with gr.Blocks(title="ποΈ Neurale netwerken als tekenaars (CPU v3)", css=CSS
|
|
| 152 |
example_btn.click(fill_and_run, inputs=[prompt, negative_prompt, guidance_scale, cn_strength, steps, seed],
|
| 153 |
outputs=[canvas, out_img, cn_prev, combo, meta])
|
| 154 |
|
| 155 |
-
#
|
| 156 |
-
|
| 157 |
if __name__ == "__main__":
|
| 158 |
demo.launch()
|
|
|
|
| 1 |
+
# app.py (v4, CPU-optimized + brede Gradio-compat)
|
| 2 |
+
# Neurale netwerken als tekenaars β Stable Diffusion + ControlNet (scribble)
|
| 3 |
+
# - Werkt op CPU (langzamer dan GPU, maar demowaardig)
|
| 4 |
+
# - Gebruikt gr.Sketchpad (compatibel met oudere Gradio-versies)
|
| 5 |
+
# - Geen queue-parameters (demo.queue() zonder args)
|
|
|
|
| 6 |
|
| 7 |
import os, random, numpy as np
|
| 8 |
from typing import Optional
|
|
|
|
| 15 |
DPMSolverMultistepScheduler,
|
| 16 |
)
|
| 17 |
|
| 18 |
+
# Modellen (kun je via Space Secrets/Opsional env-vars overriden)
|
| 19 |
MODEL_BASE = os.environ.get("SD_BASE_MODEL", "runwayml/stable-diffusion-v1-5")
|
| 20 |
MODEL_CN = os.environ.get("CN_SCRIBBLE_MODEL", "lllyasviel/sd-controlnet-scribble")
|
| 21 |
|
| 22 |
+
DEVICE = "cpu" # geforceerd CPU
|
| 23 |
DTYPE = torch.float32
|
| 24 |
+
TARGET_SIZE = 512 # lager = sneller op CPU
|
| 25 |
+
DEFAULT_STEPS = 12 # CPU-vriendelijk
|
| 26 |
|
| 27 |
pipe: Optional[StableDiffusionControlNetPipeline] = None
|
| 28 |
|
|
|
|
| 34 |
pipe = StableDiffusionControlNetPipeline.from_pretrained(
|
| 35 |
MODEL_BASE, controlnet=controlnet, torch_dtype=DTYPE, safety_checker=None
|
| 36 |
)
|
| 37 |
+
# Snellere scheduler β minder stappen nodig
|
| 38 |
pipe.scheduler = DPMSolverMultistepScheduler.from_config(pipe.scheduler.config)
|
|
|
|
| 39 |
pipe = pipe.to(DEVICE)
|
| 40 |
pipe.enable_attention_slicing()
|
| 41 |
return pipe
|
|
|
|
| 45 |
if img is None:
|
| 46 |
return None
|
| 47 |
if isinstance(img, np.ndarray):
|
|
|
|
| 48 |
if img.dtype != np.uint8:
|
| 49 |
img = img.astype(np.uint8)
|
| 50 |
+
return Image.fromarray(img).convert("RGBA")
|
|
|
|
| 51 |
if isinstance(img, Image.Image):
|
| 52 |
return img.convert("RGBA")
|
| 53 |
try:
|
|
|
|
| 56 |
return None
|
| 57 |
|
| 58 |
def rgba_to_scribble(img) -> Image.Image:
|
| 59 |
+
"""Zet RGBA schets om naar zwart-op-wit, vierkant en resized (TARGET_SIZE)."""
|
| 60 |
img = to_rgba(img)
|
| 61 |
if img is None:
|
| 62 |
return None
|
| 63 |
# transparant β wit
|
| 64 |
+
bg = Image.new("RGBA", img.size, (255, 255, 255, 255))
|
| 65 |
img = Image.alpha_composite(bg, img)
|
| 66 |
# grijs + drempel
|
| 67 |
arr = np.array(img.convert("L"))
|
| 68 |
+
thr = max(25, int(arr.mean() * 0.9))
|
| 69 |
lines = arr < thr
|
| 70 |
+
out = np.full((*arr.shape, 3), 255, dtype=np.uint8)
|
| 71 |
+
out[lines] = (0, 0, 0)
|
| 72 |
scribble = Image.fromarray(out, "RGB")
|
| 73 |
# vierkant + resize
|
| 74 |
m = max(scribble.width, scribble.height)
|
| 75 |
+
sq = Image.new("RGB", (m, m), (255, 255, 255))
|
| 76 |
+
sq.paste(scribble, ((m - scribble.width) // 2, (m - scribble.height) // 2))
|
| 77 |
return sq.resize((TARGET_SIZE, TARGET_SIZE), Image.BICUBIC)
|
| 78 |
|
| 79 |
+
def run(drawing, prompt, negative_prompt,
|
| 80 |
+
guidance_scale=7.0, controlnet_conditioning_scale=1.0,
|
| 81 |
+
num_inference_steps=DEFAULT_STEPS, seed=-1):
|
| 82 |
if drawing is None:
|
| 83 |
raise gr.Error("Teken eerst iets of gebruik de voorbeeld-schets.")
|
| 84 |
cn_image = rgba_to_scribble(drawing)
|
|
|
|
| 88 |
if not prompt or not str(prompt).strip():
|
| 89 |
prompt = "clean pencil sketch, coherent completion, subtle shading, high quality"
|
| 90 |
if seed is None or int(seed) < 0:
|
| 91 |
+
seed = random.randint(0, 2**31 - 1)
|
| 92 |
gen = torch.Generator(device=DEVICE).manual_seed(int(seed))
|
| 93 |
|
| 94 |
p = _lazy_load_pipeline()
|
| 95 |
+
full_prompt = (
|
| 96 |
+
"black pencil sketch, clean lines, coherent shape completion, subtle shading, "
|
| 97 |
+
"consistent with the input scribble, " + prompt
|
| 98 |
+
)
|
| 99 |
result = p(
|
| 100 |
prompt=full_prompt,
|
| 101 |
negative_prompt=negative_prompt or "text, watermark, extra limbs, low quality, distorted face, nsfw",
|
|
|
|
| 107 |
)
|
| 108 |
image = result.images[0]
|
| 109 |
pad = 12
|
| 110 |
+
combo = Image.new("RGB", (cn_image.width * 2 + pad, cn_image.height), (245, 245, 245))
|
| 111 |
+
combo.paste(cn_image, (0, 0))
|
| 112 |
+
combo.paste(image, (cn_image.width + pad, 0))
|
| 113 |
return image, cn_image, combo, f"Seed: {seed}, steps: {num_inference_steps}"
|
| 114 |
|
| 115 |
def make_example_scribble() -> Image.Image:
|
| 116 |
+
"""Kleine voorbeeldschets (appelvorm) voor snelle test."""
|
| 117 |
w, h = 360, 360
|
| 118 |
+
img = Image.new("RGBA", (w, h), (0, 0, 0, 0))
|
| 119 |
+
d = ImageDraw.Draw(img)
|
| 120 |
+
d.line([(100,300),(90,230),(110,170),(160,140),(200,140),
|
| 121 |
+
(260,170),(280,230),(260,300),(180,320),(100,300)],
|
| 122 |
fill=(0,0,0,255), width=12)
|
| 123 |
d.line([(200,140),(210,110)], fill=(0,0,0,255), width=10)
|
| 124 |
d.line([(210,110),(250,120),(265,150)], fill=(0,0,0,255), width=9)
|
|
|
|
| 126 |
|
| 127 |
CSS = ".gradio-container{max-width:1100px} #combo img{border-radius:10px}"
|
| 128 |
|
| 129 |
+
with gr.Blocks(title="ποΈ Neurale netwerken als tekenaars (CPU v4)", css=CSS) as demo:
|
| 130 |
gr.Markdown("## ποΈ Neurale netwerken als tekenaars (CPU)\nTeken links of klik **Voorbeeld-schets**; AI vult aan met Stable Diffusion + ControlNet (scribble).")
|
| 131 |
+
|
| 132 |
with gr.Row():
|
| 133 |
with gr.Column():
|
| 134 |
canvas = gr.Sketchpad(label="Schets (teken in zwart op transparant/wit)", height=420)
|
|
|
|
| 137 |
with gr.Accordion("Geavanceerd", open=False):
|
| 138 |
guidance_scale = gr.Slider(5.0, 10.0, value=7.0, step=0.5, label="Guidance scale")
|
| 139 |
cn_strength = gr.Slider(0.6, 1.6, value=1.0, step=0.05, label="ControlNet conditioning scale")
|
| 140 |
+
steps = gr.Slider(6, 25, value=DEFAULT_STEPS, step=1, label="Aantal diffusion-steps")
|
| 141 |
seed = gr.Number(value=-1, precision=0, label="Seed (-1 = random)")
|
| 142 |
with gr.Row():
|
| 143 |
run_btn = gr.Button("Vervolledig β¨", variant="primary")
|
| 144 |
example_btn = gr.Button("Voorbeeld-schets", variant="secondary")
|
| 145 |
+
|
| 146 |
with gr.Column():
|
| 147 |
+
out_img = gr.Image(label="Vervolledigde tekening", interactive=False)
|
| 148 |
+
cn_prev = gr.Image(label="Scribble (input voor AI)", interactive=False)
|
| 149 |
+
combo = gr.Image(label="Vergelijking (links: input, rechts: AI)", elem_id="combo")
|
| 150 |
+
meta = gr.Markdown()
|
| 151 |
|
| 152 |
def fill_and_run(p, n, gs, cs, st, sd):
|
| 153 |
img = make_example_scribble()
|
| 154 |
+
a, b, c, d = run(img, p, n, gs, cs, st, sd)
|
| 155 |
return img, a, b, c, d
|
| 156 |
|
| 157 |
run_btn.click(run, inputs=[canvas, prompt, negative_prompt, guidance_scale, cn_strength, steps, seed],
|
|
|
|
| 159 |
example_btn.click(fill_and_run, inputs=[prompt, negative_prompt, guidance_scale, cn_strength, steps, seed],
|
| 160 |
outputs=[canvas, out_img, cn_prev, combo, meta])
|
| 161 |
|
| 162 |
+
demo.queue() # zonder parameters: compatibel met oudere Gradio-versies
|
| 163 |
+
|
| 164 |
if __name__ == "__main__":
|
| 165 |
demo.launch()
|