Sign in Get started

Images

List a dataset's images, and upload, fetch, download, tag, review, split, or delete them.

View as Markdown

This page covers listing a dataset’s images and the per-image operations. For bulk annotation work across many images, see the batch resource.

An image is its path. In REST, a single image is addressed as {dataset}/{directory}/{filename}:

GET /api/v1/developer/images/road-signs/train/stop-sign-0421.jpg
                             └dataset─┘ └dir┘ └── filename ──┘

Directories nest to any depth; an image at the root is road-signs/stop-sign-0421.jpg.

In the SDK and CLI you pass the dataset name and the filename - the directory is resolved for you. A filename living in two directories of the same dataset is ambiguous and raises; narrow it with list(directory_path=...).

list

List a dataset’s images, newest first, filtered by directory, stage, split, or model confidence. Returns a single page; use iter to page over all of them.

Source: Images.list

Arg Type Default Notes
dataset_name str required Dataset name.
directory_path str | None None Restrict to one virtual directory, e.g. /train. None lists all
filename str | None None Exact-filename lookup across directories
status str | None None Restrict to a stage: new / annotate / review / complete
split ImageSplit | None None Restrict to train / val / test
include_archived bool False Include soft-deleted (archived) images
min_confidence_lt float | None None Keep only images whose model confidence is below this (0-1)
limit int 100 Page size, capped at 1000
offset int 0 Page offset
images = client.images.list(
    dataset_name="road-signs",
    directory_path="/train",
    limit=50,
)
for img in images:
    print(img.filename, img.status, img.annotation_count, img.min_confidence)
pictograph images list road-signs --directory /train --limit 50
pictograph images list road-signs --status complete --min-confidence-lt 0.9
curl -s "https://api.pictograph.io/api/v1/developer/images/?dataset=road-signs&directory_path=/train&status=complete&limit=50&offset=0" \
  -H "X-API-Key: $PICTOGRAPH_API_KEY"

Returns list[Image]

Image · 18 fields
class Image(BaseModel):
    """An image within a Pictograph dataset."""
    id: str
    dataset_id: str | None = None
    filename: str
    status: Literal['new', 'annotate', 'review', 'complete'] = 'new'
    split: Optional[Literal['train', 'val', 'test']] = None
    annotation_count: int = 0
    min_confidence: float | None = None
    file_size: int = 0
    width: int | None = None
    height: int | None = None
    content_type: str | None = None
    directory_path: str | None = None
    tags: list[str] = []
    is_archived: bool = False
    image_url: str | None = None
    thumbnail_url: str | None = None
    annotation_url: str | None = None
    created_at: datetime

The response shape is {"data": [...], "pagination": {"limit": 50, "offset": 0, "total": 1234, "has_more": true}} - total is the dataset-wide count for the active filters, so has_more is authoritative.

iter

Auto-page over every image in a dataset, with no offset bookkeeping. Materialize with .all(), or peek the first match with .first(). Filters mirror list.

Source: Images.iter

Arg Type Default Notes
dataset_name str required Dataset name.
directory_path str | None None Restrict to one virtual directory; None iterates all
filename str | None None Exact-filename lookup across directories
status str | None None Restrict to a stage; None iterates all
split ImageSplit | None None Restrict to train / val / test
include_archived bool False Include archived images
min_confidence_lt float | None None Iterate only images with model confidence below this (0-1)
page_size int 100 Items per round-trip, capped at 1000
max_total int | None None Stop after this many items; None yields all
# Walk an entire directory, paging transparently
for img in client.images.iter(dataset_name="road-signs", directory_path="/train"):
    print(img.filename)

# Cap the total and materialize to a list
complete = client.images.iter(
    dataset_name="road-signs",
    status="complete",
    max_total=500,
).all()

# Active learning: page the images a model was least confident about.
# Every listed image carries `min_confidence` (1.0 = certain).
for img in client.images.iter(dataset_name="road-signs", min_confidence_lt=0.9):
    print(img.filename, img.min_confidence)
# The CLI list command pages for you - --limit is a total, not a page size.
pictograph images list road-signs --directory /train --limit 5000
# iter hits the same endpoint as list, walking offset until
# pagination.has_more is false.
curl -s "https://api.pictograph.io/api/v1/developer/images/?dataset=road-signs&directory_path=/train&limit=100&offset=100" \
  -H "X-API-Key: $PICTOGRAPH_API_KEY"

