Spaces:
Running on Zero
Running on Zero
| """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], | |
| } | |