Skip to content

Annotations

CRUD operations for annotations.

nimbusimage.annotations.AnnotationAccessor

Access annotations for a specific dataset.

list(shape=None, tags=None, limit=0, offset=0, after_id=None, sort=None, sortdir=1)

List annotations in this dataset.

Parameters:

Name Type Description Default
shape str | None

Filter by shape ('polygon', 'point', 'line').

None
tags list[str] | None

Filter by tags (JSON-encoded array sent to server).

None
limit int

Max results. 0 = unlimited.

0
offset int

Skip this many results. NOTE: offset is positional and not stable across mutations — if you delete or add annotations between paged calls, an offset loop will skip or repeat records. Use :meth:iter_all (a stable after_id cursor) for delete/modify-as-you-go loops.

0
after_id str | None

Return only annotations whose _id is greater than this one (a stable cursor). When set, the server ignores offset. Only meaningful with sort="_id" (the server otherwise defaults to a non-_id sort); prefer :meth:iter_all, which sets this for you.

None
sort str | None

Field to sort by (e.g. "_id"). If omitted, the server uses its own default sort, which is not _id.

None
sortdir int

Sort direction, 1 ascending or -1 descending. Only applied when sort is set.

1

Returns:

Type Description
list[Annotation]

List of Annotation objects.

iter_all(shape=None, tags=None, page_size=1000)

Iterate over every matching annotation, one page at a time.

Walks the backend's stable afterId cursor: each page asks for annotations whose _id is greater than the largest _id seen so far. This is mutation-safe — unlike offset pagination, you can delete or modify annotations as you iterate without skipping records (the cursor only ever advances past IDs already yielded).

This is the recommended way to fetch large result sets and to drive delete-as-you-go cleanup loops.

The cursor's correctness requires each page to come back in ascending _id order (so page[-1] is the largest _id in the page). The server's default sort is not _id, so this explicitly requests sort="_id" on every page — without it the cursor could skip, duplicate, or loop on records.

Parameters:

Name Type Description Default
shape str | None

Filter by shape ('polygon', 'point', 'line').

None
tags list[str] | None

Filter by tags.

None
page_size int

Number of annotations to fetch per request.

1000

Yields:

Type Description
Annotation

Annotation objects, in ascending _id order.

get(annotation_id)

Get a single annotation by ID.

get_many(annotation_ids)

Fetch this dataset's annotations by id in one request (POST upenn_annotation/hydrate).

Scoped like every other method here: an id of another dataset is dropped from the result, as is an id that does not exist, so compare ids rather than lengths. Accepts any iterable of ids (e.g. a numpy array). One request takes at most the server's MAX_ANNOTATION_IDS; chunk larger lists.

count(shape=None, tags=None)

Count annotations matching filters.

summary(shape=None, tags=None, exclusive=False, property_paths=None, filters=None)

Count annotations by tag, shape and channel, and summarize property values, in one server-side aggregation (no annotations are downloaded).

Example::

s = ds.annotations.summary(tags=["cell"])
s.total, s.tags, s.shapes, s.channels
s = ds.annotations.summary(property_paths=[[prop.id, "Area"]])
s.properties[0].mean

Parameters:

Name Type Description Default
shape str | None

Only annotations of this shape ('point', 'polygon', 'line', 'rectangle').

None
tags list[str] | None

Only annotations with any of these tags, or with exactly these tags when exclusive is True (the same rule as filter_by_tags and the app's tag filter). Note that count(tags=...) instead requires all the given tags.

None
exclusive bool

See tags.

False
property_paths list[list[str]] | None

Property value paths to summarize, each a list of keys, e.g. [property_id, "Area"].

None
filters dict | None

Further filters in the server's list-filter format: location ({"XY": 0, "Z": 3, "Time": 0}, any subset), idSubstring, propertyFilters, idConstraints, analysisGates. Use shape and tags above rather than their keys here.

None

Returns:

Type Description
AnnotationSummary

AnnotationSummary with total, tags, shapes,

AnnotationSummary

channels (keyed by channel index), and one

AnnotationSummary

properties entry per requested path, in order.

Raises:

Type Description
HttpError

400 for what the server refuses: more than 200 property paths, a path key that is not a string or contains . or $, malformed filters, or property filters matching more ids than the server summarizes.

create(annotation)

Create a single annotation.

create_many(annotations, connect_to=None)

Create multiple annotations in bulk.

Parameters:

Name Type Description Default
annotations list[Annotation]

List of Annotation objects to create.

required
connect_to dict | None

If provided, auto-connect created annotations to nearest matching annotation. Dict with 'tags' and 'channel' keys.

None

Returns:

Type Description
list[Annotation]

List of created Annotations (with server-assigned IDs).

update(annotation_id, updates)

Update a single annotation.

Returns the updated annotation. If the server returns no body, fetches the annotation by ID to return the current state.

update_many(updates)

Update multiple annotations in a single HTTP request.

Parameters:

Name Type Description Default
updates list[tuple[str, dict]]

List of (annotation_id, updates_dict) tuples. Each updates_dict may include 'datasetId' (required only if moving the annotation to a different dataset).

required
Note

The bulk PUT endpoint returns no body, so this method does not return the updated annotations. Call get() on individual IDs if you need their fresh state.

delete(annotation_id)

Delete a single annotation.

delete_many(annotation_ids)

Delete multiple annotations.

compute(image, channel=0, tags=None, location=None, assignment=None, worker_interface=None, scales=None, connect_to=None, name='worker')

Run an annotation worker on this dataset.

Submits a Docker worker job via POST /upenn_annotation/compute. The worker container receives the parameters as a JSON string via --parameters and parses them with WorkerClient.

Parameters:

Name Type Description Default
image str

Docker image name (e.g., 'annotations/random_squares:latest').

required
channel int

Channel index for the worker to process.

0
tags list[str] | None

Tags to assign to created annotations.

None
location Location | None

Location (XY/Z/Time) for single-tile processing. Defaults to Location(). Mutually exclusive with assignment: in batch mode the worker iterates the assignment ranges and ignores location/tile, so passing both raises ValueError.

None
assignment dict | str | None

Assignment range for batch processing. Can be a dict like {'XY': '0-2', 'Z': 0, 'Time': 0} or range strings like {'XY': '0-2', 'Z': 0, 'Time': '0-4'}. Defaults to the location if not provided. Do not combine with location= (see above).

None
worker_interface dict | None

Parameter values matching the worker's interface schema (from client.get_worker_interface()). Keys must match exactly (e.g., 'Square size', not 'square_size').

None
scales dict | None

Scale metadata (pixel size, etc.). Passed through to the worker for unit-aware computations.

None
connect_to dict | None

Auto-connect created annotations to nearest neighbors. Dict with tags (list[str]) and channel (int) keys. If not provided, no connections are created.

None
name str

Job name shown in the Girder UI.

'worker'

Returns:

Type Description
Job

A Job object. Call job.wait() to block until completion.

Raises:

Type Description
ValueError

If location and assignment conflict, if connect_to lacks a tags key, or if image is a property worker (its role labels from /worker_interface/available have isPropertyWorker but not isAnnotationWorker) — property workers must run through ds.properties.compute instead.

Note

The worker container uses WorkerClient from the worker_client package, which requires all of these keys in the parameters: assignment, channel, connectTo, tags, tile, workerInterface. Missing keys cause the worker to skip initialization silently. The connectTo dict must always contain a tags key (use [] for no connections) — omitting it causes a KeyError after annotations are uploaded.