Metadata-Version: 2.4
Name: labeltify
Version: 0.4.0
Summary: Upload frames and optional annotations into LabelTify with a pipeline token.
Author: CleverTify
Project-URL: Homepage, https://labeltify.com
Project-URL: Source, https://github.com/CleverTify/LabelTify/tree/main/clients/python
Keywords: labeltify,annotation,computer-vision,deepstream,active-learning,dataset
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Topic :: Scientific/Engineering :: Image Recognition
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Provides-Extra: test
Requires-Dist: pytest>=8; extra == "test"
Provides-Extra: images
Requires-Dist: Pillow>=10; extra == "images"

# labeltify

Upload frames, and optionally their annotations, into [LabelTify](https://labeltify.com) from DeepStream, OpenCV, Ultralytics, or any Python pipeline. The only thing it needs is a pipeline token.

```sh
pip install labeltify
```

1. On the dataset **Upload** page, create a pipeline token.
2. Upload:

```python
from labeltify import LabelTifyClient

client = LabelTifyClient("labeltify_…")  # or set LABELTIFY_TOKEN and call LabelTifyClient()

img = client.upload_image(
    "DATASET_ID",
    jpeg_bytes,
    filename="cam01.jpg",
    camera="cam-01",
    pipeline="ds-prod",
    trigger="low_confidence",
)
client.upload_file("DATASET_ID", "frames/cam02.jpg", camera="cam-02")
```

The token knows its org, so you don't pass one. Timeouts, `429`, and `5xx` responses are retried with exponential backoff (`max_retries=4` by default). Anything else raises `LabelTifyError` with the API's message and `.status`.

Annotations are optional. When you pass `labels` (COCO, LabelTify, or YOLO JSON bytes), `work_state` is required: `done`, `in_progress`, `unsure`, or `untouched`. There is no default; the client raises `LabelTifyError` before sending if it is missing. Images with no labels stay untouched.

### Upload a folder with its annotations

```python
report = client.upload_directory(
    "DATASET_ID",
    "datasets/parking",
    work_state="done",
)
print(len(report["items"]), "images stored in", report["requests"], "requests;", report["imported"])
```

This sends the folder the way dropping it on the dataset's **Upload** page would: images, plus COCO (polygons and boxes), YOLO (`labels/*.txt` with `data.yaml` or `classes.txt`), Pascal VOC, LabelMe, CSV, `metadata.jsonl`, a folder per class, or a LabelTify export. The server reads the annotations; the client only splits the folder into requests (at most 100 images and 80 MB each). A COCO file is cut down to each request's images, so polygons stay polygons.

- The dataset must already exist. Create it on the site. Parking polygons need type **Instance segmentation**. Traffic boxes need **Object detection**.
- `work_state="done"` marks every image that received a shape. Images with no shape stay untouched. With any label file in the folder, `work_state` is required.
- Create the pipeline token on that dataset's **Upload** page.
- One credit covers 10,000 uploaded images. A free dataset holds 100 photos.
- Frame embeddings follow the dataset setting. Turn them off on the dataset before a large upload when you do not want that charge.
- Use the portable pixel COCO files. The raw Studio shards store boxes as fractions from 0 to 1, and the server divides by width and height again.
- Two images with the same file name in different folders stop the upload before anything is sent; rename one.
- Images over 20 MiB, empty files, and files that are neither images nor annotations are listed in `report["files"]` and not sent. A zip is sent as it is when it holds at most 200 files and 20 MB; unzip anything bigger.
- If a request fails after its retries, the error says how many images were already stored; run the folder again and exact duplicates are reported instead of stored twice.
- `compute_dhash=True` also skips near-duplicate frames, like the Upload page. It needs Pillow: `pip install "labeltify[images]"`.

`upload_bundle(dataset_id, files, work_state=…)` sends one such request from bytes you already have (`files` is a list of `{"filename", "data", "content_type"}`).

A deployment behind an access proxy can take extra headers on every request: `LabelTifyClient(token, base_url=…, headers={"CF-Access-Client-Id": …, "CF-Access-Client-Secret": …})`.

Send model proposals to the Loop review queue:

```python
client.propose(
    "DATASET_ID",
    img["id"],
    [{"type": "bbox", "id": "p1", "classId": "car", "box": {"x": 0.1, "y": 0.1, "w": 0.2, "h": 0.2}, "source": "model", "confidence": 0.4}],
    model_ref="deepstream@1",
    uncertainty=0.8,
)
```

Boxes are normalized `[0,1]`. Camera, pipeline, and trigger are stored on the image so Loop can filter by them.

Local API: `LabelTifyClient("labeltify_…", base_url="http://localhost:8787")`.

## Develop

```sh
pip install -e "clients/python[test]"
pytest clients/python/tests
```

Bump `version` in `pyproject.toml` to publish. Each new version that lands on `main` is published to PyPI by `.github/workflows/publish-python.yml`.
