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.
Files changed (31) hide show
  1. {tensorplate_python-0.1.3 → tensorplate_python-0.1.4}/PKG-INFO +1 -1
  2. {tensorplate_python-0.1.3 → tensorplate_python-0.1.4}/pyproject.toml +1 -1
  3. {tensorplate_python-0.1.3 → tensorplate_python-0.1.4}/src/tensorplate/__init__.py +4 -2
  4. {tensorplate_python-0.1.3 → tensorplate_python-0.1.4}/src/tensorplate/client.py +3 -2
  5. {tensorplate_python-0.1.3 → tensorplate_python-0.1.4}/src/tensorplate/conventions.py +7 -1
  6. {tensorplate_python-0.1.3 → tensorplate_python-0.1.4}/src/tensorplate/postprocess.py +65 -8
  7. tensorplate_python-0.1.4/src/tensorplate/serving.py +650 -0
  8. {tensorplate_python-0.1.3 → tensorplate_python-0.1.4}/src/tensorplate/vision.py +3 -1
  9. {tensorplate_python-0.1.3 → tensorplate_python-0.1.4}/src/tensorplate_python.egg-info/PKG-INFO +1 -1
  10. {tensorplate_python-0.1.3 → tensorplate_python-0.1.4}/tests/test_import_safety.py +2 -0
  11. {tensorplate_python-0.1.3 → tensorplate_python-0.1.4}/tests/test_postprocess.py +90 -0
  12. {tensorplate_python-0.1.3 → tensorplate_python-0.1.4}/tests/test_serving.py +175 -4
  13. {tensorplate_python-0.1.3 → tensorplate_python-0.1.4}/tests/test_vision.py +60 -2
  14. tensorplate_python-0.1.3/src/tensorplate/serving.py +0 -291
  15. {tensorplate_python-0.1.3 → tensorplate_python-0.1.4}/LICENSE +0 -0
  16. {tensorplate_python-0.1.3 → tensorplate_python-0.1.4}/README.md +0 -0
  17. {tensorplate_python-0.1.3 → tensorplate_python-0.1.4}/setup.cfg +0 -0
  18. {tensorplate_python-0.1.3 → tensorplate_python-0.1.4}/src/tensorplate/errors.py +0 -0
  19. {tensorplate_python-0.1.3 → tensorplate_python-0.1.4}/src/tensorplate/preprocess.py +0 -0
  20. {tensorplate_python-0.1.3 → tensorplate_python-0.1.4}/src/tensorplate/py.typed +0 -0
  21. {tensorplate_python-0.1.3 → tensorplate_python-0.1.4}/src/tensorplate/tensors.py +0 -0
  22. {tensorplate_python-0.1.3 → tensorplate_python-0.1.4}/src/tensorplate_python.egg-info/SOURCES.txt +0 -0
  23. {tensorplate_python-0.1.3 → tensorplate_python-0.1.4}/src/tensorplate_python.egg-info/dependency_links.txt +0 -0
  24. {tensorplate_python-0.1.3 → tensorplate_python-0.1.4}/src/tensorplate_python.egg-info/requires.txt +0 -0
  25. {tensorplate_python-0.1.3 → tensorplate_python-0.1.4}/src/tensorplate_python.egg-info/top_level.txt +0 -0
  26. {tensorplate_python-0.1.3 → tensorplate_python-0.1.4}/tests/test_client.py +0 -0
  27. {tensorplate_python-0.1.3 → tensorplate_python-0.1.4}/tests/test_e2e_worker.py +0 -0
  28. {tensorplate_python-0.1.3 → tensorplate_python-0.1.4}/tests/test_examples.py +0 -0
  29. {tensorplate_python-0.1.3 → tensorplate_python-0.1.4}/tests/test_preprocess.py +0 -0
  30. {tensorplate_python-0.1.3 → tensorplate_python-0.1.4}/tests/test_skeleton.py +0 -0
  31. {tensorplate_python-0.1.3 → tensorplate_python-0.1.4}/tests/test_tensors.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tensorplate-python
3
- Version: 0.1.3
3
+ Version: 0.1.4
4
4
  Summary: First-party Python SDK for calling deployed TensorPlate detection and vision serving models.
5
5
  Author: TensorPlate Contributors
6
6
  License: Apache-2.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.3"
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
- request.add_header("Accept", "application/json")
108
- if body is not None:
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 YOLOv8-style single-output tensor into source-pixel detections.
45
+ """Decode a supported YOLO-style output tensor into source-pixel detections.
45
46
 
46
- Expects ``[1, 4 + C, N]`` (or ``[1, N, 4 + C]`` when ``transposed``): 4
47
- box coordinates (center-x, center-y, width, height in model-input
48
- pixels) followed by C per-class scores. Applies score thresholding and
49
- class-aware NMS, then maps boxes back to source pixels via ``transform``.
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}; only {YOLO_V8_SINGLE_OUTPUT!r} is built in"
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])