Returns OffsetPager[Image]

Image · 18 fields
class Image(BaseModel):
    """An image within a Pictograph dataset."""
    id: str
    dataset_id: str | None = None
    filename: str
    status: Literal['new', 'annotate', 'review', 'complete'] = 'new'
    split: Optional[Literal['train', 'val', 'test']] = None
    annotation_count: int = 0
    min_confidence: float | None = None
    file_size: int = 0
    width: int | None = None
    height: int | None = None
    content_type: str | None = None
    directory_path: str | None = None
    tags: list[str] = []
    is_archived: bool = False
    image_url: str | None = None
    thumbnail_url: str | None = None
    annotation_url: str | None = None
    created_at: datetime

get

Fetch metadata for a single image.

Source: Images.get

Arg Type Default Notes
dataset_name str required Dataset name.
image str required Filename, or the image UUID
image = client.images.get(
    dataset_name="road-signs",
    image="stop-sign-0421.jpg",
)
print(image.filename, image.status, image.annotation_count)
pictograph images get road-signs stop-sign-0421.jpg
curl -s "https://api.pictograph.io/api/v1/developer/images/road-signs/train/stop-sign-0421.jpg/metadata" \
  -H "X-API-Key: $PICTOGRAPH_API_KEY"

# Embed the annotations in the same response:
curl -s "https://api.pictograph.io/api/v1/developer/images/road-signs/train/stop-sign-0421.jpg/metadata?include_annotations=true" \
  -H "X-API-Key: $PICTOGRAPH_API_KEY"

Returns Image

Image · 18 fields
class Image(BaseModel):
    """An image within a Pictograph dataset."""
    id: str
    dataset_id: str | None = None
    filename: str
    status: Literal['new', 'annotate', 'review', 'complete'] = 'new'
    split: Optional[Literal['train', 'val', 'test']] = None
    annotation_count: int = 0
    min_confidence: float | None = None
    file_size: int = 0
    width: int | None = None
    height: int | None = None
    content_type: str | None = None
    directory_path: str | None = None
    tags: list[str] = []
    is_archived: bool = False
    image_url: str | None = None
    thumbnail_url: str | None = None
    annotation_url: str | None = None
    created_at: datetime

tags are the user image tags that bulk_tag writes; min_confidence is the lowest per-annotation model confidence on the image (1.0 = certain, or human-drawn). Annotations live on the annotations resource: call client.annotations.get(dataset_name="road-signs", image="stop-sign-0421.jpg") to fetch them.

upload

Upload a local file to a dataset. The SDK uses the three-step signed-URL flow: request a signed upload URL, PUT the bytes straight to storage, then register the image. Bytes never relay through the API, which is faster and avoids the request-body size limit.

Source: Images.upload

Arg Type Default Notes
dataset_name str required Dataset name.
file_path str | Path required Local file. Dimensions are read client-side
directory_path str "/" Virtual directory, e.g. /cars
filename str | None basename Override the destination filename
content_type str | None inferred Override the MIME type
progress callable | None None Called with (bytes_sent, total_bytes)
from pathlib import Path

image = client.images.upload(
    dataset_name="road-signs",
    file_path=Path("./photo.jpg"),
    directory_path="/cars",
)
print(image.id, image.filename, image.directory_path)
pictograph images upload road-signs ./photo.jpg --directory /cars
# Step 1 - request a signed upload URL.
curl -s -X POST "https://api.pictograph.io/api/v1/developer/images/upload-url" \
  -H "X-API-Key: $PICTOGRAPH_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "dataset": "road-signs",
    "filename": "photo.jpg",
    "directory_path": "/cars",
    "content_type": "image/jpeg"
  }'

# Step 2 - PUT the raw bytes to the returned upload_url (no API key on that URL).
curl -s -X PUT "<upload_url>" \
  -H "Content-Type: image/jpeg" \
  --upload-file ./photo.jpg

# Step 3 - register the uploaded blob. Same field names as step 1; the server
# derives the storage path itself, so nothing storage-internal round-trips.
curl -s -X POST "https://api.pictograph.io/api/v1/developer/images/register" \
  -H "X-API-Key: $PICTOGRAPH_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "dataset": "road-signs",
    "filename": "photo.jpg",
    "directory_path": "/cars",
    "file_size": 123456,
    "content_type": "image/jpeg",
    "width": 1920,
    "height": 1080
  }'

