Marcel0123 commited on
Commit
c58149c
·
verified ·
1 Parent(s): 22bea51

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +98 -70
app.py CHANGED
@@ -1,20 +1,22 @@
1
- # app.py
2
  # Neurale netwerken als tekenaars — ControlNet (scribble) demo
3
- # UI: teken links, AI maakt rechts de tekening af o.b.v. je prompt.
4
- # Tip: kies in Hugging Face Spaces een T4 GPU voor prettige snelheid.
 
 
 
5
 
6
  import os
7
  import random
8
  import numpy as np
9
- from typing import Optional, Tuple
10
 
11
- from PIL import Image, ImageOps
12
 
13
  import gradio as gr
14
 
15
  import torch
16
  from diffusers import ControlNetModel, StableDiffusionControlNetPipeline
17
- from diffusers.utils import load_image
18
 
19
 
20
  MODEL_BASE = os.environ.get("SD_BASE_MODEL", "runwayml/stable-diffusion-v1-5")
@@ -23,7 +25,6 @@ MODEL_CN = os.environ.get("CN_SCRIBBLE_MODEL", "lllyasviel/sd-controlnet-scribbl
23
  DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
24
  DTYPE = torch.float16 if DEVICE == "cuda" else torch.float32
25
 
26
-
27
  pipe: Optional[StableDiffusionControlNetPipeline] = None
28
 
29
 
@@ -36,7 +37,7 @@ def _lazy_load_pipeline():
36
  MODEL_BASE,
37
  controlnet=controlnet,
38
  torch_dtype=DTYPE,
39
- safety_checker=None, # Gradio heeft eigen NSFW waarschuwingen; demo is voor schetsen
40
  )
41
  if DEVICE == "cuda":
42
  pipe.enable_model_cpu_offload()
@@ -45,78 +46,86 @@ def _lazy_load_pipeline():
45
  return pipe
46
 
47
 
48
- def rgba_to_scribble(img: Image.Image, stroke_color=(0, 0, 0)) -> Image.Image:
49
- """
50
- Zet een RGBA canvas om naar een wit canvas met zwarte lijnen (scribble) voor ControlNet.
51
- - Converteer transparantie naar wit.
52
- - Normaliseer naar 1024 x 1024 (vierkant) met padding, zodat aspect netjes blijft.
53
- """
54
- if img.mode != "RGBA":
55
- img = img.convert("RGBA")
56
-
57
- # Op transparante achtergrond tekenen → naar wit compositen
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
58
  bg = Image.new("RGBA", img.size, (255, 255, 255, 255))
59
  img = Image.alpha_composite(bg, img)
60
 
61
- # Naar L (grijs), dan binair threshold voor heldere lijnen
62
- gray = img.convert("L")
63
- # Auto-threshold: Otsu benadering (simple) via numpy
64
- arr = np.array(gray).astype(np.uint8)
65
- thr = max(20, int(arr.mean() * 0.9))
66
- bin_img = (arr < thr).astype(np.uint8) * 0 # donkere pixels → 0 (zwart)
67
- # We willen echte lijnen behouden; maak binaire inversie (zwart op wit)
68
- # Gebruik originele gray met punt-bij-punt: onder drempel → zwart, anders wit
69
- mask = arr < thr
70
  out = np.full((*arr.shape, 3), 255, dtype=np.uint8)
71
- out[mask] = np.array(stroke_color, dtype=np.uint8)
72
-
73
  scribble = Image.fromarray(out, mode="RGB")
74
 
75
- # Maak vierkant canvas met padding
76
  max_side = max(scribble.width, scribble.height)
77
  sq = Image.new("RGB", (max_side, max_side), (255, 255, 255))
78
  ox = (max_side - scribble.width) // 2
79
  oy = (max_side - scribble.height) // 2
80
  sq.paste(scribble, (ox, oy))
81
 
82
- # Resize naar 768 of 512 (sneller). 768 geeft betere details op T4.
83
- target = 768
84
- sq = sq.resize((target, target), Image.BICUBIC)
85
- return sq
86
 
87
 
88
  def run(
89
- drawing: Image.Image,
90
  prompt: str,
91
  negative_prompt: str,
92
  guidance_scale: float = 8.0,
93
  controlnet_conditioning_scale: float = 1.2,
94
- num_inference_steps: int = 25,
95
  seed: int = -1,
96
- strength_fill: float = 1.0,
97
  ):
98
  if drawing is None:
99
  raise gr.Error("Teken eerst iets links of laad een schetsbestand.")
100
- if not prompt.strip():
101
- prompt = "clean line-art, detailed sketch, shaded, high quality, coherent completion"
102
 
