Text-to-Image
Diffusers
Safetensors
flow-matching
pyramidal-unet
256x256
ettin-150m
from-scratch
ra
udit
Instructions to use AiArtLab/Ra with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Diffusers
How to use AiArtLab/Ra with Diffusers:
pip install -U diffusers transformers accelerate
import torch from diffusers import DiffusionPipeline # switch to "mps" for apple devices pipe = DiffusionPipeline.from_pretrained("AiArtLab/Ra", dtype=torch.bfloat16, device_map="cuda") prompt = "Astronaut in a jungle, cold color palette, muted colors, detailed, 8k" image = pipe(prompt).images[0] - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- Draw Things
- DiffusionBee
Download src/make_dataset_people.py from AiArtLab/Ra: direct link, hf CLI and curl.
- Browser
- Download file 7.85 kB
-
https://huggingface.co/AiArtLab/Ra/resolve/main/src/make_dataset_people.py
- Command line
-
hf download hf://AiArtLab/Ra/src/make_dataset_people.py
-
curl -L -o make_dataset_people.py https://huggingface.co/AiArtLab/Ra/resolve/main/src/make_dataset_people.py
7.85 kB
| """Сборка набора по классам (woman/man/cat) из ~/dataset/allphoto_train — тест анатомии. | |
| Зачем: бабочки однородны и анатомию не показывают. Нужен набор, где модель обязана выучить | |
| тело: разные люди, позы, ракурсы, и капшены, которые называют, кто это и что делает. | |
| Классы: woman / man / cat — по N уникальных капшенов на класс (дедуп по первым 60 символам, | |
| иначе один photoshoot даёт десятки почти одинаковых кадров). | |
| python3 src/make_dataset_people.py --n 60 --out ~/dataset/testset_anatomy | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import csv | |
| import math | |
| import random | |
| import re | |
| import shutil | |
| from pathlib import Path | |
| from PIL import Image, ImageOps | |
| SRC = Path.home() / "dataset" / "allphoto_train" | |
| CLASSES = { | |
| "woman": ("woman", "women", "girl"), | |
| "man": ("man", "men", "boy"), | |
| "cat": ("cat",), | |
| } | |
| # бакеты как в moss/src/make_dataset.py: обе стороны кратны 16 (латент /8, потом patch-2), | |
| # соотношение выбирается ближайшим по |log(ratio_источника / ratio_бакета)|, затем ЦЕНТР-КРОП — | |
| # растягивать нельзя, иначе лица и тела деформируются | |
| def buckets(long_side: int = 512, multiple: int = 64) -> dict: | |
| """Бакеты: длинная сторона long_side, обе стороны кратны 16 (латент /8, затем /2).""" | |
| L = long_side | |
| def r16(x): return max(multiple, int(round(x / multiple)) * multiple) | |
| return { | |
| "1:1": (L, L), | |
| "4:3": (L, r16(L * 3 / 4)), | |
| "3:4": (r16(L * 3 / 4), L), | |
| "3:2": (L, r16(L * 2 / 3)), | |
| "2:3": (r16(L * 2 / 3), L), | |
| "16:9": (L, r16(L * 9 / 16)), | |
| "9:16": (r16(L * 9 / 16), L), | |
| } | |
| BUCKETS = buckets(512) | |
| # для людей требуем, чтобы в кадре было тело, а не только лицо: бабочки-портреты анатомию не учат | |
| BODY_WORDS = ("standing", "walking", "sitting", "seated", "jeans", "shorts", "dress", "skirt", | |
| "shirt", "legs", "arms", "hands", "wearing", "full body", "walks", "stands") | |
| def classify(cap: str) -> str | None: | |
| low = cap.lower() | |
| for cls, words in CLASSES.items(): | |
| if any(re.search(rf"\b{w}\b", low) for w in words): | |
| # «man» внутри «woman» уже отсечён \b; но «woman» не должна становиться «man» | |
| if cls == "man" and re.search(r"\bwoman\b", low): | |
| return "woman" | |
| return cls | |
| return None | |
| def trim_black(im: Image.Image, thr: float = 12.0, var: float = 8.0) -> Image.Image: | |
| """Срезает однородные чёрные поля по краям (в allphoto_train их полно: 71 из 171 кадра).""" | |
| import numpy as np | |
| a = np.asarray(im.convert("L"), dtype=np.float32) | |
| def uniform_edge(line): # чёрная и без структуры -> рамка, а не ночная сцена | |
| return line.mean() < thr and line.std() < var | |
| top, bot, left, right = 0, a.shape[0], 0, a.shape[1] | |
| while top < bot - 8 and uniform_edge(a[top]): top += 1 | |
| while bot > top + 8 and uniform_edge(a[bot - 1]): bot -= 1 | |
| while left < right - 8 and uniform_edge(a[top:bot, left]): left += 1 | |
| while right > left + 8 and uniform_edge(a[top:bot, right - 1]): right -= 1 | |
| return im.crop((left, top, right, bot)) if (left or top or right != a.shape[1] or bot != a.shape[0]) else im | |
| def plan_size(w: int, h: int, max_upscale: float = 1.4): | |
| """(w,h) -> (label, tw, th, cw, ch) или None, если источника не хватает (как в moss).""" | |
| ratio = w / h | |
| label, (tw, th) = min(BUCKETS.items(), key=lambda kv: abs(math.log(ratio / (kv[1][0] / kv[1][1])))) | |
| if ratio > tw / th: | |
| cw, ch = round(h * tw / th), h | |
| else: | |
| cw, ch = w, round(w * th / tw) | |
| cw, ch = min(cw, w), min(ch, h) | |
| if cw * max_upscale < tw or ch * max_upscale < th: | |
| return None | |
| return label, tw, th, cw, ch | |
| def load_bucketed(path: Path, max_upscale: float = 1.4): | |
| """EXIF-поворот -> срез чёрных полей -> ближайший бакет -> центр-кроп -> ресайз.""" | |
| im = ImageOps.exif_transpose(Image.open(path)).convert("RGB") | |
| im = trim_black(im) | |
| plan = plan_size(im.width, im.height, max_upscale) | |
| if plan is None: | |
| return None, None | |
| label, tw, th, cw, ch = plan | |
| left, top = (im.width - cw) // 2, (im.height - ch) // 2 | |
| return im.crop((left, top, left + cw, top + ch)).resize((tw, th), Image.LANCZOS), label | |
| def main(): | |
| ap = argparse.ArgumentParser() | |
| ap.add_argument("--n", type=int, default=60, help="картинок на класс") | |
| ap.add_argument("--eval-n", type=int, default=4, help="сколько на класс отложить для показа") | |
| ap.add_argument("--max-upscale", type=float, default=1.4) | |
| ap.add_argument("--long-side", type=int, default=512) | |
| ap.add_argument("--multiple", type=int, default=64) | |
| ap.add_argument("--seed", type=int, default=0) | |
| ap.add_argument("--out", default=str(Path.home() / "dataset" / "testset_anatomy")) | |
| args = ap.parse_args() | |
| global BUCKETS | |
| BUCKETS = buckets(args.long_side, args.multiple) | |
| out = Path(args.out) | |
| for sub in ("train", "eval"): | |
| (out / sub).mkdir(parents=True, exist_ok=True) | |
| caps = sorted(SRC.glob("*.txt")) | |
| rng = random.Random(args.seed) | |
| rng.shuffle(caps) | |
| seen_prefix: set[str] = set() | |
| picked: dict[str, list[tuple[Path, str]]] = {c: [] for c in CLASSES} | |
| need = args.n + args.eval_n | |
| for f in caps: | |
| cap = " ".join(f.read_text().split()) | |
| if len(cap) < 40: | |
| continue | |
| cls = classify(cap) | |
| if cls is None or len(picked[cls]) >= need: | |
| continue | |
| low = cap.lower() | |
| if cls == "cat" and re.search(r"\b(dog|puppy|kitten|rabbit)\b", low) and "cat" not in low: | |
| continue | |
| if cls in ("woman", "man") and not any(w in low for w in BODY_WORDS): | |
| continue | |
| key = cap[:60].lower() | |
| if key in seen_prefix: | |
| continue | |
| img = f.with_suffix(".jpg") | |
| if not img.exists(): | |
| continue | |
| seen_prefix.add(key) | |
| picked[cls].append((img, cap)) | |
| if all(len(v) >= need for v in picked.values()): | |
| break | |
| rows = [] | |
| for cls, items in picked.items(): | |
| for i, (img, cap) in enumerate(items): | |
| split = "eval" if i < args.eval_n else "train" | |
| im, bucket = load_bucketed(img) | |
| if im is None: | |
| continue | |
| name = f"{cls}_{i:03d}" | |
| im.save(out / split / f"{name}.jpg", quality=92) | |
| (out / split / f"{name}.txt").write_text(cap + "\n") | |
| rows.append({"split": split, "class": cls, "name": name, "w": im.width, "h": im.height, | |
| "bucket": bucket, "caption": cap}) | |
| print(f" {split}/{name} {im.width}x{im.height} | {cap[:80]}") | |
| with open(out / "index.csv", "w", newline="") as fh: | |
| w = csv.DictWriter(fh, fieldnames=["split", "class", "name", "w", "h", "bucket", "caption"]) | |
| w.writeheader(); w.writerows(rows) | |
| per = {c: sum(1 for r in rows if r["class"] == c and r["split"] == "train") for c in CLASSES} | |
| print(f"\nитого: {sum(per.values())} train {per} + {sum(1 for r in rows if r['split']=='eval')} eval" | |
| f" -> {out}") | |
| if __name__ == "__main__": | |
| main() | |