Models
List trained CV models in your organization and download their ONNX weights.
Models are produced by training runs. The SDK doesn’t insert model rows directly — you train, then read.
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
Like datasets, models are unique by (organization, name). Every
single-model method takes the name positionally or a model_id=
keyword — exactly one — and both forms hit the same serializer, so the
returned shape is identical.
Response envelope. A single model is {"data": {…}}; a collection is
{"data": [...], "pagination": {"limit", "offset", "total", "has_more"}} where
total is the org-wide count. Internal storage paths are never returned — fetch
weights via download, which mints a short-lived signed URL.
list
Single-page list of models in your organization. Returns the collection
envelope with a server-computed total and has_more.
| Arg | Type | Default | Notes |
|---|---|---|---|
dataset_name | str | None | None | Restrict to models trained on this dataset |
status | ModelStatus | None | None | "training" / "ready" / "failed" / "archived" |
model_type | ModelType | None | None | "object_detection" / "semantic_segmentation" / "instance_segmentation" / "classification" |
limit | int | 50 | Backend cap: 100 |
offset | int | 0 | Page offset |
models = client.models.list(limit=20)
for m in models:
print(m.name, m.architecture, m.status, m.metrics)
curl -s "https://api.pictograph.io/api/v1/developer/models/?limit=20" \
-H "X-API-Key: $PICTOGRAPH_API_KEY"
iter
Auto-paging iterator across every model in your organization.
for m in client.models.iter(page_size=50):
print(m.id, m.model_type)
# Page manually with limit + offset until fewer than `limit` rows return.
curl -s "https://api.pictograph.io/api/v1/developer/models/?limit=50&offset=0" \
-H "X-API-Key: $PICTOGRAPH_API_KEY"
get
Fetch a single model by name (or model_id= UUID).
model = client.models.get("Stop Sign Detector") # by name
model = client.models.get(model_id="model-uuid") # by id
print(model.architecture, model.metrics["mAP"], model.class_mapping)
# By name (org-unique):
curl -s "https://api.pictograph.io/api/v1/developer/models/by-name/Stop%20Sign%20Detector" \
-H "X-API-Key: $PICTOGRAPH_API_KEY"
# By UUID:
curl -s "https://api.pictograph.io/api/v1/developer/models/model-uuid" \
-H "X-API-Key: $PICTOGRAPH_API_KEY"
Returns {"data": Model}. Inspect metrics (mAP, precision, recall) and
class_mapping (index → class name) for inference setup.
update
Rename a model, edit its description / readme, set its license, or flip its
visibility. Only the fields you pass change. Requires a member+ API key;
changing visibility (publishing to Explore) requires admin+. A new_name
that collides with another model in your org is rejected (400).
| Arg | Type | Notes |
|---|---|---|
name / model_id | str | Address the model by name (positional) or UUID (keyword) |
new_name | str | None | Rename the model |
description / readme | str | None | Metadata |
visibility | "private" | "public" | None | admin+ only |
license_id / license_custom_text | str | None | License catalog id, or "custom" + body |
model = client.models.update("Stop Sign Detector", description="road-signs v2")
model = client.models.update(model_id="model-uuid", new_name="Signs v2", visibility="public")
curl -s -X PATCH "https://api.pictograph.io/api/v1/developer/models/by-name/Stop%20Sign%20Detector" \
-H "X-API-Key: $PICTOGRAPH_API_KEY" -H "Content-Type: application/json" \
-d '{"description": "road-signs v2"}'
download
Stream the weights to a local file. Only status="ready" models are
downloadable. format="onnx" (default) serves the exported ONNX graph;
format="pytorch" serves the native PyTorch .pth. Models trained before
dual-format export have ONNX only — format="pytorch" raises
409 ConflictError for those.
| Arg | Type | Default | Notes |
|---|---|---|---|
name / model_id | str | required | Address by name (positional) or UUID (keyword) |
output_path | str | Path | required | Local destination |
format | "onnx" | "pytorch" | "onnx" | Weights format |
from pathlib import Path
client.models.download("Stop Sign Detector", output_path=Path("./yolox.onnx"))
client.models.download(model_id="model-uuid", output_path=Path("./yolox.onnx"))
# Returns {"data": {"download_url": …, "expires_in_minutes": 60, …}}; fetch the weights from it.
curl -s "https://api.pictograph.io/api/v1/developer/models/by-name/Stop%20Sign%20Detector/download?format=onnx" \
-H "X-API-Key: $PICTOGRAPH_API_KEY"
The SDK download is chunked and lands in a sibling .part file renamed
atomically on success. Safe for multi-GB models.
fork
Import (fork) a public model into your organization. The model analog
of forking a public dataset: the fork references the source model’s weights
(no byte copy), so it is downloadable immediately and fast even for large
models. The copy’s name is suffixed ("Name (2)") if a model of that name
already exists. Requires member, admin, or owner role.
model = client.models.fork("source-public-model-uuid")
print(model.id, model.visibility, model.forked_from_model_id)
curl -s -X POST "https://api.pictograph.io/api/v1/developer/models/source-public-model-uuid/fork" \
-H "X-API-Key: $PICTOGRAPH_API_KEY"
Returns the newly created Model in your organization
(visibility="private", status="ready").
delete
Delete a model by name (or model_id= UUID). Requires admin or owner
role. Removes the database row immediately; GCS weight cleanup runs as a
background task, so the file may linger briefly after the call returns.
client.models.delete("Stop Sign Detector") # by name
client.models.delete(model_id="model-uuid") # by id
curl -s -X DELETE "https://api.pictograph.io/api/v1/developer/models/by-name/Stop%20Sign%20Detector" \
-H "X-API-Key: $PICTOGRAPH_API_KEY"
bulk_delete
Delete many models in one atomic, org-scoped, server-side call — never fans
out N requests. Idempotent: ids that don’t resolve in your org are returned in
not_found rather than raising. Requires admin or owner role.
result = client.models.bulk_delete(["model-uuid-1", "model-uuid-2"])
print(result.succeeded, result.not_found, result.count)
curl -s -X POST "https://api.pictograph.io/api/v1/developer/models/bulk-delete" \
-H "X-API-Key: $PICTOGRAPH_API_KEY" -H "Content-Type: application/json" \
-d '{"model_ids": ["model-uuid-1", "model-uuid-2"]}'
Returns {"data": {"succeeded": [...], "not_found": [...], "count": N}}.
Status lifecycle
status | Meaning |
|---|---|
training | Training is in progress; download returns an error until ready. |
ready | Trained successfully; weights downloadable via download(). |
failed | Training stopped with an error. Inspect the source TrainingRun.error_message. |
archived | Retired via update(status="archived"). Hidden from list() unless the status="archived" filter is passed. (Distinct from delete, which removes the row.) |
Inference
The SDK’s models resource ships read access only — there is no
client.models.infer(). To run inference on the weights yourself,
download the ONNX file and use your own runtime (onnxruntime,
tensorrt, etc.):
import onnxruntime as ort
client.models.download(model.id, output_path="./model.onnx")
session = ort.InferenceSession("./model.onnx")
# … standard ORT inference loop
For managed inference, run the model through a workflow (loads the weights per run, no separate deployment required).
Evaluation
client.model_evaluations scores a trained detection or
instance-segmentation model against a labeled dataset’s ground truth —
per-class and overall precision / recall / F1 plus a confusion matrix — running
the inference for you server-side. Your ground-truth annotations are never
modified. (For a purely offline scoring pass over predictions you already have,
use pictograph.metrics.)
# One call: create the run and block until it completes.
ev = client.model_evaluations.evaluate(
model_id="model-uuid",
project_id="labeled-dataset-uuid",
iou_threshold=0.5,
confidence_threshold=0.5,
)
print(ev.overall_metrics.precision, ev.overall_metrics.recall, ev.overall_metrics.f1)
for c in ev.per_class_metrics or []:
print(c.class_name, c.precision, c.recall, c.support)
# The confusion matrix (rows = ground truth, cols = predicted; last row/col is
# `__background__` for false positives / false negatives).
cm = ev.confusion_matrix
print(cm.labels)
print(cm.grid)
Prefer to start it and poll later? Use the lower-level methods:
ev = client.model_evaluations.create("model-uuid", "labeled-dataset-uuid")
ev = client.model_evaluations.wait_for_completion(ev.id) # or .get(ev.id) to poll manually
runs = client.model_evaluations.list(model_id="model-uuid")
client.model_evaluations.cancel(ev.id)
Evaluation runs on the trained-model batch-inference path and is billed the same
way. An AsyncClient mirror is available at client.model_evaluations too. Only
detection and instance-segmentation models are supported (other model types
return a ValidationError).
Common errors
| Status | Exception | Cause |
|---|---|---|
| 404 | NotFoundError | Name/model_id missing or belongs to another org; or fork source is not a ready public model |
| 409 | ConflictError | download(format="pytorch") on a model with ONNX-only weights |
| 400 | ValidationError | download on a non-ready model; or update(new_name=…) collides with an existing model |
| 403 | ForbiddenError | update/fork require member+; update(visibility=…), delete, bulk_delete require admin+ |