Sign in Get started

Auto-annotate

SAM3 point, box, and text prompts plus async batch jobs for AI-generated annotations.

View as Markdown

SAM3 auto-annotation: three single-image prompt modes, an async batch endpoint for many images at once, and a quote that prices a batch before you run it.

Scenario Mode
User clicks one spot point
User drags a rough box box
One image, many objects text
Many images, known classes batch

Prompt results are returned, not saved: call client.annotations.save to persist them.

point

Click here, segment that. Returns one polygon annotation per prompt. The first prompt on an image generates its GPU embedding (1 to 2 seconds on a warm container); follow-ups on the same image are sub-second.

Source: AutoAnnotate.point

Arg Type Default Notes
dataset_name str required Dataset name.
image_filename str required Image filename, not the UUID
x, y int required Anchor point in absolute pixels
name str "object" Class name for the annotation
positive_points list[(x, y)] | None None Extra positive anchors
negative_points list[(x, y)] | None None Excluded regions
score_threshold float 0.75 Minimum SAM3 score, 0 to 1
result = client.auto_annotate.point(
    dataset_name="road-signs",
    image_filename="img-1.jpg",
    x=320,
    y=240,
    name="car",
    positive_points=[(310, 250)],
    negative_points=[(100, 100)],
)
pictograph auto-annotate point road-signs img-1.jpg --x 320 --y 240 --name car
curl -s -X POST "https://api.pictograph.io/api/v1/developer/auto-annotate/sam3/point" \
  -H "X-API-Key: $PICTOGRAPH_API_KEY" -H "Content-Type: application/json" \
  -d '{"dataset_name": "road-signs", "image_filename": "img-1.jpg", "x": 320, "y": 240, "name": "car", "positive_points": [[310, 250]], "negative_points": [[100, 100]]}'

Returns PromptResult

PromptResult · 4 fields
class PromptResult(BaseModel):
    """Outcome of a single SAM3 prompt (point / box / text)."""
    status: Literal['success', 'no_detection', 'below_threshold']
    annotations: list[Annotation] = []
    score: float | None = None
    inference_time: float | None = None

On success, annotations[0] is a PolygonAnnotation.

box

Segment everything inside this box. return_polygon=False returns only the refined bounding box.

Source: AutoAnnotate.box

Arg Type Default Notes
dataset_name str required Dataset name.
image_filename str required Image filename
box {x, y, w, h} required Bounding box in absolute pixels
name str required Class name
confidence_threshold float 0.5 Minimum SAM3 confidence, 0 to 1
return_polygon bool True Include a polygon as well as the bbox
negative_boxes list[{x, y, w, h}] | None None Exclusion zones
result = client.auto_annotate.box(
    dataset_name="road-signs",
    image_filename="img-1.jpg",
    box={"x": 100, "y": 200, "w": 200, "h": 150},
    name="car",
    return_polygon=True,
    negative_boxes=[{"x": 50, "y": 50, "w": 30, "h": 30}],
)
pictograph auto-annotate box road-signs img-1.jpg --box 100,200,200,150 --name car
curl -s -X POST "https://api.pictograph.io/api/v1/developer/auto-annotate/sam3/box" \
  -H "X-API-Key: $PICTOGRAPH_API_KEY" -H "Content-Type: application/json" \
  -d '{"dataset_name": "road-signs", "image_filename": "img-1.jpg", "box": {"x": 100, "y": 200, "w": 200, "h": 150}, "name": "car", "return_polygon": true, "negative_boxes": [{"x": 50, "y": 50, "w": 30, "h": 30}]}'

Returns PromptResult

PromptResult · 4 fields
class PromptResult(BaseModel):
    """Outcome of a single SAM3 prompt (point / box / text)."""
    status: Literal['success', 'no_detection', 'below_threshold']
    annotations: list[Annotation] = []
    score: float | None = None
    inference_time: float | None = None

text

Find all of a thing. Open-vocabulary phrase grounding, best for many objects in one image. Results come back sorted by confidence.

Source: AutoAnnotate.text

