File size: 7,265 Bytes
67fcd2f
 
 
 
 
98ce8b0
5f3c681
c58149c
 
22bea51
 
5f3c681
 
 
 
 
22bea51
67fcd2f
22bea51
5f3c681
22bea51
67fcd2f
5f3c681
67fcd2f
 
22bea51
 
 
 
 
 
 
 
 
5f3c681
22bea51
67fcd2f
5f3c681
 
 
22bea51
 
c58149c
98ce8b0
 
 
c58149c
98ce8b0
 
67fcd2f
c58149c
 
 
 
 
 
 
98ce8b0
67fcd2f
c58149c
98ce8b0
 
5f3c681
67fcd2f
22bea51
98ce8b0
c58149c
67fcd2f
c58149c
67fcd2f
 
5f3c681
98ce8b0
5f3c681
67fcd2f
 
5f3c681
 
67fcd2f
 
 
22bea51
5f3c681
22bea51
c58149c
5f3c681
22bea51
98ce8b0
c58149c
5f3c681
67fcd2f
5f3c681
22bea51
 
67fcd2f
 
 
 
c58149c
22bea51
 
 
5f3c681
 
22bea51
 
c58149c
 
5f3c681
67fcd2f
 
 
5f3c681
c58149c
 
67fcd2f
5f3c681
67fcd2f
 
 
 
5f3c681
 
 
c58149c
22bea51
5f3c681
22bea51
67fcd2f
5f3c681
67fcd2f
22bea51
 
98ce8b0
5f3c681
 
22bea51
5f3c681
 
67fcd2f
5f3c681
c58149c
 
 
67fcd2f
22bea51
67fcd2f
 
 
 
22bea51
c58149c
 
67fcd2f
5f3c681
c58149c
5f3c681
 
 
 
22bea51
67fcd2f
 
22bea51
 
1
2
3
4
5
6
7
8
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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
# app.py (v4, CPU-optimized + brede Gradio-compat)
# Neurale netwerken als tekenaars — Stable Diffusion + ControlNet (scribble)
# - Werkt op CPU (langzamer dan GPU, maar demowaardig)
# - Gebruikt gr.Sketchpad (compatibel met oudere Gradio-versies)
# - Geen queue-parameters (demo.queue() zonder args)

import os, random, numpy as np
from typing import Optional
from PIL import Image, ImageDraw
import gradio as gr
import torch
from diffusers import (
    ControlNetModel,
    StableDiffusionControlNetPipeline,
    DPMSolverMultistepScheduler,
)

# Modellen (kun je via Space Secrets/Opsional env-vars overriden)
MODEL_BASE = os.environ.get("SD_BASE_MODEL", "runwayml/stable-diffusion-v1-5")
MODEL_CN   = os.environ.get("CN_SCRIBBLE_MODEL", "lllyasviel/sd-controlnet-scribble")

DEVICE = "cpu"              # geforceerd CPU
DTYPE  = torch.float32
TARGET_SIZE = 512           # lager = sneller op CPU
DEFAULT_STEPS = 12          # CPU-vriendelijk

pipe: Optional[StableDiffusionControlNetPipeline] = None

def _lazy_load_pipeline():
    global pipe
    if pipe is not None:
        return pipe
    controlnet = ControlNetModel.from_pretrained(MODEL_CN, torch_dtype=DTYPE)
    pipe = StableDiffusionControlNetPipeline.from_pretrained(
        MODEL_BASE, controlnet=controlnet, torch_dtype=DTYPE, safety_checker=None
    )
    # Snellere scheduler → minder stappen nodig
    pipe.scheduler = DPMSolverMultistepScheduler.from_config(pipe.scheduler.config)
    pipe = pipe.to(DEVICE)
    pipe.enable_attention_slicing()
    return pipe

def to_rgba(img):
    """Accepteer numpy of PIL en geef een PIL RGBA image terug."""
    if img is None:
        return None
    if isinstance(img, np.ndarray):
        if img.dtype != np.uint8:
            img = img.astype(np.uint8)
        return Image.fromarray(img).convert("RGBA")
    if isinstance(img, Image.Image):
        return img.convert("RGBA")
    try:
        return Image.open(img).convert("RGBA")
    except Exception:
        return None

