perceptron 0.1.0__py3-none-any.whl
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.
- perceptron/__init__.py +108 -0
- perceptron/annotations.py +267 -0
- perceptron/cli.py +779 -0
- perceptron/client.py +521 -0
- perceptron/config.py +100 -0
- perceptron/dsl/nodes.py +141 -0
- perceptron/dsl/perceive.py +485 -0
- perceptron/errors.py +59 -0
- perceptron/highlevel.py +555 -0
- perceptron/pointing/parser.py +311 -0
- perceptron/pointing/types.py +87 -0
- perceptron/tensorstream/__init__.py +29 -0
- perceptron/tensorstream/ops.py +262 -0
- perceptron/tensorstream/tensorstream.py +721 -0
- perceptron-0.1.0.dist-info/METADATA +192 -0
- perceptron-0.1.0.dist-info/RECORD +20 -0
- perceptron-0.1.0.dist-info/WHEEL +5 -0
- perceptron-0.1.0.dist-info/entry_points.txt +2 -0
- perceptron-0.1.0.dist-info/licenses/LICENSE +80 -0
- perceptron-0.1.0.dist-info/top_level.txt +1 -0
perceptron/__init__.py
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"""
|
|
2
|
+
perceptron – Python SDK (v0.1 scaffolding)
|
|
3
|
+
|
|
4
|
+
Public surface (subject to refinement):
|
|
5
|
+
- DSL: perceive (decorator), text, system, agent, image, point, box, polygon, block
|
|
6
|
+
- Pointing: PointParser, parse_text, extract_points, strip_tags
|
|
7
|
+
- Data constructors for annotations/examples: pt, bbox, poly
|
|
8
|
+
- Config: configure, config (context manager), settings
|
|
9
|
+
|
|
10
|
+
This initial scaffold focuses on the compile/runtime pieces that do not require
|
|
11
|
+
network access. Transport and streaming are added in later phases.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
__version__ = "0.1.0"
|
|
15
|
+
|
|
16
|
+
from .config import configure, config, settings
|
|
17
|
+
from .client import Client, AsyncClient
|
|
18
|
+
from .errors import (
|
|
19
|
+
SDKError,
|
|
20
|
+
TransportError,
|
|
21
|
+
TimeoutError,
|
|
22
|
+
AuthError,
|
|
23
|
+
RateLimitError,
|
|
24
|
+
ServerError,
|
|
25
|
+
BadRequestError,
|
|
26
|
+
ExpectationError,
|
|
27
|
+
AnchorError,
|
|
28
|
+
)
|
|
29
|
+
from .pointing.types import (
|
|
30
|
+
SinglePoint,
|
|
31
|
+
BoundingBox,
|
|
32
|
+
Polygon,
|
|
33
|
+
Collection,
|
|
34
|
+
pt,
|
|
35
|
+
bbox,
|
|
36
|
+
poly,
|
|
37
|
+
collection,
|
|
38
|
+
)
|
|
39
|
+
from .pointing.parser import (
|
|
40
|
+
PointParser,
|
|
41
|
+
ReasoningExtraction,
|
|
42
|
+
ReasoningStreamCleaner,
|
|
43
|
+
extract_points,
|
|
44
|
+
extract_reasoning,
|
|
45
|
+
parse_text,
|
|
46
|
+
strip_tags,
|
|
47
|
+
)
|
|
48
|
+
from .dsl.nodes import text, system, agent, image, point, box, polygon, block
|
|
49
|
+
from .dsl.perceive import perceive, async_perceive, inspect_task, PerceiveResult
|
|
50
|
+
from .annotations import annotate_image
|
|
51
|
+
from .highlevel import caption, ocr, detect, detect_from_coco, question
|
|
52
|
+
|
|
53
|
+
__all__ = [
|
|
54
|
+
# Config
|
|
55
|
+
"configure",
|
|
56
|
+
"config",
|
|
57
|
+
"settings",
|
|
58
|
+
"Client",
|
|
59
|
+
"AsyncClient",
|
|
60
|
+
# Errors
|
|
61
|
+
"SDKError",
|
|
62
|
+
"TransportError",
|
|
63
|
+
"TimeoutError",
|
|
64
|
+
"AuthError",
|
|
65
|
+
"RateLimitError",
|
|
66
|
+
"ServerError",
|
|
67
|
+
"BadRequestError",
|
|
68
|
+
"ExpectationError",
|
|
69
|
+
"AnchorError",
|
|
70
|
+
# Pointing types & constructors
|
|
71
|
+
"SinglePoint",
|
|
72
|
+
"BoundingBox",
|
|
73
|
+
"Polygon",
|
|
74
|
+
"Collection",
|
|
75
|
+
"pt",
|
|
76
|
+
"bbox",
|
|
77
|
+
"poly",
|
|
78
|
+
"collection",
|
|
79
|
+
# Parser & helpers
|
|
80
|
+
"PointParser",
|
|
81
|
+
"ReasoningExtraction",
|
|
82
|
+
"ReasoningStreamCleaner",
|
|
83
|
+
"parse_text",
|
|
84
|
+
"extract_points",
|
|
85
|
+
"extract_reasoning",
|
|
86
|
+
"strip_tags",
|
|
87
|
+
# DSL nodes & decorator
|
|
88
|
+
"text",
|
|
89
|
+
"system",
|
|
90
|
+
"agent",
|
|
91
|
+
"image",
|
|
92
|
+
"point",
|
|
93
|
+
"box",
|
|
94
|
+
"polygon",
|
|
95
|
+
"block",
|
|
96
|
+
"perceive",
|
|
97
|
+
"async_perceive",
|
|
98
|
+
"inspect_task",
|
|
99
|
+
"PerceiveResult",
|
|
100
|
+
# High-level helpers
|
|
101
|
+
"annotate_image",
|
|
102
|
+
"caption",
|
|
103
|
+
"ocr",
|
|
104
|
+
"detect",
|
|
105
|
+
"question",
|
|
106
|
+
"detect_from_coco",
|
|
107
|
+
"__version__",
|
|
108
|
+
]
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
"""Utilities for working with annotation examples (points, boxes, polygons, collections)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any, Iterable, Mapping, Sequence
|
|
6
|
+
|
|
7
|
+
from .errors import BadRequestError
|
|
8
|
+
from .pointing.types import (
|
|
9
|
+
BoundingBox,
|
|
10
|
+
Collection,
|
|
11
|
+
Polygon,
|
|
12
|
+
SinglePoint,
|
|
13
|
+
bbox as make_bbox,
|
|
14
|
+
collection as make_collection,
|
|
15
|
+
poly as make_polygon,
|
|
16
|
+
pt as make_point,
|
|
17
|
+
)
|
|
18
|
+
from .pointing.parser import PointParser, parse_text
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
"annotate_image",
|
|
22
|
+
"coerce_annotation",
|
|
23
|
+
"serialize_annotations",
|
|
24
|
+
"canonicalize_text_collections",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
AnnotationSpec = Any
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _coerce_bbox(spec: AnnotationSpec) -> BoundingBox:
|
|
32
|
+
if isinstance(spec, BoundingBox):
|
|
33
|
+
return spec
|
|
34
|
+
if isinstance(spec, Mapping):
|
|
35
|
+
if "bbox" in spec:
|
|
36
|
+
coords = spec["bbox"]
|
|
37
|
+
elif {"x1", "y1", "x2", "y2"}.issubset(spec):
|
|
38
|
+
coords = (spec["x1"], spec["y1"], spec["x2"], spec["y2"])
|
|
39
|
+
else:
|
|
40
|
+
raise BadRequestError("Example box dict must include bbox tuple or x1/y1/x2/y2 keys")
|
|
41
|
+
mention = spec.get("label") or spec.get("mention")
|
|
42
|
+
x1, y1, x2, y2 = coords
|
|
43
|
+
return make_bbox(int(x1), int(y1), int(x2), int(y2), mention=mention)
|
|
44
|
+
if isinstance(spec, Sequence) and len(spec) == 4:
|
|
45
|
+
x1, y1, x2, y2 = spec
|
|
46
|
+
return make_bbox(int(x1), int(y1), int(x2), int(y2))
|
|
47
|
+
raise BadRequestError(f"Unsupported box spec: {spec!r}")
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _coerce_point(spec: AnnotationSpec) -> SinglePoint:
|
|
51
|
+
if isinstance(spec, SinglePoint):
|
|
52
|
+
return spec
|
|
53
|
+
if isinstance(spec, Mapping):
|
|
54
|
+
if {"x", "y"}.issubset(spec):
|
|
55
|
+
return make_point(int(spec["x"]), int(spec["y"]), mention=spec.get("label") or spec.get("mention"))
|
|
56
|
+
if "point" in spec:
|
|
57
|
+
x, y = spec["point"]
|
|
58
|
+
return make_point(int(x), int(y), mention=spec.get("label") or spec.get("mention"))
|
|
59
|
+
raise BadRequestError("Example point dict must include point or x/y keys")
|
|
60
|
+
if isinstance(spec, Sequence) and len(spec) == 2:
|
|
61
|
+
x, y = spec
|
|
62
|
+
return make_point(int(x), int(y))
|
|
63
|
+
raise BadRequestError(f"Unsupported point spec: {spec!r}")
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _coerce_polygon(spec: AnnotationSpec) -> Polygon:
|
|
67
|
+
if isinstance(spec, Polygon):
|
|
68
|
+
return spec
|
|
69
|
+
if isinstance(spec, Mapping):
|
|
70
|
+
coords = spec.get("coords") or spec.get("polygon")
|
|
71
|
+
if not coords:
|
|
72
|
+
raise BadRequestError("Example polygon dict must include coords/polygon")
|
|
73
|
+
mention = spec.get("label") or spec.get("mention")
|
|
74
|
+
else:
|
|
75
|
+
coords = spec
|
|
76
|
+
mention = None
|
|
77
|
+
if not isinstance(coords, Iterable):
|
|
78
|
+
raise BadRequestError("Polygon coords must be iterable")
|
|
79
|
+
points: list[tuple[int, int]] = []
|
|
80
|
+
for item in coords:
|
|
81
|
+
if not isinstance(item, (list, tuple)) or len(item) != 2:
|
|
82
|
+
raise BadRequestError("Polygon coordinate must be (x, y)")
|
|
83
|
+
x, y = item
|
|
84
|
+
points.append((int(x), int(y)))
|
|
85
|
+
return make_polygon(points, mention=mention)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _build_collection(spec: AnnotationSpec) -> Collection:
|
|
89
|
+
if isinstance(spec, Collection):
|
|
90
|
+
return spec
|
|
91
|
+
if isinstance(spec, Mapping):
|
|
92
|
+
mention = spec.get("label") or spec.get("mention")
|
|
93
|
+
t = spec.get("t")
|
|
94
|
+
child_specs = (
|
|
95
|
+
spec.get("points")
|
|
96
|
+
or spec.get("children")
|
|
97
|
+
or spec.get("items")
|
|
98
|
+
or spec.get("collection")
|
|
99
|
+
)
|
|
100
|
+
if child_specs is None:
|
|
101
|
+
raise BadRequestError("Collection spec must include points/children/items list")
|
|
102
|
+
children = [coerce_annotation(child) for child in child_specs]
|
|
103
|
+
return make_collection(children, mention=mention, t=t)
|
|
104
|
+
if isinstance(spec, Sequence):
|
|
105
|
+
children = [coerce_annotation(child) for child in spec]
|
|
106
|
+
return make_collection(children)
|
|
107
|
+
raise BadRequestError(f"Unsupported collection spec: {spec!r}")
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def coerce_annotation(spec: AnnotationSpec) -> Any:
|
|
111
|
+
if isinstance(spec, (SinglePoint, BoundingBox, Polygon, Collection)):
|
|
112
|
+
return spec
|
|
113
|
+
if isinstance(spec, Mapping):
|
|
114
|
+
type_hint = spec.get("type") or spec.get("kind") or spec.get("point_type")
|
|
115
|
+
if type_hint:
|
|
116
|
+
kind = str(type_hint).lower()
|
|
117
|
+
if kind in {"point", "pt"}:
|
|
118
|
+
return _coerce_point(spec)
|
|
119
|
+
if kind in {"box", "bbox", "point_box"}:
|
|
120
|
+
return _coerce_bbox(spec)
|
|
121
|
+
if kind == "polygon":
|
|
122
|
+
return _coerce_polygon(spec)
|
|
123
|
+
if kind == "collection":
|
|
124
|
+
return _build_collection(spec)
|
|
125
|
+
if {"x", "y"}.issubset(spec) or "point" in spec:
|
|
126
|
+
return _coerce_point(spec)
|
|
127
|
+
if spec.get("bbox") is not None or {"x1", "y1", "x2", "y2"}.issubset(spec):
|
|
128
|
+
return _coerce_bbox(spec)
|
|
129
|
+
if spec.get("coords") is not None or spec.get("polygon") is not None:
|
|
130
|
+
return _coerce_polygon(spec)
|
|
131
|
+
if any(key in spec for key in ("points", "children", "items", "collection")):
|
|
132
|
+
return _build_collection(spec)
|
|
133
|
+
if isinstance(spec, Sequence):
|
|
134
|
+
if len(spec) == 2 and all(isinstance(v, (int, float)) for v in spec):
|
|
135
|
+
return _coerce_point(spec)
|
|
136
|
+
if len(spec) == 4 and all(isinstance(v, (int, float)) for v in spec):
|
|
137
|
+
return _coerce_bbox(spec)
|
|
138
|
+
if all(isinstance(item, (list, tuple)) and len(item) == 2 for item in spec):
|
|
139
|
+
return _coerce_polygon(spec)
|
|
140
|
+
raise BadRequestError(f"Unsupported annotation spec: {spec!r}")
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def _point_sort_key(obj: Any) -> tuple[int, int]:
|
|
144
|
+
if isinstance(obj, BoundingBox):
|
|
145
|
+
return (obj.top_left.y, obj.top_left.x)
|
|
146
|
+
if isinstance(obj, SinglePoint):
|
|
147
|
+
return (obj.y, obj.x)
|
|
148
|
+
if isinstance(obj, Polygon):
|
|
149
|
+
hull = obj.hull
|
|
150
|
+
if not hull:
|
|
151
|
+
return (0, 0)
|
|
152
|
+
first = min(hull, key=lambda p: (p.y, p.x))
|
|
153
|
+
return (first.y, first.x)
|
|
154
|
+
if isinstance(obj, Collection):
|
|
155
|
+
if not obj.points:
|
|
156
|
+
return (0, 0)
|
|
157
|
+
first = min(obj.points, key=_point_sort_key)
|
|
158
|
+
return _point_sort_key(first)
|
|
159
|
+
return (0, 0)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def _canonicalize_collection(coll: Collection) -> Collection:
|
|
163
|
+
children: list[Any] = []
|
|
164
|
+
for child in coll.points:
|
|
165
|
+
if isinstance(child, Collection):
|
|
166
|
+
children.append(_canonicalize_collection(child))
|
|
167
|
+
else:
|
|
168
|
+
children.append(child)
|
|
169
|
+
children.sort(key=_point_sort_key)
|
|
170
|
+
return make_collection(children, mention=coll.mention, t=coll.t)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def serialize_annotations(
|
|
174
|
+
boxes: Sequence[Any] | None,
|
|
175
|
+
polygons: Sequence[Any] | None,
|
|
176
|
+
points: Sequence[Any] | None,
|
|
177
|
+
collections: Sequence[Any] | None,
|
|
178
|
+
mention_order: Mapping[str, int] | None = None,
|
|
179
|
+
) -> str:
|
|
180
|
+
tags: list[str] = []
|
|
181
|
+
if boxes:
|
|
182
|
+
for b in boxes:
|
|
183
|
+
tags.append(PointParser.serialize(_coerce_bbox(b)))
|
|
184
|
+
if polygons:
|
|
185
|
+
for poly in polygons:
|
|
186
|
+
tags.append(PointParser.serialize(_coerce_polygon(poly)))
|
|
187
|
+
if points:
|
|
188
|
+
for pt in points:
|
|
189
|
+
tags.append(PointParser.serialize(_coerce_point(pt)))
|
|
190
|
+
if collections:
|
|
191
|
+
canonical: list[Collection] = []
|
|
192
|
+
for coll in collections:
|
|
193
|
+
canonical.append(_canonicalize_collection(_build_collection(coll)))
|
|
194
|
+
|
|
195
|
+
def coll_key(c: Collection) -> tuple[int, tuple[int, int]]:
|
|
196
|
+
rank = 10 ** 6
|
|
197
|
+
if mention_order and c.mention is not None and c.mention in mention_order:
|
|
198
|
+
rank = mention_order[c.mention]
|
|
199
|
+
return (rank, _point_sort_key(c))
|
|
200
|
+
|
|
201
|
+
canonical.sort(key=coll_key)
|
|
202
|
+
for coll in canonical:
|
|
203
|
+
tags.append(PointParser.serialize(coll))
|
|
204
|
+
return " ".join(tags)
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def annotate_image(image_obj: Any, annotations: Any) -> dict[str, Any]:
|
|
208
|
+
boxes: list[BoundingBox] = []
|
|
209
|
+
polys: list[Polygon] = []
|
|
210
|
+
points: list[SinglePoint] = []
|
|
211
|
+
collections: list[Collection] = []
|
|
212
|
+
|
|
213
|
+
if isinstance(annotations, Mapping):
|
|
214
|
+
for label, child_specs in annotations.items():
|
|
215
|
+
child_objs = [coerce_annotation(child) for child in child_specs]
|
|
216
|
+
collections.append(make_collection(child_objs, mention=str(label)))
|
|
217
|
+
else:
|
|
218
|
+
for item in annotations:
|
|
219
|
+
obj = coerce_annotation(item)
|
|
220
|
+
if isinstance(obj, BoundingBox):
|
|
221
|
+
boxes.append(obj)
|
|
222
|
+
elif isinstance(obj, Polygon):
|
|
223
|
+
polys.append(obj)
|
|
224
|
+
elif isinstance(obj, SinglePoint):
|
|
225
|
+
points.append(obj)
|
|
226
|
+
elif isinstance(obj, Collection):
|
|
227
|
+
collections.append(obj)
|
|
228
|
+
else:
|
|
229
|
+
raise BadRequestError(f"Unsupported annotation: {item!r}")
|
|
230
|
+
|
|
231
|
+
example: dict[str, Any] = {"image": image_obj}
|
|
232
|
+
if boxes:
|
|
233
|
+
boxes.sort(key=_point_sort_key)
|
|
234
|
+
example["boxes"] = boxes
|
|
235
|
+
if polys:
|
|
236
|
+
polys.sort(key=_point_sort_key)
|
|
237
|
+
example["polygons"] = polys
|
|
238
|
+
if points:
|
|
239
|
+
points.sort(key=_point_sort_key)
|
|
240
|
+
example["points"] = points
|
|
241
|
+
if collections:
|
|
242
|
+
canonical = [_canonicalize_collection(coll) for coll in collections]
|
|
243
|
+
canonical.sort(key=lambda c: ((c.mention or ""), _point_sort_key(c)))
|
|
244
|
+
example["collections"] = canonical
|
|
245
|
+
return example
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def canonicalize_text_collections(text: str | None) -> str | None:
|
|
249
|
+
if not text or "<collection" not in text:
|
|
250
|
+
return text
|
|
251
|
+
segments = parse_text(text)
|
|
252
|
+
parts: list[str] = []
|
|
253
|
+
idx = 0
|
|
254
|
+
for seg in segments:
|
|
255
|
+
start = seg["span"]["start"]
|
|
256
|
+
end = seg["span"]["end"]
|
|
257
|
+
if start > idx:
|
|
258
|
+
parts.append(text[idx:start])
|
|
259
|
+
if seg["kind"] == "collection":
|
|
260
|
+
canonical = _canonicalize_collection(seg["value"])
|
|
261
|
+
parts.append(PointParser.serialize(canonical))
|
|
262
|
+
else:
|
|
263
|
+
parts.append(text[start:end])
|
|
264
|
+
idx = end
|
|
265
|
+
if idx < len(text):
|
|
266
|
+
parts.append(text[idx:])
|
|
267
|
+
return "".join(parts)
|