twanghcmut's picture
|
download
raw
8.98 kB
# Is the predicted object flow (Phi) correct? — causal ck-25000, val/199
Answer: **no, and two separate things are wrong.** Also note this run predicts
**RGB + object flow only — there is no depth**. `ABLATION=+flow` sets `gamma_sdf=0`, and the
live training log confirms `loss_sdf=0.000000`, `sdf_valid_frac=0.000000`. The SDF/depth term
has never been on in this run (the gate-4 depth anchor failed earlier and was left out).
## Finding 1 — the `_flow.mp4` rollout videos contain NO ground truth
`scripts/rollout_replay_traj.py:646`:
videos_cat_flow = np.concatenate([videos_flow, videos_flow], axis=-3)
Both halves of the flow panel are the *same* predicted Phi, duplicated to match the RGB
video's height. Measured difference between the halves: **0.85/255 = pure mp4 codec noise**.
Any "GT vs pred" read off those videos is comparing the prediction against itself.
## Finding 2 — 73% of every rollout is a frozen action against a frozen target
`val/199` is a **33-frame** episode (`video_length=33`, latents `(33,4,24,40)`). The replay
runs **22 chunks** covering GT frame ids 0..88. `get_traj_info` clamps out-of-range ids to the
last frame, so:
| chunk | gt_frame_ids | action spread |
|---|---|---|
| 0 | 0–4 | 6.267 |
| 1–5 | 4–24 | 0.05–0.12 |
| **6–21** | 24–88 | **0.00000** |
Chunks 8–21 contain **1 unique action row out of 70**. From chunk 6 on — 16 of 22 chunks —
the model is fed a constant frozen action and scored against a frozen GT frame.
**This invalidates the interpretation of every PSNR number gathered so far**: causal 16.9,
base 20.0, and the whole guidance sweep are ~73% measured over a dead segment where nothing
is asked to change. It also fully explains the earlier "stable, no divergence for 85 frames"
result — nothing was moving. Rollouts should use ~7 chunks for a 33-frame episode.
## Finding 3 — predicted Phi lands where GT Phi is not
Comparing predicted Phi against **genuinely decoded GT Phi latents**, over the valid chunks
only (frames 0–32), active pixel = |Phi − grey| > 12:
| view | GT active | GT mean\|Phi\| | PRED mean\|Phi\| | IoU(pred, GT) |
|---|---|---|---|---|
| ext1 | 0.72% | 0.916 | 1.599 | **0.008** |
| ext2 | 1.31% | 1.190 | 2.070 | **0.003** |
| wrist | 7.11% | 2.474 | 1.427 | **0.004** |
The control that makes this readable — GT Phi against *itself*:
| view | IoU(t, t+1) | IoU(t, t+2) | IoU(t, random t) |
|---|---|---|---|
| ext1 | 0.148 | 0.065 | 0.036 |
| ext2 | 0.169 | 0.052 | 0.038 |
| wrist | 0.110 | 0.070 | 0.052 |
So prediction-vs-GT (0.003–0.008) sits **5–10x below the random-frame baseline**
(0.036–0.052). The predicted flow is not merely imprecise; it occupies regions where the GT
flow essentially never is.
The magnitudes point the same way: the model over-produces flow in the two external views
(1.6–2.1 vs GT 0.9–1.2) and **under-produces it in the wrist view** (1.4 vs GT 2.5) — which is
the view with by far the most real motion (7.11% active vs 0.72%/1.31%).
## The caveat that matters
GT Phi is itself only **0.11–0.17 IoU between adjacent frames**. The supervision target is so
sparse and temporally incoherent that it is weak to learn from at all. Finding 3 should not be
read purely as "the Phi head is broken" — the target it is being trained against is close to
noise from one frame to the next. Fixing the flow-extraction stability is likely a
prerequisite to any Phi head working.
---
# Follow-up: WAFT is fine. The post-processing destroys it.
`latent_videos_flow` (raw WAFT) and `latent_videos_phi` (the Phi actually trained on) both
exist for val/99, so they can be compared directly. **Each stream has a different zero-motion
reference** — measured medians: raw WAFT = 254 (WHITE, colour-wheel centre), Phi = 128 (GREY).
Scoring WAFT against grey makes a static background look 100% "active"; that is a measurement
artifact, not saturation.
Measured against the correct reference for each:
| stream | view | active% | mean dist | IoU(t,t+1) |
|---|---|---|---|---|
| WAFT raw | ext1 | 5.62% | 3.53 | **0.359** |
| WAFT raw | ext2 | 6.07% | 4.08 | **0.425** |
| WAFT raw | wrist | **62.62%** | 39.28 | **0.666** |
| Phi | ext1 | 3.44% | 1.85 | 0.271 |
| Phi | ext2 | **1.56%** | 1.38 | 0.204 |
| Phi | wrist | **6.88%** | 2.32 | **0.128** |
Raw WAFT is 2–5x more temporally coherent. The construction on top of it discards **90% of the
wrist signal and 74% of ext2**, and drops coherence from 0.67 to 0.13.
`WAFT_raw_vs_Phi_3views_val99.mp4` shows why. At frame 18 WAFT gives a crisp, clean flow field
— the arm silhouette in both exterior views, a smooth ego-motion gradient in the wrist — while
the Phi built from it reads **0.3% / 0.1% / 0.0% active**. The wrist Phi is completely empty.
## Where the signal goes
`extract_causal_latent.py:1034`
keep = (~rob) & (lat > args.phi_noise_m) & np.isfinite(mag)
1. `~rob` removes the robot. In the exterior views the robot arm *is* the dominant moving
thing, so the strongest, cleanest part of WAFT's output is deleted by design.
2. `lat > phi_noise_m` with `phi_noise_m = 0.01` m/step. At the 5 Hz video rate that is a
5 cm/s floor — most genuine object motion in these trajectories is slower than that.
What survives is a flickering ~1% residue (IoU 0.13–0.27 between adjacent frames), which is
the target the Phi head is being trained against.
Corroborating signal from `causal_aux/val/199.npz`: `gripper_contact` is **0 for all 33
frames**, and `contact` never exceeds 0.282, on a trajectory whose instruction is "Move the
green towel to the right". `motion_evidence` peaks at 0.015 against `m_ref = 0.0518`. The
contact gate — the core switch of the causal factorization — never fires, because the Phi it
is computed from has almost nothing in it.
**Conclusion: the flow estimator is not the problem. `phi_noise_m` and the `~rob` mask are.**
---
# The fix: Phi from 2-D WAFT flow, no depth at all
## Why the metric anchoring existed, and why it goes
The contract wanted Phi's magnitude to mean "how fast is the object moving **relative to the
end-effector**": `m = |v_obj| / (kappa * |v_ee|)`. EE speed comes from kinematics in m/s, so
the object's speed had to be in m/s too -- pixel flow is not comparable across depths. Hence
Depth-Anything-3 + RANSAC anchoring to convert pixels to metres.
Legitimate goal, but the price is a monocular-depth chain that fails on 43% of trajectories,
and the payoff is only a normalisation. The model consumes Phi as a latent image; pixels vs
metres is a choice of scale, not of information.
Phi[...,0] = clip(u / flow_max[view], -1, 1)
Phi[...,1] = clip(v / flow_max[view], -1, 1)
Phi[...,2] = 0
Phi = 0 inside the (dilated) robot silhouette
Same 3-channel [-1,1] layout as before, so "no motion = grey" and `zero_phi_latent.pt` stay
valid and nothing on the model side changes.
## flow_max is per-view, because the cameras are not alike
Non-robot flow magnitude, 40 sampled trajectories:
| view | p50 | p90 | p95 | p99 | p99.9 |
|---|---|---|---|---|---|
| ext1 | 0.03 px | 0.19 | 0.40 | 6.09 | 22.1 |
| ext2 | 0.03 px | 0.19 | 0.46 | 6.42 | 21.9 |
| wrist | **3.66 px** | **23.0** | 31.0 | 49.3 | 88.0 |
The exterior cameras are genuinely static (p90 = 0.19 px). The wrist camera moves with the
arm. One shared flow_max would either saturate the wrist or push exterior object motion below
what the VAE can resolve, so: **flow_max = 20 / 20 / 50**.
## Validation before committing to a re-extract
**Boundary leakage** -- is the surviving Phi real motion, or flow bleeding off the mask edge?
Active Phi pixels bucketed by distance to the robot silhouette, 25 sampled trajectories:
| view | 0-3 px | 3-8 px | 8-20 px | >20 px |
|---|---|---|---|---|
| ext1 | 7.1% | 17.0% | 23.6% | **52.2%** |
| ext2 | 6.9% | 13.0% | 19.4% | **60.8%** |
| wrist | 0.4% | 1.4% | 3.8% | **94.4%** |
Only ~7% hugs the silhouette; the majority sits >20 px away. Not leakage.
**Result on val/99** (`NEW_phi2d_vs_OLD_val99.mp4`):
| view | OLD active | NEW active | NEW IoU(t,t+1) |
|---|---|---|---|
| ext1 | 3.54% | 4.90% | 0.298 |
| ext2 | 1.61% | 2.50% | 0.311 |
| wrist | 7.07% | 42.33% | 0.474 |
Honest reading: in the exterior views this is a **moderate** gain (~1.4x the active pixels,
comparable coherence). The point is not density -- it is that the new Phi does not ride on a
depth estimate that fails 43% of the time.
## Wrist: keeping ego-motion, deliberately
The wrist camera moves, so its non-robot flow is the scene sliding past (77.5% of pixels per
frame), not object motion -- which is why the original code excluded the wrist from
`motion_evidence`. The options were to zero it, to compensate ego-motion (needs depth again),
or to keep it. **Keeping it was chosen deliberately by the user**, accepting that the wrist
Phi channel is ego-motion rather than object motion. Recording it here so a later reader does
not mistake it for an oversight.

Xet Storage Details

Size:
8.98 kB
·
Xet hash:
50780834c7929bfff067aa178009ad98019cd556ed60d2a2a567050fed0365ff

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.