๐ŸŽ“ A1Learner
๐Ÿ”

PyTorch Synthetic Image Data

Synthetic image data is a dataset drawn with tensor operations instead of files. Masks and distances draw bars, rings, and squares on blank tensors.

A bar from a slice

Filling rows 5 to 7 across columns 2 to 13 draws one horizontal band. The bar covers 36 pixels.

import torch

image = torch.zeros(16, 16)
image[5:8, 2:14] = 1.0
print(int(image.sum()))

A ring from distances

Pixels at distance 4.5 to 6.5 from the centre form the ring. The mask counts 64 pixels.

import torch

yy, xx = torch.meshgrid(torch.arange(16), torch.arange(16), indexing="ij")
dist = torch.sqrt((yy - 7.5) ** 2 + (xx - 7.5) ** 2)
ring = (dist >= 4.5) & (dist <= 6.5)
print(int(ring.sum()))

A square from a slice

An 8 by 8 filled block covers 64 pixels. Two slices set its position.

import torch

image = torch.zeros(16, 16)
image[4:12, 4:12] = 1.0
print(int(image.sum()))

Example

import torch


def make_shape(label, gen):
    image = torch.zeros(16, 16)
    if label == 0:
        # bar: a filled horizontal band of rows
        top = int(torch.randint(5, 9, (1,), generator=gen))
        image[top:top + 3, 2:14] = 1.0
    elif label == 1:
        # ring: pixels whose distance from the centre lies in a band
        cy = 7.5 + int(torch.randint(-1, 2, (1,), generator=gen))
        cx = 7.5 + int(torch.randint(-1, 2, (1,), generator=gen))
        yy, xx = torch.meshgrid(
            torch.arange(16), torch.arange(16), indexing="ij"
        )
        dist = torch.sqrt((yy - cy) ** 2 + (xx - cx) ** 2)
        image[(dist >= 4.5) & (dist <= 6.5)] = 1.0
    else:
        # square: a filled 8 by 8 block
        top = int(torch.randint(3, 6, (1,), generator=gen))
        left = int(torch.randint(3, 6, (1,), generator=gen))
        image[top:top + 8, left:left + 8] = 1.0
    return image


gen = torch.Generator().manual_seed(0)
labels = torch.randint(0, 3, (200,), generator=gen)
images = torch.stack([make_shape(int(label), gen) for label in labels])
images = images.reshape(200, 1, 16, 16)

names = ["bar", "ring", "square"]
print(f"Dataset shape: {tuple(images.shape)}")
for i, name in enumerate(names):
    print(f"{name}: {int((labels == i).sum())}")

Key points

  • Mask: a tensor of true and false values that picks pixels by a rule
  • Use slicing to fill a rectangle, like a bar or a square block
  • Use distances from the centre to draw a ring
  • A generator with a fixed seed makes the same dataset every run
  • Jitter: a small random shift in position, so copies of a shape are not identical
  • Each image carries one label, and the label says which rule drew it
  • Drawing with rules gives a labelled dataset with no files and no downloads

In this example, a generator with seed 0 picks 200 labels at random. The make_shape function draws each image on a 16 by 16 blank tensor. Label 0 draws a bar as a filled band of rows. Label 1 draws a ring from distances to the centre. Label 2 draws a square as a filled 8 by 8 block. Positions jitter under the fixed seed, so the shapes sit in slightly different places. The stacked dataset has shape (200, 1, 16, 16). The counts print 53 bars, 75 rings, and 72 squares.

Note

The shared shape rule here is one channel, 16 by 16 pixels, with values in 0 to 1

Try it Yourself

Edit the code, then press Run.

Running is simulated ยท Python Playground

Exercise

A ring is drawn by testing each pixel's distance from the centre. Which pixels belong to the ring?