Skip to navigation

Amniscient Detection SDK for Java

Java client for the Amniscient Inference API, over HTTPS or NATS (WebSocket). It gives you low-latency object detection behind one call, client.detect(image, modelId).

Requires Java 8 or later, and is tested on 8, 11, 17 and 21. It has two dependencies, both Java 8 compatible: Jackson (JSON) and jnats (the NATS transport). HTTP is the JDK’s built-in HttpURLConnection, and compression and annotation use the JDK’s ImageIO and 2D graphics.

This SDK follows the same contract as the Python and TypeScript SDKs and passes the same conformance suite (sdk/contract). The calls, errors, retries and result shape are identical; only the language differs.

Install

Maven:

<dependency>
<groupId>com.amniscient</groupId>
<artifactId>amniscient-detection</artifactId>
<version>0.1.0</version>
</dependency>

Gradle:

implementation "com.amniscient:amniscient-detection:0.1.0"

Quick start

import com.amniscient.detection.DetectionClient;
import com.amniscient.detection.model.Detection;
import com.amniscient.detection.model.DetectionResult;
import java.nio.file.Paths;
try (DetectionClient client = DetectionClient.builder()
.endpoint("https://api.amniscient.com/inference")
.apiKey("YOUR_KEY")
.organizationId("YOUR_ORG")
.build()) {
DetectionResult result = client.detect(Paths.get("photo.jpg"), "MODEL_ID");
System.out.println(result.detectionFound());
for (Detection d : result.detections()) {
// bbox is corner form, normalized 0-1: x1, y1, x2, y2
System.out.printf("%s %.2f %s%n", d.classId(), d.confidence(), d.bbox());
}
}

One client is thread-safe; share it across your application and close it on shutdown.

How it works

  1. Auth is validated first: a missing API key throws AuthenticationException before any image work or network call.
  2. The image is prepared: checked (JPEG, PNG or WebP), then resized to fit 3840 px on the long side and JPEG-encoded at quality 70, unless you turn compression off.
  3. The request is sent over HTTPS or NATS. A transient failure (network error, timeout, 5xx) is retried with exponential backoff.
  4. The reply becomes a DetectionResult, or a typed exception is thrown (see Errors).

Over NATS (WebSocket)

For lower latency and a persistent connection, use the NATS transport. The same API key works. The organization is resolved from the key when you don’t pass it, and the server confines the connection to that organization.

try (DetectionClient client = DetectionClient.builder()
.endpoint("wss://nats-server.amniscient.com")
.apiKey("YOUR_KEY")
.transport(DetectionClient.TransportKind.NATS)
.build()) {
DetectionResult result = client.detect(Paths.get("photo.jpg"), "MODEL_ID");
}
  • One tenant per client. NATS binds the key to the connection, so per-call apiKey / organizationId in DetectOptions throw IllegalArgumentException. Use one client per tenant, or HTTP for a multi-tenant proxy.
  • Same errors and retries as HTTP. A rejected key is an AuthenticationException; timeouts and 5xx replies are retried.
  • close() drains the connection. Use try-with-resources or close the client on shutdown.

Usage

Options per call

import com.amniscient.detection.DetectOptions;
DetectionResult r = client.detect(Paths.get("shelf.jpg"), "MODEL_ID", DetectOptions.builder()
.confidenceThreshold(0.5) // default 0.25
.iouThreshold(0.5) // default 0.45
.version(3) // pin a model version; default: the active one
.detectBarcodes(true) // read barcodes and QR codes too
.requestId("order-123") // correlation id; default: a new UUID
.build());

To resolve the model from an inference point instead of a model id, set .inferencePointId("…") and pass null for the model id.

Barcodes and QR codes

With detectBarcodes(true), result.barcodes() lists the decoded codes. Object detection is unchanged.

for (Barcode code : r.barcodes()) {
System.out.println(code.type() + " " + code.value()); // e.g. upc-a 012345678905
}
  • Types: qr, upc-a, upc-e, code-39.
  • When it’s empty: the list is empty when the option is off, no codes are found, or decoding fails. Decoding never fails the call.

Filtering results

DetectionResult confident = result.filterByConfidence(0.8);
DetectionResult colas = result.filterByClassId("cola", "diet-cola");
DetectionResult tagged = result.filterByTag("tag", "cola");

Raw responses

detectRaw(bytes, modelId, options) returns the server’s JSON as a Map<String, Object>, the xywh centre-box shape. Use it when you need fields the model doesn’t cover. result.raw() gives you the same map.

Multi-tenant / per-request credentials (HTTP)

Build one shared client with no stored credentials and pass them per call. They ride the request and nothing is kept:

