ovkit 0.1.2__tar.gz → 0.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. {ovkit-0.1.2 → ovkit-0.3.0}/PKG-INFO +121 -4
  2. {ovkit-0.1.2 → ovkit-0.3.0}/README.md +109 -3
  3. {ovkit-0.1.2 → ovkit-0.3.0}/examples/README.md +1 -0
  4. {ovkit-0.1.2 → ovkit-0.3.0}/pyproject.toml +4 -2
  5. {ovkit-0.1.2 → ovkit-0.3.0}/src/ovkit/__init__.py +26 -4
  6. {ovkit-0.1.2 → ovkit-0.3.0}/src/ovkit/__main__.py +81 -15
  7. ovkit-0.3.0/src/ovkit/audio/__init__.py +13 -0
  8. ovkit-0.3.0/src/ovkit/audio/ops.py +108 -0
  9. ovkit-0.3.0/src/ovkit/audio/plot.py +47 -0
  10. {ovkit-0.1.2 → ovkit-0.3.0}/src/ovkit/core/backend.py +46 -1
  11. ovkit-0.3.0/src/ovkit/core/constants.py +335 -0
  12. {ovkit-0.1.2 → ovkit-0.3.0}/src/ovkit/core/download.py +24 -1
  13. ovkit-0.3.0/src/ovkit/core/i18n.py +317 -0
  14. {ovkit-0.1.2 → ovkit-0.3.0}/src/ovkit/core/model.py +205 -19
  15. {ovkit-0.1.2 → ovkit-0.3.0}/src/ovkit/core/registry.py +21 -1
  16. ovkit-0.3.0/src/ovkit/core/results.py +598 -0
  17. ovkit-0.3.0/src/ovkit/data/imagenet1000.txt +1000 -0
  18. ovkit-0.3.0/src/ovkit/face/__init__.py +8 -0
  19. ovkit-0.3.0/src/ovkit/face/analyzer.py +7 -0
  20. ovkit-0.3.0/src/ovkit/genai/__init__.py +40 -0
  21. {ovkit-0.1.2 → ovkit-0.3.0}/src/ovkit/genai/pipelines.py +105 -1
  22. ovkit-0.3.0/src/ovkit/gui/__init__.py +20 -0
  23. ovkit-0.3.0/src/ovkit/gui/app.py +286 -0
  24. ovkit-0.3.0/src/ovkit/gui/controller.py +291 -0
  25. {ovkit-0.1.2 → ovkit-0.3.0}/src/ovkit/manifests/aliases.yaml +11 -4
  26. ovkit-0.3.0/src/ovkit/manifests/genai.yaml +156 -0
  27. ovkit-0.3.0/src/ovkit/manifests/labels.yaml +77 -0
  28. {ovkit-0.1.2 → ovkit-0.3.0}/src/ovkit/manifests/omz.yaml +215 -73
  29. ovkit-0.3.0/src/ovkit/pipelines/__init__.py +179 -0
  30. ovkit-0.3.0/src/ovkit/pipelines/analyze.py +165 -0
  31. ovkit-0.3.0/src/ovkit/pipelines/attention.py +114 -0
  32. ovkit-0.3.0/src/ovkit/pipelines/base.py +97 -0
  33. ovkit-0.3.0/src/ovkit/pipelines/classroom.py +380 -0
  34. ovkit-0.3.0/src/ovkit/pipelines/gaze.py +227 -0
  35. ovkit-0.3.0/src/ovkit/pipelines/plates.py +137 -0
  36. ovkit-0.3.0/src/ovkit/pipelines/privacy.py +128 -0
  37. ovkit-0.3.0/src/ovkit/pipelines/reid.py +127 -0
  38. ovkit-0.3.0/src/ovkit/pipelines/scene.py +129 -0
  39. ovkit-0.3.0/src/ovkit/pipelines/teach.py +452 -0
  40. ovkit-0.3.0/src/ovkit/pipelines/temporal.py +226 -0
  41. ovkit-0.3.0/src/ovkit/pipelines/text.py +81 -0
  42. ovkit-0.3.0/src/ovkit/pipelines/tracking.py +120 -0
  43. ovkit-0.3.0/src/ovkit/recognize/audio.py +117 -0
  44. {ovkit-0.1.2 → ovkit-0.3.0}/src/ovkit/recognize/base.py +24 -3
  45. {ovkit-0.1.2 → ovkit-0.3.0}/src/ovkit/recognize/classify.py +57 -8
  46. {ovkit-0.1.2 → ovkit-0.3.0}/src/ovkit/recognize/detect.py +65 -2
  47. {ovkit-0.1.2 → ovkit-0.3.0}/src/ovkit/recognize/generic.py +30 -2
  48. {ovkit-0.1.2 → ovkit-0.3.0}/src/ovkit/recognize/segment.py +9 -3
  49. ovkit-0.3.0/src/ovkit/rtdetr/__init__.py +218 -0
  50. ovkit-0.3.0/src/ovkit/rtdetr/data/__init__.py +1 -0
  51. ovkit-0.3.0/src/ovkit/rtdetr/data/dataset.py +127 -0
  52. ovkit-0.3.0/src/ovkit/rtdetr/exporter.py +44 -0
  53. ovkit-0.3.0/src/ovkit/rtdetr/nn/__init__.py +1 -0
  54. ovkit-0.3.0/src/ovkit/rtdetr/nn/backbone.py +107 -0
  55. ovkit-0.3.0/src/ovkit/rtdetr/nn/decoder.py +145 -0
  56. ovkit-0.3.0/src/ovkit/rtdetr/nn/encoder.py +113 -0
  57. ovkit-0.3.0/src/ovkit/rtdetr/nn/rtdetr_net.py +45 -0
  58. ovkit-0.3.0/src/ovkit/rtdetr/trainer.py +150 -0
  59. ovkit-0.3.0/src/ovkit/rtdetr/utils/__init__.py +1 -0
  60. ovkit-0.3.0/src/ovkit/rtdetr/utils/loss.py +111 -0
  61. ovkit-0.3.0/src/ovkit/rtdetr/utils/ops.py +63 -0
  62. ovkit-0.3.0/src/ovkit/rtdetr/validator.py +119 -0
  63. ovkit-0.3.0/src/ovkit/solutions/__init__.py +14 -0
  64. ovkit-0.3.0/src/ovkit/solutions/anomaly.py +93 -0
  65. ovkit-0.3.0/src/ovkit/solutions/ocr.py +7 -0
  66. ovkit-0.3.0/src/ovkit/solutions/reid.py +7 -0
  67. ovkit-0.3.0/src/ovkit/solutions/tracking.py +7 -0
  68. ovkit-0.1.2/src/ovkit/core/constants.py +0 -158
  69. ovkit-0.1.2/src/ovkit/core/results.py +0 -322
  70. ovkit-0.1.2/src/ovkit/face/__init__.py +0 -19
  71. ovkit-0.1.2/src/ovkit/face/analyzer.py +0 -26
  72. ovkit-0.1.2/src/ovkit/genai/__init__.py +0 -25
  73. ovkit-0.1.2/src/ovkit/manifests/genai.yaml +0 -38
  74. ovkit-0.1.2/src/ovkit/solutions/__init__.py +0 -9
  75. ovkit-0.1.2/src/ovkit/solutions/anomaly.py +0 -19
  76. ovkit-0.1.2/src/ovkit/solutions/ocr.py +0 -24
  77. ovkit-0.1.2/src/ovkit/solutions/reid.py +0 -26
  78. ovkit-0.1.2/src/ovkit/solutions/tracking.py +0 -23
  79. {ovkit-0.1.2 → ovkit-0.3.0}/.gitignore +0 -0
  80. {ovkit-0.1.2 → ovkit-0.3.0}/LICENSE +0 -0
  81. {ovkit-0.1.2 → ovkit-0.3.0}/src/ovkit/core/__init__.py +0 -0
  82. {ovkit-0.1.2 → ovkit-0.3.0}/src/ovkit/core/convert.py +0 -0
  83. {ovkit-0.1.2 → ovkit-0.3.0}/src/ovkit/core/errors.py +0 -0
  84. {ovkit-0.1.2 → ovkit-0.3.0}/src/ovkit/core/tasks.py +0 -0
  85. {ovkit-0.1.2 → ovkit-0.3.0}/src/ovkit/image/__init__.py +0 -0
  86. {ovkit-0.1.2 → ovkit-0.3.0}/src/ovkit/image/ops.py +0 -0
  87. {ovkit-0.1.2 → ovkit-0.3.0}/src/ovkit/manifests/models.yaml +0 -0
  88. {ovkit-0.1.2 → ovkit-0.3.0}/src/ovkit/py.typed +0 -0
  89. {ovkit-0.1.2 → ovkit-0.3.0}/src/ovkit/recognize/__init__.py +0 -0
  90. {ovkit-0.1.2 → ovkit-0.3.0}/src/ovkit/recognize/face.py +0 -0
  91. {ovkit-0.1.2 → ovkit-0.3.0}/src/ovkit/recognize/ocr.py +0 -0
  92. {ovkit-0.1.2 → ovkit-0.3.0}/src/ovkit/recognize/pose.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: ovkit
