Tile a dataset
Slice every image into a grid of tiles for small-object detection — with correct annotation geometry per tile.
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).
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
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— setFalseto 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.
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:
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
# 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 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_visibilityis 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.