rocket-welder-sdk 1.1.32__py3-none-any.whl → 1.1.34__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.
- rocket_welder_sdk/__init__.py +39 -6
- rocket_welder_sdk/controllers.py +138 -101
- rocket_welder_sdk/frame_metadata.py +138 -0
- rocket_welder_sdk/high_level/__init__.py +52 -0
- rocket_welder_sdk/high_level/client.py +262 -0
- rocket_welder_sdk/high_level/connection_strings.py +331 -0
- rocket_welder_sdk/high_level/data_context.py +169 -0
- rocket_welder_sdk/high_level/schema.py +197 -0
- rocket_welder_sdk/high_level/transport_protocol.py +238 -0
- rocket_welder_sdk/keypoints_protocol.py +642 -0
- rocket_welder_sdk/rocket_welder_client.py +94 -3
- rocket_welder_sdk/segmentation_result.py +420 -0
- rocket_welder_sdk/session_id.py +238 -0
- rocket_welder_sdk/transport/__init__.py +30 -0
- rocket_welder_sdk/transport/frame_sink.py +77 -0
- rocket_welder_sdk/transport/frame_source.py +74 -0
- rocket_welder_sdk/transport/nng_transport.py +197 -0
- rocket_welder_sdk/transport/stream_transport.py +193 -0
- rocket_welder_sdk/transport/tcp_transport.py +154 -0
- rocket_welder_sdk/transport/unix_socket_transport.py +339 -0
- {rocket_welder_sdk-1.1.32.dist-info → rocket_welder_sdk-1.1.34.dist-info}/METADATA +15 -2
- rocket_welder_sdk-1.1.34.dist-info/RECORD +39 -0
- rocket_welder_sdk-1.1.32.dist-info/RECORD +0 -22
- {rocket_welder_sdk-1.1.32.dist-info → rocket_welder_sdk-1.1.34.dist-info}/WHEEL +0 -0
- {rocket_welder_sdk-1.1.32.dist-info → rocket_welder_sdk-1.1.34.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,642 @@
|
|
|
1
|
+
"""KeyPoints protocol - Binary format for efficient keypoint storage.
|
|
2
|
+
|
|
3
|
+
Binary protocol for efficient streaming of keypoint detection results.
|
|
4
|
+
Compatible with C# implementation for cross-platform interoperability.
|
|
5
|
+
|
|
6
|
+
Protocol:
|
|
7
|
+
Frame Types:
|
|
8
|
+
- Master Frame (0x00): Full keypoint data every N frames
|
|
9
|
+
- Delta Frame (0x01): Delta-encoded changes from previous frame
|
|
10
|
+
|
|
11
|
+
Master Frame:
|
|
12
|
+
[FrameType: 1B=0x00][FrameId: 8B LE][KeypointCount: varint]
|
|
13
|
+
[KeypointId: varint][X: 4B LE][Y: 4B LE][Confidence: 2B LE]
|
|
14
|
+
[KeypointId: varint][X: 4B LE][Y: 4B LE][Confidence: 2B LE]
|
|
15
|
+
...
|
|
16
|
+
|
|
17
|
+
Delta Frame:
|
|
18
|
+
[FrameType: 1B=0x01][FrameId: 8B LE][KeypointCount: varint]
|
|
19
|
+
[KeypointId: varint][DeltaX: zigzag varint][DeltaY: zigzag varint][DeltaConf: zigzag varint]
|
|
20
|
+
[KeypointId: varint][DeltaX: zigzag varint][DeltaY: zigzag varint][DeltaConf: zigzag varint]
|
|
21
|
+
...
|
|
22
|
+
|
|
23
|
+
JSON Definition:
|
|
24
|
+
{
|
|
25
|
+
"version": "1.0",
|
|
26
|
+
"compute_module_name": "YOLOv8-Pose",
|
|
27
|
+
"points": {
|
|
28
|
+
"nose": 0,
|
|
29
|
+
"left_eye": 1,
|
|
30
|
+
"right_eye": 2,
|
|
31
|
+
...
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
Features:
|
|
36
|
+
- Master/delta frame compression for temporal sequences
|
|
37
|
+
- Varint encoding for efficient integer compression
|
|
38
|
+
- ZigZag encoding for signed deltas
|
|
39
|
+
- Confidence stored as ushort (0-10000) internally, float (0.0-1.0) in API
|
|
40
|
+
- Explicit little-endian for cross-platform compatibility
|
|
41
|
+
- Default master frame interval: every 300 frames
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
import io
|
|
45
|
+
import json
|
|
46
|
+
import struct
|
|
47
|
+
from abc import ABC, abstractmethod
|
|
48
|
+
from dataclasses import dataclass
|
|
49
|
+
from typing import BinaryIO, Callable, Dict, Iterator, List, Optional, Tuple
|
|
50
|
+
|
|
51
|
+
import numpy as np
|
|
52
|
+
import numpy.typing as npt
|
|
53
|
+
from typing_extensions import TypeAlias
|
|
54
|
+
|
|
55
|
+
from .transport import IFrameSink, StreamFrameSink, StreamFrameSource
|
|
56
|
+
|
|
57
|
+
# Type aliases
|
|
58
|
+
Point = Tuple[int, int]
|
|
59
|
+
PointArray: TypeAlias = npt.NDArray[np.int32] # Shape: (N, 2)
|
|
60
|
+
|
|
61
|
+
# Frame types
|
|
62
|
+
MASTER_FRAME_TYPE = 0x00
|
|
63
|
+
DELTA_FRAME_TYPE = 0x01
|
|
64
|
+
|
|
65
|
+
# Confidence encoding constants
|
|
66
|
+
CONFIDENCE_SCALE = 10000.0
|
|
67
|
+
CONFIDENCE_MAX = 10000
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _write_varint(stream: BinaryIO, value: int) -> None:
|
|
71
|
+
"""Write unsigned integer as varint."""
|
|
72
|
+
if value < 0:
|
|
73
|
+
raise ValueError(f"Varint requires non-negative value, got {value}")
|
|
74
|
+
|
|
75
|
+
while value >= 0x80:
|
|
76
|
+
stream.write(bytes([value & 0x7F | 0x80]))
|
|
77
|
+
value >>= 7
|
|
78
|
+
stream.write(bytes([value & 0x7F]))
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _read_varint(stream: BinaryIO) -> int:
|
|
82
|
+
"""Read varint from stream and decode to unsigned integer."""
|
|
83
|
+
result = 0
|
|
84
|
+
shift = 0
|
|
85
|
+
|
|
86
|
+
while True:
|
|
87
|
+
if shift >= 35: # Max 5 bytes for uint32
|
|
88
|
+
raise ValueError("Varint too long (corrupted stream)")
|
|
89
|
+
|
|
90
|
+
byte_data = stream.read(1)
|
|
91
|
+
if not byte_data:
|
|
92
|
+
raise EOFError("Unexpected end of stream reading varint")
|
|
93
|
+
|
|
94
|
+
byte = byte_data[0]
|
|
95
|
+
result |= (byte & 0x7F) << shift
|
|
96
|
+
shift += 7
|
|
97
|
+
|
|
98
|
+
if not (byte & 0x80):
|
|
99
|
+
break
|
|
100
|
+
|
|
101
|
+
return result
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _zigzag_encode(value: int) -> int:
|
|
105
|
+
"""ZigZag encode signed integer to unsigned."""
|
|
106
|
+
return (value << 1) ^ (value >> 31)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _zigzag_decode(value: int) -> int:
|
|
110
|
+
"""ZigZag decode unsigned integer to signed."""
|
|
111
|
+
return (value >> 1) ^ -(value & 1)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def _confidence_to_ushort(confidence: float) -> int:
|
|
115
|
+
"""Convert confidence float (0.0-1.0) to ushort (0-10000)."""
|
|
116
|
+
return min(max(int(confidence * CONFIDENCE_SCALE), 0), CONFIDENCE_MAX)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _confidence_from_ushort(confidence_ushort: int) -> float:
|
|
120
|
+
"""Convert confidence ushort (0-10000) to float (0.0-1.0)."""
|
|
121
|
+
return confidence_ushort / CONFIDENCE_SCALE
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
@dataclass(frozen=True)
|
|
125
|
+
class KeyPoint:
|
|
126
|
+
"""A single keypoint with position and confidence."""
|
|
127
|
+
|
|
128
|
+
keypoint_id: int
|
|
129
|
+
x: int
|
|
130
|
+
y: int
|
|
131
|
+
confidence: float # 0.0 to 1.0
|
|
132
|
+
|
|
133
|
+
def __post_init__(self) -> None:
|
|
134
|
+
"""Validate keypoint data."""
|
|
135
|
+
if not 0.0 <= self.confidence <= 1.0:
|
|
136
|
+
raise ValueError(f"Confidence must be in [0.0, 1.0], got {self.confidence}")
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
@dataclass(frozen=True)
|
|
140
|
+
class KeyPointsDefinition:
|
|
141
|
+
"""JSON definition mapping keypoint names to IDs."""
|
|
142
|
+
|
|
143
|
+
version: str
|
|
144
|
+
compute_module_name: str
|
|
145
|
+
points: Dict[str, int] # name -> keypoint_id
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
class IKeyPointsWriter(ABC):
|
|
149
|
+
"""Interface for writing keypoints data for a single frame."""
|
|
150
|
+
|
|
151
|
+
@abstractmethod
|
|
152
|
+
def append(self, keypoint_id: int, x: int, y: int, confidence: float) -> None:
|
|
153
|
+
"""Append a keypoint to this frame."""
|
|
154
|
+
pass
|
|
155
|
+
|
|
156
|
+
@abstractmethod
|
|
157
|
+
def append_point(self, keypoint_id: int, point: Point, confidence: float) -> None:
|
|
158
|
+
"""Append a keypoint using a Point tuple."""
|
|
159
|
+
pass
|
|
160
|
+
|
|
161
|
+
@abstractmethod
|
|
162
|
+
def close(self) -> None:
|
|
163
|
+
"""Flush and close the writer."""
|
|
164
|
+
pass
|
|
165
|
+
|
|
166
|
+
def __enter__(self) -> "IKeyPointsWriter":
|
|
167
|
+
"""Context manager entry."""
|
|
168
|
+
return self
|
|
169
|
+
|
|
170
|
+
def __exit__(self, *args: object) -> None:
|
|
171
|
+
"""Context manager exit."""
|
|
172
|
+
self.close()
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
class KeyPointsWriter(IKeyPointsWriter):
|
|
176
|
+
"""
|
|
177
|
+
Writes keypoints data for a single frame via IFrameSink.
|
|
178
|
+
|
|
179
|
+
Supports master and delta frame encoding for efficient compression.
|
|
180
|
+
Frames are buffered in memory and written atomically on close.
|
|
181
|
+
|
|
182
|
+
Thread-safe: No (caller must synchronize)
|
|
183
|
+
"""
|
|
184
|
+
|
|
185
|
+
def __init__(
|
|
186
|
+
self,
|
|
187
|
+
frame_id: int,
|
|
188
|
+
frame_sink: IFrameSink,
|
|
189
|
+
is_delta: bool,
|
|
190
|
+
previous_frame: Optional[Dict[int, Tuple[Point, int]]] = None,
|
|
191
|
+
on_frame_written: Optional[Callable[[Dict[int, Tuple[Point, int]]], None]] = None,
|
|
192
|
+
) -> None:
|
|
193
|
+
"""
|
|
194
|
+
Initialize writer for a single frame.
|
|
195
|
+
|
|
196
|
+
Args:
|
|
197
|
+
frame_id: Unique frame identifier
|
|
198
|
+
frame_sink: IFrameSink to write frame to
|
|
199
|
+
is_delta: True for delta frame, False for master frame
|
|
200
|
+
previous_frame: Previous frame state (required for delta frames)
|
|
201
|
+
on_frame_written: Callback with frame state after writing
|
|
202
|
+
"""
|
|
203
|
+
if is_delta and previous_frame is None:
|
|
204
|
+
raise ValueError("Delta frame requires previous_frame")
|
|
205
|
+
|
|
206
|
+
self._frame_id = frame_id
|
|
207
|
+
self._frame_sink = frame_sink
|
|
208
|
+
self._buffer = io.BytesIO() # Buffer frame for atomic write
|
|
209
|
+
self._is_delta = is_delta
|
|
210
|
+
self._previous_frame = previous_frame
|
|
211
|
+
self._on_frame_written = on_frame_written
|
|
212
|
+
self._keypoints: List[Tuple[int, int, int, int]] = [] # (id, x, y, conf_ushort)
|
|
213
|
+
self._disposed = False
|
|
214
|
+
|
|
215
|
+
def append(self, keypoint_id: int, x: int, y: int, confidence: float) -> None:
|
|
216
|
+
"""
|
|
217
|
+
Append a keypoint to this frame.
|
|
218
|
+
|
|
219
|
+
Args:
|
|
220
|
+
keypoint_id: Unique keypoint identifier
|
|
221
|
+
x: X coordinate
|
|
222
|
+
y: Y coordinate
|
|
223
|
+
confidence: Confidence score (0.0 to 1.0)
|
|
224
|
+
|
|
225
|
+
Raises:
|
|
226
|
+
ValueError: If confidence is out of range
|
|
227
|
+
"""
|
|
228
|
+
if self._disposed:
|
|
229
|
+
raise ValueError("Writer is disposed")
|
|
230
|
+
|
|
231
|
+
if not 0.0 <= confidence <= 1.0:
|
|
232
|
+
raise ValueError(f"Confidence must be in [0.0, 1.0], got {confidence}")
|
|
233
|
+
|
|
234
|
+
confidence_ushort = _confidence_to_ushort(confidence)
|
|
235
|
+
self._keypoints.append((keypoint_id, x, y, confidence_ushort))
|
|
236
|
+
|
|
237
|
+
def append_point(self, keypoint_id: int, point: Point, confidence: float) -> None:
|
|
238
|
+
"""Append a keypoint using a Point tuple."""
|
|
239
|
+
self.append(keypoint_id, point[0], point[1], confidence)
|
|
240
|
+
|
|
241
|
+
def _write_frame(self) -> None:
|
|
242
|
+
"""Write frame to buffer."""
|
|
243
|
+
# Write frame type
|
|
244
|
+
self._buffer.write(bytes([DELTA_FRAME_TYPE if self._is_delta else MASTER_FRAME_TYPE]))
|
|
245
|
+
|
|
246
|
+
# Write frame ID (8 bytes, little-endian)
|
|
247
|
+
self._buffer.write(struct.pack("<Q", self._frame_id))
|
|
248
|
+
|
|
249
|
+
# Write keypoint count
|
|
250
|
+
_write_varint(self._buffer, len(self._keypoints))
|
|
251
|
+
|
|
252
|
+
if self._is_delta and self._previous_frame is not None:
|
|
253
|
+
self._write_delta_keypoints()
|
|
254
|
+
else:
|
|
255
|
+
self._write_master_keypoints()
|
|
256
|
+
|
|
257
|
+
def _write_master_keypoints(self) -> None:
|
|
258
|
+
"""Write keypoints in master frame format (absolute coordinates)."""
|
|
259
|
+
for keypoint_id, x, y, conf_ushort in self._keypoints:
|
|
260
|
+
# Write keypoint ID
|
|
261
|
+
_write_varint(self._buffer, keypoint_id)
|
|
262
|
+
|
|
263
|
+
# Write absolute coordinates (4 bytes each, little-endian)
|
|
264
|
+
self._buffer.write(struct.pack("<i", x))
|
|
265
|
+
self._buffer.write(struct.pack("<i", y))
|
|
266
|
+
|
|
267
|
+
# Write confidence (2 bytes, little-endian)
|
|
268
|
+
self._buffer.write(struct.pack("<H", conf_ushort))
|
|
269
|
+
|
|
270
|
+
def _write_delta_keypoints(self) -> None:
|
|
271
|
+
"""Write keypoints in delta frame format (delta from previous)."""
|
|
272
|
+
assert self._previous_frame is not None
|
|
273
|
+
|
|
274
|
+
for keypoint_id, x, y, conf_ushort in self._keypoints:
|
|
275
|
+
# Write keypoint ID
|
|
276
|
+
_write_varint(self._buffer, keypoint_id)
|
|
277
|
+
|
|
278
|
+
# Calculate deltas
|
|
279
|
+
if keypoint_id in self._previous_frame:
|
|
280
|
+
prev_point, prev_conf = self._previous_frame[keypoint_id]
|
|
281
|
+
delta_x = x - prev_point[0]
|
|
282
|
+
delta_y = y - prev_point[1]
|
|
283
|
+
delta_conf = conf_ushort - prev_conf
|
|
284
|
+
else:
|
|
285
|
+
# New keypoint - write as absolute
|
|
286
|
+
delta_x = x
|
|
287
|
+
delta_y = y
|
|
288
|
+
delta_conf = conf_ushort
|
|
289
|
+
|
|
290
|
+
# Write zigzag-encoded deltas
|
|
291
|
+
_write_varint(self._buffer, _zigzag_encode(delta_x))
|
|
292
|
+
_write_varint(self._buffer, _zigzag_encode(delta_y))
|
|
293
|
+
_write_varint(self._buffer, _zigzag_encode(delta_conf))
|
|
294
|
+
|
|
295
|
+
def close(self) -> None:
|
|
296
|
+
"""Close writer and flush data via frame sink."""
|
|
297
|
+
if self._disposed:
|
|
298
|
+
return
|
|
299
|
+
|
|
300
|
+
self._disposed = True
|
|
301
|
+
|
|
302
|
+
# Write frame to buffer
|
|
303
|
+
self._write_frame()
|
|
304
|
+
|
|
305
|
+
# Write buffered frame atomically via sink
|
|
306
|
+
frame_data = self._buffer.getvalue()
|
|
307
|
+
self._frame_sink.write_frame(frame_data)
|
|
308
|
+
|
|
309
|
+
# Update previous frame state via callback
|
|
310
|
+
if self._on_frame_written is not None:
|
|
311
|
+
frame_state: Dict[int, Tuple[Point, int]] = {}
|
|
312
|
+
for keypoint_id, x, y, conf_ushort in self._keypoints:
|
|
313
|
+
frame_state[keypoint_id] = ((x, y), conf_ushort)
|
|
314
|
+
self._on_frame_written(frame_state)
|
|
315
|
+
|
|
316
|
+
# Clean up buffer
|
|
317
|
+
self._buffer.close()
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
class KeyPointsSeries:
|
|
321
|
+
"""
|
|
322
|
+
In-memory representation of keypoints series for efficient querying.
|
|
323
|
+
|
|
324
|
+
Provides fast lookup by frame ID and keypoint trajectory queries.
|
|
325
|
+
"""
|
|
326
|
+
|
|
327
|
+
def __init__(
|
|
328
|
+
self,
|
|
329
|
+
version: str,
|
|
330
|
+
compute_module_name: str,
|
|
331
|
+
points: Dict[str, int],
|
|
332
|
+
index: Dict[int, Dict[int, Tuple[Point, float]]],
|
|
333
|
+
) -> None:
|
|
334
|
+
"""
|
|
335
|
+
Initialize keypoints series.
|
|
336
|
+
|
|
337
|
+
Args:
|
|
338
|
+
version: Version of keypoints algorithm/model
|
|
339
|
+
compute_module_name: Name of AI model or assembly
|
|
340
|
+
points: Mapping of keypoint name to ID
|
|
341
|
+
index: Frame ID -> (Keypoint ID -> (Point, confidence))
|
|
342
|
+
"""
|
|
343
|
+
self.version = version
|
|
344
|
+
self.compute_module_name = compute_module_name
|
|
345
|
+
self.points = points
|
|
346
|
+
self._index = index
|
|
347
|
+
|
|
348
|
+
@property
|
|
349
|
+
def frame_ids(self) -> List[int]:
|
|
350
|
+
"""Get all frame IDs in the series."""
|
|
351
|
+
return list(self._index.keys())
|
|
352
|
+
|
|
353
|
+
def contains_frame(self, frame_id: int) -> bool:
|
|
354
|
+
"""Check if a frame exists in the series."""
|
|
355
|
+
return frame_id in self._index
|
|
356
|
+
|
|
357
|
+
def get_frame(self, frame_id: int) -> Optional[Dict[int, Tuple[Point, float]]]:
|
|
358
|
+
"""
|
|
359
|
+
Get all keypoints for a specific frame.
|
|
360
|
+
|
|
361
|
+
Args:
|
|
362
|
+
frame_id: Frame identifier
|
|
363
|
+
|
|
364
|
+
Returns:
|
|
365
|
+
Dictionary mapping keypoint ID to (point, confidence), or None if not found
|
|
366
|
+
"""
|
|
367
|
+
return self._index.get(frame_id)
|
|
368
|
+
|
|
369
|
+
def get_keypoint(self, frame_id: int, keypoint_id: int) -> Optional[Tuple[Point, float]]:
|
|
370
|
+
"""
|
|
371
|
+
Get keypoint position and confidence at specific frame.
|
|
372
|
+
|
|
373
|
+
Args:
|
|
374
|
+
frame_id: Frame identifier
|
|
375
|
+
keypoint_id: Keypoint identifier
|
|
376
|
+
|
|
377
|
+
Returns:
|
|
378
|
+
(point, confidence) tuple or None if not found
|
|
379
|
+
"""
|
|
380
|
+
frame = self._index.get(frame_id)
|
|
381
|
+
if frame is None:
|
|
382
|
+
return None
|
|
383
|
+
return frame.get(keypoint_id)
|
|
384
|
+
|
|
385
|
+
def get_keypoint_by_name(
|
|
386
|
+
self, frame_id: int, keypoint_name: str
|
|
387
|
+
) -> Optional[Tuple[Point, float]]:
|
|
388
|
+
"""
|
|
389
|
+
Get keypoint position and confidence at specific frame by name.
|
|
390
|
+
|
|
391
|
+
Args:
|
|
392
|
+
frame_id: Frame identifier
|
|
393
|
+
keypoint_name: Keypoint name (e.g., "nose")
|
|
394
|
+
|
|
395
|
+
Returns:
|
|
396
|
+
(point, confidence) tuple or None if not found
|
|
397
|
+
"""
|
|
398
|
+
keypoint_id = self.points.get(keypoint_name)
|
|
399
|
+
if keypoint_id is None:
|
|
400
|
+
return None
|
|
401
|
+
return self.get_keypoint(frame_id, keypoint_id)
|
|
402
|
+
|
|
403
|
+
def get_keypoint_trajectory(self, keypoint_id: int) -> Iterator[Tuple[int, Point, float]]:
|
|
404
|
+
"""
|
|
405
|
+
Get trajectory of a specific keypoint across all frames.
|
|
406
|
+
|
|
407
|
+
Args:
|
|
408
|
+
keypoint_id: Keypoint identifier
|
|
409
|
+
|
|
410
|
+
Yields:
|
|
411
|
+
(frame_id, point, confidence) tuples
|
|
412
|
+
"""
|
|
413
|
+
for frame_id, keypoints in self._index.items():
|
|
414
|
+
if keypoint_id in keypoints:
|
|
415
|
+
point, confidence = keypoints[keypoint_id]
|
|
416
|
+
yield (frame_id, point, confidence)
|
|
417
|
+
|
|
418
|
+
def get_keypoint_trajectory_by_name(
|
|
419
|
+
self, keypoint_name: str
|
|
420
|
+
) -> Iterator[Tuple[int, Point, float]]:
|
|
421
|
+
"""
|
|
422
|
+
Get trajectory of a specific keypoint by name across all frames.
|
|
423
|
+
|
|
424
|
+
Args:
|
|
425
|
+
keypoint_name: Keypoint name (e.g., "nose")
|
|
426
|
+
|
|
427
|
+
Yields:
|
|
428
|
+
(frame_id, point, confidence) tuples
|
|
429
|
+
"""
|
|
430
|
+
keypoint_id = self.points.get(keypoint_name)
|
|
431
|
+
if keypoint_id is None:
|
|
432
|
+
return
|
|
433
|
+
|
|
434
|
+
yield from self.get_keypoint_trajectory(keypoint_id)
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
class IKeyPointsSink(ABC):
|
|
438
|
+
"""Interface for creating keypoints writers and reading keypoints data."""
|
|
439
|
+
|
|
440
|
+
@abstractmethod
|
|
441
|
+
def create_writer(self, frame_id: int) -> IKeyPointsWriter:
|
|
442
|
+
"""
|
|
443
|
+
Create a writer for the current frame.
|
|
444
|
+
|
|
445
|
+
Sink decides whether to write master or delta frame.
|
|
446
|
+
|
|
447
|
+
Args:
|
|
448
|
+
frame_id: Unique frame identifier
|
|
449
|
+
|
|
450
|
+
Returns:
|
|
451
|
+
KeyPoints writer for this frame
|
|
452
|
+
"""
|
|
453
|
+
pass
|
|
454
|
+
|
|
455
|
+
@staticmethod
|
|
456
|
+
@abstractmethod
|
|
457
|
+
def read(json_definition: str, blob_stream: BinaryIO) -> KeyPointsSeries:
|
|
458
|
+
"""
|
|
459
|
+
Read entire keypoints series into memory for efficient querying.
|
|
460
|
+
|
|
461
|
+
Args:
|
|
462
|
+
json_definition: JSON definition string mapping keypoint names to IDs
|
|
463
|
+
blob_stream: Binary stream containing keypoints data
|
|
464
|
+
|
|
465
|
+
Returns:
|
|
466
|
+
KeyPointsSeries for in-memory queries
|
|
467
|
+
"""
|
|
468
|
+
pass
|
|
469
|
+
|
|
470
|
+
|
|
471
|
+
class KeyPointsSink(IKeyPointsSink):
|
|
472
|
+
"""
|
|
473
|
+
Transport-agnostic keypoints sink with master/delta frame compression.
|
|
474
|
+
|
|
475
|
+
Manages master frame intervals and provides reading/writing functionality.
|
|
476
|
+
|
|
477
|
+
Thread-safe: No (caller must synchronize)
|
|
478
|
+
"""
|
|
479
|
+
|
|
480
|
+
def __init__(
|
|
481
|
+
self,
|
|
482
|
+
stream: Optional[BinaryIO] = None,
|
|
483
|
+
master_frame_interval: int = 300,
|
|
484
|
+
*,
|
|
485
|
+
frame_sink: Optional[IFrameSink] = None,
|
|
486
|
+
owns_sink: bool = False,
|
|
487
|
+
) -> None:
|
|
488
|
+
"""
|
|
489
|
+
Initialize keypoints sink.
|
|
490
|
+
|
|
491
|
+
Args:
|
|
492
|
+
stream: BinaryIO stream (convenience - auto-wraps in StreamFrameSink)
|
|
493
|
+
master_frame_interval: Write master frame every N frames (default: 300)
|
|
494
|
+
frame_sink: IFrameSink to write frames to (keyword-only, transport-agnostic)
|
|
495
|
+
owns_sink: If True, closes the sink on disposal (keyword-only)
|
|
496
|
+
|
|
497
|
+
Note:
|
|
498
|
+
Either stream or frame_sink must be provided (not both).
|
|
499
|
+
For convenience, stream is the primary parameter (auto-wraps in StreamFrameSink).
|
|
500
|
+
For transport-agnostic usage, use frame_sink= keyword argument.
|
|
501
|
+
"""
|
|
502
|
+
if frame_sink is None and stream is None:
|
|
503
|
+
raise TypeError("Either stream or frame_sink must be provided")
|
|
504
|
+
|
|
505
|
+
if frame_sink is not None and stream is not None:
|
|
506
|
+
raise TypeError("Cannot provide both stream and frame_sink")
|
|
507
|
+
|
|
508
|
+
if master_frame_interval < 1:
|
|
509
|
+
raise ValueError("master_frame_interval must be >= 1")
|
|
510
|
+
|
|
511
|
+
# Convenience: auto-wrap stream in StreamFrameSink
|
|
512
|
+
if stream is not None:
|
|
513
|
+
self._frame_sink: IFrameSink = StreamFrameSink(stream, leave_open=False)
|
|
514
|
+
self._owns_sink = True
|
|
515
|
+
else:
|
|
516
|
+
assert frame_sink is not None
|
|
517
|
+
self._frame_sink = frame_sink
|
|
518
|
+
self._owns_sink = owns_sink
|
|
519
|
+
|
|
520
|
+
self._master_frame_interval = master_frame_interval
|
|
521
|
+
self._previous_frame: Optional[Dict[int, Tuple[Point, int]]] = None
|
|
522
|
+
self._frame_count = 0
|
|
523
|
+
|
|
524
|
+
def create_writer(self, frame_id: int) -> IKeyPointsWriter:
|
|
525
|
+
"""Create a writer for the current frame."""
|
|
526
|
+
is_delta = self._frame_count > 0 and (self._frame_count % self._master_frame_interval) != 0
|
|
527
|
+
|
|
528
|
+
def on_frame_written(frame_state: Dict[int, Tuple[Point, int]]) -> None:
|
|
529
|
+
self._previous_frame = frame_state
|
|
530
|
+
|
|
531
|
+
writer = KeyPointsWriter(
|
|
532
|
+
frame_id=frame_id,
|
|
533
|
+
frame_sink=self._frame_sink,
|
|
534
|
+
is_delta=is_delta,
|
|
535
|
+
previous_frame=self._previous_frame if is_delta else None,
|
|
536
|
+
on_frame_written=on_frame_written,
|
|
537
|
+
)
|
|
538
|
+
|
|
539
|
+
self._frame_count += 1
|
|
540
|
+
return writer
|
|
541
|
+
|
|
542
|
+
@staticmethod
|
|
543
|
+
def read(json_definition: str, blob_stream: BinaryIO) -> KeyPointsSeries:
|
|
544
|
+
"""Read entire keypoints series into memory."""
|
|
545
|
+
# Parse JSON definition
|
|
546
|
+
definition_dict = json.loads(json_definition)
|
|
547
|
+
version = definition_dict.get("version", "1.0")
|
|
548
|
+
compute_module_name = definition_dict.get("compute_module_name", "")
|
|
549
|
+
points = definition_dict.get("points", {})
|
|
550
|
+
|
|
551
|
+
# Use StreamFrameSource to handle varint-prefixed frames
|
|
552
|
+
frame_source = StreamFrameSource(blob_stream, leave_open=True)
|
|
553
|
+
|
|
554
|
+
# Read all frames from binary stream
|
|
555
|
+
index: Dict[int, Dict[int, Tuple[Point, float]]] = {}
|
|
556
|
+
current_frame: Dict[int, Tuple[Point, int]] = {}
|
|
557
|
+
|
|
558
|
+
while True:
|
|
559
|
+
# Read next frame (handles varint length prefix)
|
|
560
|
+
frame_data = frame_source.read_frame()
|
|
561
|
+
if frame_data is None or len(frame_data) == 0:
|
|
562
|
+
break # End of stream
|
|
563
|
+
|
|
564
|
+
# Parse frame from bytes
|
|
565
|
+
frame_stream = io.BytesIO(frame_data)
|
|
566
|
+
|
|
567
|
+
# Read frame type
|
|
568
|
+
frame_type_bytes = frame_stream.read(1)
|
|
569
|
+
if not frame_type_bytes:
|
|
570
|
+
break # End of stream
|
|
571
|
+
|
|
572
|
+
frame_type = frame_type_bytes[0]
|
|
573
|
+
if frame_type == 0xFF:
|
|
574
|
+
break # End-of-stream marker
|
|
575
|
+
|
|
576
|
+
# Read frame ID
|
|
577
|
+
frame_id_bytes = frame_stream.read(8)
|
|
578
|
+
if len(frame_id_bytes) != 8:
|
|
579
|
+
raise EOFError("Failed to read frame ID")
|
|
580
|
+
frame_id = struct.unpack("<Q", frame_id_bytes)[0]
|
|
581
|
+
|
|
582
|
+
# Read keypoint count
|
|
583
|
+
keypoint_count = _read_varint(frame_stream)
|
|
584
|
+
|
|
585
|
+
frame_keypoints: Dict[int, Tuple[Point, float]] = {}
|
|
586
|
+
|
|
587
|
+
if frame_type == MASTER_FRAME_TYPE:
|
|
588
|
+
# Master frame - read absolute coordinates
|
|
589
|
+
current_frame.clear()
|
|
590
|
+
for _ in range(keypoint_count):
|
|
591
|
+
keypoint_id = _read_varint(frame_stream)
|
|
592
|
+
|
|
593
|
+
# Read absolute coordinates
|
|
594
|
+
x_bytes = frame_stream.read(4)
|
|
595
|
+
y_bytes = frame_stream.read(4)
|
|
596
|
+
if len(x_bytes) != 4 or len(y_bytes) != 4:
|
|
597
|
+
raise EOFError("Failed to read coordinates")
|
|
598
|
+
|
|
599
|
+
x = struct.unpack("<i", x_bytes)[0]
|
|
600
|
+
y = struct.unpack("<i", y_bytes)[0]
|
|
601
|
+
|
|
602
|
+
# Read confidence
|
|
603
|
+
conf_bytes = frame_stream.read(2)
|
|
604
|
+
if len(conf_bytes) != 2:
|
|
605
|
+
raise EOFError("Failed to read confidence")
|
|
606
|
+
conf_ushort = struct.unpack("<H", conf_bytes)[0]
|
|
607
|
+
|
|
608
|
+
point = (x, y)
|
|
609
|
+
current_frame[keypoint_id] = (point, conf_ushort)
|
|
610
|
+
frame_keypoints[keypoint_id] = (point, _confidence_from_ushort(conf_ushort))
|
|
611
|
+
|
|
612
|
+
elif frame_type == DELTA_FRAME_TYPE:
|
|
613
|
+
# Delta frame - read deltas and reconstruct
|
|
614
|
+
for _ in range(keypoint_count):
|
|
615
|
+
keypoint_id = _read_varint(frame_stream)
|
|
616
|
+
|
|
617
|
+
delta_x = _zigzag_decode(_read_varint(frame_stream))
|
|
618
|
+
delta_y = _zigzag_decode(_read_varint(frame_stream))
|
|
619
|
+
delta_conf = _zigzag_decode(_read_varint(frame_stream))
|
|
620
|
+
|
|
621
|
+
if keypoint_id in current_frame:
|
|
622
|
+
# Apply delta to previous
|
|
623
|
+
prev_point, prev_conf = current_frame[keypoint_id]
|
|
624
|
+
x = prev_point[0] + delta_x
|
|
625
|
+
y = prev_point[1] + delta_y
|
|
626
|
+
conf_ushort = max(0, min(CONFIDENCE_MAX, prev_conf + delta_conf))
|
|
627
|
+
else:
|
|
628
|
+
# New keypoint - deltas are absolute
|
|
629
|
+
x = delta_x
|
|
630
|
+
y = delta_y
|
|
631
|
+
conf_ushort = max(0, min(CONFIDENCE_MAX, delta_conf))
|
|
632
|
+
|
|
633
|
+
point = (x, y)
|
|
634
|
+
current_frame[keypoint_id] = (point, conf_ushort)
|
|
635
|
+
frame_keypoints[keypoint_id] = (point, _confidence_from_ushort(conf_ushort))
|
|
636
|
+
|
|
637
|
+
else:
|
|
638
|
+
raise ValueError(f"Unknown frame type: {frame_type}")
|
|
639
|
+
|
|
640
|
+
index[frame_id] = frame_keypoints
|
|
641
|
+
|
|
642
|
+
return KeyPointsSeries(version, compute_module_name, points, index)
|