evren-sdk 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.
- evren_sdk-0.3.0/.gitignore +78 -0
- evren_sdk-0.3.0/PKG-INFO +206 -0
- evren_sdk-0.3.0/README.md +171 -0
- evren_sdk-0.3.0/evren_sdk/__init__.py +40 -0
- evren_sdk-0.3.0/evren_sdk/client.py +412 -0
- evren_sdk-0.3.0/evren_sdk/exceptions.py +35 -0
- evren_sdk-0.3.0/evren_sdk/models.py +91 -0
- evren_sdk-0.3.0/evren_sdk/py.typed +0 -0
- evren_sdk-0.3.0/pyproject.toml +60 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.pyc
|
|
4
|
+
*.pyo
|
|
5
|
+
*.egg-info/
|
|
6
|
+
dist/
|
|
7
|
+
build/
|
|
8
|
+
htmlcov/
|
|
9
|
+
.coverage
|
|
10
|
+
.ruff_cache/
|
|
11
|
+
.mypy_cache/
|
|
12
|
+
.pytest_cache/
|
|
13
|
+
|
|
14
|
+
# Virtual environments
|
|
15
|
+
.venv/
|
|
16
|
+
venv/
|
|
17
|
+
env/
|
|
18
|
+
|
|
19
|
+
# Node.js
|
|
20
|
+
node_modules/
|
|
21
|
+
*.tsbuildinfo
|
|
22
|
+
.vite/
|
|
23
|
+
coverage/
|
|
24
|
+
|
|
25
|
+
# Environment
|
|
26
|
+
.env
|
|
27
|
+
.env.*
|
|
28
|
+
!.env.example
|
|
29
|
+
|
|
30
|
+
# IDE
|
|
31
|
+
.idea/
|
|
32
|
+
.vscode/
|
|
33
|
+
*.swp
|
|
34
|
+
*.swo
|
|
35
|
+
|
|
36
|
+
# Cursor / AI
|
|
37
|
+
.cursor/
|
|
38
|
+
AGENTS.md
|
|
39
|
+
agent-transcripts/
|
|
40
|
+
mcps/
|
|
41
|
+
prompts/
|
|
42
|
+
|
|
43
|
+
# OS
|
|
44
|
+
.DS_Store
|
|
45
|
+
Thumbs.db
|
|
46
|
+
Desktop.ini
|
|
47
|
+
|
|
48
|
+
# Terminals (cursor IDE)
|
|
49
|
+
terminals/
|
|
50
|
+
|
|
51
|
+
# SSL / TLS secrets
|
|
52
|
+
ssl/
|
|
53
|
+
infra_scripts/**/certs/*.key
|
|
54
|
+
infra_scripts/**/certs/*.pem
|
|
55
|
+
infra_scripts/elasticsearch/certs/
|
|
56
|
+
|
|
57
|
+
# Build artifacts
|
|
58
|
+
src/frontend/dist/
|
|
59
|
+
|
|
60
|
+
# TypeScript compiled .js files (source is .tsx)
|
|
61
|
+
src/frontend/src/**/*.js
|
|
62
|
+
|
|
63
|
+
# Env file (secrets)
|
|
64
|
+
.env
|
|
65
|
+
|
|
66
|
+
# Test data / archives
|
|
67
|
+
*.zip
|
|
68
|
+
drones/
|
|
69
|
+
_*.py
|
|
70
|
+
!__init__.py
|
|
71
|
+
!src/backend/**/_*.py
|
|
72
|
+
|
|
73
|
+
# Internal planning
|
|
74
|
+
tasks/
|
|
75
|
+
altyapi.txt
|
|
76
|
+
*.bundle
|
|
77
|
+
start.bat
|
|
78
|
+
.cursorignore
|
evren_sdk-0.3.0/PKG-INFO
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: evren-sdk
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: EVREN MLOps Platform — Python inference SDK for object detection, classification, and segmentation models.
|
|
5
|
+
Project-URL: Homepage, https://evren.ssyz.org.tr
|
|
6
|
+
Project-URL: Documentation, https://docs.ssyz.org.tr/sdk
|
|
7
|
+
Project-URL: Repository, https://gitlab.crudfab.com/ssb/ssyz/evren/platform
|
|
8
|
+
Project-URL: Changelog, https://gitlab.crudfab.com/ssb/ssyz/evren/platform/-/blob/main/sdk/CHANGELOG.md
|
|
9
|
+
Project-URL: Issues, https://gitlab.crudfab.com/ssb/ssyz/evren/platform/-/issues
|
|
10
|
+
Author-email: Serkan Peker <serkan.peker@crudfab.com>
|
|
11
|
+
Maintainer-email: Serkan Peker <serkan.peker@crudfab.com>
|
|
12
|
+
License-Expression: Apache-2.0
|
|
13
|
+
Keywords: computer-vision,deep-learning,evren,inference,mlops,object-detection,sdk,yolo
|
|
14
|
+
Classifier: Development Status :: 4 - Beta
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: Intended Audience :: Science/Research
|
|
17
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
18
|
+
Classifier: Operating System :: OS Independent
|
|
19
|
+
Classifier: Programming Language :: Python :: 3
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
24
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
25
|
+
Classifier: Topic :: Scientific/Engineering :: Image Recognition
|
|
26
|
+
Classifier: Typing :: Typed
|
|
27
|
+
Requires-Python: >=3.10
|
|
28
|
+
Requires-Dist: httpx>=0.27
|
|
29
|
+
Provides-Extra: dev
|
|
30
|
+
Requires-Dist: mypy; extra == 'dev'
|
|
31
|
+
Requires-Dist: pytest; extra == 'dev'
|
|
32
|
+
Requires-Dist: pytest-asyncio; extra == 'dev'
|
|
33
|
+
Requires-Dist: ruff; extra == 'dev'
|
|
34
|
+
Description-Content-Type: text/markdown
|
|
35
|
+
|
|
36
|
+
# evren-sdk
|
|
37
|
+
|
|
38
|
+
EVREN platformu üzerinde eğitilmiş bilgisayarlı görü modellerine
|
|
39
|
+
Python'dan çıkarım (inference) yapmanızı sağlayan resmi SDK.
|
|
40
|
+
|
|
41
|
+
Desteklenen görevler: **nesne tespiti**, **sınıflandırma**, **segmentasyon**,
|
|
42
|
+
**döndürülmüş kutu (OBB)**, **anahtar nokta (keypoint)**.
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
pip install evren-sdk
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Hızlı Başlangıç
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
from evren_sdk import EvrenClient
|
|
52
|
+
|
|
53
|
+
client = EvrenClient(api_key="evren_xxxxx")
|
|
54
|
+
|
|
55
|
+
result = client.predict("kullanici/model-adi", "foto.jpg", confidence=0.3)
|
|
56
|
+
|
|
57
|
+
for det in result.predictions:
|
|
58
|
+
print(f"{det.class_name}: %{det.confidence:.0%} bbox={det.bbox}")
|
|
59
|
+
|
|
60
|
+
print(f"Çıkarım süresi: {result.inference_ms:.0f} ms")
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Kimlik Doğrulama
|
|
64
|
+
|
|
65
|
+
Platformda **Ayarlar → API Anahtarları** sayfasından anahtar oluşturun.
|
|
66
|
+
Anahtar `evren_` ön eki ile başlar ve `X-API-Key` başlığı üzerinden
|
|
67
|
+
otomatik gönderilir.
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
# API anahtarı ile (önerilen)
|
|
71
|
+
client = EvrenClient(api_key="evren_xxxxx")
|
|
72
|
+
|
|
73
|
+
# JWT token ile de çalışır (Authorization: Bearer)
|
|
74
|
+
client = EvrenClient(api_key="eyJhbGci...")
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Tekil Çıkarım
|
|
78
|
+
|
|
79
|
+
```python
|
|
80
|
+
result = client.predict(
|
|
81
|
+
model="kullanici/model-adi", # slug veya UUID
|
|
82
|
+
image="resim.jpg", # dosya yolu, Path veya bytes
|
|
83
|
+
confidence=0.25, # minimum güven eşiği
|
|
84
|
+
iou=0.45, # NMS IoU eşiği
|
|
85
|
+
image_size=640, # giriş boyutu
|
|
86
|
+
classes=["araba", "insan"], # isteğe bağlı sınıf filtresi
|
|
87
|
+
)
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
`model` parametresi üç format kabul eder:
|
|
91
|
+
|
|
92
|
+
| Format | Açıklama |
|
|
93
|
+
|---|---|
|
|
94
|
+
| `"owner/slug"` | Son versiyonu otomatik çözer |
|
|
95
|
+
| `"owner/slug:v2.0"` | Belirli versiyon etiketi |
|
|
96
|
+
| `"019cec..."` (UUID) | Doğrudan versiyon kimliği |
|
|
97
|
+
|
|
98
|
+
## Toplu Çıkarım (Batch)
|
|
99
|
+
|
|
100
|
+
Birden fazla görseli tek istekte GPU batch çıkarımı ile işleyin.
|
|
101
|
+
Tekil çağrılardan önemli ölçüde hızlıdır.
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
batch = client.predict_batch(
|
|
105
|
+
model="kullanici/model-adi",
|
|
106
|
+
images=["img1.jpg", "img2.jpg", "img3.jpg"],
|
|
107
|
+
confidence=0.3,
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
for r in batch:
|
|
111
|
+
print(f"{r.count} tespit, {r.inference_ms:.0f} ms")
|
|
112
|
+
|
|
113
|
+
print(f"Toplam: {batch.total_ms:.0f} ms, {batch.count} görsel")
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Model Bilgileri
|
|
117
|
+
|
|
118
|
+
```python
|
|
119
|
+
# Sınıf listesi ve renkleri
|
|
120
|
+
info = client.model_classes("kullanici/model-adi")
|
|
121
|
+
print(info.architecture) # "yolo26x"
|
|
122
|
+
for cls in info.classes:
|
|
123
|
+
print(f" {cls.name}: {cls.color}")
|
|
124
|
+
|
|
125
|
+
# Modelleri listele
|
|
126
|
+
for m in client.list_models():
|
|
127
|
+
print(f"{m.full_slug} — {m.architecture}")
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## Ön Yükleme (Warmup)
|
|
131
|
+
|
|
132
|
+
İlk çıkarım gecikmesini ortadan kaldırmak için modelleri
|
|
133
|
+
önceden GPU'ya yükleyin.
|
|
134
|
+
|
|
135
|
+
```python
|
|
136
|
+
client.warmup(["kullanici/model-adi", "diger/model"])
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## Asenkron Kullanım
|
|
140
|
+
|
|
141
|
+
Aynı API, `async/await` ile:
|
|
142
|
+
|
|
143
|
+
```python
|
|
144
|
+
import asyncio
|
|
145
|
+
from evren_sdk import AsyncEvrenClient
|
|
146
|
+
|
|
147
|
+
async def main():
|
|
148
|
+
async with AsyncEvrenClient(api_key="evren_xxxxx") as client:
|
|
149
|
+
result = await client.predict("kullanici/model-adi", "foto.jpg")
|
|
150
|
+
batch = await client.predict_batch(
|
|
151
|
+
"kullanici/model-adi",
|
|
152
|
+
["a.jpg", "b.jpg"],
|
|
153
|
+
)
|
|
154
|
+
print(result.predictions, batch.count)
|
|
155
|
+
|
|
156
|
+
asyncio.run(main())
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## Hata Yönetimi
|
|
160
|
+
|
|
161
|
+
```python
|
|
162
|
+
from evren_sdk import (
|
|
163
|
+
EvrenClient, NotFoundError,
|
|
164
|
+
RateLimitError, InferenceError,
|
|
165
|
+
)
|
|
166
|
+
import time
|
|
167
|
+
|
|
168
|
+
client = EvrenClient(api_key="evren_xxxxx")
|
|
169
|
+
|
|
170
|
+
try:
|
|
171
|
+
result = client.predict("kullanici/model", "test.jpg")
|
|
172
|
+
except NotFoundError:
|
|
173
|
+
print("Model bulunamadı")
|
|
174
|
+
except RateLimitError as e:
|
|
175
|
+
time.sleep(e.retry_after)
|
|
176
|
+
except InferenceError:
|
|
177
|
+
print("GPU sunucusu geçici olarak kullanılamıyor")
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
| Exception | HTTP | Açıklama |
|
|
181
|
+
|---|---|---|
|
|
182
|
+
| `AuthenticationError` | 401, 403 | Geçersiz veya süresi dolmuş anahtar |
|
|
183
|
+
| `NotFoundError` | 404 | Model veya versiyon bulunamadı |
|
|
184
|
+
| `ValidationError` | 422 | Hatalı parametre |
|
|
185
|
+
| `RateLimitError` | 429 | İstek limiti aşıldı |
|
|
186
|
+
| `InferenceError` | 502, 503 | GPU sunucusu hatası |
|
|
187
|
+
|
|
188
|
+
## Veri Modelleri
|
|
189
|
+
|
|
190
|
+
| Sınıf | Temel Alanlar |
|
|
191
|
+
|---|---|
|
|
192
|
+
| `PredictResult` | `predictions`, `inference_ms`, `image_width`, `image_height`, `count` |
|
|
193
|
+
| `Prediction` | `class_name`, `confidence`, `bbox`, `color`, `mask`, `keypoints`, `obb` |
|
|
194
|
+
| `BatchResult` | `results`, `total_ms`, `count` — iterable |
|
|
195
|
+
| `ModelClasses` | `classes`, `architecture`, `model_name`, `imgsz` — `in` operatörü destekler |
|
|
196
|
+
| `ModelInfo` | `id`, `name`, `slug`, `architecture`, `full_slug` |
|
|
197
|
+
| `ModelVersion` | `id`, `version_tag`, `framework`, `metrics` |
|
|
198
|
+
|
|
199
|
+
## Gereksinimler
|
|
200
|
+
|
|
201
|
+
- Python ≥ 3.10
|
|
202
|
+
- httpx ≥ 0.27
|
|
203
|
+
|
|
204
|
+
## Lisans
|
|
205
|
+
|
|
206
|
+
Apache License 2.0
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
# evren-sdk
|
|
2
|
+
|
|
3
|
+
EVREN platformu üzerinde eğitilmiş bilgisayarlı görü modellerine
|
|
4
|
+
Python'dan çıkarım (inference) yapmanızı sağlayan resmi SDK.
|
|
5
|
+
|
|
6
|
+
Desteklenen görevler: **nesne tespiti**, **sınıflandırma**, **segmentasyon**,
|
|
7
|
+
**döndürülmüş kutu (OBB)**, **anahtar nokta (keypoint)**.
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pip install evren-sdk
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Hızlı Başlangıç
|
|
14
|
+
|
|
15
|
+
```python
|
|
16
|
+
from evren_sdk import EvrenClient
|
|
17
|
+
|
|
18
|
+
client = EvrenClient(api_key="evren_xxxxx")
|
|
19
|
+
|
|
20
|
+
result = client.predict("kullanici/model-adi", "foto.jpg", confidence=0.3)
|
|
21
|
+
|
|
22
|
+
for det in result.predictions:
|
|
23
|
+
print(f"{det.class_name}: %{det.confidence:.0%} bbox={det.bbox}")
|
|
24
|
+
|
|
25
|
+
print(f"Çıkarım süresi: {result.inference_ms:.0f} ms")
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Kimlik Doğrulama
|
|
29
|
+
|
|
30
|
+
Platformda **Ayarlar → API Anahtarları** sayfasından anahtar oluşturun.
|
|
31
|
+
Anahtar `evren_` ön eki ile başlar ve `X-API-Key` başlığı üzerinden
|
|
32
|
+
otomatik gönderilir.
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
# API anahtarı ile (önerilen)
|
|
36
|
+
client = EvrenClient(api_key="evren_xxxxx")
|
|
37
|
+
|
|
38
|
+
# JWT token ile de çalışır (Authorization: Bearer)
|
|
39
|
+
client = EvrenClient(api_key="eyJhbGci...")
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Tekil Çıkarım
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
result = client.predict(
|
|
46
|
+
model="kullanici/model-adi", # slug veya UUID
|
|
47
|
+
image="resim.jpg", # dosya yolu, Path veya bytes
|
|
48
|
+
confidence=0.25, # minimum güven eşiği
|
|
49
|
+
iou=0.45, # NMS IoU eşiği
|
|
50
|
+
image_size=640, # giriş boyutu
|
|
51
|
+
classes=["araba", "insan"], # isteğe bağlı sınıf filtresi
|
|
52
|
+
)
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
`model` parametresi üç format kabul eder:
|
|
56
|
+
|
|
57
|
+
| Format | Açıklama |
|
|
58
|
+
|---|---|
|
|
59
|
+
| `"owner/slug"` | Son versiyonu otomatik çözer |
|
|
60
|
+
| `"owner/slug:v2.0"` | Belirli versiyon etiketi |
|
|
61
|
+
| `"019cec..."` (UUID) | Doğrudan versiyon kimliği |
|
|
62
|
+
|
|
63
|
+
## Toplu Çıkarım (Batch)
|
|
64
|
+
|
|
65
|
+
Birden fazla görseli tek istekte GPU batch çıkarımı ile işleyin.
|
|
66
|
+
Tekil çağrılardan önemli ölçüde hızlıdır.
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
batch = client.predict_batch(
|
|
70
|
+
model="kullanici/model-adi",
|
|
71
|
+
images=["img1.jpg", "img2.jpg", "img3.jpg"],
|
|
72
|
+
confidence=0.3,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
for r in batch:
|
|
76
|
+
print(f"{r.count} tespit, {r.inference_ms:.0f} ms")
|
|
77
|
+
|
|
78
|
+
print(f"Toplam: {batch.total_ms:.0f} ms, {batch.count} görsel")
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Model Bilgileri
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
# Sınıf listesi ve renkleri
|
|
85
|
+
info = client.model_classes("kullanici/model-adi")
|
|
86
|
+
print(info.architecture) # "yolo26x"
|
|
87
|
+
for cls in info.classes:
|
|
88
|
+
print(f" {cls.name}: {cls.color}")
|
|
89
|
+
|
|
90
|
+
# Modelleri listele
|
|
91
|
+
for m in client.list_models():
|
|
92
|
+
print(f"{m.full_slug} — {m.architecture}")
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Ön Yükleme (Warmup)
|
|
96
|
+
|
|
97
|
+
İlk çıkarım gecikmesini ortadan kaldırmak için modelleri
|
|
98
|
+
önceden GPU'ya yükleyin.
|
|
99
|
+
|
|
100
|
+
```python
|
|
101
|
+
client.warmup(["kullanici/model-adi", "diger/model"])
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Asenkron Kullanım
|
|
105
|
+
|
|
106
|
+
Aynı API, `async/await` ile:
|
|
107
|
+
|
|
108
|
+
```python
|
|
109
|
+
import asyncio
|
|
110
|
+
from evren_sdk import AsyncEvrenClient
|
|
111
|
+
|
|
112
|
+
async def main():
|
|
113
|
+
async with AsyncEvrenClient(api_key="evren_xxxxx") as client:
|
|
114
|
+
result = await client.predict("kullanici/model-adi", "foto.jpg")
|
|
115
|
+
batch = await client.predict_batch(
|
|
116
|
+
"kullanici/model-adi",
|
|
117
|
+
["a.jpg", "b.jpg"],
|
|
118
|
+
)
|
|
119
|
+
print(result.predictions, batch.count)
|
|
120
|
+
|
|
121
|
+
asyncio.run(main())
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## Hata Yönetimi
|
|
125
|
+
|
|
126
|
+
```python
|
|
127
|
+
from evren_sdk import (
|
|
128
|
+
EvrenClient, NotFoundError,
|
|
129
|
+
RateLimitError, InferenceError,
|
|
130
|
+
)
|
|
131
|
+
import time
|
|
132
|
+
|
|
133
|
+
client = EvrenClient(api_key="evren_xxxxx")
|
|
134
|
+
|
|
135
|
+
try:
|
|
136
|
+
result = client.predict("kullanici/model", "test.jpg")
|
|
137
|
+
except NotFoundError:
|
|
138
|
+
print("Model bulunamadı")
|
|
139
|
+
except RateLimitError as e:
|
|
140
|
+
time.sleep(e.retry_after)
|
|
141
|
+
except InferenceError:
|
|
142
|
+
print("GPU sunucusu geçici olarak kullanılamıyor")
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
| Exception | HTTP | Açıklama |
|
|
146
|
+
|---|---|---|
|
|
147
|
+
| `AuthenticationError` | 401, 403 | Geçersiz veya süresi dolmuş anahtar |
|
|
148
|
+
| `NotFoundError` | 404 | Model veya versiyon bulunamadı |
|
|
149
|
+
| `ValidationError` | 422 | Hatalı parametre |
|
|
150
|
+
| `RateLimitError` | 429 | İstek limiti aşıldı |
|
|
151
|
+
| `InferenceError` | 502, 503 | GPU sunucusu hatası |
|
|
152
|
+
|
|
153
|
+
## Veri Modelleri
|
|
154
|
+
|
|
155
|
+
| Sınıf | Temel Alanlar |
|
|
156
|
+
|---|---|
|
|
157
|
+
| `PredictResult` | `predictions`, `inference_ms`, `image_width`, `image_height`, `count` |
|
|
158
|
+
| `Prediction` | `class_name`, `confidence`, `bbox`, `color`, `mask`, `keypoints`, `obb` |
|
|
159
|
+
| `BatchResult` | `results`, `total_ms`, `count` — iterable |
|
|
160
|
+
| `ModelClasses` | `classes`, `architecture`, `model_name`, `imgsz` — `in` operatörü destekler |
|
|
161
|
+
| `ModelInfo` | `id`, `name`, `slug`, `architecture`, `full_slug` |
|
|
162
|
+
| `ModelVersion` | `id`, `version_tag`, `framework`, `metrics` |
|
|
163
|
+
|
|
164
|
+
## Gereksinimler
|
|
165
|
+
|
|
166
|
+
- Python ≥ 3.10
|
|
167
|
+
- httpx ≥ 0.27
|
|
168
|
+
|
|
169
|
+
## Lisans
|
|
170
|
+
|
|
171
|
+
Apache License 2.0
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""EVREN Python SDK — nesne tespiti ve goruntu cikarim istemcisi."""
|
|
2
|
+
|
|
3
|
+
from .client import AsyncEvrenClient, EvrenClient
|
|
4
|
+
from .exceptions import (
|
|
5
|
+
AuthenticationError,
|
|
6
|
+
EvrenError,
|
|
7
|
+
InferenceError,
|
|
8
|
+
NotFoundError,
|
|
9
|
+
RateLimitError,
|
|
10
|
+
ValidationError,
|
|
11
|
+
)
|
|
12
|
+
from .models import (
|
|
13
|
+
BatchResult,
|
|
14
|
+
ClassInfo,
|
|
15
|
+
ModelClasses,
|
|
16
|
+
ModelInfo,
|
|
17
|
+
ModelVersion,
|
|
18
|
+
Prediction,
|
|
19
|
+
PredictResult,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
__version__ = "0.3.0"
|
|
23
|
+
|
|
24
|
+
__all__ = [
|
|
25
|
+
"EvrenClient",
|
|
26
|
+
"AsyncEvrenClient",
|
|
27
|
+
"EvrenError",
|
|
28
|
+
"AuthenticationError",
|
|
29
|
+
"NotFoundError",
|
|
30
|
+
"RateLimitError",
|
|
31
|
+
"InferenceError",
|
|
32
|
+
"ValidationError",
|
|
33
|
+
"Prediction",
|
|
34
|
+
"PredictResult",
|
|
35
|
+
"BatchResult",
|
|
36
|
+
"ClassInfo",
|
|
37
|
+
"ModelClasses",
|
|
38
|
+
"ModelInfo",
|
|
39
|
+
"ModelVersion",
|
|
40
|
+
]
|
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import io
|
|
4
|
+
import uuid
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
import httpx
|
|
9
|
+
|
|
10
|
+
from .exceptions import (
|
|
11
|
+
AuthenticationError,
|
|
12
|
+
InferenceError,
|
|
13
|
+
NotFoundError,
|
|
14
|
+
RateLimitError,
|
|
15
|
+
ValidationError,
|
|
16
|
+
)
|
|
17
|
+
from .models import (
|
|
18
|
+
BatchResult,
|
|
19
|
+
ClassInfo,
|
|
20
|
+
ModelClasses,
|
|
21
|
+
ModelInfo,
|
|
22
|
+
ModelVersion,
|
|
23
|
+
Prediction,
|
|
24
|
+
PredictResult,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
_API_BASE = "https://api.ssyz.org.tr/api/v1"
|
|
28
|
+
_BATCH_TIMEOUT = 120.0
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _auth_headers(key: str) -> dict[str, str]:
|
|
32
|
+
if key.startswith("evren_"):
|
|
33
|
+
return {"X-API-Key": key}
|
|
34
|
+
return {"Authorization": f"Bearer {key}"}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _raise_for(resp: httpx.Response) -> None:
|
|
38
|
+
code = resp.status_code
|
|
39
|
+
try:
|
|
40
|
+
msg = resp.json().get("detail", resp.text[:300])
|
|
41
|
+
except Exception:
|
|
42
|
+
msg = resp.text[:300]
|
|
43
|
+
|
|
44
|
+
if code in (401, 403):
|
|
45
|
+
raise AuthenticationError(msg, code)
|
|
46
|
+
if code == 404:
|
|
47
|
+
raise NotFoundError(msg, code)
|
|
48
|
+
if code == 422:
|
|
49
|
+
raise ValidationError(msg, code)
|
|
50
|
+
if code == 429:
|
|
51
|
+
wait = int(resp.headers.get("Retry-After", "5"))
|
|
52
|
+
raise RateLimitError(msg, wait)
|
|
53
|
+
if code >= 500:
|
|
54
|
+
raise InferenceError(msg, code)
|
|
55
|
+
resp.raise_for_status()
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _read_img(source: str | Path | bytes) -> tuple[bytes, str]:
|
|
59
|
+
if isinstance(source, bytes):
|
|
60
|
+
return source, "image.jpg"
|
|
61
|
+
p = Path(source)
|
|
62
|
+
return p.read_bytes(), p.name
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _is_uuid(val: str) -> bool:
|
|
66
|
+
try:
|
|
67
|
+
uuid.UUID(val)
|
|
68
|
+
return True
|
|
69
|
+
except (ValueError, AttributeError):
|
|
70
|
+
return False
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _unwrap(data: Any) -> Any:
|
|
74
|
+
if isinstance(data, dict) and "data" in data:
|
|
75
|
+
return data["data"]
|
|
76
|
+
return data
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _build_form(version_id: str, confidence: float,
|
|
80
|
+
iou: float, imgsz: int,
|
|
81
|
+
classes: list[str] | None) -> dict[str, str]:
|
|
82
|
+
d: dict[str, str] = {
|
|
83
|
+
"model_version_id": version_id,
|
|
84
|
+
"confidence_threshold": str(confidence),
|
|
85
|
+
"iou_threshold": str(iou),
|
|
86
|
+
"image_size": str(imgsz),
|
|
87
|
+
}
|
|
88
|
+
if classes:
|
|
89
|
+
d["classes"] = ",".join(classes)
|
|
90
|
+
return d
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _to_prediction(raw: dict) -> Prediction:
|
|
94
|
+
return Prediction(
|
|
95
|
+
class_name=raw.get("class_name", ""),
|
|
96
|
+
confidence=raw.get("confidence", 0.0),
|
|
97
|
+
bbox=raw.get("bbox", []),
|
|
98
|
+
color=raw.get("color"),
|
|
99
|
+
keypoints=raw.get("keypoints"),
|
|
100
|
+
mask=raw.get("mask"),
|
|
101
|
+
obb=raw.get("obb"),
|
|
102
|
+
task=raw.get("task"),
|
|
103
|
+
probs=raw.get("probs"),
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _to_result(body: dict) -> PredictResult:
|
|
108
|
+
return PredictResult(
|
|
109
|
+
predictions=[_to_prediction(p) for p in body.get("predictions", [])],
|
|
110
|
+
inference_ms=body.get("inference_ms", 0),
|
|
111
|
+
image_width=body.get("image_width"),
|
|
112
|
+
image_height=body.get("image_height"),
|
|
113
|
+
model_version_id=body.get("model_version_id"),
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _to_model_info(m: dict) -> ModelInfo:
|
|
118
|
+
return ModelInfo(
|
|
119
|
+
id=m["id"], name=m["name"], slug=m.get("slug", ""),
|
|
120
|
+
architecture=m.get("architecture"),
|
|
121
|
+
owner_username=m.get("owner_username"),
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _to_model_ver(v: dict) -> ModelVersion:
|
|
126
|
+
return ModelVersion(
|
|
127
|
+
id=v["id"], version_tag=v.get("version_tag", ""),
|
|
128
|
+
weights_url=v.get("weights_url"),
|
|
129
|
+
framework=v.get("framework", "pytorch"),
|
|
130
|
+
metrics=v.get("metrics", {}),
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _resolve_versions(body: Any) -> list[dict]:
|
|
135
|
+
data = _unwrap(body)
|
|
136
|
+
if isinstance(data, list):
|
|
137
|
+
return data
|
|
138
|
+
return data.get("items", data.get("data", []))
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def _pick_version(versions: list[dict], tag: str | None, slug: str) -> str:
|
|
142
|
+
if not versions:
|
|
143
|
+
raise NotFoundError(f"'{slug}' icin versiyon yok", 404)
|
|
144
|
+
|
|
145
|
+
if tag is None:
|
|
146
|
+
return versions[0]["id"]
|
|
147
|
+
|
|
148
|
+
for v in versions:
|
|
149
|
+
if v.get("version_tag") == tag:
|
|
150
|
+
return v["id"]
|
|
151
|
+
raise NotFoundError(f"'{slug}' icin '{tag}' versiyonu yok", 404)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
# ---------------------------------------------------------------
|
|
155
|
+
# sync
|
|
156
|
+
# ---------------------------------------------------------------
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
class EvrenClient:
|
|
160
|
+
"""EVREN inference API senkron istemcisi.
|
|
161
|
+
|
|
162
|
+
>>> with EvrenClient(api_key="evren_xxx") as c:
|
|
163
|
+
... r = c.predict("owner/slug", "foto.jpg")
|
|
164
|
+
... print(r.predictions)
|
|
165
|
+
"""
|
|
166
|
+
|
|
167
|
+
def __init__(self, api_key: str, *,
|
|
168
|
+
base_url: str = _API_BASE,
|
|
169
|
+
timeout: float = 60.0) -> None:
|
|
170
|
+
self._http = httpx.Client(
|
|
171
|
+
base_url=base_url.rstrip("/"),
|
|
172
|
+
headers=_auth_headers(api_key),
|
|
173
|
+
timeout=timeout,
|
|
174
|
+
)
|
|
175
|
+
self._vcache: dict[str, str] = {}
|
|
176
|
+
|
|
177
|
+
def resolve(self, slug: str) -> str:
|
|
178
|
+
"""``owner/slug`` veya ``owner/slug:tag`` -> version UUID."""
|
|
179
|
+
if slug in self._vcache:
|
|
180
|
+
return self._vcache[slug]
|
|
181
|
+
|
|
182
|
+
slug_part, _, tag = slug.partition(":")
|
|
183
|
+
tag = tag or None
|
|
184
|
+
|
|
185
|
+
r = self._http.get(f"/models/{slug_part}")
|
|
186
|
+
if r.status_code != 200:
|
|
187
|
+
_raise_for(r)
|
|
188
|
+
mid = _unwrap(r.json())["id"]
|
|
189
|
+
|
|
190
|
+
r2 = self._http.get(f"/models/{mid}/versions")
|
|
191
|
+
if r2.status_code != 200:
|
|
192
|
+
_raise_for(r2)
|
|
193
|
+
|
|
194
|
+
vid = _pick_version(_resolve_versions(r2.json()), tag, slug)
|
|
195
|
+
self._vcache[slug] = vid
|
|
196
|
+
return vid
|
|
197
|
+
|
|
198
|
+
def _vid(self, model: str) -> str:
|
|
199
|
+
return model if _is_uuid(model) else self.resolve(model)
|
|
200
|
+
|
|
201
|
+
def predict(self, model: str, image: str | Path | bytes, *,
|
|
202
|
+
confidence: float = 0.25, iou: float = 0.45,
|
|
203
|
+
image_size: int = 640,
|
|
204
|
+
classes: list[str] | None = None) -> PredictResult:
|
|
205
|
+
raw, name = _read_img(image)
|
|
206
|
+
r = self._http.post(
|
|
207
|
+
"/inference/predict",
|
|
208
|
+
data=_build_form(self._vid(model), confidence, iou, image_size, classes),
|
|
209
|
+
files={"file": (name, io.BytesIO(raw), "image/jpeg")},
|
|
210
|
+
)
|
|
211
|
+
if r.status_code != 200:
|
|
212
|
+
_raise_for(r)
|
|
213
|
+
return _to_result(_unwrap(r.json()))
|
|
214
|
+
|
|
215
|
+
def predict_batch(self, model: str, images: list[str | Path | bytes], *,
|
|
216
|
+
confidence: float = 0.25, iou: float = 0.45,
|
|
217
|
+
image_size: int = 640,
|
|
218
|
+
classes: list[str] | None = None) -> BatchResult:
|
|
219
|
+
vid = self._vid(model)
|
|
220
|
+
parts = []
|
|
221
|
+
for i, img in enumerate(images):
|
|
222
|
+
raw, fname = _read_img(img)
|
|
223
|
+
n = fname if fname != "image.jpg" else f"img_{i}.jpg"
|
|
224
|
+
parts.append(("files", (n, io.BytesIO(raw), "image/jpeg")))
|
|
225
|
+
|
|
226
|
+
r = self._http.post(
|
|
227
|
+
"/inference/predict/batch",
|
|
228
|
+
data=_build_form(vid, confidence, iou, image_size, classes),
|
|
229
|
+
files=parts, timeout=_BATCH_TIMEOUT,
|
|
230
|
+
)
|
|
231
|
+
if r.status_code != 200:
|
|
232
|
+
_raise_for(r)
|
|
233
|
+
|
|
234
|
+
body = _unwrap(r.json())
|
|
235
|
+
items = [_to_result(x) for x in body.get("results", [])]
|
|
236
|
+
return BatchResult(results=items,
|
|
237
|
+
total_ms=body.get("total_ms", 0),
|
|
238
|
+
count=body.get("count", len(items)))
|
|
239
|
+
|
|
240
|
+
def model_classes(self, model: str) -> ModelClasses:
|
|
241
|
+
vid = self._vid(model)
|
|
242
|
+
r = self._http.get(f"/inference/model-classes/{vid}")
|
|
243
|
+
if r.status_code != 200:
|
|
244
|
+
_raise_for(r)
|
|
245
|
+
b = _unwrap(r.json())
|
|
246
|
+
return ModelClasses(
|
|
247
|
+
model_version_id=b.get("model_version_id", vid),
|
|
248
|
+
classes=[ClassInfo(name=c["name"], color=c["color"])
|
|
249
|
+
for c in b.get("classes", [])],
|
|
250
|
+
model_name=b.get("model_name"),
|
|
251
|
+
architecture=b.get("architecture"),
|
|
252
|
+
total=b.get("total", 0),
|
|
253
|
+
imgsz=b.get("imgsz", 640),
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
def warmup(self, models: list[str]) -> dict:
|
|
257
|
+
ids = [self._vid(m) for m in models]
|
|
258
|
+
r = self._http.post("/inference/warmup", json=ids,
|
|
259
|
+
timeout=_BATCH_TIMEOUT)
|
|
260
|
+
if r.status_code != 200:
|
|
261
|
+
_raise_for(r)
|
|
262
|
+
return _unwrap(r.json())
|
|
263
|
+
|
|
264
|
+
def list_models(self, limit: int = 50) -> list[ModelInfo]:
|
|
265
|
+
r = self._http.get("/models",
|
|
266
|
+
params={"limit": limit, "include_public": "true"})
|
|
267
|
+
if r.status_code != 200:
|
|
268
|
+
_raise_for(r)
|
|
269
|
+
body = _unwrap(r.json())
|
|
270
|
+
rows = body if isinstance(body, list) else body.get("items", [])
|
|
271
|
+
return [_to_model_info(m) for m in rows]
|
|
272
|
+
|
|
273
|
+
def list_versions(self, model_id: str) -> list[ModelVersion]:
|
|
274
|
+
r = self._http.get(f"/models/{model_id}/versions")
|
|
275
|
+
if r.status_code != 200:
|
|
276
|
+
_raise_for(r)
|
|
277
|
+
return [_to_model_ver(v) for v in _resolve_versions(r.json())]
|
|
278
|
+
|
|
279
|
+
def close(self) -> None:
|
|
280
|
+
self._http.close()
|
|
281
|
+
|
|
282
|
+
def __enter__(self) -> EvrenClient:
|
|
283
|
+
return self
|
|
284
|
+
|
|
285
|
+
def __exit__(self, *exc: Any) -> None:
|
|
286
|
+
self.close()
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
# ---------------------------------------------------------------
|
|
290
|
+
# async
|
|
291
|
+
# ---------------------------------------------------------------
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
class AsyncEvrenClient:
|
|
295
|
+
"""EVREN inference API asenkron istemcisi.
|
|
296
|
+
|
|
297
|
+
>>> async with AsyncEvrenClient(api_key="evren_xxx") as c:
|
|
298
|
+
... r = await c.predict("owner/slug", "foto.jpg")
|
|
299
|
+
"""
|
|
300
|
+
|
|
301
|
+
def __init__(self, api_key: str, *,
|
|
302
|
+
base_url: str = _API_BASE,
|
|
303
|
+
timeout: float = 60.0) -> None:
|
|
304
|
+
self._http = httpx.AsyncClient(
|
|
305
|
+
base_url=base_url.rstrip("/"),
|
|
306
|
+
headers=_auth_headers(api_key),
|
|
307
|
+
timeout=timeout,
|
|
308
|
+
)
|
|
309
|
+
self._vcache: dict[str, str] = {}
|
|
310
|
+
|
|
311
|
+
async def resolve(self, slug: str) -> str:
|
|
312
|
+
if slug in self._vcache:
|
|
313
|
+
return self._vcache[slug]
|
|
314
|
+
|
|
315
|
+
slug_part, _, tag = slug.partition(":")
|
|
316
|
+
tag = tag or None
|
|
317
|
+
|
|
318
|
+
r = await self._http.get(f"/models/{slug_part}")
|
|
319
|
+
if r.status_code != 200:
|
|
320
|
+
_raise_for(r)
|
|
321
|
+
mid = _unwrap(r.json())["id"]
|
|
322
|
+
|
|
323
|
+
r2 = await self._http.get(f"/models/{mid}/versions")
|
|
324
|
+
if r2.status_code != 200:
|
|
325
|
+
_raise_for(r2)
|
|
326
|
+
|
|
327
|
+
vid = _pick_version(_resolve_versions(r2.json()), tag, slug)
|
|
328
|
+
self._vcache[slug] = vid
|
|
329
|
+
return vid
|
|
330
|
+
|
|
331
|
+
async def _vid(self, model: str) -> str:
|
|
332
|
+
return model if _is_uuid(model) else await self.resolve(model)
|
|
333
|
+
|
|
334
|
+
async def predict(self, model: str, image: str | Path | bytes, *,
|
|
335
|
+
confidence: float = 0.25, iou: float = 0.45,
|
|
336
|
+
image_size: int = 640,
|
|
337
|
+
classes: list[str] | None = None) -> PredictResult:
|
|
338
|
+
raw, name = _read_img(image)
|
|
339
|
+
r = await self._http.post(
|
|
340
|
+
"/inference/predict",
|
|
341
|
+
data=_build_form(await self._vid(model), confidence, iou, image_size, classes),
|
|
342
|
+
files={"file": (name, io.BytesIO(raw), "image/jpeg")},
|
|
343
|
+
)
|
|
344
|
+
if r.status_code != 200:
|
|
345
|
+
_raise_for(r)
|
|
346
|
+
return _to_result(_unwrap(r.json()))
|
|
347
|
+
|
|
348
|
+
async def predict_batch(self, model: str, images: list[str | Path | bytes], *,
|
|
349
|
+
confidence: float = 0.25, iou: float = 0.45,
|
|
350
|
+
image_size: int = 640,
|
|
351
|
+
classes: list[str] | None = None) -> BatchResult:
|
|
352
|
+
vid = await self._vid(model)
|
|
353
|
+
parts = []
|
|
354
|
+
for i, img in enumerate(images):
|
|
355
|
+
raw, fname = _read_img(img)
|
|
356
|
+
n = fname if fname != "image.jpg" else f"img_{i}.jpg"
|
|
357
|
+
parts.append(("files", (n, io.BytesIO(raw), "image/jpeg")))
|
|
358
|
+
|
|
359
|
+
r = await self._http.post(
|
|
360
|
+
"/inference/predict/batch",
|
|
361
|
+
data=_build_form(vid, confidence, iou, image_size, classes),
|
|
362
|
+
files=parts, timeout=_BATCH_TIMEOUT,
|
|
363
|
+
)
|
|
364
|
+
if r.status_code != 200:
|
|
365
|
+
_raise_for(r)
|
|
366
|
+
body = _unwrap(r.json())
|
|
367
|
+
items = [_to_result(x) for x in body.get("results", [])]
|
|
368
|
+
return BatchResult(results=items,
|
|
369
|
+
total_ms=body.get("total_ms", 0),
|
|
370
|
+
count=body.get("count", len(items)))
|
|
371
|
+
|
|
372
|
+
async def model_classes(self, model: str) -> ModelClasses:
|
|
373
|
+
vid = await self._vid(model)
|
|
374
|
+
r = await self._http.get(f"/inference/model-classes/{vid}")
|
|
375
|
+
if r.status_code != 200:
|
|
376
|
+
_raise_for(r)
|
|
377
|
+
b = _unwrap(r.json())
|
|
378
|
+
return ModelClasses(
|
|
379
|
+
model_version_id=b.get("model_version_id", vid),
|
|
380
|
+
classes=[ClassInfo(name=c["name"], color=c["color"])
|
|
381
|
+
for c in b.get("classes", [])],
|
|
382
|
+
model_name=b.get("model_name"),
|
|
383
|
+
architecture=b.get("architecture"),
|
|
384
|
+
total=b.get("total", 0),
|
|
385
|
+
imgsz=b.get("imgsz", 640),
|
|
386
|
+
)
|
|
387
|
+
|
|
388
|
+
async def warmup(self, models: list[str]) -> dict:
|
|
389
|
+
ids = [await self._vid(m) for m in models]
|
|
390
|
+
r = await self._http.post("/inference/warmup", json=ids,
|
|
391
|
+
timeout=_BATCH_TIMEOUT)
|
|
392
|
+
if r.status_code != 200:
|
|
393
|
+
_raise_for(r)
|
|
394
|
+
return _unwrap(r.json())
|
|
395
|
+
|
|
396
|
+
async def list_models(self, limit: int = 50) -> list[ModelInfo]:
|
|
397
|
+
r = await self._http.get("/models",
|
|
398
|
+
params={"limit": limit, "include_public": "true"})
|
|
399
|
+
if r.status_code != 200:
|
|
400
|
+
_raise_for(r)
|
|
401
|
+
body = _unwrap(r.json())
|
|
402
|
+
rows = body if isinstance(body, list) else body.get("items", [])
|
|
403
|
+
return [_to_model_info(m) for m in rows]
|
|
404
|
+
|
|
405
|
+
async def close(self) -> None:
|
|
406
|
+
await self._http.aclose()
|
|
407
|
+
|
|
408
|
+
async def __aenter__(self) -> AsyncEvrenClient:
|
|
409
|
+
return self
|
|
410
|
+
|
|
411
|
+
async def __aexit__(self, *exc: Any) -> None:
|
|
412
|
+
await self.close()
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class EvrenError(Exception):
|
|
5
|
+
|
|
6
|
+
def __init__(self, message: str, status_code: int | None = None) -> None:
|
|
7
|
+
self.status_code = status_code
|
|
8
|
+
super().__init__(message)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class AuthenticationError(EvrenError):
|
|
12
|
+
"""Gecersiz veya suresi dolmus API anahtari (HTTP 401/403)."""
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class NotFoundError(EvrenError):
|
|
16
|
+
"""Belirtilen model veya versiyon bulunamadi (HTTP 404)."""
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class RateLimitError(EvrenError):
|
|
20
|
+
"""Istek limiti asildi (HTTP 429).
|
|
21
|
+
|
|
22
|
+
``retry_after`` saniye bekleyip tekrar deneyin.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def __init__(self, message: str, retry_after: int = 5) -> None:
|
|
26
|
+
self.retry_after = retry_after
|
|
27
|
+
super().__init__(message, 429)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class InferenceError(EvrenError):
|
|
31
|
+
"""GPU cikarim sunucusu gecici olarak kullanilamiyor (HTTP 502/503)."""
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class ValidationError(EvrenError):
|
|
35
|
+
"""Istek parametrelerinde dogrulama hatasi (HTTP 422)."""
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@dataclass(slots=True)
|
|
7
|
+
class Prediction:
|
|
8
|
+
class_name: str = ""
|
|
9
|
+
confidence: float = 0.0
|
|
10
|
+
bbox: list[float] = field(default_factory=list)
|
|
11
|
+
color: str | None = None
|
|
12
|
+
keypoints: list[list[float]] | None = None
|
|
13
|
+
mask: list[list[float]] | None = None
|
|
14
|
+
obb: dict | None = None
|
|
15
|
+
task: str | None = None
|
|
16
|
+
probs: list[dict] | None = None
|
|
17
|
+
|
|
18
|
+
def __repr__(self) -> str:
|
|
19
|
+
return f"<Prediction {self.class_name} conf={self.confidence:.3f}>"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass(slots=True)
|
|
23
|
+
class PredictResult:
|
|
24
|
+
predictions: list[Prediction] = field(default_factory=list)
|
|
25
|
+
inference_ms: float = 0.0
|
|
26
|
+
image_width: int | None = None
|
|
27
|
+
image_height: int | None = None
|
|
28
|
+
model_version_id: str | None = None
|
|
29
|
+
|
|
30
|
+
@property
|
|
31
|
+
def count(self) -> int:
|
|
32
|
+
return len(self.predictions)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass(slots=True)
|
|
36
|
+
class BatchResult:
|
|
37
|
+
results: list[PredictResult] = field(default_factory=list)
|
|
38
|
+
total_ms: float = 0.0
|
|
39
|
+
count: int = 0
|
|
40
|
+
|
|
41
|
+
def __iter__(self):
|
|
42
|
+
return iter(self.results)
|
|
43
|
+
|
|
44
|
+
def __len__(self) -> int:
|
|
45
|
+
return self.count
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@dataclass(slots=True, frozen=True)
|
|
49
|
+
class ClassInfo:
|
|
50
|
+
name: str
|
|
51
|
+
color: str
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@dataclass(slots=True)
|
|
55
|
+
class ModelClasses:
|
|
56
|
+
model_version_id: str
|
|
57
|
+
classes: list[ClassInfo] = field(default_factory=list)
|
|
58
|
+
model_name: str | None = None
|
|
59
|
+
architecture: str | None = None
|
|
60
|
+
total: int = 0
|
|
61
|
+
imgsz: int = 640
|
|
62
|
+
|
|
63
|
+
def __contains__(self, name: str) -> bool:
|
|
64
|
+
return any(c.name == name for c in self.classes)
|
|
65
|
+
|
|
66
|
+
def names(self) -> list[str]:
|
|
67
|
+
return [c.name for c in self.classes]
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@dataclass(slots=True)
|
|
71
|
+
class ModelInfo:
|
|
72
|
+
id: str
|
|
73
|
+
name: str
|
|
74
|
+
slug: str
|
|
75
|
+
architecture: str | None = None
|
|
76
|
+
owner_username: str | None = None
|
|
77
|
+
|
|
78
|
+
@property
|
|
79
|
+
def full_slug(self) -> str:
|
|
80
|
+
if self.owner_username:
|
|
81
|
+
return f"{self.owner_username}/{self.slug}"
|
|
82
|
+
return self.slug
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@dataclass(slots=True)
|
|
86
|
+
class ModelVersion:
|
|
87
|
+
id: str
|
|
88
|
+
version_tag: str
|
|
89
|
+
weights_url: str | None = None
|
|
90
|
+
framework: str = "pytorch"
|
|
91
|
+
metrics: dict = field(default_factory=dict)
|
|
File without changes
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "evren-sdk"
|
|
7
|
+
version = "0.3.0"
|
|
8
|
+
description = "EVREN MLOps Platform — Python inference SDK for object detection, classification, and segmentation models."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = "Apache-2.0"
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "Serkan Peker", email = "serkan.peker@crudfab.com" },
|
|
14
|
+
]
|
|
15
|
+
maintainers = [
|
|
16
|
+
{ name = "Serkan Peker", email = "serkan.peker@crudfab.com" },
|
|
17
|
+
]
|
|
18
|
+
keywords = [
|
|
19
|
+
"evren", "mlops", "inference", "object-detection",
|
|
20
|
+
"yolo", "computer-vision", "sdk", "deep-learning",
|
|
21
|
+
]
|
|
22
|
+
classifiers = [
|
|
23
|
+
"Development Status :: 4 - Beta",
|
|
24
|
+
"Intended Audience :: Developers",
|
|
25
|
+
"Intended Audience :: Science/Research",
|
|
26
|
+
"License :: OSI Approved :: Apache Software License",
|
|
27
|
+
"Operating System :: OS Independent",
|
|
28
|
+
"Programming Language :: Python :: 3",
|
|
29
|
+
"Programming Language :: Python :: 3.10",
|
|
30
|
+
"Programming Language :: Python :: 3.11",
|
|
31
|
+
"Programming Language :: Python :: 3.12",
|
|
32
|
+
"Programming Language :: Python :: 3.13",
|
|
33
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
34
|
+
"Topic :: Scientific/Engineering :: Image Recognition",
|
|
35
|
+
"Typing :: Typed",
|
|
36
|
+
]
|
|
37
|
+
dependencies = [
|
|
38
|
+
"httpx>=0.27",
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
[project.optional-dependencies]
|
|
42
|
+
dev = ["pytest", "pytest-asyncio", "ruff", "mypy"]
|
|
43
|
+
|
|
44
|
+
[project.urls]
|
|
45
|
+
Homepage = "https://evren.ssyz.org.tr"
|
|
46
|
+
Documentation = "https://docs.ssyz.org.tr/sdk"
|
|
47
|
+
Repository = "https://gitlab.crudfab.com/ssb/ssyz/evren/platform"
|
|
48
|
+
Changelog = "https://gitlab.crudfab.com/ssb/ssyz/evren/platform/-/blob/main/sdk/CHANGELOG.md"
|
|
49
|
+
Issues = "https://gitlab.crudfab.com/ssb/ssyz/evren/platform/-/issues"
|
|
50
|
+
|
|
51
|
+
[tool.hatch.build.targets.wheel]
|
|
52
|
+
packages = ["evren_sdk"]
|
|
53
|
+
|
|
54
|
+
[tool.ruff]
|
|
55
|
+
target-version = "py310"
|
|
56
|
+
line-length = 100
|
|
57
|
+
|
|
58
|
+
[tool.mypy]
|
|
59
|
+
python_version = "3.10"
|
|
60
|
+
strict = true
|