Skip to content

Observation Terms

Observation terms read the simulation, commands and motion reference into the tensors a policy sees. Observation compute() is a read, not a tick: it runs at build, at reset, in several step phases and from get_observations(), so per-step state advances in post_physics_refresh(). The observations guide covers groups, noise, history and modifiers; the term catalog lists every parameter.

from eden import observations

The base classes ObservationTerm and ObservationTermFuncWrapper are on the managers page.

Proprioception

Proprioceptive observation terms (base pose/velocity; joint position, velocity and force).

Attributes

Classes

DofsPos

DofsPos(env: EnvBase, options: ObservationTermOptions)

Bases: _DofsObservation

Joint positions (angles; positions for linear actuators) of an entity's DOFs.

PARAMETER DESCRIPTION
entity_name

The entity whose DOFs are read.

dofs_name

Joints to read, in output order; None for every controlled DOF (see :class:_DofsObservation).

terms_idx

Positions in the entity's dofs_name instead of names; prefer dofs_name.

offset_from_default

Subtract the entity's default positions (default_dofs_pos) of the same DOFs.

Source code in eden/managers/observation_manager.py
def __init__(self, env: EnvBase, options: ObservationTermOptions):
    super().__init__(env=env, options=options)
    self._init_post_compute_fields(env)

Attributes

offset_from_default class-attribute instance-attribute
offset_from_default: bool = False

Methods:

compute
compute() -> Tensor
Source code in eden/managers/terms/observations/proprio.py
def compute(self) -> torch.Tensor:
    entity = self.resolve_entity(self.entity_name)
    dofs_pos = entity.get_dofs_pos()
    if self.offset_from_default:
        dofs_pos = dofs_pos - entity.default_dofs_pos
    return self._select(dofs_pos)

DofsVel

DofsVel(env: EnvBase, options: ObservationTermOptions)

Bases: _DofsObservation

Joint velocities of an entity's DOFs.

PARAMETER DESCRIPTION
entity_name

The entity whose DOFs are read.

dofs_name

Joints to read, in output order; None for every controlled DOF (see :class:_DofsObservation).

terms_idx

Positions in the entity's dofs_name instead of names; prefer dofs_name.

Source code in eden/managers/observation_manager.py
def __init__(self, env: EnvBase, options: ObservationTermOptions):
    super().__init__(env=env, options=options)
    self._init_post_compute_fields(env)

Methods:

compute
compute() -> Tensor
Source code in eden/managers/terms/observations/proprio.py
def compute(self) -> torch.Tensor:
    return self._select(self.resolve_entity(self.entity_name).get_dofs_vel())

DofsForce

DofsForce(env: EnvBase, options: ObservationTermOptions)

Bases: _DofsObservation

Generalized force the solver applied to each of an entity's DOFs (get_dofs_force).

PARAMETER DESCRIPTION
entity_name

The entity whose DOFs are read.

dofs_name

Joints to read, in output order; None for every controlled DOF (see :class:_DofsObservation).

terms_idx

Positions in the entity's dofs_name instead of names; prefer dofs_name.

Source code in eden/managers/observation_manager.py
def __init__(self, env: EnvBase, options: ObservationTermOptions):
    super().__init__(env=env, options=options)
    self._init_post_compute_fields(env)

Methods:

compute
compute() -> Tensor
Source code in eden/managers/terms/observations/proprio.py
def compute(self) -> torch.Tensor:
    return self._select(self.resolve_entity(self.entity_name).get_dofs_force())

DofsControlForce

DofsControlForce(env: EnvBase, options: ObservationTermOptions)

Bases: _DofsObservation

Control force the actuators applied on each of an entity's DOFs over the last physics step (get_dofs_control_force).

PARAMETER DESCRIPTION
entity_name

The entity whose DOFs are read.

dofs_name

Joints to read, in output order; None for every controlled DOF (see :class:_DofsObservation).

terms_idx

Positions in the entity's dofs_name instead of names; prefer dofs_name.

Source code in eden/managers/observation_manager.py
def __init__(self, env: EnvBase, options: ObservationTermOptions):
    super().__init__(env=env, options=options)
    self._init_post_compute_fields(env)

Methods:

compute
compute() -> Tensor
Source code in eden/managers/terms/observations/proprio.py
def compute(self) -> torch.Tensor:
    return self._select(self.resolve_entity(self.entity_name).get_dofs_control_force())

Functions:

base_pos

base_pos(env: EnvBase, *, entity_name: str, envs_idx: slice | Tensor | None = None, relative: bool = False) -> Tensor

Return the position of the entity's base link.

PARAMETER DESCRIPTION
env

The environment instance.

TYPE: EnvBase

entity_name

The name of the entity.

TYPE: str

envs_idx

The indices of the environments. If None, all environments will be considered. Defaults to None.

TYPE: slice | Tensor | None DEFAULT: None

relative

Frame the position is reported in. False (the default) reads the solver frame, which Genesis re-origins onto a free rigid body's inertial frame; True reads the frame the asset was authored in. Pass True whenever the rest of the task reasons in the asset's own frame: the two differ by the body's centre-of-mass offset (6.8 cm on robosuite's hammer), and because that offset lives in the body frame it rotates with the object — so a policy handed this frame alongside a gripper_to_<obj> vector measured in the other one sees two different positions for one object. Same switch as :func:~eden.managers.terms.metrics.containment.object_above_height.

TYPE: bool DEFAULT: False

Source code in eden/managers/terms/observations/proprio.py
@OBSERVATION_TERM_REGISTRY.register()
def base_pos(
    env: EnvBase, *, entity_name: str, envs_idx: slice | torch.Tensor | None = None, relative: bool = False
) -> torch.Tensor:
    """Return the position of the entity's base link.

    Parameters
    ----------
    env : EnvBase
        The environment instance.
    entity_name : str
        The name of the entity.
    envs_idx : slice | torch.Tensor | None, optional
        The indices of the environments. If None, all environments will be considered. Defaults to None.
    relative : bool, optional
        Frame the position is reported in. ``False`` (the default) reads the solver frame, which
        Genesis re-origins onto a free rigid body's inertial frame; ``True`` reads the frame the
        asset was authored in. Pass ``True`` whenever the rest of the task reasons in the asset's own
        frame: the two differ by the body's centre-of-mass offset (6.8 cm on robosuite's hammer), and
        because that offset lives in the body frame it *rotates with the object* — so a policy handed
        this frame alongside a ``gripper_to_<obj>`` vector measured in the other one sees two
        different positions for one object. Same switch as
        :func:`~eden.managers.terms.metrics.containment.object_above_height`.
    """
    entity: Entity = env.get_entity(entity_name)
    return entity.get_pos(envs_idx=envs_idx, relative=relative)

base_quat

base_quat(env: EnvBase, *, entity_name: str, envs_idx: slice | Tensor | None = None, relative: bool = False) -> Tensor

Return the quaternion of the entity's base link.

PARAMETER DESCRIPTION
env

The environment instance.

TYPE: EnvBase

entity_name

The name of the entity.

TYPE: str

envs_idx

The indices of the environments. If None, all environments will be considered. Defaults to None.

TYPE: slice | Tensor | None DEFAULT: None

relative

Frame the orientation is reported in — see :func:base_pos. Defaults to False, the solver frame. Pass True to report the frame the asset was authored in, which is the frame an object_offset taken from the asset's own geometry is measured in.

TYPE: bool DEFAULT: False

Source code in eden/managers/terms/observations/proprio.py
@OBSERVATION_TERM_REGISTRY.register()
def base_quat(
    env: EnvBase, *, entity_name: str, envs_idx: slice | torch.Tensor | None = None, relative: bool = False
) -> torch.Tensor:
    """Return the quaternion of the entity's base link.

    Parameters
    ----------
    env : EnvBase
        The environment instance.
    entity_name : str
        The name of the entity.
    envs_idx : slice | torch.Tensor | None, optional
        The indices of the environments. If None, all environments will be considered. Defaults to None.
    relative : bool, optional
        Frame the orientation is reported in — see :func:`base_pos`. Defaults to ``False``, the
        solver frame. Pass ``True`` to report the frame the asset was authored in, which is the frame
        an ``object_offset`` taken from the asset's own geometry is measured in.
    """
    entity: Entity = env.get_entity(entity_name)
    return entity.get_quat(envs_idx=envs_idx, relative=relative)

base_rpy

base_rpy(env: EnvBase, *, entity_name: str, envs_idx: slice | Tensor | None = None, terms_idx: Tensor | None = None) -> Tensor

Return the roll, pitch, and yaw of the entity's base link.

PARAMETER DESCRIPTION
env

The environment instance.

TYPE: EnvBase

entity_name

The name of the entity.

TYPE: str

envs_idx

The indices of the environments. If None, all environments will be considered. Defaults to None.

TYPE: slice | Tensor | None DEFAULT: None

terms_idx

The indices of the terms to return. If None, all terms will be returned. Defaults to None.

TYPE: Tensor | None DEFAULT: None

Source code in eden/managers/terms/observations/proprio.py
@OBSERVATION_TERM_REGISTRY.register()
def base_rpy(
    env: EnvBase,
    *,
    entity_name: str,
    envs_idx: slice | torch.Tensor | None = None,
    terms_idx: torch.Tensor | None = None,
) -> torch.Tensor:
    """Return the roll, pitch, and yaw of the entity's base link.

    Parameters
    ----------
    env : EnvBase
        The environment instance.
    entity_name : str
        The name of the entity.
    envs_idx : slice | torch.Tensor | None, optional
        The indices of the environments. If None, all environments will be considered. Defaults to None.
    terms_idx : torch.Tensor | None, optional
        The indices of the terms to return. If None, all terms will be returned. Defaults to None.
    """
    entity: Entity = env.get_entity(entity_name)
    if terms_idx is not None:
        return quat_to_xyz(entity.get_quat(envs_idx=envs_idx), rpy=True)[..., terms_idx]
    return quat_to_xyz(entity.get_quat(envs_idx=envs_idx), rpy=True)

base_lin_vel

base_lin_vel(env: EnvBase, *, entity_name: str, envs_idx: slice | Tensor | None = None, frame: Literal['world', 'body'] = 'world') -> Tensor
Source code in eden/managers/terms/observations/proprio.py
@OBSERVATION_TERM_REGISTRY.register()
def base_lin_vel(
    env: EnvBase,
    *,
    entity_name: str,
    envs_idx: slice | torch.Tensor | None = None,
    frame: Literal["world", "body"] = "world",
) -> torch.Tensor:
    entity: Entity = env.get_entity(entity_name)
    return entity.get_vel(envs_idx=envs_idx, frame=frame)

base_ang_vel

base_ang_vel(env: EnvBase, *, entity_name: str, envs_idx: slice | Tensor | None = None, frame: Literal['world', 'body'] = 'world') -> Tensor
Source code in eden/managers/terms/observations/proprio.py
@OBSERVATION_TERM_REGISTRY.register()
def base_ang_vel(
    env: EnvBase,
    *,
    entity_name: str,
    envs_idx: slice | torch.Tensor | None = None,
    frame: Literal["world", "body"] = "world",
) -> torch.Tensor:
    asset: Entity = env.get_entity(entity_name)
    return asset.get_ang(envs_idx=envs_idx, frame=frame)
links_pos(env: EnvBase, *, entity_name: str, ls_idx_local: Tensor | None = None, envs_idx: slice | Tensor | None = None) -> Tensor
Source code in eden/managers/terms/observations/proprio.py
@OBSERVATION_TERM_REGISTRY.register()
def links_pos(
    env: EnvBase,
    *,
    entity_name: str,
    ls_idx_local: torch.Tensor | None = None,
    envs_idx: slice | torch.Tensor | None = None,
) -> torch.Tensor:
    robot: Entity = env.get_entity(entity_name)
    return robot.get_links_pos(ls_idx_local=ls_idx_local, envs_idx=envs_idx)
links_quat(env: EnvBase, *, entity_name: str, ls_idx_local: Tensor | None = None, envs_idx: slice | Tensor | None = None) -> Tensor
Source code in eden/managers/terms/observations/proprio.py
@OBSERVATION_TERM_REGISTRY.register()
def links_quat(
    env: EnvBase,
    *,
    entity_name: str,
    ls_idx_local: torch.Tensor | None = None,
    envs_idx: slice | torch.Tensor | None = None,
) -> torch.Tensor:
    robot: Entity = env.get_entity(entity_name)
    return robot.get_links_quat(ls_idx_local=ls_idx_local, envs_idx=envs_idx)
links_vel(env: EnvBase, *, entity_name: str, ls_idx_local: Tensor | None = None, envs_idx: slice | Tensor | None = None) -> Tensor
Source code in eden/managers/terms/observations/proprio.py
@OBSERVATION_TERM_REGISTRY.register()
def links_vel(
    env: EnvBase,
    *,
    entity_name: str,
    ls_idx_local: torch.Tensor | None = None,
    envs_idx: slice | torch.Tensor | None = None,
) -> torch.Tensor:
    robot: Entity = env.get_entity(entity_name)
    return robot.get_links_vel(ls_idx_local=ls_idx_local, envs_idx=envs_idx)
links_ang(env: EnvBase, *, entity_name: str, ls_idx_local: Tensor | None = None, envs_idx: slice | Tensor | None = None) -> Tensor
Source code in eden/managers/terms/observations/proprio.py
@OBSERVATION_TERM_REGISTRY.register()
def links_ang(
    env: EnvBase,
    *,
    entity_name: str,
    ls_idx_local: torch.Tensor | None = None,
    envs_idx: slice | torch.Tensor | None = None,
) -> torch.Tensor:
    robot: Entity = env.get_entity(entity_name)
    return robot.get_links_ang(ls_idx_local=ls_idx_local, envs_idx=envs_idx)

Locomotion

Locomotion observation terms (gait phase, projected gravity, base height, height scan).

Attributes

Classes

ProjectedGravity

ProjectedGravity(env: EnvBase, options: ObservationTermOptions)

Bases: ObservationTerm

Gravity direction expressed in the entity's base frame.

Caches only the constant world-frame gravity vector in :meth:build; the entity handle is resolved each :meth:compute via resolve_entity so the deployment's sim→RobotStateEntity swap is picked up automatically (a cached handle would keep reading the simulated robot on hardware).