3
- Version: 0.1.2
3
+ Version: 0.3.0
4
4
  Summary: A simple Python inference API for OpenVINO: one import, one Model class, clean Results — with AUTO/NPU, async, and INT8.
5
5
  Project-URL: Homepage, https://github.com/leeyunjai82/ovkit
6
6
  Project-URL: Documentation, https://leeyunjai82.github.io/ovkit/
@@ -28,8 +28,12 @@ Requires-Dist: pyyaml>=6.0
28
28
  Provides-Extra: all
29
29
  Requires-Dist: anomalib; extra == 'all'
30
30
  Requires-Dist: nncf; extra == 'all'
31
+ Requires-Dist: onnx; extra == 'all'
31
32
  Requires-Dist: openvino-genai; extra == 'all'
32
33
  Requires-Dist: optimum-intel; extra == 'all'
34
+ Requires-Dist: scipy; extra == 'all'
35
+ Requires-Dist: torch>=2.0; extra == 'all'
36
+ Requires-Dist: torchvision>=0.15; extra == 'all'
33
37
  Provides-Extra: anomaly
34
38
  Requires-Dist: anomalib; extra == 'anomaly'
35
39
  Provides-Extra: dev
@@ -39,8 +43,15 @@ Requires-Dist: ruff; extra == 'dev'
39
43
  Provides-Extra: genai
