---
title: Local & Framework Use
description: Run Pictograph datasets, annotations, and trained models locally — export to any of 8 formats, convert COCO/YOLO/Pascal VOC offline, feed a PyTorch training loop, run local ONNX inference, evaluate a model's precision/recall, and draw annotations, all with Pictograph's own tooling.
section: Workflows
order: 7
---
The Pictograph SDK is a complete, self-contained computer-vision toolkit — you don't need to bolt on a third-party library to get your data into a training loop, convert it between COCO/YOLO, export it, or visualize it. Everything below runs locally with Pictograph's own tooling. Only PyTorch and the local ONNX runtime are optional extras, and only because they're heavy.

```bash
pip install 'pictograph[torch]'         # client.datasets.as_pytorch(...)
pip install 'pictograph[inference]'     # local ONNX inference: get_model(...).predict(...)
pip install 'pictograph[all]'           # everything
```

## Export to any format

Export a dataset to a downloadable ZIP in any of the 12 Pictograph formats — `pictograph`, `coco`, `yolo`, `yolo_obb`, `yolo_pose`, `dota`, `pascal_voc`, `darwin`, `cvat`, `datumaro`, `labelme`, `csv` — built by Pictograph's own server-side converters, no extra dependency. `yolo_obb` and `dota` carry **oriented (rotated) boxes**; `coco` and `yolo_pose` carry **keypoint skeletons** (pose).

```python
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")
```

Or one command from the CLI:

```bash
pictograph datasets export road-signs --format yolo --include-images -o ./out
```

## COCO, YOLO & Pascal VOC converters (offline)

`pictograph.formats` converts between external COCO / YOLO / Pascal VOC annotations and Pictograph's typed annotation models entirely on your machine — no API call, no third-party library. Use it to bring an existing dataset into Pictograph's models (then save it with `client.annotations.save`), or to emit those formats from annotations you already hold. Unlike the ZIP export above (a whole dataset, server-side), these are pure in-memory functions over the same annotation objects the rest of the SDK uses.

```python
from pictograph.formats import from_coco, to_yolo

# Parse a local COCO file into Pictograph's typed 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)
```

`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. For hole-accurate COCO (RLE) or a downloadable ZIP, use the server-side export above.

To go from a local file to annotations saved on a dataset in **one call** — create missing classes, match images by filename, chunked bulk-save, and a per-image report — use the import pipelines (the dataset must already hold the images the file references):

```python
from pictograph import Client
from pictograph.pipelines import import_coco_annotations

client = Client()
report = import_coco_annotations(client, "road-signs", "instances_val.json")
print(report.images_saved, "images annotated;", len(report.unmatched_files), "unmatched")
```

Or one command from the CLI:

```bash
pictograph datasets import-coco road-signs instances_val.json
```

## PyTorch

`client.datasets.as_pytorch(name)` returns a map-style `torch.utils.data.Dataset` that plugs straight into a `DataLoader`. Each item is an `(image, target)` pair, where `target` follows the torchvision detection convention (`boxes` in xyxy, integer `labels`, `area`, `iscrowd`, `image_id`, and the raw annotations). Images download lazily on first access and are cached.

```python
from torch.utils.data import DataLoader
from pictograph import Client

client = Client()
dataset = client.datasets.as_pytorch("road-signs")
loader = DataLoader(dataset, batch_size=8, collate_fn=lambda batch: tuple(zip(*batch)))

for images, targets in loader:
    ...  # your training step
```

## Visualization

`draw_annotations` renders any Pictograph annotations onto an image using only Pillow, which is a base dependency, so it works out of the box — no third-party renderer. All four annotation types render, and each class gets a stable, distinct color.

```python
from pictograph import Client, draw_annotations

client = Client()
annotations = client.annotations.get("image-uuid")
annotated = draw_annotations("photo.jpg", annotations)
annotated.save("photo.annotated.png")
```

## Local inference

Trained Pictograph models run on your own machine with the `inference` extra. `get_model(...).predict(...)` returns an `InferenceResult` whose `.predictions` are ordinary Pictograph annotations — so you can draw them, save them back, or export them with everything above. See [Deployments](/docs/deployments.md) for the hosted inference endpoint option.

```python
from pictograph import get_model, draw_annotations

model = get_model("my-detector")          # downloads + caches the ONNX weights
result = model.predict("photo.jpg")
draw_annotations("photo.jpg", result.predictions).save("photo.pred.png")
```

## Local model evaluation

`pictograph.metrics` scores a model against a labeled set entirely on your machine — 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, and average precision — including **mAP** (`result.mean_average_precision`, the standard detection metric) and per-class `average_precision`. Predictions can come from `client.auto_annotate`, a deployed model's `/predict`, or local inference above.

```python
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}

result = evaluate_detections(predictions, ground_truth, iou_threshold=0.5)
print(result.precision, result.recall, result.f1)
for name, m in result.per_class.items():
    print(name, m.precision, m.recall, m.support)
```

`confusion_matrix()` (same module) returns a class-agnostic confusion matrix that
surfaces cross-class confusion. Want Pictograph to **run the inference for you**
and store the result? Use the server-side
[`client.model_evaluations`](/docs/api-reference/models#evaluation) — the metric
math is identical, so a server run and a local run on the same data agree.

## Active learning — rank by uncertainty

`pictograph.metrics.rank_by_uncertainty` turns a model's predictions into a
**review queue ordered by how unsure the model is**, so a human labels the
most-informative images first — the highest label-efficiency order, Pictograph's
native answer to the active-learning loop. Every score is in `[0, 1]` where higher
means more uncertain. It reads each prediction's `confidence`, so it works on
output from `client.auto_annotate`, a deployment `/predict`, or local inference.

```python
from pictograph.metrics import rank_by_uncertainty

# predictions for your UNLABELED / lightly-labeled images
predictions = {img_id: run_my_model(img_id) for img_id in unlabeled_image_ids}

for item in rank_by_uncertainty(predictions, method="least_confidence")[:20]:
    print(item.image_key, round(item.score, 3), item.num_predictions)
```

Four strategies aggregate an image's per-detection confidences: `least_confidence`
(default, `1 − mean(confidence)`), `min_confidence` (`1 − min`), `margin`
(closeness to the 0.5 decision boundary), and `entropy`. An image with **no
predictions** scores `1.0` by default — the model found nothing, which is itself
worth a look (pass `empty_score=0.0` to sink them instead).

For the whole loop in one call — enumerate a dataset's images, run your inference,
and rank — use the `pictograph.pipelines.rank_dataset_by_uncertainty` pipeline. It
returns an `ActiveLearningReport` (`.queue` ranked most-uncertain first, plus a
per-image `.failures` list so one bad inference never shrinks the queue silently):

```python
from pictograph import Client
from pictograph.pipelines import rank_dataset_by_uncertainty

client = Client()

def predict(image):                      # bring your own inference
    return run_my_model(image.filename)   # -> list[Annotation]

report = rank_dataset_by_uncertainty(
    client, "my-dataset", predict, unlabeled_only=True, max_images=500,
)
for item in report.queue[:20]:
    print(item.image_key, round(item.score, 3), item.num_predictions)
```