Metadata-Version: 2.5
Name: ovkit
Version: 0.4.0
Summary: A simple Python inference API for OpenVINO: one import, one Model class, clean Results — with AUTO/NPU, async, and INT8.
Project-URL: Homepage, https://github.com/leeyunjai82/ovkit
Project-URL: Documentation, https://leeyunjai82.github.io/ovkit/
Project-URL: Repository, https://github.com/leeyunjai82/ovkit
Project-URL: Issues, https://github.com/leeyunjai82/ovkit/issues
Project-URL: Models, https://huggingface.co/leeyunjai/ovkit-models
Author: ovkit contributors
License: Apache-2.0
License-File: LICENSE
Keywords: computer-vision,detection,inference,npu,openvino,quantization
Classifier: Development Status :: 3 - Alpha
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Requires-Python: >=3.10
Requires-Dist: huggingface-hub>=0.20
Requires-Dist: numpy>=1.21
Requires-Dist: opencv-python-headless>=4.5
Requires-Dist: openvino>=2024.0
Requires-Dist: pillow>=9.0
Requires-Dist: pyyaml>=6.0
Provides-Extra: all
Requires-Dist: anomalib; extra == 'all'
Requires-Dist: mediapipe; extra == 'all'
Requires-Dist: nncf; extra == 'all'
Requires-Dist: openvino-genai; extra == 'all'
Requires-Dist: optimum-intel[openvino]>=1.20; extra == 'all'
Requires-Dist: rtdetr[train]>=0.6; extra == 'all'
Requires-Dist: transformers>=4.40; extra == 'all'
Provides-Extra: anomaly
Requires-Dist: anomalib; extra == 'anomaly'
Provides-Extra: dev
Requires-Dist: black; extra == 'dev'
Requires-Dist: pytest>=7; extra == 'dev'
Requires-Dist: ruff; extra == 'dev'
Provides-Extra: genai
Requires-Dist: openvino-genai; extra == 'genai'
Requires-Dist: optimum-intel; extra == 'genai'
Provides-Extra: hand
Requires-Dist: mediapipe; extra == 'hand'
Provides-Extra: hf
Requires-Dist: optimum-intel[openvino]>=1.20; extra == 'hf'
Requires-Dist: transformers>=4.40; extra == 'hf'
Provides-Extra: quant
Requires-Dist: nncf; extra == 'quant'
Provides-Extra: train
Requires-Dist: rtdetr[train]>=0.6; extra == 'train'
Description-Content-Type: text/markdown

<div align="center">

<img src="https://raw.githubusercontent.com/leeyunjai82/ovkit/main/docs/_static/logo.svg" width="110" alt="ovkit logo"/>

# ovkit

**OpenVINO inference in one line.** One `Model` class, clean `Results`, and 19
composed capabilities over 68 ready models — with `AUTO`/`NPU`/`GPU` devices,
async throughput, INT8, and your own detector trainable in the same package.

