tensorplate-python 0.1.3__tar.gz → 0.1.4__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.
- {tensorplate_python-0.1.3 → tensorplate_python-0.1.4}/PKG-INFO +1 -1
- {tensorplate_python-0.1.3 → tensorplate_python-0.1.4}/pyproject.toml +1 -1
- {tensorplate_python-0.1.3 → tensorplate_python-0.1.4}/src/tensorplate/__init__.py +4 -2
- {tensorplate_python-0.1.3 → tensorplate_python-0.1.4}/src/tensorplate/client.py +3 -2
- {tensorplate_python-0.1.3 → tensorplate_python-0.1.4}/src/tensorplate/conventions.py +7 -1
- {tensorplate_python-0.1.3 → tensorplate_python-0.1.4}/src/tensorplate/postprocess.py +65 -8
- tensorplate_python-0.1.4/src/tensorplate/serving.py +650 -0
- {tensorplate_python-0.1.3 → tensorplate_python-0.1.4}/src/tensorplate/vision.py +3 -1
- {tensorplate_python-0.1.3 → tensorplate_python-0.1.4}/src/tensorplate_python.egg-info/PKG-INFO +1 -1
- {tensorplate_python-0.1.3 → tensorplate_python-0.1.4}/tests/test_import_safety.py +2 -0
- {tensorplate_python-0.1.3 → tensorplate_python-0.1.4}/tests/test_postprocess.py +90 -0
- {tensorplate_python-0.1.3 → tensorplate_python-0.1.4}/tests/test_serving.py +175 -4
- {tensorplate_python-0.1.3 → tensorplate_python-0.1.4}/tests/test_vision.py +60 -2
- tensorplate_python-0.1.3/src/tensorplate/serving.py +0 -291
- {tensorplate_python-0.1.3 → tensorplate_python-0.1.4}/LICENSE +0 -0
- {tensorplate_python-0.1.3 → tensorplate_python-0.1.4}/README.md +0 -0
- {tensorplate_python-0.1.3 → tensorplate_python-0.1.4}/setup.cfg +0 -0
- {tensorplate_python-0.1.3 → tensorplate_python-0.1.4}/src/tensorplate/errors.py +0 -0
- {tensorplate_python-0.1.3 → tensorplate_python-0.1.4}/src/tensorplate/preprocess.py +0 -0
- {tensorplate_python-0.1.3 → tensorplate_python-0.1.4}/src/tensorplate/py.typed +0 -0
- {tensorplate_python-0.1.3 → tensorplate_python-0.1.4}/src/tensorplate/tensors.py +0 -0
- {tensorplate_python-0.1.3 → tensorplate_python-0.1.4}/src/tensorplate_python.egg-info/SOURCES.txt +0 -0
- {tensorplate_python-0.1.3 → tensorplate_python-0.1.4}/src/tensorplate_python.egg-info/dependency_links.txt +0 -0
- {tensorplate_python-0.1.3 → tensorplate_python-0.1.4}/src/tensorplate_python.egg-info/requires.txt +0 -0
- {tensorplate_python-0.1.3 → tensorplate_python-0.1.4}/src/tensorplate_python.egg-info/top_level.txt +0 -0
- {tensorplate_python-0.1.3 → tensorplate_python-0.1.4}/tests/test_client.py +0 -0
- {tensorplate_python-0.1.3 → tensorplate_python-0.1.4}/tests/test_e2e_worker.py +0 -0
- {tensorplate_python-0.1.3 → tensorplate_python-0.1.4}/tests/test_examples.py +0 -0
- {tensorplate_python-0.1.3 → tensorplate_python-0.1.4}/tests/test_preprocess.py +0 -0
- {tensorplate_python-0.1.3 → tensorplate_python-0.1.4}/tests/test_skeleton.py +0 -0
- {tensorplate_python-0.1.3 → tensorplate_python-0.1.4}/tests/test_tensors.py +0 -0
|
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "tensorplate-python"
|
|
7
7
|
description = "First-party Python SDK for calling deployed TensorPlate detection and vision serving models."
|
|
8
|
-
version = "0.1.
|
|
8
|
+
version = "0.1.4"
|
|
9
9
|
requires-python = ">=3.10"
|
|
10
10
|
license = { text = "Apache-2.0" }
|
|
11
11
|
readme = "README.md"
|
|
@@ -16,7 +16,7 @@ from tensorplate.client import (
|
|
|
16
16
|
canonicalize_serving_url,
|
|
17
17
|
resolve_serving_url,
|
|
18
18
|
)
|
|
19
|
-
from tensorplate.conventions import YOLO_V8_SINGLE_OUTPUT, detections
|
|
19
|
+
from tensorplate.conventions import YOLO26_E2E_DETECTIONS, YOLO_V8_SINGLE_OUTPUT, detections
|
|
20
20
|
from tensorplate.errors import (
|
|
21
21
|
EndpointResolutionError,
|
|
22
22
|
ErrorCode,
|
|
@@ -29,7 +29,7 @@ from tensorplate.errors import (
|
|
|
29
29
|
)
|
|
30
30
|
from tensorplate.postprocess import Detection, decode_detections
|
|
31
31
|
from tensorplate.preprocess import LetterboxTransform, PreprocessConfig, preprocess
|
|
32
|
-
from tensorplate.serving import HealthSnapshot, InferResult, ServingClient, Timing
|
|
32
|
+
from tensorplate.serving import ClientTiming, HealthSnapshot, InferResult, ServingClient, Timing
|
|
33
33
|
from tensorplate.tensors import DType, Layout, TensorInput, TensorOutput
|
|
34
34
|
from tensorplate.vision import VisionClient
|
|
35
35
|
|
|
@@ -40,7 +40,9 @@ except PackageNotFoundError: # pragma: no cover - only hit outside an installed
|
|
|
40
40
|
|
|
41
41
|
__all__ = [
|
|
42
42
|
"LOOPBACK_DEFAULT",
|
|
43
|
+
"YOLO26_E2E_DETECTIONS",
|
|
43
44
|
"YOLO_V8_SINGLE_OUTPUT",
|
|
45
|
+
"ClientTiming",
|
|
44
46
|
"DType",
|
|
45
47
|
"Detection",
|
|
46
48
|
"EndpointResolutionError",
|
|
@@ -104,8 +104,9 @@ def http_request(
|
|
|
104
104
|
:class:`RequestTimeoutError`.
|
|
105
105
|
"""
|
|
106
106
|
request = urllib.request.Request(url, data=body, method=method)
|
|
107
|
-
|
|
108
|
-
|
|
107
|
+
normalized_headers = {key.lower(): value for key, value in (headers or {}).items()}
|
|
108
|
+
request.add_header("Accept", normalized_headers.get("accept", "application/json"))
|
|
109
|
+
if body is not None and "content-type" not in normalized_headers:
|
|
109
110
|
request.add_header("Content-Type", "application/json")
|
|
110
111
|
for key, value in (headers or {}).items():
|
|
111
112
|
request.add_header(key, value)
|
|
@@ -21,9 +21,15 @@ class detections:
|
|
|
21
21
|
classes = "detections.classes"
|
|
22
22
|
|
|
23
23
|
|
|
24
|
-
#: Supported built-in detector output contract: a single tensor shaped
|
|
24
|
+
#: Supported built-in YOLOv8-style detector output contract: a single tensor shaped
|
|
25
25
|
#: ``[1, 4 + C, N]`` (YOLOv8-style; 4 box coords + C class scores over N
|
|
26
26
|
#: anchors), or its transpose ``[1, N, 4 + C]`` when the caller declares
|
|
27
27
|
#: it. Other heads (YOLOv5 objectness, exporter-side NMS, masks,
|
|
28
28
|
#: keypoints) remain application-side postprocessing in v0.1.3.
|
|
29
29
|
YOLO_V8_SINGLE_OUTPUT = "yolo_v8_single_output"
|
|
30
|
+
|
|
31
|
+
#: Supported built-in YOLO26 default one-to-one / end-to-end detector output
|
|
32
|
+
#: contract: a single NMS-free tensor shaped ``[1, K, 6]`` with
|
|
33
|
+
#: ``K <= 300`` and columns ``x1, y1, x2, y2, score, class_id`` in
|
|
34
|
+
#: letterboxed model-input pixels.
|
|
35
|
+
YOLO26_E2E_DETECTIONS = "yolo26_e2e_detections"
|
|
@@ -7,13 +7,14 @@ lazily.
|
|
|
7
7
|
|
|
8
8
|
from __future__ import annotations
|
|
9
9
|
|
|
10
|
+
import math
|
|
10
11
|
from collections.abc import Sequence
|
|
11
12
|
from dataclasses import dataclass
|
|
12
13
|
from typing import TYPE_CHECKING
|
|
13
14
|
|
|
14
|
-
from tensorplate.conventions import YOLO_V8_SINGLE_OUTPUT
|
|
15
|
+
from tensorplate.conventions import YOLO26_E2E_DETECTIONS, YOLO_V8_SINGLE_OUTPUT
|
|
15
16
|
from tensorplate.errors import ProtocolError
|
|
16
|
-
from tensorplate.tensors import TensorOutput
|
|
17
|
+
from tensorplate.tensors import DType, TensorOutput
|
|
17
18
|
|
|
18
19
|
if TYPE_CHECKING:
|
|
19
20
|
import numpy as np
|
|
@@ -41,16 +42,22 @@ def decode_detections(
|
|
|
41
42
|
transposed: bool = False,
|
|
42
43
|
contract: str = YOLO_V8_SINGLE_OUTPUT,
|
|
43
44
|
) -> list[Detection]:
|
|
44
|
-
"""Decode a
|
|
45
|
+
"""Decode a supported YOLO-style output tensor into source-pixel detections.
|
|
45
46
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
class-aware NMS
|
|
47
|
+
``yolo_v8_single_output`` expects ``[1, 4 + C, N]`` (or
|
|
48
|
+
``[1, N, 4 + C]`` when ``transposed``): 4 center-format box coordinates
|
|
49
|
+
followed by C per-class scores. It applies score thresholding and
|
|
50
|
+
class-aware NMS.
|
|
51
|
+
|
|
52
|
+
``yolo26_e2e_detections`` expects NMS-free ``[1, K, 6]`` with columns
|
|
53
|
+
x1, y1, x2, y2, score, class_id. It applies only score thresholding.
|
|
50
54
|
"""
|
|
55
|
+
if contract == YOLO26_E2E_DETECTIONS:
|
|
56
|
+
return _decode_yolo26_e2e(output, transform, score_threshold=score_threshold, labels=labels)
|
|
51
57
|
if contract != YOLO_V8_SINGLE_OUTPUT:
|
|
52
58
|
raise ValueError(
|
|
53
|
-
f"unsupported output contract {contract!r};
|
|
59
|
+
f"unsupported output contract {contract!r}; supported contracts are "
|
|
60
|
+
f"{YOLO_V8_SINGLE_OUTPUT!r} and {YOLO26_E2E_DETECTIONS!r}"
|
|
54
61
|
)
|
|
55
62
|
import numpy as np
|
|
56
63
|
|
|
@@ -99,6 +106,56 @@ def decode_detections(
|
|
|
99
106
|
return detections
|
|
100
107
|
|
|
101
108
|
|
|
109
|
+
def _decode_yolo26_e2e(
|
|
110
|
+
output: TensorOutput,
|
|
111
|
+
transform: LetterboxTransform,
|
|
112
|
+
*,
|
|
113
|
+
score_threshold: float,
|
|
114
|
+
labels: Sequence[str] | None,
|
|
115
|
+
) -> list[Detection]:
|
|
116
|
+
if output.dtype not in (DType.FLOAT16, DType.FLOAT32):
|
|
117
|
+
raise ProtocolError(
|
|
118
|
+
f"detector output for {YOLO26_E2E_DETECTIONS!r} must be float16 or float32, "
|
|
119
|
+
f"got {output.dtype.value!r}"
|
|
120
|
+
)
|
|
121
|
+
try:
|
|
122
|
+
array = output.to_numpy().astype("float32")
|
|
123
|
+
except (RuntimeError, ValueError) as exc:
|
|
124
|
+
raise ProtocolError(
|
|
125
|
+
f"could not convert detector output for {YOLO26_E2E_DETECTIONS!r} to float32"
|
|
126
|
+
) from exc
|
|
127
|
+
if array.ndim != 3 or array.shape[0] != 1 or array.shape[2] != 6:
|
|
128
|
+
raise ProtocolError(
|
|
129
|
+
f"detector output must be [1, K, 6] for {YOLO26_E2E_DETECTIONS!r}, "
|
|
130
|
+
f"got shape {tuple(array.shape)}"
|
|
131
|
+
)
|
|
132
|
+
if array.shape[1] > 300:
|
|
133
|
+
raise ProtocolError(
|
|
134
|
+
f"detector output has {int(array.shape[1])} rows for {YOLO26_E2E_DETECTIONS!r}; "
|
|
135
|
+
"expected K <= 300"
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
detections: list[Detection] = []
|
|
139
|
+
for row in array[0]:
|
|
140
|
+
score = float(row[4])
|
|
141
|
+
# Skip non-finite scores (NaN never compares >= threshold and would
|
|
142
|
+
# otherwise fall through to int(row[5]) and raise on a NaN class id).
|
|
143
|
+
if not math.isfinite(score) or score < score_threshold:
|
|
144
|
+
continue
|
|
145
|
+
class_value = float(row[5])
|
|
146
|
+
# A real YOLO26 head never emits a negative or non-finite class id;
|
|
147
|
+
# drop such rows rather than store a nonsensical Detection.class_id.
|
|
148
|
+
if not math.isfinite(class_value) or class_value < 0:
|
|
149
|
+
continue
|
|
150
|
+
class_id = int(class_value)
|
|
151
|
+
box = transform.map_box_to_source(
|
|
152
|
+
float(row[0]), float(row[1]), float(row[2]), float(row[3])
|
|
153
|
+
)
|
|
154
|
+
label = labels[class_id] if labels is not None and class_id < len(labels) else None
|
|
155
|
+
detections.append(Detection(class_id=class_id, score=score, box=box, label=label))
|
|
156
|
+
return detections
|
|
157
|
+
|
|
158
|
+
|
|
102
159
|
def _looks_like_wrong_layout(array: np.ndarray, transposed: bool) -> bool:
|
|
103
160
|
"""Return true for the common unambiguous YOLO transpose mismatch."""
|
|
104
161
|
rows = int(array.shape[1])
|