---
title: Augment a dataset
description: Generate an augmented version of a dataset — flip, rotate, crop, and color ops with correct annotation geometry.
section: Workflows
order: 6
---
`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).

```python
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

```python
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.

```python
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:

```python
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

```bash
# 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 `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.