Reference/API

Hand Retargeting

robokit.helpers.hand_retargeting.HandRetargeterOnline(
config: HandRetargeterOnlineConfig,
robot: Robot,
spec: HandSpec,
device: Optional[wp_device_type] = None
)

Retarget point streams; reference_origin="wrist" centers floating hands internally, but I/O stays world-frame.

Temporal cost:

  • First frame after warmup/reset: none.
  • Later frames: smooth against the previous solution, independently of the initial guess.

Example

python
>>> from robokit.robo.robot_spec import RobotSpec
>>> from robokit.utils.ee_coord_utils import MANOPTH_HAND_COORD_SPEC as coords
>>> from robokit.opt.population_solver import PopulationSolverConfig, Stage
>>> from robokit.opt.lm_optimizer import LMOptimizerConfig
>>> from robokit.utils.warp_utils import wp_vec7
>>> urdf = '''<robot name="finger"><link name="palm"/><link name="tip"/>
... <joint name="finger" type="revolute"><parent link="palm"/><child link="tip"/>
... <origin xyz="0 0.03 0"/><axis xyz="0 0 1"/>
... <limit lower="0" upper="1" effort="1" velocity="1"/></joint></robot>'''
>>> robot = Robot(RobotSpec.parse(urdf, load_meshes=False, mesh_dir=None))
>>> spec = HandSpec(True, ("tip", "wrist"), {"tip": "tip", "wrist": "palm"},
...                 (("tip",),), "wrist", ("tip",), coords)
>>> config = HandRetargeterOnlineConfig(reference_origin="wrist",
...     solver=PopulationSolverConfig(stages=[Stage(2, optimizer=LMOptimizerConfig())],
...         cuda_graph_mode="none"))
>>> config.task("positions").weight = {"tip": 24.0, "wrist": 0.1}
>>> solver = HandRetargeterOnline(config, robot, spec, device="cpu")
>>> solver.warmup(1)
>>> points = np.array([[[1, 2.03, 3], [1, 2, 3]]], dtype=np.float32)
>>> packed = solver.solve_numpy(points)
>>> packed.shape
(1, 8)
>>> output = wp.empty((1, 8), dtype=wp.float32, device="cpu")
>>> solver.solve(wp.from_numpy(points, dtype=wp.float32, device="cpu"), out=output) is output
True
>>> state = robot.state(q=wp.from_numpy(packed[:, 7:].copy(), dtype=wp.float32, device="cpu"),
...     T_world_base=wp.from_numpy(packed[:, :7].copy(), dtype=wp_vec7, device="cpu"))
>>> all(np.isfinite(cost.numpy()).all() for cost in solver.compute_costs(state).values())
True
>>> solver.reset()
Parameters
configHandRetargeterOnlineConfigrequired

Online solver, objective, filtering, and sampling settings.

robotRobotrequired

Loaded robot model; self-collision skip pairs come from its spec (Robot.load(self_collision_ignore_path=...)).

specHandSpecrequired

Ordered semantic point topology.

deviceOptional[wp_device_type]= None

Warp device. The first CUDA device is used when available.

Members
method[source]
HandRetargeterOnline.warmup(
batch_size: int
) -> None

Build all batch-shaped terms, buffers, and solver state.

Parameters
batch_sizeintrequired

Number of independent streams advanced by each solve.

Returns
None
method[source]
HandRetargeterOnline.solve(
target_points: wp.array,
init_state: Optional[RobotState] = None,
out: Optional[wp.array] = None
) -> wp.array

Retarget one batched frame.

Parameters
target_pointswp.arrayrequired

Point targets with shape (batch, targets, 3) in spec.target_names order.

init_stateOptional[RobotState]= None

Optional state overriding the streaming warm start.

outOptional[wp.array]= None

Optional packed output buffer with shape (batch, 7 + dofs).

Returns
wp.array

Packed base transform and joint positions; a helper-owned buffer is reused when out is omitted.

method[source]
HandRetargeterOnline.solve_numpy(
target_points: np.ndarray,
init_state: Optional[RobotState] = None
) -> np.ndarray

NumPy wrapper for solve().

Parameters
target_pointsnp.ndarrayrequired
init_stateOptional[RobotState]= None
Returns
np.ndarray
method[source]
HandRetargeterOnline.reset() -> None