Arg Type Default Notes
dataset_name str required Dataset name.
image_filename str required Image filename
text_prompt str required Natural-language description
output_type str "polygon" "polygon" or "bbox"
confidence_threshold float 0.3 Minimum confidence, 0 to 1
max_detections int 50 Cap on result count, 1 to 100
result = client.auto_annotate.text(
    dataset_name="road-signs",
    image_filename="img-1.jpg",
    text_prompt="red cars",
    output_type="polygon",
    max_detections=50,
)
pictograph auto-annotate text road-signs img-1.jpg --prompt "red cars"
curl -s -X POST "https://api.pictograph.io/api/v1/developer/auto-annotate/sam3/text" \
  -H "X-API-Key: $PICTOGRAPH_API_KEY" -H "Content-Type: application/json" \
  -d '{"dataset_name": "road-signs", "image_filename": "img-1.jpg", "text_prompt": "red cars", "output_type": "polygon", "max_detections": 50}'

Returns PromptResult

PromptResult · 4 fields
class PromptResult(BaseModel):
    """Outcome of a single SAM3 prompt (point / box / text)."""
    status: Literal['success', 'no_detection', 'below_threshold']
    annotations: list[Annotation] = []
    score: float | None = None
    inference_time: float | None = None

quote

Ask what a batch would cost without running it. The quote and the charge come from one server-side function, so the number here is what batch deducts.

projected prices images that do not exist yet. That is what lets you decide before you spend, most importantly for video: a video is one file but hundreds of frames, and the frames are what you pay for.

Source: AutoAnnotate.quote

Arg Type Default Notes
dataset_name str | None None Required to price images that already exist
image_filenames list[str] () Existing images; their stored dimensions drive SAHI pricing
projected list[{count, width, height}] () Images that do not exist yet
classes list[BatchClass] () Each class adds a grounding pass, so it changes the price
model str | None None Trained model by name; None routes to SAM3
sahi / sahi_slice_size bool / int False / 640 See SAHI
from pictograph import BatchClass

uploaded = client.video.upload(
    local_path="drive.mp4",
)
meta = client.video.probe(
    gcs_path=uploaded.gcs_path,
)
frames = int(meta.duration_seconds * 5)   # sampling at 5 fps
quote = client.auto_annotate.quote(
    projected=[{"count": frames, "width": meta.width, "height": meta.height}],
    classes=[BatchClass(name="car", output_type="bbox")],
)
print(quote.estimated_credits / 1e6, "USD", quote.sufficient)
pictograph auto-annotate quote --frames 900 --width 1920 --height 1080 --classes car:bbox
curl -s -X POST "https://api.pictograph.io/api/v1/developer/auto-annotate/batch/quote" \
  -H "X-API-Key: $PICTOGRAPH_API_KEY" -H "Content-Type: application/json" \
  -d '{"projected": [{"count": 900, "width": 1920, "height": 1080}], "classes": [{"name": "car", "output_type": "bbox"}]}'

Returns BatchQuote

BatchQuote · 8 fields
class BatchQuote(BaseModel):
    """What a batch job WOULD cost - the same deposit `batch()` would take."""
    total_images: int
    estimated_credits: int
    sahi_tiles: int = 0
    containers: int = 0
    remaining_credits: int = 0
    sufficient: bool = True
    max_images: int = 5000
    exceeds_max_images: bool = False

batch

Async, many images. Use it above roughly 10 images. Kicks off one job and, by default, polls until it reaches a terminal status.

SAM3 jobs (model omitted) need at least one class. Trained-model jobs (model="Road Sign Detector") accept empty classes for classification models. On the REST wire the trained model is addressed as model_id with a UUID, and SAHI is opted in with sahi_enabled / sahi_slice_size.

Source: AutoAnnotate.batch

Arg Type Default Notes
dataset_name str required Dataset name.
image_filenames list[str] required Filenames to process, 1 to 5000
classes list[BatchClass] required for SAM3 {name, output_type} per class
confidence_threshold float 0.5 Minimum confidence
model str | None None Trained model by name (a UUID works); None routes to SAM3
top_k int 1 Classifier only: tags per image
wait bool True Poll until terminal
poll_interval / timeout float 5.0 / 1800.0 Polling cadence and deadline, in seconds
from pictograph import BatchClass

job = client.auto_annotate.batch(
    dataset_name="road-signs",
    image_filenames=["img-1.jpg", "img-2.jpg", "img-3.jpg"],
    classes=[
        BatchClass(name="car", output_type="polygon"),
        BatchClass(name="person", output_type="bbox"),
    ],
    confidence_threshold=0.5,
    wait=True,
)
print(job.status, job.processed_images, job.total_annotations_added)
pictograph auto-annotate batch road-signs \
  --images img-1.jpg,img-2.jpg,img-3.jpg \
  --classes car:polygon,person:bbox
curl -s -X POST "https://api.pictograph.io/api/v1/developer/auto-annotate/batch" \
  -H "X-API-Key: $PICTOGRAPH_API_KEY" -H "Content-Type: application/json" \
  -d '{"dataset_name": "road-signs", "image_filenames": ["img-1.jpg", "img-2.jpg"], "classes": [{"name": "car", "output_type": "polygon"}, {"name": "person", "output_type": "bbox"}], "confidence_threshold": 0.5}'

Returns BatchJob

BatchJob · 10 fields
class BatchJob(BaseModel):
    """Snapshot of an auto-annotate batch job's progress."""
    job_id: str
    status: Literal['pending', 'running', 'completed', 'failed', 'cancelled']
    progress: int = 0
    total_images: int = 0
    processed_images: int = 0
    total_annotations_added: int = 0
    failed_images: int = 0
    error_message: str | None = None
    estimated_credits: int | None = None
    completed_at: datetime | None = None

SAHI sliced inference

For high-resolution images with small objects (drone shots, wide industrial scenes, litter detection), enable SAHI. Each image is sliced into overlapping tiles, every tile runs at near-native resolution alongside one full-image pass, and tile fragments are merged back into whole instances server-side.

job = client.auto_annotate.batch(
    dataset_name="road-signs",
    image_filenames=["site-4k.jpg"],
    classes=[BatchClass(name="person", output_type="polygon")],
    sahi=True,
    sahi_slice_size=640,
)
pictograph auto-annotate batch road-signs \
  --images site-4k.jpg --classes person:polygon --sahi --sahi-slice-size 640
curl -s -X POST "https://api.pictograph.io/api/v1/developer/auto-annotate/batch" \
  -H "X-API-Key: $PICTOGRAPH_API_KEY" -H "Content-Type: application/json" \
  -d '{"dataset_name": "road-signs", "image_filenames": ["site-4k.jpg"], "classes": [{"name": "person", "output_type": "polygon"}], "sahi_enabled": true, "sahi_slice_size": 640}'

SAHI is SAM3 only; a trained-model job with SAHI enabled is rejected with a 400. Smaller slices (256 to 1024 pixels) find smaller objects but run more passes, and cost scales with each image’s tile count. quote prices it exactly.

dataset

Pages the dataset’s image list, holds back images that already have annotations unless overwrite=True, runs batch or text mode, saves the results, and returns a per-image report. No CLI command; use pictograph auto-annotate batch with an explicit --images list.

Source: AutoAnnotate.dataset

Arg Type Default Notes
dataset_name str required Dataset name.
classes Sequence[BatchClass | tuple[str, str] | dict[str, str]] required Class configs to detect. Accepts: - BatchClass instances (canonical), - (name, output_type) tuples (shorthand), - {"name": ..., "output_type": ...} dicts. output_type defaults to "polygon" for tuples without one.
mode AnnotateMode 'batch' "batch" (default) - async batch job; "text" - synchronous per-image text prompt.
confidence_threshold float 0.5 SAM3 confidence cutoff (0-1).
overwrite bool False When False (default), skip images that already have at least one annotation. When True, re-annotate every image.
max_images int | None None Cap the number of images processed (useful for dry-runs). None means “all”.
poll_interval float 5.0 "batch" mode only - seconds between polls.
timeout float 1800.0 "batch" mode only - max seconds to wait.
report = client.auto_annotate.dataset(
    dataset_name="road-signs",
    classes=[("car", "polygon"), ("person", "bbox")],
    mode="batch",
    overwrite=False,
    max_images=None,
)
print(report.images_processed, report.annotations_added, len(report.failures))
# Batch auto-annotation across a dataset.
pictograph auto-annotate batch road-signs \
  --images "img-001.jpg,img-002.jpg" \
  --classes "stop sign:bbox"
curl -s -X POST "https://api.pictograph.io/api/v1/developer/auto-annotate/batch" \
  -H "X-API-Key: $PICTOGRAPH_API_KEY" -H "Content-Type: application/json" \
  -d '{"dataset_name": "road-signs", "classes": ["stop sign"]}'

Returns AnnotateReport

AnnotateReport · 8 fields
class AnnotateReport(BaseModel):
    """Outcome of an AutoAnnotate.dataset call."""
    dataset_name: str
    images_attempted: int = 0
    images_processed: int = 0
    images_skipped: int = 0
    images_capped: int = 0
    annotations_added: int = 0
    failures: list[AnnotationFailure] = []
    job_id: str | None = None

cancel_batch

Cancel a pending or running batch job.

Source: AutoAnnotate.cancel_batch

