Spaces:
Running on Zero
Running on Zero
Commit ·
b84a490
1
Parent(s): c57dcae
Give the ViTPose keypoints a COCO-17 annotation context
Browse filesNamed, 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 +18 -2
- fdanyone/viz.py +62 -0
- tests/test_app_helpers.py +9 -0
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(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
|