Update app.py
Browse files
app.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
| 1 |
# app.py
|
| 2 |
-
import os,
|
| 3 |
from typing import Tuple
|
| 4 |
import gradio as gr
|
| 5 |
import numpy as np
|
|
@@ -15,19 +15,19 @@ MODEL_ID = os.getenv("MODEL_ID", "stabilityai/stable-diffusion-2-inpainting")
|
|
| 15 |
DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
|
| 16 |
DTYPE = torch.float16 if DEVICE == "cuda" else torch.float32
|
| 17 |
|
| 18 |
-
OUT_DIR = "/mnt/data"
|
| 19 |
os.makedirs(OUT_DIR, exist_ok=True)
|
| 20 |
|
| 21 |
print(f"Loading model: {MODEL_ID} (device={DEVICE}, dtype={DTYPE})")
|
| 22 |
pipe = StableDiffusionInpaintPipeline.from_pretrained(
|
| 23 |
MODEL_ID,
|
| 24 |
torch_dtype=DTYPE,
|
| 25 |
-
safety_checker=None,
|
| 26 |
).to(DEVICE)
|
| 27 |
pipe.enable_attention_slicing()
|
| 28 |
if DEVICE == "cuda":
|
| 29 |
try:
|
| 30 |
-
pipe.enable_model_cpu_offload()
|
| 31 |
except Exception:
|
| 32 |
pass
|
| 33 |
|
|
@@ -38,57 +38,43 @@ def round_to_eight(x: int) -> int:
|
|
| 38 |
return int(max(64, (x // 8) * 8))
|
| 39 |
|
| 40 |
def make_square_canvas(img: Image.Image, pad_px: int, bg=(200,200,200)) -> Tuple[Image.Image, Tuple[int,int]]:
|
| 41 |
-
"""Возвращает квадратный холст + координаты вставки исходника (x0,y0)."""
|
| 42 |
w, h = img.size
|
| 43 |
base = max(w, h)
|
| 44 |
W = base + 2*pad_px
|
| 45 |
H = base + 2*pad_px
|
| 46 |
canvas = Image.new("RGB", (W, H), bg)
|
| 47 |
-
# центрируем исходник без масштабирования
|
| 48 |
x0 = (W - w)//2
|
| 49 |
y0 = (H - h)//2
|
| 50 |
canvas.paste(img, (x0, y0))
|
| 51 |
return canvas, (x0, y0)
|
| 52 |
|
| 53 |
def make_ring_mask(canvas_size: Tuple[int,int], inner_rect: Tuple[int,int,int,int], feather_px:int) -> Image.Image:
|
| 54 |
-
"""Белый (генерировать) за пределами inner_rect, чёрный внутри.
|
| 55 |
-
Дополнительно — перья по границе."""
|
| 56 |
W, H = canvas_size
|
| 57 |
x0,y0,x1,y1 = inner_rect
|
| 58 |
-
mask = Image.new("L", (W,H), 255)
|
| 59 |
draw = ImageDraw.Draw(mask)
|
| 60 |
-
draw.rectangle([x0,y0,x1,y1], fill=0)
|
| 61 |
if feather_px > 0:
|
| 62 |
mask = mask.filter(ImageFilter.GaussianBlur(radius=feather_px))
|
| 63 |
return mask
|
| 64 |
|
| 65 |
def paste_original_back(generated: Image.Image, original: Image.Image, offset: Tuple[int,int]) -> Image.Image:
|
| 66 |
-
"""Жёстко возвращаем оригинал поверх финального результата (центр не испортится)."""
|
| 67 |
out = generated.copy()
|
| 68 |
-
|
| 69 |
-
out.paste(original, (x0,y0))
|
| 70 |
return out
|
| 71 |
|
| 72 |
-
def preview_frame(image: Image.Image, pad_px:int)
|
| 73 |
-
"""Готовит превью квадрата с рамкой и текстом про пиксели по сторонам."""
|
| 74 |
w,h = image.size
|
| 75 |
base = max(w,h)
|
| 76 |
W = base + 2*pad_px
|
| 77 |
H = base + 2*pad_px
|
| 78 |
-
# уменьшаем для UI
|
| 79 |
-
show_W, show_H = 512, 512
|
| 80 |
canvas, offset = make_square_canvas(image, pad_px)
|
| 81 |
-
# рисуем рамку (граница области дорисовки)
|
| 82 |
draw = ImageDraw.Draw(canvas)
|
| 83 |
x0,y0 = offset
|
| 84 |
x1,y1 = x0 + w, y0 + h
|
| 85 |
draw.rectangle([x0-1,y0-1,x1+1,y1+1], outline=(255, 90, 90), width=3)
|
| 86 |
-
|
| 87 |
-
# ресайз для превью
|
| 88 |
prev = canvas.copy()
|
| 89 |
-
prev.thumbnail((
|
| 90 |
-
|
| 91 |
-
# текст
|
| 92 |
top = y0
|
| 93 |
left = x0
|
| 94 |
right = W - (x0 + w)
|
|
@@ -112,16 +98,13 @@ def outpaint_generate(
|
|
| 112 |
if input_image is None:
|
| 113 |
raise gr.Error("Сначала загрузите изображение.")
|
| 114 |
|
| 115 |
-
# габариты и холст
|
| 116 |
canvas, offset = make_square_canvas(input_image.convert("RGB"), pad_px)
|
| 117 |
w, h = input_image.size
|
| 118 |
x0, y0 = offset
|
| 119 |
x1, y1 = x0 + w, y0 + h
|
| 120 |
|
| 121 |
-
# маска: белое = расширять, чёрное = не трогать
|
| 122 |
mask = make_ring_mask(canvas.size, (x0,y0,x1,y1), feather_px)
|
| 123 |
|
| 124 |
-
# безопасные размеры (многократность 8, ограничение по памяти)
|
| 125 |
W = round_to_eight(canvas.size[0])
|
| 126 |
H = round_to_eight(canvas.size[1])
|
| 127 |
MAX_SIDE = 1536 if DEVICE == "cuda" else 1024
|
|
@@ -131,7 +114,6 @@ def outpaint_generate(
|
|
| 131 |
newH = round_to_eight(int(H*scale))
|
| 132 |
canvas_small = canvas.resize((newW,newH), Image.LANCZOS)
|
| 133 |
mask_small = mask.resize((newW,newH), Image.LANCZOS)
|
| 134 |
-
# координаты центра тоже масштабируем для обратной вставки:
|
| 135 |
sx = int(x0*scale); sy = int(y0*scale)
|
| 136 |
sw = int(w*scale); sh = int(h*scale)
|
| 137 |
inner_rect_small = (sx,sy,sx+sw,sy+sh)
|
|
@@ -140,13 +122,9 @@ def outpaint_generate(
|
|
| 140 |
inner_rect_small = (x0,y0,x1,y1)
|
| 141 |
|
| 142 |
g = torch.Generator(device=DEVICE)
|
| 143 |
-
if seed is None or int(seed) < 0
|
| 144 |
-
seed_val = random.randint(0, 2**32 - 1)
|
| 145 |
-
else:
|
| 146 |
-
seed_val = int(seed)
|
| 147 |
g.manual_seed(seed_val)
|
| 148 |
|
| 149 |
-
# инференс
|
| 150 |
with torch.autocast(device_type=DEVICE if DEVICE!="mps" else "cpu"):
|
| 151 |
out = pipe(
|
| 152 |
prompt=prompt,
|
|
@@ -158,14 +136,11 @@ def outpaint_generate(
|
|
| 158 |
generator=g,
|
| 159 |
).images[0]
|
| 160 |
|
| 161 |
-
# если генерили уменьшенно — апскейл обратно
|
| 162 |
if out.size != canvas.size:
|
| 163 |
out = out.resize(canvas.size, Image.LANCZOS)
|
| 164 |
|
| 165 |
-
# возвращаем исходник поверх
|
| 166 |
final = paste_original_back(out, input_image.convert("RGB"), offset)
|
| 167 |
|
| 168 |
-
# сохраняем
|
| 169 |
fname = f"outpaint_{canvas.size[0]}x{canvas.size[1]}_{int(time.time()*1000)}.png"
|
| 170 |
out_path = os.path.join(OUT_DIR, fname)
|
| 171 |
os.makedirs(os.path.dirname(out_path), exist_ok=True)
|
|
@@ -206,7 +181,6 @@ with gr.Blocks(css="""
|
|
| 206 |
meta = gr.Markdown("")
|
| 207 |
file_out = gr.File(label="Download PNG")
|
| 208 |
|
| 209 |
-
# live preview
|
| 210 |
def _update_preview(img, pad_px):
|
| 211 |
if img is None:
|
| 212 |
return None, ""
|
|
@@ -233,4 +207,10 @@ with gr.Blocks(css="""
|
|
| 233 |
)
|
| 234 |
|
| 235 |
if __name__ == "__main__":
|
| 236 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
# app.py
|
| 2 |
+
import os, time, random
|
| 3 |
from typing import Tuple
|
| 4 |
import gradio as gr
|
| 5 |
import numpy as np
|
|
|
|
| 15 |
DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
|
| 16 |
DTYPE = torch.float16 if DEVICE == "cuda" else torch.float32
|
| 17 |
|
| 18 |
+
OUT_DIR = "/mnt/data" # оставляем /mnt/data
|
| 19 |
os.makedirs(OUT_DIR, exist_ok=True)
|
| 20 |
|
| 21 |
print(f"Loading model: {MODEL_ID} (device={DEVICE}, dtype={DTYPE})")
|
| 22 |
pipe = StableDiffusionInpaintPipeline.from_pretrained(
|
| 23 |
MODEL_ID,
|
| 24 |
torch_dtype=DTYPE,
|
| 25 |
+
safety_checker=None, # для локального теста
|
| 26 |
).to(DEVICE)
|
| 27 |
pipe.enable_attention_slicing()
|
| 28 |
if DEVICE == "cuda":
|
| 29 |
try:
|
| 30 |
+
pipe.enable_model_cpu_offload()
|
| 31 |
except Exception:
|
| 32 |
pass
|
| 33 |
|
|
|
|
| 38 |
return int(max(64, (x // 8) * 8))
|
| 39 |
|
| 40 |
def make_square_canvas(img: Image.Image, pad_px: int, bg=(200,200,200)) -> Tuple[Image.Image, Tuple[int,int]]:
|
|
|
|
| 41 |
w, h = img.size
|
| 42 |
base = max(w, h)
|
| 43 |
W = base + 2*pad_px
|
| 44 |
H = base + 2*pad_px
|
| 45 |
canvas = Image.new("RGB", (W, H), bg)
|
|
|
|
| 46 |
x0 = (W - w)//2
|
| 47 |
y0 = (H - h)//2
|
| 48 |
canvas.paste(img, (x0, y0))
|
| 49 |
return canvas, (x0, y0)
|
| 50 |
|
| 51 |
def make_ring_mask(canvas_size: Tuple[int,int], inner_rect: Tuple[int,int,int,int], feather_px:int) -> Image.Image:
|
|
|
|
|
|
|
| 52 |
W, H = canvas_size
|
| 53 |
x0,y0,x1,y1 = inner_rect
|
| 54 |
+
mask = Image.new("L", (W,H), 255)
|
| 55 |
draw = ImageDraw.Draw(mask)
|
| 56 |
+
draw.rectangle([x0,y0,x1,y1], fill=0)
|
| 57 |
if feather_px > 0:
|
| 58 |
mask = mask.filter(ImageFilter.GaussianBlur(radius=feather_px))
|
| 59 |
return mask
|
| 60 |
|
| 61 |
def paste_original_back(generated: Image.Image, original: Image.Image, offset: Tuple[int,int]) -> Image.Image:
|
|
|
|
| 62 |
out = generated.copy()
|
| 63 |
+
out.paste(original, offset)
|
|
|
|
| 64 |
return out
|
| 65 |
|
| 66 |
+
def preview_frame(image: Image.Image, pad_px:int):
|
|
|
|
| 67 |
w,h = image.size
|
| 68 |
base = max(w,h)
|
| 69 |
W = base + 2*pad_px
|
| 70 |
H = base + 2*pad_px
|
|
|
|
|
|
|
| 71 |
canvas, offset = make_square_canvas(image, pad_px)
|
|
|
|
| 72 |
draw = ImageDraw.Draw(canvas)
|
| 73 |
x0,y0 = offset
|
| 74 |
x1,y1 = x0 + w, y0 + h
|
| 75 |
draw.rectangle([x0-1,y0-1,x1+1,y1+1], outline=(255, 90, 90), width=3)
|
|
|
|
|
|
|
| 76 |
prev = canvas.copy()
|
| 77 |
+
prev.thumbnail((512, 512), Image.LANCZOS)
|
|
|
|
|
|
|
| 78 |
top = y0
|
| 79 |
left = x0
|
| 80 |
right = W - (x0 + w)
|
|
|
|
| 98 |
if input_image is None:
|
| 99 |
raise gr.Error("Сначала загрузите изображение.")
|
| 100 |
|
|
|
|
| 101 |
canvas, offset = make_square_canvas(input_image.convert("RGB"), pad_px)
|
| 102 |
w, h = input_image.size
|
| 103 |
x0, y0 = offset
|
| 104 |
x1, y1 = x0 + w, y0 + h
|
| 105 |
|
|
|
|
| 106 |
mask = make_ring_mask(canvas.size, (x0,y0,x1,y1), feather_px)
|
| 107 |
|
|
|
|
| 108 |
W = round_to_eight(canvas.size[0])
|
| 109 |
H = round_to_eight(canvas.size[1])
|
| 110 |
MAX_SIDE = 1536 if DEVICE == "cuda" else 1024
|
|
|
|
| 114 |
newH = round_to_eight(int(H*scale))
|
| 115 |
canvas_small = canvas.resize((newW,newH), Image.LANCZOS)
|
| 116 |
mask_small = mask.resize((newW,newH), Image.LANCZOS)
|
|
|
|
| 117 |
sx = int(x0*scale); sy = int(y0*scale)
|
| 118 |
sw = int(w*scale); sh = int(h*scale)
|
| 119 |
inner_rect_small = (sx,sy,sx+sw,sy+sh)
|
|
|
|
| 122 |
inner_rect_small = (x0,y0,x1,y1)
|
| 123 |
|
| 124 |
g = torch.Generator(device=DEVICE)
|
| 125 |
+
seed_val = random.randint(0, 2**32 - 1) if (seed is None or int(seed) < 0) else int(seed)
|
|
|
|
|
|
|
|
|
|
| 126 |
g.manual_seed(seed_val)
|
| 127 |
|
|
|
|
| 128 |
with torch.autocast(device_type=DEVICE if DEVICE!="mps" else "cpu"):
|
| 129 |
out = pipe(
|
| 130 |
prompt=prompt,
|
|
|
|
| 136 |
generator=g,
|
| 137 |
).images[0]
|
| 138 |
|
|
|
|
| 139 |
if out.size != canvas.size:
|
| 140 |
out = out.resize(canvas.size, Image.LANCZOS)
|
| 141 |
|
|
|
|
| 142 |
final = paste_original_back(out, input_image.convert("RGB"), offset)
|
| 143 |
|
|
|
|
| 144 |
fname = f"outpaint_{canvas.size[0]}x{canvas.size[1]}_{int(time.time()*1000)}.png"
|
| 145 |
out_path = os.path.join(OUT_DIR, fname)
|
| 146 |
os.makedirs(os.path.dirname(out_path), exist_ok=True)
|
|
|
|
| 181 |
meta = gr.Markdown("")
|
| 182 |
file_out = gr.File(label="Download PNG")
|
| 183 |
|
|
|
|
| 184 |
def _update_preview(img, pad_px):
|
| 185 |
if img is None:
|
| 186 |
return None, ""
|
|
|
|
| 207 |
)
|
| 208 |
|
| 209 |
if __name__ == "__main__":
|
| 210 |
+
# КРИТИЧЕСКОЕ ИСПРАВЛЕНИЕ: разрешаем /mnt/data
|
| 211 |
+
demo.launch(
|
| 212 |
+
server_name="0.0.0.0",
|
| 213 |
+
server_port=7860,
|
| 214 |
+
inbrowser=False,
|
| 215 |
+
allowed_paths=["/mnt/data"]
|
| 216 |
+
)
|