LYNX Python API Reference

Install

1pip install golynx # default — GPU on Linux, CoreML on macOS,
2 # DirectML on Windows; no setup required
3pip install golynx-cpu # Linux opt-in lean wheel, CPU-only ORT

The Python import path is always import lynx, regardless of which distribution was installed (same decoupling as scikit-learnsklearn).


Quickstart

1from lynx import LYNX
2
3# Load model — name (downloads from golynx.ai) or local path
4model = LYNX("lynx-basic")
5# model = LYNX("/path/to/model.lnx")
6
7# Run detection
8results = model("image.jpg")
9
10# Access detections
11for box, score, label in zip(results.boxes.xyxy, results.scores, results.labels):
12 x1, y1, x2, y2 = box
13 print(f"{label}: {score:.2f} at ({x1:.0f}, {y1:.0f}, {x2:.0f}, {y2:.0f})")
14
15# Visualize / save
16results.show()
17results.save("annotated.jpg")

Top-level imports

lynx.__all__ exports:

NameKindPurpose
LYNXclassMain detector
ResultsclassDetection results from one frame
OBBclassOriented bounding-box accessor (carried on Results.obb)
ProbsclassClassification probabilities accessor (carried on Results.probs)
StreamManagerclassMulti-stream orchestration
servefunctionHTTP detection server
predict_clifunctionRun the lynx CLI programmatically (e.g. from notebooks)
submit_feedbackfunctionSend Synetic an image plus an optional 'what I expected' note (developer feedback path)
autotunefunctionProbe channel-order × rotation × resize on an image + model to find a config that produces detections (onboarding diagnostic)
LYNXErrorexceptionBase class for all LYNX errors
LYNXAuthErrorexceptionAuth / license key issues
LYNXModelNotFoundErrorexceptionModel slug / file missing
LYNXDownloadErrorexceptionNetwork failure during model download
LYNXFormatErrorexception.lnx file malformed or signature invalid
LYNXLicenseErrorexceptionLicense expired or device unbound
LYNXIntegrityErrorexceptionWeight hash mismatch (tamper detection)
LYNXPathSecurityErrorexceptionPath traversal attempt blocked

CLASSLYNX

The detector. Constructed once per model; reused across many frames.

METHODLYNXclick to expand
LYNX(model, key=None, device="auto", conf=0.25, iou=0.45, track=False, ...)
1model = LYNX(
2 "lynx-basic", # name or path
3 key=None, # license key (None for free models)
4 device="auto", # "auto" / "cuda" / "cpu" / "mps"
5 conf=0.25, # confidence threshold (float or dict)
6 iou=0.45, # NMS IoU threshold
7 track=False, # enable persistent track IDs
8 active_learning=False, # flag low-confidence for review
9 al_conf_threshold=0.4,
10 max_latency_ms=None, # warn if inference exceeds budget
11 deterministic=False,
12 camera=None, # camera preset: "realsense-d435" etc.
13 output_format="numpy", # no-op; back-compat only
14 backend=None,
15 version="latest",
16 auto_update=True,
17 behavior=False, # temporal behavior classifier (requires track=True)
18 behavior_window=32,
19 behavior_max_tracks=32,
20 behavior_model=None,
21)

model resolution:

  • Bare name ("lynx-basic") → downloaded from golynx.ai/api/models/<name>
  • Path with .lnx extension → loaded from disk
  • Both undergo cert chain + decrypt verification before use

conf as dict: per-class thresholds with a default:

1conf={"person": 0.5, "car": 0.3, "default": 0.25}

camera presets: "realsense-d435", "realsense-d455", "zed-2i", "zed-x" — sets intrinsics for RGBD models.

behavior=True requires track=True; behaviors fire per track_id.

METHODmodelclick to expand
model(source, depth=None, conf=None, iou=None, max_det=300, mode=None, ...) *(__call__)*

Run detection on input. Returns Results (single) or list[Results] (batch/directory).

ParamTypeDefaultNotes
sourcestr / Path / np.ndarray / PIL.Image / listrequiredFile path, directory, image array, or list of any
depthnp.ndarray / str / NoneNoneDepth map for RGBD models
conffloat / dict / Noneself.confPer-call override
ioufloat / Noneself.iouNMS IoU override
max_detint300Cap detection count
mode"rgb" / "thermal" / "rgbd" / "ocr" / NoneautoAuto-detects thermal from uint16 arrays
ocr_depthint 1-73OCR analysis depth (mode="ocr" only)
invertboolTrueRun on color-inverted too (mode="ocr")
correct_perspectiveboolFalseFlatten warped surfaces (mode="ocr")

