Source code for unisim.backend.motrix.playback
"""Motrix-owned playback execution helpers."""
from __future__ import annotations
import time
from collections.abc import Mapping
from os import PathLike
from typing import Any, Callable, TypeVar
import numpy as np
from unisim.backend.base import CameraCfg
from unisim.backend.playback import env_cfg_value, write_playback_video
ObsT = TypeVar("ObsT")
[docs]
def run_motrix_playback(
*,
backend: Any,
env: Any,
initialize: Callable[[], ObsT],
step: Callable[[ObsT], ObsT],
num_steps: int | None,
output_video: str | PathLike[str] | None,
render_spacing: float | None,
render_offset_mode: str | None,
headless: bool,
record_video: bool,
camera_kwargs: CameraCfg | Mapping[str, Any] | None,
) -> str | None:
if record_video and not headless:
raise ValueError("Motrix video recording requires headless=true.")
if headless or record_video:
if num_steps is None:
raise ValueError("Motrix captured playback requires a finite num_steps value.")
if record_video and output_video is None:
raise ValueError("Motrix video recording requires an output_video path.")
effective_spacing = (
float(render_spacing)
if render_spacing is not None
else float(env_cfg_value(env, "render_spacing", 1.0))
)
backend.bind_renderer(
spacing=effective_spacing,
offset_mode=str(render_offset_mode) if render_offset_mode is not None else "grid",
headless=headless,
capture=True,
width=1280,
height=720,
camera_kwargs=camera_kwargs,
)
obs = initialize()
frames: list[np.ndarray] | None = [] if record_video else None
for _ in range(num_steps):
obs = step(obs)
frame = np.asarray(backend.capture_video_frame(), dtype=np.uint8)
if frames is not None:
frames.append(frame.copy())
if not record_video:
return None
assert output_video is not None
assert frames is not None
ctrl_dt = float(env_cfg_value(env, "ctrl_dt", 1.0 / 60.0))
write_playback_video(str(output_video), frames, fps=int(1.0 / ctrl_dt))
return str(output_video)
effective_spacing = (
float(render_spacing)
if render_spacing is not None
else float(env_cfg_value(env, "render_spacing", 1.0))
)
backend.bind_renderer(
spacing=effective_spacing,
offset_mode=str(render_offset_mode) if render_offset_mode is not None else "grid",
)
obs = initialize()
last_render_time = time.perf_counter()
render_dt = 1.0 / 60.0
steps_run = 0
while num_steps is None or steps_run < num_steps:
obs = step(obs)
current_time = time.perf_counter()
elapsed = current_time - last_render_time
if elapsed < render_dt:
time.sleep(render_dt - elapsed)
last_render_time = time.perf_counter()
backend.render()
steps_run += 1
return None