Source code in eden/managers/observation_manager.py
def __init__(self, env: EnvBase, options: ObservationTermOptions):
    super().__init__(env=env, options=options)
    self._init_post_compute_fields(env)

Attributes

entity_name class-attribute instance-attribute
entity_name: str = 'robot'

Methods:

build
build() -> None
Source code in eden/managers/terms/observations/locomotion.py
def build(self) -> None:
    self._grav = gravity_vec(self.num_envs, self.device)
compute
compute() -> Tensor
Source code in eden/managers/terms/observations/locomotion.py
def compute(self) -> torch.Tensor:
    return projected_gravity(self.resolve_entity(self.entity_name).get_quat(), self._grav)

ScaledCommands

ScaledCommands(env: EnvBase, options: ObservationTermOptions)

Bases: ObservationTerm

Command vector multiplied by a per-component scale; prefer the built-in scale=[...] modifier.

For plain per-component scaling prefer the built-in scale=[...] post-compute modifier (ObsTerm.configure(func=..., scale=[2.0, 1.0])). command_scale is the per-component multiplier (named to avoid colliding with the inherited post-compute scale modifier). Both the command ref and the scale tensor are resolved once in :meth:build.

The swap is exact only when the term configures no noise and no clip. This class scales inside compute(), so the manager's noise and clip see the scaled command; the scale modifier is applied after both (compute_group runs noise -> clip -> scale). So migrating a term that also sets noise divides the effective noise std by the per-component factor, and one that sets clip reinterprets the bounds from scaled units into raw ones. Rescale those two alongside the swap, or keep this class.

Source code in eden/managers/observation_manager.py
def __init__(self, env: EnvBase, options: ObservationTermOptions):
    super().__init__(env=env, options=options)
    self._init_post_compute_fields(env)

Attributes

command_name class-attribute instance-attribute
command_name: str = ''
command_scale class-attribute instance-attribute
command_scale: list[float] = []

Methods:

build
build() -> None
Source code in eden/managers/terms/observations/locomotion.py
def build(self) -> None:
    self._command = self._env.command_manager.get_term(self.command_name)
    self._scale = torch.tensor(self.command_scale, device=self.device, dtype=torch.float32)
compute
compute() -> Tensor
Source code in eden/managers/terms/observations/locomotion.py
def compute(self) -> torch.Tensor:
    return torch.mul(self._command.command, self._scale)

VelocityTrackingErrorIntegral

VelocityTrackingErrorIntegral(env: EnvBase, options: ObservationTermOptions)

Bases: ObservationTerm

Running integral of the base-velocity tracking error (N, 3).

The worked example of a stateful observation term.

∫ (commanded − measured) dt over [vx, vy, wz] in the base frame, against a velocity command laid out [vx, vy, wz] (:class:~eden.managers.terms.commands.velocity.UniformVelocityCommand). Zero at reset, exactly k increments after k control steps, and clamped per component to ±max_abs so a persistent error (a robot that cannot reach the commanded speed) saturates the feature instead of growing without bound — the anti-windup clamp of a PI controller.

This term exists as much to be copied as to be used. It is the reference for per-step state on an observation term, which the manager's compute() cannot hold because that call is a read and runs several times a step (:class:~eden.managers.observation_manager.ObservationTerm):

  • :meth:build resolves the command (a bad name raises at env construction), touches the entity, and allocates the buffer — outside any inference-mode region, so every hook may write it.
  • :meth:post_physics_refresh is the once-per-step tick: it reads the post-physics robot and adds the increment in place. It runs at phase 7 — after the command caches and metrics, above the graded layer and the auto-reset — so the final-observation snapshot carries this step's increment and reset() zeroes it for the envs that restart before their first fresh observation. The deploy loop ticks it once per control tick.
  • :meth:reset zeroes the rows of the restarting envs.
  • :meth:compute returns the buffer, live — like a metric buffer, not a copy; the manager's snapshot paths copy what they keep. Nothing else.

The entity is resolved in the hook, not cached in build — the deployment swaps env.entities[name] for a RobotStateEntity and a cached handle would keep integrating the simulated robot on hardware. self._env.dt is the control dt, so the integral is in m / rad regardless of decimation.

Source code in eden/managers/observation_manager.py
def __init__(self, env: EnvBase, options: ObservationTermOptions):
    super().__init__(env=env, options=options)
    self._init_post_compute_fields(env)

Attributes

entity_name class-attribute instance-attribute
entity_name: str = 'robot'
command_name class-attribute instance-attribute
command_name: str = 'velocity_command'
max_abs class-attribute instance-attribute
max_abs: float = 1.0

Methods:

build
build() -> None
Source code in eden/managers/terms/observations/locomotion.py
def build(self) -> None:
    # ``getattr``: a plain ``EnvBase`` has no command manager attribute at all, and that is the
    # same misconfiguration as an RL env with none — one message for both.
    command_manager = getattr(self._env, "command_manager", None)
    if command_manager is None:
        raise ValueError(f"{term_subject(self)} requires a command manager with a '{self.command_name}' term.")
    # ``get_term`` raises naming the available commands; touching the entity does the same for
    # ``entity_name``. Both fail here, at env construction, rather than on the first tick.
    self._command = command_manager.get_term(self.command_name)
    self.resolve_entity(self.entity_name)
    # The command buffer exists at build (the command manager is constructed first), so its width
    # is checked here: a narrower command would otherwise fail on the first tick as a broadcasting
    # error in ``post_physics_refresh``, not at env construction.
    command_dim = self._command.command.shape[-1]
    if command_dim < 3:
        raise ValueError(
            f"{term_subject(self)}: command '{self.command_name}' publishes {command_dim} component(s); this term "
            f"integrates the first three as [vx, vy, wz] and needs at least 3 (UniformVelocityCommand's layout)."
        )
    if self.max_abs <= 0.0:
        raise ValueError(f"{term_subject(self)}: max_abs must be positive, got {self.max_abs}.")
    self._integral = torch.zeros(self.num_envs, 3, device=self.device)
post_physics_refresh
post_physics_refresh() -> None
Source code in eden/managers/terms/observations/locomotion.py
def post_physics_refresh(self) -> None:
    entity = self.resolve_entity(self.entity_name)
    measured = torch.cat([entity.get_vel(frame="body")[:, :2], entity.get_ang(frame="body")[:, 2:3]], dim=-1)
    # In place: the buffer was allocated in build(), and this may run under inference mode.
    self._integral.add_((self._command.command[:, :3] - measured) * self._env.dt).clamp_(
        -self.max_abs, self.max_abs
    )
reset
reset(envs_idx: Tensor | slice | None = None) -> None
Source code in eden/managers/terms/observations/locomotion.py
def reset(self, envs_idx: torch.Tensor | slice | None = None) -> None:
    self._integral[slice(None) if envs_idx is None else envs_idx] = 0.0
compute
compute() -> Tensor
Source code in eden/managers/terms/observations/locomotion.py
def compute(self) -> torch.Tensor:
    return self._integral

GridScanDots

GridScanDots(env: EnvBase, options: ObservationTermOptions)

Bases: ObservationTerm

Height scan of the terrain at the given xy coordinates.

This is specifically used for Terrain entity. For the general LiDAR sensor, use the LiDAR observation term.

PARAMETER DESCRIPTION
entity_name

The name of the entity where the grid is "attached" to. Defaults to "robot".

TYPE: str

terrain_entity_name

The name of the terrain entity. Defaults to "terrain".

TYPE: str

measured_points_x

The x coordinates (m) of the measured points, in the base's yaw-aligned frame. Defaults to the values in the class.

TYPE: list[float]

measured_points_y

The y coordinates (m) of the measured points, in the base's yaw-aligned frame. Defaults to the values in the class.

TYPE: list[float]

flatten_measured_points

Whether to flatten the heights per env. Defaults to True: shape (num_envs, len(measured_points_x) * len(measured_points_y)). If False, the shape is (num_envs, len(measured_points_x), len(measured_points_y)).

TYPE: bool

offset_from_base

If True (default), return each terrain height relative to the base height (terrain_z - base_z), with NaN zeroed and the result clamped to clamp_offset_range. If False, return the absolute world-frame terrain heights, unclamped.

TYPE: bool

clamp_offset_range

The (min, max) clamp (m) applied when offset_from_base is True. Defaults to (-5.0, 5.0).

TYPE: tuple[float, float]

Source code in eden/managers/terms/observations/locomotion.py
def __init__(self, env: EnvBase, options: ObservationTermOptions):
    super().__init__(env, options)
    self._grid_buffer: torch.Tensor | None = None

Attributes

entity_name class-attribute instance-attribute
entity_name: str = 'robot'
terrain_entity_name class-attribute instance-attribute
terrain_entity_name: str = 'terrain'
measured_points_x class-attribute instance-attribute
measured_points_x: list[float] = [-0.8, -0.7, -0.6, -0.5, -0.4, -0.3, -0.2, -0.1, 0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8]
measured_points_y class-attribute instance-attribute
measured_points_y: list[float] = [-0.5, -0.4, -0.3, -0.2, -0.1, 0.0, 0.1, 0.2, 0.3, 0.4, 0.5]
flatten_measured_points class-attribute instance-attribute
flatten_measured_points: bool = True
offset_from_base class-attribute instance-attribute
offset_from_base: bool = True
clamp_offset_range class-attribute instance-attribute
clamp_offset_range: Vec2FType = (-5.0, 5.0)

Methods:

build
build() -> None
Source code in eden/managers/terms/observations/locomotion.py
def build(self) -> None:
    # Cache only grid buffers; resolve entities lazily in compute() (see ManagerTermBase.resolve_entity).
    entity = self.resolve_entity(self.entity_name)
    if entity.has_immobile_base:
        raise ValueError(
            f"{term_subject(self)} needs an entity whose base moves through the world; "
            f"it has an immobile base (fixed, or welded onto another entity)."
        )
    _ = self.resolve_entity(self.terrain_entity_name)  # fail fast if the terrain entity is missing
    self._grid_buffer = torch.zeros(
        (
            self._env.num_envs,
            len(self.measured_points_x),
            len(self.measured_points_y),
            3,
        ),
        device=gs.device,
        dtype=gs.tc_float,
    )
    measured_points_x = torch.tensor(self.measured_points_x, device=gs.device, dtype=gs.tc_float)
    measured_points_y = torch.tensor(self.measured_points_y, device=gs.device, dtype=gs.tc_float)
    grid_x, grid_y = torch.meshgrid(measured_points_x, measured_points_y, indexing="ij")
    self._grid_buffer[..., 0] = grid_x
    self._grid_buffer[..., 1] = grid_y
    self.num_measured_points = grid_x.numel()
    self.num_measured_points_x = len(self.measured_points_x)
    self.num_measured_points_y = len(self.measured_points_y)
    self.grid = torch.zeros(
        (
            self._env.num_envs,
            len(self.measured_points_x),
            len(self.measured_points_y),
            2,
        ),
        device=gs.device,
        dtype=gs.tc_float,
    )
compute
compute(*args, **kwargs)
Source code in eden/managers/terms/observations/locomotion.py
def compute(self, *args, **kwargs):
    base_pose = self.resolve_entity(self.entity_name).get_qpos(slice(0, 7))
    self.grid = transform_by_quat_yaw_approx(
        self._grid_buffer.view(-1, 3),
        base_pose[:, 3:7].view(-1, 4).repeat_interleave(self.num_measured_points, dim=0),
    ).view(-1, self.num_measured_points_x, self.num_measured_points_y, 3)[..., :2]
    self.grid += base_pose[:, :2].view(-1, 1, 1, 2)
    scan_dots = self.resolve_entity(self.terrain_entity_name).get_height(self.grid)  # B, h_out, w_out

    if self.offset_from_base:
        scan_dots -= base_pose[:, 2].view(-1, 1, 1)
        min_clamp, max_clamp = self.clamp_offset_range
        scan_dots = torch.nan_to_num(scan_dots, nan=0.0, posinf=max_clamp, neginf=min_clamp).clamp(
            min=min_clamp, max=max_clamp
        )

    if self.flatten_measured_points:
        return scan_dots.view(-1, self.num_measured_points)
    else:
        return scan_dots

Functions:

sin_phase

sin_phase(env: EnvBase, *, command_name: str) -> Tensor

Sine of the gait phase for each foot. Shape (num_envs, num_feet).

Source code in eden/managers/terms/observations/locomotion.py
@OBSERVATION_TERM_REGISTRY.register()
def sin_phase(env: EnvBase, *, command_name: str) -> torch.Tensor:
    """Sine of the gait phase for each foot. Shape (num_envs, num_feet)."""
    gait_cmd: GaitPhaseCommand = env.command_manager.get_term(command_name)
    return torch.sin(gait_cmd.command)

cos_phase

cos_phase(env: EnvBase, *, command_name: str) -> Tensor

Cosine of the gait phase for each foot. Shape (num_envs, num_feet).

Source code in eden/managers/terms/observations/locomotion.py
@OBSERVATION_TERM_REGISTRY.register()
def cos_phase(env: EnvBase, *, command_name: str) -> torch.Tensor:
    """Cosine of the gait phase for each foot. Shape (num_envs, num_feet)."""
    gait_cmd: GaitPhaseCommand = env.command_manager.get_term(command_name)
    return torch.cos(gait_cmd.command)

base_height

base_height(env: EnvBase, *, entity_name: str = 'robot', terrain_entity_name: str | None = None, relative: bool = False) -> Tensor

Height of the entity's base above the ground, shape (num_envs, 1).

The locomotion counterpart of Isaac Lab's base_pos_z, and the observation half of :func:~eden.managers.terms.terminations.locomotion.root_height_below_minimum — the two read the same :func:~eden.managers.terms.utils.base_height_above_ground helper, so an observation and a termination configured alike cannot disagree about what "height" means.

PARAMETER DESCRIPTION
env

The environment instance.

TYPE: EnvBase

entity_name

Entity whose base height is reported. Defaults to "robot".

TYPE: str DEFAULT: 'robot'

terrain_entity_name

