> For the complete documentation index, see [llms.txt](https://mapir.gitbook.io/chloros/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://mapir.gitbook.io/chloros/api-python-sdk.md).

# API : Python SDK

{% hint style="info" %}
**Looking for the complete API?** This page is a hands-on tutorial. Every public class, method, exact signature, and copy-pasteable example is in the [SDK Reference](/chloros/reference-cli-and-sdk/sdk-reference.md), which is optimized for AI assistants.

**Working with an AI assistant?** Paste this URL into the chat so it has the full, current Chloros 1.2.0 API:

`https://mapir.gitbook.io/chloros/reference/sdk-reference.md`

Every page of this manual is available as raw markdown at its lowercase slug + `.md`, and the whole manual is indexed at `https://mapir.gitbook.io/chloros/llms.txt`.
{% endhint %}

The **Chloros Python SDK** (`chloros-sdk` on PyPI) drives everything the desktop app can do from Python: batch image processing, live LATTICE camera and array control, DAQ light-sensor sessions, and saved-project automation. It is a thin layer over the same local backend the GUI and CLI use (HTTP on `127.0.0.1:5000`), so behavior is identical across all three surfaces.

## Install

Installation is two steps: the Chloros desktop package first (it provides the processing backend and hardware runtimes), then the Python package.

**Step 1 — Install Chloros.** Windows: run the desktop installer (default path `C:\Program Files\MAPIR\Chloros\`) from the [Download](/chloros/download.md) page. Linux: install the `.deb` package ([Linux Installation](/chloros/linux-and-edge-computing/linux-installation.md)).

**Step 2 — Install the SDK** (Python 3.7+):

```bash
pip install chloros-sdk
```

You may not even need pip: every installer ships a matching SDK wheel. The Windows installer auto-installs it into your system Python; the Linux `.deb` places it at `/usr/lib/chloros/sdk/` and prints the exact `pip install --user` command. PyPI is updated on release builds, so `pip install chloros-sdk` matches the latest stable release.

**Step 3 — Log in once per machine:**

```bash
chloros-cli login user@example.com 'YourPassword'
```

Credentials are cached in `~/.chloros/` (both platforms). On Windows you can equivalently sign in through the desktop app's User <img src="https://1328410239-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOlCbgXiWzMtaOKj49kpf%2Fuploads%2Fgit-blob-bd2b76bb89da1465de57b81b175d111d6d804ac3%2Ficon_user.JPG?alt=media" alt="" data-size="line"> tab. The SDK requires a paid Chloros+ plan — see [License requirement](#license-requirement) below.

| Requirement           | Details                                                                         |
| --------------------- | ------------------------------------------------------------------------------- |
| **Chloros installed** | Windows: desktop installer; Linux: `.deb` package (provides the backend binary) |
| **Python**            | 3.7 or higher (developed/tested on 3.10)                                        |
| **Operating system**  | Windows 10/11 64-bit, Ubuntu 22.04 LTS or newer, or NVIDIA Jetson (JetPack 6)   |
| **License**           | Active Chloros+ login, any paid tier (Copper or higher)                         |

## The 60-second win

One call creates a project, imports a folder, configures processing, and runs the pipeline — auto-starting the backend if one isn't already running:

```python
import chloros_sdk

results = chloros_sdk.process_folder(
    "C:/DroneImages/Flight001",
    indices=["NDVI", "NDRE", "GNDVI"],
)
```

(On Linux, use Linux paths: `/home/user/drone_images/flight001`. The SDK works identically on both platforms.)

Processing a LATTICE capture folder? Use the LATTICE-friendly wrapper — it applies the right defaults (no panel-target detection, standard debayer):

```python
results = chloros_sdk.process_lattice_capture(
    folder_path="C:/Captures/2026-05-13_Field",
    indices=["NDVI"],
)
```

## `ChlorosLocal` — full pipeline control

For anything beyond a one-liner, use `ChlorosLocal`. It spawns the backend on first use (`auto_start_backend=True`), creates and configures projects, monitors progress, and returns a post-run summary.

```python
ChlorosLocal(
    api_url="http://127.0.0.1:5000",   # backend URL (also: backend_url=)
    auto_start_backend=True,            # spawn backend if not running
    backend_exe=None,                   # override backend binary path
    timeout=30,                         # request timeout seconds
    backend_startup_timeout=60,         # backend boot timeout
    processing_timeout=14400,           # hard cap on process() (4 h)
    processing_stuck_timeout=1800,      # no-progress threshold (30 min)
)
```

{% hint style="info" %}
Keep the default `http://127.0.0.1:5000` rather than substituting `localhost` — on Windows, `localhost` resolves to `::1` first and costs \~2 seconds per request against the IPv4-only backend.
{% endhint %}

Use it as a context manager for guaranteed cleanup:

```python
import chloros_sdk

with chloros_sdk.ChlorosLocal() as cl:
    cl.create_project("FieldA_2026-05-26", camera="Survey3N_RGN")
    cl.import_images("C:/DroneImages/Flight001")
    cl.configure(
        vignette_correction=True,
        reflectance_calibration=True,
        indices=["NDVI", "NDRE", "GNDVI"],
        export_format="TIFF (16-bit)",
    )
    results = cl.process(mode="parallel", wait=True)
print(results["summary"])
```

`configure()` accepts these keywords: `debayer`, `vignette_correction`, `reflectance_calibration`, `indices`, `export_format`, `ppk`, `daq_log_path`, `input_level`, `radiometric_output`, `array_alignment`, `array_alignment_crop`, `array_alignment_interpolation`, and `custom_settings`. The main values:

```python
# export_format
"TIFF (16-bit)"           # default, recommended
"TIFF (32-bit, Percent)"  # reflectance percentage as float32
"PNG (8-bit)"
"JPG (8-bit)"

# debayer
"High Quality (Faster)"                  # standard, default
"Texture Aware (Slow, Highest Quality)"  # neural debayer, Chloros+ only
```

The LATTICE-specific knobs (`input_level`, `radiometric_output`, the `array_alignment*` family) are documented with their full value tables in the [SDK Reference](/chloros/reference-cli-and-sdk/sdk-reference.md#supported-values).

### Watching progress

```python
def show_progress(percent, message):
    print(f"[{percent:3d}%] {message}")

with chloros_sdk.ChlorosLocal() as cl:
    cl.create_project("FieldA")
    cl.import_images("C:/DroneImages/Flight001")
    cl.configure(indices=["NDVI"])
    cl.process(progress_callback=show_progress, poll_interval=1.0)
```

### Reading the post-run summary — and catching empty runs

On completion, `process()` attaches the backend's processing summary as `result["summary"]`. Each entry in `summary["hints"]` is a full sentence explaining anything notable — for example, why a run produced zero output — and every hint is also re-emitted as a Python `UserWarning`, so empty runs are self-diagnosing even if you never inspect the dict:

```python
result = cl.process()
for hint in result.get("summary", {}).get("hints", []):
    print("HINT:", hint)
# hints also arrive on the warnings channel:
#   python -W always::UserWarning your_script.py
```

{% hint style="warning" %}
**`process()` does not raise when a run produces no images.** This is the one place the SDK and the CLI deliberately differ: `chloros-cli process` treats "products were requested, none were written" as a failure and exits non-zero, whereas the SDK returns normally and reports the condition through `summary` / hints. If your pipeline should stop on an empty run, check for it yourself — inspect `summary` (or count the files under the project folder) rather than relying on an exception.
{% endhint %}

## Smart Connect — live hardware

Three helpers open persistent sessions in the backend's hardware pool — the same pool the GUI uses, so SDK scripts co-exist with the desktop app without fighting over serial ports or network bandwidth. All three auto-start a local backend if none is running.

### Single LATTICE camera — `connect_camera`

```python
import chloros_sdk

# Open by serial; reuses existing pool entry if one exists
with chloros_sdk.connect_camera("213800234") as cam:
    cam.set_settings(exposure_time=10000, gain=0.0)   # microseconds, dB
    cam.capture("output/")
```

### Synchronized array — `connect_array`

`connect_array` is the recommended entry point for multi-camera rigs. It runs the same smart-prep flow as the GUI: network analysis, sync-tier auto-pick, PTP time-sync, per-camera pixel-format selection, AE seeding, and GPIO trigger arming. The **first serial is the master** (it fires the hardware trigger pulse); the rest are slaves.

```python
with chloros_sdk.connect_array(
        ["213800234", "214000533", "214701288", "214701292"]) as arr:
    arr.capture("output/", processing="reflectance")
```

Add `smart=True` to any array capture to wait for auto-exposure to settle across all cameras before triggering. For capture modes (Single / Continuous / Interval / Fastest), recorders, burst-to-video, and array alignment, see the [SDK Reference](/chloros/reference-cli-and-sdk/sdk-reference.md#synchronized-array--arraysession-smart-prep).

### DAQ light sensor — `connect_daq_sensor`

With no arguments, `connect_daq_sensor()` smart-detects the transport (precedence: Ethernet → BLE → USB):

```python
with chloros_sdk.connect_daq_sensor() as daq:    # smart-detect USB / BLE / ETH
    for frame in daq.latest(n=5):
        print(frame["spectrum"][:10])
```

Each frame carries the 135-point `spectrum` (W/m²/nm when calibrated), an `is_saturated` flag, and CIE `x`, `y`, `z`. To pin a specific sensor or transport — the reliable choice on hosts with multiple network interfaces, where Ethernet auto-discovery can miss a healthy DAQ-E on the first attempt — pass one explicit hint:

```python
daq = chloros_sdk.connect_daq_sensor(transport="usb", port="COM3")
daq = chloros_sdk.connect_daq_sensor(mac="AA:BB:CC:DD:EE:FF")        # implies BLE
daq = chloros_sdk.connect_daq_sensor(eth_host="daq-e-xxx.local")     # implies Ethernet
```

Note that cap-correction profiles (`cap_id`) are **not** an SDK knob — select them via `chloros-cli daq pool-connect --cap-id …` / `pool-set-cap` instead.

### Saved projects — `open_project`

A saved Chloros project persists its connected hardware (`cameras.json` + `sensors.json` alongside `project.json`), and `chloros_sdk.open_project(path)` can reconnect everything at once and drive captures by device name. See [Project Automation](/chloros/reference-cli-and-sdk/sdk-reference.md#project-automation--chlorosproject) in the reference.

## What a pip-only install gets

Check the module-level availability flags before using hardware surfaces:

```python
import chloros_sdk
print(chloros_sdk.__version__)
print("CAMERA_AVAILABLE =", chloros_sdk.CAMERA_AVAILABLE)    # True iff lattice_sdk imported cleanly
print("DAQ_AVAILABLE    =", chloros_sdk.DAQ_AVAILABLE)       # True iff daq_sdk imported cleanly
print("PROJECT_AVAILABLE =", chloros_sdk.PROJECT_AVAILABLE)  # True iff ChlorosProject deps available
```

On a host with **only** `pip install chloros-sdk` and no Chloros desktop package:

* `ChlorosLocal`, `process_folder`, and `process_lattice_capture` do **not** work — they need the backend binary that ships inside the desktop installer.
* The smart-connect helpers (`connect_camera`, `connect_array`, `connect_daq_sensor`) are pure HTTP clients, so they work against a backend on another machine — but shipped backends bind loopback only, so you must forward the port yourself (e.g. `ssh -N -L 5000:127.0.0.1:5000 user@chloros-host`) and pass `backend_url="http://127.0.0.1:5000"` with `auto_start_backend=False`. See [Remote-Backend Mode](/chloros/reference-cli-and-sdk/sdk-reference.md#remote-backend-mode-pip-only-host-via-tunnel).
* The direct-hardware LATTICE classes (`LatticeCamera`, `CameraPool`, …) import, but need the Arena SDK runtime from the desktop package — without it `CAMERA_AVAILABLE` is `False`.
* `daq_sdk` (the direct DAQ classes) ships with the desktop install, not the PyPI package, so `DAQ_AVAILABLE` is `False` on a pip-only host — drive DAQ sensors through `connect_daq_sensor()` against a (tunneled) backend instead.

## License requirement

SDK access requires an active Chloros+ login on any paid tier — **Copper or higher** (Copper / Bronze / Silver / Gold); the free Iron tier has no SDK/CLI access. Enforcement is **server-side**: every SDK request must carry both a live session and a paid plan, or the backend returns `403` / `PLAN_UPGRADE_REQUIRED` (raised as `ChlorosLicenseError` by `ChlorosLocal`, and as `ChlorosConnectError` by the `connect_*` helpers). A logged-out caller gets `401` / `AUTH_REQUIRED` (`ChlorosAuthenticationError`) instead — re-running `chloros-cli login` fixes the first case but not the second.

Offline use works within the plan's grace period: the tier is read from the server-validation cache (5 minutes) or the signed, machine-bound license cache (30 days for monthly plans; to subscription expiry for yearly). When the grace lapses, the plan resolves to free and SDK access stops until the machine reaches the server once. `chloros-cli status` stays reachable on the free tier so the reason is always visible. See [Chloros+ Login](/chloros/chloros+-login.md).

## Exceptions

Catch the base class to handle "anything Chloros went wrong":

```python
import chloros_sdk

try:
    chloros_sdk.process_folder("/path/to/folder")
except chloros_sdk.ChlorosAuthenticationError:
    print("Run `chloros-cli login` first.")
except chloros_sdk.ChlorosLicenseError:
    print("Chloros+ subscription required.")
except chloros_sdk.ChlorosError as e:
    print(f"Chloros error: {e}")
```

All pipeline exceptions (`ChlorosBackendError`, `ChlorosConnectionError`, `ChlorosLicenseError`, `ChlorosAuthenticationError`, `ChlorosConfigurationError`, `ChlorosProcessingError`) derive from `ChlorosError`. One gotcha: `ChlorosConnectError` — raised only by `connect_camera` / `connect_array` / `connect_daq_sensor` — derives from plain `Exception`, **not** from `ChlorosError`, so `except ChlorosError` will not catch it. The full hierarchy is in the [SDK Reference](/chloros/reference-cli-and-sdk/sdk-reference.md#exceptions).

## See also

* [SDK Reference](/chloros/reference-cli-and-sdk/sdk-reference.md) — the complete API surface, optimized for AI assistants.
* [CLI Reference](/chloros/reference-cli-and-sdk/cli-reference.md) — every CLI subcommand mirrors an SDK call.
* [Download](/chloros/download.md) — installers for Windows and Linux.