Clear all streaming history and disable smoothness until a previous frame exists.

Returns
None
method[source]
HandRetargeterOnline.compute_costs(
state: RobotState
) -> Dict[str, wp.array]

Evaluate the current targets for a caller-provided robot state.

Parameters
stateRobotStaterequired

Robot state matching the warmed batch and device.

Returns
Dict[str, wp.array]

Per-term costs keyed by task cost name.

robokit.helpers.hand_retargeting.HandRetargeterOnlineConfig(
terms: List[ResidualTask] = _default_online_terms(),
pinch_correspondences: Tuple[_Correspondence, ...] = (),
target_scale: float = 1.0,
target_ema_alpha: float = 1.0,
output_ema_alpha: float = 1.0,
sampler: RobertsSamplerConfig = (lambda: RobertsSamplerConfig(keep_first=True))(),
base_init_sample_range: Tuple[float, float, float] = (0.1, 0.1, 0.0),
solver: PopulationSolverConfig = _online_solver(),
pinch_threshold: float = 0.03,
pinch_release_threshold: float = 0.05,
reference_origin: Literal['world', 'wrist'] = 'world'
)

Frame-level hand-retargeting configuration.

Parameters
termsList[ResidualTask]= _default_online_terms()
pinch_correspondencesTuple[_Correspondence, ...]= ()
target_scalefloat= 1.0
target_ema_alphafloat= 1.0
output_ema_alphafloat= 1.0
samplerRobertsSamplerConfig= (lambda: RobertsSamplerConfig(keep_first=True))()
base_init_sample_rangeTuple[float, float, float]= (0.1, 0.1, 0.0)
solverPopulationSolverConfig= _online_solver()
pinch_thresholdfloat= 0.03
pinch_release_thresholdfloat= 0.05
reference_originLiteral['world', 'wrist']= 'world'
Members
attribute[source]
HandRetargeterOnlineConfig.terms: List[ResidualTask] = field(default_factory=_default_online_terms)

Task templates built at warmup; HandContext lists their inputs.

attribute[source]
HandRetargeterOnlineConfig.pinch_correspondences: Tuple[_Correspondence, ...] = ()

Vector rows whose target shrinks to near zero while their two targets pinch.

attribute[source]
HandRetargeterOnlineConfig.target_scale: float = 1.0

Scale from human to robot hand size applied to vector targets; pinched rows keep their own length.

attribute[source]
HandRetargeterOnlineConfig.target_ema_alpha: float = 1.0

EMA weight of the newest vector and direction targets, in (0, 1]; 1 disables smoothing.

attribute[source]
HandRetargeterOnlineConfig.output_ema_alpha: float = 1.0

EMA weight of the newest output joints, in (0, 1]; 1 disables smoothing.

attribute[source]
HandRetargeterOnlineConfig.sampler: RobertsSamplerConfig = field(default_factory=lambda: RobertsSamplerConfig(keep_first=True))

Joint noise around the previous solution; scale is a fraction of each joint's range.

attribute[source]
HandRetargeterOnlineConfig.base_init_sample_range: Tuple[float, float, float] = (0.1, 0.1, 0.0)

Per-axis base translation noise (m) around the previous solution of a floating hand.

attribute[source]
HandRetargeterOnlineConfig.solver: PopulationSolverConfig = field(default_factory=_online_solver)

Candidate schedule and LM settings of each frame's solve.

attribute[source]
HandRetargeterOnlineConfig.pinch_threshold: float = 0.03

Target distance (m) below which a pinch pair latches.

attribute[source]
HandRetargeterOnlineConfig.pinch_release_threshold: float = 0.05

Target distance (m) above which a latched pinch pair releases.

attribute[source]
HandRetargeterOnlineConfig.reference_origin: Literal['world', 'wrist'] = 'world'

Solve floating hands relative to the target wrist position ("wrist") or in world; I/O frames are unchanged.

method[source]
HandRetargeterOnlineConfig.add(
term: ResidualTask,
name: Optional[str] = None
) -> HandRetargeterOnlineConfig

Append a task, optionally assigning its input role.