def rgba_to_scribble(img) -> Image.Image:
    """Zet RGBA schets om naar zwart-op-wit, vierkant en resized (TARGET_SIZE)."""
    img = to_rgba(img)
    if img is None:
        return None
    # transparant → wit
    bg = Image.new("RGBA", img.size, (255, 255, 255, 255))
    img = Image.alpha_composite(bg, img)
    # grijs + drempel
    arr = np.array(img.convert("L"))
    thr = max(25, int(arr.mean() * 0.9))
    lines = arr < thr
    out = np.full((*arr.shape, 3), 255, dtype=np.uint8)
    out[lines] = (0, 0, 0)
    scribble = Image.fromarray(out, "RGB")
    # vierkant + resize
    m = max(scribble.width, scribble.height)
    sq = Image.new("RGB", (m, m), (255, 255, 255))
    sq.paste(scribble, ((m - scribble.width) // 2, (m - scribble.height) // 2))
    return sq.resize((TARGET_SIZE, TARGET_SIZE), Image.BICUBIC)

def run(drawing, prompt, negative_prompt,
        guidance_scale=7.0, controlnet_conditioning_scale=1.0,
        num_inference_steps=DEFAULT_STEPS, seed=-1):
    if drawing is None:
        raise gr.Error("Teken eerst iets of gebruik de voorbeeld-schets.")
    cn_image = rgba_to_scribble(drawing)
    if cn_image is None:
        raise gr.Error("Schets niet leesbaar. Probeer opnieuw of upload PNG/JPG.")

    if not prompt or not str(prompt).strip():
        prompt = "clean pencil sketch, coherent completion, subtle shading, high quality"
    if seed is None or int(seed) < 0:
        seed = random.randint(0, 2**31 - 1)
    gen = torch.Generator(device=DEVICE).manual_seed(int(seed))

    p = _lazy_load_pipeline()
    full_prompt = (
        "black pencil sketch, clean lines, coherent shape completion, subtle shading, "
        "consistent with the input scribble, " + prompt
    )
    result = p(
        prompt=full_prompt,
        negative_prompt=negative_prompt or "text, watermark, extra limbs, low quality, distorted face, nsfw",
        image=cn_image,
        width=cn_image.width, height=cn_image.height,
        generator=gen, num_inference_steps=int(num_inference_steps),
        guidance_scale=float(guidance_scale),
        controlnet_conditioning_scale=float(controlnet_conditioning_scale),
    )
    image = result.images[0]
    pad = 12
    combo = Image.new("RGB", (cn_image.width * 2 + pad, cn_image.height), (245, 245, 245))
    combo.paste(cn_image, (0, 0))
    combo.paste(image, (cn_image.width + pad, 0))
    return image, cn_image, combo, f"Seed: {seed}, steps: {num_inference_steps}"

def make_example_scribble() -> Image.Image:
    """Kleine voorbeeldschets (appelvorm) voor snelle test."""
    w, h = 360, 360
    img = Image.new("RGBA", (w, h), (0, 0, 0, 0))
    d = ImageDraw.Draw(img)
    d.line([(100,300),(90,230),(110,170),(160,140),(200,140),
            (260,170),(280,230),(260,300),(180,320),(100,300)],
           fill=(0,0,0,255), width=12)
    d.line([(200,140),(210,110)], fill=(0,0,0,255), width=10)
    d.line([(210,110),(250,120),(265,150)], fill=(0,0,0,255), width=9)
    return img

CSS = ".gradio-container{max-width:1100px} #combo img{border-radius:10px}"

with gr.Blocks(title="🖌️ Neurale netwerken als tekenaars (CPU v4)", css=CSS) as demo:
    gr.Markdown("## 🖌️ Neurale netwerken als tekenaars (CPU)\nTeken links of klik **Voorbeeld-schets**; AI vult aan met Stable Diffusion + ControlNet (scribble).")

    with gr.Row():
        with gr.Column():
            canvas = gr.Sketchpad(label="Schets (teken in zwart op transparant/wit)", height=420)
            prompt = gr.Textbox(label="Prompt", value="realistische appel met blad, potloodschets")
            negative_prompt = gr.Textbox(label="Negative prompt (optioneel)", value="tekst, watermerk, wazig")
            with gr.Accordion("Geavanceerd", open=False):
                guidance_scale = gr.Slider(5.0, 10.0, value=7.0, step=0.5, label="Guidance scale")
                cn_strength   = gr.Slider(0.6, 1.6, value=1.0, step=0.05, label="ControlNet conditioning scale")
                steps         = gr.Slider(6, 25, value=DEFAULT_STEPS, step=1, label="Aantal diffusion-steps")
                seed          = gr.Number(value=-1, precision=0, label="Seed (-1 = random)")
            with gr.Row():
                run_btn = gr.Button("Vervolledig ✨", variant="primary")
                example_btn = gr.Button("Voorbeeld-schets", variant="secondary")

        with gr.Column():
            out_img = gr.Image(label="Vervolledigde tekening", interactive=False)
            cn_prev = gr.Image(label="Scribble (input voor AI)", interactive=False)
            combo   = gr.Image(label="Vergelijking (links: input, rechts: AI)", elem_id="combo")
            meta    = gr.Markdown()

    def fill_and_run(p, n, gs, cs, st, sd):
        img = make_example_scribble()
        a, b, c, d = run(img, p, n, gs, cs, st, sd)
        return img, a, b, c, d

    run_btn.click(run, inputs=[canvas, prompt, negative_prompt, guidance_scale, cn_strength, steps, seed],
                  outputs=[out_img, cn_prev, combo, meta])
    example_btn.click(fill_and_run, inputs=[prompt, negative_prompt, guidance_scale, cn_strength, steps, seed],
                      outputs=[canvas, out_img, cn_prev, combo, meta])

demo.queue()  # zonder parameters: compatibel met oudere Gradio-versies

if __name__ == "__main__":
    demo.launch()