Source code for gensbi.models.core.patching

"""Invertible 2D patchify/depatchify — pure einops reshapes (no learned state).

Moved out of recipes.utils so model/flow code can depend on it without pulling
in the recipes package (which imports gensbi.models, creating a cycle).
"""

import jax
from jax import Array
from einops import rearrange


@jax.jit(static_argnames=["size"])
[docs] def patchify_2d(x: Array, size=2): """Patchify a 2D image into a sequence of non-overlapping patches. Invertible 2D patchify via einops reshape. The inverse operation is :func:`depatchify_2d`. Parameters ---------- x : Array Image of shape ``(B, H, W, C)``. ``H`` and ``W`` must each be divisible by ``size``. size : int, optional Patch edge length. Default is 2. Returns ------- Array Token sequence of shape ``(B, T, F)`` where ``T = (H // size) * (W // size)`` and ``F = C * size * size``. Tokens are in raster (row-major) order. """ return rearrange(x, "b (h ph) (w pw) c -> b (h w) (c ph pw)", ph=size, pw=size)
@jax.jit(static_argnames=["size", "grid"])
[docs] def depatchify_2d(x: Array, size=2, grid=None): """Inverse of :func:`patchify_2d`. Parameters ---------- x : Array Patchified tensor of shape ``(B, h*w, C*size*size)``. size : int Patch edge length used by :func:`patchify_2d`. grid : tuple of int, optional The ``(h, w)`` patch grid. The grid cannot be inferred from the token count alone, so it is required for non-square grids. If ``None``, a square grid (``h == w``) is assumed. Returns ------- Array Image of shape ``(B, H, W, C)`` where ``H = h * size`` and ``W = w * size``. Raises ------ ValueError If ``grid`` is ``None`` and the token count is not a perfect square. """ if grid is None: n = x.shape[1] side = int(round(n ** 0.5)) if side * side != n: raise ValueError( f"Cannot infer a square grid from {n} tokens; pass grid=(h, w)." ) h = w = side else: h, w = grid return rearrange( x, "b (h w) (c ph pw) -> b (h ph) (w pw) c", h=h, w=w, ph=size, pw=size )