Metadata-Version: 2.5
Name: cognix-sdk
Version: 0.2.0
Summary: Package your model as a CogniX bundle and check it on your machine before you publish it
Author: Carlos Prados
License-Expression: LicenseRef-Proprietary
License-File: LICENSE
License-File: THIRD-PARTY-NOTICES.md
Keywords: inference,iot,mlops,model-packaging,onnx,starlark
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Science/Research
Classifier: License :: Other/Proprietary License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.11
Requires-Dist: numpy>=1.26.0
Provides-Extra: runtime
Requires-Dist: onnxruntime<1.24,>=1.23; extra == 'runtime'
Provides-Extra: sklearn
Requires-Dist: onnx>=1.16.0; extra == 'sklearn'
Requires-Dist: onnxruntime<1.24,>=1.23; extra == 'sklearn'
Requires-Dist: scikit-learn>=1.5.0; extra == 'sklearn'
Requires-Dist: skl2onnx>=1.17.0; extra == 'sklearn'
Description-Content-Type: text/markdown

# cognix-sdk

Package your own model as a **CogniX bundle** and check it on your machine before you publish
it.

```bash
pip install "cognix-sdk[sklearn]"     # or: uv add "cognix-sdk[sklearn]"
```

You install `cognix-sdk` and import `cgx`. The `sklearn` extra brings scikit-learn and
skl2onnx to export a model, and onnxruntime so that `check()` can run it. If you bring an
`.onnx` exported with anything else (PyTorch, TensorFlow…), `cognix-sdk[runtime]` is enough.

## The problem it solves

You train the model, with whatever you like. What has to be right is the **artifact**, and
there is a trap in it that raises no error:

> the feature extractor is written **twice** — your `featurize()` in Python, used to train,
> and `features.star` (Starlark), which the engine evaluates in production. When they drift
> apart, the model keeps answering — only about **a different input**.

No exception, no trace, no metric going down. This package exists so that it fails on your
laptop instead.

## Quickstart

Two runnable examples ship with the source distribution, and neither needs data of your own
or a network connection:

| example | what it builds |
|---|---|
| `examples/quickstart.py` | a ClassiX classifier (scikit-learn on iris), with calibration, decision, explanation and the OOD gate |
| `examples/desviantix_autoencoder.py` | a DesviantiX anomaly detector: an autoencoder, with its threshold calibrated on held-out normal data |

The core of the first one:

```python
import cgx
from cgx import starlark
from sklearn.datasets import load_iris
from sklearn.linear_model import LogisticRegression
from sklearn.preprocessing import StandardScaler

iris = load_iris()
schema = ["sepal_length", "sepal_width", "petal_length", "petal_width"]
X, y = iris.data, iris.target_names[iris.target]

scaler = StandardScaler().fit(X)
clf = LogisticRegression(max_iter=1000).fit(scaler.transform(X), y)
onnx = cgx.to_onnx(clf, n_features=len(schema), out_path="dist/model.onnx")

bundle = cgx.Bundle(
    domain="iris_byo",
    feature="classix",
    schema=schema,
    featurizer=starlark.identity_featurizer(schema),       # your Python extractor…
    starlark=starlark.identity("iris_byo", schema),        # …and the one production runs
    onnx_path=onnx,
    output_names=cgx.onnx.scores_output(onnx),              # the scores tensor, not `label`
    output_kind="probabilities",                            # skl2onnx already normalizes them
    payloads=[dict(zip(schema, map(float, r))) for r in X[:8]],
    labels=[str(c) for c in clf.classes_],
    mean=scaler.mean_.tolist(),
    std=scaler.scale_.tolist(),
    extra_manifest={                                        # ClassiX's task block
        "calibration": {"method": "temperature", "temperature": 1.0},
        "decision": {"mode": "argmax", "top_k": 1},
        "explanation": {"method": "global",
                        "global_importance": abs(clf.coef_).mean(axis=0).tolist()},
        "ood": cgx.ood.mahalanobis(scaler.transform(X)),
    },
)

bundle.check("dist/iris_byo")    # writes the bundle and verifies it; raises BundleError if not
```

