Skip to content

eval: log validation videos to wandb - #684

Draft
RyanPCo wants to merge 3 commits into
ryanco/gpu-orphan-reapfrom
ryanco/eval-video-wandb
Draft

RyanPCo wants to merge 3 commits into
ryanco/gpu-orphan-reapfrom
ryanco/eval-video-wandb

Conversation

@RyanPCo

@RyanPCo RyanPCo commented Sep 22, 2026 •

Copy link
Copy Markdown
Contributor

Each mp4 EvalVideo writes is also logged to any WandbLogger under <video dir>/<embodiment> (videos/, videos_train_viz/, videos_unseen_op_valid/), captioned with the epoch, so runs can be reviewed without copying files off the cluster.

It also adds a GT/prediction replay video mode (egomimic/eval/replay_viz.py) and makes it the default viz, with a chunk-based cap and an inferred stride.

Tests: tests/unit/test_eval_video_upload.py, tests/unit/test_replay_viz.py.


Folded in (to keep the stack under Graphite's 50-branch cap):

🤖 Generated with Claude Code

https://claude.ai/code/session_01HjCQBqQCr2igFEMR7yUovw

RyanPCo commented Sep 22, 2026 •

Copy link
Copy Markdown
Contributor Author

Warning

This pull request is not mergeable via GitHub because a downstack PR is open. Once all requirements are satisfied, merge this PR as a stack on Graphite.
Learn more

This stack of pull requests is managed by Graphite. Learn more about stacking.

@RyanPCo
RyanPCo changed the base branch from ryanco/gpu-orphan-reap to graphite-base/684 September 22, 2026 18:51
@RyanPCo
RyanPCo force-pushed the ryanco/eval-video-wandb branch 2 times, most recently from 3a1c92d to fc272d2 Compare September 22, 2026 18:51
@RyanPCo
RyanPCo changed the base branch from graphite-base/684 to ryanco/ddp-world-size September 22, 2026 18:51
@RyanPCo
RyanPCo force-pushed the ryanco/eval-video-wandb branch from fc272d2 to 4a21de7 Compare September 24, 2026 03:19
@aidang3019
aidang3019 force-pushed the ryanco/ddp-world-size branch from e9a9545 to ec615c7 Compare September 30, 2026 01:06
@aidang3019
aidang3019 force-pushed the ryanco/eval-video-wandb branch from 4a21de7 to 388ce59 Compare September 30, 2026 01:07
@RyanPCo
RyanPCo force-pushed the ryanco/eval-video-wandb branch from 388ce59 to b649f4e Compare October 12, 2026 20:26
@RyanPCo
RyanPCo changed the base branch from ryanco/ddp-world-size to ryanco/gpu-orphan-reap October 12, 2026 20:27
@RyanPCo
RyanPCo force-pushed the ryanco/eval-video-wandb branch from b649f4e to d04cb33 Compare October 12, 2026 21:50

@RyanPCo RyanPCo left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated adversarial review (read-only; findings were checked by a second skeptical pass before posting): 3 low, 2 nit.

Comment thread egomimic/eval/replay_viz.py Outdated
return image


def _draw_skeleton(image, keypoints, intrinsics, edges, color):

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

low (consistency): _draw_skeleton is a second keypoint renderer, and Human.viz says there is deliberately no static-skeleton path. Move it into egomimic/utils/viz_utils.py next to _viz_keypoints_horizon_trace (sharing _N_KP and the layout checks), and update the Human.viz comment to say the replay video is the one sanctioned per-step skeleton renderer.

self.val_image_buffer[key].clear()
self.val_counter[key] += 1
if isinstance(images, ReplayClip):
self.val_image_buffer[key].append(images)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

low (correctness): Replay mode caps a head at replay_chunks * H * stride frames over one concatenated clip of all its pinned episodes. For unseen_op_valid (two pins) that is 500 frames, so only the first operator's episode is ever rendered, while the yaml says one episode per operator. If a pinned episode is shorter than the cap, a chunk anchored near its end draws on the next episode's frames and rigid_fit pairs hands from two episodes. Either apply the cap per episode and don't anchor chunks across an episode boundary (carry the episode hash per frame in ReplayClip), or document that replay renders only the first pin.

_REPLAY_OWN_KWARGS = {
"image_key",
"action_key",
"annotation_key",

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

low (correctness): Replay mode removes annotation_key from the viz kwargs, and render_replay never draws the prompt. Pinned-episode videos for configs that set it (cartesian.yaml, cotrain_lang.yaml, pi__lang.yaml) therefore silently lose the prompt overlay now that replay is the default. Carry the per-frame annotation in ReplayClip and draw it next to the GT/PRED label, or at least log once that replay ignores annotation_key.

Comment thread egomimic/eval/replay_viz.py Outdated
from egomimic.utils.type_utils import _to_numpy
from egomimic.utils.viz_utils import _prepare_viz_image

_KP_WIDTH = 126

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit (duplication): _KP_WIDTH = 126 duplicates the layout that viz_utils derives from _N_KP. Define it as 2 * 3 * _N_KP, or export a named constant from viz_utils, so the two cannot drift.

Comment thread egomimic/eval/eval_video.py Outdated
self._replay_seen[key] = seen
self._replay_horizon[key] = images.gt.shape[1]
# infer_stride compares steps 1-8 against up to 32 frames on.
if key not in self._replay_stride and seen >= 40:

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit (magic-number): seen >= 40 only works because it exceeds max(candidates) * steps (32) for infer_stride's defaults. If those defaults change, larger strides are silently skipped. Derive it from named constants in replay_viz, e.g. MIN_FRAMES_FOR_STRIDE = max(STRIDE_CANDIDATES) * STRIDE_STEPS + 1.

RyanPCo and others added 3 commits October 30, 2026 18:51
Each mp4 EvalVideo writes is also logged to any WandbLogger under
<video dir>/<embodiment> (videos/, videos_train_viz/, videos_unseen_op_valid/),
captioned with the epoch, so runs can be reviewed without copying files
off the cluster.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01415567AQYskZLeSpSMsgwL
viz_mode=replay plays each action chunk twice over the frames it spans:
first the ground-truth hand at each step, then the predicted one. Each step
is drawn on the frame it belongs to, with the chunk mapped into that frame's
camera by a rigid fit of the GT keypoints (the same hand at the same instant
in both frames), so head motion needs no head pose. The 126-D keypoint
layout draws both hands' 21-keypoint skeletons; other layouts fall back to
the embodiment's own viz over a short trail.

Chunks don't overlap and span horizon * action_stride frames; frames of an
incomplete chunk carry over to the next flush. viz_max_batches must cover at
least one chunk (a 100-step horizon needs ~13 batches of 8).

_visualize_preds moves from HPTEvalVideo / PIEvalVideo (identical copies)
into EvalVideo.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01516MaELbiPNmJzkjPGebhu
Replay becomes EvalVideo's default. It needs consecutive frames, so it runs
only on a pinned video loader; a head without one keeps the overlay video,
and EvalLatent stays on overlay. The replay video loader is capped at
replay_chunks (5) chunks instead of viz_max_batches, which at 8 frames a
batch could not cover one 100-step chunk; viz_max_batches still bounds a
loader that yields no clips.

action_stride defaults to None and is inferred from the GT keypoints (step k
of a chunk rigidly matches frame k*stride's own step 0): data configs use 1
or 3 and cotrain mixes them under one embodiment. TrainVizEvalVideo passes
the replay settings through and tells its base which mode each step is.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01516MaELbiPNmJzkjPGebhu
@RyanPCo

RyanPCo commented Oct 30, 2026

Copy link
Copy Markdown
Contributor Author

Review findings: status after the fixup pass. The fixes are squashed into this PR's commits, and the whole stack was force-pushed.

Fixed

  • Static-skeleton renderer that Human.viz forbids: moved to viz_utils._viz_keypoints_skeleton; Human.viz comment updated (human.py comment outside the PR, needed for the fix; kept short next to viz: one keypoint renderer, the horizon trace #669's one-line version); fixup targets fe8933a to avoid autosquash conflicts
  • _KP_WIDTH = 126 hardcoded: viz_utils.HEAD_KEYPOINT_WIDTH = 23_N_KP, used in replay_viz and test_replay_viz
  • Stride-inference threshold 40 and flush 1000 are magic numbers: STRIDE_CANDIDATES, STRIDE_STEPS and MIN_FRAMES_FOR_STRIDE in replay_viz; _MAX_BUFFERED_FRAMES in eval_video
  • Replay mode silently drops the prompt overlay: ReplayClip carries annotation_key prompts and render_replay draws them via viz(mode='annotations'); test added

Partial

  • Replay merges pinned episodes; chunks cross boundaries; cap stops before later episodes: ReplayClip carries episode_hash per frame and render_replay never anchors a chunk across episodes (test added); cap is still per key, now documented rather than applied per episode

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant