Pictograph SDK — agent-native computer vision annotation platform
Project description
Pictograph Python SDK
Official Python SDK for Pictograph Context Engine - a powerful computer vision annotation platform for creating high-quality training datasets.
Features
- Simple, intuitive API - Get started with just a few lines of code
- Dataset management - List, download, and manage annotation datasets
- Image operations - Upload images, retrieve metadata, and manage assets
- Annotation tools - Get, save, and delete annotations in Pictograph JSON format
- Batch operations - Download entire datasets with parallel processing
- Notification feed -
client.notifications(+ agent toollist_notifications) polls the org job-lifecycle event feed (training complete / export ready) so agents learn what finished without tracking every run id - Async client -
pictograph.AsyncClientmirrors the full API surface forasyncioapps (HTTP/2, coroutine methods,async forpagers) - Local export -
pictograph datasets export <name> --format coco|yolo|pascal_voc|…writes a dataset ZIP in any of the 9 Pictograph formats, built by Pictograph's own server-side converters - Offline format converters -
pictograph.formatsreads/writes COCO, YOLO & Pascal VOC annotations locally (no API call, no third-party SDK) — bring an existing dataset straight into Pictograph's typed models - One-call annotation import -
pictograph.pipelines.import_coco_annotations/import_yolo_annotations/import_pascal_voc_annotationsparse a local COCO/YOLO/Pascal VOC file, create any missing classes, match images by filename, and bulk-save — the whole recipe in one call (async twins underpictograph.aio.pipelines) - Local model evaluation -
pictograph.metrics.evaluate_detectionsscores predictions vs ground truth (per-class + overall precision / recall / F1 by IoU matching), offline, no third-party library - Visualization - Draw annotations onto images with
draw_annotations(Pillow-only, no extra deps) - Dataset augmentation -
pictograph.augment(flip / rotate / crop / colour ops with correct annotation-geometry remapping) +pictograph.pipelines.augment_datasetgenerate an augmented version of a dataset — Pillow-only, no extra deps - Dataset tiling -
pictograph.tile+pictograph.pipelines.tile_datasetslice each image into an N×M grid (with per-tile annotation clipping) for small-object detection — the Roboflow-style "Tile" preprocessing, Pillow-only - PyTorch adapter -
client.datasets.as_pytorch(...)yields a ready-to-traintorch.Dataset(passaugment=Augmenter([...])for on-the-fly augmentation with the target boxes remapped) - Automatic retries - Built-in retry logic for transient failures
- Rate limiting - Automatic handling of API rate limits
- Type hints - Full type annotations for better IDE support
Installation
pip install pictograph
Optional extras pull in heavier dependencies only when you need them:
pip install 'pictograph[torch]' # client.datasets.as_pytorch(...)
pip install 'pictograph[cli]' # the `pictograph` command-line tool
pip install 'pictograph[all]' # everything
Quick Start
from pictograph import Client
# Initialize the client with your API key
client = Client(api_key="pk_live_your_key_here")
# List all datasets
datasets = client.datasets.list()
for dataset in datasets:
print(f"{dataset['name']}: {dataset['image_count']} images")
# Download a complete dataset
client.datasets.download(
"dataset-uuid",
output_dir="./my_dataset",
mode="full" # Download images + annotations
)
# Upload an image
result = client.images.upload(
"dataset-uuid",
"/path/to/image.jpg",
folder_path="/train/images"
)
print(f"Uploaded: {result['image_id']}")
# Get annotations for an image
annotations = client.annotations.get("image-uuid")
for ann in annotations:
print(f"{ann['name']}: {ann['type']}")
# Save annotations
client.annotations.save("image-uuid", [
{
"id": "ann-1",
"name": "person",
"type": "bbox",
"bbox": [100, 200, 50, 80], # [x, y, width, height]
"confidence": 1.0
}
])
Authentication
Get your API key from the Pictograph dashboard.
from pictograph import Client
client = Client(api_key="pk_live_your_key_here")
You can also configure the base URL and timeout:
client = Client(
api_key="pk_live_your_key_here",
base_url="https://your-instance.pictograph.io",
timeout=60,
max_retries=5
)
Usage
Working with Datasets
List Datasets
# List all datasets
datasets = client.datasets.list()
# With pagination
datasets = client.datasets.list(limit=50, offset=0)
Get Dataset Details
# Get basic info
dataset = client.datasets.get("dataset-uuid")
print(dataset['name'], dataset['image_count'])
# Get with images included
dataset = client.datasets.get(
"dataset-uuid",
include_images=True,
images_limit=1000
)
for img in dataset['images']:
print(img['filename'], img['image_url'])
List Images in Dataset
# List all images
images = client.datasets.list_images("dataset-uuid")
# Filter by annotation status
completed_images = client.datasets.list_images(
"dataset-uuid",
status="complete",
limit=500
)
Download Dataset
# Download everything (images + annotations)
result = client.datasets.download(
"dataset-uuid",
output_dir="./dataset",
mode="full",
max_workers=20, # Parallel downloads
show_progress=True
)
print(f"Downloaded {result['images_downloaded']} images")
# Download only annotations
result = client.datasets.download(
"dataset-uuid",
output_dir="./annotations",
mode="annotations_only"
)
# Download only completed images
result = client.datasets.download(
"dataset-uuid",
output_dir="./completed",
mode="full",
status_filter="complete"
)
Working with Images
Get Image Metadata
image = client.images.get("image-uuid")
print(image['filename'])
print(image['image_url']) # CDN URL for viewing
print(image['annotation_count'])
Upload Image
# Simple upload
result = client.images.upload(
"dataset-uuid",
"/path/to/image.jpg"
)
# Upload to specific folder
result = client.images.upload(
"dataset-uuid",
"/path/to/image.jpg",
folder_path="/train/images",
filename="custom_name.jpg"
)
print(result['image_id'])
Delete Image
# Archive (soft delete)
client.images.delete("image-uuid")
# Permanent delete
client.images.delete("image-uuid", permanent=True)
Working with Annotations
Pictograph uses a JSON format that supports multiple annotation types:
- bbox - Bounding boxes
[x, y, width, height] - polygon - Polygons
[[x1, y1], [x2, y2], ...] - polyline - Polylines
[[x1, y1], [x2, y2], ...] - keypoint - Single points
[x, y]
Get Annotations
annotations = client.annotations.get("image-uuid")
for ann in annotations:
print(ann['id'], ann['name'], ann['type'])
if ann['type'] == 'bbox':
x, y, width, height = ann['bbox']
print(f" Bbox: ({x}, {y}) - {width}x{height}")
Save Annotations
# Bounding box
annotations = [
{
"id": "ann-1",
"name": "person",
"type": "bbox",
"bbox": [100, 200, 50, 80],
"confidence": 1.0
}
]
result = client.annotations.save("image-uuid", annotations)
print(result['new_count'])
# Polygon
annotations = [
{
"id": "ann-2",
"name": "car",
"type": "polygon",
"polygon": [[10, 20], [30, 40], [50, 60], [10, 20]],
"confidence": 1.0
}
]
client.annotations.save("image-uuid", annotations)
# Multiple annotations
annotations = [
{"id": "ann-1", "name": "person", "type": "bbox", "bbox": [100, 200, 50, 80]},
{"id": "ann-2", "name": "car", "type": "polygon", "polygon": [[10,20], [30,40], [50,60], [10,20]]},
{"id": "ann-3", "name": "road", "type": "polyline", "polyline": [[0,100], [50,100], [100,100]]},
{"id": "ann-4", "name": "landmark", "type": "keypoint", "keypoint": [150, 200]}
]
client.annotations.save("image-uuid", annotations)
Helper Methods
# Create properly formatted annotations
bbox = client.annotations.create_bbox(
"ann-1",
"person",
[100, 200, 50, 80],
confidence=0.95
)
polygon = client.annotations.create_polygon(
"ann-2",
"car",
[[10, 20], [30, 40], [50, 60], [10, 20]]
)
polyline = client.annotations.create_polyline(
"ann-3",
"road",
[[0, 100], [50, 100], [100, 100]]
)
keypoint = client.annotations.create_keypoint(
"ann-4",
"landmark",
[150, 200]
)
# Save them all
client.annotations.save("image-uuid", [bbox, polygon, polyline, keypoint])
Delete Annotations
# Delete all annotations for an image
result = client.annotations.delete("image-uuid")
print(result['deleted_count'])
Context Manager
Use the client as a context manager to ensure proper cleanup:
with Client(api_key="pk_live_your_key_here") as client:
datasets = client.datasets.list()
# Client session automatically closed when done
Async client
pictograph.AsyncClient is the asyncio twin of Client. It mirrors the exact
same resource surface (datasets, images, annotations, exports, training,
models, deployments, credits, organizations, projects, folders,
batch, search, auto_annotate, video, connectors, api_keys, webhooks,
workflows) — every I/O method is a coroutine (await it), and every iter(...)
accessor returns an async pager you consume with async for. It runs over HTTP/2
on a single shared connection pool, so concurrent calls multiplex efficiently.
import asyncio
from pictograph import AsyncClient
async def main() -> None:
async with AsyncClient(api_key="pk_live_your_key_here") as client:
# await any resource method
datasets = await client.datasets.list(limit=5)
# async-for the auto-paging iterators
async for img in client.images.iter(datasets[0].id, folder_path="/train"):
print(img.filename, img.annotation_count)
# fan out concurrent requests with asyncio.gather
insights = await asyncio.gather(*(client.datasets.insights(d.name) for d in datasets))
for d, health in zip(datasets, insights):
print(d.name, health.total_annotations)
asyncio.run(main())
Retries, idempotency keys, typed errors, streaming downloads, and poll helpers
(await client.training.create(..., wait=True), await client.exports.wait_for_completion(...))
behave identically to the sync client. Call await client.aclose() if you are not
using the async with form.
Async batteries-included pipelines live under pictograph.aio.pipelines — the
data-ingestion flows where concurrency is a real win:
await upload_dataset_from_folder(async_client, "my-set", "./images") uploads a
folder concurrently, and
await import_coco_annotations(async_client, "my-set", "instances_val.json") runs
the chunked bulk-saves concurrently.
Local model evaluation (pictograph.metrics)
Measure how good a model is against a labeled set — offline, no server round-trip,
no third-party library. evaluate_detections matches predicted annotations to
ground truth by IoU and returns per-class and overall precision / recall / F1.
from pictograph import Client
from pictograph.metrics import evaluate_detections
client = Client()
ground_truth = {img_id: client.annotations.get(img_id) for img_id in image_ids}
predictions = {img_id: run_my_model(img_id) for img_id in image_ids} # your model's output
result = evaluate_detections(predictions, ground_truth, iou_threshold=0.5)
print(f"overall P={result.precision:.3f} R={result.recall:.3f} F1={result.f1:.3f}")
for name, m in sorted(result.per_class.items()):
print(f" {name:<12} P={m.precision:.3f} R={m.recall:.3f} support={m.support}")
Predictions can come from any source — client.auto_annotate, a deployed model's
/predict, or local pictograph.get_model(...).predict(...).
Offline format converters (pictograph.formats)
Convert between COCO / YOLO and Pictograph's typed Annotation models entirely
on your machine — no API round-trip, no third-party dependency. Handy for
bringing an existing COCO/YOLO dataset into Pictograph, or emitting those formats
from annotations you already hold.
from pictograph import Client
from pictograph.formats import from_coco, to_yolo
client = Client(api_key="pk_live_your_key_here")
# Parse a local COCO file into Pictograph's models.
imp = from_coco("instances_val.json") # -> CocoImport(annotations, class_names)
# Or emit YOLO label text for one image (normalized to its pixel size).
yolo_txt = to_yolo(imp.annotations["a.jpg"], imp.class_names, image_width=640, image_height=480)
To go from a local COCO/YOLO file to annotations saved on a dataset in one call (create missing classes, match images by filename, chunked bulk-save, per-image report), use the import pipelines:
from pictograph.pipelines import import_coco_annotations
# The dataset must already exist and hold the images the file references.
report = import_coco_annotations(client, "my-set", "instances_val.json")
print(report.images_saved, "images annotated;", len(report.unmatched_files), "unmatched")
from_coco / to_coco handle bounding boxes (exact round-trip), polygon
segmentation, and keypoints; from_yolo / to_yolo handle detection and
segmentation labels; from_pascal_voc / to_pascal_voc handle the Pascal VOC
per-image XML (bounding boxes). For hole-accurate COCO (RLE) or a full dataset ZIP
in any of the 8 formats, use the server-side export instead
(client.exports.create(..., format="coco")).
Error Handling
The SDK raises specific exceptions for different error types:
from pictograph import Client, AuthenticationError, RateLimitError, NotFoundError
client = Client(api_key="pk_live_your_key_here")
try:
dataset = client.datasets.get("invalid-uuid")
except AuthenticationError:
print("Invalid API key")
except RateLimitError as e:
print(f"Rate limited. Retry after {e.retry_after} seconds")
except NotFoundError:
print("Dataset not found")
except Exception as e:
print(f"Unexpected error: {e}")
Advanced Usage
Batch Processing
from concurrent.futures import ThreadPoolExecutor
# Upload multiple images in parallel
def upload_image(image_path):
return client.images.upload("dataset-uuid", image_path)
image_paths = ["img1.jpg", "img2.jpg", "img3.jpg"]
with ThreadPoolExecutor(max_workers=10) as executor:
results = list(executor.map(upload_image, image_paths))
print(f"Uploaded {len(results)} images")
Custom Metadata
# Add custom metadata to annotations
annotation = client.annotations.create_bbox(
"ann-1",
"person",
[100, 200, 50, 80],
metadata={
"annotator": "john@example.com",
"difficulty": "easy",
"verified": True
}
)
client.annotations.save("image-uuid", [annotation])
Local dataset export
Export a dataset to any of the 9 Pictograph formats — built by Pictograph's own server-side converters, with no third-party dependency — from the SDK or the CLI:
from pictograph import Client
client = Client()
export = client.exports.create(
"road-signs", "road-signs-coco", format="coco", include_images=True
)
client.exports.download("road-signs", export.name, "road-signs-coco.zip")
The same from the CLI (pip install 'pictograph[cli]'):
pictograph datasets export road-signs --format coco -o ./out
pictograph datasets export road-signs --format yolo --include-images -o ./out
Formats: pictograph, coco, yolo, pascal_voc, darwin, cvat, datumaro, labelme, csv.
Visualization
draw_annotations renders Pictograph annotations onto an image using only Pillow
(a base dependency) — no extra install, no third-party renderer:
from pictograph import Client, draw_annotations
client = Client()
annotations = client.annotations.get("image-uuid")
draw_annotations("photo.jpg", annotations).save("photo.annotated.png")
All four annotation types render (bbox / polygon / polyline / keypoint), each class a stable color.
Augmentation
pictograph.augment generates augmented variants of an image and remaps its
annotation geometry — a flip moves every box, a rotation rotates every polygon
point, a crop clips and drops out-of-frame objects. Pillow-only, no extra deps:
from pictograph.augment import Augmenter, HorizontalFlip, Rotate, Brightness
aug = Augmenter([HorizontalFlip(), Rotate((-15, 15)), Brightness((0.8, 1.2))], seed=42)
image, annotations = aug("photo.jpg", annotations) # one variant
variants = aug.generate("photo.jpg", annotations, n=3) # three reproducible variants
To augment a whole dataset (the "generate a version" workflow) — download every image + its annotations, produce N variants, and upload them back through the standard ingest pipeline (embeddings, auto-tags, thumbnails):
from pictograph import Client
from pictograph.pipelines import augment_dataset
client = Client()
report = augment_dataset(
client, "road-signs",
ops=aug.ops, multiplier=3, into="road-signs-aug",
)
print(report.variants_created, "images generated")
Or from the CLI: pictograph augment dataset road-signs --into road-signs-aug --flip --rotate 15 --brightness 0.2 -m 3 (pictograph augment ops lists every flag).
Tiling
pictograph.tile slices an image into a grid of tiles and clips each annotation
into the tile it falls in — the standard small-object-detection preprocessing
(aerial / satellite / microscopy). Pillow-only:
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") # geometry translated + clipped per tile
To tile a whole dataset — download every image + its annotations, slice each, and upload the tiles back through the standard ingest pipeline:
from pictograph import Client
from pictograph.pipelines import tile_dataset
client = Client()
report = tile_dataset(client, "aerial", rows=2, cols=2, into="aerial-tiled")
print(report.tiles_created, "tiles generated")
Or from the CLI: pictograph tile dataset aerial --into aerial-tiled --rows 2 --cols 2.
Rate Limits
The SDK automatically handles rate limits:
- Free tier: 1,000 requests/hour
- Core tier: 5,000 requests/hour
- Pro tier: 20,000 requests/hour
- Enterprise tier: 100,000 requests/hour
If you hit a rate limit, the SDK will automatically wait and retry (if retry time < 2 minutes).
Requirements
- Python 3.8+
- requests >= 2.31.0
- Pillow >= 10.0.0
- tqdm >= 4.65.0
Support
- Documentation: https://pictograph.io/docs
- Email: support@pictograph.io
- Issues: https://github.com/pictograph-labs/pictograph-sdk/issues
License
MIT License - see LICENSE file for details.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pictograph-1.54.0.tar.gz.
File metadata
- Download URL: pictograph-1.54.0.tar.gz
- Upload date:
- Size: 480.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.8
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8c4be9f5c1b42d5438462026fa53276e2fc980f55853d3ce7902791343ba5563
|
|
| MD5 |
596942ca193d9058a890021a8e5d16ed
|
|
| BLAKE2b-256 |
c4d5b61c5d295f6f1c87561a3dcd3aa4e86236223f192028ede2a72daacb1982
|
File details
Details for the file pictograph-1.54.0-py3-none-any.whl.
File metadata
- Download URL: pictograph-1.54.0-py3-none-any.whl
- Upload date:
- Size: 406.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.8
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b5f759e812a119a52786f5e91a94c6a99cd3486c6900646cae73d3cd311dac2d
|
|
| MD5 |
2fa041a09a72205b3415327e874703fb
|
|
| BLAKE2b-256 |
e1fba02e0b621b7fbcb5d3881b0002dbd57e803614026f7d7587780981784caf
|