Sign in Get started

Tile & augment

Slice images into tiles for small-object detection, or make flipped, rotated and color-jittered variants. Boxes, polygons and keypoints follow each transform.

View as Markdown
On this page

Tiling slices each image into a grid, so small objects fill more of each frame. Augmenting makes randomized variants, so the model sees more variety. Annotations follow every transform. To do both, tile first, then augment the tiled dataset.

Tile

For aerial, satellite and microscope images:

from pictograph import Client, TileReport

client = Client()

report: TileReport = client.images.tile(
    dataset="aerial",
    rows=2,
    cols=2,
    overlap=0.1,
    into="aerial-tiled",
)
print(f"{report.tiles_created} tiles from {report.source_images} images")
pictograph tile dataset aerial --into aerial-tiled --rows 2 --cols 2 --overlap 0.1

overlap extends each tile past its edge, so an object on a boundary appears whole in a neighbor. include_empty=False (--exclude-empty) skips tiles with no annotations. Every argument: tile.

Augment

from pictograph import AugmentReport, Client
from pictograph.augment import Brightness, HorizontalFlip, Rotate

client = Client()

report: AugmentReport = client.images.augment(
    dataset="road-signs",
    ops=[HorizontalFlip(), Rotate((-15, 15)), Brightness((0.8, 1.2))],
    multiplier=3,
    into="road-signs-aug",
)
print(f"{report.variants_created} variants of {report.source_images} images")
pictograph augment dataset road-signs --into road-signs-aug --multiplier 3 --flip --rotate 15 --brightness 0.2

A CLI flag sets one op’s range: --rotate 15 is Rotate((-15, 15)); pictograph augment ops lists them. drop_classes (--drop-class) removes those classes’ annotations first, and skip_empty=True (--skip-empty) skips images left with none. Every argument: augment.

Ops

A magnitude is a value or a (low, high) range sampled each time; p is the chance an op runs.

Geometric ops change the image and move its annotations:

Op What it does
HorizontalFlip(p=0.5) Mirrors left to right
VerticalFlip(p=0.5) Mirrors top to bottom
Rotate90(k=1) Rotates 90, 180 or 270 degrees with no loss (k=None picks one)
Rotate(degrees=(-15, 15)) Rotates by any angle, growing the canvas so nothing is cut
Resize(width, height) Resizes to a fixed size
Crop(scale=(0.8, 1.0)) Crops a random region that keeps this fraction of each side
Shear(degrees=(-10, 10)) Shears horizontally, keeping the canvas

Photometric ops change pixels only:

Op What it does
Brightness(factor=(0.8, 1.2)) Scales brightness
Contrast(factor=(0.8, 1.2)) Scales contrast
Saturation(factor=(0.8, 1.2)) Scales color saturation
HueShift(degrees=(-20, 20)) Rotates the hue
Grayscale(p=1.0) Converts to grayscale
Blur(radius=(0.0, 2.0)) Applies a Gaussian blur
Noise(amount=(0.0, 0.08)) Adds luminance noise
CutOut(size=(0.1, 0.3), count=1) Erases random rectangles

Output

into names the output dataset, created if missing with the source’s classes. Without into, new images go into the source dataset under /tiles or /augmented. Generated images count toward your image quota.

Geometry

  • Boxes and polygons are clipped to their tile or crop; keypoints outside it are dropped.
  • A rotated box grows to enclose its rotated corners.
  • An annotation with less than min_visibility of its area left is removed.
  • Tiling is deterministic; augmenting is too with a fixed seed.

Local engines

Source: tile/_tiler.py, augment/_engine.py

Both run on a local image and its annotations, with no API call:

from pictograph import Client
from pictograph.tile import tile_image

client = Client()
annotations = client.annotations.get(
    dataset="aerial",
    image="aerial.jpg",
)

tiles = tile_image("aerial.jpg", annotations, rows=2, cols=2, overlap=0.1)
for t in tiles:
    t.image.save(f"tile_r{t.row}_c{t.col}.jpg")
    print(len(t.annotations), "annotations in this tile", t.origin)

Each Tile has the cropped image, its shifted annotations, its row and col, and its origin (its top-left corner in the source).

from pictograph import Client
from pictograph.augment import Augmenter, Brightness, HorizontalFlip, Rotate

client = Client()
annotations = client.annotations.get(
    dataset="road-signs",
    image="photo.jpg",
)

aug = Augmenter([HorizontalFlip(), Rotate((-15, 15)), Brightness((0.8, 1.2))], seed=42)

image, moved = aug(image="photo.jpg", annotations=annotations)  # one variant
variants = aug.generate(image="photo.jpg", annotations=annotations, n=3)  # three variants
print(len(moved), "annotations moved;", len(variants), "variants")

Augmenter takes a file path or a Pillow image, and annotations as models or dicts.

Copied to clipboard