Sign in Get started

Augment a dataset

Generate an augmented version of a dataset — flip, rotate, crop, and color ops with correct annotation geometry.

View as Markdown

augment_dataset() generates an augmented version of a dataset: for every source image it produces N variants — flipped, rotated, cropped, colour-jittered — and uploads them back through the standard ingest pipeline (SigLIP2 embeddings, auto-tags, CDN thumbnails). Crucially, it remaps the annotation geometry for every variant, so a horizontal flip moves each bounding box, a rotation rotates each polygon’s points, and a crop clips and drops out-of-frame objects. This expands the effective training set the way “generate a version” does in other CV tools — natively, on the base install (Pillow only, no extra dependencies).

from pictograph import Client
from pictograph.augment import HorizontalFlip, Rotate, Brightness
from pictograph.pipelines import augment_dataset

client = Client()

report = augment_dataset(
    client,
    "road-signs",
    ops=[HorizontalFlip(), Rotate((-15, 15)), Brightness((0.8, 1.2))],
    multiplier=3,
    into="road-signs-aug",   # new dataset; the source's class config is copied
)
print(f"{report.variants_created} images generated across {report.source_images} originals")

Pass into=None (or the source’s own name) to append the variants into the source dataset itself, under an /augmented virtual folder. Every generated image counts toward your organization’s image quota, exactly like a normal upload.

Signature

augment_dataset(
    client: Client,
    source: str,
    ops: Sequence[Augmentation],
    *,
    multiplier: int = 3,
    into: str | None = None,
    include_original: bool = True,
    folder_path: str = "/augmented",
    seed: int | None = None,
    max_source_images: int | None = None,
    jpeg_quality: int = 95,
    drop_classes: Iterable[str] | None = None,
    skip_empty: bool = False,
    on_progress: Callable[[int, int], None] | None = None,
) -> AugmentReport

Preprocessing (applied before augmentation, mirroring “generate a version”): drop_classes removes annotations of the named classes (and drops them from a new target’s class config); skip_empty=True skips a source image left with no annotations (Roboflow’s “filter null”), counted in report.skipped_empty. From the CLI: --drop-class person --drop-class bike --skip-empty.

Like every workflow, it returns a report rather than raising on a single bad image — inspect report.failures to retry the affected source images.

report = augment_dataset(client, "road-signs", ops=[HorizontalFlip()], into="road-signs-aug")
print(report.source_images, report.variants_created, report.annotations_written)
for f in report.failures:
    print(f.image_id, f.reason)

The engine: pictograph.augment

Under the pipeline is a standalone, reusable engine you can drive on any local (image, annotations) pair — no API call required. Compose ops into an Augmenter and produce reproducible variants:

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

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

image, annotations = aug("photo.jpg", annotations)      # one variant
variants = aug.generate("photo.jpg", annotations, n=3)  # three distinct, reproducible variants

Augmenter accepts a file path or an open Pillow image, and annotations as typed models or raw dicts. With a seed, the sequence of variants is deterministic (re-running yields identical output) while each variant still differs from the last.

Available ops

Op magnitudes accept either a fixed value or a (low, high) range sampled per application (e.g. Rotate((-15, 15)), Brightness((0.8, 1.2))). p is the probability an op fires.

Geometric — transform the image and remap annotation geometry:

OpWhat it does
HorizontalFlip(p=0.5)Mirror left↔right.
VerticalFlip(p=0.5)Mirror top↔bottom.
Rotate90(k=1)Lossless 90/180/270° rotation (k=None picks one at random).
Rotate(degrees=(-15, 15))Arbitrary rotation; the canvas expands so nothing is cropped. Boxes grow to the axis-aligned enclosure of the rotated box.
Resize(width, height)Resize to a fixed size; scales geometry.
Crop(scale=(0.8, 1.0))Random crop keeping a fraction of each side; clips geometry and drops objects below min_visibility.
Shear(degrees=(-10, 10))Horizontal shear; keeps the canvas and clips geometry.

Photometric — change pixels only, geometry unchanged:

OpWhat it does
Brightness(factor=(0.8, 1.2))Scale brightness.
Contrast(factor=(0.8, 1.2))Scale contrast.
Saturation(factor=(0.8, 1.2))Scale colour saturation.
HueShift(degrees=(-20, 20))Rotate the hue channel.
Grayscale(p=1.0)Convert to grayscale.
Blur(radius=(0.0, 2.0))Gaussian blur.
Noise(amount=(0.0, 0.08))Additive luminance noise.
CutOut(size=(0.1, 0.3), count=1)Erase random rectangles (random-erasing regularization).

CLI

# generate a 3× augmented copy into a new dataset
pictograph augment dataset road-signs --into road-signs-aug \
    --multiplier 3 --flip --rotate 15 --brightness 0.2

# list every op and its flag
pictograph augment ops

Each flag maps to an op with a sensible strength (--rotate 15Rotate((-15, 15)), --brightness 0.2Brightness((0.8, 1.2))). See pictograph augment dataset --help for the full flag set.

Notes

  • Geometry stays correct across every op. Flips and 90° rotations are lossless; arbitrary rotations expand the canvas so no object is lost; crops clip polygons (Sutherland–Hodgman) and drop out-of-frame keypoints.
  • The pipeline runs sequentially, so a fixed seed yields byte-identical output across re-runs.
  • Generated images ride the normal ingest path — they get embeddings, auto-tags, and thumbnails just like uploads, so you can search and filter them immediately.
Copied to clipboard