Returns:

  • Single image → Results
  • Directory or list → list[Results]
  • mode="ocr"OCRResults (separate type with .text / .text_blocks)
METHODmodel.encodeclick to expand
model.encode(source, layer="pooled")

Encoder mode — extracts feature embeddings (raw feature vectors for retrieval, clustering, downstream training). Requires the model to export encoder_<layer> outputs. Raises ValueError listing available outputs if not.

Returns:

  • Single input → (D,) np.ndarray
  • Batch/list → (N, D) np.ndarray
  • Directory → ((N, D) np.ndarray, list[str] paths)
METHODmodel.classifyclick to expand
model.classify(source, top_k=5)

Classification mode — predicts per-class probabilities. Requires a classifier artifact (lynx_is_classifier=true metadata flag + classifier_logits or classifier_probs ONNX output). Detection-only models raise ValueError.

Returns:

  • Single input → Results with .probs populated, .boxes empty.
  • Batch / list / directory → list[Results].

Shape of a classification Results:

AttributeValue
len(results)0 (detections list is always empty)
results.boxesBoxes wrapping a (0, 4) array — every accessor (xyxy, xywh, xyxyn, xywhn) returns a (0, 4) array; len(results.boxes) == 0
results.scores(0,) float32 np.ndarray
results.labels[]
results.probs.data(num_classes,) float32 — full probability vector
results.probs.indicesNone (the position IS the class ID)
results.probs.top_k(k)(indices, scores) tuple
results.probs.top1()(class_id, confidence) tuple
results.save_txt(path, save_conf=True, top_k=5)Writes top-top_k class IDs (auto-detected via probs is not None + empty detections). top_k defaults to 5; pass top_k=1 for the single top class.

Pass the Results through top_k(k) to get a Results carrying just the K most-probable classes — see Results.top_k below.

encode() returns feature vectors; classify() returns class probabilities. They are different operations served by different ONNX outputs (encoder_* vs classifier_*).

METHODmodel.trackclick to expand
model.track(source, conf=None, iou=None, max_det=300)

Equivalent to __call__ with persistent tracking enabled. model.tracker() returns the tracker state object.

METHODmodel.streamclick to expand
model.stream(source, conf=None, iou=None, max_det=300)

Generator that yields Results per frame for video file or webcam input.

METHODmodel.exportclick to expand
model.export(format="onnx", output=None)

Extract the verified ONNX bytes from the loaded .lnx to disk. Only format="onnx" supported (CoreML / TFLite / TensorRT conversion happens upstream in Pax). Returns the output path.

METHODmodel.benchmarkclick to expand
model.benchmark(img_size=None, n=100, warmup=10)

Time n inference passes. Returns dict with timing metrics.

METHODmodel.warmupclick to expand
model.warmup(n=3)

Force-compile + run n warm-up inferences. Useful before timing-sensitive operations.

METHODmodel.set_input_resolutionclick to expand
model.set_input_resolution(w, h)

Override the model's native input resolution. Subsequent calls use the new size.

Other accessors and properties

Method/PropertyPurpose
model.namesdict[int, str] — class index → name
model.info()dict of model metadata (num_classes, img_size, strides, etc.)
model.skeleton_edges (prop)Keypoint connectivity edges
model.emits_obb (prop)boolTrue when the artifact carries lynx_emits_obb=true
model.is_classifier (prop)boolTrue when the artifact carries lynx_is_classifier=true
model.is_ready()bool
model.close()Explicit cleanup (releases C handles)
METHODArtifact-metadata accessorsclick to expand
Artifact-metadata accessors (v2 schema)

Every .lnx artifact built by Pax v2+ carries preprocessing convention and decoding-default metadata directly. The SDK reads it at load and exposes the resolved view through read-only properties. Use these when you need to know what the model expects without reading the producer's documentation.

PropertyTypeDefault (v1 fallback)Source key
model.channel_order"rgb" / "bgr""rgb"lynx_channel_order
model.resize_mode"stretch" / "letterbox""stretch"lynx_resize_mode
model.normalize_mean[r, g, b] floatImageNetlynx_normalize_mean
model.normalize_std[r, g, b] floatImageNetlynx_normalize_std
model.normalize_scalefloat1/255lynx_normalize_scale
model.letterbox_pad_valueint 0-255114 (YOLO)lynx_letterbox_pad_value
model.default_conf_thresholdfloat / NoneNonelynx_default_conf_threshold
model.default_iou_thresholdfloat / NoneNonelynx_default_iou_threshold
model.per_class_conf{class_name: float}{}lynx_per_class_conf
model.output_headslist[dict] / NoneNonelynx_output_heads
model.metadatadictfull resolved snapshot (debug)