40
44
  Requires-Dist: openvino-genai; extra == 'genai'
41
45
  Requires-Dist: optimum-intel; extra == 'genai'
46
+ Provides-Extra: hand
47
+ Requires-Dist: mediapipe; extra == 'hand'
42
48
  Provides-Extra: quant
43
49
  Requires-Dist: nncf; extra == 'quant'
50
+ Provides-Extra: train
51
+ Requires-Dist: onnx; extra == 'train'
52
+ Requires-Dist: scipy; extra == 'train'
53
+ Requires-Dist: torch>=2.0; extra == 'train'
54
+ Requires-Dist: torchvision>=0.15; extra == 'train'
44
55
  Description-Content-Type: text/markdown
45
56
 
46
57
  <div align="center">
@@ -69,14 +80,36 @@ ready models — with `AUTO`/`NPU`/`GPU` devices, async throughput, and INT8.
69
80
  ```python
70
81
  from ovkit import Model
71
82
 
72
- r = Model("detect")("image.jpg")[0] # download -> convert -> cache -> run
73
- r.save("out.jpg") # boxes drawn; r.boxes.xyxy / .conf / .cls
83
+ r = Model("detect", "image.jpg") # download -> convert -> cache -> run
84
+ print(r) # 2x person, car
85
+ r.save("out.jpg") # the boxes drawn on the photo
86
+ ```
87
+
88
+ One call fits every input — and Korean names work too:
89
+
90
+ ```python
91
+ r = Model("얼굴분석", "group.jpg") # == Model("face_analyze", ...)
92
+ for row in r.found: # [{'name': '사람', 'name_en': 'person',
93
+ print(row["name"], row["pos"]) # 'score': 0.93, 'box': [...], 'pos': '왼쪽 위'}]
94
+
95
+ for r in Model("track", 0): # webcam / video / "mic" -> a stream
96
+ print(r, r.elapsed_ms, r.device) # 2x person (#1, #4) 14.2 GPU
97
+ ```
98
+
99
+ The same call also runs whole **capabilities** — several models chained into one
100
+ answer, so you never have to crop, chain and stitch by hand:
101
+
102
+ ```python
103
+ r = Model("face_analyze", "group.jpg")
104
+ print(r) # 2 faces: age 31 · male 98% · happy 92%, age 27 · female 95% · neutral 88%
74
105
  ```
75
106
 
76
107
  Or without writing Python at all:
77
108
 
78
109
  ```bash
110
+ ovkit gui # a window: pick a capability, point it at your webcam
79
111
  ovkit run detect image.jpg # prints results, saves image_out.jpg
112
+ ovkit capabilities # what Model(name) can answer
80
113
  ```
81
114
 
82
115
  ## Install
@@ -94,9 +127,89 @@ python -m venv .venv && source .venv/bin/activate # Windows: .venv\Scripts\act
94
127
  pip install -e ".[dev]"