Then serve it with the `cognix` binary and ask it:

```bash
cognix serve --bundles dist --addr 127.0.0.1:8090
curl -s -X POST http://127.0.0.1:8090/classix/d/iris_byo/infer \
     -H 'Content-Type: application/json' \
     -d '{"sepal_length": 6.3, "sepal_width": 3.4, "petal_length": 5.6, "petal_width": 2.4}'
```

## What `check()` verifies

In this order, and it stops at the first failure with the fix in the message:

1. **Your featurizer, in Python**: a vector of the schema's length, made of numbers, for every
   payload.
2. **The model, with onnxruntime**: it runs on each payload **one row at a time**, the way the
   engine feeds it, and answers with the shape the feature reads. A graph that passes
   everything else and would answer 500 on every inference fails here.
3. **With the `cgx` binary**, which ships inside this package: the manifest against the
   feature's own contract (`bundle inspect`), and `verify-parity` — your Python featurizer
   against the real Starlark extractor.

Step 3 is done by the **binary**, not by a Starlark re-implementation in Python: a second
interpreter could differ from the production engine, which is the very problem this checks
for. If the binary is missing, `check()` says so and does **not** report an OK.

What it cannot verify for a bundle built outside a CogniX trainer is `verify`, which replays a
recorded result (class or value, probabilities, OOD gate) through the whole pipeline: that
recording is written by each feature's own trainer. `check()` says what is left uncovered. The
bundle serves just the same.

## The five features

`feature=` picks the engine that will serve the bundle. The SDK writes the `model` block the
way each feature reads it; each feature's **task block** goes in `extra_manifest=`, and the
engine's own validation (step 3) names any field that is missing.

| feature | what it answers | model | task block (`extra_manifest`) |
|---|---|---|---|
| `classix` | class + calibrated probabilities + OOD + attribution | one scores tensor; `output_kind` `logits` or `probabilities` | `calibration`, `decision`, `explanation`, `ood` |
| `regrex` | value + prediction interval + extrapolation flag + attribution | one output | `prediction`, `interval`, `explanation`, `extrapolation` |
| `clusterix` | cluster + membership + OOD + attribution | **two** outputs: label and per-cluster scores (`output_kind="distances"`) | `clusters`, `membership`, `decision`, `explanation`, `ood` |
| `desviantix` | `normal` / `novelty` / `anomaly` + explanation | one output: the reconstruction (or a direct score); `mean`/`std` **required** | `scoring`, `classification` |
| `decidix` | action + uncertainty + safety fallback | none for `linucb` and `mlp_actor` (`onnx_path=None`) | `policy`, `ood` |

`cgx.ood.mahalanobis(X)` computes the `ood` (or `extrapolation`) block from your training
features, normalized the same way the model sees them.

## If your featurizer is not the identity

`starlark.identity()` covers the case "each feature is a numeric field of the payload". For
anything else — a ratio of two fields, a one-hot, a saturation — write `features.star` by hand
and pass it in `starlark=`. When `featurize()` derives features from other fields, name the
keys a caller actually sends in `payload_fields=`.

**There is no automatic Python-to-Starlark translation, on purpose.** Translating an
arbitrary `featurize()` is the feature that charms and the one that can translate wrong
**silently** — exactly the class of failure this SDK exists to catch. `check()` works with any
extractor, and that is the guarantee that matters.

## Reference

| | |
|---|---|
| `Bundle(...)` | the artifact: `.manifest()`, `.payload_schema()`, `.save(dir)`, `.check(dir)` |
| `to_onnx(model, n_features)` | scikit-learn export with the opset and input name the engine expects, checked against the model after writing |
| `onnx.scores_output(path)` / `onnx.output_names(path)` | which graph outputs to declare |
| `ood.mahalanobis(X)` | the OOD gate block, from your normalized training features |
| `starlark.identity(domain, schema)` | the `features.star` of the identity case |
| `starlark.identity_featurizer(schema)` | its Python twin, to pass to `featurizer=` |

## What this package is not

It is **not a client for the CogniX REST API**: that is `cgxctl`. Its contract is the
**bundle format** (v3), which is versioned and changes on purpose.
