latest (dev)
Copy
Latest development documentation · Updated 2026-10-08
Transforms
Data rarely arrives ready for a model. Images are different sizes, pixel values are in a
different range than a network expects, and you want to augment training data with random
flips and crops. Transforms are small callables that turn one piece of data into another, and
tensorplay.vision.transforms bundles a standard set.
import tensorplay as tp
import tensorplay.vision.transforms as T
A transform is just a callable
Pass a tensor in, get a tensor out:
img = tp.randn(3, 32, 32) # a channel-first image (C, H, W)
print(T.Resize((16, 16))(img).shape) # (3, 16, 16)
The channel-first layout (C, H, W) is TensorPlay’s image convention. Transforms operate on it
directly.
Compose chains transforms
Compose runs a list of transforms in order. This is the standard building block for a
preprocessing or augmentation pipeline:
pipeline = T.Compose([
T.Resize((28, 28)),
T.RandomHorizontalFlip(), # augment: flip with 50% probability
T.Normalize((0.5,), (0.25,)), # center and scale each channel
])
img = tp.randn(3, 48, 48)
out = pipeline(img)
print(out.shape) # (3, 28, 28)
Transforms you will use
Shape and size —
Resize,RandomCrop,CenterCrop,Pad. These change(H, W).Geometry for augmentation —
RandomHorizontalFlip,RandomVerticalFlip,RandomRotation,RandomAffine,RandomPerspective. These randomly perturb the image on each call, which is how you get more varied training data from a fixed dataset.Photometric —
ColorJitter(brightness, contrast, saturation, hue together),Grayscale,GaussianBlur,RandomSolarize,RandomPosterize,RandomEqualize,RandomInvert.Type and range —
Normalize(center and scale each channel),ConvertImageDtype,ToTensor(convert a PIL or NumPy image into an int tensor in the[0, 1]range).
Random combinators
Three wrappers turn plain transforms into randomized policies:
RandomApply([t, ...], p)— run the whole list with probabilityp, pass through otherwise.RandomChoice([t1, t2, ...])— pick one transform at random each call.RandomOrder([t1, t2, ...])— run all of them, in a fresh random order each call.
augment = T.RandomApply([T.ColorJitter(0.4, 0.4, 0.4)], p=0.5)
img = tp.randn(3, 32, 32)
print(augment(img).shape) # (3, 32, 32) — either jittered or untouched
policy = T.RandomChoice([T.Resize((16, 16)), T.Resize((8, 8))])
print(policy(img).shape) # (3, 16, 16) or (3, 8, 8), decided per call
Combinators nest: T.RandomApply([T.RandomOrder([...]), ...]) composes the same way
Compose does.
Reproducibility
Random transforms draw from TensorPlay’s global RNG, so seeding makes a pipeline reproducible:
tp.manual_seed(42)
a = T.RandomCrop(4)(tp.randn(3, 8, 8))
tp.manual_seed(42)
b = T.RandomCrop(4)(tp.randn(3, 8, 8))
print(tp.equal(a, b)) # True — same seed, same input, same crop
In a multi-worker DataLoader, each worker’s RNG is seeded deterministically from the
base seed plus the worker id, so augmentations stay random within a run but reproducible
across runs. See the randomness note for the details.
Transforms on batches
Geometry transforms such as Resize accept a batched (N, C, H, W) tensor and map the
same geometry over every image — handy for augmenting a whole batch in one call:
batch = tp.randn(4, 3, 32, 32)
print(T.Resize((16, 16))(batch).shape) # (4, 3, 16, 16)
Random transforms applied to a batch still roll one random draw per call, so the whole
batch gets the same flip or crop. If every image should get its own coin flip, apply the
transform per-image inside __getitem__ — which is the common pattern anyway.
The functional API
Every transform is a thin object wrapper around a plain function in
tensorplay.vision.transforms.functional — Resize calls functional.resize,
RandomHorizontalFlip calls functional.hflip, and so on. Use the functions directly when
you want the operation without the randomness or the class machinery:
from tensorplay.vision.transforms import functional as F
img = tp.randn(3, 32, 32)
print(F.resize(img, [16, 16]).shape) # (3, 16, 16)
The functional namespace is also where photometric operations such as adjust_brightness,
adjust_contrast, adjust_saturation, adjust_hue, and autocontrast live, and where
InterpolationMode (the nearest/bilinear/bicubic choice for resizing) is defined.
Using a transform in a data pipeline
Transforms are usually applied inside a dataset’s __getitem__ so every sample is preprocessed
as the loader yields it:
from tensorplay.utils.data import Dataset, DataLoader
import tensorplay.vision.transforms as T
class ImageDataset(Dataset):
def __init__(self, images, labels, transform=None):
self.images = images
self.labels = labels
self.transform = transform
def __len__(self):
return len(self.images)
def __getitem__(self, i):
x = self.images[i]
if self.transform is not None:
x = self.transform(x)
return x, self.labels[i]
images = tp.randn(20, 3, 40, 40) # N, C, H, W
labels = tp.randint(0, 10, (20,))
dataset = ImageDataset(images, labels, transform=T.Compose([
T.RandomHorizontalFlip(),
T.Normalize((0.5,), (0.25,)),
]))
loader = DataLoader(dataset, batch_size=4)
for batch_x, batch_y in loader:
print(batch_x.shape, batch_y.shape) # (4, 3, 40, 40) (4,)
break
Augmentation inside __getitem__ also means each epoch sees a slightly different version of
the data, which generally helps the model generalize.
Note: ToTensor converts a PIL image or NumPy array (height, width, channels) into a
channel-first tensor. If you are building a pipeline around a dataset that yields tensors, you
can apply Resize, RandomHorizontalFlip, and Normalize directly to them and skip ToTensor.
Where to go next
The vision reference lists every transform and dataset available.
Models for building the network that consumes these tensors.
Help improve this page
Found an error, an unclear step, or a missing example?

