File size: 6,712 Bytes
63c9b21 | 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 | #!/usr/bin/env python3
import argparse
import os
import re
import glob
from collections import Counter
from PIL import Image, ImageDraw, ImageFont
def detect_prefix(in_dir):
"""
Возвращает определённый префикс по файлам в папке.
Ищем имена по шаблону: <prefix>_e######_##_*.png
Если найден ровно один префикс — возвращаем его.
Если найдено несколько — возвращаем наиболее частый и предупреждаем.
Если ничего не найдено — возвращаем None.
"""
pat = re.compile(r"^([^_]+)_e(\d{6})_(\d{2})_.+\.png$", re.IGNORECASE)
prefixes = []
for p in glob.glob(os.path.join(in_dir, "*.png")):
m = pat.match(os.path.basename(p))
if m:
prefixes.append(m.group(1))
if not prefixes:
return None
cnt = Counter(prefixes)
if len(cnt) == 1:
return prefixes[0]
# несколько префиксов — выбрать самый частый
most_common, freq = cnt.most_common(1)[0]
print(f"Найдено несколько префиксов; выбран наиболее частый: '{most_common}' (встречается {freq} раз).")
return most_common
def build_grid_for_prefix(in_dir, prefix, out_name, args):
"""
Основная логика: собирает картинки с данным префиксом в сетку и сохраняет.
"""
if out_name is None:
out_name = f"{prefix}_grid.png"
out = os.path.join(in_dir, out_name)
pat = re.compile(rf"^{re.escape(prefix)}_e(\d{{6}})_(\d{{2}})_.+\.png$", re.IGNORECASE)
paths = [p for p in glob.glob(os.path.join(in_dir, "*.png")) if pat.search(os.path.basename(p))]
if not paths:
print(f"[{prefix}] Не нашёл файлов по шаблону {prefix}_e******_**_*.png в {in_dir}")
return
items = []
for p in paths:
m = pat.search(os.path.basename(p))
epoch = int(m.group(1))
prompt_idx = int(m.group(2))
items.append((epoch, prompt_idx, p))
epochs = sorted({e for e, _, _ in items})
rows = sorted({i for _, i, _ in items})
table = {(e, i): p for e, i, p in items}
# Определяем максимальный размер ячеек
max_w = max_h = 0
for _, _, p in items:
with Image.open(p) as im:
max_w = max(max_w, im.width)
max_h = max(max_h, im.height)
cell_w, cell_h = max_w, max_h
# Параметры (можно переопределить через args)
MARGIN = args.margin
PAD = args.padding
GX = args.gapx
GY = args.gapy
HEADER = args.header
ROWLBL = args.rowlbl
BG = (12, 12, 12)
FG = (230, 230, 230)
W = MARGIN + ROWLBL + len(epochs) * (cell_w + 2 * PAD) + (len(epochs) - 1) * GX + MARGIN
H = MARGIN + HEADER + len(rows) * (cell_h + 2 * PAD) + (len(rows) - 1) * GY + MARGIN
canvas = Image.new("RGB", (W, H), BG)
draw = ImageDraw.Draw(canvas)
try:
font = ImageFont.truetype("arial.ttf", 18)
font_small = ImageFont.truetype("arial.ttf", 14)
except Exception:
font = ImageFont.load_default()
font_small = ImageFont.load_default()
# Заголовки колонок (эпохи)
for c, e in enumerate(epochs):
x0 = MARGIN + ROWLBL + c * (cell_w + 2 * PAD + GX)
xc = x0 + (cell_w + 2 * PAD) // 2
draw.text((xc, MARGIN + HEADER // 2), f"e{e:06d}", fill=FG, anchor="mm", font=font)
# Подписи строк (prompts)
for r, i in enumerate(rows):
y0 = MARGIN + HEADER + r * (cell_h + 2 * PAD + GY)
yc = y0 + (cell_h + 2 * PAD) // 2
draw.text((MARGIN + ROWLBL // 2, yc), f"p{i:02d}", fill=FG, anchor="mm", font=font)
# Размещение изображений
for r, i in enumerate(rows):
for c, e in enumerate(epochs):
x0 = MARGIN + ROWLBL + c * (cell_w + 2 * PAD + GX) + PAD
y0 = MARGIN + HEADER + r * (cell_h + 2 * PAD + GY) + PAD
box = (x0, y0, x0 + cell_w, y0 + cell_h)
draw.rectangle(box, outline=(60, 60, 60), width=1)
p = table.get((e, i))
if not p:
draw.text((x0 + cell_w // 2, y0 + cell_h // 2), "N/A", fill=(160, 160, 160), anchor="mm", font=font_small)
continue
with Image.open(p) as im:
scale = min(cell_w / im.width, cell_h / im.height, 1.0)
nw, nh = int(im.width * scale), int(im.height * scale)
if scale != 1.0:
im = im.resize((nw, nh), Image.LANCZOS)
ox = x0 + (cell_w - nw) // 2
oy = y0 + (cell_h - nh) // 2
canvas.paste(im, (ox, oy))
canvas.save(out, "PNG")
print(f"[{prefix}] Сохранено: {out}")
print(f"[{prefix}] Эпох: {len(epochs)} | Промптов: {len(rows)} | Картинок: {len(items)}")
def main():
ap = argparse.ArgumentParser(description="Склейка превью в сетку: строки=промпты (00..), столбцы=эпохи (e000001..)")
ap.add_argument("--dir", default=".", help="папка с PNG (по умолчанию текущая)")
ap.add_argument("--prefix", default=None, help="префикс имён файлов (до '_'). Если не указан — будет определён автоматически")
ap.add_argument("--out", default=None, help="имя итогового PNG (по умолчанию: <prefix>_grid.png)")
ap.add_argument("--margin", type=int, default=16)
ap.add_argument("--padding", type=int, default=10)
ap.add_argument("--gapx", type=int, default=8)
ap.add_argument("--gapy", type=int, default=8)
ap.add_argument("--header", type=int, default=40)
ap.add_argument("--rowlbl", type=int, default=60)
args = ap.parse_args()
in_dir = os.path.abspath(args.dir)
if args.prefix:
prefix = args.prefix
else:
prefix = detect_prefix(in_dir)
if prefix is None:
print(f"Не нашёл файлов, соответствующих шаблону <prefix>_e######_##_*.png в папке {in_dir}")
return
print("Авто-определён префикс:", prefix)
build_grid_for_prefix(in_dir, prefix, args.out, args)
if __name__ == "__main__":
main()
|