The SDK preprocess path reads these automatically — customers don't need to touch them unless they want to introspect. If model.channel_order == "bgr" and you hand in cv2-loaded BGR bytes, the SDK swaps for you. If model.resize_mode == "letterbox", the SDK pre-letterboxes with model.letterbox_pad_value and back-maps predictions to source coords accounting for pad offsets.

model.metadata is a single dict carrying every v2 field plus the v1 fields (class_names, img_size, strides, output_map) and producer provenance (model_slug, model_version, producer, producer_version, training_dataset, export_timestamp). Useful for support tickets and "which model do I have loaded?" debugging.

Confidence threshold resolution. When you don't pass conf= to LYNX(), the SDK uses the artifact's lynx_default_conf_threshold. When the artifact also declares lynx_per_class_conf, the SDK promotes model.conf to a {class_name: float} dict carrying the artifact's per-class overrides plus your scalar as the "default" fallback. Passing your own conf={"car": 0.6, "default": 0.4} merges on top — your keys win, the artifact's keys survive where you didn't override.

Callbacks

1def on_apple_detected(result, det_idx):
2 print(f"Apple at {result.boxes.xyxy[det_idx]}")
3
4model.on_detect("apple", on_apple_detected)
5model.on_detect(["apple", "banana"], on_apple_detected) # multiple classes
MethodFires when
on_detect(classes, callback)Specific class(es) detected; callback receives (results, det_idx)
on_enter_zone(zone, callback)Track enters a LYNXZone
on_exit_zone(zone, callback)Track leaves a LYNXZone
on_line_cross(line, callback)Track crosses a LYNXLine
on_behavior(labels, callback)Behavior classifier fires for a label (needs behavior=True)

CLASSResults

Detection results for a single image. Ultralytics-compatible API.

Core attributes

AttributeTypeDescription
boxesBoxes.xyxy, .xywh, .xyxyn, .xywhn accessors (all (N, 4) np arrays)
scores(N,) float32 np.ndarrayConfidence scores
class_ids (prop)(N,) int64 np.ndarrayClass indices
labels (prop)list[str] of length NClass names
orig_imgnp.ndarray HWC uint8 RGBOriginal image
orig_shape(H, W) tupleOriginal dimensions
namesdict[int, str]Class index → name
pathstr or NoneSource file path

Extended attributes

Populated when model emits them; otherwise None / empty:

AttributeTypeNotes
keypoints(N, K, 3) np.ndarrayPose models
masksMasks.xy (polygons), .xyn (normalized)
track_ids(N,) int np.ndarrayWhen track=True
embeddings(N, D) float32When model exports them (currently placeholder)
instance_depth(N,) float32 metersRGBD models
dense_depth(h, w) float32 metersRGBD models
segmentationlist of binary masksPer-detection seg
keypoint_lod(N,) intPer-detection level-of-detail
keypoint_mask(N, K) boolPer-keypoint visibility
skeleton_edgeslist[tuple[int, int]]Keypoint connectivity
behaviorslist[LYNXBehavior]When behavior=True
textstr or NoneOCR mode
text_blockslist or NoneOCR mode
obbOBBOriented bounding boxes — .xywhr (N, 5) and .xyxyxyxy (N, 4, 2). len(obb) == 0 for non-OBB models
probsProbsClassification probabilities. Iterate via .class_id_at(i) / .score_at(i) / .top_k(k) / .top1() — they work uniformly across both internal shapes. Direct .data indexing does NOT (position is the class ID in full-vector shape; position is the rank in top-K view shape). .data is None for detection-only models.

Methods

