Sign in Get started

Exports

Build and download dataset exports in COCO, YOLO, CVAT, Pascal VOC, LabelMe, CSV, or canonical Pictograph JSON.

View as Markdown

An export is a ZIP of an annotated dataset in a chosen format, optionally embedding the original image files. Export builds run server-side (a few seconds for hundreds of images, longer for tens of thousands).

Every example below shows the Python SDK call and the equivalent raw REST request. The REST examples authenticate with an X-API-Key header; set PICTOGRAPH_API_KEY in your shell to copy-and-run them.

from pictograph import Client
client = Client()  # reads PICTOGRAPH_API_KEY

Addressing + envelope. An export is identified by its UUID (/exports/{id}) or by its (dataset_name, export_name) pair under /exports/by-name/{dataset}/{export} — the :uuid id route and the /by-name/ prefix never shadow each other. A single export is {"data": {…}}; a collection is {"data": [...], "pagination": {"limit", "offset", "total", "has_more"}} where total is the org-wide count. A signed download URL comes back as {"data": {"download_url": …, "expires_in_minutes": 60}}.

Formats

formatNotes
pictograph (default)Canonical Pictograph JSON — the wire format the SDK consumes
darwinDarwin V7 JSON 2.0 (one per image) — drop-in compatible with V7 dataset imports
cocoCOCO instance segmentation / object detection (Darwin-consistent dialect: polygon holes export as RLE)
yoloYOLO darknet .txt files (one per image)
yolo_obbOriented boxes — Ultralytics YOLO-OBB: class x1 y1 x2 y2 x3 y3 x4 y4, four corners normalized to [0,1]. This is the format yolo obb trains on. Every annotation type exports: a box is a rotated box at angle 0, a polygon reduces to its minimum-area rotated rectangle
dotaOriented boxes — the aerial/satellite standard (labelTxt/): four corners in absolute pixels plus the class name, so it needs no image dimensions and no class-index ordering
cvatCVAT XML — a rotated box exports natively as <box rotation="…">, an exact round-trip
pascal_vocPascal VOC XML (one per image)
datumaroIntel Datumaro / CVAT’s native JSON dataset format
labelmeLabelMe JSON (one per image)
csvFlat CSV — bbox annotations only

Every format understands an oriented (rotated) box. Formats with a native rotated representation (yolo_obb, dota, cvat) keep the angle exactly; the rest receive the box’s four rotated corners as a polygon, so the shape survives losslessly even where the parameterization cannot be expressed.

create

Build a new export. Defaults to wait=True, which blocks until the ZIP is ready.

ArgTypeDefaultNotes
dataset_namestrrequired
namestrrequiredUnique within the dataset
formatExportFormat"pictograph"See table above
include_imagesboolFalseWhen True, the ZIP also bundles the original image bytes
class_filterlist[str] | NoneNoneLimit to these class names
status_filterstr | NoneNoneImage status filter (e.g. "complete")
organize_by_splitboolFalseOrganize the ZIP into train/ valid/ test/ folders by each image’s assigned split (see Train/valid/test split layout below)
waitboolTrueBlock until terminal status
poll_intervalfloat2.0Seconds between status checks
timeoutfloat300.0Max seconds to wait
export = client.exports.create(
    "my-dataset",
    "for-yolov8",
    format="yolo",
    include_images=True,
    class_filter=["car", "truck"],   # None = all classes
    status_filter="complete",        # None = all statuses
    wait=True,
    poll_interval=2.0,
    timeout=600.0,
)
print(export.id, export.status, export.image_count, export.annotation_count)
curl -s -X POST "https://api.pictograph.io/api/v1/developer/exports/" \
  -H "X-API-Key: $PICTOGRAPH_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"dataset_name": "my-dataset", "name": "for-yolov8", "format": "yolo", "include_images": true, "class_filter": ["car", "truck"], "status_filter": "complete"}'

wait=False returns a pending / processing Export. Poll via get or wait_for_completion.

list

Single-page list of exports for the organization. Optionally filter by dataset name or status.

ArgTypeDefaultNotes
dataset_namestr | NoneNoneRestrict to one dataset
statusstr | NoneNonee.g. "completed" / "processing" / "failed"
limitint100Backend cap: 1000
offsetint0Page offset
exports = client.exports.list(limit=20)
for e in exports:
    print(e.dataset_name, e.name, e.status)
curl -s "https://api.pictograph.io/api/v1/developer/exports/?limit=20" \
  -H "X-API-Key: $PICTOGRAPH_API_KEY"

iter

Auto-paging iterator over every export (transparently follows offset).

for e in client.exports.iter(page_size=50):
    print(e.dataset_name, e.name, e.status)
# Page manually with limit + offset until fewer than `limit` rows return.
curl -s "https://api.pictograph.io/api/v1/developer/exports/?limit=50&offset=0" \
  -H "X-API-Key: $PICTOGRAPH_API_KEY"