103
  cn_image = rgba_to_scribble(drawing)
 
 
104
 
105
- gen = None
106
- if seed is not None and seed >= 0:
107
- gen = torch.Generator(device=DEVICE).manual_seed(int(seed))
108
- else:
109
- seed = random.randint(0, 2**31 - 1)
110
- gen = torch.Generator(device=DEVICE).manual_seed(seed)
111
 
112
  p = _lazy_load_pipeline()
113
-
114
  full_prompt = (
115
  "black pencil sketch, clean lines, coherent shape completion, subtle shading, "
116
  "consistent with the input scribble, " + prompt
117
  )
118
-
119
- image = p(
120
  prompt=full_prompt,
121
  negative_prompt=negative_prompt or "text, watermark, extra limbs, low quality, distorted face, nsfw",
122
  image=cn_image,
@@ -126,37 +135,52 @@ def run(
126
  num_inference_steps=int(num_inference_steps),
127
  guidance_scale=float(guidance_scale),
128
  controlnet_conditioning_scale=float(controlnet_conditioning_scale),
129
- ).images[0]
 
130
 
131
- # Combineer input en output naast elkaar voor een prettige vergelijking
132
  pad = 16
133
  combo = Image.new("RGB", (cn_image.width * 2 + pad, cn_image.height), (245, 245, 245))
134
  combo.paste(cn_image, (0, 0))
135
  combo.paste(image, (cn_image.width + pad, 0))
136
 
137
- return image, cn_image, combo, f"Seed: {seed}"
 
 
 
 
 
 
 
 
 
 
 
 
138
 
139
 
140
  CSS = """
141
- #combo img { border-radius: 10px; }
142
  .gradio-container { max-width: 1100px !important; }
 
143
  """
144
 
145
- with gr.Blocks(title="🖌️ Neurale netwerken als tekenaars", css=CSS) as demo:
146
  gr.Markdown(
147
  """
148
  # 🖌️ Neurale netwerken als tekenaars
149
- Teken links een schets. Rechts **maakt een AI** (Stable Diffusion + ControlNet *scribble*) de tekening af op basis van jouw **prompt**.
150
-
151
- **Tip:** duidelijke contouren werken het best. Probeer bijvoorbeeld: *“een vliegende vis met vleugels, potloodschets”*.
152
  """
153
  )
154
 
155
  with gr.Row():
156
  with gr.Column():
157
- canvas = gr.Sketchpad(
158
- label="Tekening (schets) — teken in zwart op wit",
159
- brush=gr.Brush(default_color="#000000", color_mode="fixed"),
 
 
 
160
  height=480,
161
  )
162
 
@@ -175,30 +199,34 @@ Teken links een schets. Rechts **maakt een AI** (Stable Diffusion + ControlNet *
175
  with gr.Accordion("Geavanceerd", open=False):
176
  guidance_scale = gr.Slider(4.0, 15.0, value=8.0, step=0.5, label="Guidance scale")
177
  cn_strength = gr.Slider(0.1, 2.0, value=1.2, step=0.05, label="ControlNet conditioning scale")
178
- steps = gr.Slider(10, 40, value=25, step=1, label="Aantal diffusion-steps")
179
  seed = gr.Number(value=-1, precision=0, label="Seed (-1 = random)")
180
 
181
- run_btn = gr.Button("Vervolledig ✨", variant="primary")
 
 
182
 
183
  with gr.Column():
184
  out_img = gr.Image(label="Vervolledigde tekening", interactive=False)
185
  cn_preview = gr.Image(label="Scribble (input voor AI)", interactive=False)
186
  combo = gr.Image(label="Vergelijking (links: input, rechts: AI)", elem_id="combo")
187
- meta = gr.Markdown()
188
 
189
  run_btn.click(
190
  run,
191
  inputs=[canvas, prompt, negative_prompt, guidance_scale, cn_strength, steps, seed],
192
- outputs=[out_img, cn_preview, combo, meta],
193
  )
194
 
195
- gr.Markdown(
196
- """
197
- ### Over deze demo
198
- - **Model:** Stable Diffusion v1.5 + ControlNet (scribble).
199
- - **Hardware:** bij voorkeur **GPU (T4 small)** in Hugging Face Spaces. Op CPU werkt het ook maar trager.
200
- - **Educatief doel:** laat zien hoe een **conditioned diffusion model** een schets omzet naar een coherente tekening.
201
- """
 
 
 
202
  )
203
 
204
  if __name__ == "__main__":
 
1
+ # app.py (v2)
2
  # Neurale netwerken als tekenaars — ControlNet (scribble) demo
3
+ # Fixes:
4
+ # - Gebruik gr.Image(tool="sketch", type="pil") i.p.v. Sketchpad (betere compatibiliteit met Gradio 4)
5
+ # - Robuuste conversie van numpy/PIL naar RGBA
6
+ # - Testknop "Voorbeeld-schets" om pipeline te verifiëren
7
+ # - Kleinere default-steps voor snellere eerste run
8
 
9
  import os
10
  import random
11
  import numpy as np
12
+ from typing import Optional
13
 
14
+ from PIL import Image, ImageDraw
15
 
16
  import gradio as gr
17
 
18
  import torch
19
  from diffusers import ControlNetModel, StableDiffusionControlNetPipeline
 
20
 
21
 
22
  MODEL_BASE = os.environ.get("SD_BASE_MODEL", "runwayml/stable-diffusion-v1-5")
 
25
  DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
26
  DTYPE = torch.float16 if DEVICE == "cuda" else torch.float32
27
 
 
28
  pipe: Optional[StableDiffusionControlNetPipeline] = None
29
 
30
 
 
37
  MODEL_BASE,
38
  controlnet=controlnet,
39
  torch_dtype=DTYPE,
40
+ safety_checker=None,
41
  )
42
  if DEVICE == "cuda":
43
  pipe.enable_model_cpu_offload()
 
46
  return pipe
47
 
48
 
49
+ def to_rgba(img):
50
+ """Accepteer PIL of numpy; geef RGBA terug."""
51
+ if img is None:
52
+ return None
53
+ if isinstance(img, np.ndarray):
54
+ # Gradio kan np.uint8 HxWxC teruggeven
55
+ if img.ndim == 2:
56
+ pil = Image.fromarray(img).convert("RGBA")
57
+ else:
58
+ pil = Image.fromarray(img.astype(np.uint8))
59
+ if pil.mode != "RGBA":
60
+ pil = pil.convert("RGBA")
61
+ return pil
62
+ if isinstance(img, Image.Image):
63
+ return img.convert("RGBA")
64
+ # Defensief: probeer te openen als pad/bytes
65
+ try:
66
+ return Image.open(img).convert("RGBA")
67
+ except Exception:
68
+ return None
69
+
70
+
71
+ def rgba_to_scribble(img: Image.Image) -> Image.Image:
72
+ """Zet RGBA schets om naar zwart-op-wit scribble, vierkant 768x768."""
73
+ img = to_rgba(img)
74
+ if img is None:
75
+ return None
76
+
77
+ # Witte achtergrond + compositing (zodat transparant wit wordt)
78
  bg = Image.new("RGBA", img.size, (255, 255, 255, 255))
79
  img = Image.alpha_composite(bg, img)
80
 
81
+ # Grijswaarden en drempel
82
+ arr = np.array(img.convert("L"))
83
+ thr = max(25, int(arr.mean() * 0.9))
84
+ # Donkerder dan thr -> lijn
85
+ lines = arr < thr
 
 
 
 
86
  out = np.full((*arr.shape, 3), 255, dtype=np.uint8)
87
+ out[lines] = (0, 0, 0)
 
88
  scribble = Image.fromarray(out, mode="RGB")
89
 
90
+ # Vierkant maken met padding
91
  max_side = max(scribble.width, scribble.height)
92
  sq = Image.new("RGB", (max_side, max_side), (255, 255, 255))
93
  ox = (max_side - scribble.width) // 2
94
  oy = (max_side - scribble.height) // 2
95
  sq.paste(scribble, (ox, oy))
96
 
97
+ # Resize naar 768 (kwaliteit) maak 512 als je CPU gebruikt
98
+ return sq.resize((768, 768), Image.BICUBIC)
 
 
99
 
100
 
101
  def run(
102
+ drawing,
103
  prompt: str,
104
  negative_prompt: str,
105
  guidance_scale: float = 8.0,
106
  controlnet_conditioning_scale: float = 1.2,
107
+ num_inference_steps: int = 18,
108
  seed: int = -1,
 
109
  ):
110
  if drawing is None:
111
  raise gr.Error("Teken eerst iets links of laad een schetsbestand.")
 
 
112
 
113
  cn_image = rgba_to_scribble(drawing)
114
+ if cn_image is None:
115
+ raise gr.Error("Kon de schets niet lezen. Probeer opnieuw te tekenen of upload een PNG/JPG.")
116
 
117
+ if not prompt.strip():
118
+ prompt = "clean pencil sketch, coherent completion, subtle shading, high quality"
119
+ gen = torch.Generator(device=DEVICE).manual_seed(
120
+ int(seed) if (seed is not None and int(seed) >= 0) else random.randint(0, 2**31 - 1)
121
+ )
 
122
 
123
  p = _lazy_load_pipeline()
 
124
  full_prompt = (
125
  "black pencil sketch, clean lines, coherent shape completion, subtle shading, "
126
  "consistent with the input scribble, " + prompt
127
  )
128
+ result = p(
 
129
  prompt=full_prompt,
130
  negative_prompt=negative_prompt or "text, watermark, extra limbs, low quality, distorted face, nsfw",
131
  image=cn_image,
 
135
  num_inference_steps=int(num_inference_steps),
136
  guidance_scale=float(guidance_scale),
137
  controlnet_conditioning_scale=float(controlnet_conditioning_scale),
138
+ )
139
+ image = result.images[0]
140
 
141
+ # Combo (input vs output) naast elkaar
142
  pad = 16
143
  combo = Image.new("RGB", (cn_image.width * 2 + pad, cn_image.height), (245, 245, 245))
144
  combo.paste(cn_image, (0, 0))
145
  combo.paste(image, (cn_image.width + pad, 0))
146
 
147
+ return image, cn_image, combo
148
+
149
+
150
+ def make_example_scribble() -> Image.Image:
151
+ """Genereer een eenvoudige voorbeeldschets (appelvorm)."""
152
+ w, h = 400, 400
153
+ img = Image.new("RGBA", (w, h), (0, 0, 0, 0))
154
+ d = ImageDraw.Draw(img)
155
+ d.line([(120, 320), (100, 240), (110, 180), (160, 140), (240, 140),
156
+ (290, 180), (300, 240), (280, 320), (200, 340), (120, 320)], fill=(0, 0, 0, 255), width=14)
157
+ d.line([(200, 140), (210, 110)], fill=(0, 0, 0, 255), width=12)
158
+ d.line([(210, 110), (250, 120), (270, 150)], fill=(0, 0, 0, 255), width=10)
159
+ return img
160
 
161
 
162
  CSS = """
 
163
  .gradio-container { max-width: 1100px !important; }
164
+ #combo img { border-radius: 10px; }
165
  """
166
 
167
+ with gr.Blocks(title="🖌️ Neurale netwerken als tekenaars (v2)", css=CSS) as demo:
168
  gr.Markdown(
169
  """
170
  # 🖌️ Neurale netwerken als tekenaars
171
+ Teken links (of klik op **Voorbeeld-schets**) en laat AI de tekening aanvullen
172
+ met **Stable Diffusion + ControlNet (scribble)**.
 
173
  """
174
  )
175
 
176
  with gr.Row():
177
  with gr.Column():
178
+ canvas = gr.Image(
179
+ label="Tekening (schets) — gebruik de penseel-tool",
180
+ sources=["upload", "clipboard"],
181
+ type="pil",
182
+ image_mode="RGBA",
183
+ tool="sketch",
184
  height=480,
185
  )
186
 
 
199
  with gr.Accordion("Geavanceerd", open=False):
200
  guidance_scale = gr.Slider(4.0, 15.0, value=8.0, step=0.5, label="Guidance scale")
201
  cn_strength = gr.Slider(0.1, 2.0, value=1.2, step=0.05, label="ControlNet conditioning scale")
202
+ steps = gr.Slider(10, 40, value=18, step=1, label="Aantal diffusion-steps")
203
  seed = gr.Number(value=-1, precision=0, label="Seed (-1 = random)")
204
 
205
+ with gr.Row():
206
+ run_btn = gr.Button("Vervolledig ✨", variant="primary")
207
+ example_btn = gr.Button("Voorbeeld-schets", variant="secondary")
208
 
209
  with gr.Column():
210
  out_img = gr.Image(label="Vervolledigde tekening", interactive=False)
211
  cn_preview = gr.Image(label="Scribble (input voor AI)", interactive=False)
212
  combo = gr.Image(label="Vergelijking (links: input, rechts: AI)", elem_id="combo")
 
213
 
214
  run_btn.click(
215
  run,
216
  inputs=[canvas, prompt, negative_prompt, guidance_scale, cn_strength, steps, seed],
217
+ outputs=[out_img, cn_preview, combo],
218
  )
219
 
220
+ # Voorbeeld-schets vullen en direct runnen
221
+ def fill_and_run(p, n, gs, cs, st, sd):
222
+ img = make_example_scribble()
223
+ out, scrib, both = run(img, p, n, gs, cs, st, sd)
224
+ return img, out, scrib, both
225
+
226
+ example_btn.click(
227
+ fill_and_run,
228
+ inputs=[prompt, negative_prompt, guidance_scale, cn_strength, steps, seed],
229
+ outputs=[canvas, out_img, cn_preview, combo],
230
  )
231
 
232
  if __name__ == "__main__":