Register returns the full canonical Image ({"data": {...}}), so no follow-up metadata fetch is needed. Raises ConflictError (409) if a file with the same name already exists in the same directory.

If you would rather hand the bytes to the API in one request, POST /upload relays the file and does all three steps server-side:

curl -s -X POST "https://api.pictograph.io/api/v1/developer/images/upload" \
  -H "X-API-Key: $PICTOGRAPH_API_KEY" \
  -F "dataset=road-signs" \
  -F "directory_path=/cars" \
  -F "file=@./photo.jpg"

Returns Image

Image · 18 fields
class Image(BaseModel):
    """An image within a Pictograph dataset."""
    id: str
    dataset_id: str | None = None
    filename: str
    status: Literal['new', 'annotate', 'review', 'complete'] = 'new'
    split: Optional[Literal['train', 'val', 'test']] = None
    annotation_count: int = 0
    min_confidence: float | None = None
    file_size: int = 0
    width: int | None = None
    height: int | None = None
    content_type: str | None = None
    directory_path: str | None = None
    tags: list[str] = []
    is_archived: bool = False
    image_url: str | None = None
    thumbnail_url: str | None = None
    annotation_url: str | None = None
    created_at: datetime

Supported extensions: .jpg, .jpeg, .png, .webp, .bmp, .tif, .tiff, .gif, .heic. HEIC is auto-converted to PNG server-side.

bulk_upload

Upload many local files to one directory in a single efficient pass, up to 500 files per call. Rather than the per-file three-step flow, this makes one bulk-upload-url call, PUTs each file straight to storage, then makes one bulk-register call: two round-trips plus N PUTs, not 3N. A filename collision lands in failed rather than failing the whole batch.

Source: Images.bulk_upload

Arg Type Default Notes
dataset_name str required Dataset name.
file_paths Sequence[str | Path] required Local files, up to 500
directory_path str "/" Virtual directory for every file in the batch
max_workers int 8 Parallel upload threads
progress callable | None None Called with (done, total)
result = client.images.bulk_upload(
    dataset_name="road-signs",
    file_paths=["a.jpg", "b.jpg"],
    directory_path="/",
)
print(result.count, "registered,", len(result.failed), "failed")
for img in result.succeeded:
    print(img.filename, img.id, img.status)
pictograph images bulk-upload road-signs ./a.jpg ./b.jpg --directory /
# Step 1 - request signed upload URLs for the whole batch.
curl -s -X POST "https://api.pictograph.io/api/v1/developer/images/bulk-upload-url" \
  -H "X-API-Key: $PICTOGRAPH_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "dataset": "road-signs",
    "images": [
      {"filename": "a.jpg", "directory_path": "/", "content_type": "image/jpeg"},
      {"filename": "b.jpg", "directory_path": "/", "content_type": "image/jpeg"}
    ]
  }'

# Step 2 - PUT each file's bytes to its returned upload_url.
curl -s -X PUT "<upload_url_for_a>" -H "Content-Type: image/jpeg" --upload-file ./a.jpg

# Step 3 - register the uploaded blobs in one call.
curl -s -X POST "https://api.pictograph.io/api/v1/developer/images/bulk-register" \
  -H "X-API-Key: $PICTOGRAPH_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "dataset": "road-signs",
    "images": [
      {"filename": "a.jpg", "directory_path": "/", "file_size": 12345,
       "content_type": "image/jpeg", "width": 640, "height": 480},
      {"filename": "b.jpg", "directory_path": "/", "file_size": 23456,
       "content_type": "image/jpeg", "width": 800, "height": 600}
    ]
  }'

Returns BulkUploadResult

BulkUploadResult · 2 fields
class BulkUploadResult(BaseModel):
    """Outcome of Images.bulk_upload."""
    succeeded: list[Image]
    failed: list[BulkUploadFailure]

The REST response is {"data": {"succeeded": [...], "failed": [...], "count": N}}

  • succeeded carries the full canonical image per registered row, and failed carries {filename, directory_path, error} per declined item. The SDK returns BulkUploadResult with succeeded (list[Image]), failed (list[BulkUploadFailure]), and count.

upload_from_directory

Upload a whole local directory tree, recreating its structure on the dataset. Batches the signed-URL and register calls the same way bulk_upload does, and creates the dataset if it does not exist.

