Skip to content

Grasp Sampling

Grasp samplers propose 6-DoF grasps of an object in a built env, from a camera's point cloud or the object's own cloud, for a given end effector. A sampler is a GraspSamplerBase subclass registered in GRASP_SAMPLER_REGISTRY and returns a GraspResult. Two backends ship: GraspGen (one model per gripper) and GraspGenX (one cross-embodiment model, the gripper selected by name). The grasp sampling guide covers installation and use.

from eden.extensions.grasp_sampling.base import GRASP_SAMPLER_REGISTRY, GraspResult, GraspSamplerBase
from eden.options.extensions.grasp_sampling import GraspSamplerOptions

Base class, result and registry

Base class and result type for grasp samplers.

A grasp sampler turns one entity's point cloud into 6-DoF grasp poses. :class:GraspSamplerBase owns everything that is the same for every backend — reading the target's points off the camera or its surface, the obstacle cloud for collision filtering, ranking, the frame the poses place, and packing the result into Eden's pos / wxyz quat tensors — so a backend only implements :meth:~GraspSamplerBase._infer (the model call), declares its pose frame, and, if its grippers carry collision geometry, implements :meth:~GraspSamplerBase._collision_free.

Frames. Every backend's model returns grasp frames: z is the approach direction and x the direction the fingers close along, whatever the gripper. A grasp frame is generally no link of the real gripper. Each backend therefore names the frame its poses place instead (GraspGenX: its gripper description's base link), and with end_effector= an Eden robot's link, from the pairings it has verified (:attr:GraspSamplerBase.END_EFFECTORS).

One call samples for one env (sample_grasps(..., env_idx=)), or for several with one render and one batched model call (:meth:GraspSamplerBase.sample_grasps_batch). In a batched env the camera must render each env on its own (EnvOptions(env_separate_rigid=True) or the batch renderer): a shared view holds every env's copy of the target under one segmentation id.

Attributes

GRASP_SAMPLER_REGISTRY module-attribute

GRASP_SAMPLER_REGISTRY = Registry('GRASP_SAMPLER', populated_by=('eden.extensions.grasp_sampling.graspgen.sampler', 'eden.extensions.grasp_sampling.graspgenx.sampler'))

IDENTITY_POSE module-attribute

IDENTITY_POSE = ((0.0, 0.0, 0.0), (1.0, 0.0, 0.0, 0.0))

Classes

EndEffectorPairing dataclass

EndEffectorPairing(gripper: str, link_name: str, link_in_frame: tuple[tuple[float, float, float], tuple[float, float, float, float]] = IDENTITY_POSE)

How one of a backend's grippers maps onto an Eden robot's link.

ATTRIBUTE DESCRIPTION
gripper

The backend's gripper: the value of its gripper option (GraspGenX's gripper_name, GraspGen's gripper_config).

TYPE: str

link_name

The Eden robot's link each pose then places.

TYPE: str

link_in_frame