Terrain entity to measure against. None (the default) reports the world-frame z, which is the height above the ground only on flat terrain; naming a :class:~eden.entities.terrain.Terrain subtracts the terrain height under the base's xy so the feature means the same thing everywhere on a heightfield.

TYPE: str | None DEFAULT: None

relative

Frame the base pose is read in — see :func:~eden.managers.terms.observations.proprio.base_pos. Defaults to False (the solver frame), matching the other base-pose observations and :func:~eden.managers.terms.rewards.locomotion.base_height_l2.

TYPE: bool DEFAULT: False

Source code in eden/managers/terms/observations/locomotion.py
@OBSERVATION_TERM_REGISTRY.register()
def base_height(
    env: EnvBase,
    *,
    entity_name: str = "robot",
    terrain_entity_name: str | None = None,
    relative: bool = False,
) -> torch.Tensor:
    """Height of the entity's base above the ground, shape ``(num_envs, 1)``.

    The locomotion counterpart of Isaac Lab's ``base_pos_z``, and the observation half of
    :func:`~eden.managers.terms.terminations.locomotion.root_height_below_minimum` — the two read the
    same :func:`~eden.managers.terms.utils.base_height_above_ground` helper, so an observation and a
    termination configured alike cannot disagree about what "height" means.

    Parameters
    ----------
    env : EnvBase
        The environment instance.
    entity_name : str, optional
        Entity whose base height is reported. Defaults to ``"robot"``.
    terrain_entity_name : str | None, optional
        Terrain entity to measure *against*. ``None`` (the default) reports the world-frame ``z``,
        which is the height above the ground only on flat terrain; naming a
        :class:`~eden.entities.terrain.Terrain` subtracts the terrain height under the base's ``xy``
        so the feature means the same thing everywhere on a heightfield.
    relative : bool, optional
        Frame the base pose is read in — see
        :func:`~eden.managers.terms.observations.proprio.base_pos`. Defaults to ``False`` (the solver
        frame), matching the other base-pose observations and
        :func:`~eden.managers.terms.rewards.locomotion.base_height_l2`.
    """
    terrain = resolve_terrain(env.entities, terrain_entity_name)
    height = base_height_above_ground(resolve_named_entity(env.entities, entity_name), terrain, relative=relative)
    return height.unsqueeze(-1)

Commands, actions and episode state

Common observation terms (commands, last action, episode phase).

Attributes

Classes

MetricObservation

MetricObservation(env: EnvBase, options: ObservationTermOptions)

Bases: ObservationTerm

Expose a :class:~eden.managers.metric_manager.MetricTerm's cached value as a (num_envs, 1) observation.

Pulls the per-env value the metric manager computed this step (metric_manager runs before the observation manager in env.step()). Same rationale as metric_reward: compute a shared quantity once as a metric and read it here by name rather than recomputing it or stashing it on env. The standard obs modifiers (scale/clip/noise/ history) still apply. Observations are computed after env resets, so an environment that reset this step reads the metric's post-reset value (0 until its next compute), consistent with Eden's post-reset observation convention.

A missing metric manager or an unset or unknown metric_name (the error lists the active metrics) raises in :meth:build, i.e. at env construction. :meth:compute is a pure read of the manager-owned buffer, so the extra calls an observation term receives (build-time shape probe, reset(), the final-observation snapshot) are harmless.

Source code in eden/managers/observation_manager.py
def __init__(self, env: EnvBase, options: ObservationTermOptions):
    super().__init__(env=env, options=options)
    self._init_post_compute_fields(env)

Attributes

metric_name class-attribute instance-attribute
metric_name: str = ''

Methods:

build
build() -> None
Source code in eden/managers/terms/observations/common.py
def build(self) -> None:
    resolve_metric_manager(self._env, term="metric_observation", metric_name=self.metric_name)
compute
compute() -> Tensor
Source code in eden/managers/terms/observations/common.py
def compute(self) -> torch.Tensor:
    return self._env.metric_manager.get_value(self.metric_name).unsqueeze(-1)

Functions:

generated_commands

generated_commands(env: RLEnvBase, *, command_name: str) -> Tensor
Source code in eden/managers/terms/observations/common.py
@OBSERVATION_TERM_REGISTRY.register()
def generated_commands(
    env: RLEnvBase,
    *,
    command_name: str,
) -> torch.Tensor:
    return env.command_manager.get_command(command_name)

last_action

last_action(env: RLEnvBase, *, action_name: str | None = None) -> Tensor
Source code in eden/managers/terms/observations/common.py
@OBSERVATION_TERM_REGISTRY.register()
def last_action(
    env: RLEnvBase,
    *,
    action_name: str | None = None,
) -> torch.Tensor:
    if action_name is not None:
        action_term: ActionTerm = env.action_manager.get_term(action_name)
        return action_term.action
    return env.action_manager.action

episode_phase

episode_phase(env: RLEnvBase, *, centered: bool = False) -> Tensor

Return normalized episode time as a (num_envs, 1) tensor.

centered=False (default): t / max_episode_length in [0, 1]. centered=True: 2 * t / max_episode_length - 1 in [-1, 1].

Source code in eden/managers/terms/observations/common.py
@OBSERVATION_TERM_REGISTRY.register()
def episode_phase(
    env: RLEnvBase,
    *,
    centered: bool = False,
) -> torch.Tensor:
    """Return normalized episode time as a ``(num_envs, 1)`` tensor.

    ``centered=False`` (default): ``t / max_episode_length`` in ``[0, 1]``.
    ``centered=True``: ``2 * t / max_episode_length - 1`` in ``[-1, 1]``.
    """
    phase = env.episode_length_buf.float() / env.max_episode_length
    if centered:
        phase = 2.0 * phase - 1.0
    return phase.unsqueeze(-1)

Privileged dynamics

Per-env domain-randomized quantities (gain and motor-strength scales, added mass, foot friction, CoM offset) for a critic or a teacher policy.

Privileged observation terms exposing per-env domain-randomized dynamics.

These read the realized randomized physical parameters relative to their nominal defaults, so an asymmetric (privileged) critic can condition on the domain randomization the actor cannot observe — the standard sim-to-real privileged-critic technique. The actor sees only proprioception; the critic additionally sees terms like these.

Each term reads back exactly one domain_rand event:

=========================== ======================================================================= term event it reads back =========================== ======================================================================= :class:GainScale :class:~eden.managers.terms.events.domain_rand.RandomizeKpKdGains :class:MotorStrengthScale :class:~eden.managers.terms.events.domain_rand.RandomizeMotorStrength :class:AddedMass :class:~eden.managers.terms.events.domain_rand.RandomizeMassShift :class:FootFriction :class:~eden.managers.terms.events.domain_rand.RandomizeFrictionRatio :class:ComOffset :class:~eden.managers.terms.events.domain_rand.RandomizeComShift =========================== =======================================================================

Convention: the shift/deviation terms are 0 at nominal (GainScale, MotorStrengthScale, AddedMass, ComOffset); FootFriction is a ratio, 1.0 at nominal.

Inspired by TWIST2's critic_extras (critic_motor_scales / critic_added_mass / critic_foot_friction / critic_base_com_offset).

Attributes

Classes

GainScale

GainScale(env: EnvBase, options: ObservationTermOptions)

Bases: ObservationTerm

Per-DOF relative deviation of the realized PD gains from their defaults.

Returns concat([kp/kp0 - 1, kd/kd0 - 1]) over the entity's actuated DOFs (in dofs_name order); 0 means nominal, so it is all zeros when there is no gain randomization. Pairs with :class:~eden.managers.terms.events.domain_rand.RandomizeKpKdGains. Output shape (num_envs, 2 * num_dofs).

Notes

Reads the gains held on the entity (get_dofs_kp / get_dofs_kd), so it only reflects gain randomization written to the entity — i.e. RandomizeKpKdGains with action_term_name=None (the default, Genesis built-in PD path). RandomizeKpKdGains with action_term_name set writes the PD-controller's _kp / _kd caches instead and never touches the entity, so this term would read all zeros; :meth:build raises if such an event targets the same entity. (The controller mirrors the entity gains into its caches on every action reset, but that mirror is one-way entity→controller, so it does not carry controller-side randomization back to the entity.) Per-env gains require batch_dofs_info=True in env_options; otherwise the gains are shared across envs and the term is uniformly zero (still valid, just uninformative).

This is the gain deviation, distinct from :class:MotorStrengthScale (the actuator torque-scale modifier).

PARAMETER DESCRIPTION
entity_name

Name of the articulated entity whose actuator gains to read.

TYPE: str

Source code in eden/managers/observation_manager.py
def __init__(self, env: EnvBase, options: ObservationTermOptions):
    super().__init__(env=env, options=options)
    self._init_post_compute_fields(env)

Attributes

entity_name class-attribute instance-attribute
entity_name: str = 'robot'

Methods:

build
build() -> None
Source code in eden/managers/terms/observations/privileged.py
def build(self) -> None:
    entity = self.resolve_entity(self.entity_name)
    # Baselines are build-time constants (the nominal per-DOF gains). Cache
    # them plus the derived zero-masks / safe denominators so compute() does
    # no per-step allocation. Clone so the cache never aliases
    # ``default_dofs_kp`` (``as_tensor`` returns the same tensor when it is
    # already float32 on-device).
    self._kp0 = self._ensure_2d(entity.default_dofs_kp).clone()
    self._kd0 = self._ensure_2d(entity.default_dofs_kd).clone()
    self._kp_zero, self._kp_safe = self._zero_mask_and_safe(self._kp0)
    self._kd_zero, self._kd_safe = self._zero_mask_and_safe(self._kd0)
    self._guard_conflicting_gain_events()
    self._warn_zero_baseline_dofs(entity)
compute
compute() -> Tensor
Source code in eden/managers/terms/observations/privileged.py
def compute(self) -> torch.Tensor:
    entity = self.resolve_entity(self.entity_name)
    # The getters already reach the full env count, which fixes the output shape; the
    # ``(1, ndof)`` denominators then broadcast against them.
    kp = entity.get_dofs_kp()
    kd = entity.get_dofs_kd()
    dev = torch.cat(
        [self._rel_dev(kp, self._kp_zero, self._kp_safe), self._rel_dev(kd, self._kd_zero, self._kd_safe)],
        dim=-1,
    )
    return dev.float()

MotorStrengthScale

MotorStrengthScale(env: EnvBase, options: ObservationTermOptions)

Bases: ObservationTerm

Per-DOF deviation of the realized actuator torque-scale from its nominal.

Returns motor_strength - motor_strength_baseline over the controller's DOFs (0 means the configured nominal strength), read from the :class:~eden.managers.modifiers.actions.actuators.MotorStrength action modifier that :class:~eden.managers.terms.events.domain_rand.RandomizeMotorStrength writes. All zeros without motor-strength randomization. Output shape (num_envs, num_dofs).

The baseline is the modifier's configured strength row (usually 1.0 but honoring a non-unity motor_strength config), so the term stays 0 at the deterministic nominal regardless of that configured value.

This is the actuator torque-scale (a multiplier on the PD output torque), distinct from :class:GainScale (the kp/kd gain deviation).

PARAMETER DESCRIPTION
action_term_name

Name of the PD-controller action term hosting the MotorStrength modifier (directly or inside a Compose chain).

TYPE: str

Source code in eden/managers/observation_manager.py
def __init__(self, env: EnvBase, options: ObservationTermOptions):
    super().__init__(env=env, options=options)
    self._init_post_compute_fields(env)

Attributes

action_term_name class-attribute instance-attribute
action_term_name: str = 'dofs_pos_controller'

Methods:

build
build() -> None
Source code in eden/managers/terms/observations/privileged.py
def build(self) -> None:
    from eden.managers.modifiers.actions.actuators import MotorStrength
    from eden.managers.terms.events.domain_rand import _require_modifier

    term = self._env.action_manager.get_term(self.action_term_name)
    self._modifier = _require_modifier(term, MotorStrength, type(self).__name__, self.action_term_name)
compute
compute() -> Tensor
Source code in eden/managers/terms/observations/privileged.py
def compute(self) -> torch.Tensor:
    return (self._modifier.motor_strength_realized - self._modifier.motor_strength_baseline).float()

AddedMass

AddedMass(env: EnvBase, options: ObservationTermOptions)

Bases: _LinkDrTerm

Per-env mass shift (kg) of the named links vs nominal — 0 without mass DR.

Ports TWIST2's critic_added_mass. Pairs with :class:~eden.managers.terms.events.domain_rand.RandomizeMassShift (or :class:~eden.managers.terms.events.domain_rand.RandomizeLinkMassScale). Output shape (num_envs, len(links_name)).

Source code in eden/managers/observation_manager.py
def __init__(self, env: EnvBase, options: ObservationTermOptions):
    super().__init__(env=env, options=options)
    self._init_post_compute_fields(env)

Methods:

compute
compute() -> Tensor
Source code in eden/managers/terms/observations/privileged.py
def compute(self) -> torch.Tensor:
    return self.resolve_entity(self.entity_name).get_mass_shift(self._ls_idx).float()

FootFriction

FootFriction(env: EnvBase, options: ObservationTermOptions)

Bases: _LinkDrTerm

Mean per-env friction ratio over the named links — 1.0 without friction DR.

Ports TWIST2's critic_foot_friction. Pairs with :class:~eden.managers.terms.events.domain_rand.RandomizeFrictionRatio. Output shape (num_envs, 1).

Source code in eden/managers/observation_manager.py
def __init__(self, env: EnvBase, options: ObservationTermOptions):
    super().__init__(env=env, options=options)
    self._init_post_compute_fields(env)

Methods:

compute
compute() -> Tensor
Source code in eden/managers/terms/observations/privileged.py
def compute(self) -> torch.Tensor:
    return self.resolve_entity(self.entity_name).get_friction_ratio(self._ls_idx).mean(dim=1, keepdim=True).float()

ComOffset

ComOffset(env: EnvBase, options: ObservationTermOptions)

Bases: _LinkDrTerm

Per-env COM shift (m) of the named links vs nominal — 0 without COM DR.

Ports TWIST2's critic_base_com_offset. Pairs with :class:~eden.managers.terms.events.domain_rand.RandomizeComShift. Output shape (num_envs, 3 * len(links_name)).

