LYNX Python API Reference
Install
1pip install golynx # default — GPU on Linux, CoreML on macOS,2 # DirectML on Windows; no setup required3pip install golynx-cpu # Linux opt-in lean wheel, CPU-only ORTThe Python import path is always import lynx, regardless of which
distribution was installed (same decoupling as scikit-learn →
sklearn).
Quickstart
1from lynx import LYNX2 3# Load model — name (downloads from golynx.ai) or local path4model = LYNX("lynx-basic")5# model = LYNX("/path/to/model.lnx")6 7# Run detection8results = model("image.jpg")9 10# Access detections11for box, score, label in zip(results.boxes.xyxy, results.scores, results.labels):12 x1, y1, x2, y2 = box13 print(f"{label}: {score:.2f} at ({x1:.0f}, {y1:.0f}, {x2:.0f}, {y2:.0f})")14 15# Visualize / save16results.show()17results.save("annotated.jpg")Top-level imports
lynx.__all__ exports:
| Name | Kind | Purpose |
|---|---|---|
LYNX | class | Main detector |
Results | class | Detection results from one frame |
OBB | class | Oriented bounding-box accessor (carried on Results.obb) |
Probs | class | Classification probabilities accessor (carried on Results.probs) |
StreamManager | class | Multi-stream orchestration |
serve | function | HTTP detection server |
predict_cli | function | Run the lynx CLI programmatically (e.g. from notebooks) |
submit_feedback | function | Send Synetic an image plus an optional 'what I expected' note (developer feedback path) |
autotune | function | Probe channel-order × rotation × resize on an image + model to find a config that produces detections (onboarding diagnostic) |
LYNXError | exception | Base class for all LYNX errors |
LYNXAuthError | exception | Auth / license key issues |
LYNXModelNotFoundError | exception | Model slug / file missing |
LYNXDownloadError | exception | Network failure during model download |
LYNXFormatError | exception | .lnx file malformed or signature invalid |
LYNXLicenseError | exception | License expired or device unbound |
LYNXIntegrityError | exception | Weight hash mismatch (tamper detection) |
LYNXPathSecurityError | exception | Path 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 path3 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 threshold7 track=False, # enable persistent track IDs8 active_learning=False, # flag low-confidence for review9 al_conf_threshold=0.4,10 max_latency_ms=None, # warn if inference exceeds budget11 deterministic=False,12 camera=None, # camera preset: "realsense-d435" etc.13 output_format="numpy", # no-op; back-compat only14 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 fromgolynx.ai/api/models/<name> - Path with
.lnxextension → 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).
| Param | Type | Default | Notes |
|---|---|---|---|
source | str / Path / np.ndarray / PIL.Image / list | required | File path, directory, image array, or list of any |
depth | np.ndarray / str / None | None | Depth map for RGBD models |
conf | float / dict / None | self.conf | Per-call override |
iou | float / None | self.iou | NMS IoU override |
max_det | int | 300 | Cap detection count |
mode | "rgb" / "thermal" / "rgbd" / "ocr" / None | auto | Auto-detects thermal from uint16 arrays |
ocr_depth | int 1-7 | 3 | OCR analysis depth (mode="ocr" only) |
invert | bool | True | Run on color-inverted too (mode="ocr") |
correct_perspective | bool | False | Flatten 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 →
Resultswith.probspopulated,.boxesempty. - Batch / list / directory →
list[Results].
Shape of a classification Results:
| Attribute | Value |
|---|---|
len(results) | 0 (detections list is always empty) |
results.boxes | Boxes 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.indices | None (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/Property | Purpose |
|---|---|
model.names | dict[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) | bool — True when the artifact carries lynx_emits_obb=true |
model.is_classifier (prop) | bool — True 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.
| Property | Type | Default (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] float | ImageNet | lynx_normalize_mean |
model.normalize_std | [r, g, b] float | ImageNet | lynx_normalize_std |
model.normalize_scale | float | 1/255 | lynx_normalize_scale |
model.letterbox_pad_value | int 0-255 | 114 (YOLO) | lynx_letterbox_pad_value |
model.default_conf_threshold | float / None | None | lynx_default_conf_threshold |
model.default_iou_threshold | float / None | None | lynx_default_iou_threshold |
model.per_class_conf | {class_name: float} | {} | lynx_per_class_conf |
model.output_heads | list[dict] / None | None | lynx_output_heads |
model.metadata | dict | — | full 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| Method | Fires 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
| Attribute | Type | Description |
|---|---|---|
boxes | Boxes | .xyxy, .xywh, .xyxyn, .xywhn accessors (all (N, 4) np arrays) |
scores | (N,) float32 np.ndarray | Confidence scores |
class_ids (prop) | (N,) int64 np.ndarray | Class indices |
labels (prop) | list[str] of length N | Class names |
orig_img | np.ndarray HWC uint8 RGB | Original image |
orig_shape | (H, W) tuple | Original dimensions |
names | dict[int, str] | Class index → name |
path | str or None | Source file path |
Extended attributes
Populated when model emits them; otherwise None / empty:
| Attribute | Type | Notes |
|---|---|---|
keypoints | (N, K, 3) np.ndarray | Pose models |
masks | Masks | .xy (polygons), .xyn (normalized) |
track_ids | (N,) int np.ndarray | When track=True |
embeddings | (N, D) float32 | When model exports them (currently placeholder) |
instance_depth | (N,) float32 meters | RGBD models |
dense_depth | (h, w) float32 meters | RGBD models |
segmentation | list of binary masks | Per-detection seg |
keypoint_lod | (N,) int | Per-detection level-of-detail |
keypoint_mask | (N, K) bool | Per-keypoint visibility |
skeleton_edges | list[tuple[int, int]] | Keypoint connectivity |
behaviors | list[LYNXBehavior] | When behavior=True |
text | str or None | OCR mode |
text_blocks | list or None | OCR mode |
obb | OBB | Oriented bounding boxes — .xywhr (N, 5) and .xyxyxyxy (N, 4, 2). len(obb) == 0 for non-OBB models |
probs | Probs | Classification 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
| Method | Returns |
|---|---|
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, StreamManager2 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()| Method | Notes |
|---|---|
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, serve2serve(LYNX("lynx-basic"), port=8080)1POST /detect multipart/form-data with `image` field → JSON2GET /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, LYNXAuthError2 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}")| Exception | Triggered by |
|---|---|
LYNXAuthError | License key is wrong / missing for a paid model |
LYNXModelNotFoundError | Model name not found in registry; or file path doesn't exist |
LYNXDownloadError | Network failure / server returned non-200 during model download |
LYNXFormatError | .lnx magic wrong, cert signature failed, decrypt failed, payload format unsupported, master version unknown |
LYNXLicenseError | License expired (LicenseExpired) or cert from future (CertFromFuture) |
LYNXIntegrityError | Weight hash mismatch (tampered file) |
LYNXPathSecurityError | Path 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 version2lynx info --model lynx-basic3lynx detect --model lynx-basic --source image.jpg --output annotated.jpg4lynx detect --model lynx-basic --source video.mp4 --output annotated.mp45lynx export --model lynx-basic --format onnx --output model.onnx6lynx encode --model lynx-basic --source image.jpg --layer pooled7lynx classify --model lynx-basic --source image.jpg --top-k 58lynx serve --model lynx-basic --port 8080Optional dependency: opencv-python for video I/O (pip install golynx[cli]).
Programmatic CLI
1import lynx2lynx.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 lynx2 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)
| Arg | Purpose |
|---|---|
image | path / bytes / numpy / PIL — same as submit_feedback |
model | a LYNX instance (or anything model(image) → Results) |
min_detections | floor before a variant is considered a winner (default 1) |
min_confidence | floor on mean score before a variant is considered a winner (default 0.30) |
verbose | print each variant's result as it runs |
Returns: dict with:
recommendation: winning config{channel_order, rotation, resize, n_detections, mean_confidence}orNonetried: 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 onlygray_rgb)rotation:0,90,180,270(counter-clockwise vianp.rot90)resize:stretch(SDK default — independent X/Y scale to model.img_size) vsletterbox(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 lynx2 3# From a file4lynx.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-attached10results = 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)
| Arg | Type | Purpose |
|---|---|---|
image | str / Path / bytes / np.ndarray / PIL.Image | Source frame. Numpy/PIL inputs are re-encoded to PNG by the C primitive so Synetic always receives a known-format byte stream. |
expected | str or None | Free-text description of what you expected the model to return |
model | str or None | Optional model slug for context (e.g. "lynx-basic") |
key | str or None | Optional API key; falls back to env LYNX_API_KEY then the saved key file. Anonymous submission is accepted. |
timeout_ms | int | HTTP 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
| Var | Purpose |
|---|---|
LYNX_API_KEY | Default license key (fallback when key=None to LYNX()) |
LYNX_INFERENCE_LOG | Set 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_PROVIDERS | Override 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
.lnxtwice 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"toLYNX()to pin a specific model release, orversion="latest"(default) to auto-update.
See also
- Quickstart in README
- Wheel matrix
- CI architecture
- Source:
lynx/model.py,lynx/results.py,lynx/streaming.py,lynx/server.py,lynx/errors.py