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()