MethodReturns
len(results)Number of detections
repr(results)Results(detections=N, classes=[...])
results.update(boxes=, scores=, label_indices=, ...)Mutate in place
results.new()Empty copy with same class names
results.plot(color=, thickness=2, labels=True, conf=True, ...)np.ndarray annotated image
results.show()Display in window (blocking)
results.save(path)Save annotated PNG to disk
results.crop()list[np.ndarray] per-detection crops
results.save_crop(save_dir, save_conf=False, file_name=None)Write per-detection PNGs under <save_dir>/<label>/<file_name>_<i>.png (Ultralytics layout). Returns list of written paths.
results.save_txt(txt_file, save_conf=False, top_k=5)YOLO-format label file. With save_conf=True, appends a 6th confidence column per line. For classification results (empty detections + probs populated), writes the top-top_k class predictions instead (top_k ignored in detection mode — pre-filter with results.top_k(N) if you want fewer rows).
results.top_k(k)New Results limited to top-K entries. Detection: top-K detections (descending score), with keypoints/masks/track_ids/instance_depth reindexed. Classification: Results carrying a top-K Probs view — probs.data is (K,) float32 of top scores, probs.indices is (K,) int64 of the original class IDs; iterate via probs.class_id_at(i) / probs.score_at(i) or unpack with probs.top_k(k). Always returns Results, so callers can pass the result to save_txt() / to_json() generically.
results.save_flagged(output_dir)Save low-confidence flagged detections (with active_learning=True)
results.submit_feedback(expected=None, key=None, timeout_ms=15000)One-liner: POST this result's source image + optional 'expected' note to Synetic. Wraps lynx.submit_feedback(). See the Developer feedback section.
results.to_dict()Dict form for JSON serialization
results.to_csv(path=None)CSV string or write to path
results.to_df()pandas DataFrame
results.to_json(path=None)JSON string or write to path
results.track_history(track_id, n=30)Last n frames for a track (when behavior=True)
results.cpu() / .cuda() / .to(device) / .numpy()No-ops, return self. Preserved for code-portability with Torch results

CLASSStreamManager

Multi-stream orchestration: one detection model, N camera/video sources.

1from lynx import LYNX, StreamManager
2
3model = LYNX("lynx-basic")
4mgr = StreamManager(model)
5mgr.add_stream("camera-1", "rtsp://...")
6mgr.add_stream("camera-2", "/path/to/video.mp4")
7mgr.start()
8# ... callbacks fire from model.on_detect() etc.
9mgr.stop()
MethodNotes
StreamManager(model)model is LYNX instance or model name string
mgr.add_stream(name, source)Register a stream
mgr.start()Launch processing threads
mgr.stop()Stop all streams, join threads

FUNCTIONserve

def serve(model, host="0.0.0.0", port=8080)

Start an HTTP detection server. POST images to /detect, get JSON results.

1from lynx import LYNX, serve
2serve(LYNX("lynx-basic"), port=8080)
1POST /detect multipart/form-data with `image` field → JSON
2GET /healthz health check → {"status": "ok"}

Blocking; use a process manager (systemd, Docker, etc.) to run in production.


Exceptions

All LYNX exceptions inherit from LYNXError (which inherits from Exception).

1from lynx import LYNX, LYNXFormatError, LYNXAuthError
2
3try:
4 model = LYNX("lynx-basic", key="WRONG_KEY")
5except LYNXAuthError:
6 print("Invalid license key")
7except LYNXFormatError as e:
8 print(f"Model file corrupt or signature invalid: {e}")
ExceptionTriggered by
LYNXAuthErrorLicense key is wrong / missing for a paid model
LYNXModelNotFoundErrorModel name not found in registry; or file path doesn't exist
LYNXDownloadErrorNetwork failure / server returned non-200 during model download
LYNXFormatError.lnx magic wrong, cert signature failed, decrypt failed, payload format unsupported, master version unknown
LYNXLicenseErrorLicense expired (LicenseExpired) or cert from future (CertFromFuture)
LYNXIntegrityErrorWeight hash mismatch (tampered file)
LYNXPathSecurityErrorPath traversal blocked (../ in archive entries, symlink escapes)

The string form of the exception (str(e)) returns the spec §5 verdict name (e.g., "BadCertSignature", "LicenseExpired") — useful for programmatic error handling beyond exception class.


CLI

The lynx command (installed by the wheel) wraps the Python API:

1lynx version
2lynx info --model lynx-basic
3lynx detect --model lynx-basic --source image.jpg --output annotated.jpg
4lynx detect --model lynx-basic --source video.mp4 --output annotated.mp4
5lynx export --model lynx-basic --format onnx --output model.onnx
6lynx encode --model lynx-basic --source image.jpg --layer pooled
7lynx classify --model lynx-basic --source image.jpg --top-k 5
8lynx serve --model lynx-basic --port 8080

Optional dependency: opencv-python for video I/O (pip install golynx[cli]).

Programmatic CLI

1import lynx
2lynx.predict_cli(["detect", "--model", "lynx-basic", "--source", "image.jpg"])

Useful in notebooks and shell-out tests. Equivalent to running python -m lynx.cli detect --model lynx-basic --source image.jpg from the shell.


Autotune

