---
title: Tile a dataset
description: Slice every image into a grid of tiles for small-object detection — with correct annotation geometry per tile.
section: Workflows
order: 7
---
`tile_dataset()` slices every image in a dataset into a `rows × cols` grid of smaller tiles and uploads the tiles back through the standard ingest pipeline (SigLIP2 embeddings, auto-tags, CDN thumbnails). It's the standard fix for **small-object detection** — aerial, satellite, and microscopy imagery, where objects are tiny relative to the frame: after tiling, each object occupies a larger fraction of its tile and trains far better. Crucially, every annotation is translated into its tile's local coordinates and **clipped to the tile frame**, so a box or polygon straddling a boundary is split correctly across the adjacent tiles. Native, on the base install (Pillow only, no extra dependencies).

```python
from pictograph import Client
from pictograph.pipelines import tile_dataset

client = Client()

report = tile_dataset(
    client,
    "aerial",
    rows=2,
    cols=2,
    overlap=0.1,          # each tile extends 10% past its edge so boundary objects stay whole
    into="aerial-tiled",  # new dataset; the source's class config is copied
)
print(f"{report.tiles_created} tiles generated from {report.source_images} images")
```

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

## Signature

```python
tile_dataset(
    client: Client,
    source: str,
    *,
    rows: int = 2,
    cols: int = 2,
    overlap: float = 0.0,
    min_visibility: float = 0.1,
    include_empty: bool = True,
    into: str | None = None,
    folder_path: str = "/tiles",
    max_source_images: int | None = None,
    jpeg_quality: int = 95,
    on_progress: Callable[[int, int], None] | None = None,
) -> TileReport
```

- **`rows` / `cols`** — the grid dimensions (e.g. `2 × 2` = four tiles per image).
- **`overlap`** — a fraction (`0.0`–`0.9`) each tile extends past its edge, so an object sitting on a tile boundary appears whole in at least one neighbour.
- **`min_visibility`** — an annotation is dropped from a tile when less than this fraction of its area survives the clip.
- **`include_empty`** — set `False` to skip tiles left with no annotations (Roboflow's "exclude tiles without annotations"); the default keeps them as useful background negatives.

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 = tile_dataset(client, "aerial", rows=3, cols=3, into="aerial-tiled")
print(report.source_images, report.tiles_created, report.annotations_written)
for f in report.failures:
    print(f.image_id, f.reason)
```

## The engine: `pictograph.tile`

Under the pipeline is a standalone, reusable engine you can drive on any local `(image, annotations)` pair — no API call required:

```python
from pictograph.tile import tile_image

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` carries the cropped `image`, the geometry-remapped `annotations`, its grid `row`/`col`, and its `origin` (the tile's top-left corner in source pixels). The grid partitions the image exactly; a box straddling a boundary is clipped (Sutherland–Hodgman for polygons) into every tile it touches.

## CLI

```bash
# slice every image into a 2×2 grid → a new dataset
pictograph tile dataset aerial --into aerial-tiled --rows 2 --cols 2

# a 3×3 grid with 10% overlap, dropping empty tiles
pictograph tile dataset aerial --into aerial-tiled \
    --rows 3 --cols 3 --overlap 0.1 --exclude-empty
```

See `pictograph tile dataset --help` for the full flag set.

## Tiling vs. augmentation

Tiling is a **preprocessing** step — it deterministically slices each image so small objects are bigger relative to their tile. [Augmentation](/docs/workflows/augment.md) is a **version-generation** step — it produces randomized variants (flip/rotate/colour) to expand the effective training set. They compose: tile first for small-object detection, then augment the tiled dataset.

## Notes

- Geometry stays correct per tile: boxes are clipped to the tile, polygons are Sutherland–Hodgman-clipped, keypoints outside a tile are dropped, and an annotation whose visible area falls below `min_visibility` is removed from that tile.
- The pipeline runs deterministically — re-running yields byte-identical output.
- Generated tiles ride the normal ingest path — they get embeddings, auto-tags, and thumbnails just like uploads, so you can search and filter them immediately.