diff --git a/src/gesim/server/dashboard.py b/src/gesim/server/dashboard.py index a03ffe4..f24cc90 100644 --- a/src/gesim/server/dashboard.py +++ b/src/gesim/server/dashboard.py @@ -99,7 +99,7 @@
-

Action vs predicted state  (commanded action vs world-model prediction, last frame)

+

Action vs predicted state  (commanded action vs PE prediction, last frame; grippers in mm)

waiting for a step…
@@ -117,12 +117,19 @@ } const LABELS = ["L1","L2","L3","L4","L5","L6","L7","L grip","R1","R2","R3","R4","R5","R6","R7","R grip"]; +// Match gesim.types GRIPPER_APERTURE_* — WM action grippers are [0,1], PE state is mm. +const GRIP_MIN = 35, GRIP_SPAN = 120 - 35, GRIP_DIMS = new Set([7, 15]); + +function actionForCompare(i, v) { + return GRIP_DIMS.has(i) ? v * GRIP_SPAN + GRIP_MIN : v; +} function tableFor(title, action, state, lo, hi) { let rows = ""; for (let i = lo; i < hi; i++) { - const a = action[i], p = state[i], d = p - a; - const cls = Math.abs(d) < 1e-4 ? "" : (d > 0 ? "pos" : "neg"); + const a = actionForCompare(i, action[i]), p = state[i], d = p - a; + const tol = GRIP_DIMS.has(i) ? 2.0 : 1e-4; + const cls = Math.abs(d) < tol ? "" : (d > 0 ? "pos" : "neg"); rows += "" + LABELS[i] + "" + a.toFixed(3) + "" + p.toFixed(3) + "" + (d >= 0 ? "+" : "") + d.toFixed(3) + ""; diff --git a/src/gesim/types.py b/src/gesim/types.py index c2146b5..a1a2060 100644 --- a/src/gesim/types.py +++ b/src/gesim/types.py @@ -18,6 +18,26 @@ STATE_DIM = 16 ACTION_DIM = 16 +# Genie-01 OmniPicker: WM actions use normalised gripper commands in [0, 1]; PE +# state uses physical aperture in mm. Keep in sync with conditioning/band.py. +GRIPPER_APERTURE_MIN_MM = 35.0 +GRIPPER_APERTURE_MAX_MM = 120.0 +WM_GRIPPER_DIMS = (7, 15) + + +def wm_action_gripper_to_mm(norm: float) -> float: + """Map a normalised WM gripper command to OmniPicker aperture in mm.""" + span = GRIPPER_APERTURE_MAX_MM - GRIPPER_APERTURE_MIN_MM + return float(norm) * span + GRIPPER_APERTURE_MIN_MM + + +def wm_action_row_for_state_compare(action: np.ndarray) -> np.ndarray: + """Return a WM action row with gripper channels converted to mm for PE comparison.""" + out = np.asarray(action, dtype=np.float32).reshape(-1).copy() + for dim in WM_GRIPPER_DIMS: + out[dim] = wm_action_gripper_to_mm(out[dim]) + return out + @dataclass(frozen=True) class Observation: diff --git a/src/gesim/video.py b/src/gesim/video.py index f23fdf0..3f3afbe 100644 --- a/src/gesim/video.py +++ b/src/gesim/video.py @@ -10,6 +10,18 @@ logger = logging.getLogger("gesim.video") +def _resolve_ffmpeg() -> str | None: + """Return an ffmpeg binary, preferring PATH then optional imageio-ffmpeg.""" + ffmpeg = shutil.which("ffmpeg") + if ffmpeg: + return ffmpeg + try: + import imageio_ffmpeg + except ImportError: + return None + return imageio_ffmpeg.get_ffmpeg_exe() + + def save_video(frames: np.ndarray, path: str, fps: int = 16): """Save ``(T, 3, V, H, W)`` float video to mp4. Views are tiled horizontally.""" T, C, V, H, W = frames.shape @@ -22,7 +34,7 @@ def save_video(frames: np.ndarray, path: str, fps: int = 16): rows = np.ascontiguousarray(rows) height, width = rows.shape[1], rows.shape[2] - ffmpeg = shutil.which("ffmpeg") + ffmpeg = _resolve_ffmpeg() if ffmpeg: cmd = [ ffmpeg, diff --git a/tests/test_gripper_units.py b/tests/test_gripper_units.py new file mode 100644 index 0000000..d13e90a --- /dev/null +++ b/tests/test_gripper_units.py @@ -0,0 +1,23 @@ +import numpy as np +import pytest + +from gesim.types import ( + GRIPPER_APERTURE_MAX_MM, + GRIPPER_APERTURE_MIN_MM, + wm_action_gripper_to_mm, + wm_action_row_for_state_compare, +) + + +def test_wm_action_gripper_to_mm_endpoints(): + assert wm_action_gripper_to_mm(0.0) == pytest.approx(GRIPPER_APERTURE_MIN_MM) + assert wm_action_gripper_to_mm(1.0) == pytest.approx(GRIPPER_APERTURE_MAX_MM) + + +def test_wm_action_row_for_state_compare_only_touches_grippers(): + row = np.arange(16, dtype=np.float32) + out = wm_action_row_for_state_compare(row) + assert np.array_equal(out[:7], row[:7]) + assert np.array_equal(out[8:15], row[8:15]) + assert out[7] == pytest.approx(wm_action_gripper_to_mm(row[7])) + assert out[15] == pytest.approx(wm_action_gripper_to_mm(row[15]))