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?