Arg Type Default Notes
job_id str required Job id, as returned when the job was created.
job = client.auto_annotate.cancel_batch(
    job_id="1a6e4f28-9b30-4c57-8d21-6f3b0a5e7c94",
)
print(job.status, job.processed_images, "of", job.total_images, "already done")
pictograph auto-annotate cancel-batch 1a6e4f28-9b30-4c57-8d21-6f3b0a5e7c94
curl -s -X POST "https://api.pictograph.io/api/v1/developer/auto-annotate/batch/$JOB_ID/cancel" \
  -H "X-API-Key: $PICTOGRAPH_API_KEY"

Returns BatchJob

BatchJob · 10 fields
class BatchJob(BaseModel):
    """Snapshot of an auto-annotate batch job's progress."""
    job_id: str
    status: Literal['pending', 'running', 'completed', 'failed', 'cancelled']
    progress: int = 0
    total_images: int = 0
    processed_images: int = 0
    total_annotations_added: int = 0
    failed_images: int = 0
    error_message: str | None = None
    estimated_credits: int | None = None
    completed_at: datetime | None = None

get_batch

Fetch the current status of a batch job.

Source: AutoAnnotate.get_batch

Arg Type Default Notes
job_id str required Job id, as returned when the job was created.
job = client.auto_annotate.get_batch(
    job_id="1a6e4f28-9b30-4c57-8d21-6f3b0a5e7c94",
)
print(job.status, job.progress, job.total_annotations_added)
# No `get-batch` command; use the SDK or REST.
pictograph auto-annotate get 1a6e4f28-9b30-4c57-8d21-6f3b0a5e7c94
curl -s "https://api.pictograph.io/api/v1/developer/auto-annotate/batch/$JOB_ID" \
  -H "X-API-Key: $PICTOGRAPH_API_KEY"

Returns BatchJob

BatchJob · 10 fields
class BatchJob(BaseModel):
    """Snapshot of an auto-annotate batch job's progress."""
    job_id: str
    status: Literal['pending', 'running', 'completed', 'failed', 'cancelled']
    progress: int = 0
    total_images: int = 0
    processed_images: int = 0
    total_annotations_added: int = 0
    failed_images: int = 0
    error_message: str | None = None
    estimated_credits: int | None = None
    completed_at: datetime | None = None

wait_for_batch

Poll a batch job until terminal status or timeout.

Source: AutoAnnotate.wait_for_batch

Arg Type Default Notes
job_id str required Job id, as returned when the job was created.
poll_interval float 5.0 Seconds between polls.
timeout float 1800.0 Max seconds to wait before raising PollTimeoutError. The job keeps running.
job = client.auto_annotate.wait_for_batch(
    job_id="1a6e4f28-9b30-4c57-8d21-6f3b0a5e7c94",
    timeout=1800.0,
)
print(job.status, job.total_annotations_added, "annotations across", job.total_images, "images")
# No `wait-for-batch` command; use the SDK or REST.
pictograph auto-annotate get 1a6e4f28-9b30-4c57-8d21-6f3b0a5e7c94
# No wait endpoint - `wait_for_batch` polls this one until `status` is terminal:
curl -s "https://api.pictograph.io/api/v1/developer/auto-annotate/batch/$JOB_ID" \
  -H "X-API-Key: $PICTOGRAPH_API_KEY"

Returns BatchJob

BatchJob · 10 fields
class BatchJob(BaseModel):
    """Snapshot of an auto-annotate batch job's progress."""
    job_id: str
    status: Literal['pending', 'running', 'completed', 'failed', 'cancelled']
    progress: int = 0
    total_images: int = 0
    processed_images: int = 0
    total_annotations_added: int = 0
    failed_images: int = 0
    error_message: str | None = None
    estimated_credits: int | None = None
    completed_at: datetime | None = None

Cost

SAM3 auto-annotation draws on your USD compute credit and is available on every plan. Point and box prompts share one image embedding per session, so follow-ups on the same image are near-free. Batch is priced per image processed, times the class count, times the SAHI tile count: quote computes it exactly, and PaymentRequiredError.credit_cost carries the ask in micro-USD on rejection.

Common errors

Status Exception Cause
402 PaymentRequiredError Out of credits
404 NotFoundError Dataset or image missing
408 PollTimeoutError Batch did not finish within timeout. The job keeps running server-side.
422 ValidationError SAM3 with no classes, SAHI with a trained model, or sahi_slice_size out of range

See also

Copied to clipboard