95
128
  ```
96
129
 
130
+ ## Capabilities
131
+
132
+ A capability name gives you the answer, not the plumbing. Each one chains a
133
+ detector with the models that describe what it found — same `Model(...)` call,
134
+ same `Results`, and it takes the same sources (path, ndarray, folder, video,
135
+ camera index).
136
+
137
+ | `Model(...)` | Answers | Chains |
138
+ | ------------ | ------- | ------ |
139
+ | `scene` | `2 people (1 happy) · a laptop and a cup · floor 47%` | detection + segmentation + faces |
140
+ | `face_analyze` | `2 faces: age 31 · male 98% · happy 92%, ...` | face detection + age/gender + emotion (+ head pose, landmarks) |
141
+ | `person_analyze` | `3 people: male 0.98 · long pants 0.95 · bag 0.71, ...` | person detection + attributes |
142
+ | `vehicle_analyze` | `2 vehicles: type: car (0.98) · color: black (0.83), ...` | vehicle detection + type/colour |
143
+ | `read_text` | `'STOP AHEAD'` — every word, in reading order | text detection + text recognition |
144
+ | `read_plate` | `2 vehicles: black car — 12GA3456, ...` | plate detection + text recognition + vehicle attributes |
145
+ | `track` | `2x person (#1, #4)` — ids stable across frames | detection + IoU association |
146
+ | `drowsiness` | `EYES CLOSED 1.4s — drowsy` | face + landmarks + eye state + head pose, **over time** |
147
+ | `gesture` | `thumb up 0.94` | sign-language model over a rolling 8-frame clip |
148
+ | `gaze` | `1 face: looking right and slightly up` | face detection + landmarks + head pose + gaze |
149
+ | `attention` | `1 person looking at: laptop` | gaze + object detection (ray-cast into the boxes) |
150
+ | `anonymize` | the picture with every face pixelated | face (and plate) detection + redaction |
151
+ | `face_match` | `('yunjai', 0.81)` — who this is | embedding + cosine matching against your gallery |
152
+ | `count` | `pencil 3 · cup 1` (optionally one kind only) | detection + per-kind tally |
153
+ | `posture` | `neck 32° — sit up (6s)` | pose + neck angle **over time** |
154
+ | `exercise` | `squat x 12 (down)` | pose + joint-angle hysteresis (squat, push-up) |
155
+ | `attendance` | `present 24/26` + `roll.csv` | face detection + roster matching |
156
+ | `teach` | your own classes, from example photos | embedding + cosine k-NN (5 modes) |
157
+
158
+ ```python
159
+ from ovkit import Model, list_pipelines
160
+
161
+ list_pipelines() # every capability, described
162
+ Model("read_text")("sign.jpg")[0].text # 'STOP AHEAD'
163
+ Model("track")(0) # webcam, ids kept across frames
164
+ Model("face_analyze", attributes=("age_gender",)) # configure what runs
165
+ ```
166
+
167
+ Aliases: `ocr`, `anpr`, `blur`, `driver`, `describe`, `faces`, `people`, `vehicle`, `tracking`, `reid`.
168
+
169
+ ## Teach your own AI (no GPU)
170
+
171
+ A Teachable Machine in five lines — an embedding model turns examples into
172
+ vectors, new inputs match the nearest ones. Five modes: `photo` (default),
173
+ `face` (expressions), `hand` (needs `ovkit[hand]`), `upper`, `body`:
174
+
175
+ ```python
176
+ ai = Model("teach") # or Model("가르치기")
177
+ ai.learn("can", "photos/cans/") # a folder per thing to recognise
178
+ ai.learn("bottle", "photos/bottles/")
179
+ print(ai.guess("new_photo.jpg")) # ('can', 0.93)
180
+ print(ai.score("test_photos/")) # accuracy + what it confuses
181
+ ai.save("recycling") # -> Documents/ovkit/recycling.json
182
+ for r in ai.predict(0, stream=True): ... # webcam over YOUR classes
183
+ ```
184
+
185
+ Korean method names work too: `배우기`, `맞혀봐`, `점수`, `저장`. Collect
186
+ examples with the webcam: `from ovkit.pipelines.teach import collect; collect("가위", 30)`.
187
+
188
+ ## Train your own detector
189
+
190
+ RT-DETR, trainable, Apache-2.0 end to end — an Ultralytics-style workflow with
191
+ no AGPL code or weights anywhere (`pip install "ovkit[train]"`):
192
+
193
+ ```python
194
+ from ovkit import RTDETR
195
+
196
+ model = RTDETR("rtdetr-r18") # or RTDETR("best.pt") to resume
197
+ model.train(data="data.yaml", epochs=100) # the YOLO-format labels you already have
198
+ model.val(data="data.yaml") # mAP50 / mAP50-95
199
+ model.export(half=True) # -> IR + labels.txt
200
+
201
+ r = model("bus.jpg", conf=0.5) # ovkit Results: print(r), r.found, r.save()
202
+ ```
203
+
204
+ The exported IR drops straight into `Model("path/to/rtdetr-r18.xml")` — the
205
+ `labels.txt` written next to it means your classes answer by name. CLI:
206
+ `ovkit train --data data.yaml`, `ovkit val`, `ovkit export`.
207
+ `ovkit capabilities` prints the list; **`ovkit gui` opens a window** where you can
208
+ click through them against your webcam or a picture.
209
+
97
210
  ## Supported tasks
98
211
 
99
- Every task below runs end-to-end through the same 3 lines — swap the alias:
212
+ Every single-model task below runs end-to-end through the same 3 lines — swap the alias:
100
213
 
101
214
  | Task | Alias | Output | Example |
102
215
  | ---- | ----- | ------ | ------- |
@@ -106,6 +219,7 @@ Every task below runs end-to-end through the same 3 lines — swap the alias:
106
219
  | Pose / landmarks | `pose`, `face_landmarks` | `r.keypoints` | [pose.py](https://github.com/leeyunjai82/ovkit/blob/main/examples/pose.py) |
107
220
  | 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) |
108
221
  | OCR | `text_recognition` | `r.text` | [ocr.py](https://github.com/leeyunjai82/ovkit/blob/main/examples/ocr.py) |
222
+ | 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) |
109
223
  | Super-resolution | `super_resolution` | upscaled image via `r.plot()` | [super_resolution.py](https://github.com/leeyunjai82/ovkit/blob/main/examples/super_resolution.py) |
110
224
  | 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) |
111
225
  | 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) |
@@ -174,7 +288,10 @@ control for any model: `model.infer({name: tensor})` with `model.inputs`.
174
288
  | `r.boxes` | `xyxy`, `xywh`, `conf`, `cls` |
175
289
  | `r.masks` / `r.keypoints` / `r.probs` | masks · `[x,y,conf]` · `top1`/`top5` |
176
290
  | `r.text` | decoded text (OCR, face attributes) |
291
+ | `r.labels` / `r.track_ids` | per-box label a pipeline wrote · per-box track id |
177
292
  | `r.tensors` | raw `{name: ndarray}` |
