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.
- {ovkit-0.1.2 → ovkit-0.3.0}/PKG-INFO +121 -4
- {ovkit-0.1.2 → ovkit-0.3.0}/README.md +109 -3
- {ovkit-0.1.2 → ovkit-0.3.0}/examples/README.md +1 -0
- {ovkit-0.1.2 → ovkit-0.3.0}/pyproject.toml +4 -2
- {ovkit-0.1.2 → ovkit-0.3.0}/src/ovkit/__init__.py +26 -4
- {ovkit-0.1.2 → ovkit-0.3.0}/src/ovkit/__main__.py +81 -15
- ovkit-0.3.0/src/ovkit/audio/__init__.py +13 -0
- ovkit-0.3.0/src/ovkit/audio/ops.py +108 -0
- ovkit-0.3.0/src/ovkit/audio/plot.py +47 -0
- {ovkit-0.1.2 → ovkit-0.3.0}/src/ovkit/core/backend.py +46 -1
- ovkit-0.3.0/src/ovkit/core/constants.py +335 -0
- {ovkit-0.1.2 → ovkit-0.3.0}/src/ovkit/core/download.py +24 -1
- ovkit-0.3.0/src/ovkit/core/i18n.py +317 -0
- {ovkit-0.1.2 → ovkit-0.3.0}/src/ovkit/core/model.py +205 -19
- {ovkit-0.1.2 → ovkit-0.3.0}/src/ovkit/core/registry.py +21 -1
- ovkit-0.3.0/src/ovkit/core/results.py +598 -0
- ovkit-0.3.0/src/ovkit/data/imagenet1000.txt +1000 -0
- ovkit-0.3.0/src/ovkit/face/__init__.py +8 -0
- ovkit-0.3.0/src/ovkit/face/analyzer.py +7 -0
- ovkit-0.3.0/src/ovkit/genai/__init__.py +40 -0
- {ovkit-0.1.2 → ovkit-0.3.0}/src/ovkit/genai/pipelines.py +105 -1
- ovkit-0.3.0/src/ovkit/gui/__init__.py +20 -0
- ovkit-0.3.0/src/ovkit/gui/app.py +286 -0
- ovkit-0.3.0/src/ovkit/gui/controller.py +291 -0
- {ovkit-0.1.2 → ovkit-0.3.0}/src/ovkit/manifests/aliases.yaml +11 -4
- ovkit-0.3.0/src/ovkit/manifests/genai.yaml +156 -0
- ovkit-0.3.0/src/ovkit/manifests/labels.yaml +77 -0
- {ovkit-0.1.2 → ovkit-0.3.0}/src/ovkit/manifests/omz.yaml +215 -73
- ovkit-0.3.0/src/ovkit/pipelines/__init__.py +179 -0
- ovkit-0.3.0/src/ovkit/pipelines/analyze.py +165 -0
- ovkit-0.3.0/src/ovkit/pipelines/attention.py +114 -0
- ovkit-0.3.0/src/ovkit/pipelines/base.py +97 -0
- ovkit-0.3.0/src/ovkit/pipelines/classroom.py +380 -0
- ovkit-0.3.0/src/ovkit/pipelines/gaze.py +227 -0
- ovkit-0.3.0/src/ovkit/pipelines/plates.py +137 -0
- ovkit-0.3.0/src/ovkit/pipelines/privacy.py +128 -0
- ovkit-0.3.0/src/ovkit/pipelines/reid.py +127 -0
- ovkit-0.3.0/src/ovkit/pipelines/scene.py +129 -0
- ovkit-0.3.0/src/ovkit/pipelines/teach.py +452 -0
- ovkit-0.3.0/src/ovkit/pipelines/temporal.py +226 -0
- ovkit-0.3.0/src/ovkit/pipelines/text.py +81 -0
- ovkit-0.3.0/src/ovkit/pipelines/tracking.py +120 -0
- ovkit-0.3.0/src/ovkit/recognize/audio.py +117 -0
- {ovkit-0.1.2 → ovkit-0.3.0}/src/ovkit/recognize/base.py +24 -3
- {ovkit-0.1.2 → ovkit-0.3.0}/src/ovkit/recognize/classify.py +57 -8
- {ovkit-0.1.2 → ovkit-0.3.0}/src/ovkit/recognize/detect.py +65 -2
- {ovkit-0.1.2 → ovkit-0.3.0}/src/ovkit/recognize/generic.py +30 -2
- {ovkit-0.1.2 → ovkit-0.3.0}/src/ovkit/recognize/segment.py +9 -3
- ovkit-0.3.0/src/ovkit/rtdetr/__init__.py +218 -0
- ovkit-0.3.0/src/ovkit/rtdetr/data/__init__.py +1 -0
- ovkit-0.3.0/src/ovkit/rtdetr/data/dataset.py +127 -0
- ovkit-0.3.0/src/ovkit/rtdetr/exporter.py +44 -0
- ovkit-0.3.0/src/ovkit/rtdetr/nn/__init__.py +1 -0
- ovkit-0.3.0/src/ovkit/rtdetr/nn/backbone.py +107 -0
- ovkit-0.3.0/src/ovkit/rtdetr/nn/decoder.py +145 -0
- ovkit-0.3.0/src/ovkit/rtdetr/nn/encoder.py +113 -0
- ovkit-0.3.0/src/ovkit/rtdetr/nn/rtdetr_net.py +45 -0
- ovkit-0.3.0/src/ovkit/rtdetr/trainer.py +150 -0
- ovkit-0.3.0/src/ovkit/rtdetr/utils/__init__.py +1 -0
- ovkit-0.3.0/src/ovkit/rtdetr/utils/loss.py +111 -0
- ovkit-0.3.0/src/ovkit/rtdetr/utils/ops.py +63 -0
- ovkit-0.3.0/src/ovkit/rtdetr/validator.py +119 -0
- ovkit-0.3.0/src/ovkit/solutions/__init__.py +14 -0
- ovkit-0.3.0/src/ovkit/solutions/anomaly.py +93 -0
- ovkit-0.3.0/src/ovkit/solutions/ocr.py +7 -0
- ovkit-0.3.0/src/ovkit/solutions/reid.py +7 -0
- ovkit-0.3.0/src/ovkit/solutions/tracking.py +7 -0
- ovkit-0.1.2/src/ovkit/core/constants.py +0 -158
- ovkit-0.1.2/src/ovkit/core/results.py +0 -322
- ovkit-0.1.2/src/ovkit/face/__init__.py +0 -19
- ovkit-0.1.2/src/ovkit/face/analyzer.py +0 -26
- ovkit-0.1.2/src/ovkit/genai/__init__.py +0 -25
- ovkit-0.1.2/src/ovkit/manifests/genai.yaml +0 -38
- ovkit-0.1.2/src/ovkit/solutions/__init__.py +0 -9
- ovkit-0.1.2/src/ovkit/solutions/anomaly.py +0 -19
- ovkit-0.1.2/src/ovkit/solutions/ocr.py +0 -24
- ovkit-0.1.2/src/ovkit/solutions/reid.py +0 -26
- ovkit-0.1.2/src/ovkit/solutions/tracking.py +0 -23
- {ovkit-0.1.2 → ovkit-0.3.0}/.gitignore +0 -0
- {ovkit-0.1.2 → ovkit-0.3.0}/LICENSE +0 -0
- {ovkit-0.1.2 → ovkit-0.3.0}/src/ovkit/core/__init__.py +0 -0
- {ovkit-0.1.2 → ovkit-0.3.0}/src/ovkit/core/convert.py +0 -0
- {ovkit-0.1.2 → ovkit-0.3.0}/src/ovkit/core/errors.py +0 -0
- {ovkit-0.1.2 → ovkit-0.3.0}/src/ovkit/core/tasks.py +0 -0
- {ovkit-0.1.2 → ovkit-0.3.0}/src/ovkit/image/__init__.py +0 -0
- {ovkit-0.1.2 → ovkit-0.3.0}/src/ovkit/image/ops.py +0 -0
- {ovkit-0.1.2 → ovkit-0.3.0}/src/ovkit/manifests/models.yaml +0 -0
- {ovkit-0.1.2 → ovkit-0.3.0}/src/ovkit/py.typed +0 -0
- {ovkit-0.1.2 → ovkit-0.3.0}/src/ovkit/recognize/__init__.py +0 -0
- {ovkit-0.1.2 → ovkit-0.3.0}/src/ovkit/recognize/face.py +0 -0
- {ovkit-0.1.2 → ovkit-0.3.0}/src/ovkit/recognize/ocr.py +0 -0
- {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.
|
|
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"
|
|
73
|
-
r
|
|
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"
|
|
28
|
-
r
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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``
|
|
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
|
-
|
|
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"]
|