Source: Images.upload_from_directory

Arg Type Default Notes
dataset_name str required Dataset name.
directory str | Path required Local directory, walked recursively
organize_by_class bool True ImageFolder mode: the first subdirectory level becomes the virtual directory
preserve_structure bool False Recreate the full nested tree instead
parallel bool True Upload concurrently
max_workers int 8 Parallel upload threads
skip_existing bool True Skip filenames already present
create_if_missing bool True Create the dataset if it does not exist
progress callable | None None Called with (done, total, filename)
report = client.images.upload_from_directory(
    dataset_name="road-signs",
    directory="./photos",
    organize_by_class=True,
    parallel=True,
    max_workers=8,
)
print(report.images_uploaded, len(report.failures))
pictograph images upload-directory road-signs ./photos --by-class --workers 8
# The helper drives these two endpoints, up to 500 images per call.
#   POST /api/v1/developer/images/bulk-upload-url   (request many signed URLs)
#   POST /api/v1/developer/images/bulk-register     (register many uploaded blobs)
curl -s -X POST "https://api.pictograph.io/api/v1/developer/images/bulk-upload-url" \
  -H "X-API-Key: $PICTOGRAPH_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "dataset": "road-signs",
    "images": [
      {"filename": "a.jpg", "directory_path": "/cars", "content_type": "image/jpeg"},
      {"filename": "b.jpg", "directory_path": "/cars", "content_type": "image/jpeg"}
    ]
  }'

Returns UploadReport

UploadReport · 5 fields
class UploadReport(BaseModel):
    """Outcome of an Images.upload_from_directory call."""
    dataset_name: str
    images_attempted: int = 0
    images_uploaded: int = 0
    images_skipped: int = 0
    failures: list[UploadFailure] = []

download

Stream the original image bytes to a local file, chunked so large images are safe. Bytes land in a sibling .part file and are renamed onto output_path only once the transfer completes, so a failed download never leaves a partial file behind.

Source: Images.download

Arg Type Default Notes
dataset_name str required Dataset name.
image str required Filename, or the image UUID
output_path str | Path required Local destination; parents are created
chunk_size int 8388608 Bytes per chunk (8 MiB)
client.images.download(
    dataset_name="road-signs",
    image="stop-sign-0421.jpg",
    output_path="./photo.jpg",
)
pictograph images download road-signs stop-sign-0421.jpg --output ./photo.jpg
curl -s "https://api.pictograph.io/api/v1/developer/images/road-signs/train/stop-sign-0421.jpg" \
  -H "X-API-Key: $PICTOGRAPH_API_KEY" \
  -o ./photo.jpg

Returns Path

The bytes are served through a CDN with 30-day edge caching, so repeat downloads are fast.

download_bundle

Stream the image’s data bundle to a local zip: the original bytes, its depth map when one has been generated, its annotations as Pictograph JSON, and a manifest.json naming exactly what is and is not inside. This is the same archive the annotation editor’s “Image data” button produces - one server-side builder assembles both, so the two cannot drift.

A missing depth map is not an error: the zip still arrives and the manifest records the omission and why. Same atomic .part rename as download, so a failed transfer leaves no partial zip.

Source: Images.download_bundle

Arg Type Default Notes
dataset_name str required Dataset name.
image str required Filename, or the image UUID
output_path str | Path required Local destination; parents are created
chunk_size int 8388608 Bytes per chunk (8 MiB)
client.images.download_bundle(
    dataset_name="road-signs",
    image="stop-sign-0421.jpg",
    output_path="./stop-sign-0421.zip",
)
pictograph images download-bundle "road-signs" stop-sign-0421.jpg
curl -s "https://api.pictograph.io/api/v1/developer/images/road-signs/train/stop-sign-0421.jpg/data-bundle" \
  -H "X-API-Key: $PICTOGRAPH_API_KEY" \
  -o ./stop-sign-0421.zip

Returns Path

The CLI defaults --output to ./<image-stem>.zip in the current directory, so the bare command lands the same file the editor’s button does.

delete

Soft-delete (archive) by default, which is recoverable. Set permanent=True to free the stored bytes; that is irreversible and needs an admin+ API key.

Source: Images.delete

Arg Type Default Notes
dataset_name str required Dataset name.
image str required Filename, or the image UUID
permanent bool False True deletes the bytes irreversibly
client.images.delete(
    dataset_name="road-signs",
    image="stop-sign-0421.jpg",
)