That link's pose, (pos, wxyz quat), in the frame the backend's poses place without a pairing (GraspGenX: the description's base link; GraspGen: its grasp frame).

TYPE: tuple[tuple[float, float, float], tuple[float, float, float, float]]

Attributes

gripper instance-attribute
gripper: str
link_name: str

Methods:

GraspResult dataclass

GraspResult(pos: Tensor, quat: Tensor, confidences: Tensor)

World-frame grasp poses, best first.

Each pose places the sampler's :attr:~GraspSamplerBase.pose_link_name.

ATTRIBUTE DESCRIPTION
pos

Grasp positions, shape (n_grasps, 3), on the env's device.

TYPE: Tensor

quat

Grasp orientations as wxyz quaternions, shape (n_grasps, 4).

TYPE: Tensor

confidences

Discriminator confidences in [0, 1], shape (n_grasps,), in descending order.

TYPE: Tensor

Attributes

pos instance-attribute
pos: Tensor
quat instance-attribute
quat: Tensor
confidences instance-attribute
confidences: Tensor
success property
success: bool

Whether any grasp was found.

Methods:

from_transforms classmethod
from_transforms(transforms: ndarray, confidences: ndarray, device: device, max_grasps: int | None = None) -> GraspResult

Rank (n, 4, 4) world-frame transforms by confidence and convert them to pos/quat.

Source code in eden/extensions/grasp_sampling/base.py
@classmethod
def from_transforms(
    cls,
    transforms: np.ndarray,
    confidences: np.ndarray,
    device: torch.device,
    max_grasps: int | None = None,
) -> GraspResult:
    """Rank ``(n, 4, 4)`` world-frame ``transforms`` by confidence and convert them to pos/quat."""
    order = np.argsort(-confidences, kind="stable")[:max_grasps]
    transforms = torch.as_tensor(transforms[order], dtype=gs.tc_float, device=device).reshape(-1, 4, 4)
    pos, quat = T_to_trans_quat(transforms)
    return cls(
        pos=pos, quat=quat, confidences=torch.as_tensor(confidences[order], dtype=gs.tc_float, device=device)
    )

GraspSamplerBase

GraspSamplerBase(env: EnvBase, options: GraspSamplerOptions)

Bases: ConfigurableMixin[GraspSamplerOptions], ABC

Abstract base class for grasp samplers.

The shared options (camera_name, object_cloud, grasp_threshold, max_grasps, filter_collisions, end_effector, …) are documented on :class:~eden.options.extensions.grasp_sampling.GraspSamplerOptions.

A backend's __init__ calls :meth:_set_pose_frame once it knows its gripper, naming the frame its poses place without a pairing.

PARAMETER DESCRIPTION
env

The built environment the grasps are sampled in. With num_envs > 1, a camera the sampler reads must render each env on its own.

TYPE: EnvBase

options

From the backend's configure().

TYPE: GraspSamplerOptions

Source code in eden/extensions/grasp_sampling/base.py
def __init__(self, env: EnvBase, options: GraspSamplerOptions) -> None:
    super().__init__(options)
    self.env = env
    if self.filter_collisions and type(self)._collision_free is GraspSamplerBase._collision_free:
        raise ValueError(
            f"{type(self).__name__} cannot filter_collisions: {self._no_collision_filter_reason} Fix: configure "
            f"filter_collisions=False, then reject grasps by posing the gripper in the scene and calling "
            f"detect_collision — see examples/recipes/planning/grasp_sampling_graspgenx.py."
        )
    self._pairing = self._resolve_end_effector()
    self._rng = np.random.default_rng(self.seed)
    # Set by the backend through _set_pose_frame.
    self._grasp_T_pose: np.ndarray | None = None

    reads_camera = self.object_cloud == "camera" or self.filter_collisions
    self.camera: Camera | None = self._resolve_camera() if reads_camera else None

    # Resolved now, so a typo fails here rather than leaving the arm in the obstacle cloud. Only the
    # names are kept: deployment can swap ``env.entities[name]``, so each call re-reads the entities.
    if self.collision_ignore_entities is None:
        self._ignored_entity_names = [
            name for name, entity in env.entities.items() if getattr(entity, "is_actuated", False)
        ]
    else:
        for name in self.collision_ignore_entities:
            resolve_named_entity(env.entities, name, hint="collision_ignore_entities lists it; list only those.")
        self._ignored_entity_names = list(self.collision_ignore_entities)

Attributes

camera_name class-attribute instance-attribute
camera_name: str = 'camera'
object_cloud class-attribute instance-attribute
object_cloud: Literal['camera', 'surface'] = 'camera'
surface_points class-attribute instance-attribute
surface_points: int = 2048
grasp_threshold class-attribute instance-attribute
grasp_threshold: float = 0.8
num_grasps class-attribute instance-attribute
num_grasps: int = 200
max_grasps class-attribute instance-attribute
max_grasps: int | None = None
filter_collisions class-attribute instance-attribute
filter_collisions: bool = False
collision_threshold class-attribute instance-attribute
collision_threshold: float = 0.02
collision_ignore_entities class-attribute instance-attribute
collision_ignore_entities: list[str] | None = None
max_scene_points class-attribute instance-attribute
max_scene_points: int = 8192
seed class-attribute instance-attribute
seed: int = 0
end_effector class-attribute instance-attribute
end_effector: str | None = None
END_EFFECTORS class-attribute
END_EFFECTORS: dict[str, EndEffectorPairing] = {}
env instance-attribute
env = env
camera instance-attribute
camera: Camera | None = self._resolve_camera() if reads_camera else None
pose_link_name: str | None

The link each returned pose places: end_effector's link when set, else the backend's own.

None when the poses are the backend's grasp frame itself, which is no link of the gripper (GraspGen without end_effector).

approach_axis property
approach_axis: Tensor

The approach direction, a unit vector of shape (3,) in the posed link's frame, on the env's device.

A grasp moves the gripper along this axis onto the object, so a pregrasp backs off along it: pos - transform_by_quat(approach_axis[None], quat) * distance.

closing_axis property
closing_axis: Tensor

The direction the fingers close along, a unit vector of shape (3,) in the posed link's frame.

With :attr:approach_axis and :attr:tcp_offset it is what a scripted demonstrator's top-down move_to(pos, yaw) needs from a grasp: the heading of transform_by_quat(closing_axis[None], quat) is the one its fingers must close along.

tcp_offset property
tcp_offset: Tensor

The TCP in the posed link's frame, shape (3,), on the env's device.

The TCP is the point the grasp puts on the object (for a parallel jaw, the fingertip center), so pos + transform_by_quat(tcp_offset[None], quat) is where a grasp puts it.

Methods:

sample_grasps
sample_grasps(entity_name: str, env_idx: int = 0) -> GraspResult

Sample grasps of entity entity_name in its current pose, in env env_idx.

PARAMETER DESCRIPTION
entity_name

The entity to grasp.

TYPE: str

env_idx

The env to sample in. The returned poses are world-frame poses of that env's copy.

TYPE: int DEFAULT: 0

RETURNS DESCRIPTION
GraspResult

World-frame grasps, best first. Empty (success is False) when no grasp clears grasp_threshold or, with filter_collisions, the collision check.

RAISES DESCRIPTION
ValueError

When there are no points of the entity to sample from: the camera sees none of it (object_cloud="camera"). That is a different failure from "the model found no grasp", so it is not reported as an empty result.

Source code in eden/extensions/grasp_sampling/base.py
def sample_grasps(self, entity_name: str, env_idx: int = 0) -> GraspResult:
    """Sample grasps of entity ``entity_name`` in its current pose, in env ``env_idx``.

    Parameters
    ----------
    entity_name : str
        The entity to grasp.
    env_idx : int
        The env to sample in. The returned poses are world-frame poses of that env's copy.

    Returns
    -------
    GraspResult
        World-frame grasps, best first. Empty (``success`` is False) when no grasp clears
        ``grasp_threshold`` or, with ``filter_collisions``, the collision check.

    Raises
    ------
    ValueError
        When there are no points of the entity to sample from: the camera sees none of it
        (``object_cloud="camera"``). That is a different failure from "the model found no grasp",
        so it is not reported as an empty result.
    """
    return self.sample_grasps_batch(entity_name, [env_idx])[0]
sample_grasps_batch
sample_grasps_batch(entity_name: str, envs_idx: Sequence[int] | None = None) -> list[GraspResult]

Sample grasps of entity entity_name in several envs at once.

The camera renders once for all of them and the model runs once on all their clouds, where :meth:sample_grasps per env would render the whole batch and run the model for each. A scripted demonstrator that runs one routine per env samples every env here first and hands each routine its result.

PARAMETER DESCRIPTION
entity_name

The entity to grasp.

TYPE: str

envs_idx

The envs to sample in; None samples every env.

TYPE: Sequence[int] | None DEFAULT: None

RETURNS DESCRIPTION
list[GraspResult]

One result per entry of envs_idx, in its order; each as :meth:sample_grasps returns it.

Source code in eden/extensions/grasp_sampling/base.py
def sample_grasps_batch(self, entity_name: str, envs_idx: Sequence[int] | None = None) -> list[GraspResult]:
    """Sample grasps of entity ``entity_name`` in several envs at once.

    The camera renders once for all of them and the model runs once on all their clouds, where
    :meth:`sample_grasps` per env would render the whole batch and run the model for each. A
    scripted demonstrator that runs one routine per env samples every env here first and hands
    each routine its result.

    Parameters
    ----------
    entity_name : str
        The entity to grasp.
    envs_idx : Sequence[int] | None
        The envs to sample in; ``None`` samples every env.

    Returns
    -------
    list[GraspResult]
        One result per entry of ``envs_idx``, in its order; each as :meth:`sample_grasps` returns it.
    """
    entity = resolve_named_entity(
        self.env.entities, entity_name, hint="sample_grasps(entity_name=...) names the entity to grasp."
    )
    envs_idx = list(range(self.env.num_envs)) if envs_idx is None else [int(i) for i in envs_idx]
    for env_idx in envs_idx:
        if not 0 <= env_idx < self.env.num_envs:
            raise IndexError(f"env_idx={env_idx} is out of range for this env's num_envs={self.env.num_envs}.")
    grasp_T_pose = self._pose_frame()
    clouds = self._read_clouds(entity_name, entity, envs_idx)

    outputs = self._infer_batch([object_points for object_points, _ in clouds])
    if len(outputs) != len(clouds):
        raise RuntimeError(
            f"{type(self).__name__}._infer_batch returned {len(outputs)} results for {len(clouds)} clouds; it "
            f"must return one (transforms, confidences) pair per cloud, in order."
        )
    return [
        self._finish(grasps, confidences, scene_points, grasp_T_pose)
        for (grasps, confidences), (_, scene_points) in zip(outputs, clouds)
    ]

Functions:

Options

Configuration options for grasp-sampling extensions.

:class:GraspSamplerOptions mirrors :class:~eden.extensions.grasp_sampling.base.GraspSamplerBase's attributes, so the fields every backend shares are type-checked at configure() time. Backend-specific knobs stay as class attributes on the backend and arrive as extras — e.g. GraspGen's gripper_config or GraspGenX's gripper_name / planner.

:class:GraspMoEOptions groups GraspGenX's GraspMoE planner knobs so they validate as a unit (extra="forbid" plus Literal choices) instead of reaching the planner unchecked.

Classes

GraspSamplerOptions

GraspSamplerOptions(**data)

Bases: DynamicConfigurableOptions

Options shared by every grasp-sampling backend.

PARAMETER DESCRIPTION
camera_name

Camera whose point cloud the sampler reads: the target's visible points when object_cloud="camera", and the obstacle cloud filter_collisions checks against. It must use the legacy camera backend (no sensor_options), the one that renders point clouds and segmentation. Not needed when neither of those reads it.

TYPE: str

object_cloud

Where the target's points come from. "camera" takes the pixels the camera sees of it, which is what a real robot has. "surface" samples the entity's whole surface, hidden underside included, so the model also proposes grasps from below whatever it rests on.

TYPE: Literal['camera', 'surface']

surface_points

Points sampled over the entity's surface when object_cloud="surface".

TYPE: int

grasp_threshold

Discriminator confidence, in [0, 1], a grasp needs to be returned.

TYPE: float

num_grasps

Grasps the generator proposes per inference pass, before thresholding.

TYPE: int

max_grasps

Keep at most this many of the highest-confidence grasps. None keeps every grasp that clears grasp_threshold.

TYPE: int | None

filter_collisions

Drop grasps whose gripper collision mesh comes within collision_threshold of the camera's obstacle cloud. Needs a backend whose grippers ship real collision geometry.

TYPE: bool

collision_threshold

Distance in meters under which a gripper surface point counts as colliding.

TYPE: float

collision_ignore_entities

Entities left out of the obstacle cloud; names are checked whether or not the filter is on. None leaves out every actuated entity: the arm and gripper that move to reach the grasp would otherwise reject the grasps they are about to reach. [] keeps every entity.

TYPE: list[str] | None

max_scene_points

Randomly downsample the obstacle cloud to at most this many points before the check.

TYPE: int

seed

Seed of the obstacle cloud's downsampling. It does not make grasps reproducible: neither the surface sampling nor the model's own sampling reads it.

TYPE: int

end_effector

The Eden robot that executes the grasps, by its robot options class name (e.g. "FrankaEmikaPanda", "InspireRH56_R"). The backend picks the gripper it pairs with that robot, and each pose then places that robot's link (pose_link_name). Each backend lists the pairings it has verified in its END_EFFECTORS. None leaves the gripper to the backend's own option and the poses in the backend's own frame.

TYPE: str | None

Source code in eden/options/options.py
def __init__(self, **data):
    # Seed type metadata as construction data so BaseModel validates it into
    # __pydantic_extra__ (no-op for extra="forbid" classes, which use
    # the serialization envelope instead).
    data.update(option_metadata(self))
    # NOTE: all of the option modifications/validations should be done in the __init__ method
    try:
        BaseModel.__init__(self, **data)
    except ValidationError as exc:
        # A misspelled field on an extra="forbid" class should teach the field set, not just
        # name the typo. Only rewritten when every error is extra_forbidden.
        enriched = _enrich_extra_forbidden(type(self), exc)
        if enriched is None:
            raise
        raise enriched from None

Attributes

camera_name class-attribute instance-attribute
camera_name: str = 'camera'
object_cloud class-attribute instance-attribute
object_cloud: Literal['camera', 'surface'] = 'camera'
surface_points class-attribute instance-attribute
surface_points: int = Field(default=2048, gt=0)
grasp_threshold class-attribute instance-attribute
grasp_threshold: float = Field(default=0.8, ge=0.0, le=1.0)
num_grasps class-attribute instance-attribute
num_grasps: int = Field(default=200, gt=0)
max_grasps class-attribute instance-attribute
max_grasps: int | None = Field(default=None, gt=0)
filter_collisions class-attribute instance-attribute
filter_collisions: bool = False
collision_threshold class-attribute instance-attribute
collision_threshold: float = Field(default=0.02, gt=0.0)
collision_ignore_entities class-attribute instance-attribute
collision_ignore_entities: list[str] | None = None
max_scene_points class-attribute instance-attribute
max_scene_points: int = Field(default=8192, gt=0)
seed class-attribute instance-attribute
seed: int = 0
end_effector class-attribute instance-attribute
end_effector: str | None = None

GraspMoEOptions

GraspMoEOptions(**data)

Bases: ConfigurableOptions

GraspGenX's GraspMoE planner knobs (GraspGenXSampler.planner="graspmoe").

GraspMoE scores the union of the diffusion model's grasps and a set of grasps swept over the object's oriented bounding box (OBB). These fields tune only the OBB branch. The defaults are those of GraspGenX's own scripts/demo_object_pc.py.

PARAMETER DESCRIPTION
num_yaws

World-Z yaw samples of the OBB sweep.

TYPE: int

z_offsets_cm

Offsets in cm, relative to the OBB top, at which candidates are placed.

TYPE: tuple[float, ...]

outlier_threshold

Distance threshold in meters of the outlier removal run on the object cloud.

TYPE: float

outlier_k

Neighbour count of that outlier removal.

TYPE: int

obb_mode

How the OBB is fitted.

TYPE: Literal['advanced', 'pca']

skip_obb_rule

"auto" skips the OBB branch when every OBB extent exceeds the gripper's width (there is nothing to grasp across); "never" always runs it.

TYPE: Literal['auto', 'never']

obb_density

How densely candidates are placed over the OBB: "sparse" puts every candidate at its centroid, "dense" sweeps positions along its long axis, "dense-topandside" adds side approaches on its four vertical faces.

TYPE: Literal['sparse', 'dense', 'dense-topandside']

obb_position_spacing_cm

Spacing in cm of candidate positions along the OBB's long axis; read by the dense densities only.

TYPE: float

Source code in eden/options/options.py
def __init__(self, **data):
    # Seed type metadata as construction data so BaseModel validates it into
    # __pydantic_extra__ (no-op for extra="forbid" classes, which use
    # the serialization envelope instead).
    data.update(option_metadata(self))
    # NOTE: all of the option modifications/validations should be done in the __init__ method
    try:
        BaseModel.__init__(self, **data)
    except ValidationError as exc:
        # A misspelled field on an extra="forbid" class should teach the field set, not just
        # name the typo. Only rewritten when every error is extra_forbidden.
        enriched = _enrich_extra_forbidden(type(self), exc)
        if enriched is None:
            raise
        raise enriched from None

Attributes

num_yaws class-attribute instance-attribute
num_yaws: int = Field(default=36, gt=0)
z_offsets_cm class-attribute instance-attribute
z_offsets_cm: tuple[float, ...] = (-2.0, 0.0)
outlier_threshold class-attribute instance-attribute
outlier_threshold: float = Field(default=0.014, gt=0.0)
outlier_k class-attribute instance-attribute
outlier_k: int = Field(default=20, gt=0)
obb_mode class-attribute instance-attribute
obb_mode: Literal['advanced', 'pca'] = 'advanced'
skip_obb_rule class-attribute instance-attribute
skip_obb_rule: Literal['auto', 'never'] = 'auto'
obb_density class-attribute instance-attribute
obb_density: Literal['sparse', 'dense', 'dense-topandside'] = 'dense-topandside'
obb_position_spacing_cm class-attribute instance-attribute
obb_position_spacing_cm: float = Field(default=1.0, gt=0.0)

GraspGenX

Eden wrapper around GraspGenX: cross-embodiment 6-DoF grasp generation.

GraspGenX (NVlabs/GraspGenX <https://github.com/NVlabs/GraspGenX>) conditions one diffusion model, and its discriminator, on a gripper description: the gripper's swept volume, its open and closed point clouds, and its URDF. A gripper is therefore picked by name, with no per-gripper model. Eden consumes the inference-only fork Kashu7100/GraspGenX <https://github.com/Kashu7100/GraspGenX>, published as eden-graspgenx (import name graspgenx) and installed by the graspgenx extra. The checkpoints and the gripper descriptions download from Hugging Face on first use. See docs/guides/grasp-sampling.md.

Frames. GraspGenX poses a grasp at the world link of the description's URDF: its z axis is the approach direction and its x axis the closing direction, whatever the gripper. That frame is no link of the real gripper; the description welds the gripper's base link to it through a fixed world_joint (rotated 90° about z for the Franka hand and the Robotiq 2F-85, offset by several centimeters for the dexterous hands). The sampler applies that joint, so each pose it returns is the pose of the description's base link (pose_link_name) — or, with end_effector=, of the Eden robot's link it pairs that base link with (:attr:GraspGenXSampler.END_EFFECTORS).

Attributes

Planner module-attribute

Planner = Literal['graspmoe', 'diffusion']

GraspGenXGripper module-attribute

GraspGenXGripper = Literal['abb_yumi', 'arx_x5', 'fetch_robot', 'franka_panda', 'franka_umi', 'galaxea_g1', 'piper_hand', 'robotiq_hande', 'bd_spot', 'dh_ag95', 'ezgripper', 'onrobot_RG2', 'onrobot_RG6', 'robotiq_2f_140', 'robotiq_2f_85', 'sawyer_hand', 'schunk_wsg50', 'tesollo_delto2f', 'xarm_hand', 'barrett_hand', 'inspire_hand', 'robotiq_3f', 'sharpa_wave', 'surge_hand', 'unitree_g1', 'wuji_hand']

GraspGenXEndEffector module-attribute

GraspGenXEndEffector = Literal['FrankaEmikaPanda', 'FrankaHand', 'InspireRH56_R', 'Robotiq2f85', 'SharpaWave_R', 'WujiHand1_R']

Classes

GraspGenXSampler

GraspGenXSampler(env: EnvBase, options: GraspSamplerOptions)

Bases: GraspSamplerBase

Sample grasps with GraspGenX for any gripper it has a description of.

The shared options (camera_name, object_cloud, grasp_threshold, max_grasps, …) are documented on :class:~eden.options.extensions.grasp_sampling.GraspSamplerOptions; the fields below are GraspGenX's own and reach the options as extras. grasp_threshold defaults to 0.7 here, GraspGenX's own default: its confidences run lower than GraspGen's.

Each returned pose places the description's base link, or with end_effector that Eden robot's link (:attr:pose_link_name either way); see the module docstring. The model runs on CUDA only, and is loaded at construction.

filter_collisions is refused: no GraspGenX description ships a collision mesh (coll_mesh.obj), and GraspGenX substitutes a 1 cm box that every grasp clears. Check a grasp against the scene in simulation instead, by posing the gripper there and calling detect_collision (as examples/recipes/planning/grasp_sampling_graspgenx.py does).

PARAMETER DESCRIPTION
gripper_name

The GraspGenX gripper description to sample for, one of :data:GraspGenXGripper (e.g. "franka_panda", "robotiq_2f_85", "sharpa_wave"). It names GraspGenX's description, not an Eden robot class. Required unless end_effector picks it; a name with no description on disk raises with the ones that are listed.

TYPE: GraspGenXGripper | None

checkpoint_dir

Directory holding GraspGenX's gen/ and dis/ checkpoint directories. Empty uses its release checkpoints: $GRASPGENX_CHECKPOINT_DIR when set, else a Hugging Face download.

TYPE: str

end_effector

The Eden robot that executes the grasps (see :class:~eden.options.extensions.grasp_sampling.GraspSamplerOptions). It picks gripper_name, and each pose then places that robot's link: one of :attr:END_EFFECTORS.

TYPE: GraspGenXEndEffector | None

planner

"graspmoe" scores the diffusion model's grasps together with grasps swept over the object's bounding box, which helps on boxy objects; "diffusion" returns the diffusion model's grasps alone.

TYPE: Literal['graspmoe', 'diffusion']

graspmoe

The GraspMoE planner's knobs. Read only when planner="graspmoe".

TYPE: GraspMoEOptions

Source code in eden/extensions/grasp_sampling/graspgenx/sampler.py
def __init__(self, env: EnvBase, options: GraspSamplerOptions) -> None:
    super().__init__(env, options)
    if not self.gripper_name:
        raise ValueError(
            "GraspGenXSampler needs gripper_name=, the GraspGenX gripper description to sample for, e.g. "
            "gripper_name='franka_panda' or 'robotiq_2f_85' — or end_effector=, the Eden robot that executes the "
            f"grasps, which picks it: one of {sorted(self.END_EFFECTORS)}."
        )
    if self.planner not in get_args(Planner):
        raise ValueError(f"planner='{self.planner}' is not a GraspGenX planner; choose one of {get_args(Planner)}.")
    # A saved config loads this back as a plain dict; validating either form also rejects a typo'd knob.
    self.graspmoe = GraspMoEOptions.model_validate(self.graspmoe)
    graspgenx, Model, self._run_planner, self._run_planner_on_batch, load_model_cfg = _require_graspgenx()
    if not torch.cuda.is_available():
        raise RuntimeError(
            "GraspGenX runs on CUDA only, and torch sees no CUDA device. Fix: run on a machine with an NVIDIA "
            "GPU (select one with CUDA_VISIBLE_DEVICES); the Eden env itself may stay on gs.cpu."
        )

    self._description_dir = self._resolve_description(graspgenx)
    base_link_name, grasp_T_base = _root_joint(self._description_dir / "gripper.urdf")
    with open(self._description_dir / "config.json") as f:
        config = json.load(f)
    missing = [key for key in ("open", "close", "fingertip") if key not in config]
    if missing:
        raise ValueError(
            f"Gripper description '{self._description_dir}' has no {missing} configuration in its config.json, "
            f"which every GraspGenX description declares; check $GRASPGENX_GRIPPER_CFG_DIR if it points at a "
            f"custom one."
        )
    # The TCP — the fingertip center GraspGenX grasps at — sits along the grasp frame's approach axis.
    self._set_pose_frame(base_link_name, grasp_T_base, np.asarray(config["fingertip"], dtype=np.float64))
    self._open_dofs_pos = {name: float(value) for name, value in config["open"].items()}
    self._close_dofs_pos = {name: float(value) for name, value in config["close"].items()}

    checkpoint_dir = self._resolve_checkpoint_dir(graspgenx)
    en.logger.info(f"GraspGenX: loading '{self.gripper_name}' with the checkpoints under {checkpoint_dir}")
    with _stdout_to_debug_log():
        model_cfg = load_model_cfg(str(checkpoint_dir / "gen"), str(checkpoint_dir / "dis"))
        self._model = Model(model_cfg, self.gripper_name)

Attributes

gripper_name class-attribute instance-attribute
gripper_name: GraspGenXGripper | None = None
end_effector class-attribute instance-attribute
end_effector: GraspGenXEndEffector | None = None
checkpoint_dir class-attribute instance-attribute
checkpoint_dir: str = ''
planner class-attribute instance-attribute
planner: Planner = 'graspmoe'
grasp_threshold class-attribute instance-attribute
grasp_threshold: float = 0.7
END_EFFECTORS class-attribute
END_EFFECTORS: dict[str, EndEffectorPairing] = {'FrankaEmikaPanda': EndEffectorPairing('franka_panda', 'hand'), 'FrankaHand': EndEffectorPairing('franka_panda', 'hand'), 'InspireRH56_R': EndEffectorPairing('inspire_hand', 'base', ((0.0, 0.0, 0.0), _HALF_TURN_Y_MINUS_Z)), 'Robotiq2f85': EndEffectorPairing('robotiq_2f_85', 'world'), 'SharpaWave_R': EndEffectorPairing('sharpa_wave', 'right_hand_C_MC'), 'WujiHand1_R': EndEffectorPairing('wuji_hand', 'right_palm_link')}
graspmoe class-attribute instance-attribute
graspmoe: GraspMoEOptions = GraspMoEOptions.model_validate(self.graspmoe)
open_dofs_pos property
open_dofs_pos: dict[str, float]

The description's open configuration, by joint name: the one GraspGenX's grasps assume.

Joint names follow the description's URDF. Eden's dexterous hands share them (sharpa_wave with SharpaWave_R, inspire_hand with InspireRH56_R); its parallel jaws carry their own open/close targets (ManipulatorOptions.gripper_open).

close_dofs_pos property
close_dofs_pos: dict[str, float]

The description's closed configuration, by joint name (see :attr:open_dofs_pos).

Methods:

Functions:

GraspGen

Eden wrapper around GraspGen: diffusion-based 6-DoF grasp generation, one model per gripper.

GraspGen (NVlabs/GraspGen <https://github.com/NVlabs/GraspGen>_) is consumed through the packaged eden-grasp-gen (import name grasp_gen), installed by the graspgen extra; grasp_gen download fetches its per-gripper models into $GRASPGEN_CHECKPOINT_DIR. For a gripper GraspGen has no model of, use :class:~eden.extensions.grasp_sampling.graspgenx.GraspGenXSampler, whose single model takes the gripper by name.

Frames. A GraspGen pose is its grasp frame (z approach, x closing), with its origin at the gripper's base. That is no link of an Eden robot: the Franka's hand link closes its fingers along y, so it is the grasp frame turned 90° about z, and a hand driven onto the bare grasp frame closes across the wrong axis. Configure end_effector= (:attr:GraspGenSampler.END_EFFECTORS) for poses of the Eden link instead.

Attributes

GraspGenEndEffector module-attribute

GraspGenEndEffector = Literal['FrankaEmikaPanda', 'FrankaHand']

Classes

GraspGenSampler

GraspGenSampler(env: EnvBase, options: GraspSamplerOptions)

Bases: GraspSamplerBase

Sample grasps with GraspGen, for a gripper it ships a model of.

The shared options are documented on :class:~eden.options.extensions.grasp_sampling.GraspSamplerOptions. grasp_gen download ships models of franka_panda, robotiq_2f_140 and suction_cup_30mm. Each pose is that model's grasp frame, or with end_effector the Eden robot's link (:attr:pose_link_name); see the module docstring. Unlike GraspGenX's, GraspGen's grippers carry collision meshes, so filter_collisions works.

PARAMETER DESCRIPTION
gripper_config

The model's gripper config, e.g. "graspgen_franka_panda.yml": an existing path, or a path relative to $GRASPGEN_CHECKPOINT_DIR. Required unless end_effector picks it.

TYPE: str

end_effector

The Eden robot that executes the grasps (see :class:~eden.options.extensions.grasp_sampling.GraspSamplerOptions). It picks gripper_config, and each pose then places that robot's link: one of :attr:END_EFFECTORS.

TYPE: GraspGenEndEffector | None

Source code in eden/extensions/grasp_sampling/graspgen/sampler.py
def __init__(self, env: EnvBase, options: GraspSamplerOptions) -> None:
    super().__init__(env, options)
    Model, load_grasp_cfg, get_gripper_info, self._filter_colliding_grasps = _require_grasp_gen()

    grasp_cfg = load_grasp_cfg(str(self._resolve_gripper_config()))
    self._model = Model(grasp_cfg)
    gripper = get_gripper_info(grasp_cfg.data.gripper_name)
    self._collision_mesh = gripper.collision_mesh
    # The grasp frame is GraspGen's base-link frame, so its tool TCP is the grasp's TCP.
    tcp = np.asarray(gripper.transform_from_base_link_to_tool_tcp, dtype=np.float64)[:3, 3]
    self._set_pose_frame(None, np.eye(4), tcp)

Attributes

gripper_config class-attribute instance-attribute
gripper_config: str = ''
end_effector class-attribute instance-attribute
end_effector: GraspGenEndEffector | None = None
END_EFFECTORS class-attribute
END_EFFECTORS: dict[str, EndEffectorPairing] = {'FrankaEmikaPanda': EndEffectorPairing('graspgen_franka_panda.yml', 'hand', _FRANKA_HAND_IN_GRASP), 'FrankaHand': EndEffectorPairing('graspgen_franka_panda.yml', 'hand', _FRANKA_HAND_IN_GRASP)}

Methods:

Functions:

See also