dragoneye-python 0.4.1__tar.gz → 1.0.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.
- {dragoneye_python-0.4.1 → dragoneye_python-1.0.0}/PKG-INFO +2 -1
- {dragoneye_python-0.4.1 → dragoneye_python-1.0.0}/dragoneye_python.egg-info/PKG-INFO +2 -1
- {dragoneye_python-0.4.1 → dragoneye_python-1.0.0}/dragoneye_python.egg-info/SOURCES.txt +1 -0
- dragoneye_python-0.4.1/requirements.txt → dragoneye_python-1.0.0/dragoneye_python.egg-info/requires.txt +2 -1
- {dragoneye_python-0.4.1 → dragoneye_python-1.0.0}/pyproject.toml +1 -1
- dragoneye_python-0.4.1/dragoneye_python.egg-info/requires.txt → dragoneye_python-1.0.0/requirements.txt +1 -0
- dragoneye_python-1.0.0/src/dragoneye/__init__.py +44 -0
- {dragoneye_python-0.4.1 → dragoneye_python-1.0.0}/src/dragoneye/classification.py +57 -15
- {dragoneye_python-0.4.1 → dragoneye_python-1.0.0}/src/dragoneye/models.py +26 -11
- dragoneye_python-1.0.0/src/dragoneye/parquet_deserializer.py +136 -0
- dragoneye_python-1.0.0/src/dragoneye/types/common.py +12 -0
- {dragoneye_python-0.4.1 → dragoneye_python-1.0.0}/src/dragoneye/types/media.py +4 -4
- dragoneye_python-0.4.1/src/dragoneye/__init__.py +0 -25
- dragoneye_python-0.4.1/src/dragoneye/types/common.py +0 -31
- {dragoneye_python-0.4.1 → dragoneye_python-1.0.0}/README.md +0 -0
- {dragoneye_python-0.4.1 → dragoneye_python-1.0.0}/dragoneye_python.egg-info/dependency_links.txt +0 -0
- {dragoneye_python-0.4.1 → dragoneye_python-1.0.0}/dragoneye_python.egg-info/top_level.txt +0 -0
- {dragoneye_python-0.4.1 → dragoneye_python-1.0.0}/setup.cfg +0 -0
- {dragoneye_python-0.4.1 → dragoneye_python-1.0.0}/src/dragoneye/client.py +0 -0
- {dragoneye_python-0.4.1 → dragoneye_python-1.0.0}/src/dragoneye/constants.py +0 -0
- {dragoneye_python-0.4.1 → dragoneye_python-1.0.0}/src/dragoneye/types/__init__.py +0 -0
- {dragoneye_python-0.4.1 → dragoneye_python-1.0.0}/src/dragoneye/types/exception.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dragoneye-python
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 1.0.0
|
|
4
4
|
License-Expression: MIT
|
|
5
5
|
Requires-Python: >=3.8
|
|
6
6
|
Requires-Dist: requests
|
|
@@ -8,3 +8,4 @@ Requires-Dist: pydantic>=2
|
|
|
8
8
|
Requires-Dist: typing-extensions>=4.0.0
|
|
9
9
|
Requires-Dist: backoff>=2.0.0
|
|
10
10
|
Requires-Dist: aiohttp
|
|
11
|
+
Requires-Dist: polars>=1.0.0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dragoneye-python
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 1.0.0
|
|
4
4
|
License-Expression: MIT
|
|
5
5
|
Requires-Python: >=3.8
|
|
6
6
|
Requires-Dist: requests
|
|
@@ -8,3 +8,4 @@ Requires-Dist: pydantic>=2
|
|
|
8
8
|
Requires-Dist: typing-extensions>=4.0.0
|
|
9
9
|
Requires-Dist: backoff>=2.0.0
|
|
10
10
|
Requires-Dist: aiohttp
|
|
11
|
+
Requires-Dist: polars>=1.0.0
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
from .classification import (
|
|
2
|
+
Classification,
|
|
3
|
+
)
|
|
4
|
+
from .client import Dragoneye
|
|
5
|
+
from .models import (
|
|
6
|
+
ClassificationAttributeOption,
|
|
7
|
+
ClassificationAttributeResponse,
|
|
8
|
+
ClassificationCategory,
|
|
9
|
+
ClassificationCategoryPrediction,
|
|
10
|
+
ClassificationObjectPrediction,
|
|
11
|
+
ClassificationPredictImageResponse,
|
|
12
|
+
ClassificationPredictVideoResponse,
|
|
13
|
+
ClassificationVideoObjectPrediction,
|
|
14
|
+
)
|
|
15
|
+
from .types.common import NormalizedBbox
|
|
16
|
+
from .types.exception import (
|
|
17
|
+
PredictionTaskBeginError,
|
|
18
|
+
PredictionTaskError,
|
|
19
|
+
PredictionTaskResultsUnavailableError,
|
|
20
|
+
PredictionTimeoutException,
|
|
21
|
+
PredictionUploadError,
|
|
22
|
+
)
|
|
23
|
+
from .types.media import Image, Video
|
|
24
|
+
|
|
25
|
+
__all__ = [
|
|
26
|
+
"Classification",
|
|
27
|
+
"ClassificationAttributeOption",
|
|
28
|
+
"ClassificationAttributeResponse",
|
|
29
|
+
"ClassificationCategory",
|
|
30
|
+
"ClassificationCategoryPrediction",
|
|
31
|
+
"ClassificationObjectPrediction",
|
|
32
|
+
"ClassificationPredictImageResponse",
|
|
33
|
+
"ClassificationPredictVideoResponse",
|
|
34
|
+
"ClassificationVideoObjectPrediction",
|
|
35
|
+
"Dragoneye",
|
|
36
|
+
"Image",
|
|
37
|
+
"NormalizedBbox",
|
|
38
|
+
"PredictionTaskBeginError",
|
|
39
|
+
"PredictionTaskError",
|
|
40
|
+
"PredictionTaskResultsUnavailableError",
|
|
41
|
+
"PredictionTimeoutException",
|
|
42
|
+
"PredictionUploadError",
|
|
43
|
+
"Video",
|
|
44
|
+
]
|
|
@@ -2,10 +2,12 @@ import asyncio
|
|
|
2
2
|
import logging
|
|
3
3
|
import time
|
|
4
4
|
from typing import TYPE_CHECKING, Any, Dict, List, Literal, Optional, Union, overload
|
|
5
|
+
from urllib.parse import urlencode
|
|
5
6
|
|
|
6
7
|
import aiohttp
|
|
7
8
|
import backoff
|
|
8
9
|
from aiohttp import ClientError
|
|
10
|
+
from multidict import CIMultiDict, CIMultiDictProxy
|
|
9
11
|
from pydantic import BaseModel
|
|
10
12
|
|
|
11
13
|
from .constants import FAILED_STATUS_PREFIX, PREDICTED_STATUS
|
|
@@ -14,6 +16,10 @@ from .models import (
|
|
|
14
16
|
ClassificationPredictVideoResponse,
|
|
15
17
|
PredictionTaskStatusResponse,
|
|
16
18
|
)
|
|
19
|
+
from .parquet_deserializer import (
|
|
20
|
+
deserialize_image_predictions,
|
|
21
|
+
deserialize_video_predictions,
|
|
22
|
+
)
|
|
17
23
|
from .types.common import (
|
|
18
24
|
BASE_API_URL,
|
|
19
25
|
PredictionTaskState,
|
|
@@ -129,8 +135,9 @@ class Classification:
|
|
|
129
135
|
"""
|
|
130
136
|
Given a prediction task UUID, return
|
|
131
137
|
"""
|
|
132
|
-
|
|
133
|
-
|
|
138
|
+
query = urlencode({"predictionTaskUuid": prediction_task_uuid})
|
|
139
|
+
url = f"{BASE_API_URL}/prediction-task/status?{query}"
|
|
140
|
+
headers = {"X-API-Key": self._client.api_key}
|
|
134
141
|
|
|
135
142
|
@self._backoff_on_429
|
|
136
143
|
async def _make_request():
|
|
@@ -187,31 +194,60 @@ class Classification:
|
|
|
187
194
|
async def _get_results_unified(
|
|
188
195
|
self, prediction_task_uuid: PredictionTaskUUID, prediction_type: PredictionType
|
|
189
196
|
) -> Union[ClassificationPredictImageResponse, ClassificationPredictVideoResponse]:
|
|
190
|
-
|
|
191
|
-
|
|
197
|
+
query = urlencode(
|
|
198
|
+
{
|
|
199
|
+
"predictionTaskUuid": prediction_task_uuid,
|
|
200
|
+
"response_version": "parquet",
|
|
201
|
+
}
|
|
202
|
+
)
|
|
203
|
+
url = f"{BASE_API_URL}/prediction-task/results?{query}"
|
|
204
|
+
headers = {"X-API-Key": self._client.api_key}
|
|
192
205
|
|
|
193
206
|
@self._backoff_on_429
|
|
194
|
-
async def _make_request():
|
|
207
|
+
async def _make_request() -> tuple[bytes, CIMultiDictProxy[str]]:
|
|
195
208
|
async with aiohttp.ClientSession() as session:
|
|
196
209
|
async with session.get(url, headers=headers) as resp:
|
|
210
|
+
if resp.status == 400:
|
|
211
|
+
payload = await resp.json()
|
|
212
|
+
raise PredictionTaskResultsUnavailableError(
|
|
213
|
+
payload.get("detail", "")
|
|
214
|
+
)
|
|
197
215
|
resp.raise_for_status()
|
|
198
|
-
|
|
199
|
-
|
|
216
|
+
body_bytes = await resp.read()
|
|
217
|
+
response_headers = CIMultiDictProxy(CIMultiDict(resp.headers))
|
|
218
|
+
return body_bytes, response_headers
|
|
200
219
|
|
|
201
220
|
try:
|
|
202
|
-
|
|
221
|
+
parquet_bytes, response_headers = await _make_request()
|
|
222
|
+
except PredictionTaskResultsUnavailableError:
|
|
223
|
+
raise
|
|
203
224
|
except ClientError as error:
|
|
204
225
|
raise PredictionTaskResultsUnavailableError(
|
|
205
226
|
f"Error getting prediction task results: {error}"
|
|
206
227
|
)
|
|
207
228
|
|
|
208
|
-
|
|
209
|
-
payload["prediction_task_uuid"] = prediction_task_uuid
|
|
229
|
+
original_file_name = response_headers.get("X-Original-File-Name")
|
|
210
230
|
|
|
211
231
|
if prediction_type == "image":
|
|
212
|
-
return ClassificationPredictImageResponse
|
|
232
|
+
return ClassificationPredictImageResponse(
|
|
233
|
+
object_predictions=deserialize_image_predictions(parquet_bytes),
|
|
234
|
+
prediction_task_uuid=prediction_task_uuid,
|
|
235
|
+
original_file_name=original_file_name,
|
|
236
|
+
)
|
|
213
237
|
elif prediction_type == "video":
|
|
214
|
-
|
|
238
|
+
frames_per_second_header = response_headers.get("X-Frames-Per-Second")
|
|
239
|
+
if frames_per_second_header is None:
|
|
240
|
+
raise PredictionTaskResultsUnavailableError(
|
|
241
|
+
"Missing X-Frames-Per-Second header on video prediction response"
|
|
242
|
+
)
|
|
243
|
+
return ClassificationPredictVideoResponse(
|
|
244
|
+
timestamp_us_to_predictions=deserialize_video_predictions(
|
|
245
|
+
parquet_bytes
|
|
246
|
+
),
|
|
247
|
+
frames_per_second=int(frames_per_second_header),
|
|
248
|
+
prediction_task_uuid=prediction_task_uuid,
|
|
249
|
+
original_file_name=original_file_name,
|
|
250
|
+
)
|
|
215
251
|
else:
|
|
216
252
|
raise ValueError(f"Unsupported prediction type: {prediction_type}")
|
|
217
253
|
|
|
@@ -255,13 +291,13 @@ class Classification:
|
|
|
255
291
|
)
|
|
256
292
|
|
|
257
293
|
predict_url = f"{BASE_API_URL}/predict"
|
|
258
|
-
predict_data = {
|
|
294
|
+
predict_data: dict[str, Any] = {
|
|
259
295
|
"model_name": model_name,
|
|
260
296
|
"prediction_task_uuid": prediction_task_begin_response.prediction_task_uuid,
|
|
261
297
|
**kwargs,
|
|
262
298
|
}
|
|
263
299
|
predict_headers = {
|
|
264
|
-
"
|
|
300
|
+
"X-API-Key": self._client.api_key,
|
|
265
301
|
}
|
|
266
302
|
|
|
267
303
|
@self._backoff_on_429
|
|
@@ -354,6 +390,7 @@ class Classification:
|
|
|
354
390
|
|
|
355
391
|
form_data = aiohttp.FormData()
|
|
356
392
|
form_data.add_field("mimetype", mime_type)
|
|
393
|
+
form_data.add_field("response_version", "parquet")
|
|
357
394
|
if file_name is not None:
|
|
358
395
|
form_data.add_field("file_name", file_name)
|
|
359
396
|
|
|
@@ -361,19 +398,24 @@ class Classification:
|
|
|
361
398
|
form_data.add_field("frames_per_second", str(frames_per_second))
|
|
362
399
|
|
|
363
400
|
headers = {
|
|
364
|
-
"
|
|
401
|
+
"X-API-Key": self._client.api_key,
|
|
365
402
|
}
|
|
366
403
|
|
|
367
404
|
@self._backoff_on_429
|
|
368
405
|
async def _make_request():
|
|
369
406
|
async with aiohttp.ClientSession() as session:
|
|
370
407
|
async with session.post(url, data=form_data, headers=headers) as resp:
|
|
408
|
+
if resp.status == 400:
|
|
409
|
+
error_payload = await resp.json()
|
|
410
|
+
raise PredictionTaskBeginError(error_payload.get("detail", ""))
|
|
371
411
|
resp.raise_for_status()
|
|
372
412
|
payload = await resp.json()
|
|
373
413
|
return payload
|
|
374
414
|
|
|
375
415
|
try:
|
|
376
416
|
payload = await _make_request()
|
|
417
|
+
except PredictionTaskBeginError:
|
|
418
|
+
raise
|
|
377
419
|
except ClientError as error:
|
|
378
420
|
raise PredictionTaskBeginError("Error beginning prediction task:", error)
|
|
379
421
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from typing import Dict,
|
|
1
|
+
from typing import Dict, List, Optional
|
|
2
2
|
|
|
3
3
|
from pydantic import BaseModel
|
|
4
4
|
|
|
@@ -7,8 +7,6 @@ from dragoneye.types.common import (
|
|
|
7
7
|
PredictionTaskState,
|
|
8
8
|
PredictionTaskUUID,
|
|
9
9
|
PredictionType,
|
|
10
|
-
TaxonID,
|
|
11
|
-
TaxonPrediction,
|
|
12
10
|
)
|
|
13
11
|
|
|
14
12
|
|
|
@@ -18,22 +16,38 @@ class PredictionTaskStatusResponse(BaseModel):
|
|
|
18
16
|
status: PredictionTaskState
|
|
19
17
|
|
|
20
18
|
|
|
21
|
-
class
|
|
22
|
-
|
|
19
|
+
class ClassificationAttributeOption(BaseModel):
|
|
20
|
+
option_id: int
|
|
23
21
|
name: str
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
score: float
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class ClassificationAttributeResponse(BaseModel):
|
|
26
|
+
attribute_id: int
|
|
27
|
+
name: str
|
|
28
|
+
options: List[ClassificationAttributeOption]
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class ClassificationCategory(BaseModel):
|
|
32
|
+
id: int
|
|
33
|
+
name: str
|
|
34
|
+
score: float
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class ClassificationCategoryPrediction(BaseModel):
|
|
38
|
+
category: ClassificationCategory
|
|
39
|
+
attributes: List[ClassificationAttributeResponse]
|
|
26
40
|
|
|
27
41
|
|
|
28
42
|
class ClassificationObjectPrediction(BaseModel):
|
|
29
43
|
normalizedBbox: NormalizedBbox
|
|
30
|
-
|
|
31
|
-
traits: Sequence[ClassificationTraitRootPrediction]
|
|
44
|
+
predictions: List[ClassificationCategoryPrediction]
|
|
32
45
|
|
|
33
46
|
|
|
34
47
|
class ClassificationPredictImageResponse(BaseModel):
|
|
35
|
-
|
|
48
|
+
object_predictions: List[ClassificationObjectPrediction]
|
|
36
49
|
prediction_task_uuid: PredictionTaskUUID
|
|
50
|
+
original_file_name: Optional[str]
|
|
37
51
|
|
|
38
52
|
|
|
39
53
|
class ClassificationVideoObjectPrediction(ClassificationObjectPrediction):
|
|
@@ -43,7 +57,8 @@ class ClassificationVideoObjectPrediction(ClassificationObjectPrediction):
|
|
|
43
57
|
|
|
44
58
|
class ClassificationPredictVideoResponse(BaseModel):
|
|
45
59
|
timestamp_us_to_predictions: Dict[
|
|
46
|
-
int,
|
|
60
|
+
int, List[ClassificationVideoObjectPrediction]
|
|
47
61
|
]
|
|
48
62
|
frames_per_second: int
|
|
49
63
|
prediction_task_uuid: PredictionTaskUUID
|
|
64
|
+
original_file_name: Optional[str]
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"""Convert a zstd-compressed parquet prediction blob into typed SDK models.
|
|
2
|
+
|
|
3
|
+
Matches the server-side schema produced by the dragoneye pipeline:
|
|
4
|
+
|
|
5
|
+
image_id: String
|
|
6
|
+
normalized_bbox: Array(Float64, 4)
|
|
7
|
+
bbox_score: Float64
|
|
8
|
+
predictions: List(Struct{
|
|
9
|
+
category_id: Int64,
|
|
10
|
+
name: String,
|
|
11
|
+
score: Float64,
|
|
12
|
+
attributes: List(Struct{
|
|
13
|
+
attribute_id: Int64,
|
|
14
|
+
name: String,
|
|
15
|
+
options: List(Struct{
|
|
16
|
+
option_id: Int64,
|
|
17
|
+
name: String,
|
|
18
|
+
score: Float64,
|
|
19
|
+
}),
|
|
20
|
+
}),
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
For video parquet blobs the extra column ``timestamp_microseconds: Int64`` is
|
|
24
|
+
present.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
import io
|
|
28
|
+
from typing import Any, Dict, List
|
|
29
|
+
|
|
30
|
+
import polars as pl
|
|
31
|
+
|
|
32
|
+
from .models import (
|
|
33
|
+
ClassificationAttributeOption,
|
|
34
|
+
ClassificationAttributeResponse,
|
|
35
|
+
ClassificationCategory,
|
|
36
|
+
ClassificationCategoryPrediction,
|
|
37
|
+
ClassificationObjectPrediction,
|
|
38
|
+
ClassificationVideoObjectPrediction,
|
|
39
|
+
)
|
|
40
|
+
from .types.common import NormalizedBbox
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _predictions_to_models(
|
|
44
|
+
raw_predictions: List[Dict[str, Any]],
|
|
45
|
+
) -> List[ClassificationCategoryPrediction]:
|
|
46
|
+
return [
|
|
47
|
+
ClassificationCategoryPrediction(
|
|
48
|
+
category=ClassificationCategory(
|
|
49
|
+
id=pred["category_id"],
|
|
50
|
+
name=pred["name"],
|
|
51
|
+
score=pred["score"],
|
|
52
|
+
),
|
|
53
|
+
attributes=[
|
|
54
|
+
ClassificationAttributeResponse(
|
|
55
|
+
attribute_id=attr["attribute_id"],
|
|
56
|
+
name=attr["name"],
|
|
57
|
+
options=[
|
|
58
|
+
ClassificationAttributeOption(
|
|
59
|
+
option_id=opt["option_id"],
|
|
60
|
+
name=opt["name"],
|
|
61
|
+
score=opt["score"],
|
|
62
|
+
)
|
|
63
|
+
for opt in attr["options"]
|
|
64
|
+
],
|
|
65
|
+
)
|
|
66
|
+
for attr in pred["attributes"]
|
|
67
|
+
],
|
|
68
|
+
)
|
|
69
|
+
for pred in raw_predictions
|
|
70
|
+
]
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _read_dataframe(parquet_bytes: bytes, columns: List[str]) -> pl.DataFrame:
|
|
74
|
+
return pl.read_parquet(io.BytesIO(parquet_bytes), columns=columns)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def deserialize_image_predictions(
|
|
78
|
+
parquet_bytes: bytes,
|
|
79
|
+
) -> List[ClassificationObjectPrediction]:
|
|
80
|
+
df = _read_dataframe(
|
|
81
|
+
parquet_bytes, columns=["normalized_bbox", "predictions"]
|
|
82
|
+
).filter(pl.col("normalized_bbox").is_not_null())
|
|
83
|
+
return [
|
|
84
|
+
ClassificationObjectPrediction(
|
|
85
|
+
normalizedBbox=NormalizedBbox(tuple(row["normalized_bbox"])),
|
|
86
|
+
predictions=_predictions_to_models(row["predictions"] or []),
|
|
87
|
+
)
|
|
88
|
+
for row in df.select("normalized_bbox", "predictions").iter_rows(named=True)
|
|
89
|
+
]
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def deserialize_video_predictions(
|
|
93
|
+
parquet_bytes: bytes,
|
|
94
|
+
) -> Dict[int, List[ClassificationVideoObjectPrediction]]:
|
|
95
|
+
df = _read_dataframe(
|
|
96
|
+
parquet_bytes,
|
|
97
|
+
columns=[
|
|
98
|
+
"image_id",
|
|
99
|
+
"normalized_bbox",
|
|
100
|
+
"predictions",
|
|
101
|
+
"timestamp_microseconds",
|
|
102
|
+
],
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
# Frames with detections contribute rows; frames without still appear with a
|
|
106
|
+
# null bbox so the caller can distinguish "no detections" from "frame not
|
|
107
|
+
# processed".
|
|
108
|
+
real_df = df.filter(pl.col("normalized_bbox").is_not_null())
|
|
109
|
+
|
|
110
|
+
result: Dict[int, List[ClassificationVideoObjectPrediction]] = {
|
|
111
|
+
int(ts): [
|
|
112
|
+
ClassificationVideoObjectPrediction(
|
|
113
|
+
normalizedBbox=NormalizedBbox(tuple(row["normalized_bbox"])),
|
|
114
|
+
predictions=_predictions_to_models(row["predictions"] or []),
|
|
115
|
+
frame_id=row["image_id"],
|
|
116
|
+
timestamp_microseconds=int(ts),
|
|
117
|
+
)
|
|
118
|
+
for row in group.select(
|
|
119
|
+
"normalized_bbox", "predictions", "image_id"
|
|
120
|
+
).iter_rows(named=True)
|
|
121
|
+
]
|
|
122
|
+
for (ts,), group in real_df.group_by(
|
|
123
|
+
"timestamp_microseconds",
|
|
124
|
+
)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
# Include no-detection frames with empty prediction lists.
|
|
128
|
+
if len(df):
|
|
129
|
+
for row in df.select("timestamp_microseconds").unique().iter_rows(named=True):
|
|
130
|
+
ts_raw = row["timestamp_microseconds"]
|
|
131
|
+
if ts_raw is None:
|
|
132
|
+
continue
|
|
133
|
+
ts = int(ts_raw)
|
|
134
|
+
result.setdefault(ts, [])
|
|
135
|
+
|
|
136
|
+
return result
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from typing import Literal, NewType, Tuple
|
|
2
|
+
|
|
3
|
+
PredictionType = Literal["image", "video"]
|
|
4
|
+
PredictionTaskState = NewType("PredictionTaskState", str)
|
|
5
|
+
|
|
6
|
+
NormalizedBbox = NewType("NormalizedBbox", Tuple[float, float, float, float])
|
|
7
|
+
|
|
8
|
+
PredictionTaskUUID = NewType("PredictionTaskUUID", str)
|
|
9
|
+
|
|
10
|
+
TimestampUs = NewType("TimestampUs", int)
|
|
11
|
+
|
|
12
|
+
BASE_API_URL = "https://api.dragoneye.ai"
|
|
@@ -136,10 +136,10 @@ class Media:
|
|
|
136
136
|
|
|
137
137
|
# For any readable object with .read()
|
|
138
138
|
if hasattr(src, "read"):
|
|
139
|
-
pos = _tell_safe(src)
|
|
140
|
-
data = src.read()
|
|
141
|
-
_seek_safe(src, pos)
|
|
142
|
-
return BytesIO(data)
|
|
139
|
+
pos = _tell_safe(src) # pyright: ignore [reportArgumentType]
|
|
140
|
+
data = src.read() # pyright: ignore [reportUnknownMemberType, reportAttributeAccessIssue, reportUnknownVariableType]
|
|
141
|
+
_seek_safe(src, pos) # pyright: ignore [reportArgumentType]
|
|
142
|
+
return BytesIO(data) # pyright: ignore [reportUnknownArgumentType]
|
|
143
143
|
|
|
144
144
|
raise TypeError(
|
|
145
145
|
"Invalid media source: expected bytes, BytesIO, or a readable binary stream."
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
from .classification import (
|
|
2
|
-
Classification,
|
|
3
|
-
)
|
|
4
|
-
from .client import Dragoneye
|
|
5
|
-
from .models import (
|
|
6
|
-
ClassificationObjectPrediction,
|
|
7
|
-
ClassificationPredictImageResponse,
|
|
8
|
-
ClassificationTraitRootPrediction,
|
|
9
|
-
)
|
|
10
|
-
from .types.common import NormalizedBbox, TaxonID, TaxonPrediction, TaxonType
|
|
11
|
-
from .types.media import Image, Video
|
|
12
|
-
|
|
13
|
-
__all__ = [
|
|
14
|
-
"Classification",
|
|
15
|
-
"ClassificationObjectPrediction",
|
|
16
|
-
"ClassificationPredictImageResponse",
|
|
17
|
-
"ClassificationTraitRootPrediction",
|
|
18
|
-
"Dragoneye",
|
|
19
|
-
"Image",
|
|
20
|
-
"Video",
|
|
21
|
-
"NormalizedBbox",
|
|
22
|
-
"TaxonID",
|
|
23
|
-
"TaxonPrediction",
|
|
24
|
-
"TaxonType",
|
|
25
|
-
]
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
from enum import Enum
|
|
2
|
-
from typing import Literal, NewType, Optional, Sequence, Tuple
|
|
3
|
-
|
|
4
|
-
from pydantic import BaseModel
|
|
5
|
-
|
|
6
|
-
PredictionType = Literal["image", "video"]
|
|
7
|
-
PredictionTaskState = NewType("PredictionTaskState", str)
|
|
8
|
-
|
|
9
|
-
NormalizedBbox = NewType("NormalizedBbox", Tuple[float, float, float, float])
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
class TaxonType(str, Enum):
|
|
13
|
-
CATEGORY = ("category",)
|
|
14
|
-
TRAIT = ("trait",)
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
TaxonID = NewType("TaxonID", int)
|
|
18
|
-
|
|
19
|
-
PredictionTaskUUID = NewType("PredictionTaskUUID", str)
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
class TaxonPrediction(BaseModel):
|
|
23
|
-
id: TaxonID
|
|
24
|
-
type: TaxonType
|
|
25
|
-
name: str
|
|
26
|
-
displayName: str
|
|
27
|
-
score: Optional[float]
|
|
28
|
-
children: Sequence["TaxonPrediction"]
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
BASE_API_URL = "https://api.dragoneye.ai"
|
|
File without changes
|
{dragoneye_python-0.4.1 → dragoneye_python-1.0.0}/dragoneye_python.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|