pablovela5620 Claude Fable 5 commited on
Commit
b84a490
·
1 Parent(s): c57dcae

Give the ViTPose keypoints a COCO-17 annotation context

Browse files

Named, side-colored joints (left blue, right orange) with the COCO
skeleton drawn between them, low-confidence joints filtered out, labels
on hover only — the stage previously rendered seventeen identical dots.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

fdanyone/rerun_streaming.py CHANGED
@@ -38,9 +38,9 @@ from fdanyone.video import choose_canonical_fps
38
  from fdanyone.viz import (
39
  BOX_COLOR,
40
  DIFFUSION_TIMELINE,
41
- KEYPOINT_COLOR,
42
  VIEWS,
43
  body_blueprint,
 
44
  diffusion_blueprint,
45
  log_body,
46
  log_result,
@@ -362,6 +362,10 @@ def pump(session: Session, work: Callable[[], T]) -> Iterator[str]:
362
  return outcome[0]
363
 
364
 
 
 
 
 
365
  def _motion_hook(
366
  recording: rr.RecordingStream, session: Session
367
  ) -> Callable[[str, dict[str, object]], None]:
@@ -381,11 +385,23 @@ def _motion_hook(
381
  )
382
  elif stage == "keypoints_2d":
383
  keypoints: Float[torch.Tensor, "frames joints 3"] = payload["kp2d"] # pyrefly: ignore
 
 
 
384
  for index, frame in enumerate(keypoints.numpy()[: INFERENCE.num_frames]):
385
  set_frame_on_recording(recording, index, session.spec.fps)
 
386
  recording.log(
387
  "world/camera/video/keypoints",
388
- rr.Points2D(positions=frame[:, :2], radii=6.0, colors=KEYPOINT_COLOR),
 
 
 
 
 
 
 
 
389
  )
390
  log_status(recording, f"motion: {stage}")
391
  # Only the ``:start`` announcements become user-facing labels; the
 
38
  from fdanyone.viz import (
39
  BOX_COLOR,
40
  DIFFUSION_TIMELINE,
 
41
  VIEWS,
42
  body_blueprint,
43
+ coco17_annotation_context,
44
  diffusion_blueprint,
45
  log_body,
46
  log_result,
 
362
  return outcome[0]
363
 
364
 
365
+ KEYPOINT_CONFIDENCE: float = 0.3
366
+ """ViTPose confidence below which a joint is occlusion noise, not a detection."""
367
+
368
+
369
  def _motion_hook(
370
  recording: rr.RecordingStream, session: Session
371
  ) -> Callable[[str, dict[str, object]], None]:
 
385
  )
386
  elif stage == "keypoints_2d":
387
  keypoints: Float[torch.Tensor, "frames joints 3"] = payload["kp2d"] # pyrefly: ignore
388
+ # Named, side-colored joints with the COCO skeleton drawn between
389
+ # them; the class ids resolve against this static context.
390
+ recording.log("world/camera/video/keypoints", coco17_annotation_context(), static=True)
391
  for index, frame in enumerate(keypoints.numpy()[: INFERENCE.num_frames]):
392
  set_frame_on_recording(recording, index, session.spec.fps)
393
+ confident: np.ndarray = frame[:, 2] > KEYPOINT_CONFIDENCE
394
  recording.log(
395
  "world/camera/video/keypoints",
396
+ rr.Points2D(
397
+ positions=frame[confident, :2],
398
+ radii=6.0,
399
+ class_ids=0,
400
+ keypoint_ids=np.flatnonzero(confident),
401
+ # Names on seventeen joints at once bury the clip;
402
+ # hovering a joint still shows its label.
403
+ show_labels=False,
404
+ ),
405
  )
406
  log_status(recording, f"motion: {stage}")
407
  # Only the ``:start`` announcements become user-facing labels; the
fdanyone/viz.py CHANGED
@@ -42,6 +42,68 @@ KEYPOINT_COLOR: tuple[int, int, int] = (248, 129, 81)
42
  JOINT_COLOR: tuple[int, int, int] = (255, 212, 59)
43
  BONE_COLOR: tuple[int, int, int] = (116, 192, 252)
44
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
45
 
46
  def set_frame(index: int, fps: Fraction) -> None:
47
  """Put the next log calls on the shared frame and seconds timelines."""
 
42
  JOINT_COLOR: tuple[int, int, int] = (255, 212, 59)
43
  BONE_COLOR: tuple[int, int, int] = (116, 192, 252)
44
 
45
+ COCO17_KEYPOINTS: tuple[str, ...] = (
46
+ "nose",
47
+ "left_eye",
48
+ "right_eye",
49
+ "left_ear",
50
+ "right_ear",
51
+ "left_shoulder",
52
+ "right_shoulder",
53
+ "left_elbow",
54
+ "right_elbow",
55
+ "left_wrist",
56
+ "right_wrist",
57
+ "left_hip",
58
+ "right_hip",
59
+ "left_knee",
60
+ "right_knee",
61
+ "left_ankle",
62
+ "right_ankle",
63
+ )
64
+ """ViTPose's COCO-17 joint order; GVHMR's ``kp2d`` rows follow it."""
65
+
66
+ COCO17_CONNECTIONS: tuple[tuple[int, int], ...] = (
67
+ (0, 1), (0, 2), (1, 3), (2, 4),
68
+ (3, 5), (4, 6), (5, 6), (5, 7), (6, 8), (7, 9), (8, 10),
69
+ (5, 11), (6, 12), (11, 12), (11, 13), (12, 14), (13, 15), (14, 16),
70
+ )
71
+ """The COCO skeleton over those joints: face, arms, torso, legs."""
72
+
73
+ LEFT_COLOR: tuple[int, int, int] = (77, 171, 247)
74
+ RIGHT_COLOR: tuple[int, int, int] = (255, 146, 43)
75
+ CENTER_COLOR: tuple[int, int, int] = (255, 212, 59)
76
+
77
+
78
+ def coco17_annotation_context():
79
+ """Class description that names, colors, and connects the ViTPose joints.
80
+
81
+ Left joints are blue and right joints orange, so a crossed detection reads
82
+ instantly; without this the keypoints were seventeen identical dots.
83
+ """
84
+
85
+ import rerun as rr
86
+
87
+ def joint_color(name: str) -> tuple[int, int, int]:
88
+ if name.startswith("left"):
89
+ return LEFT_COLOR
90
+ if name.startswith("right"):
91
+ return RIGHT_COLOR
92
+ return CENTER_COLOR
93
+
94
+ return rr.AnnotationContext(
95
+ [
96
+ rr.ClassDescription(
97
+ info=rr.AnnotationInfo(id=0, label="Person", color=KEYPOINT_COLOR),
98
+ keypoint_annotations=[
99
+ rr.AnnotationInfo(id=index, label=name, color=joint_color(name))
100
+ for index, name in enumerate(COCO17_KEYPOINTS)
101
+ ],
102
+ keypoint_connections=COCO17_CONNECTIONS,
103
+ )
104
+ ]
105
+ )
106
+
107
 
108
  def set_frame(index: int, fps: Fraction) -> None:
109
  """Put the next log calls on the shared frame and seconds timelines."""
tests/test_app_helpers.py CHANGED
@@ -306,6 +306,15 @@ def test_a_stream_per_link_adds_no_framing_the_viewer_did_not_already_get(tmp_pa
306
  assert payload.startswith(RRD_MAGIC), f"read {row} is not a self-contained document"
307
 
308
 
 
 
 
 
 
 
 
 
 
309
  def test_format_status_counts_known_stages_and_passes_failures_through() -> None:
310
  """The banner shows x/x plus elapsed for the fixed run shape, nothing else."""
311
 
 
306
  assert payload.startswith(RRD_MAGIC), f"read {row} is not a self-contained document"
307
 
308
 
309
+ def test_coco17_annotation_context_matches_the_vitpose_layout() -> None:
310
+ """Seventeen named joints, and every skeleton edge points at one of them."""
311
+
312
+ assert len(viz.COCO17_KEYPOINTS) == 17
313
+ assert all(0 <= a < 17 and 0 <= b < 17 for a, b in viz.COCO17_CONNECTIONS)
314
+ context: rr.AnnotationContext = viz.coco17_annotation_context()
315
+ assert context is not None
316
+
317
+
318
  def test_format_status_counts_known_stages_and_passes_failures_through() -> None:
319
  """The banner shows x/x plus elapsed for the fixed run shape, nothing else."""
320