python
>>> from robokit.terms.dense.configuration_task import ConfigurationTask
>>> config = HandRetargeterOnlineConfig(terms=[]).add(ConfigurationTask(weight=0.1), name="rest")
>>> config.task("rest").weight
0.1
Parameters
termResidualTaskrequired
nameOptional[str]= None
Returns
HandRetargeterOnlineConfig
method[source]
HandRetargeterOnlineConfig.task(
name: str
) -> ResidualTask

Return a named task; raise KeyError when absent.

python
>>> from robokit.terms.dense.configuration_task import ConfigurationTask
>>> config = HandRetargeterOnlineConfig(terms=[]).add(ConfigurationTask(weight=0.2), name="rest")
>>> config.task("rest").weight
0.2
Parameters
namestrrequired
Returns
ResidualTask
robokit.helpers.hand_retargeting.HandRetargeterOffline(
config: HandRetargeterOfflineConfig,
robot: Robot,
spec: HandSpec,
scene: Optional[WarpScene] = None,
device: Optional[wp_device_type] = None,
scene_indices: Optional[wp.array] = None
)

Retarget trajectory batches in HandSpec.target_names order by solving config.terms over HandContext inputs.

Parameters
configHandRetargeterOfflineConfigrequired

Trajectory, contact, and solver settings.

robotRobotrequired

Loaded robot model.

specHandSpecrequired

Ordered semantic point topology.

sceneOptional[WarpScene]= None

Optional caller-owned collision scene.

deviceOptional[wp_device_type]= None

Warp device. The first CUDA device is used when available.

scene_indicesOptional[wp.array]= None

Optional static (batch,) or per-frame (batch, frames) scene mapping.

Members
method[source]
HandRetargeterOffline.warmup(
batch_size: int,
num_frames: int
) -> None

Build buffers and the config's tasks, and capture the solver for one trajectory shape.

Parameters
batch_sizeintrequired

Number of independent trajectories.

num_framesintrequired

Frames in each fixed-shape trajectory.

Returns
None
method[source]
HandRetargeterOffline.set_valid_lengths(
lengths: np.ndarray
) -> None

Mask padded frames and temporal edges in every task.

Parameters
lengthsnp.ndarrayrequired
Returns
None
method[source]
HandRetargeterOffline.set_target_scales(
scales: np.ndarray
) -> None

Set one positive pairwise target scale per warmed trajectory without rebuilding the solver.

Example

python
>>> from robokit.helpers.full_body_retargeting import FullBodyRetargeter
>>> from robokit.helpers.hand_retargeting.presets.inspire import offline, spec
>>> solver = HandRetargeterOffline(offline, FullBodyRetargeter().hand_robot("right"), spec)
>>> solver.warmup(2, 3)
>>> solver.set_target_scales(np.array([0.9, 1.2]))
Parameters
scalesnp.ndarrayrequired
Returns
None
method[source]
HandRetargeterOffline.solve(
target_points: wp.array,
root_quat_wxyz: Optional[wp.array] = None,
init_state: Optional[RobotState] = None,
out: Optional[wp.array] = None
) -> wp.array

Retarget one Warp trajectory batch.

Parameters
target_pointswp.arrayrequired

Float points shaped (batch, frames, targets, 3) in spec.target_names order.

root_quat_wxyzOptional[wp.array]= None

Optional root rotations shaped (batch, frames, 4).

init_stateOptional[RobotState]= None

Optional joint and base warm start.

outOptional[wp.array]= None

Optional packed output shaped (batch, frames, 7 + dofs).

Returns
wp.array

Packed base transforms and joint positions.

method[source]
HandRetargeterOffline.solve_numpy(
target_points: np.ndarray,
root_quat_wxyz: Optional[np.ndarray] = None,
init_state: Optional[RobotState] = None
) -> np.ndarray

NumPy wrapper for solve().

Parameters
target_pointsnp.ndarrayrequired
root_quat_wxyzOptional[np.ndarray]= None
init_stateOptional[RobotState]= None
Returns
np.ndarray
method[source]
HandRetargeterOffline.solve_with_contact(
target_points: wp.array,
contact_points: wp.array,
contact_mask: wp.array,
init_state: Optional[RobotState] = None,
local_contact_points: Optional[wp.array] = None,
out: Optional[wp.array] = None,
surface_contact_mask: Optional[wp.array] = None
) -> wp.array

