| <!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>World frame offset: 2026-05-19</title><style> |
| :root{--bg:#fbfbfa;--fg:#1a1a19;--mut:#6b6b66;--line:#e2e2dd;--card:#fff;--code:#f4f4f0; |
| --ok:#1a7f5a;--no:#b23a3a} |
| @media (prefers-color-scheme:dark){:root{--bg:#141413;--fg:#eceae4;--mut:#9a978e;--line:#2e2d2a; |
| --card:#1c1b19;--code:#232220;--ok:#4bc99a;--no:#e07c7c}} |
| *{box-sizing:border-box} |
| body{margin:0;background:var(--bg);color:var(--fg); |
| font:15.5px/1.62 -apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif} |
| .wrap{max-width:1080px;margin:0 auto;padding:44px 24px 90px} |
| h1{font-size:30px;line-height:1.2;margin:0 0 8px;letter-spacing:-.02em} |
| h2{font-size:21px;margin:48px 0 12px;border-top:1px solid var(--line);padding-top:24px} |
| .sub{color:var(--mut);font-size:16px;margin:0} |
| code{background:var(--code);padding:1px 5px;border-radius:4px;font-size:.88em; |
| font-family:ui-monospace,Menlo,monospace} |
| pre{background:var(--code);border:1px solid var(--line);border-radius:8px;padding:12px 15px; |
| overflow-x:auto;font-size:12.6px;font-family:ui-monospace,Menlo,monospace} |
| .tw{overflow-x:auto;margin:14px 0} |
| table{border-collapse:collapse;width:100%;font-size:13.6px;min-width:560px} |
| th,td{border-bottom:1px solid var(--line);padding:7px 10px;text-align:right} |
| th:first-child,td:first-child{text-align:left} |
| thead th{color:var(--mut);font-weight:600;font-size:12.3px;text-transform:uppercase; |
| letter-spacing:.04em;white-space:nowrap} |
| .ok{color:var(--ok);font-weight:650} .no{color:var(--no);font-weight:650} |
| .dim{color:var(--mut)} |
| .note{background:var(--card);border:1px solid var(--line);border-radius:9px;padding:14px 18px; |
| margin:18px 0} |
| .verdict{border-left:4px solid var(--no)} |
| figure{margin:22px 0 0} figure img{width:100%;border:1px solid var(--line);border-radius:8px} |
| figcaption{color:var(--mut);font-size:12.8px;margin-top:7px} |
| ul{padding-left:20px} li{margin:5px 0} |
| </style></head><body> |
| <div class="wrap"> |
| <h1>Does <code>world_frame_offset</code> still need applying?</h1> |
| <p class="sub">The release manifest records a 23 cm world-frame offset for the five 2026-05-19 episodes, and no code reads it. This applies it and measures what changes.</p> |
| <h2>The field</h2> |
| <pre>data/motherboard/episodes.jsonl |
|
|
| date world_frame_offset (m) episodes |
| 2026-05-10 [0.0, 0.0, 0.0 ] 12 |
| 2026-05-11 [0.0, 0.0, 0.0 ] 15 |
| 2026-05-19 [0.23, 0.0, 0.175] 5</pre> |
| <p>Built both ways: <code>build_view_actions.py</code> as-is, and with <code>--apply-world-offset</code>, which adds the vector to the translation before the projection. Rotation is untouched, and the tactile action is identical either way — <code>dp = R<sub>t</sub><sup>T</sup>(p<sub>t+1</sub> − p<sub>t</sub>)</code> cancels any constant translation, so <b>only the camera-view half of the action can change</b>.</p> |
| <h2>What changes</h2> |
| <div class="tw"><table><thead><tr><th>episode</th><th>offset</th><th>shift (px)</th><th>max</th><th>in-frame as-is</th><th>in-frame w/ offset</th></tr></thead><tbody> |
| <tr><td>0519_episode_000</td><td class="dim">[0.23, 0.0, 0.175]</td><td class="no">219</td><td>276</td><td class="ok">100.0%</td><td class="no">78.7%</td></tr> |
| <tr><td>0519_episode_001</td><td class="dim">[0.23, 0.0, 0.175]</td><td class="no">221</td><td>280</td><td class="ok">100.0%</td><td class="no">83.2%</td></tr> |
| <tr><td>0519_episode_002</td><td class="dim">[0.23, 0.0, 0.175]</td><td class="no">219</td><td>268</td><td class="ok">100.0%</td><td class="no">85.9%</td></tr> |
| <tr><td>0519_episode_003</td><td class="dim">[0.23, 0.0, 0.175]</td><td class="no">220</td><td>246</td><td class="ok">100.0%</td><td class="no">73.3%</td></tr> |
| <tr><td>0519_episode_004</td><td class="dim">[0.23, 0.0, 0.175]</td><td class="no">220</td><td>258</td><td class="ok">100.0%</td><td class="no">71.0%</td></tr> |
| <tr><td colspan="6" class="dim">the other 27 episodes have a zero offset and are bit-identical either way (shift 0.0000 px) — a built-in control</td></tr> |
| </tbody></table></div> |
| <p>Applying it moves the projected gel centre <b>220 px</b> on a 640-wide frame — a third of the image — and pushes 22% of the points out of frame entirely.</p> |
| <h2>Which one is right</h2> |
| <p>No labels needed: AllTracker already says where the image is moving, and a correct projection must put the sensor where the motion is. Score = fraction of moving frames whose projected cell falls in that frame's top-10% flow cells. <b>Chance is 0.10.</b></p> |
| <div class="tw"><table><thead><tr><th>episodes</th><th>as-is</th><th>offset applied</th></tr></thead><tbody> |
| <tr><td>2026-05-19 <span class="dim">(offset ≠ 0)</span></td><td class="ok">0.74</td><td class="no">0.36</td></tr> |
| <tr><td>2026-05-10 / 05-11 <span class="dim">(offset = 0, control)</span></td><td class="ok">0.61</td><td class="dim">unchanged</td></tr> |
| </tbody></table></div> |
| <p class="dim">The control is the important row: those dates carry a zero offset, so their as-is score is what a correct projection looks like on this rig. 05-19 as-is sits in the same band; 05-19 with the offset applied does not.</p> |
| <figure><img src="overlay.png" alt="projection overlay"><figcaption>All five 05-19 episodes at three time points, plus a 05-10 control on the bottom row. <b>○ green</b> = as-is, <b>□ red</b> = offset applied, arrow = the shift. On the control row the two coincide exactly, because that date's offset is zero.</figcaption></figure> |
| <h2>Verdict</h2> |
| <div class="note verdict"><p><b>The offset is already applied in this copy of the data.</b> Adding it again moves the projection off the sensor and onto bare table, on five episodes that are all in the <i>training</i> split. The manifest field is provenance of a correction made upstream, not a pending one.</p><p>The likely reason the two readings disagree: the parquets here are not the original release. <code>data/motherboard/meta/*.parquet</code> was produced by <code>twm.force_recovery.export_force_columns</code> from <code>/media/yxma/Disk1/twm/release/...</code> (not mounted here), and the manifest that travels with it uses a flat <code>world_frame_offset</code> key rather than the original's nested <code>calib_epoch.world_offset_m</code>. Different file, same numbers — one before the correction, one after.</p></div> |
| <p>Two further checks pointing the same way, both independent of the camera calibration:</p> |
| <ul><li><b>Sensor position.</b> Mean <code>sensor_left_pose</code> x is 0.33 m on 05-19 vs 0.29 m on 05-10 — the same workspace. A pending 0.23 m offset would put them 0.23 m apart.</li><li><b>Object position.</b> The motherboard sits at a fixed station; mean <code>object_pose</code> differs between the dates by [0.034, 0.004, 0.039] m, not by [0.23, 0, 0.175].</li></ul> |
| <h2>Settling it definitively</h2> |
| <p>If the original release can be mounted, one diff decides it:</p> |
| <pre>f = lambda p: np.asarray(pq.read_table(p, columns=["sensor_left_pose"]) |
| .column(0).to_pylist())[:, :3] |
| a = f("/media/yxma/Disk1/twm/release/motherboard/meta/2026-05-19/episode_000.parquet") |
| b = f("data/motherboard/meta/2026-05-19/episode_000.parquet") |
| print((b - a).mean(0)) # [0.23 0. 0.175] -> the export applied it</pre> |
| <p class="dim">Reproduce: <code>python data_preprocessing/build_view_actions.py --apply-world-offset --out-name view_actions_woff.pt</code>, then <code>python -m vm_diffusion.scripts.build_hf_space_woffset</code>. The gate <code>vm_diffusion/scripts/verify_view_projection.py</code> re-runs the flow check over all 32 episodes.</p> |
| </div> |
| </body></html> |
|
|