diffu_test / diffu /data /collate.py
Gabriel's picture
Diffu Studio ZeroGPU Space: app.py + vendored packages + aokit AoT + redesigned panel
333ff0e verified
Raw
History Blame Contribute Delete
3.77 kB
"""Width-bucketing collate for variable-width handwriting lines.
Lines share a fixed height but vary in width, so a batch must be padded to a common width before
stacking. We right-pad with white (1.0 in the ``[-1, 1]`` range) up to the batch max width rounded
to a multiple of ``width_multiple`` (keeps the latent grid divisible by the VAE downscale × patch),
capped at ``max_width``. Style references are already a fixed size, so they stack directly.
Optional white-pad augmentation (``white_pad_prob`` > 0, TRAIN SPLIT ONLY): before bucketing, each
line is independently right-padded with extra white with probability ``white_pad_prob``. This puts
short text on a wider white canvas at train time so the model learns to leave the tail blank — the
exact regime the fill-ratio scalar (model/diffu.py) explains. Val/test pass ``white_pad_prob=0`` so
their canvases stay text-tight for honest metrics.
"""
from __future__ import annotations
import random
from typing import Any
def _round_up(value: int, multiple: int) -> int:
return ((value + multiple - 1) // multiple) * multiple
def collate_lines(
batch: list[dict[str, Any]],
*,
width_multiple: int = 16,
max_width: int = 1024,
white_pad_prob: float = 0.0,
white_pad_max_frac: float = 0.5,
rng: random.Random | None = None,
) -> dict[str, Any]:
"""Collate dataset items into a training batch.
Args:
batch: Items from ``HandwritingLineDataset`` (image / text / writer_id / style_pixel_values).
width_multiple: Padded width is rounded up to this (16 = VAE f8 × patch 2).
max_width: Hard cap on width; wider lines are cropped.
white_pad_prob: Per-line probability of right-padding with extra white BEFORE bucketing
(train-split augmentation; 0 = off). Val/test must pass 0 so canvases stay text-tight.
white_pad_max_frac: Max extra white width as a fraction of the line's natural width.
rng: RNG for the augmentation (inject for determinism); defaults to the module ``random``.
Returns:
``{"images": [B,3,H,W], "texts": list[str], "style_pixel_values": [B,3,S,S],
"writer_ids": list[str], "style_texts": list[str], "style_is_self": list[bool]}``.
"""
import torch
from torch.nn import functional as F
rand = rng or random
images = [b["image"] for b in batch]
if white_pad_prob > 0.0: # train-only: simulate short-text-on-wide-canvas before bucketing
augmented = []
for im in images:
w = im.shape[-1]
if rand.random() < white_pad_prob:
extra = rand.randint(0, int(white_pad_max_frac * w))
if extra > 0:
im = F.pad(im, (0, extra), value=1.0) # right-pad with white
augmented.append(im)
images = augmented
target_w = min(max_width, _round_up(max(im.shape[-1] for im in images), width_multiple))
padded = []
for im in images:
w = im.shape[-1]
if w >= target_w:
padded.append(im[..., :target_w])
else:
padded.append(F.pad(im, (0, target_w - w), value=1.0)) # right-pad with white
return {
"images": torch.stack(padded),
"texts": [str(b["text"]) for b in batch],
"style_pixel_values": torch.stack([b["style_pixel_values"] for b in batch]),
"writer_ids": [str(b["writer_id"]) for b in batch],
# Display-only metadata for the live eval (the style ref's text + whether it was forced to the
# target itself). .get keeps synthetic-data callers (smoketest) working without these keys.
"style_texts": [str(b.get("style_text", "")) for b in batch],
"style_is_self": [bool(b.get("style_is_self", False)) for b in batch],
}