Source code in eden/managers/observation_manager.py
def __init__(self, env: EnvBase, options: ObservationTermOptions):
    super().__init__(env=env, options=options)
    self._init_post_compute_fields(env)

Methods:

compute
compute() -> Tensor
Source code in eden/managers/terms/observations/privileged.py
def compute(self) -> torch.Tensor:
    return self.resolve_entity(self.entity_name).get_COM_shift(self._ls_idx).flatten(start_dim=1).float()

Functions:

Manipulation

Manipulation observation terms: end-effector pose, gripper state, object distances.

Attributes

RotationFormat module-attribute

RotationFormat = Literal['quat', 'euler_xyz', 'axis_angle', 'rot6d']

GripperNormalize module-attribute

GripperNormalize = Literal['raw', 'openness', 'closedness']

Classes

EndEffectorToObjectDistance

EndEffectorToObjectDistance(env: EnvBase, options: ObservationTermOptions)

Bases: ObservationTerm

End-effector-to-object vector (N, 3).

From the mean of the two end-effector links to the object (or its link). Expressed in the robot's base frame (frame="robot", the default) or the world frame.

Source code in eden/managers/observation_manager.py
def __init__(self, env: EnvBase, options: ObservationTermOptions):
    super().__init__(env=env, options=options)
    self._init_post_compute_fields(env)

Attributes

robot_name class-attribute instance-attribute
robot_name: str = 'robot'
left_ee_link_name: str = 'left_gripper'
right_ee_link_name: str = 'right_gripper'
entity_name class-attribute instance-attribute
entity_name: str = 'obj'
entity_link_name: str | None = None
frame class-attribute instance-attribute
frame: Literal['world', 'robot'] = 'robot'

Methods:

build
build() -> None
Source code in eden/managers/terms/observations/manipulation.py
def build(self) -> None:
    # Cache only derived idx; resolve entities lazily in compute() (see ManagerTermBase.resolve_entity).
    robot = self.resolve_entity(self.robot_name)
    self.ee_idx_local = resolve_link_idx(robot, [self.left_ee_link_name, self.right_ee_link_name], self.device)
    _resolve_target(self)  # fail fast if the object entity / link is missing
compute
compute(*args, **kwargs)
Source code in eden/managers/terms/observations/manipulation.py
def compute(self, *args, **kwargs):
    robot = self.resolve_entity(self.robot_name)
    ee_pos_w = robot.get_links_pos(ls_idx_local=self.ee_idx_local).mean(dim=1)
    obj_pos_w = _resolve_target(self).get_pos()
    distance_vec_w = obj_pos_w - ee_pos_w

    if self.frame == "world":
        return distance_vec_w
    elif self.frame == "robot":
        base_quat_w = robot.get_quat()
        distance_vec_b = inv_transform_by_quat(distance_vec_w, base_quat_w)
        return distance_vec_b
    else:
        raise ValueError(f"Invalid frame '{self.frame}'. Expected 'world' or 'robot'.")

ObjectToOracleDistance

ObjectToOracleDistance(env: EnvBase, options: ObservationTermOptions)

Bases: ObservationTerm

Object-to-oracle vector (N, 3).

From the object (or its link) to the oracle target. The target is the first three components of command command_name, a position in the robot's base frame. The result is expressed in the robot's base frame (frame="robot", the default) or the world frame.

Source code in eden/managers/observation_manager.py
def __init__(self, env: EnvBase, options: ObservationTermOptions):
    super().__init__(env=env, options=options)
    self._init_post_compute_fields(env)

Attributes

entity_name class-attribute instance-attribute
entity_name: str = 'obj'
entity_link_name: str | None = None
command_name class-attribute instance-attribute
command_name: str = 'command'
robot_name class-attribute instance-attribute
robot_name: str = 'robot'
frame class-attribute instance-attribute
frame: Literal['world', 'robot'] = 'robot'

Methods:

build
build() -> None
Source code in eden/managers/terms/observations/manipulation.py
def build(self) -> None:
    # Command terms aren't swapped, so cache the ref; resolve entities lazily (see ManagerTermBase.resolve_entity).
    self._command = self._env.command_manager.get_term(self.command_name)
    _ = self.resolve_entity(self.robot_name)  # fail fast if the robot entity is missing
    _resolve_target(self)  # fail fast if the object entity / link is missing
compute
compute(*args, **kwargs)
Source code in eden/managers/terms/observations/manipulation.py
def compute(self, *args, **kwargs):
    robot = self.resolve_entity(self.robot_name)
    oracle_pos_b = self._command.command[..., :3]
    base_pos_w = robot.get_pos()
    base_quat_w = robot.get_quat()
    oracle_pos_w = transform_by_trans_quat(oracle_pos_b, base_pos_w, base_quat_w)

    obj_pos_w = _resolve_target(self).get_pos()
    distance_vec_w = oracle_pos_w - obj_pos_w

    if self.frame == "world":
        return distance_vec_w
    elif self.frame == "robot":
        distance_vec_b = inv_transform_by_quat(distance_vec_w, base_quat_w)
        return distance_vec_b
    else:
        raise ValueError(f"Invalid frame '{self.frame}'. Expected 'world' or 'robot'.")

EndEffectorPose

EndEffectorPose(env: EnvBase, options: ObservationTermOptions)

Bases: ObservationTerm

End-effector pose [pos(3), rotation(k)] of a link, in the base or world frame.

PARAMETER DESCRIPTION
entity_name

Robot entity owning the link.

ee_link_name

The end-effector link.

frame

Frame the pose is expressed in.

offset_pos

Virtual tool frame relative to the link (position in m, wxyz quaternion).

offset_quat

Virtual tool frame relative to the link (position in m, wxyz quaternion).

frame_correction_quat

Fixed wxyz rotation right-multiplied onto the orientation (R @ R_corr), e.g. the DROID rot6d frame correction or a hardware tool-frame yaw.

rotation

Output rotation format; the term is 7-, 6-, 6- or 9-D accordingly.

Source code in eden/managers/observation_manager.py
def __init__(self, env: EnvBase, options: ObservationTermOptions):
    super().__init__(env=env, options=options)
    self._init_post_compute_fields(env)

Attributes

entity_name class-attribute instance-attribute
entity_name: str = 'robot'
ee_link_name: str = ''
frame class-attribute instance-attribute
frame: Literal['base', 'world'] = 'base'
offset_pos class-attribute instance-attribute
offset_pos: tuple[float, float, float] = (0.0, 0.0, 0.0)
offset_quat class-attribute instance-attribute
offset_quat: tuple[float, float, float, float] = _IDENTITY_QUAT
frame_correction_quat class-attribute instance-attribute
frame_correction_quat: tuple[float, float, float, float] = _IDENTITY_QUAT
rotation class-attribute instance-attribute
rotation: RotationFormat = 'quat'

Methods:

build
build() -> None
Source code in eden/managers/terms/observations/manipulation.py
def build(self) -> None:
    if self.rotation not in ("quat", "euler_xyz", "axis_angle", "rot6d"):
        raise ValueError(
            f"{term_subject(self)}: rotation={self.rotation!r}; expected quat, euler_xyz, axis_angle or rot6d."
        )
    if self.frame not in ("base", "world"):
        raise ValueError(f"{term_subject(self)}: frame={self.frame!r}; expected 'base' or 'world'.")
    entity = self._env.entities[self.entity_name]
    self._link_idx = resolve_link_idx(entity, [self.ee_link_name], self.device)
    has_offset = tuple(self.offset_pos) != (0.0, 0.0, 0.0) or tuple(self.offset_quat) != _IDENTITY_QUAT
    self._offset_pos = (
        torch.as_tensor(self.offset_pos, dtype=torch.float32, device=self.device).reshape(1, 3)
        if has_offset
        else None
    )
    self._offset_quat = _quat_tensor(self.offset_quat, self.device, name="offset_quat") if has_offset else None
    self._correction_quat = (
        _quat_tensor(self.frame_correction_quat, self.device, name="frame_correction_quat")
        if tuple(self.frame_correction_quat) != _IDENTITY_QUAT
        else None
    )
compute
compute(*args, **kwargs) -> Tensor
Source code in eden/managers/terms/observations/manipulation.py
def compute(self, *args, **kwargs) -> torch.Tensor:
    entity = self.resolve_entity(self.entity_name)
    pos, quat = end_effector_pose(
        entity,
        self._link_idx,
        frame=self.frame,
        offset_pos=self._offset_pos,
        offset_quat=self._offset_quat,
        correction_quat=self._correction_quat,
    )
    return torch.cat([pos, rotation_as(quat, self.rotation)], dim=-1)

GripperState

GripperState(env: EnvBase, options: ObservationTermOptions)

Bases: ObservationTerm

Gripper DOF positions as raw values, openness or closedness.

PARAMETER DESCRIPTION
entity_name

Entity owning the gripper DOFs (the arm, or a separately mounted gripper).

dofs_name

Gripper DOFs to report (one driver, or both fingers for a raw two-finger state).

normalize

raw reports pos * scale; the others map [open_pos, close_pos] to [0, 1] (see :func:gripper_state).

open_pos

DOF positions of the fully open / fully closed gripper (required unless raw).

close_pos

DOF positions of the fully open / fully closed gripper (required unless raw).

scale

