Augment a dataset
Generate an augmented version of a dataset — flip, rotate, crop, and color ops with correct annotation geometry.
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:
| Op | What 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:
| Op | What 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 15 → Rotate((-15, 15)), --brightness 0.2 → Brightness((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
seedyields 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.