client.images.delete(
    dataset_name="road-signs",
    image="stop-sign-0421.jpg",
    permanent=True,
)
# The CLI defaults the OTHER way - it deletes permanently unless you pass --archive.
pictograph images delete road-signs stop-sign-0421.jpg --archive
pictograph images delete road-signs stop-sign-0421.jpg --yes
# Archive (recoverable)
curl -s -X DELETE "https://api.pictograph.io/api/v1/developer/images/road-signs/train/stop-sign-0421.jpg" \
  -H "X-API-Key: $PICTOGRAPH_API_KEY"

# Permanent (irreversible, admin+ role)
curl -s -X DELETE "https://api.pictograph.io/api/v1/developer/images/road-signs/train/stop-sign-0421.jpg?permanent=true" \
  -H "X-API-Key: $PICTOGRAPH_API_KEY"

Returns None

Permanent delete is reference-counted: a blob still referenced by a fork of the image is retained.

review

Approve or request changes on an image in the annotation review workflow. approve marks the image complete and accepts its annotations; request_changes sends it back to annotate with an optional note the annotator sees in the editor. Returns the image’s new status. Requires a member+ API key.

This is the programmatic entry point for QA automation, for example auto-approving high-confidence predictions and bouncing low-confidence ones for human review.

Source: Images.review

Arg Type Default Notes
dataset_name str required Dataset name.
image str required Filename, or the image UUID
action str required "approve" or "request_changes"
note str | None None Message for the annotator on request_changes
# Approve - accept the annotations, mark complete
client.images.review(
    dataset_name="road-signs",
    image="stop-sign-0421.jpg",
    action="approve",
)

# Request changes - send back to the annotator with a note
client.images.review(
    dataset_name="road-signs",
    image="stop-sign-0422.jpg",
    action="request_changes",
    note="tighten the left car bbox",
)

# QA loop: bounce every low-confidence image for a human to fix
for img in client.images.iter(dataset_name="road-signs", min_confidence_lt=0.6):
    client.images.review(
        dataset_name="road-signs",
        image=img.filename,
        action="request_changes",
        note="model unsure - please verify",
    )
pictograph images review road-signs stop-sign-0421.jpg
pictograph images review road-signs stop-sign-0422.jpg \
  --request-changes --note "tighten the left car bbox"
# Approve
curl -s -X POST "https://api.pictograph.io/api/v1/developer/images/road-signs/train/stop-sign-0421.jpg/review" \
  -H "X-API-Key: $PICTOGRAPH_API_KEY" -H "Content-Type: application/json" \
  -d '{"action": "approve"}'

# Request changes with a note
curl -s -X POST "https://api.pictograph.io/api/v1/developer/images/road-signs/train/stop-sign-0422.jpg/review" \
  -H "X-API-Key: $PICTOGRAPH_API_KEY" -H "Content-Type: application/json" \
  -d '{"action": "request_changes", "note": "tighten the left car bbox"}'

Returns ImageStatus

ImageStatus
ImageStatus = Literal['new', 'annotate', 'review', 'complete']

set_split

Assign one image to a train / val / test split, or clear its assignment with split=None. To partition a whole dataset at once, use assign_splits; to read a partition back, filter list or iter with split=.

Source: Images.set_split

Arg Type Default Notes
dataset_name str required Dataset name.
image str required Filename, or the image UUID
split ImageSplit | None required "train" / "val" / "test", or None to clear
client.images.set_split(
    dataset_name="road-signs",
    image="stop-sign-0421.jpg",
    split="test",
)

client.images.set_split(
    dataset_name="road-signs",
    image="stop-sign-0421.jpg",
    split=None,
)

# Read a partition back
train = client.images.iter(dataset_name="road-signs", split="train").all()
pictograph images split road-signs stop-sign-0421.jpg test
pictograph images split road-signs stop-sign-0421.jpg none
pictograph images list road-signs --split train
# Assign
curl -s -X POST "https://api.pictograph.io/api/v1/developer/images/road-signs/train/stop-sign-0421.jpg/split" \
  -H "X-API-Key: $PICTOGRAPH_API_KEY" -H "Content-Type: application/json" \
  -d '{"split": "test"}'