DetectionClient shared = DetectionClient.builder().endpoint("https://api.amniscient.com/inference").build();
DetectionResult r = shared.detect(image, "MODEL_ID",
DetectOptions.builder().apiKey(tenantKey).organizationId(tenantOrg).build());

Login gate, model picker, default model

boolean ok = client.validate(); // true = key accepted; false = rejected
List<ModelInfo> models = client.listModels(); // id(), name(), status()
Map<String, Object> def = client.getDefaultModel(); // null when the org has none

validate() returns false only for a rejected key. A route that isn’t deployed (EndpointNotAvailableException), a server error or a connection problem is thrown, never reported as false.

Annotating detections

Map<String, Object> payload = client.detectRaw(bytes, "MODEL_ID", DetectOptions.defaults());
AnnotationResult a = client.annotate(bytes, payload, AnnotateOptions.builder().label("id").build());
a.getBoxes(); // pixel-space, labelled boxes: draw them in any toolkit
byte[] png = a.toPng(); // or a ready-made annotated PNG

Configuration

Builder methodDefaultDescription
endpointrequiredhttps://api.amniscient.com/inference (HTTP) or wss://nats-server.amniscient.com (NATS)
apiKey—Your API key; over HTTP it can be given per call instead
organizationId—Optional: the gateway sets it from the key over HTTP, and it’s resolved from the key over NATS
transportHTTPTransportKind.HTTP or TransportKind.NATS
timeout30 sPer-request timeout
maxRetries3Retries after the first attempt for transient failures (network, timeout, 5xx). 0 means one attempt. Waits 0.25 s, 0.5 s, 1 s, … capped at 4 s
compressiontrueResize and JPEG-encode before sending
highCompressionfalseFit within 800x600 at a lower quality
jpegQuality70JPEG quality, 1-100
maxDimension3840Longest-side cap in pixels
detectPath/detectHTTP detect route; a {model_id} placeholder is substituted
whoamiUrlknown for Amniscient endpointsGateway /whoami, used to resolve the org over NATS when organizationId isn’t set

Other environments: https://api.stage.amniscient.com/uat/inference + wss://nats-server.uat.internal.amniscient.com, …/dev/… + wss://nats-server.dev.internal.amniscient.com, https://api.sandbox.amniscient.com/sandbox/inference + wss://nats-server.sandbox.internal.amniscient.com. All of them are listed in Contract.HTTP_ENDPOINTS and Contract.NATS_ENDPOINTS.

Errors

Every failure is an unchecked exception under DetectionException. Each carries getStatusCode() and getResponseBody() when a server answered.

ExceptionMeaning
AuthenticationExceptionMissing or rejected API key (401, or a 403 that isn’t a missing route)
EndpointNotAvailableExceptionThe route isn’t deployed on this gateway/stage (403 MISSING_AUTHENTICATION_TOKEN); the key is valid
ModelNotFoundExceptionThe model doesn’t exist (404)
ImageTooLargeExceptionThe image is over the server’s limit (413)
RateLimitExceptionToo many requests (429)
ServerExceptionServer error (5xx), after retries for detect
ConnectionExceptionNetwork failure or timeout, after retries for detect
InvalidImageExceptionThe image is empty or not a JPEG, PNG or WebP
NoDetectionsExceptionannotate was given a payload with no detections
IllegalArgumentExceptionA bad argument, e.g. neither a model id nor an inference point
try {
client.detect(Paths.get("photo.jpg"), "MODEL_ID");
} catch (AuthenticationException e) {
// fix the key
} catch (RateLimitException e) {
// back off and retry later
} catch (DetectionException e) {
// anything else; e.getStatusCode() / e.getResponseBody() for details
}

Live harness

HarnessRunner drives the SDK end to end against a live endpoint. It runs validate(), listModels(), then detect() over one image or a folder, and prints a summary. Config comes from the environment, so no secrets go on the command line:

export AMNI_ENDPOINT="https://api.amniscient.com/inference" # or wss://nats-server.amniscient.com
export AMNI_TRANSPORT="http" # or nats
export AMNI_API_KEY="…"
export AMNI_ORG="…"
export AMNI_MODEL="…"
export AMNI_IMAGE="/path/to/image.jpg" # a file, or a directory to batch
export AMNI_BARCODES="true" # optional
export AMNI_ANNOTATE_OUT="annotated.png" # optional: write an annotated PNG (single image)
mvn -q compile exec:java

Build & test

Requires JDK 8+ and Maven.

mvn test # unit tests (canned local HTTP server, no network)
./conformance.sh # the shared cross-SDK suite over HTTP and NATS (needs nats-server + python nats-py)
mvn package # build the jar

Contract.java is generated from sdk/contract/contract.json; don’t edit it by hand. Run python3 sdk/contract/generate.py instead.