Retarget with world-frame contacts; an init_state anchors the solution and its motion to that trajectory.

Parameters
target_pointswp.arrayrequired

Named points shaped (batch, frames, targets, 3).

contact_pointswp.arrayrequired

World contacts shaped (batch, frames, contacts, 3).

contact_maskwp.arrayrequired

Active contacts shaped (batch, frames, contacts).

init_stateOptional[RobotState]= None

Optional state that anchors the solution around that trajectory.

local_contact_pointsOptional[wp.array]= None

Optional contacts in each link frame.

outOptional[wp.array]= None

Optional caller-owned packed output.

surface_contact_maskOptional[wp.array]= None

Optional mask for sphere-surface contacts; uses contact_mask when omitted.

Returns
wp.array

Packed base transforms and joint positions.

method[source]
HandRetargeterOffline.solve_with_contact_numpy(
target_points: np.ndarray,
contact_points: np.ndarray,
contact_mask: np.ndarray,
init_state: Optional[RobotState] = None,
local_contact_points: Optional[np.ndarray] = None,
surface_contact_mask: Optional[np.ndarray] = None
) -> np.ndarray

NumPy wrapper for solve_with_contact().

Parameters
target_pointsnp.ndarrayrequired
contact_pointsnp.ndarrayrequired
contact_masknp.ndarrayrequired
init_stateOptional[RobotState]= None
local_contact_pointsOptional[np.ndarray]= None
surface_contact_maskOptional[np.ndarray]= None
Returns
np.ndarray
robokit.helpers.hand_retargeting.HandRetargeterOfflineConfig(
terms: List[ResidualTask] = _default_offline_terms(),
target_scale: float = 1.0,
optimize_base: bool = True,
active_joint_names: Optional[Tuple[str, ...]] = None,
locked_prefix_frames: int = 0,
solver: LMOptimizerConfig = _offline_solver()
)

One whole-trajectory problem; contact_config and refine_config derive the contact ones from tracking.

Parameters
termsList[ResidualTask]= _default_offline_terms()
target_scalefloat= 1.0
optimize_basebool= True
active_joint_namesOptional[Tuple[str, ...]]= None
locked_prefix_framesint= 0
solverLMOptimizerConfig= _offline_solver()
Members
attribute[source]
HandRetargeterOfflineConfig.terms: List[ResidualTask] = field(default_factory=_default_offline_terms)

Task templates of the tracking solve; HandContext lists their inputs.

attribute[source]
HandRetargeterOfflineConfig.target_scale: float = 1.0

Scale from human to robot hand size of each trajectory's pairwise targets until set_target_scales.

attribute[source]
HandRetargeterOfflineConfig.optimize_base: bool = True

Optimize a floating hand's base trajectory; False keeps the initial one.

attribute[source]
HandRetargeterOfflineConfig.active_joint_names: Optional[Tuple[str, ...]] = None

Joints the solve moves; None moves every joint.

attribute[source]
HandRetargeterOfflineConfig.locked_prefix_frames: int = 0

Leading frames whose joints keep their initial values; base activity follows optimize_base.

attribute[source]
HandRetargeterOfflineConfig.solver: LMOptimizerConfig = field(default_factory=_offline_solver)

LM settings of the whole-trajectory solve.

method[source]
HandRetargeterOfflineConfig.add(
term: ResidualTask,
name: Optional[str] = None
) -> HandRetargeterOfflineConfig

Append a task, optionally assigning its input role.

python
>>> from robokit.terms.dense.configuration_task import ConfigurationTask
>>> config = HandRetargeterOfflineConfig(terms=[]).add(ConfigurationTask(weight=0.1), name="rest")
>>> config.task("rest").weight
0.1
Parameters
termResidualTaskrequired
nameOptional[str]= None
Returns
HandRetargeterOfflineConfig
method[source]
HandRetargeterOfflineConfig.task(
name: str
) -> ResidualTask

Return a named task; raise KeyError when absent.

python
>>> from robokit.terms.dense.configuration_task import ConfigurationTask
>>> config = HandRetargeterOfflineConfig(terms=[]).add(ConfigurationTask(weight=0.2), name="rest")
>>> config.task("rest").weight
0.2
Parameters
namestrrequired
Returns
ResidualTask