Files
Mujoco_WASM/python/mujoco/sysid/_src/plotting.py
T
2026-02-11 00:22:56 -05:00

115 lines
3.6 KiB
Python

# Copyright 2026 DeepMind Technologies Limited
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
"""Plotting utilities."""
from __future__ import annotations
from collections.abc import Sequence
import mujoco
import numpy as np
def render_rollout(
model: mujoco.MjModel | Sequence[mujoco.MjModel],
data: mujoco.MjData,
state: np.ndarray,
framerate: int,
camera: str | int = -1,
width: int = 640,
height: int = 480,
light_pos: Sequence[float] | None = None,
) -> list[np.ndarray]:
"""Renders a rollout or batch of rollouts.
Args:
model: Single model or list of models (one per batch).
data: MjData scratch object.
state: State array of shape (nbatch, nsteps, nstate).
framerate: Frames per second to render.
camera: Camera name or ID.
width: Image width.
height: Image height.
light_pos: Optional light position [x, y, z] to add a spotlight.
Returns:
List of rendered frames (numpy arrays).
"""
nbatch = state.shape[0]
if isinstance(model, mujoco.MjModel):
models_list = [model] * nbatch
else:
models_list = list(model)
if len(models_list) == 1:
models_list = models_list * nbatch
else:
assert len(models_list) == nbatch
# Visual options
vopt = mujoco.MjvOption()
vopt.geomgroup[3] = 1 # Show visualization geoms
pert = mujoco.MjvPerturb()
catmask = mujoco.mjtCatBit.mjCAT_DYNAMIC.value
# Simulate and render.
frames = []
with mujoco.Renderer(models_list[0], height=height, width=width) as renderer:
for i in range(state.shape[1]):
# Check if we should capture this frame based on framerate
if len(frames) < i * models_list[0].opt.timestep * framerate:
for j in range(state.shape[0]):
# Set state
mujoco.mj_setState(
models_list[j],
data,
state[j, i, :],
mujoco.mjtState.mjSTATE_FULLPHYSICS.value,
)
mujoco.mj_forward(models_list[j], data)
# Use first model to make the scene, add subsequent models
if j == 0:
renderer.update_scene(data, camera, scene_option=vopt)
else:
mujoco.mjv_addGeoms(
models_list[j], data, vopt, pert, catmask, renderer.scene
)
# Add light, if requested
if light_pos is not None:
if renderer.scene.nlight < 100: # check limit
light = renderer.scene.lights[renderer.scene.nlight]
light.ambient = [0, 0, 0]
light.attenuation = [1, 0, 0]
light.castshadow = 1
light.cutoff = 45
light.diffuse = [0.8, 0.8, 0.8]
light.dir = [0, 0, -1]
light.type = mujoco.mjtLightType.mjLIGHT_SPOT
light.exponent = 10
light.headlight = 0
light.specular = [0.3, 0.3, 0.3]
light.pos = light_pos
renderer.scene.nlight += 1
# Render and add the frame.
pixels = renderer.render()
frames.append(pixels)
return frames