# Clear
curl -s -X POST "https://api.pictograph.io/api/v1/developer/images/road-signs/train/stop-sign-0421.jpg/split" \
  -H "X-API-Key: $PICTOGRAPH_API_KEY" -H "Content-Type: application/json" \
  -d '{"split": null}'

# Filter a dataset by split
curl -s "https://api.pictograph.io/api/v1/developer/images/?dataset=road-signs&split=train" \
  -H "X-API-Key: $PICTOGRAPH_API_KEY"

Returns ImageSplit | None

ImageSplit
ImageSplit = Literal['train', 'val', 'test']

assign_splits

One-call Rebalance: partition the whole non-archived dataset by ratio in a single atomic call - the fast path compared with per-image set_split, and the same operation the grid’s Rebalance button performs. val and test take their floor and train takes the remainder, so the counts sum to the total exactly (a 0 weight yields 0 images, for example 80/20/0), and the shuffle is deterministic under seed. Requires a member+ API key. Pairs with a split-organized, directly-trainable export.

Source: Images.assign_splits

Arg Type Default Notes
dataset_name str required Dataset name.
train int 70 Integer percentage weight
val int 20 Integer percentage weight
test int 10 Integer percentage weight
seed int 42 Deterministic shuffle seed
mode "random" | "embedding" "random" "embedding" clusters visually similar images first, so near-duplicates land in the same split instead of leaking across train and val. Adds clusters / unclustered to the result
counts = client.images.assign_splits(
    dataset_name="road-signs",
    train=70,
    val=20,
    test=10,
)
print(counts)
# The CLI takes the dataset UUID here, not the name.
pictograph images rebalance b7e4c1a0-83f2-4d55-9a6e-1f0c2d38b915 \
  --train 80 --val 10 --test 10
curl -s -X POST "https://api.pictograph.io/api/v1/developer/images/assign-splits" \
  -H "X-API-Key: $PICTOGRAPH_API_KEY" -H "Content-Type: application/json" \
  -d '{"dataset": "road-signs", "train": 70, "val": 20, "test": 10, "seed": 42}'

Returns dict[str, int]

On the REST body the embedding mode is "split_mode": "embedding".

bulk_tag

Add or remove user tags across many images in one call. Returns the number of images updated.

Source: Images.bulk_tag

Arg Type Default Notes
dataset_name str required Dataset name.
image_ids Sequence[str] required Image UUIDs to tag
tags Sequence[str] required Tags to apply
add bool True False removes the tags instead
reviewed = client.images.iter(dataset_name="road-signs", status="complete").all()

n = client.images.bulk_tag(
    dataset_name="road-signs",
    image_ids=[i.id for i in reviewed],
    tags=["reviewed"],
)
print(n, "images tagged")

client.images.bulk_tag(
    dataset_name="road-signs",
    image_ids=[i.id for i in reviewed],
    tags=["blurry"],
    add=False,
)
# Image ids are POSITIONAL and are UUIDs, not filenames. --tag is repeatable.
pictograph images tag road-signs \
  3f1c8e42-6b90-4a71-9d0e-2b5c7a11e004 \
  9a2d5b17-4c83-4f60-8e11-6d7f0c93a221 \
  --tag reviewed

# --remove strips the same tags instead
pictograph images tag road-signs \
  3f1c8e42-6b90-4a71-9d0e-2b5c7a11e004 \
  --tag blurry --remove
curl -s -X POST "https://api.pictograph.io/api/v1/developer/images/bulk-tag" \
  -H "X-API-Key: $PICTOGRAPH_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
        "dataset": "road-signs",
        "image_ids": ["3f1c8e42-6b90-4a71-9d0e-2b5c7a11e004"],
        "tags": ["reviewed"],
        "add": true
      }'

Returns int

augment

Generate multiplier augmented variants of every image in source.

Source: Images.augment

Arg Type Default Notes
source str required Source dataset name.
ops Sequence[Augmentation] required Augmentation ops (from pictograph.augment) applied in order.
multiplier int 3 Variants generated per source image (>= 1).
into str | None None Target dataset name. None (or equal to source) appends the variants into the source dataset itself; any other name is created if missing, copying the source’s class config.
include_original bool True When writing to a new dataset, also copy each original image + annotations (so the new dataset is a superset). Ignored when appending to the source (the originals are already there).
directory_path str '/augmented' Virtual directory the generated images land in.
seed int | None None RNG seed for reproducible variants.
max_source_images int | None None Cap the number of source images processed (handy for a quick trial). None processes all.
jpeg_quality int 95 Quality for the generated JPEG images (1-100).
drop_classes Iterable[str] | None None Preprocessing - annotation class names to remove before augmenting. Dropped classes are also removed from a newly-created target’s class config.
skip_empty bool False Preprocessing - when True, a source image left with no annotations (originally, or after drop_classes) is skipped entirely and counted in report.skipped_empty.
on_progress Callable[[int, int], None] | None None Optional (done, total) callback fired per source image.
from pictograph.augment import Brightness, HorizontalFlip

