Madiy commited on
Commit
331791f
·
verified ·
1 Parent(s): e1f49bb

Delete CanvasAI

Browse files
Files changed (3) hide show
  1. CanvasAI/CanvasAI +0 -1
  2. CanvasAI/app.py +0 -350
  3. CanvasAI/requirements.txt +0 -11
CanvasAI/CanvasAI DELETED
@@ -1 +0,0 @@
1
- Subproject commit 670a32412994a446097c26da0fb7b97f0aebfa32
 
 
CanvasAI/app.py DELETED
@@ -1,350 +0,0 @@
1
- import spaces
2
- import torch
3
- import gradio as gr
4
- import numpy as np
5
- import os
6
- import urllib
7
- import sys
8
-
9
- from PIL import Image, ImageFilter, ImageDraw
10
- from diffusers import StableDiffusionInpaintPipeline
11
- from transformers import BlipProcessor, BlipForConditionalGeneration
12
-
13
- import torchvision.transforms.functional as F
14
- sys.modules["torchvision.transforms.functional_tensor"] = F
15
-
16
- from basicsr.archs.rrdbnet_arch import RRDBNet
17
- from realesrgan import RealESRGANer
18
-
19
- # LOAD MODELS AT STARTUP
20
- # ── Real-ESRGAN ──────────────────────────────────────────────
21
- print("Downloading Real-ESRGAN weights...")
22
-
23
- weights_dir = "weights"
24
- os.makedirs(weights_dir, exist_ok=True)
25
- weights_path = f"{weights_dir}/RealESRGAN_x4plus.pth"
26
- if not os.path.exists(weights_path):
27
- url = "https://github.com/xinntao/Real-ESRGAN/releases/download/v0.1.0/RealESRGAN_x4plus.pth"
28
- urllib.request.urlretrieve(url, weights_path)
29
- print("Weights downloaded.")
30
-
31
- esrgan_model = RRDBNet(
32
- num_in_ch=3,
33
- num_out_ch=3,
34
- num_feat=64,
35
- num_block=23,
36
- num_grow_ch=32,
37
- scale=4
38
- )
39
-
40
- enhancer = RealESRGANer(
41
- scale=4,
42
- model_path=weights_path,
43
- model=esrgan_model,
44
- tile=512,
45
- tile_pad=10,
46
- pre_pad=0,
47
- half=True,
48
- )
49
- print("Real-ESRGAN ready.")
50
-
51
- # ── SD Inpainting ────────────────────────────────────────────
52
- print("Loading SD Inpainting...")
53
-
54
- inpaint = StableDiffusionInpaintPipeline.from_pretrained(
55
- "runwayml/stable-diffusion-inpainting",
56
- torch_dtype=torch.float16,
57
- )
58
- print("SD Inpainting ready.")
59
-
60
-
61
- # BLIP
62
- print("Loading BLIP...")
63
-
64
- blip_processor = BlipProcessor.from_pretrained(
65
- "Salesforce/blip-image-captioning-base"
66
- )
67
- blip_model = BlipForConditionalGeneration.from_pretrained(
68
- "Salesforce/blip-image-captioning-base",
69
- torch_dtype=torch.float16,
70
- )
71
- # Same — no .to("cuda") at load time for ZeroGPU
72
- print("All models loaded.")
73
-
74
-
75
- # HELPER FUNCTIONS
76
- def is_greyscale(image):
77
- rgb = image.convert("RGB")
78
- r, g, b = rgb.split()
79
- r_arr = np.array(r, dtype=float)
80
- g_arr = np.array(g, dtype=float)
81
- b_arr = np.array(b, dtype=float)
82
- diff_rg = np.mean(np.abs(r_arr - g_arr))
83
- diff_rb = np.mean(np.abs(r_arr - b_arr))
84
- return (diff_rg < 10 and diff_rb < 10)
85
-
86
- def get_caption(image):
87
- inputs = blip_processor(
88
- image.convert("RGB"),
89
- return_tensors="pt"
90
- ).to("cuda", torch.float16)
91
- # Inside @spaces.GPU function, cuda is available
92
- output = blip_model.generate(**inputs, max_new_tokens=60)
93
- caption = blip_processor.decode(output[0], skip_special_tokens=True)
94
- return caption
95
-
96
- def build_prompt(caption, image):
97
- bw = is_greyscale(image)
98
- style_hint = (
99
- "black and white photography, monochrome, greyscale, "
100
- "vintage photograph, same tonal range"
101
- if bw else
102
- "same color palette, same lighting conditions, photorealistic"
103
- )
104
- return (
105
- f"seamless natural continuation of scene, {caption}, "
106
- f"{style_hint}, extending background only, "
107
- f"same atmosphere, high quality"
108
- )
109
-
110
- def extend_one_side(image, direction, pixels, prompt, negative_prompt):
111
- orig_w = image.width
112
- orig_h = image.height
113
-
114
- new_w = orig_w
115
- new_h = orig_h
116
- paste_x = 0
117
- paste_y = 0
118
-
119
- if direction == "left":
120
- new_w = orig_w + pixels
121
- paste_x = pixels
122
- elif direction == "right":
123
- new_w = orig_w + pixels
124
- paste_x = 0
125
- elif direction == "top":
126
- new_h = orig_h + pixels
127
- paste_y = pixels
128
- elif direction == "bottom":
129
- new_h = orig_h + pixels
130
- paste_y = 0
131
-
132
- new_w = (new_w // 8) * 8
133
- new_h = (new_h // 8) * 8
134
-
135
- if direction in ["left", "right"]:
136
- pixels = new_w - orig_w
137
- else:
138
- pixels = new_h - orig_h
139
-
140
- if direction == "left":
141
- paste_x = pixels
142
- if direction == "top":
143
- paste_y = pixels
144
-
145
- canvas = Image.new("RGB", (new_w, new_h), (0, 0, 0))
146
- canvas.paste(image, (paste_x, paste_y))
147
-
148
- mask = Image.new("L", (new_w, new_h), 255)
149
- draw = ImageDraw.Draw(mask)
150
- feather = 30
151
-
152
- if direction == "left":
153
- draw.rectangle([paste_x + feather, feather, new_w - feather, new_h - feather], fill=0)
154
- elif direction == "right":
155
- draw.rectangle([feather, feather, orig_w - feather, new_h - feather], fill=0)
156
- elif direction == "top":
157
- draw.rectangle([feather, paste_y + feather, new_w - feather, new_h - feather], fill=0)
158
- elif direction == "bottom":
159
- draw.rectangle([feather, feather, new_w - feather, orig_h - feather], fill=0)
160
-
161
- mask = mask.filter(ImageFilter.GaussianBlur(radius=30))
162
-
163
- sd_size = 512
164
- canvas_sd = canvas.resize((sd_size, sd_size), Image.LANCZOS)
165
- mask_sd = mask.resize((sd_size, sd_size), Image.LANCZOS)
166
-
167
- result = inpaint(
168
- prompt = prompt,
169
- image = canvas_sd,
170
- mask_image = mask_sd,
171
- height = sd_size,
172
- width = sd_size,
173
- num_inference_steps = 40,
174
- guidance_scale = 7.0,
175
- negative_prompt = negative_prompt,
176
- )
177
-
178
- generated_512 = result.images[0]
179
- generated_full = generated_512.resize((new_w, new_h), Image.LANCZOS)
180
- # No hard paste — GaussianBlur mask handles the boundary softly
181
- return generated_full
182
-
183
- @spaces.GPU
184
- def enhance_image(image, scale_factor):
185
- if image is None:
186
- raise gr.Error("Please upload an image first.")
187
-
188
- # Move models to GPU — happens inside the decorated function
189
- # because GPU is only available here
190
- enhancer.device = torch.device("cuda")
191
- enhancer.half = True
192
-
193
- image_array = np.array(image)
194
- image_array = image_array[:, :, :3]
195
- outscale = 4 if scale_factor == "4x" else 2
196
-
197
- try:
198
- output_array, _ = enhancer.enhance(image_array, outscale=outscale)
199
- except RuntimeError as e:
200
- raise gr.Error(f"Enhancement failed: {e}. Try a smaller image.")
201
-
202
- output_rgb = output_array[:, :, ::-1]
203
- output_image = Image.fromarray(output_rgb)
204
-
205
- original_size = f"{image.width}×{image.height}"
206
- new_size = f"{output_image.width}×{output_image.height}"
207
-
208
- return output_image, f"Original: {original_size} → Enhanced: {new_size}"
209
-
210
- @spaces.GPU
211
- def outpaint_image(image, direction, extend_percent, custom_prompt, progress=gr.Progress()):
212
- if image is None:
213
- raise gr.Error("Please upload an image first.")
214
-
215
- # Move models to GPU inside the decorated function
216
- inpaint.to("cuda")
217
- blip_model.to("cuda")
218
-
219
- target_side = 512
220
- ratio = min(target_side / image.width, target_side / image.height)
221
- new_size = (int(image.width * ratio), int(image.height * ratio))
222
- image = image.resize(new_size, Image.LANCZOS)
223
-
224
- progress(0.05, desc="Analyzing image with BLIP...")
225
-
226
- blip_caption = custom_prompt.strip() if custom_prompt.strip() else get_caption(image)
227
- prompt = build_prompt(blip_caption, image)
228
-
229
- negative_prompt = (
230
- "blurry, bad quality, watermark, text, "
231
- "new person, new face, extra people, "
232
- "colorful, vibrant colors, color photography, "
233
- "duplicate, border, frame, seam, visible edge, "
234
- "distorted, inconsistent style"
235
- )
236
-
237
- STEP_PX = 64
238
- extend = extend_percent / 100.0
239
- h_total = int(image.width * extend)
240
- v_total = int(image.height * extend)
241
-
242
- def make_passes(side, total_px):
243
- passes = []
244
- remaining = total_px
245
- while remaining > 0:
246
- step = min(STEP_PX, remaining)
247
- passes.append((side, step))
248
- remaining -= step
249
- return passes
250
-
251
- if direction == "Horizontal":
252
- passes = make_passes("right", h_total) + make_passes("left", h_total)
253
- elif direction == "Vertical":
254
- passes = make_passes("bottom", v_total) + make_passes("top", v_total)
255
- else:
256
- passes = (
257
- make_passes("bottom", v_total) +
258
- make_passes("top", v_total) +
259
- make_passes("right", h_total) +
260
- make_passes("left", h_total)
261
- )
262
-
263
- total_passes = len(passes)
264
- current_image = image
265
-
266
- for i, (side, px) in enumerate(passes):
267
- progress(
268
- 0.1 + 0.85 * (i / total_passes),
269
- desc=f"Pass {i+1}/{total_passes} — extending {side} by {px}px"
270
- )
271
- current_image = extend_one_side(
272
- current_image, side, px, prompt, negative_prompt
273
- )
274
-
275
- progress(1.0, desc="Done!")
276
-
277
- bw_note = " [B&W detected]" if is_greyscale(image) else ""
278
- return (
279
- current_image,
280
- f"Caption{bw_note}:\n{blip_caption}\n\nPrompt:\n{prompt}"
281
- )
282
-
283
- # GRADIO UI
284
- with gr.Blocks(title="CanvasAI — Enhance & Outpaint") as demo:
285
-
286
- gr.Markdown("# CanvasAI")
287
- gr.Markdown(
288
- "**Enhance** any image with Real-ESRGAN super resolution, "
289
- "or **Outpaint** to extend the scene in any direction using AI."
290
- )
291
-
292
- with gr.Tabs():
293
-
294
- with gr.Tab(" Enhance"):
295
- gr.Markdown("Upscale and sharpen any image 2x or 4x using Real-ESRGAN.")
296
- with gr.Row():
297
- with gr.Column():
298
- enh_input = gr.Image(label="Upload Image", type="pil")
299
- enh_scale = gr.Dropdown(
300
- choices=["2x", "4x"],
301
- value="4x",
302
- label="Upscale Factor"
303
- )
304
- enh_btn = gr.Button("Enhance", variant="primary")
305
- with gr.Column():
306
- enh_output = gr.Image(label="Result", type="pil", interactive=False)
307
- enh_info = gr.Textbox(label="Size Info", interactive=False)
308
-
309
- enh_btn.click(
310
- fn=enhance_image,
311
- inputs=[enh_input, enh_scale],
312
- outputs=[enh_output, enh_info]
313
- )
314
-
315
- with gr.Tab(" Outpaint"):
316
- gr.Markdown(
317
- "Upload an image and extend it in any direction. "
318
- "BLIP reads the scene automatically — no prompt needed."
319
- )
320
- with gr.Row():
321
- with gr.Column():
322
- out_input = gr.Image(label="Upload Image", type="pil")
323
- out_dir = gr.Radio(
324
- choices=["Horizontal", "Vertical", "Both"],
325
- value="Horizontal",
326
- label="Direction"
327
- )
328
- out_pct = gr.Slider(
329
- minimum=10, maximum=50,
330
- value=25, step=5,
331
- label="Extend by (%)"
332
- )
333
- out_prompt = gr.Textbox(
334
- label="Custom Prompt (optional)",
335
- placeholder="Leave empty for auto-detection",
336
- lines=2
337
- )
338
- out_btn = gr.Button("🔲 Outpaint", variant="primary")
339
- with gr.Column():
340
- out_output = gr.Image(label="Result", type="pil", interactive=False)
341
- out_caption = gr.Textbox(label="Prompt Used", interactive=False, lines=4)
342
-
343
- out_btn.click(
344
- fn=outpaint_image,
345
- inputs=[out_input, out_dir, out_pct, out_prompt],
346
- outputs=[out_output, out_caption]
347
- )
348
-
349
- #launch
350
- demo.launch()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
CanvasAI/requirements.txt DELETED
@@ -1,11 +0,0 @@
1
- gradio
2
- torch
3
- diffusers
4
- transformers
5
- accelerate
6
- Pillow
7
- numpy
8
- basicsr
9
- facexlib
10
- gfpgan
11
- realesrgan@ git+https://github.com/xinntao/Real-ESRGAN.git