Amniscient Detection SDK for Python
Python client for the Amniscient Inference API: low-latency object detection
behind a single call, client.detect(image, model_id).
Installation
With optional dependencies for image compression:
Quick Start
Endpoint. The production endpoint is
https://api.amniscient.com/inference— plain HTTPS.
How it works
The whole surface is detect(). You give it an image and a model; it returns a
DetectionResult. Everything in between is handled for you:
- Auth is validated first — a missing or invalid API key raises
AuthenticationErrorbefore anything else happens. It is never skipped. - The image is prepared — loaded, optionally compressed, and validated.
- The request is sent and, on a transient failure, retried with backoff.
- The reply becomes a
DetectionResult, or a specific error is raised (see Error Handling).
How the request travels to the server is an internal detail with a sensible default — see Connecting if you need to change it.
For contributors, the pieces are small and single-purpose: client.py
orchestrates the flow and owns retries + error mapping; request_builder.py
builds and credential-checks the request; response_parser.py is the one place
a reply becomes a DetectionResult; models.py and exceptions.py hold the
result and error types.
Features
- One call,
detect(): connection, retries, and parsing are handled for you - Automatic Image Compression: Resize and compress images to optimize payload size
- Retry Logic: Exponential backoff for transient errors
- Validated auth: a missing or invalid API key raises
AuthenticationError— never a silent failure - Async Support:
detect_async()for asyncio compatibility - Raw Mode:
detect(raw=True)returns the unparsed server dict when you need it - Type Hints: Full type annotations for IDE support
Usage Examples
Using Context Manager
Async Detection
Different Image Inputs
Filtering Results
Raw Responses
By default detect() parses the server reply into a DetectionResult. Pass
raw=True to skip parsing and get back the exact server dict instead — use
this when you need the unparsed response (e.g. for logging/debugging) or when
downstream code already depends on the original xywh contract and you don’t
want to introduce the DetectionResult shape:
raw works the same way on detect_async(). Retries, error mapping, and auth
validation are unchanged — only the return value skips parsing.
Barcode & QR reading
Pass detect_barcodes=True to decode barcodes/QR codes alongside object
detection, in the same request:
It’s off by default — pass detect_barcodes=True explicitly (works the same
on detect_async()). result.barcodes is a list of {"type", "value"} dicts,
and the raw response (raw=True) has the same top-level barcodes array:
Supported symbologies (wire type values): qr, upc-a, upc-e, code-39.
Notes:
barcodesis[]whendetect_barcodesis off, no codes are found, or the decode step fails or times out — it never raises and never affectsresult.detections/xywh.- Numeric 1D codes (UPC-A/UPC-E) decode to their expanded digit string (e.g.
"0012345678905"), so don’t expect an exact round-trip of a shorter printed value.code-39returns the encoded text as-is.
Pinning a model version
Models are versioned. By default a request runs whichever version is currently active, so results follow promotions automatically:
Versions are 1-based. version works the same on detect_async() (sent as a
version form field).
Notes:
- Pin when you need reproducible output — a benchmark, a regression suite, or a customer held to a specific model revision. Leave it unset to get improvements as they’re promoted.
- The version also selects that version’s confidence/IoU thresholds and detection-item tags, not just its weights.
- A version that doesn’t exist raises
ModelNotFoundError. - A malformed value (non-numeric, or less than 1) is rejected up front rather than silently falling back to the active version.
Multi-tenant / per-request credentials
The client normally holds one credential for its lifetime. If you’re a
multi-tenant proxy whose
credentials arrive with each request and are never stored server-side, build
one shared client with no stored credentials and pass api_key /
organization_id per detect() call — they ride each request, and nothing is
kept between calls:
Per-call credentials ride each request and are never stored between calls — use this for the per-request, never-store proxy pattern. HTTP only: a NATS client is bound to one key at connect time.
Validating credentials (login gate)
To accept an API key + org before a model is chosen (e.g. a web login
step), use validate() — it needs no model id and no image:
validate() returns True/False — it calls GET /models, so an accepted
key gets a 200 (a real positive signal) and a rejected key (401/403) →
False. Anything else (an unexposed route, a 5xx) propagates rather than
masquerading as valid, so you never get a false “looks valid” from a route that
simply isn’t there — inspect status_code on the raised DetectionError if you
need to tell those apart. get_default_model() returns the org’s default model
dict (or None when none is set), so you can pre-fill the model at a later
step. Both are HTTP only and authenticate through the API gateway.
Listing models (model picker)
To let a user pick a model instead of pasting a UUID, list_models() returns
the org’s selectable models — no model id or image needed:
It returns top-level models (specialist sub-models are excluded), each with
id, name, and status. HTTP only, authenticated through the gateway;
organization_id / api_key override the client’s defaults.
Putting it together — a typical flow
The methods above compose into one flow: validate the key at login → list models to pick one → detect on each capture. How you hold the client depends on your architecture.
Single-tenant app (one org, credentials known at startup) — one client for its lifetime:
Multi-tenant proxy (e.g. Iris — credentials arrive per request, nothing stored) — one shared client, credentials passed per call:
Distinguish a real credential failure from a gateway routing error at the
login gate — see Error Handling for the 401 vs 403
pattern.
Annotating detections (draw boxes)
Turn a /detect response + the source image into draw-ready boxes, or a
ready-to-display PNG. Pass the raw payload (detect(raw=True)), unchanged:
Options: label="name" (default) or label="id"; color as a hex string
(default #00FF00); render=True to render eagerly. Box thickness + font size
scale with the image resolution. Install the drawing dependency for to_png():
Handle the no-detections case — an empty payload raises NoDetectionsError
instead of returning a blank image:
Name labels are pulled by id from the model’s detection-items (the metadata
endpoint, a sibling of your inference endpoint), cached per model and falling
back to the id. The SDK derives that URL from an https://…/inference endpoint;
if yours differs, pass metadata_endpoint= when constructing the client — or
pass a names={id: name} map to annotate_detections to skip the network call.
Legacy methods
health_check() and load_model() remain for backward compatibility but run
over the deprecated gRPC transport — they need a gRPC host:port endpoint
and are not part of the detect() path. New integrations should
not depend on them.
Connecting
The SDK talks to the API over HTTPS by default, or over NATS (WebSocket)
when you ask for it. Over HTTPS you only supply the endpoint, key, and org (as
in every example above); no transport= needed:
gRPC was the original connection method; it is deprecated and no longer on the
detect()path.
Over NATS (WebSocket)
For lower latency and a persistent connection, use the NATS transport. It’s
included in the standard install; pass transport="nats" and the NATS
endpoint. Your API key is all you need: the SDK resolves your organization
from the key, and the NATS server confines the connection to that organization.
Everything else is the same as HTTP: detect(), detect_async(),
list_models(), validate(), get_default_model(), barcodes, version pinning
and the same exceptions (an unknown model is ModelNotFoundError, a rejected
key is AuthenticationError).
Differences to know:
- One tenant per client. NATS binds the key to the connection, so per-call
api_key/organization_idoverrides raiseValueError. For a multi-tenant proxy, use HTTP (see Multi-tenant / per-request credentials). http_detect_pathdoesn’t apply (HTTP only).
HTTP detect path
Over HTTP the SDK posts to the customer detect route /detect with model_id
(or inference_point_id) in the form body. To hit a route that carries the
model in the URL path instead (e.g. the internal /detectwithModel/{model_id}),
override it with http_detect_path (only used when transport="http"):
The path must contain a {model_id} placeholder; it’s filled in per request.
Configuration Options
Error Handling
Every failure surfaces as a typed exception. Authentication is always
validated: a missing or invalid API key raises AuthenticationError — the
call never silently succeeds or hangs.
Every exception carries status_code and response_body (the server’s HTTP
status and parsed body) when the failure came from an HTTP response — both
None for client-side errors.
A missing route is not a rejected key. An API Gateway returns 403 MISSING_AUTHENTICATION_TOKEN for an unmatched route even when the key is
valid. The SDK recognizes that signature and raises EndpointNotAvailableError
(a distinct type — not AuthenticationError), so a route that simply isn’t
deployed never masquerades as “bad credentials”:
validate() returns True/False for a genuine accept/reject, and raises
EndpointNotAvailableError when the route isn’t deployed — it never answers a
confident False for a route that isn’t there.
Development
Setup
Requires Python 3.10+ (use an explicit python3.12 — macOS python3 is often
3.9, and there’s no bare python):
Testing
Unit tests — fast, no network (the connection layer is mocked). Run these while developing:
Live sandbox test — proves a real detect() succeeds and a bad key is
actually rejected, end to end against the sandbox environment. One script sets
up credentials, tunnels, and env, then runs the integration suite:
(Requires an AWS SSO refresh first; the script fetches a fresh cluster token, so re-run it if you hit an auth error.)
Building, releasing to PyPI, and the full contributor workflow are documented in BUILD.md.
License
MIT License - see LICENSE file for details.