Recipe — Python image classification
For LYNX SDK 1.0. Complete, end-to-end. Install:
install/python.md. API:api/python.md. Conventions:api/conventions.md.
Goal: run a whole-image classification head — "what is this image?" (one top-1 label + score for the frame), not "where are the objects?".
Read this first — capability honesty. Classification is a real head in the SDK (
lynx.Task.CLASSIFICATION, read back throughframe.classifications), but neither catalog model has one.lynx-basicdeclares detection, segmentation, pose, and depth;lynx-ocr-fleetdeclares detection only (seemodels/catalog.md). So on today's keyless modelsframe.classificationsis empty — there is no "every model has a classifier" fallback. The code below always checksmodel.capabilitiesfirst and tells you the truth, then shows the exact same code working against a classifier model you'd supply by slug.
Check the capability first
model.capabilities is a lynx.Task bitmask of the heads the model actually declares. Test for the classification head with in before you trust frame.classifications.
1import lynx2 3with lynx.open("lynx-basic") as model:4 has_cls = lynx.Task.CLASSIFICATION in model.capabilities5 print("capabilities:", model.capabilities) # e.g. Task.BOX|SEGMENTATION|POSE|DEPTH6 print("has a classification head:", has_cls) # False for lynx-basicFor lynx-basic this prints False — it is a detector, so classification is genuinely unavailable. Don't pretend otherwise; branch on has_cls.
The whole thing
lynx.open and model.predict are synchronous and raise on failure; the first open downloads + verifies + caches the model, so do it once and reuse the Model. predict takes an image path directly (or a (H, W, 3) uint8 RGB numpy array). Narrow the run to just the classification head with tasks=lynx.Task.CLASSIFICATION. The class name is resolved through model.classes — there's no Classification.class_name.
1import lynx2from lynx.errors import LynxError, ModelNotFound3 4def classify(slug, image_path):5 """Open a model (keyless), run the classification head, print top-1 label + score.6 7 Honest about capability: if the model has no classification head (lynx-basic8 does not), say so instead of faking a label.9 """10 with lynx.open(slug) as model:11 if lynx.Task.CLASSIFICATION not in model.capabilities:12 print(f"{slug!r} has no classification head "13 f"(capabilities: {model.capabilities}). It is not a classifier.")14 return15 16 # tasks= narrows the run to just the classification head.17 frame = model.predict(image_path, tasks=lynx.Task.CLASSIFICATION)18 19 # frame.classifications is the whole-image top-1 collection: len 0 or 1.20 if len(frame.classifications) == 0:21 print(f"No classification produced for {image_path!r}.")22 return23 24 top = frame.classifications[0] # a lynx.Classification25 name = model.classes(top.class_id).name # e.g. "GOLDEN_RETRIEVER"26 print(f"{name} {top.confidence:.2%}")27 28 29if __name__ == "__main__":30 import sys31 slug = sys.argv[1] if len(sys.argv) > 1 else "lynx-basic"32 image_path = sys.argv[2] if len(sys.argv) > 2 else "photo.jpg"33 try:34 classify(slug, image_path)35 except ModelNotFound as e:36 print("model not found (bad slug/version):", e.message)37 except LynxError as e:38 print(f"lynx error [{e.code}]: {e.message}")Run it against lynx-basic and it honestly reports "not a classifier"; run it against a classifier model's slug and the same code prints the label + score.
Reading the result
frame.classifications is a small collection over the C surface's whole-image top-1:
1cls = frame.classifications2 3len(cls) # 0 when the model emitted no classification, else 14top = cls[0] # lynx.Classification — raises IndexError if len == 05top.class_id # int class id, resolve the name via model.classes(top.class_id).name6top.confidence # float in [0, 1]7 8# vectorized columns (parallel to the collection), for uniform handling:9cls.class_id # (N,) intp — N is 0 or 110cls.confidence # (N,) float32There is no top_k / top5 and no probs array — the surface exposes top-1 only (the collection shape is forward-compat for a future top-k, but today len is 0 or 1). Resolve the human-readable label with model.classes(top.class_id).name, the same classes enum detection uses.
Passing pixels instead of a path
If the image is already decoded (Pillow, a camera, lynx.camera_open), hand predict the array directly — C-contiguous (H, W, 3) uint8 RGB:
1import numpy as np2from PIL import Image # pip install pillow3 4rgb = np.asarray(Image.open("photo.jpg").convert("RGB"))5frame = model.predict(rgb, tasks=lynx.Task.CLASSIFICATION)Notes
- No universal classifier. Only models that declare
lynx.Task.CLASSIFICATIONinmodel.capabilitiesreturn classifications; the catalog'slynx-basicandlynx-ocr-fleetdo not. If you need whole-image categories, train/supply a classification model and load it by slug — seemodels/no-model.md. The oldclassify()/results.probs.top1()API is fiction; usemodel.predict(..., tasks=lynx.Task.CLASSIFICATION)→frame.classifications. - Detection ≠ classification. To find where objects are, use the box head (every model has it) — see
recipes/python-detection.md. Classification answers what is this whole image. top.confidenceis afloatin[0, 1]; gate on it (if top.confidence < 0.6: mark_uncertain()).- One model, many heads: a classifier-plus-detector model can run both — open it and pass
tasks=lynx.Task.BOX | lynx.Task.CLASSIFICATION(or omittasksto run every head it declares), then read bothframe.detectionsandframe.classifications. - Reuse one loaded
Modelacross calls; don't reopen per image. Run the firstopenoff the UI thread (it downloads).