Multiplier for raw output (e.g. to match a checkpoint's normaliser units).

Source code in eden/managers/observation_manager.py
def __init__(self, env: EnvBase, options: ObservationTermOptions):
    super().__init__(env=env, options=options)
    self._init_post_compute_fields(env)

Attributes

entity_name class-attribute instance-attribute
entity_name: str = 'robot'
dofs_name class-attribute instance-attribute
dofs_name: list[str] = []
normalize class-attribute instance-attribute
normalize: GripperNormalize = 'raw'
open_pos class-attribute instance-attribute
open_pos: float | None = None
close_pos class-attribute instance-attribute
close_pos: float | None = None
scale class-attribute instance-attribute
scale: float = 1.0

Methods:

build
build() -> None
Source code in eden/managers/terms/observations/manipulation.py
def build(self) -> None:
    if not self.dofs_name:
        raise ValueError(f"{term_subject(self)} needs at least one gripper DOF name.")
    if self.normalize not in ("raw", "openness", "closedness"):
        raise ValueError(
            f"{term_subject(self)}: normalize={self.normalize!r}; expected raw, openness or closedness."
        )
    if self.normalize != "raw" and (
        self.open_pos is None or self.close_pos is None or self.open_pos == self.close_pos
    ):
        raise ValueError(
            f"{term_subject(self)}: normalize={self.normalize!r} needs distinct open_pos/close_pos "
            f"(got {self.open_pos}, {self.close_pos})."
        )
    entity = self._env.entities[self.entity_name]
    _, idx = entity.find_named_dofs_idx_local(list(self.dofs_name), preserve_order=True)
    self._dofs_idx = torch.as_tensor(idx, dtype=torch.long, device=self.device)
compute
compute(*args, **kwargs) -> Tensor
Source code in eden/managers/terms/observations/manipulation.py
def compute(self, *args, **kwargs) -> torch.Tensor:
    pos = self.resolve_entity(self.entity_name).get_dofs_pos(dofs_idx_local=self._dofs_idx)
    return gripper_state(
        pos, normalize=self.normalize, open_pos=self.open_pos, close_pos=self.close_pos, scale=self.scale
    )

Functions:

rotation_as

rotation_as(quat_wxyz: Tensor, rotation: RotationFormat) -> Tensor

Express a batch of wxyz quaternions (N, 4) in rotation format.

euler_xyz is extrinsic roll/pitch/yaw (scipy "xyz"), axis_angle a rotation vector, rot6d the first two rows of the rotation matrix (Zhou et al.).

Source code in eden/managers/terms/observations/manipulation.py
def rotation_as(quat_wxyz: torch.Tensor, rotation: RotationFormat) -> torch.Tensor:
    """Express a batch of wxyz quaternions ``(N, 4)`` in ``rotation`` format.

    ``euler_xyz`` is extrinsic roll/pitch/yaw (scipy ``"xyz"``), ``axis_angle`` a rotation
    vector, ``rot6d`` the first two rows of the rotation matrix (Zhou et al.).
    """
    if rotation == "quat":
        return quat_wxyz
    if rotation == "euler_xyz":
        roll, pitch, yaw = euler_xyz_from_quat(quat_wxyz)
        return torch.stack([roll, pitch, yaw], dim=-1)
    if rotation == "axis_angle":
        return axis_angle_from_quat(quat_wxyz)
    if rotation == "rot6d":
        return R_to_rot6d(quat_to_R(quat_wxyz))
    raise ValueError(f"Unknown rotation format {rotation!r}; expected quat, euler_xyz, axis_angle or rot6d.")

end_effector_pose

end_effector_pose(entity, link_idx: Tensor, *, frame: Literal['base', 'world'] = 'base', offset_pos: Tensor | None = None, offset_quat: Tensor | None = None, correction_quat: Tensor | None = None) -> tuple[Tensor, Tensor]

(pos (N, 3), quat_wxyz (N, 4)) of one link, optionally re-expressed and corrected.

offset_pos / offset_quat (1, 3) / (1, 4) place a virtual tool frame on the link (RobotArena's ee_gripper_link sits 9.36 cm ahead of WidowX's gripper_link). frame="base" expresses the result in the entity's base frame. correction_quat (1, 4) is a fixed rotation right-multiplied onto the orientation (R_out = R @ R_corr), as the DROID rot6d convention or a hardware tool-frame yaw offset require.

Source code in eden/managers/terms/observations/manipulation.py
def end_effector_pose(
    entity,
    link_idx: torch.Tensor,
    *,
    frame: Literal["base", "world"] = "base",
    offset_pos: torch.Tensor | None = None,
    offset_quat: torch.Tensor | None = None,
    correction_quat: torch.Tensor | None = None,
) -> tuple[torch.Tensor, torch.Tensor]:
    """``(pos (N, 3), quat_wxyz (N, 4))`` of one link, optionally re-expressed and corrected.

    ``offset_pos`` / ``offset_quat`` ``(1, 3)`` / ``(1, 4)`` place a virtual tool frame on
    the link (RobotArena's ``ee_gripper_link`` sits 9.36 cm ahead of WidowX's
    ``gripper_link``). ``frame="base"`` expresses the result in the entity's base frame.
    ``correction_quat`` ``(1, 4)`` is a fixed rotation right-multiplied onto the
    orientation (``R_out = R @ R_corr``), as the DROID rot6d convention or a hardware
    tool-frame yaw offset require.
    """
    pos_w = entity.get_links_pos(ls_idx_local=link_idx)[:, 0, :]
    quat_w = entity.get_links_quat(ls_idx_local=link_idx)[:, 0, :]
    if offset_pos is not None or offset_quat is not None:
        pos_w, quat_w = combine_frame_transforms(pos_w, quat_w, offset_pos, offset_quat)
    if frame == "base":
        pos, quat = subtract_frame_transforms(entity.get_pos(), entity.get_quat(), pos_w, quat_w)
    elif frame == "world":
        pos, quat = pos_w, quat_w
    else:
        raise ValueError(f"Invalid frame {frame!r}. Expected 'base' or 'world'.")
    if correction_quat is not None:
        quat = quat_mul(quat, correction_quat.expand_as(quat))
    return pos, quat

gripper_state

gripper_state(dofs_pos: Tensor, *, normalize: GripperNormalize = 'raw', open_pos: float | None = None, close_pos: float | None = None, scale: float = 1.0) -> Tensor

Map gripper DOF positions (N, k) to raw (scaled) values, openness or closedness.

closedness = (pos - open_pos) / (close_pos - open_pos) and openness = 1 - closedness, so a WidowX finger (open at 0.037, closed at 0) and a Robotiq driver (open at 0, closed at 0.8) both read 0 = open, 1 = closed for closedness without per-robot special cases.

Source code in eden/managers/terms/observations/manipulation.py
def gripper_state(
    dofs_pos: torch.Tensor,
    *,
    normalize: GripperNormalize = "raw",
    open_pos: float | None = None,
    close_pos: float | None = None,
    scale: float = 1.0,
) -> torch.Tensor:
    """Map gripper DOF positions ``(N, k)`` to raw (scaled) values, openness or closedness.

    ``closedness = (pos - open_pos) / (close_pos - open_pos)`` and
    ``openness = 1 - closedness``, so a WidowX finger (open at 0.037, closed at 0) and a
    Robotiq driver (open at 0, closed at 0.8) both read 0 = open, 1 = closed for
    ``closedness`` without per-robot special cases.
    """
    if normalize == "raw":
        return dofs_pos * scale
    if open_pos is None or close_pos is None or open_pos == close_pos:
        raise ValueError(
            f"normalize={normalize!r} needs distinct open_pos and close_pos, got open_pos={open_pos}, close_pos={close_pos}."
        )
    closedness = (dofs_pos - open_pos) / (close_pos - open_pos)
    if normalize == "closedness":
        return closedness
    if normalize == "openness":
        return 1.0 - closedness
    raise ValueError(f"Unknown gripper normalize {normalize!r}; expected raw, openness or closedness.")

Contact

Per-link contact-force-norm observation.

Reads :meth:RigidEntity.get_links_net_contact_force (Genesis-aggregated per-link net force; (num_envs, n_links, 3) in world frame), slices to the configured link set, and returns the per-link force magnitude.

Trade-off vs. gs.sensors.ContactForce: the Genesis sensor binds to one link per SensorOptions declaration, so a 14-link hand needs 14 sensor entries plus 14 obs-term entries to do what :class:ContactForceNorm does in a single declaration. The cost is no NoisySensorMixin noise / delay / decimation modeling; that path is reachable via the :class:~eden.managers.terms.observations.sensors.SensorRead term plus a per-link sensor declaration if a config wants noise on a small subset of links.

Attributes

Classes

ContactForceNorm

ContactForceNorm(env: EnvBase, options: ObservationTermOptions)

Bases: ObservationTerm

Per-link contact-force magnitude (Euclidean, world frame).

Returns (num_envs, K) where K = len(matched links), each entry being ‖f_link‖ at the most recent scene.step().

PARAMETER DESCRIPTION
entity_name

Entity to read forces from (must be a RigidEntity).

links_name

:func:resolve_matching_names-compatible patterns selecting which links contribute. Empty list raises in :meth:build — this term is per-link by design; use :func:self_collision_cost for a global "any contact" scalar.

Notes

Output scaling, noise, clipping, and history are controlled via the inherited :class:ObservationTermOptions fields (scale, noise, clip, history_length) — pass e.g. scale=0.01 on the term options when forces are in newtons and the policy expects roughly-unit observations.

The underlying force is in world frame — Genesis's get_links_net_contact_force returns the world-frame net force per link. gs.sensors.ContactForce (the noise-aware path reachable via the generic :class:~eden.managers.terms.observations.sensors.SensorRead obs term) returns the same vector in the link's local frame. Magnitudes are equal under any choice of frame, so this term's per-link norm is unaffected by the choice; a config swapping between this term and a sensor-reading-plus-norm composition gets identical scalar values but watch the frame if a downstream consumer uses the raw force vector.

Source code in eden/managers/observation_manager.py
def __init__(self, env: EnvBase, options: ObservationTermOptions):
    super().__init__(env=env, options=options)
    self._init_post_compute_fields(env)

Attributes

entity_name class-attribute instance-attribute
entity_name: str = 'robot'
links_name: list[str] = []

Methods:

build
build() -> None
Source code in eden/managers/terms/observations/contact.py
def build(self) -> None:
    # Touch the entity here for fail-fast, but cache only the derived link idx —
    # resolve the entity lazily in compute() so deployment hot-swaps of
    # env.entities[name] are honoured (see ManagerTermBase.resolve_entity).
    entity = self.resolve_entity(self.entity_name)
    self._link_idx = resolve_link_idx(entity, self.links_name, self.device, term=type(self).__name__)
compute
compute() -> Tensor
Source code in eden/managers/terms/observations/contact.py
def compute(self) -> torch.Tensor:
    forces = self.resolve_entity(self.entity_name).get_links_net_contact_force()  # (B, n_links, 3)
    return torch.norm(forces[:, self._link_idx], dim=-1)

GroupedContactFlags

GroupedContactFlags(env: EnvBase, options: ObservationTermOptions)

Bases: ObservationTerm

Per-(link, partner-link) boolean contact flags with a specific entity.

Wraps :meth:RigidEntity.get_grouped_contacts and returns its valid mask as floats in {0, 1}, flattened to (num_envs, n_a * n_b) — the simulated counterpart of :func:motion_contact_flags for contact-annotated mimic tasks (ConTrack xhand_contact).

PARAMETER DESCRIPTION
entity_name

Active entity (usually the hand); its links index the first axis.

object_entity_name

Contact partner (usually the manipulated object).

links_name

Link patterns on entity_name selecting the rows. Required — the row order defines the observation layout.

object_links_name

Link patterns on the partner (columns). Empty selects all links.

force_threshold

None (default) flags a pair whose aggregate contact force is strictly positive (the valid mask). A number flags a pair only when that aggregate force (force_norm_sum, N) exceeds it — a contact sensor threshold, like Weave's 1 N, that ignores grazing contacts carrying almost no force.

Source code in eden/managers/observation_manager.py
def __init__(self, env: EnvBase, options: ObservationTermOptions):
    super().__init__(env=env, options=options)
    self._init_post_compute_fields(env)

Attributes

entity_name class-attribute instance-attribute
entity_name: str = 'robot'
object_entity_name class-attribute instance-attribute
object_entity_name: str = 'object'
links_name: list[str] = []
object_links_name: list[str] = []
force_threshold class-attribute instance-attribute
force_threshold: float | None = None

Methods:

build
build() -> None
Source code in eden/managers/terms/observations/contact.py
def build(self) -> None:
    # Touch both entities here for fail-fast, but cache only the derived link
    # idx — resolve the entities lazily in compute() so deployment hot-swaps of
    # env.entities[name] are honoured (see ManagerTermBase.resolve_entity).
    entity = self.resolve_entity(self.entity_name)
    obj = self.resolve_entity(self.object_entity_name)
    if not self.links_name:
        raise ValueError(f"{term_subject(self)}: links_name is required (defines the observation layout).")
    self._links_idx = resolve_link_idx(entity, self.links_name, self.device)
    self._object_links_idx = (
        resolve_link_idx(obj, self.object_links_name, self.device) if self.object_links_name else None
    )
compute
compute() -> Tensor
Source code in eden/managers/terms/observations/contact.py
def compute(self) -> torch.Tensor:
    contacts = self.resolve_entity(self.entity_name).get_grouped_contacts(
        self.resolve_entity(self.object_entity_name),
        links_a_idx_local=self._links_idx,
        links_b_idx_local=self._object_links_idx,
    )
    flags = contacts["valid"] if self.force_threshold is None else contacts["force_norm_sum"] > self.force_threshold
    return flags.reshape(flags.shape[0], -1).to(torch.float32)

Functions:

motion_contact_flags

motion_contact_flags(env: MimicRLEnvBase, *, entity_name: str, links_name: list[str]) -> Tensor

Demo per-(link, partner) contact flags at the current motion frame.

Reads motion_manager.data[entity_name].contact_points_valid (frame 0) for the named motion links; returns floats in {0, 1} flattened to (num_envs, L * num_partners) — the reference counterpart of :class:GroupedContactFlags (ConTrack ref_xhand_contact).

Source code in eden/managers/terms/observations/contact.py
@OBSERVATION_TERM_REGISTRY.register()
def motion_contact_flags(env: MimicRLEnvBase, *, entity_name: str, links_name: list[str]) -> torch.Tensor:
    """Demo per-(link, partner) contact flags at the current motion frame.

    Reads ``motion_manager.data[entity_name].contact_points_valid`` (frame 0)
    for the named motion links; returns floats in ``{0, 1}`` flattened to
    ``(num_envs, L * num_partners)`` — the reference counterpart of
    :class:`GroupedContactFlags` (ConTrack ``ref_xhand_contact``).
    """
    assert_motion_field_present(env, entity_name, "contact_points_valid")
    motion_idx = get_motion_link_idx(env, entity_name, links_name)
    valid = env.motion_manager.data[entity_name].contact_points_valid[:, 0].index_select(1, motion_idx)
    return valid.reshape(valid.shape[0], -1).to(torch.float32)

motion_future_contact_label

motion_future_contact_label(env: MimicRLEnvBase, *, entity_name: str, frame_offsets: Sequence[int], links_name: list[str] | None = None, flatten: bool = True) -> Tensor

Demo tri-state per-link contact labels (+1 contact / -1 no contact / 0 unlabelled) at future frames.

Shape (N, K*L) (or (N, K, L) if flatten=False), as floats. Reads motion_manager.data[entity_name].contact_label; links_name=None returns every motion link of the entity. The reference counterpart of :class:GroupedContactFlags for a dataset whose contact annotation distinguishes "must not touch" from "don't care" (e.g. Weave's G1-Inspire-HOI labels).

Source code in eden/managers/terms/observations/contact.py
@OBSERVATION_TERM_REGISTRY.register()
def motion_future_contact_label(
    env: MimicRLEnvBase,
    *,
    entity_name: str,
    frame_offsets: Sequence[int],
    links_name: list[str] | None = None,
    flatten: bool = True,
) -> torch.Tensor:
    """Demo tri-state per-link contact labels (+1 contact / -1 no contact / 0 unlabelled) at future frames.

    Shape ``(N, K*L)`` (or ``(N, K, L)`` if ``flatten=False``), as floats. Reads
    ``motion_manager.data[entity_name].contact_label``; ``links_name=None`` returns every motion link of the entity.
    The reference counterpart of :class:`GroupedContactFlags` for a dataset whose contact annotation distinguishes
    "must not touch" from "don't care" (e.g. Weave's G1-Inspire-HOI labels).
    """
    assert_motion_field_present(env, entity_name, "contact_label")
    ref = env.motion_manager.data[entity_name]
    offsets = resolve_frame_offsets(env, "contact_label", frame_offsets, ref.contact_label.shape[1])
    sel = None if links_name is None else get_motion_link_idx(env, entity_name, links_name)
    labels = select_motion_frames(ref.contact_label, offsets, sel).to(torch.float32)  # (N, K, L)
    return labels.reshape(labels.shape[0], -1) if flatten else labels

Sensors

Observation term that concatenates configured sensor readings.

Attributes

Classes

SensorRead

SensorRead(env: EnvBase, options: ObservationTermOptions)

Bases: ObservationTerm

Concatenate selected sensor readings into a single observation tensor.

Sensors return a tensor or NamedTuple of tensors; a tensor with more than two dimensions is flattened to (num_envs, -1), and every tensor is cast to float before concatenation along the last dimension.

PARAMETER DESCRIPTION
sensor_names

Sensor names, regexes or globs to include, in output order. If None, all sensors are included.

TYPE: list[str]

read_ground_truth

If True, read from the sensors' ground truth values instead of their noisy readings.

TYPE: bool

post_process_func

An optional function to apply to each sensor reading before concatenation.

TYPE: Callable

Source code in eden/managers/observation_manager.py
def __init__(self, env: EnvBase, options: ObservationTermOptions):
    super().__init__(env=env, options=options)
    self._init_post_compute_fields(env)

Attributes

sensor_names class-attribute instance-attribute
sensor_names: list[str] | None = None
read_ground_truth class-attribute instance-attribute
read_ground_truth: bool = False
post_process_func class-attribute instance-attribute
post_process_func: Callable | None = None

Methods:

build
build() -> None
Source code in eden/managers/terms/observations/sensors.py
def build(self) -> None:
    all_sensor_names = list(self._env.sensors.keys())
    if self.sensor_names is None:
        matched_sensor_names = all_sensor_names
    else:
        _, matched_sensor_names = resolve_matching_names(
            self.sensor_names,
            all_sensor_names,
            preserve_order=True,
        )
    self._sensors = [self._env.sensors[sensor_name] for sensor_name in matched_sensor_names]
compute
compute(*args, **kwargs) -> Tensor
Source code in eden/managers/terms/observations/sensors.py
def compute(self, *args, **kwargs) -> torch.Tensor:
    sensor_readings = []
    for sensor in self._sensors:
        data = sensor.read_ground_truth() if self.read_ground_truth else sensor.read()
        if self.post_process_func is not None:
            data = self.post_process_func(data)
        if not isinstance(data, tuple):
            data = (data,)

        for tensor in data:
            if tensor.ndim > 2:
                tensor = tensor.flatten(start_dim=1)
            sensor_readings.append(tensor.float())

    self._cached = torch.cat(sensor_readings, dim=-1)
    return self._cached

Functions:

Cameras

Camera observation terms (RGB, depth, segmentation, normals, point cloud).

Attributes

Classes

Functions:

rgb_image

rgb_image(env: RLEnvBase, *, camera_name: str, normalize: bool = False, channels_first: bool = False) -> Tensor

RGB image from a camera, optionally CNN-ready.

By default returns the renderer's native output ((N, H, W, C), uint8). For feeding a CNN policy (rsl_rl's ActorCriticCNN expects float (N, C, H, W)), set normalize=True (uint8 -> float in [0, 1]) and channels_first=True. Either flag also drops an alpha channel (the madrona batch renderer returns RGBA).

Source code in eden/managers/terms/observations/camera.py
@OBSERVATION_TERM_REGISTRY.register()
def rgb_image(
    env: RLEnvBase,
    *,
    camera_name: str,
    normalize: bool = False,
    channels_first: bool = False,
) -> torch.Tensor:
    """RGB image from a camera, optionally CNN-ready.

    By default returns the renderer's native output (``(N, H, W, C)``, uint8).
    For feeding a CNN policy (rsl_rl's ``ActorCriticCNN`` expects float
    ``(N, C, H, W)``), set ``normalize=True`` (uint8 -> float in [0, 1]) and
    ``channels_first=True``. Either flag also drops an alpha channel (the
    madrona batch renderer returns RGBA).
    """
    rgb = env.cameras[camera_name].render_rgb()
    if normalize or channels_first:
        rgb = rgb[..., :3]
    if normalize:
        rgb = rgb.float() / 255.0 if rgb.dtype == torch.uint8 else rgb.float()
    if channels_first:
        rgb = rgb.permute(0, 3, 1, 2).contiguous()
    return rgb

depth_image

depth_image(env: RLEnvBase, *, camera_name: str) -> Tensor
Source code in eden/managers/terms/observations/camera.py
@OBSERVATION_TERM_REGISTRY.register()
def depth_image(
    env: RLEnvBase,
    *,
    camera_name: str,
) -> torch.Tensor:
    return env.cameras[camera_name].render_depth()

segmentation_image

segmentation_image(env: RLEnvBase, *, camera_name: str) -> Tensor
Source code in eden/managers/terms/observations/camera.py
@OBSERVATION_TERM_REGISTRY.register()
def segmentation_image(
    env: RLEnvBase,
    *,
    camera_name: str,
) -> torch.Tensor:
    return env.cameras[camera_name].render_segm()

normal_image

normal_image(env: RLEnvBase, *, camera_name: str) -> Tensor
Source code in eden/managers/terms/observations/camera.py
@OBSERVATION_TERM_REGISTRY.register()
def normal_image(
    env: RLEnvBase,
    *,
    camera_name: str,
) -> torch.Tensor:
    return env.cameras[camera_name].render_normal()

pointcloud_image

pointcloud_image(env: RLEnvBase, *, camera_name: str, world_frame: bool = False) -> Tensor
Source code in eden/managers/terms/observations/camera.py
@OBSERVATION_TERM_REGISTRY.register()
def pointcloud_image(
    env: RLEnvBase,
    *,
    camera_name: str,
    world_frame: bool = False,
) -> torch.Tensor:
    pc, valid_mask = env.cameras[camera_name].render_pointcloud(world_frame=world_frame)
    # Zero invalid/background pixels so inf depth points never reach the policy.
    pc[~valid_mask] = 0.0
    return pc

Motion tracking

Terms that expose env.motion_manager reference state on a MimicRLEnvBase.

Observation terms exposing motion-reference state to the policy.

Reads env.motion_manager.data[entity_name] (filled per step at the start of MimicRLEnvBase.step() and refilled per env on reset) — NOT reference_frame, which would force a recompute pass and a GPU sync.

Current vs future frames

The motion-reference buffer is shaped (num_envs, num_frames, ...) where num_frames comes from :attr:MotionManagerOptions.num_frames. Terms in this module split into two families:

  • Current-frame terms (motion_base_*_w, motion_dofs_*, motion_links_*_b) index [:, 0, ...]. With the default data_start_from="one_frame_interval" frame 0 is one tick ahead; with data_start_from="current_time" frame 0 is the current step.
  • Future-frame terms (motion_future_*) index a caller-supplied frame_offsets tuple into the same buffer. They are intended for policies that consume a short lookahead (e.g. ProtoMotions' (1, 2, 4, 8)-step target poses on top of the current frame). The caller must size num_frames to cover max(frame_offsets) + 1; the term raises a clear error on the first call otherwise.
Shape convention
  • Single-target terms (motion_base_*_w, motion_dofs_*) return (num_envs, F).
  • Multi-link terms (motion_links_*) return (num_envs, K*F) by default — flat, so they slot into rank-2 obs concat groups (the common case for MLP policies) without the caller having to pass flatten=True every time. Pass flatten=False to keep the link axis as a separate (num_envs, K, F) dimension for structured policies (transformer / per-link MLP).
  • Future-frame terms add a leading K = len(frame_offsets) axis: (num_envs, K*D) flat / (num_envs, K, D) unflattened for DOF terms; (num_envs, K*L*F) / (num_envs, K, L, F) for link terms.

Each term calls :func:assert_motion_field_present for the field(s) it actually reads, so a dataset that doesn't supply the required field (e.g. a fixed-base motion sequence with no base_pos_w, or a hand sequence with no links_quat_w) fails fast on first read instead of silently feeding the MotionReferenceData.make_empty default (zeros / identity quat) to the policy. Position-only and orientation-only terms intentionally assert only on the field they consume — a dataset with only positions can still feed motion_links_pos_w / motion_links_pos_b.

Attributes

Classes

Functions:

motion_base_pos_w

motion_base_pos_w(env: MimicRLEnvBase, *, entity_name: str) -> Tensor

World-frame motion-ref base position. Shape (num_envs, 3).

Free-base entities only — fixed-base motion sequences leave base_pos_w at the make-empty default and this term raises.

Source code in eden/managers/terms/observations/tracking.py
@OBSERVATION_TERM_REGISTRY.register()
def motion_base_pos_w(env: MimicRLEnvBase, *, entity_name: str) -> torch.Tensor:
    """World-frame motion-ref base position. Shape ``(num_envs, 3)``.

    Free-base entities only — fixed-base motion sequences leave
    ``base_pos_w`` at the make-empty default and this term raises.
    """
    assert_motion_field_present(env, entity_name, "base_pos_w")
    ref = env.motion_manager.data[entity_name]
    return current_motion_frame(ref.base_pos_w)

motion_base_quat_w

motion_base_quat_w(env: MimicRLEnvBase, *, entity_name: str) -> Tensor

World-frame motion-ref base quaternion (wxyz). Shape (num_envs, 4).

Quat is unit-norm — guaranteed by MotionSequence.__post_init__.

Source code in eden/managers/terms/observations/tracking.py
@OBSERVATION_TERM_REGISTRY.register()
def motion_base_quat_w(env: MimicRLEnvBase, *, entity_name: str) -> torch.Tensor:
    """World-frame motion-ref base quaternion (wxyz). Shape ``(num_envs, 4)``.

    Quat is unit-norm — guaranteed by ``MotionSequence.__post_init__``.
    """
    assert_motion_field_present(env, entity_name, "base_quat_w")
    ref = env.motion_manager.data[entity_name]
    return current_motion_frame(ref.base_quat_w)
motion_links_pos_w(env: MimicRLEnvBase, *, entity_name: str, links_name: list[str], flatten: bool = True) -> Tensor

Return world-frame motion-ref link positions.

Shape (num_envs, K*3) (or (num_envs, K, 3) if flatten=False).

Single-link (links_name=["wrist"]) gives e.g. dexmachina ref wrist position; multi-link gives ref keypoints (fingertips, etc.).

Source code in eden/managers/terms/observations/tracking.py
@OBSERVATION_TERM_REGISTRY.register()
def motion_links_pos_w(
    env: MimicRLEnvBase,
    *,
    entity_name: str,
    links_name: list[str],
    flatten: bool = True,
) -> torch.Tensor:
    """Return world-frame motion-ref link positions.

    Shape ``(num_envs, K*3)`` (or ``(num_envs, K, 3)`` if ``flatten=False``).

    Single-link (``links_name=["wrist"]``) gives e.g. dexmachina ref wrist
    position; multi-link gives ref keypoints (fingertips, etc.).
    """
    assert_motion_field_present(env, entity_name, "links_pos_w")
    motion_idx = get_motion_link_idx(env, entity_name, links_name)
    ref = env.motion_manager.data[entity_name]
    return _maybe_flatten(current_motion_frame(ref.links_pos_w, motion_idx), flatten)
motion_links_quat_w(env: MimicRLEnvBase, *, entity_name: str, links_name: list[str], flatten: bool = True) -> Tensor

Return world-frame motion-ref link quaternions (wxyz).

Shape (num_envs, K*4) (or (num_envs, K, 4) if flatten=False).

Quats are unit-norm — guaranteed by MotionSequence.__post_init__.

Source code in eden/managers/terms/observations/tracking.py
@OBSERVATION_TERM_REGISTRY.register()
def motion_links_quat_w(
    env: MimicRLEnvBase,
    *,
    entity_name: str,
    links_name: list[str],
    flatten: bool = True,
) -> torch.Tensor:
    """Return world-frame motion-ref link quaternions (wxyz).

    Shape ``(num_envs, K*4)`` (or ``(num_envs, K, 4)`` if ``flatten=False``).

    Quats are unit-norm — guaranteed by ``MotionSequence.__post_init__``.
    """
    assert_motion_field_present(env, entity_name, "links_quat_w")
    motion_idx = get_motion_link_idx(env, entity_name, links_name)
    ref = env.motion_manager.data[entity_name]
    return _maybe_flatten(current_motion_frame(ref.links_quat_w, motion_idx), flatten)

motion_dofs_pos

motion_dofs_pos(env: MimicRLEnvBase, *, entity_name: str, dofs_name: list[str] | None = None) -> Tensor

Motion-ref qpos. Shape (num_envs, D).

dofs_name=None returns every motion-ref DOF for the entity. Otherwise returns just the named DOFs (motion-ref-axis order). Sibling of :class:eden.managers.terms.observations.proprio.DofsPos, which reads the robot's own state.

Source code in eden/managers/terms/observations/tracking.py
@OBSERVATION_TERM_REGISTRY.register()
def motion_dofs_pos(
    env: MimicRLEnvBase,
    *,
    entity_name: str,
    dofs_name: list[str] | None = None,
) -> torch.Tensor:
    """Motion-ref qpos. Shape ``(num_envs, D)``.

    ``dofs_name=None`` returns every motion-ref DOF for the entity.
    Otherwise returns just the named DOFs (motion-ref-axis order).
    Sibling of :class:`eden.managers.terms.observations.proprio.DofsPos`,
    which reads the robot's own state.
    """
    assert_motion_field_present(env, entity_name, "dofs_pos")
    ref = env.motion_manager.data[entity_name]
    sel = None if dofs_name is None else get_motion_dofs_idx(env, entity_name, dofs_name)
    return current_motion_frame(ref.dofs_pos, sel)

motion_dofs_vel

motion_dofs_vel(env: MimicRLEnvBase, *, entity_name: str, dofs_name: list[str] | None = None) -> Tensor

Motion-ref qvel. Symmetric to :func:motion_dofs_pos.

Sibling of :class:eden.managers.terms.observations.proprio.DofsVel.

Source code in eden/managers/terms/observations/tracking.py
@OBSERVATION_TERM_REGISTRY.register()
def motion_dofs_vel(
    env: MimicRLEnvBase,
    *,
    entity_name: str,
    dofs_name: list[str] | None = None,
) -> torch.Tensor:
    """Motion-ref qvel. Symmetric to :func:`motion_dofs_pos`.

    Sibling of :class:`eden.managers.terms.observations.proprio.DofsVel`.
    """
    assert_motion_field_present(env, entity_name, "dofs_vel")
    ref = env.motion_manager.data[entity_name]
    sel = None if dofs_name is None else get_motion_dofs_idx(env, entity_name, dofs_name)
    return current_motion_frame(ref.dofs_vel, sel)
motion_links_pos_b(env: MimicRLEnvBase, *, entity_name: str, links_name: list[str], anchor_link_name: str, flatten: bool = True) -> Tensor

Return motion-ref links_name positions in the robot's anchor_link_name frame.

Shape (num_envs, K*3) (or (num_envs, K, 3) if flatten=False).

Position-only — reads links_pos_w (motion side) plus the robot's anchor link pose. Does not require links_quat_w from the motion sequence (a dataset with only positions still works here).

To reproduce beyondmimic's motion_anchor_pos_b, pass links_name=[anchor_link_name] (e.g. links_name=["torso_link"], anchor_link_name="torso_link") — that reproduces the original anchor-vs-anchor numerics and the (num_envs, 3) flat shape.

Source code in eden/managers/terms/observations/tracking.py
@OBSERVATION_TERM_REGISTRY.register()
def motion_links_pos_b(
    env: MimicRLEnvBase,
    *,
    entity_name: str,
    links_name: list[str],
    anchor_link_name: str,
    flatten: bool = True,
) -> torch.Tensor:
    """Return motion-ref ``links_name`` positions in the robot's ``anchor_link_name`` frame.

    Shape ``(num_envs, K*3)`` (or ``(num_envs, K, 3)`` if ``flatten=False``).

    Position-only — reads ``links_pos_w`` (motion side) plus the robot's
    anchor link pose. Does **not** require ``links_quat_w`` from the
    motion sequence (a dataset with only positions still works here).

    To reproduce beyondmimic's ``motion_anchor_pos_b``,
    pass ``links_name=[anchor_link_name]`` (e.g.
    ``links_name=["torso_link"], anchor_link_name="torso_link"``) — that
    reproduces the original anchor-vs-anchor numerics and the
    ``(num_envs, 3)`` flat shape.
    """
    assert_motion_field_present(env, entity_name, "links_pos_w")

    anchor_robot_idx = get_robot_link_idx(env, entity_name, [anchor_link_name])
    motion_idx = get_motion_link_idx(env, entity_name, links_name)

    entity = env.get_entity(entity_name)
    anchor_pos = entity.get_links_pos(ls_idx_local=anchor_robot_idx)[:, 0]  # (N, 3)
    anchor_quat = entity.get_links_quat(ls_idx_local=anchor_robot_idx)[:, 0]  # (N, 4)

    ref = env.motion_manager.data[entity_name]
    motion_pos = current_motion_frame(ref.links_pos_w, motion_idx)  # (N, L, 3)

    pos_b = links_pos_in_anchor_frame(anchor_pos, anchor_quat, motion_pos)  # (N, L, 3)
    return _maybe_flatten(pos_b, flatten)
motion_links_ori_b(env: MimicRLEnvBase, *, entity_name: str, links_name: list[str], anchor_link_name: str, flatten: bool = True) -> Tensor

Return motion-ref links_name orientations in the robot's anchor_link_name frame.

Expressed as 2-column rotation matrices, shape (num_envs, K*6) (or (num_envs, K, 6) if flatten=False).

Orientation-only — reads links_quat_w (motion side) plus the robot's anchor link orientation. Does not require links_pos_w.

To reproduce beyondmimic's motion_anchor_ori_b, pass links_name=[anchor_link_name]. The 2-column rotation matrix output (not quaternion) is beyondmimic's representation, so its pretrained policies work unchanged; configs that prefer quat can read :func:motion_links_quat_w instead.

Source code in eden/managers/terms/observations/tracking.py
@OBSERVATION_TERM_REGISTRY.register()
def motion_links_ori_b(
    env: MimicRLEnvBase,
    *,
    entity_name: str,
    links_name: list[str],
    anchor_link_name: str,
    flatten: bool = True,
) -> torch.Tensor:
    """Return motion-ref ``links_name`` orientations in the robot's ``anchor_link_name`` frame.

    Expressed as 2-column rotation matrices, shape ``(num_envs, K*6)``
    (or ``(num_envs, K, 6)`` if ``flatten=False``).

    Orientation-only — reads ``links_quat_w`` (motion side) plus the
    robot's anchor link orientation. Does **not** require ``links_pos_w``.

    To reproduce beyondmimic's ``motion_anchor_ori_b``, pass
    ``links_name=[anchor_link_name]``. The 2-column rotation matrix
    output (not quaternion) is beyondmimic's representation, so its
    pretrained policies work unchanged; configs that prefer quat can read
    :func:`motion_links_quat_w` instead.
    """
    assert_motion_field_present(env, entity_name, "links_quat_w")

    anchor_robot_idx = get_robot_link_idx(env, entity_name, [anchor_link_name])
    motion_idx = get_motion_link_idx(env, entity_name, links_name)

    entity = env.get_entity(entity_name)
    anchor_quat = entity.get_links_quat(ls_idx_local=anchor_robot_idx)[:, 0]  # (N, 4)

    ref = env.motion_manager.data[entity_name]
    motion_quat = current_motion_frame(ref.links_quat_w, motion_idx)  # (N, L, 4)

    quat_b = links_quat_in_anchor_frame(anchor_quat, motion_quat)  # (N, L, 4)
    ori_b = quat_to_rot6d_cols(quat_b)  # (N, L, 6)
    return _maybe_flatten(ori_b, flatten)

robot_body_pos_b

robot_body_pos_b(env: MimicRLEnvBase, *, entity_name: str, anchor_link_name: str, tracked_link_names: list[str], flatten: bool = True) -> Tensor

Return robot body positions relative to its own anchor link.

Shape (N, K*3) (or (N, K, 3) if flatten=False).

Symmetric to :func:motion_links_pos_b but reads the robot's own state instead of the motion reference. Useful as a critic-only observation that sees the policy's body geometry in the same frame as the motion targets.

Source code in eden/managers/terms/observations/tracking.py
@OBSERVATION_TERM_REGISTRY.register()
def robot_body_pos_b(
    env: MimicRLEnvBase,
    *,
    entity_name: str,
    anchor_link_name: str,
    tracked_link_names: list[str],
    flatten: bool = True,
) -> torch.Tensor:
    """Return robot body positions relative to its own anchor link.

    Shape ``(N, K*3)`` (or ``(N, K, 3)`` if ``flatten=False``).

    Symmetric to :func:`motion_links_pos_b` but reads the robot's own state
    instead of the motion reference. Useful as a critic-only observation that
    sees the policy's body geometry in the same frame as the motion targets.
    """
    pos_b, _ = _robot_body_relative(env, entity_name, anchor_link_name, tracked_link_names)
    return _maybe_flatten(pos_b, flatten)

robot_body_ori_b

robot_body_ori_b(env: MimicRLEnvBase, *, entity_name: str, anchor_link_name: str, tracked_link_names: list[str], flatten: bool = True) -> Tensor

Return robot body orientations relative to its own anchor, as 2-column rotation matrices.

Shape (N, K*6) (or (N, K, 6) if flatten=False).

Symmetric to :func:motion_links_ori_b.

Source code in eden/managers/terms/observations/tracking.py
@OBSERVATION_TERM_REGISTRY.register()
def robot_body_ori_b(
    env: MimicRLEnvBase,
    *,
    entity_name: str,
    anchor_link_name: str,
    tracked_link_names: list[str],
    flatten: bool = True,
) -> torch.Tensor:
    """Return robot body orientations relative to its own anchor, as 2-column rotation matrices.

    Shape ``(N, K*6)`` (or ``(N, K, 6)`` if ``flatten=False``).

    Symmetric to :func:`motion_links_ori_b`.
    """
    _, ori_b = _robot_body_relative(env, entity_name, anchor_link_name, tracked_link_names)
    ori_6d = quat_to_rot6d_cols(ori_b)
    return _maybe_flatten(ori_6d, flatten)

motion_future_dofs_pos

motion_future_dofs_pos(env: MimicRLEnvBase, *, entity_name: str, frame_offsets: Sequence[int], dofs_name: list[str] | None = None, flatten: bool = True) -> Tensor

Return motion-ref qpos at multiple future buffer frames.

Shape (N, K*D) (or (N, K, D) if flatten=False).

frame_offsets are interpreted as buffer indices into env.motion_manager.data[entity_name].dofs_pos (shape (N, F, D)). With data_start_from="current_time" offset 0 is the current step and (1, 2, 4, 8) are 1/2/4/8 buffer ticks ahead; with the default data_start_from="one_frame_interval" offset 0 is already one tick ahead. The per-tick wall-clock duration is :attr:MotionManagerOptions.frame_interval_s_range.

dofs_name=None returns every motion-ref DOF for the entity.

Source code in eden/managers/terms/observations/tracking.py
@OBSERVATION_TERM_REGISTRY.register()
def motion_future_dofs_pos(
    env: MimicRLEnvBase,
    *,
    entity_name: str,
    frame_offsets: Sequence[int],
    dofs_name: list[str] | None = None,
    flatten: bool = True,
) -> torch.Tensor:
    """Return motion-ref qpos at multiple future buffer frames.

    Shape ``(N, K*D)`` (or ``(N, K, D)`` if ``flatten=False``).

    ``frame_offsets`` are interpreted as **buffer indices** into
    ``env.motion_manager.data[entity_name].dofs_pos`` (shape ``(N, F, D)``).
    With ``data_start_from="current_time"`` offset 0 is the current step
    and ``(1, 2, 4, 8)`` are 1/2/4/8 buffer ticks ahead; with the default
    ``data_start_from="one_frame_interval"`` offset 0 is already one tick
    ahead. The per-tick wall-clock duration is
    :attr:`MotionManagerOptions.frame_interval_s_range`.

    ``dofs_name=None`` returns every motion-ref DOF for the entity.
    """
    assert_motion_field_present(env, entity_name, "dofs_pos")
    ref = env.motion_manager.data[entity_name]
    offsets = resolve_frame_offsets(env, "dofs_pos", frame_offsets, ref.dofs_pos.shape[1])
    sel = None if dofs_name is None else get_motion_dofs_idx(env, entity_name, dofs_name)
    future = select_motion_frames(ref.dofs_pos, offsets, sel)  # (N, K, D)
    return _maybe_flatten(future, flatten)

motion_future_dofs_vel

motion_future_dofs_vel(env: MimicRLEnvBase, *, entity_name: str, frame_offsets: Sequence[int], dofs_name: list[str] | None = None, flatten: bool = True) -> Tensor

Return motion-ref qvel at multiple future buffer frames.

Symmetric to :func:motion_future_dofs_pos.

Source code in eden/managers/terms/observations/tracking.py
@OBSERVATION_TERM_REGISTRY.register()
def motion_future_dofs_vel(
    env: MimicRLEnvBase,
    *,
    entity_name: str,
    frame_offsets: Sequence[int],
    dofs_name: list[str] | None = None,
    flatten: bool = True,
) -> torch.Tensor:
    """Return motion-ref qvel at multiple future buffer frames.

    Symmetric to :func:`motion_future_dofs_pos`.
    """
    assert_motion_field_present(env, entity_name, "dofs_vel")
    ref = env.motion_manager.data[entity_name]
    offsets = resolve_frame_offsets(env, "dofs_vel", frame_offsets, ref.dofs_vel.shape[1])
    sel = None if dofs_name is None else get_motion_dofs_idx(env, entity_name, dofs_name)
    future = select_motion_frames(ref.dofs_vel, offsets, sel)
    return _maybe_flatten(future, flatten)
motion_future_links_ori_b(env: MimicRLEnvBase, *, entity_name: str, links_name: list[str], anchor_link_name: str, frame_offsets: Sequence[int], representation: Literal['rot6d', 'tan_norm'] = 'rot6d', flatten: bool = True) -> Tensor

Return future-frame motion-ref link orientations in the robot's current anchor frame.

Shape (N, K*L*6) (or (N, K, L, 6) if flatten=False).

The relative rotation is R_rel = R_robot_anchor⁻¹ · R_motion_future — i.e. the motion future orientation is brought into the robot's current anchor frame, matching the ProtoMotions reduced-coords target pose formulation. Two output representations:

  • representation="rot6d": 2-column rotation matrix [R_rel·x̂, R_rel·ŷ] (Zhou et al. 2019). Same convention as :func:motion_links_ori_b, so a config can swap the current-frame and future-frame variants without retraining the head.
  • representation="tan_norm": ProtoMotions quat_to_tan_norm output [R_rel·x̂, R_rel·ẑ]. Use this when feeding a checkpoint trained against ProtoMotions' target-poses obs (e.g. the g1-bones-deploy actor).

Set links_name=[anchor_link_name] to reproduce the ProtoMotions "anchor-relative anchor future rotation" channel (single link, K futures).

Source code in eden/managers/terms/observations/tracking.py
@OBSERVATION_TERM_REGISTRY.register()
def motion_future_links_ori_b(
    env: MimicRLEnvBase,
    *,
    entity_name: str,
    links_name: list[str],
    anchor_link_name: str,
    frame_offsets: Sequence[int],
    representation: Literal["rot6d", "tan_norm"] = "rot6d",
    flatten: bool = True,
) -> torch.Tensor:
    """Return future-frame motion-ref link orientations in the robot's current anchor frame.

    Shape ``(N, K*L*6)`` (or ``(N, K, L, 6)`` if ``flatten=False``).

    The relative rotation is ``R_rel = R_robot_anchor⁻¹ · R_motion_future``
    — i.e. the motion future orientation is brought into the robot's
    *current* anchor frame, matching the ProtoMotions reduced-coords target
    pose formulation. Two output representations:

    * ``representation="rot6d"``: 2-column rotation matrix
      ``[R_rel·x̂, R_rel·ŷ]`` (Zhou et al. 2019). Same convention as
      :func:`motion_links_ori_b`, so a config can swap the current-frame
      and future-frame variants without retraining the head.
    * ``representation="tan_norm"``: ProtoMotions ``quat_to_tan_norm``
      output ``[R_rel·x̂, R_rel·ẑ]``. Use this when feeding a checkpoint
      trained against ProtoMotions' target-poses obs (e.g. the
      ``g1-bones-deploy`` actor).

    Set ``links_name=[anchor_link_name]`` to reproduce the ProtoMotions
    "anchor-relative anchor future rotation" channel (single link, K
    futures).
    """
    assert_motion_field_present(env, entity_name, "links_quat_w")

    ref = env.motion_manager.data[entity_name]
    offsets = resolve_frame_offsets(env, "links_quat_w", frame_offsets, ref.links_quat_w.shape[1])
    motion_idx = get_motion_link_idx(env, entity_name, links_name)
    anchor_robot_idx = get_robot_link_idx(env, entity_name, [anchor_link_name])

    # (N, K, L_all, 4) → (N, K, L, 4): pick K frames, then L motion links.
    motion_future_quat = select_motion_frames(ref.links_quat_w, offsets, motion_idx)

    entity = env.get_entity(entity_name)
    # Robot anchor quat is read at the current sim step (no future axis on the
    # robot side; only the motion ref looks ahead).
    anchor_quat = entity.get_links_quat(ls_idx_local=anchor_robot_idx)[:, 0]  # (N, 4)

    # Robot anchor has no future axis; broadcast it over the K motion futures so
    # (N, K) is the batch and L the link axis for the shared transform.
    K = offsets.shape[0]
    anchor_quat_k = anchor_quat[:, None, :].expand(-1, K, -1)  # (N, K, 4)
    quat_b = links_quat_in_anchor_frame(anchor_quat_k, motion_future_quat)  # (N, K, L, 4)
    return _maybe_flatten(_orientation_6d(quat_b, representation), flatten)  # (N, K, L, 6)
motion_future_links_pos_b(env: MimicRLEnvBase, *, entity_name: str, links_name: list[str], anchor_link_name: str, frame_offsets: Sequence[int], flatten: bool = True) -> Tensor

Return future-frame motion-ref links_name positions in the robot's current anchor frame.

Shape (N, K*L*3) (or (N, K, L, 3) if flatten=False).

Position-only sibling of :func:motion_future_links_ori_b. The position delta is rotated into the robot's current anchor frame (R_robot_anchor⁻¹ · (p_motion_future − p_robot_anchor)), matching the ProtoMotions reduced-coords target-position formulation. Reads links_pos_w (motion side) plus the robot's anchor link pose.

Set links_name=[anchor_link_name] to reproduce the ProtoMotions "anchor-relative anchor future position" channel (single link, K futures). The current-frame counterpart is :func:motion_links_pos_b.

Source code in eden/managers/terms/observations/tracking.py
@OBSERVATION_TERM_REGISTRY.register()
def motion_future_links_pos_b(
    env: MimicRLEnvBase,
    *,
    entity_name: str,
    links_name: list[str],
    anchor_link_name: str,
    frame_offsets: Sequence[int],
    flatten: bool = True,
) -> torch.Tensor:
    """Return future-frame motion-ref ``links_name`` positions in the robot's current anchor frame.

    Shape ``(N, K*L*3)`` (or ``(N, K, L, 3)`` if ``flatten=False``).

    Position-only sibling of :func:`motion_future_links_ori_b`. The position
    delta is rotated into the robot's *current* anchor frame
    (``R_robot_anchor⁻¹ · (p_motion_future − p_robot_anchor)``), matching the
    ProtoMotions reduced-coords target-position formulation. Reads
    ``links_pos_w`` (motion side) plus the robot's anchor link pose.

    Set ``links_name=[anchor_link_name]`` to reproduce the ProtoMotions
    "anchor-relative anchor future position" channel (single link, K
    futures). The current-frame counterpart is :func:`motion_links_pos_b`.
    """
    assert_motion_field_present(env, entity_name, "links_pos_w")

    ref = env.motion_manager.data[entity_name]
    offsets = resolve_frame_offsets(env, "links_pos_w", frame_offsets, ref.links_pos_w.shape[1])
    motion_idx = get_motion_link_idx(env, entity_name, links_name)
    anchor_robot_idx = get_robot_link_idx(env, entity_name, [anchor_link_name])

    # (N, K, L_all, 3) → (N, K, L, 3): pick K frames, then L motion links.
    motion_future_pos = select_motion_frames(ref.links_pos_w, offsets, motion_idx)

    entity = env.get_entity(entity_name)
    # Robot anchor pose is read at the current sim step (no future axis on the
    # robot side; only the motion ref looks ahead).
    anchor_pos = entity.get_links_pos(ls_idx_local=anchor_robot_idx)[:, 0]  # (N, 3)
    anchor_quat = entity.get_links_quat(ls_idx_local=anchor_robot_idx)[:, 0]  # (N, 4)

    # Robot anchor has no future axis; broadcast it over the K motion futures so
    # (N, K) is the batch and L the link axis for the shared transform.
    K = offsets.shape[0]
    anchor_pos_k = anchor_pos[:, None, :].expand(-1, K, -1)  # (N, K, 3)
    anchor_quat_k = anchor_quat[:, None, :].expand(-1, K, -1)  # (N, K, 4)
    pos_b = links_pos_in_anchor_frame(anchor_pos_k, anchor_quat_k, motion_future_pos)  # (N, K, L, 3)
    return _maybe_flatten(pos_b, flatten)

motion_future_base_pos_w

motion_future_base_pos_w(env: MimicRLEnvBase, *, entity_name: str, frame_offsets: Sequence[int], flatten: bool = True) -> Tensor

World-frame motion-ref base position at multiple buffer frames.

Shape (N, K*3) (or (N, K, 3) if flatten=False). The base-pose counterpart of :func:motion_future_dofs_pos — used e.g. for future object-pose targets in hand-object mimic tasks.

Source code in eden/managers/terms/observations/tracking.py
@OBSERVATION_TERM_REGISTRY.register()
def motion_future_base_pos_w(
    env: MimicRLEnvBase,
    *,
    entity_name: str,
    frame_offsets: Sequence[int],
    flatten: bool = True,
) -> torch.Tensor:
    """World-frame motion-ref base position at multiple buffer frames.

    Shape ``(N, K*3)`` (or ``(N, K, 3)`` if ``flatten=False``). The base-pose
    counterpart of :func:`motion_future_dofs_pos` — used e.g. for future
    object-pose targets in hand-object mimic tasks.
    """
    assert_motion_field_present(env, entity_name, "base_pos_w")
    ref = env.motion_manager.data[entity_name]
    offsets = resolve_frame_offsets(env, "base_pos_w", frame_offsets, ref.base_pos_w.shape[1])
    return _maybe_flatten(select_motion_frames(ref.base_pos_w, offsets), flatten)

motion_future_base_quat_w

motion_future_base_quat_w(env: MimicRLEnvBase, *, entity_name: str, frame_offsets: Sequence[int], flatten: bool = True) -> Tensor

World-frame motion-ref base quaternion (wxyz) at multiple buffer frames.

Shape (N, K*4) (or (N, K, 4) if flatten=False).

Source code in eden/managers/terms/observations/tracking.py
@OBSERVATION_TERM_REGISTRY.register()
def motion_future_base_quat_w(
    env: MimicRLEnvBase,
    *,
    entity_name: str,
    frame_offsets: Sequence[int],
    flatten: bool = True,
) -> torch.Tensor:
    """World-frame motion-ref base quaternion (wxyz) at multiple buffer frames.

    Shape ``(N, K*4)`` (or ``(N, K, 4)`` if ``flatten=False``).
    """
    assert_motion_field_present(env, entity_name, "base_quat_w")
    ref = env.motion_manager.data[entity_name]
    offsets = resolve_frame_offsets(env, "base_quat_w", frame_offsets, ref.base_quat_w.shape[1])
    return _maybe_flatten(select_motion_frames(ref.base_quat_w, offsets), flatten)

motion_future_base_pos_b

motion_future_base_pos_b(env: MimicRLEnvBase, *, entity_name: str, anchor_entity_name: str, anchor_link_name: str, frame_offsets: Sequence[int], flatten: bool = True) -> Tensor

Future-frame motion-ref base position of entity_name in another entity's live anchor-link frame.

Shape (N, K*3) (or (N, K, 3) if flatten=False). R_anchor⁻¹ · (p_ref_future − p_anchor) with the anchor read from the simulation at the current step — the full 6-DoF anchor frame, like :func:motion_future_links_pos_b. The object-in-humanoid-frame target of a humanoid-object tracker: entity_name="object", anchor_entity_name="robot", anchor_link_name="pelvis".

Source code in eden/managers/terms/observations/tracking.py
@OBSERVATION_TERM_REGISTRY.register()
def motion_future_base_pos_b(
    env: MimicRLEnvBase,
    *,
    entity_name: str,
    anchor_entity_name: str,
    anchor_link_name: str,
    frame_offsets: Sequence[int],
    flatten: bool = True,
) -> torch.Tensor:
    """Future-frame motion-ref base position of ``entity_name`` in another entity's *live* anchor-link frame.

    Shape ``(N, K*3)`` (or ``(N, K, 3)`` if ``flatten=False``). ``R_anchor⁻¹ · (p_ref_future − p_anchor)`` with
    the anchor read from the simulation at the current step — the full 6-DoF anchor frame, like
    :func:`motion_future_links_pos_b`. The object-in-humanoid-frame target of a humanoid-object tracker:
    ``entity_name="object", anchor_entity_name="robot", anchor_link_name="pelvis"``.
    """
    assert_motion_field_present(env, entity_name, "base_pos_w")
    ref = env.motion_manager.data[entity_name]
    offsets = resolve_frame_offsets(env, "base_pos_w", frame_offsets, ref.base_pos_w.shape[1])
    target = select_motion_frames(ref.base_pos_w, offsets)  # (N, K, 3)
    anchor_pos, anchor_quat = _anchor_link_pose(env, anchor_entity_name, anchor_link_name)
    pos_b = links_pos_in_anchor_frame(anchor_pos, anchor_quat, target)  # (N, K, 3): K plays the link axis
    return _maybe_flatten(pos_b, flatten)

motion_future_base_ori_b

motion_future_base_ori_b(env: MimicRLEnvBase, *, entity_name: str, anchor_entity_name: str, anchor_link_name: str, frame_offsets: Sequence[int], representation: Literal['rot6d', 'tan_norm'] = 'rot6d', flatten: bool = True) -> Tensor

Future-frame motion-ref base orientation of entity_name in another entity's live anchor-link frame.

Shape (N, K*6) (or (N, K, 6) if flatten=False). R_anchor⁻¹ · R_ref_future as a 6-D rotation, representation as in :func:motion_future_links_ori_b. The orientation sibling of :func:motion_future_base_pos_b.

Source code in eden/managers/terms/observations/tracking.py
@OBSERVATION_TERM_REGISTRY.register()
def motion_future_base_ori_b(
    env: MimicRLEnvBase,
    *,
    entity_name: str,
    anchor_entity_name: str,
    anchor_link_name: str,
    frame_offsets: Sequence[int],
    representation: Literal["rot6d", "tan_norm"] = "rot6d",
    flatten: bool = True,
) -> torch.Tensor:
    """Future-frame motion-ref base orientation of ``entity_name`` in another entity's live anchor-link frame.

    Shape ``(N, K*6)`` (or ``(N, K, 6)`` if ``flatten=False``). ``R_anchor⁻¹ · R_ref_future`` as a 6-D rotation,
    ``representation`` as in :func:`motion_future_links_ori_b`. The orientation sibling of
    :func:`motion_future_base_pos_b`.
    """
    assert_motion_field_present(env, entity_name, "base_quat_w")
    ref = env.motion_manager.data[entity_name]
    offsets = resolve_frame_offsets(env, "base_quat_w", frame_offsets, ref.base_quat_w.shape[1])
    target = select_motion_frames(ref.base_quat_w, offsets)  # (N, K, 4)
    _, anchor_quat = _anchor_link_pose(env, anchor_entity_name, anchor_link_name)
    ori_b = _orientation_6d(links_quat_in_anchor_frame(anchor_quat, target), representation)  # (N, K, 6)
    return _maybe_flatten(ori_b, flatten)

entity_base_pos_b

entity_base_pos_b(env: MimicRLEnvBase, *, entity_name: str, anchor_entity_name: str, anchor_link_name: str) -> Tensor

Live base position of entity_name in another entity's live anchor-link frame. Shape (N, 3).

The simulated counterpart of :func:motion_future_base_pos_b — e.g. where the object is relative to the humanoid's pelvis. Reads get_pos(), the entity's solver root frame: for a free rigid object that is its centre of mass unless the object is built with align=False (then the authored body frame, the frame a reference object pose is recorded in).

Source code in eden/managers/terms/observations/tracking.py
@OBSERVATION_TERM_REGISTRY.register()
def entity_base_pos_b(
    env: MimicRLEnvBase,
    *,
    entity_name: str,
    anchor_entity_name: str,
    anchor_link_name: str,
) -> torch.Tensor:
    """Live base position of ``entity_name`` in another entity's live anchor-link frame. Shape ``(N, 3)``.

    The simulated counterpart of :func:`motion_future_base_pos_b` — e.g. where the object is relative to the
    humanoid's pelvis. Reads ``get_pos()``, the entity's solver root frame: for a free rigid object that is its
    centre of mass unless the object is built with ``align=False`` (then the authored body frame, the frame a
    reference object pose is recorded in).
    """
    anchor_pos, anchor_quat = _anchor_link_pose(env, anchor_entity_name, anchor_link_name)
    target = env.get_entity(entity_name).get_pos()[:, None]  # (N, 1, 3)
    return links_pos_in_anchor_frame(anchor_pos, anchor_quat, target)[:, 0]

entity_base_ori_b

entity_base_ori_b(env: MimicRLEnvBase, *, entity_name: str, anchor_entity_name: str, anchor_link_name: str, representation: Literal['rot6d', 'tan_norm'] = 'rot6d') -> Tensor

Live base orientation of entity_name in another entity's live anchor-link frame, 6-D. Shape (N, 6).

The orientation sibling of :func:entity_base_pos_b.

Source code in eden/managers/terms/observations/tracking.py
@OBSERVATION_TERM_REGISTRY.register()
def entity_base_ori_b(
    env: MimicRLEnvBase,
    *,
    entity_name: str,
    anchor_entity_name: str,
    anchor_link_name: str,
    representation: Literal["rot6d", "tan_norm"] = "rot6d",
) -> torch.Tensor:
    """Live base orientation of ``entity_name`` in another entity's live anchor-link frame, 6-D. Shape ``(N, 6)``.

    The orientation sibling of :func:`entity_base_pos_b`.
    """
    _, anchor_quat = _anchor_link_pose(env, anchor_entity_name, anchor_link_name)
    target = env.get_entity(entity_name).get_quat()[:, None]  # (N, 1, 4)
    return _orientation_6d(links_quat_in_anchor_frame(anchor_quat, target), representation)[:, 0]

motion_phase

motion_phase(env: MimicRLEnvBase, *, entity_name: str) -> Tensor

Normalized clip playback phase in [0, 1]. Shape (num_envs, 1).

frame / (clip_length - 1) at the current reference frame — unlike :func:eden.managers.terms.observations.common.episode_phase, a mid-clip RSI start reports its true position in the clip.

:meth:MotionManager.get_motion_phase raises when no motion term tracks entity_name, so a misspelled entity fails on the first compute instead of feeding the policy a constant-zero column.

Source code in eden/managers/terms/observations/tracking.py
@OBSERVATION_TERM_REGISTRY.register()
def motion_phase(env: MimicRLEnvBase, *, entity_name: str) -> torch.Tensor:
    """Normalized clip playback phase in ``[0, 1]``. Shape ``(num_envs, 1)``.

    ``frame / (clip_length - 1)`` at the current reference frame — unlike
    :func:`eden.managers.terms.observations.common.episode_phase`, a mid-clip
    RSI start reports its true position in the clip.

    :meth:`MotionManager.get_motion_phase` raises when no motion term tracks
    ``entity_name``, so a misspelled entity fails on the first compute instead of
    feeding the policy a constant-zero column.
    """
    return env.motion_manager.get_motion_phase(entity_name).unsqueeze(-1)

See also