report = client.images.augment(
    source="road-signs",
    ops=[HorizontalFlip(), Brightness(0.2)],
    multiplier=3,
    into="road-signs-augmented",
)
print(report.variants_created, "variants from", report.source_images, "images")
pictograph augment dataset road-signs --into road-signs-aug
# `augment` builds the new images on your machine (Pillow), so it has no endpoint of
# its own - it drives the two public ones, once per generated image:
curl -s -X POST "https://api.pictograph.io/api/v1/developer/images/upload" \
  -H "X-API-Key: $PICTOGRAPH_API_KEY" \
  -F "dataset=road-signs" -F "directory_path=/augmented" -F "file=@tile.jpg"

curl -s -X POST "https://api.pictograph.io/api/v1/developer/annotations/bulk" \
  -H "X-API-Key: $PICTOGRAPH_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"saves": [{"image_id": "$IMAGE_ID", "annotations": [ ... ]}]}'

Returns AugmentReport

AugmentReport · 8 fields
class AugmentReport(BaseModel):
    """Outcome of an Images.augment run."""
    source: str
    target: str
    source_images: int = 0
    originals_copied: int = 0
    variants_created: int = 0
    annotations_written: int = 0
    skipped_empty: int = 0
    failures: list[AugmentFailure] = []

tile

Slice every image in source into a rows x cols grid of tiles.

Source: Images.tile

Arg Type Default Notes
source str required Source dataset name.
rows int 2 Grid rows per image (>= 1).
cols int 2 Grid columns per image (>= 1).
overlap float 0.0 Fractional overlap added to each tile edge, [0.0, 0.9).
min_visibility float 0.1 Drop an annotation from a tile when less than this fraction of its area survives the clip.
include_empty bool True When False, tiles with no surviving annotations are not uploaded.
into str | None None Target dataset name. None (or equal to source) appends the tiles into the source dataset itself; any other name is created if missing, copying the source’s class config.
directory_path str '/tiles' Virtual directory the generated tiles land in.
max_source_images int | None None Cap the number of source images processed. None processes all.
jpeg_quality int 95 Quality for the generated JPEG tiles (1-100).
on_progress Callable[[int, int], None] | None None Optional (done, total) callback fired per source image.
report = client.images.tile(
    source="aerial",
    rows=2,
    cols=2,
    overlap=0.1,
    into="aerial-tiled",
)
print(report.tiles_created, "tiles;", report.annotations_written, "annotations")
pictograph tile dataset road-signs --into road-signs-tiled --rows 2
# `tile` builds the new images on your machine (Pillow), so it has no endpoint of
# its own - it drives the two public ones, once per generated image:
curl -s -X POST "https://api.pictograph.io/api/v1/developer/images/upload" \
  -H "X-API-Key: $PICTOGRAPH_API_KEY" \
  -F "dataset=road-signs" -F "directory_path=/tiles" -F "file=@tile.jpg"

curl -s -X POST "https://api.pictograph.io/api/v1/developer/annotations/bulk" \
  -H "X-API-Key: $PICTOGRAPH_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"saves": [{"image_id": "$IMAGE_ID", "annotations": [ ... ]}]}'

Returns TileReport

TileReport · 7 fields
class TileReport(BaseModel):
    """Outcome of an Images.tile run."""
    source: str
    target: str
    source_images: int = 0
    tiles_created: int = 0
    empty_tiles: int = 0
    annotations_written: int = 0
    failures: list[TileFailure] = []

Common errors

Status Exception Cause
404 NotFoundError The image or dataset does not exist, or belongs to another organization
409 ConflictError Filename collision in the same virtual directory
403 ForbiddenError Upload requires member+; permanent delete requires admin+
400 ApiError Invalid filename or directory path, or the file is not an image
Copied to clipboard