get

Fetch a single export by (dataset_name, export_name).

export = client.exports.get("my-dataset", "for-yolov8")
print(export.status, export.download_url)
curl -s "https://api.pictograph.io/api/v1/developer/exports/by-name/my-dataset/for-yolov8" \
  -H "X-API-Key: $PICTOGRAPH_API_KEY"

Returns Export.

get_by_id

Fetch a single export by its UUID — the by-id complement to get’s (dataset_name, export_name) lookup.

export = client.exports.get_by_id("export-uuid")
print(export.status, export.download_url)
curl -s "https://api.pictograph.io/api/v1/developer/exports/export-uuid" \
  -H "X-API-Key: $PICTOGRAPH_API_KEY"

Returns Export. A cross-org or missing id is a 404.

download

Stream the ZIP to a local file (chunked). The SDK first fetches a signed download URL, then streams the bytes from storage.

ArgTypeDefaultNotes
dataset_namestrrequired
export_namestrrequired
output_pathstr | PathrequiredLocal destination
chunk_sizeint8 MBStreaming chunk size
from pathlib import Path

client.exports.download(
    "my-dataset", "for-yolov8",
    output_path=Path("./my-dataset.zip"),
)
# Returns a signed download URL valid for 60 minutes; fetch the ZIP from it.
curl -s "https://api.pictograph.io/api/v1/developer/exports/by-name/my-dataset/for-yolov8/download" \
  -H "X-API-Key: $PICTOGRAPH_API_KEY"

The download URL is a signed URL valid for 60 minutes — generated fresh on every call.

download_by_id

Stream the ZIP to a local file by export UUID — the by-id complement to download. The SDK first fetches a signed download URL, then streams the bytes.

ArgTypeDefaultNotes
export_idstrrequiredExport UUID
output_pathstr | PathrequiredLocal destination
chunk_sizeint8 MBStreaming chunk size
from pathlib import Path

path = client.exports.download_by_id("export-uuid", Path("./out.zip"))
print(path)  # Path("./out.zip")
# Returns a signed download_url valid for 60 minutes; fetch the ZIP from it.
curl -s "https://api.pictograph.io/api/v1/developer/exports/export-uuid/download" \
  -H "X-API-Key: $PICTOGRAPH_API_KEY"

Returns Path. A cross-org or missing id is a 404.

wait_for_completion

If you used wait=False, poll until the export reaches a terminal status.

export = client.exports.create("ds", "name", format="coco", wait=False)
# … later
export = client.exports.wait_for_completion("ds", "name", timeout=300.0)
# Poll the get endpoint until "status" becomes "completed" or "failed".
curl -s "https://api.pictograph.io/api/v1/developer/exports/by-name/ds/name" \
  -H "X-API-Key: $PICTOGRAPH_API_KEY"

delete

Removes the export and the stored ZIP. Requires admin or owner role. Other ongoing downloads of the same export will fail mid-stream.

client.exports.delete("my-dataset", "for-yolov8")
curl -s -X DELETE "https://api.pictograph.io/api/v1/developer/exports/by-name/my-dataset/for-yolov8" \
  -H "X-API-Key: $PICTOGRAPH_API_KEY"

Train/valid/test split layout

Assign each image a split (train / val / test) with client.images.set_split, then export with organize_by_split=True to get a directly-trainable ZIP — images and their annotation files are grouped into top-level train/ / valid/ / test/ folders (images with no assigned split go to train).

# 80/10/10, then export a directly-trainable YOLO layout
imgs = list(client.images.iter(dataset_name="my-dataset"))
import random; random.seed(0); random.shuffle(imgs)
n = len(imgs); n_train, n_val = int(n * 0.8), int(n * 0.1)
for i, im in enumerate(imgs):
    client.images.set_split(im.id, "train" if i < n_train else "val" if i < n_train + n_val else "test")

export = client.exports.create(
    "my-dataset", "yolo-split", format="yolo",
    include_images=True, organize_by_split=True,
)

The resulting YOLO ZIP has one root data.yaml pointing at train: train/images, val: valid/images, test: test/images (plus a root classes.txt), so it trains with Ultralytics as-is. For COCO the manifest is written per split (train/annotations.json, …); every per-image format nests its files under the split folder. Without organize_by_split the flat layout is unchanged.

Class filtering

class_filter only includes annotations matching the given class names. Images with no surviving annotations are still included if their status matches status_filter — they get an empty annotation list. Pass class_filter=None (default) to keep every annotation.

Common errors

StatusExceptionCause
404NotFoundErrorDataset or export missing
409ConflictErrorExport name already exists in this dataset
422ValidationErrorUnknown format or status_filter
403ForbiddenErrordelete requires admin+ role
408PollTimeoutErrorwait=True timed out (export keeps building)
Copied to clipboard