293
+ | `r.summary()` | the whole result as one readable line |
294
+ | `r.crop(i)` / `r.to_dict()` / `r.to_json()` | cut a box out · plain Python · JSON |
178
295
  | `r.plot()` / `r.save(path)` | annotated image (or the model's output image) |
179
296
 
180
297
  </details>
@@ -24,14 +24,36 @@ ready models — with `AUTO`/`NPU`/`GPU` devices, async throughput, and INT8.
24
24
  ```python
25
25
  from ovkit import Model
26
26
 
27
- r = Model("detect")("image.jpg")[0] # download -> convert -> cache -> run
28
- r.save("out.jpg") # boxes drawn; r.boxes.xyxy / .conf / .cls
27
+ r = Model("detect", "image.jpg") # download -> convert -> cache -> run
28
+ print(r) # 2x person, car
29
+ r.save("out.jpg") # the boxes drawn on the photo
30
+ ```
31
+
32
+ One call fits every input — and Korean names work too:
33
+
34
+ ```python
35
+ r = Model("얼굴분석", "group.jpg") # == Model("face_analyze", ...)
36
+ for row in r.found: # [{'name': '사람', 'name_en': 'person',
37
+ print(row["name"], row["pos"]) # 'score': 0.93, 'box': [...], 'pos': '왼쪽 위'}]
38
+
39
+ for r in Model("track", 0): # webcam / video / "mic" -> a stream
40
+ print(r, r.elapsed_ms, r.device) # 2x person (#1, #4) 14.2 GPU
41
+ ```
42
+
43
+ The same call also runs whole **capabilities** — several models chained into one
44
+ answer, so you never have to crop, chain and stitch by hand:
45
+
46
+ ```python
47
+ r = Model("face_analyze", "group.jpg")
48
+ print(r) # 2 faces: age 31 · male 98% · happy 92%, age 27 · female 95% · neutral 88%
29
49
  ```
30
50
 
31
51
  Or without writing Python at all:
32
52
 
33
53
  ```bash
54
+ ovkit gui # a window: pick a capability, point it at your webcam
34
55
  ovkit run detect image.jpg # prints results, saves image_out.jpg
56
+ ovkit capabilities # what Model(name) can answer
35
57
  ```
36
58
 
37
59
  ## Install
@@ -49,9 +71,89 @@ python -m venv .venv && source .venv/bin/activate # Windows: .venv\Scripts\act
49
71
  pip install -e ".[dev]"
50
72
  ```
51
73
 
74
+ ## Capabilities
75
+
76
+ A capability name gives you the answer, not the plumbing. Each one chains a
77
+ detector with the models that describe what it found — same `Model(...)` call,
78
+ same `Results`, and it takes the same sources (path, ndarray, folder, video,
79
+ camera index).
80
+
81
+ | `Model(...)` | Answers | Chains |
82
+ | ------------ | ------- | ------ |
83
+ | `scene` | `2 people (1 happy) · a laptop and a cup · floor 47%` | detection + segmentation + faces |
84
+ | `face_analyze` | `2 faces: age 31 · male 98% · happy 92%, ...` | face detection + age/gender + emotion (+ head pose, landmarks) |
85
+ | `person_analyze` | `3 people: male 0.98 · long pants 0.95 · bag 0.71, ...` | person detection + attributes |
86
+ | `vehicle_analyze` | `2 vehicles: type: car (0.98) · color: black (0.83), ...` | vehicle detection + type/colour |
87
+ | `read_text` | `'STOP AHEAD'` — every word, in reading order | text detection + text recognition |
88
+ | `read_plate` | `2 vehicles: black car — 12GA3456, ...` | plate detection + text recognition + vehicle attributes |
89
+ | `track` | `2x person (#1, #4)` — ids stable across frames | detection + IoU association |
90
+ | `drowsiness` | `EYES CLOSED 1.4s — drowsy` | face + landmarks + eye state + head pose, **over time** |
91
+ | `gesture` | `thumb up 0.94` | sign-language model over a rolling 8-frame clip |
92
+ | `gaze` | `1 face: looking right and slightly up` | face detection + landmarks + head pose + gaze |
93
+ | `attention` | `1 person looking at: laptop` | gaze + object detection (ray-cast into the boxes) |
94
+ | `anonymize` | the picture with every face pixelated | face (and plate) detection + redaction |
95
+ | `face_match` | `('yunjai', 0.81)` — who this is | embedding + cosine matching against your gallery |
96
+ | `count` | `pencil 3 · cup 1` (optionally one kind only) | detection + per-kind tally |
97
+ | `posture` | `neck 32° — sit up (6s)` | pose + neck angle **over time** |
98
+ | `exercise` | `squat x 12 (down)` | pose + joint-angle hysteresis (squat, push-up) |
99
+ | `attendance` | `present 24/26` + `roll.csv` | face detection + roster matching |
100
+ | `teach` | your own classes, from example photos | embedding + cosine k-NN (5 modes) |
101
+
102
+ ```python
103
+ from ovkit import Model, list_pipelines
104
+
105
+ list_pipelines() # every capability, described
106
+ Model("read_text")("sign.jpg")[0].text # 'STOP AHEAD'
107
+ Model("track")(0) # webcam, ids kept across frames
108
+ Model("face_analyze", attributes=("age_gender",)) # configure what runs
109
+ ```
110
+
111
+ Aliases: `ocr`, `anpr`, `blur`, `driver`, `describe`, `faces`, `people`, `vehicle`, `tracking`, `reid`.
112
+
113
+ ## Teach your own AI (no GPU)
114
+
115
+ A Teachable Machine in five lines — an embedding model turns examples into
116
+ vectors, new inputs match the nearest ones. Five modes: `photo` (default),
117
+ `face` (expressions), `hand` (needs `ovkit[hand]`), `upper`, `body`:
118
+
119
+ ```python
120
+ ai = Model("teach") # or Model("가르치기")
121
+ ai.learn("can", "photos/cans/") # a folder per thing to recognise
122
+ ai.learn("bottle", "photos/bottles/")
123
+ print(ai.guess("new_photo.jpg")) # ('can', 0.93)
124
+ print(ai.score("test_photos/")) # accuracy + what it confuses
125
+ ai.save("recycling") # -> Documents/ovkit/recycling.json
126
+ for r in ai.predict(0, stream=True): ... # webcam over YOUR classes
127
+ ```
128
+
129
+ Korean method names work too: `배우기`, `맞혀봐`, `점수`, `저장`. Collect
130
+ examples with the webcam: `from ovkit.pipelines.teach import collect; collect("가위", 30)`.
131
+
132
+ ## Train your own detector
133
+
134
+ RT-DETR, trainable, Apache-2.0 end to end — an Ultralytics-style workflow with
135
+ no AGPL code or weights anywhere (`pip install "ovkit[train]"`):
136
+
137
+ ```python
138
+ from ovkit import RTDETR
139
+
140
+ model = RTDETR("rtdetr-r18") # or RTDETR("best.pt") to resume
141
+ model.train(data="data.yaml", epochs=100) # the YOLO-format labels you already have
142
+ model.val(data="data.yaml") # mAP50 / mAP50-95
143
+ model.export(half=True) # -> IR + labels.txt
144
+
145
+ r = model("bus.jpg", conf=0.5) # ovkit Results: print(r), r.found, r.save()
146
+ ```
147
+
148
+ The exported IR drops straight into `Model("path/to/rtdetr-r18.xml")` — the
149
+ `labels.txt` written next to it means your classes answer by name. CLI:
150
+ `ovkit train --data data.yaml`, `ovkit val`, `ovkit export`.
151
+ `ovkit capabilities` prints the list; **`ovkit gui` opens a window** where you can
152
+ click through them against your webcam or a picture.
153
+
52
154
  ## Supported tasks
53
155
 
54
- Every task below runs end-to-end through the same 3 lines — swap the alias:
156
+ Every single-model task below runs end-to-end through the same 3 lines — swap the alias:
55
157
 
56
158
  | Task | Alias | Output | Example |
57
159
  | ---- | ----- | ------ | ------- |
@@ -61,6 +163,7 @@ Every task below runs end-to-end through the same 3 lines — swap the alias:
61
163
  | Pose / landmarks | `pose`, `face_landmarks` | `r.keypoints` | [pose.py](https://github.com/leeyunjai82/ovkit/blob/main/examples/pose.py) |
62
164
  | 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) |
63
165
  | OCR | `text_recognition` | `r.text` | [ocr.py](https://github.com/leeyunjai82/ovkit/blob/main/examples/ocr.py) |
166
+ | 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) |
64
167
  | Super-resolution | `super_resolution` | upscaled image via `r.plot()` | [super_resolution.py](https://github.com/leeyunjai82/ovkit/blob/main/examples/super_resolution.py) |
65
168
  | 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) |
66
169
  | 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) |
@@ -129,7 +232,10 @@ control for any model: `model.infer({name: tensor})` with `model.inputs`.
129
232
  | `r.boxes` | `xyxy`, `xywh`, `conf`, `cls` |
130
233
  | `r.masks` / `r.keypoints` / `r.probs` | masks · `[x,y,conf]` · `top1`/`top5` |
131
234
  | `r.text` | decoded text (OCR, face attributes) |
235
+ | `r.labels` / `r.track_ids` | per-box label a pipeline wrote · per-box track id |
132
236
  | `r.tensors` | raw `{name: ndarray}` |
237
+ | `r.summary()` | the whole result as one readable line |
238
+ | `r.crop(i)` / `r.to_dict()` / `r.to_json()` | cut a box out · plain Python · JSON |
133
239
  | `r.plot()` / `r.save(path)` | annotated image (or the model's output image) |
134
240
 
135
241
  </details>
@@ -16,6 +16,7 @@ pip install -r examples/requirements.txt # fastapi / uvicorn (web demos only)
16
16
  | [`classify.py`](classify.py) | `classify` | top-5 classes |
17
17
  | [`ocr.py`](ocr.py) | `text_recognition` | decoded text |
18
18
  | [`super_resolution.py`](super_resolution.py) | `super_resolution` | upscaled image |
19
+ | [`background_matting.py`](background_matting.py) | `background_matting_mobilenetv2` | cut the subject out (needs frame + empty-scene photo) |
19
20
 
20
21
  ```bash
21
22
  python examples/detect.py photo.jpg # each is ~15 lines — read the source
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "ovkit"
7
- version = "0.1.2"
7
+ version = "0.3.0"
8
8
  description = "A simple Python inference API for OpenVINO: one import, one Model class, clean Results — with AUTO/NPU, async, and INT8."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -33,7 +33,9 @@ dependencies = [
33
33
  genai = ["openvino-genai", "optimum-intel"]
34
34
  anomaly = ["anomalib"]
35
35
  quant = ["nncf"]
36
- all = ["openvino-genai", "optimum-intel", "anomalib", "nncf"]
36
+ train = ["torch>=2.0", "torchvision>=0.15", "scipy", "onnx"]
37
+ hand = ["mediapipe"]
38
+ all = ["openvino-genai", "optimum-intel", "anomalib", "nncf", "torch>=2.0", "torchvision>=0.15", "scipy", "onnx"]
37
39
  dev = ["pytest>=7", "ruff", "black"]
38
40
 
39
41
  [project.urls]
@@ -3,14 +3,23 @@
3
3
  One import, one :class:`Model` class, a callable object, and clean
4
4
  :class:`Results` — plus OpenVINO's strengths (AUTO/NPU devices, async, INT8).
5
5
 
6
- Example
7
- -------
6
+ One model
7
+ ---------
8
8
  >>> from ovkit import Model
9
9
  >>> model = Model("rtdetr_r50") # name -> auto download/convert/cache
10
10
  >>> results = model("img.jpg", conf=0.25)
11
11
  >>> for r in results:
12
- ... print(r.boxes.xyxy, r.boxes.conf, r.boxes.cls)
12
+ ... print(r.summary()) # '2x person, car'
13
13
  ... r.save("out.jpg")
14
+
15
+ Several models, one answer
16
+ --------------------------
17
+ >>> from ovkit import Model
18
+ >>> for r in Model("face_analyze")("group.jpg"):
19
+ ... print(r.summary()) # '2 faces: age 31 · male 98% · happy 92%, ...'
20
+
21
+ A capability name composes the models the answer needs — detection plus
22
+ whatever describes what was found. :func:`list_pipelines` shows them all.
14
23
  """
15
24
 
16
25
  from __future__ import annotations
@@ -29,11 +38,24 @@ from .core.errors import (
29
38
  from .core.model import Model
30
39
  from .core.registry import list_models
31
40
  from .core.results import Boxes, Keypoints, Masks, Probs, Results
41
+ from .pipelines import Pipeline, list_pipelines
42
+
43
+ __version__ = "0.3.0"
44
+
45
+
46
+ def __getattr__(name: str): # lazy: keeps `import ovkit` free of the train stack
47
+ if name == "RTDETR":
48
+ from .rtdetr import RTDETR
49
+
50
+ return RTDETR
51
+ raise AttributeError(f"module 'ovkit' has no attribute {name!r}")
32
52
 
33
- __version__ = "0.1.2"
34
53
 
35
54
  __all__ = [
36
55
  "Model",
56
+ "RTDETR",
57
+ "Pipeline",
58
+ "list_pipelines",
37
59
  "Results",
38
60
  "Boxes",
39
61
  "Masks",
@@ -1,4 +1,4 @@
1
- """``ovkit`` command-line interface: ``run``, ``list``, ``info``, ``download``, ``devices``."""
1
+ """``ovkit`` CLI: ``gui``, ``run``, ``train``, ``val``, ``export``, ``list``, ``info``, ``download``, ``devices``."""
2
2
 
3
3
  from __future__ import annotations
4
4
 
@@ -41,6 +41,19 @@ def _cmd_list(_: argparse.Namespace) -> int:
41
41
  return 0
42
42
 
43
43
 
44
+ def _cmd_capabilities(_: argparse.Namespace) -> int:
45
+ """What ``Model(name)`` can answer beyond a single network."""
46
+ from .pipelines import ALIASES, list_pipelines
47
+
48
+ print("capabilities — Model(name) chains the models the answer needs:\n")
49
+ for name, description in list_pipelines().items():
50
+ print(f" {name:16s} {description}")
51
+ print("\naliases:")
52
+ for alias, target in sorted(ALIASES.items()):
53
+ print(f" {alias:16s} -> {target}")
54
+ return 0
55
+
56
+
44
57
  def _cmd_info(args: argparse.Namespace) -> int:
45
58
  entry = resolve(args.name)
46
59
  if entry is None:
@@ -96,25 +109,12 @@ def _cmd_run(args: argparse.Namespace) -> int:
96
109
  return 0
97
110
 
98
111
  for r in results:
99
- parts = [f"task={r.task}"]
100
- if r.text:
101
- parts.append(f'text="{r.text}"')
112
+ print(r.summary())
102
113
  if r.boxes is not None:
103
- parts.append(f"{len(r.boxes)} boxes")
104
114
  for x1, y1, x2, y2, c, cl in r.boxes.data[:20]:
105
115
  print(
106
116
  f" {r.name_for(int(cl)):16s} {c:.2f} [{int(x1)},{int(y1)},{int(x2)},{int(y2)}]"
107
117
  )
108
- if r.probs is not None:
109
- top = ", ".join(
110
- f"{r.name_for(int(i))} {r.probs.data[int(i)]:.2f}" for i in r.probs.top5
111
- )
112
- parts.append(f"top-5: {top}")
113
- if r.masks is not None:
114
- parts.append(f"masks {tuple(r.masks.data.shape)}")
115
- if r.keypoints is not None:
116
- parts.append(f"keypoints {tuple(r.keypoints.data.shape)}")
117
- print(" | ".join(parts))
118
118
 
119
119
  save = args.save
120
120
  if save is None and results and Path(str(args.source)).is_file():
@@ -125,15 +125,57 @@ def _cmd_run(args: argparse.Namespace) -> int:
125
125
  return 0
126
126
 
127
127
 
128
+ def _cmd_gui(args: argparse.Namespace) -> int:
129
+ """Open the desktop window: ``ovkit gui``."""
130
+ from .gui import main as gui_main
131
+
132
+ return gui_main(device=args.device, camera=args.camera)
133
+
134
+
135
+ def _cmd_train(args: argparse.Namespace) -> int:
136
+ """Train RT-DETR on YOLO-format data: ``ovkit train --data data.yaml``."""
137
+ from .rtdetr import RTDETR
138
+
139
+ model = RTDETR(args.model, device=args.device)
140
+ best = model.train(
141
+ data=args.data, epochs=args.epochs, imgsz=args.imgsz, batch=args.batch, lr=args.lr
142
+ )
143
+ print(f"best checkpoint: {best}")
144
+ return 0
145
+
146
+
147
+ def _cmd_val(args: argparse.Namespace) -> int:
148
+ from .rtdetr import RTDETR
149
+
150
+ RTDETR(args.model, device=args.device).val(data=args.data, imgsz=args.imgsz)
151
+ return 0
152
+
153
+
154
+ def _cmd_export(args: argparse.Namespace) -> int:
155
+ from .rtdetr import RTDETR
156
+
157
+ out = RTDETR(args.model).export(imgsz=args.imgsz, half=args.half, out_dir=args.out)
158
+ print(f"exported: {out} (+ labels.txt)")
159
+ return 0
160
+
161
+
128
162
  def main(argv: list[str] | None = None) -> int:
129
163
  """CLI entry point. Returns a process exit code."""
130
164
  parser = argparse.ArgumentParser(prog="ovkit", description="ovkit model utilities")
131
165
  parser.add_argument("--version", action="version", version=f"ovkit {__version__}")
132
166
  sub = parser.add_subparsers(dest="command", required=True)
133
167
 
168
+ p_gui = sub.add_parser("gui", help="open the desktop window (easiest way to try ovkit)")
169
+ p_gui.add_argument("--device", default="AUTO", help="AUTO | CPU | GPU | NPU")
170
+ p_gui.add_argument("--camera", type=int, default=0, help="camera index for the webcam button")
171
+ p_gui.set_defaults(func=_cmd_gui)
172
+
134
173
  p_list = sub.add_parser("list", help="list registered models")
135
174
  p_list.set_defaults(func=_cmd_list)
136
175
 
176
+ p_caps = sub.add_parser("capabilities", help="list composed capabilities (Model(name))")
177
+ p_caps.set_defaults(func=_cmd_capabilities)
178
+
137
179
  p_info = sub.add_parser("info", help="show details for a model")
138
180
  p_info.add_argument("name")
139
181
  p_info.set_defaults(func=_cmd_info)
@@ -143,6 +185,30 @@ def main(argv: list[str] | None = None) -> int:
143
185
  p_dl.add_argument("--no-convert", action="store_true", help="skip IR conversion")
144
186
  p_dl.set_defaults(func=_cmd_download)
145
187
 
188
+ p_train = sub.add_parser("train", help="train RT-DETR on your own data (needs ovkit[train])")
189
+ p_train.add_argument("--data", required=True, help="data.yaml (YOLO format)")
190
+ p_train.add_argument("--model", default="rtdetr-r18", help="variant or a .pt to resume")
191
+ p_train.add_argument("--epochs", type=int, default=100)
192
+ p_train.add_argument("--imgsz", type=int, default=640)
193
+ p_train.add_argument("--batch", type=int, default=8)
194
+ p_train.add_argument("--lr", type=float, default=1e-4)
195
+ p_train.add_argument("--device", default=None)
196
+ p_train.set_defaults(func=_cmd_train)
197
+
198
+ p_val = sub.add_parser("val", help="mAP on the val split of a data.yaml")
199
+ p_val.add_argument("--model", required=True, help="a trained .pt")
200
+ p_val.add_argument("--data", required=True)
201
+ p_val.add_argument("--imgsz", type=int, default=640)
202
+ p_val.add_argument("--device", default=None)
203
+ p_val.set_defaults(func=_cmd_val)
204
+
205
+ p_exp = sub.add_parser("export", help="export a trained .pt to OpenVINO IR + labels.txt")
206
+ p_exp.add_argument("--model", required=True, help="a trained .pt")
207
+ p_exp.add_argument("--imgsz", type=int, default=640)
208
+ p_exp.add_argument("--half", action="store_true", help="FP16 IR")
209
+ p_exp.add_argument("--out", default=".", help="output directory")
210
+ p_exp.set_defaults(func=_cmd_export)
211
+
146
212
  p_dev = sub.add_parser("devices", help="list OpenVINO devices")
147
213
  p_dev.set_defaults(func=_cmd_devices)
148
214
 
@@ -0,0 +1,13 @@
1
+ """Audio I/O helpers: read a file into the array a model actually wants.
2
+
3
+ Models want float32 mono at a fixed sample rate; files are int16 stereo at
4
+ whatever rate the recorder used. These helpers close that gap so callers never
5
+ have to touch :mod:`wave` or write their own resampler.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from .ops import read_audio, read_wav, resample, to_mono, write_wav
11
+ from .plot import waveform
12
+
13
+ __all__ = ["read_audio", "read_wav", "resample", "to_mono", "waveform", "write_wav"]