When model(image) returns zero / poor detections, lynx.autotune sweeps the small cartesian product of (channel-order × rotation × resize) and reports which combination produces useful detections. One-shot diagnostic — runs ~16-24 inferences, not for predict loops.

1import lynx
2
3rv = lynx.autotune("frame.jpg", model)
4if rv["recommendation"]:
5 print(rv["hint"])
6 # "your image was BGR — pass channel_order='bgr' or swap before predict."
7else:
8 print(rv["hint"])
9 # "no variant produced any detections — submit_feedback() if it should."

Signature: lynx.autotune(image, model, *, min_detections=1, min_confidence=0.30, verbose=False)

ArgPurpose
imagepath / bytes / numpy / PIL — same as submit_feedback
modela LYNX instance (or anything model(image) → Results)
min_detectionsfloor before a variant is considered a winner (default 1)
min_confidencefloor on mean score before a variant is considered a winner (default 0.30)
verboseprint each variant's result as it runs

Returns: dict with:

  • recommendation: winning config {channel_order, rotation, resize, n_detections, mean_confidence} or None
  • tried: ranked list of every variant (sort: n_detections desc, mean_conf desc)
  • hint: one-sentence human-readable guidance

Sweep dimensions:

  • channel_order: rgb, bgr, gray_rgb (RGB inputs try all three; grayscale inputs try only gray_rgb)
  • rotation: 0, 90, 180, 270 (counter-clockwise via np.rot90)
  • resize: stretch (SDK default — independent X/Y scale to model.img_size) vs letterbox (aspect-preserve + zero-pad)

When the SDK ships metadata-driven preprocess (planned), channel-order and resize-mode become knowns from .lnx metadata, and autotune's primary role shifts to rotation + diagnostic backstop.


Developer feedback

One-liner to send Synetic an image plus an optional expectation note. Useful when the model surprised you and you want us to see the case.

1import lynx
2
3# From a file
4lynx.submit_feedback("frame.jpg", expected="should detect 2 dogs")
5
6# From a numpy array (auto-encoded to PNG)
7lynx.submit_feedback(arr, expected="missed the pedestrian on the left")
8
9# From a Results object — source image auto-attached
10results = model("frame.jpg")
11results.submit_feedback(expected="this is a deer, not a horse")

Signature: lynx.submit_feedback(image, expected=None, *, model=None, key=None, timeout_ms=15000, url=None)

ArgTypePurpose
imagestr / Path / bytes / np.ndarray / PIL.ImageSource frame. Numpy/PIL inputs are re-encoded to PNG by the C primitive so Synetic always receives a known-format byte stream.
expectedstr or NoneFree-text description of what you expected the model to return
modelstr or NoneOptional model slug for context (e.g. "lynx-basic")
keystr or NoneOptional API key; falls back to env LYNX_API_KEY then the saved key file. Anonymous submission is accepted.
timeout_msintHTTP timeout (default 15000)

Returns: dict with status (HTTP code), feedback_id (str or None), and body (parsed JSON or raw bytes).

Raises: ConnectionError on transport failure (DNS / TLS / unreachable); ValueError on HTTP 4xx (bad request, auth failure). HTTP 5xx returns the dict instead — caller decides whether to retry.

Endpoint: https://golynx.ai/api/feedback (override with url= for testing). Multipart/form-data POST via the libcurl-backed _core.http_post_multipart primitive.


Environment variables

VarPurpose
LYNX_API_KEYDefault license key (fallback when key=None to LYNX())
LYNX_INFERENCE_LOGSet to 1 to stderr-log the active execution provider chain (CoreML/CUDA/DirectML/CPU) on session build. Useful for verifying GPU acceleration is active
LYNX_PROVIDERSOverride execution provider chain: comma-separated names like cuda,cpu or cpu. Useful for debugging

Performance notes

  • All per-frame compute is in C (post-2026-05 C-first migration). Python is type marshalling at the FFI boundary.
  • Inference backend is ONNX Runtime under the hood, with platform-appropriate execution providers (CoreML on macOS, DirectML on Windows, TensorRT/CUDA on Linux GPU wheel, CPU fallback everywhere).
  • First-call latency includes ORT session build + provider load + dlopen of CUDA/CoreML libraries. Use model.warmup() before timing.
  • Model load is cached process-wide; loading the same .lnx twice in one process reuses the session.

Versioning

  • lynx.__version__ is the SDK version (currently "0.1.0").
  • Distribution: pip install golynx==<version> pins the wheel.
  • Models are versioned independently — pass version="ep207" to LYNX() to pin a specific model release, or version="latest" (default) to auto-update.

See also