[![CI](https://github.com/leeyunjai82/ovkit/actions/workflows/ci.yml/badge.svg)](https://github.com/leeyunjai82/ovkit/actions/workflows/ci.yml)
[![Docs](https://img.shields.io/badge/docs-github.io-0a7d8c)](https://leeyunjai82.github.io/ovkit/)
[![Python](https://img.shields.io/badge/python-3.10%2B-blue)](https://github.com/leeyunjai82/ovkit/blob/main/pyproject.toml)
[![License](https://img.shields.io/badge/license-Apache--2.0-green)](https://github.com/leeyunjai82/ovkit/blob/main/LICENSE)
[![PyPI](https://img.shields.io/pypi/v/ovkit)](https://pypi.org/project/ovkit/)
[![Models](https://img.shields.io/badge/🤗%20models-ovkit--models-yellow)](https://huggingface.co/leeyunjai/ovkit-models)

[Docs](https://leeyunjai82.github.io/ovkit/) ·
[한국어 문서](https://leeyunjai82.github.io/ovkit/ko/) ·
[Model catalog](https://leeyunjai82.github.io/ovkit/models.html) ·
[Examples](https://github.com/leeyunjai82/ovkit/tree/main/examples)

</div>

```python
from ovkit import Model

r = Model("detect", "image.jpg")      # download -> convert -> cache -> run
print(r)                              # 2x person, car
r.save("out.jpg")                     # the boxes drawn on the photo
```

One call fits every input — and Korean names work too:

```python
r = Model("얼굴분석", "group.jpg")      # == Model("face_analyze", ...)
for row in r.found:                    # [{'name': '사람', 'name_en': 'person',
    print(row["name"], row["pos"])     #   'score': 0.93, 'box': [...], 'pos': '왼쪽 위'}]

for r in Model("track", 0):            # webcam / video / "mic" -> a stream
    print(r, r.elapsed_ms, r.device)   # 2x person (#1, #4) 14.2 GPU
    if not r.show("track"):            # a window; q or Esc ends the loop
        break
```

The same call also runs whole **capabilities** — several models chained into one
answer, so you never have to crop, chain and stitch by hand:

```python
r = Model("face_analyze", "group.jpg")
print(r)                # 2 faces: age 31 · male 98% · happy 92%, age 27 · female 95% · neutral 88%
```

Or without writing Python at all:

```bash
ovkit gui                             # a window: pick a capability, point it at your webcam
ovkit run detect image.jpg            # prints results, saves image_out.jpg
ovkit capabilities                    # what Model(name) can answer
```

## Install

```bash
pip install ovkit
```

Extras: `ovkit[quant]` (INT8/NNCF) · `ovkit[genai]` (LLM/STT) · `ovkit[all]`.
Python 3.10+. For development, install from source:

```bash
git clone https://github.com/leeyunjai82/ovkit.git && cd ovkit
python -m venv .venv && source .venv/bin/activate   # Windows: .venv\Scripts\activate
pip install -e ".[dev]"

python scripts/check.py --fix    # what CI runs: ruff, black, pytest
```

## Capabilities

A capability name gives you the answer, not the plumbing. Each one chains a
detector with the models that describe what it found — same `Model(...)` call,
same `Results`, and it takes the same sources (path, ndarray, folder, video,
camera index).

| `Model(...)` | Answers | Chains |
| ------------ | ------- | ------ |
| `scene` | `2 people (1 happy) · a laptop and a cup · floor 47%` | detection + segmentation + faces |
| `face_analyze` | `2 faces: age 31 · male 98% · happy 92%, ...` | face detection + age/gender + emotion (+ head pose, landmarks) |
| `person_analyze` | `3 people: male 0.98 · long pants 0.95 · bag 0.71, ...` | person detection + attributes |
| `vehicle_analyze` | `2 vehicles: type: car (0.98) · color: black (0.83), ...` | vehicle detection + type/colour |
| `read_text` | `'STOP AHEAD'` — every word, in reading order | text detection + text recognition |
| `read_plate` | `2 vehicles: black car — 12GA3456, ...` | plate detection + text recognition + vehicle attributes |
| `track` | `2x person (#1, #4)` — ids stable across frames | detection + IoU association |
| `drowsiness` | `EYES CLOSED 1.4s — drowsy` | face + landmarks + eye state + head pose, **over time** |
| `gesture` | `thumb up 0.94` | sign-language model over a rolling 8-frame clip |
| `gaze` | `1 face: looking right and slightly up` | face detection + landmarks + head pose + gaze |
| `attention` | `1 person looking at: laptop` | gaze + object detection (ray-cast into the boxes) |
| `anonymize` | the picture with every face pixelated | face (and plate) detection + redaction |
| `face_match` | `('yunjai', 0.81)` — who this is | embedding + cosine matching against your gallery |
| `count` | `pencil 3 · cup 1` (optionally one kind only) | detection + per-kind tally |
| `posture` | `neck 32° — sit up (6s)` | pose + neck angle **over time** |
| `exercise` | `squat x 12 (down)` | pose + joint-angle hysteresis (squat, push-up) |
| `attendance` | `present 24/26` + `roll.csv` | face detection + roster matching |
| `teach` | your own classes, from example photos | embedding + cosine k-NN (5 modes) |
| `depth` | `nearest: bottom-left · 34% of the frame is close` + a colour map | Depth Anything V2 Small |
| `remove_background` | the subject, saved as a transparent PNG | U2-Net |

```python
from ovkit import Model, list_pipelines

list_pipelines()                                    # every capability, described
Model("read_text")("sign.jpg")[0].text              # 'STOP AHEAD'
Model("track")(0)                                   # webcam, ids kept across frames
Model("face_analyze", attributes=("age_gender",))   # configure what runs
```

Aliases: `ocr`, `anpr`, `blur`, `driver`, `describe`, `faces`, `people`, `vehicle`, `tracking`, `reid`.

## Teach your own AI (no GPU)

A Teachable Machine in five lines — an embedding model turns examples into
vectors, new inputs match the nearest ones. Five modes: `photo` (default),
`face` (expressions), `hand` (needs `ovkit[hand]`), `upper`, `body`:

```python
ai = Model("teach")                       # or Model("가르치기")
ai.learn("can", "photos/cans/")           # a folder per thing to recognise
ai.learn("bottle", "photos/bottles/")
print(ai.guess("new_photo.jpg"))          # ('can', 0.93)
print(ai.score("test_photos/"))           # accuracy + what it confuses
ai.save("recycling")                      # -> Documents/ovkit/recycling.json
for r in ai.predict(0, stream=True): ...  # webcam over YOUR classes
```

Korean method names work too: `배우기`, `맞혀봐`, `점수`, `저장`. Collect
examples with the webcam: `from ovkit.pipelines.teach import collect; collect("가위", 30)`.

## Any model on the Hugging Face Hub

The Open Model Zoo is an archive; the models people want are on the Hub.
`optimum-intel` converts almost any of them to OpenVINO IR, so ovkit does not
have to mirror a model to serve it:

```bash
pip install "ovkit[hf]"                      # the converter — needed once
ovkit pull google/vit-base-patch16-224
```

```python
Model("google/vit-base-patch16-224", "photo.jpg")   # tabby 0.94
```

Converting is the heavy half and happens once; what lands in the cache is a
plain OpenVINO model, so **running it afterwards needs nothing but ovkit** — a
classroom machine can be handed the cache and never see PyTorch. The pull also
writes the model's own class names and the normalisation its image processor
declares, so results read as answers and not as confident nonsense.

Tasks: image classification, zero-shot (CLIP) classification, feature
extraction. Detection and segmentation come from the registry instead
(`detect`, `segment`) — optimum-intel does not export those.

## Train your own detector

RT-DETR, trainable, Apache-2.0 end to end — an Ultralytics-style workflow with
no AGPL code or weights anywhere (`pip install "ovkit[train]"`):

```python
from ovkit import RTDETR

model = RTDETR("rtdetr-r18")                 # COCO weights, downloaded on first use
model.train(data="data.yaml", epochs=100)    # the YOLO-format labels you already have
model.val(data="data.yaml").box.map50        # mAP50 / mAP50-95
model.export(half=True)                      # -> IR + labels.txt
```

The three sizes are `rtdetr_r18` (20M, 46.4 COCO AP — what `detect` uses),
`rtdetr_r34` (31M, 48.9) and `rtdetr_r50` (43M, 53.1), mirrored like every
other model. Running them needs nothing but ovkit; `ovkit[train]` adds PyTorch
for fine-tuning:

```python
Model("detect", "street.jpg")      # r18 — keeps up with a webcam
Model("rtdetr_r50", "street.jpg")  # when you want the accuracy instead
```

A fine-tuned model's exported IR drops straight into `Model("path/to/best.xml")` — the
`labels.txt` written next to it means your classes answer by name. CLI:
`ovkit train --data data.yaml`, `ovkit val`, `ovkit export`.

## Supported tasks

Every single-model task below runs end-to-end through the same 3 lines — swap the alias:

| Task | Alias | Output | Example |
| ---- | ----- | ------ | ------- |
| Object detection | `detect`, `face_detection`, `person_detection`, `vehicle_detection`, `text_detection`, `license_plate` | `r.boxes` | [detect.py](https://github.com/leeyunjai82/ovkit/blob/main/examples/detect.py) |
| Classification | `classify`, `person_attributes`, `vehicle_attributes` | `r.probs` / text | [classify.py](https://github.com/leeyunjai82/ovkit/blob/main/examples/classify.py) |
| Segmentation | `segment`, `instance_segmentation` | `r.masks` | [segment.py](https://github.com/leeyunjai82/ovkit/blob/main/examples/segment.py) |
| Pose / landmarks | `pose`, `face_landmarks` | `r.keypoints` | [pose.py](https://github.com/leeyunjai82/ovkit/blob/main/examples/pose.py) |
| Face analysis | `age_gender`, `emotion`, `head_pose`, `face_reid` | `r.text` (e.g. `"age 31 · male 98%"`) | [face_analysis.py](https://github.com/leeyunjai82/ovkit/blob/main/examples/face_analysis.py) |
| OCR | `text_recognition` | `r.text` | [ocr.py](https://github.com/leeyunjai82/ovkit/blob/main/examples/ocr.py) |
| Tracking / matching | `track`, `face_match` | `r.track_ids` · `(label, score)` | [track.py](https://github.com/leeyunjai82/ovkit/blob/main/examples/track.py) / [face_match.py](https://github.com/leeyunjai82/ovkit/blob/main/examples/face_match.py) |
| Super-resolution | `super_resolution` | upscaled image via `r.plot()` | [super_resolution.py](https://github.com/leeyunjai82/ovkit/blob/main/examples/super_resolution.py) |
| LLM / STT (GenAI) | `llm`, `stt` | generated text | [llm.py](https://github.com/leeyunjai82/ovkit/blob/main/examples/llm.py) / [stt.py](https://github.com/leeyunjai82/ovkit/blob/main/examples/stt.py) |
| NLP / audio / time series | `qa`, `translation`, `noise_suppression`, `time_series` | tensors via `model.infer()` | [denoise_audio.py](https://github.com/leeyunjai82/ovkit/blob/main/examples/denoise_audio.py) |

The registry exposes **one well-tested model per capability (43 OpenVINO IR + 13 GenAI)**; the
[HF mirror](https://huggingface.co/leeyunjai/ovkit-models) hosts the full
Apache-2.0 OMZ set (other tiers, `int8`, `sparse` variants) — surfacing a
variant is a one-line edit ([catalog](https://leeyunjai82.github.io/ovkit/models.html)).
`ovkit list` shows everything with descriptions.

## Devices

| Device | How | Notes |
| ------ | --- | ----- |
| **AUTO** (default) | `Model("detect")` | OpenVINO picks the best device |
| **CPU** | `Model("detect", device="CPU")` | works everywhere |
| **GPU** | `device="GPU"` | Intel iGPU / Arc |
| **NPU** | `device="NPU"` | Intel® Core™ Ultra AI accelerator |

Single images run synchronously; `stream=True` uses an `AsyncInferQueue` for
video/webcam throughput. INT8: `model.quantize(calib_images)` (NNCF).

### Benchmarks

```bash
python scripts/benchmark.py        # prints a paste-ready CPU/GPU/NPU table
```

| model | CPU | GPU | NPU |
| --- | --- | --- | --- |
| `rtdetr_r50` | 429.4 ms (2 FPS) | 36.9 ms (27 FPS) | —* |
| `face_detection_0205` | 11.7 ms (85 FPS) | 4.5 ms (224 FPS) | —* |
| `person_detection_0202` | 13.4 ms (75 FPS) | 4.7 ms (211 FPS) | 6.6 ms (151 FPS) |
| `resnet50_binary_0001` | 7.9 ms (126 FPS) | 5.1 ms (195 FPS) | —* |
| `road_segmentation_adas_0001` | 28.6 ms (35 FPS) | 13.0 ms (77 FPS) | 23.8 ms (42 FPS) |
| `human_pose_estimation_0007` | 82.2 ms (12 FPS) | 14.0 ms (71 FPS) | 22.7 ms (44 FPS) |
| `age_gender_recognition_retail_0013` | 0.5 ms (1867 FPS) | 0.5 ms (2098 FPS) | 0.6 ms (1569 FPS) |

*Measured on an Intel® Core™ Ultra (Lunar Lake) laptop — CPU / integrated GPU /
NPU, median of 30 runs, 1280x720 input, OpenVINO 2026.3.*
`—` = model not supported by the NPU compiler (dynamic shapes or unsupported ops).

## Usage

<details open>
<summary><b>Python</b></summary>

```python
from ovkit import Model

model = Model("face_detection")              # alias, name, .xml, or .onnx
results = model("photo.jpg", conf=0.25)      # image / ndarray / folder / video
for r in model.predict(0, stream=True):      # webcam (lazy generator)
    annotated = r.plot()

print(Model("age_gender")("face.jpg")[0].text)   # "age 31 · male 98%"
```

Inputs are **auto-detected**: image path / `ndarray` / folder / video / camera
index → vision pipeline; `.npy` / `.wav` → raw inference. Grayscale models and
all-image multi-input models (super-resolution) are handled automatically. Full
control for any model: `model.infer({name: tensor})` with `model.inputs`.

| `Results` | holds |
| --------- | ----- |
| `r.boxes` | `xyxy`, `xywh`, `conf`, `cls` |
| `r.masks` / `r.keypoints` / `r.probs` | masks · `[x,y,conf]` · `top1`/`top5` |
| `r.text` | decoded text (OCR, face attributes) |
| `r.labels` / `r.track_ids` | per-box label a pipeline wrote · per-box track id |
| `r.tensors` | raw `{name: ndarray}` |
| `r.summary()` | the whole result as one readable line |
| `r.crop(i)` / `r.to_dict()` / `r.to_json()` | cut a box out · plain Python · JSON |
| `r.plot()` / `r.save(path)` | annotated image (or the model's output image) |

</details>

<details>
<summary><b>CLI</b></summary>

```bash
ovkit run detect image.jpg --save out.jpg   # one-shot inference
ovkit run age_gender face.jpg --device NPU
ovkit list                                  # aliases + models with descriptions
ovkit info face_detection                   # source / task / license
ovkit download detect                       # warm the cache
ovkit devices                               # available OpenVINO devices
```

</details>

<details>
<summary><b>GenAI (LLM / speech-to-text)</b></summary>

```python
from ovkit.genai import pipeline

llm = pipeline("llm")                        # qwen25_1_5b_instruct from the mirror
print(llm.generate("Explain OpenVINO in one sentence.", max_new_tokens=64))

stt = pipeline("stt")                        # whisper_base
print(stt.generate(audio_16k_mono_float32))
```

Needs `pip install "ovkit[genai]"`.

</details>

<details>
<summary><b>Web demo (image / webcam / audio / text)</b></summary>

```bash
pip install -r examples/requirements.txt
python examples/web_app.py                   # http://127.0.0.1:8000
```

Pick any model — the right input (upload / webcam / audio / text) appears
automatically and results render with overlays.

</details>

## The model mirror

Everything ovkit downloads comes from one repository,
[leeyunjai/ovkit-models](https://huggingface.co/leeyunjai/ovkit-models) — one
copy for an offline site to take, one repository to keep alive. Models that
originate elsewhere (the RT-DETR sizes, Depth Anything V2 Small, U2-Net) are
copied in, and their home stays registered as the fallback.

To refresh it after adding a model, run the **Sync the model mirror** workflow
from the Actions tab (dry run by default; tick *upload* to copy). It needs an
`HF_TOKEN` repository secret with write access, set once. The same thing runs
locally:

```bash
export HF_TOKEN=hf_...
python scripts/sync_mirror.py            # what is missing
python scripts/sync_mirror.py --upload   # copy just that
```

It lists the target first and copies only what is absent, so re-running is safe.

The traffic goes the other way too. A mirror only ever added to fills up with
models that left the lineup and weights uploaded under an old path — and it is
the thing a school clones whole:

```bash
python scripts/audit_mirror.py           # what no manifest references
python scripts/audit_mirror.py --prune   # delete just that
```

A model's `README.md`, `LICENSE` and `labels.txt` stay as long as the model
does, and go with it when it leaves. Deletions are ordinary Hub commits, so
the history still has them.

## Adding a model

Models are data, not code — one manifest entry
(`src/ovkit/manifests/`):

```yaml
my_model:
  src: hf
  repo: leeyunjai/ovkit-models
  filename: detect/my_model/model.xml
  task: detect
  description: Shown by `ovkit list`.
  license: apache-2.0            # must be permissive — enforced at load time
```

Resolution: alias → local path → cache (`~/.cache/ovkit`) → download → convert
→ cache, with atomic writes, `sha256` checks, upstream `fallback`, and
`OVKIT_OFFLINE=1`. Maintainer tooling (mirror build / verify / self-check /
benchmark) lives in [`scripts/`](https://github.com/leeyunjai82/ovkit/tree/main/scripts) — see the
[guide](https://leeyunjai82.github.io/ovkit/guide.html).

## License

ovkit is [Apache-2.0](https://github.com/leeyunjai82/ovkit/blob/main/LICENSE) and **license-clean by design**: only permissive
(Apache/MIT/BSD) models and libraries — no AGPL model stacks, no non-commercial
weights; every manifest entry must declare a permissive license (enforced at
load time).
