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.
@@ -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)