Scripted Demonstrations¶
Scripted demonstrators produce demo-like trajectories, the kind a person teleoperating would record: smooth
minimum-jerk-timed motion through randomized keyframes, a per-episode speed, pauses, off-centre grasps, retries and
deliberate mistakes the demonstrator recovers from. They are a port of OCBench's scripted policies onto Eden's
batched envs. SCRIPTED_DEMO_REGISTRY says which demonstrator drives which task, and generate_demos (what
eden datagen runs) rolls one out in parallel envs and records every episode. The
scripted demonstrations guide covers eden datagen and writing a demonstrator.
from eden.extensions.scripted_demos.generate import generate_demos
from eden.extensions.scripted_demos.registry import SCRIPTED_DEMO_REGISTRY
Generation and registry¶
Roll a task's scripted demonstrator out in parallel envs and record every episode (eden datagen).
OCBench's data_gen_scripts/generate_ocbench.py, on Eden's recorder: the env records itself
(:class:~eden.managers.recorder_manager.RecorderManager), so a demonstration is written in the same
HDF5 layout as a teleoperated one and eden lerobot convert reads either.
A demonstrator is either a keyframe controller
(:class:~eden.extensions.scripted_demos.controller.ScriptedPickPlaceController) or a tool routine
(:class:~eden.extensions.scripted_demos.routines.RoutineDemonstrator, or one per arm composed by a
:class:~eden.extensions.scripted_demos.multi_arm.MultiArmDemonstrator); all expose reset /
update_subgoals / act. A two-arm task records both arms' state, side by side. A routine
demonstrator also keeps a per-episode trace, and the report carries the first failed episode's —
the place to start when the success rate is low.
An episode ends when the demonstrator has finished the task — its success is the task's own
success metric at that step — or when the env ends it (time limit, an object off the table). Every
ended episode is exported with its success flag; export_mode decides whether the failed
ones are kept. Generation stops once num_episodes have ended; the episodes still running then
are dropped rather than written cut short.
Attributes¶
Classes¶
GenerationReport
dataclass
¶
GenerationReport(dataset_path: str, episodes: int = 0, successes: int = 0, cut_short: int = 0, steps: int = 0, wall_s: float = 0.0, subgoals: dict[str, int] = dict(), failure_trace: list[str] = list())
What a generation run produced.
| ATTRIBUTE | DESCRIPTION |
|---|---|
dataset_path |
The dataset file the successful (or all) episodes went to.
TYPE:
|
episodes |
Episodes that ended (exported or not, per
TYPE:
|
successes |
Of those, the ones the task's success metric passed.
TYPE:
|
cut_short |
Episodes that ended unsuccessfully before the demonstrator finished: the time limit, or a task termination (an object off the table). A task that ends its episode on success (Wipe) cuts nothing short.
TYPE:
|
steps |
Control steps recorded across all ended episodes.
TYPE:
|
wall_s |
Wall-clock duration of the rollout.
TYPE:
|
subgoals |
Subgoals started, by kind ( |
failure_trace |
A routine demonstrator's trace of the first episode that ended unsuccessfully (empty for a keyframe demonstrator, or when every episode succeeded). |
Functions:¶
recorder_options
¶
recorder_options(output_dir: str, filename: str, export_mode: DatasetExportMode, *, state_entity: str | list[str]) -> RecorderManagerOptions
What a demonstration records: observations, actions, rewards / dones / timestamps, the arm's state, and each episode's initial state.
obs/<group> and action are the behavior-cloning pairs; state/* is state_entity's
joint and root state — every arm's, concatenated in order, when it lists several — which
eden lerobot convert reads as observation.state; initial_state lets an episode be
replayed from its first frame.
Source code in eden/extensions/scripted_demos/generate.py
generate_demos
¶
generate_demos(config: EdenRLConfig, task_name: str, *, num_episodes: int, output_dir: str, filename: str = 'demos', export_mode: DatasetExportMode = EXPORT_ALL, seed: int = 0, p_mistake: float | None = None, episode_length_s: float | None = None, camera_only: bool = False, show_viewer: bool = False) -> GenerationReport
Generate num_episodes scripted demonstrations of task_name into output_dir/filename.
| PARAMETER | DESCRIPTION |
|---|---|
config
|
The task config (mods applied); its recorder options and episode length are replaced.
TYPE:
|
task_name
|
Key of the task's demonstrator in :data:
TYPE:
|
num_episodes
|
Episodes to end before stopping.
TYPE:
|
output_dir
|
Where the dataset goes (
TYPE:
|
filename
|
Where the dataset goes (
TYPE:
|
export_mode
|
Keep every episode, the successful ones only, or both in separate files. Not
TYPE:
|
seed
|
Seeds the env and the demonstrator.
TYPE:
|
p_mistake
|
Probability a subgoal is a deliberate mistake; the demonstrator's default when None.
TYPE:
|
episode_length_s
|
Time limit [s] of an episode; the demonstrator's own
TYPE:
|
camera_only
|
Run the demonstrator without the simulator's object state (
TYPE:
|
show_viewer
|
Open the Genesis viewer.
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
GenerationReport
|
Counts and where the dataset went. |
Source code in eden/extensions/scripted_demos/generate.py
134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 | |
recorder_dataset_path
¶
recorder_dataset_path(options: RecorderManagerOptions) -> str
The file a recorder with options writes its (successful) episodes to.
Source code in eden/extensions/scripted_demos/generate.py
Which scripted demonstrator drives which task: SCRIPTED_DEMO_REGISTRY, keyed by task name.
An entry is a factory (env, *, seed, p_mistake) -> demonstrator — a keyframe
:class:~eden.extensions.scripted_demos.controller.ScriptedPickPlaceController, a tool-routine
:class:~eden.extensions.scripted_demos.routines.RoutineDemonstrator, or a two-arm
:class:~eden.extensions.scripted_demos.multi_arm.MultiArmDemonstrator — registered under the task's
:data:~eden.tasks.registry.TASK_REGISTRY name, next to the task it scripts
(eden/tasks/<suite>/scripted_demos.py) — list that module in populated_by below. A factory
that also takes privileged has a camera-only mode (eden datagen --camera_only), and one whose
episodes need longer than the generator's default declares an episode_length_s attribute.
Attributes¶
SCRIPTED_DEMO_REGISTRY
module-attribute
¶
SCRIPTED_DEMO_REGISTRY = Registry('SCRIPTED_DEMO', populated_by=('eden.tasks.robosuite.scripted_demos', 'eden.tasks.simpler.scripted_demos', 'eden.tasks.polaris.scripted_demos', 'eden.tasks.whiteboard.scripted_demos', 'eden.tasks.art.calligraphy.scripted_demos', 'eden.tasks.art.painting.scripted_demos'))
Classes¶
Keyframe trajectories and pick-and-place primitives¶
Batched keyframe trajectories with demo-like timing: the motion layer of OCBench's scripted policies.
Port of ocbench/oracles/primitives/primitive.py (Primitive._compute_smooth_plan,
_smooth_keyframe_times, current_plan_action), vectorized over environments.
A primitive samples a handful of keyframes — tool poses, times and a gripper value — and the trajectory between them is what makes a scripted rollout read like a human demonstration rather than a rule-based controller:
- Position follows a cubic Hermite spline. The tangent is zero at stop keyframes (where a person pauses: before closing the gripper, at the place pose, at the end) and passes through the others at the neighbours' average velocity, clamped to 1.25x the faster neighbouring segment.
- Rotation and gripper ease with the quintic smoothstep
u^3 (10 - 15u + 6u^2). - Timing is stretched so no segment exceeds a peak speed: a minimum-jerk profile peaks at
1.875 d / T, so a segment of lengthdgets at leastT = 1.875 d / v_peak. - Gates freeze the plan clock until the tool is within a tolerance of a keyframe, so the plan never runs ahead of an arm that lags behind it.
- Wobble adds a small sideways bulge
16 u^2 (1 - u)^2 ((1 - u) c_1 + u c_2)to the vertical approach and retreat phases, which a human never executes as a straight line.
Every tensor is batched over environments in the first dimension and lives on the env device; a
plan is written for a subset of environments with :meth:KeyframeTrajectory.assign and read for
all of them with :meth:KeyframeTrajectory.evaluate.
Attributes¶
Classes¶
Keyframes
dataclass
¶
A batch of keyframe plans, one row per environment.
Optional keyframes (OCBench's pick_hold, or the release half of a pick that only lifts) are
authored in place and switched off with valid; :meth:compacted moves the valid ones to the
front, so the spline is built over exactly the keyframes each environment uses.
| ATTRIBUTE | DESCRIPTION |
|---|---|
times |
TYPE:
|
pos |
TYPE:
|
quat |
TYPE:
|
grasp |
TYPE:
|
stop |
TYPE:
|
valid |
TYPE:
|
Attributes¶
Methods:¶
compacted
¶
compacted() -> Keyframes
The valid keyframes first, the tail padded with the last valid one (so it adds no motion and no time).
Source code in eden/extensions/scripted_demos/trajectory.py
filled
¶
filled() -> Keyframes
Every invalid keyframe replaced by the last valid one before it (time, pose and grasp).
Keeps the authored layout, so a primitive can still read a keyframe's time by its slot
after :meth:retimed — an invalid slot then reports its predecessor's time.
Source code in eden/extensions/scripted_demos/trajectory.py
retimed
¶
retimed(peak_speed: Tensor) -> Keyframes
Stretch every segment that would exceed peak_speed ((B,) m/s) under a minimum-jerk profile.
OCBench Primitive._smooth_keyframe_times. Expects a :meth:filled or :meth:compacted
plan, where an unused keyframe duplicates a used one: zero length and zero duration, so it
stays put and the segments around it are timed as if it were absent.
Source code in eden/extensions/scripted_demos/trajectory.py
KeyframeTrajectory
¶
The current keyframe plan of every environment, evaluated at a per-environment plan time.
| PARAMETER | DESCRIPTION |
|---|---|
num_envs
|
Number of environments.
TYPE:
|
num_keyframes
|
Keyframes per plan (the primitive's layout, before compaction).
TYPE:
|
device
|
Device of every buffer.
TYPE:
|
Source code in eden/extensions/scripted_demos/trajectory.py
Attributes¶
Methods:¶
assign
¶
assign(envs_idx: Tensor, keyframes: Keyframes) -> None
Write a new plan for envs_idx (long indices). keyframes need not be compacted.
Source code in eden/extensions/scripted_demos/trajectory.py
evaluate
¶
evaluate(t: Tensor) -> tuple[Tensor, Tensor, Tensor]
Tool position (N, 3), orientation (N, 4) and gripper command (N,) at plan time t (N,).
Source code in eden/extensions/scripted_demos/trajectory.py
Wobble
¶
Sideways bulges over a few plan-time windows (OCBench CubePrimitive's _vertical_phases).
Inside window [t_0, t_1] the tool's xy is offset by 16 u^2 (1 - u)^2 ((1 - u) c_1 + u c_2)
with u the window's progress, so the offset is zero, with zero slope, at both ends.
| PARAMETER | DESCRIPTION |
|---|---|
num_envs
|
Number of environments.
TYPE:
|
num_windows
|
Windows per plan.
TYPE:
|
device
|
Device of every buffer.
TYPE:
|
Source code in eden/extensions/scripted_demos/trajectory.py
Attributes¶
Methods:¶
assign
¶
Set (B, W, 2) windows [t_0, t_1] and (B, W, 2, 2) control offsets (c_1, c_2) for
envs_idx.
Source code in eden/extensions/scripted_demos/trajectory.py
offset
¶
The (N, 2) xy offset at plan time t (N,) (the first window containing t wins, as in
OCBench).
Source code in eden/extensions/scripted_demos/trajectory.py
Functions:¶
smoothstep5
¶
Quintic smoothstep u^3 (10 - 15u + 6u^2) on u clamped to [0, 1] (zero velocity and acceleration at
both ends).
Source code in eden/extensions/scripted_demos/trajectory.py
hermite_tangents
¶
hermite_tangents(keyframes: Keyframes) -> Tensor
Per-keyframe position tangents (B, K, 3) for a :meth:~Keyframes.compacted plan.
Zero at the first keyframe, at the last valid one, and at every stop keyframe; elsewhere the
central difference, clamped to :data:TANGENT_SPEED_LIMIT x the faster neighbouring segment.
Source code in eden/extensions/scripted_demos/trajectory.py
Demo-like pick-and-place keyframes: a batched port of OCBench's CubePrimitive.
ocbench/oracles/primitives/cube.py (compute_keyframes). One call samples a whole
pick -> carry -> place -> retreat plan for a batch of environments. Everything that makes it read
like a person rather than a script is sampled per plan:
- a grasp yaw that is usually the face nearest the current wrist yaw but sometimes a quarter turn off, a wrist tilt of up to 25 deg that carries (attenuated) into the place,
- approach heights, contact offsets and a slightly off-centre place approach,
- an optional pause before lifting, and when the fingers close (on arrival or after a beat),
- a carry midpoint that bows to one side and, a third of the time, overshoots the target,
- +-3 % jitter on every keyframe time.
A failed pick is retried from wherever the hand is (:attr:PickPlaceRequest.num_retries), and
every random offset shrinks by 0.75 per retry — a person is more careful the second time.
Poses here are effector rotations in OCBench's sense — the rotation of a gripper that points
straight down when it is the identity — so yaw, roll and pitch read as the wrist's heading and
tilt. :func:tool_quat maps one to the tool link's world orientation through the rig's
down_quat.
Attributes¶
STOP_SLOTS
module-attribute
¶
WOBBLE_WINDOWS
module-attribute
¶
WOBBLE_WINDOWS = ((Slot.PICK, Slot.PICK_START), (Slot.PICK_HOLD, Slot.POSTPICK), (Slot.PLACE, Slot.PLACE_START), (Slot.PLACE_END, Slot.POSTPLACE))
GATE_SLOTS
module-attribute
¶
GATE_SLOTS = (Slot.PICK, Slot.PICK_START, Slot.PLACE, Slot.PLACE_START)
Classes¶
Slot
¶
PickPlaceParams
dataclass
¶
PickPlaceParams(peak_speed: float = 0.3, descent_height: tuple[float, float] = (0.06, 0.22), descent_xy: tuple[float, float] = (0.016, 0.06), max_tilt: float = radians(25.0), p_pick_hold: float = 0.4, p_overshoot: float = 0.35, gate_tolerance: float = 0.04, contact_gate_tolerance: float = 0.01, retry_noise_decay: float = 0.75)
The tunables of :func:sample_pick_place, OCBench's values by default.
| ATTRIBUTE | DESCRIPTION |
|---|---|
peak_speed |
Peak tool speed [m/s] of a nominal-speed segment; the plan's segments are stretched so a
minimum-jerk profile does not exceed
TYPE:
|
descent_height |
Height [m] of the hover pose above the pick / place pose, along the approach axis. |
descent_xy |
Sideways offset [m] of the place hover pose from straight above the target. |
max_tilt |
Largest wrist tilt [rad] at the pick.
TYPE:
|
p_pick_hold |
Probability of a pause between closing on the object and lifting it.
TYPE:
|
p_overshoot |
Probability the carry midpoint overshoots the target instead of sitting between the two.
TYPE:
|
gate_tolerance |
Distance [m] the tool must be within before the plan clock passes a hover gate.
TYPE:
|
contact_gate_tolerance |
The same at the grasp and release poses (:data:
TYPE:
|
retry_noise_decay |
Every random offset is scaled by
TYPE:
|
PickPlaceRequest
dataclass
¶
PickPlaceRequest(tool_pos: Tensor, tool_rot: Tensor, object_pos: Tensor, object_yaw: Tensor, goal_pos: Tensor, goal_yaw: Tensor, final_pos: Tensor, final_yaw: Tensor, release: Tensor, segment_dt: Tensor, num_retries: Tensor, yaw_window: Tensor, object_symmetry: int = 4)
What to pick and where to put it, for a batch of B environments.
| ATTRIBUTE | DESCRIPTION |
|---|---|
tool_pos |
TYPE:
|
tool_rot |
TYPE:
|
object_pos, object_yaw |
TYPE:
|
goal_pos, goal_yaw |
TYPE:
|
final_pos, final_yaw |
TYPE:
|
release |
TYPE:
|
segment_dt |
TYPE:
|
num_retries |
TYPE:
|
yaw_window |
TYPE:
|
object_symmetry |
The object looks the same every
TYPE:
|
PickPlacePlan
dataclass
¶
PickPlacePlan(keyframes: Keyframes, gate_times: Tensor, gate_pos: Tensor, gate_tolerance: Tensor, gate_valid: Tensor, wobble_windows: Tensor, wobble_curves: Tensor, pick_check_time: Tensor)
A sampled plan: keyframes in effector rotations, plus its gates, wobble and pick check.
| ATTRIBUTE | DESCRIPTION |
|---|---|
keyframes |
:meth:
TYPE:
|
gate_times, gate_pos, gate_tolerance |
TYPE:
|
gate_valid |
TYPE:
|
wobble_windows, wobble_curves |
TYPE:
|
pick_check_time |
TYPE:
|
Functions:¶
effector_rot
¶
Effector rotation (B, 4) from heading and tilt: Rz(yaw) Ry(pitch) Rx(roll), OCBench's to_pose.
Source code in eden/extensions/scripted_demos/primitives.py
tool_quat
¶
World orientation of the tool link for effector rotation rot (down_quat is the tool's straight-down
pose).
Source code in eden/extensions/scripted_demos/primitives.py
fit_yaw
¶
fit_yaw(yaw: Tensor, window: Tensor, period: float) -> Tensor
Shift each heading by a whole number of period into its (low, high) window.
The shift that lands inside the window and is smallest wins; if none does (a window narrower than the period), the one closest to it. This is what keeps a 7-DOF arm's last joint off its stop — OCBench's UR5e wrist turns +-2 pi, a Panda's +-2.9 rad: the planner is free to grasp a cube by any of its faces and to finish with the fingers either way round, so it picks the equivalent heading the wrist can still reach.
Source code in eden/extensions/scripted_demos/primitives.py
sample_pick_place
¶
sample_pick_place(request: PickPlaceRequest, *, generator: Generator, workspace: tuple[Tensor, Tensor], params: PickPlaceParams | None = None) -> PickPlacePlan
Sample a demo-like pick-and-place plan per environment (OCBench CubePrimitive.compute_keyframes).
| PARAMETER | DESCRIPTION |
|---|---|
request
|
The batch to plan for.
TYPE:
|
generator
|
Source of every random draw, on the request's device.
TYPE:
|
workspace
|
TYPE:
|
params
|
Tunables; OCBench's values by default.
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
PickPlacePlan
|
The plan, in the :class: |
Source code in eden/extensions/scripted_demos/primitives.py
249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 | |
Pick-and-place controller¶
Scripted pick-and-place demonstrator for an arm + two-finger-gripper rig, batched over environments.
Port of OCBench's controller / primitive split (ocbench/oracles/controllers/controller.py,
ocbench/oracles/primitives/primitive.py): a task subclass decides what to move next
(:meth:ScriptedPickPlaceController.choose_subgoals); this base turns each decision into a
demo-like pick-and-place plan (:func:~eden.extensions.scripted_demos.primitives.sample_pick_place)
and drives the rig along it:
- Speed. Every episode draws one demonstrator speed (
segment_dtuniform inspeed_range), which scales every segment of every plan in it. - Plan clock and gates. Each env has its own plan time, advanced by the control step and
frozen at each gate (the pick / place hovers and the grasp / release poses,
:data:
~eden.extensions.scripted_demos.primitives.GATE_SLOTS) until the tool has caught up. - Retries. If the object does not rise with the hand on the way up from the grasp, the pick
is re-planned from wherever the hand is, up to
max_pick_retriestimes. - Actions. The planned tool pose becomes the 6-D delta-pose command of the rig's
DifferentialIKControllerand the plan's gripper value theBinaryJointControllercommand, whatever the task names them (:mod:~eden.extensions.scripted_demos.rig) — the task's own action space, so a recorded action replays as-is.
Object state is read straight off the scene (OCBench's privileged/ observations), so a
demonstration never depends on the noise the policy observations carry.
The control flow is batched torch on the env device. Only the subgoal decision is a per-env Python loop on a CPU snapshot: it runs once per finished plan, not per step, and keeps the task logic as readable as OCBench's.
Attributes¶
Classes¶
Subgoal
dataclass
¶
Subgoal(object_index: int, goal_pos: ndarray, goal_yaw: float, release: bool = True, kind: str = 'goal')
One pick-and-place decision for one environment.
| ATTRIBUTE | DESCRIPTION |
|---|---|
object_index |
Index into :attr:
TYPE:
|
goal_pos |
TYPE:
|
goal_yaw |
Heading [rad] the object should end at.
TYPE:
|
release |
Open the gripper at the goal and retreat (a place) — or keep holding it there (a lift).
TYPE:
|
kind |
Free-form label (
TYPE:
|
ObjectSnapshot
dataclass
¶
ScriptedPickPlaceController
¶
ScriptedPickPlaceController(env: EnvBase, object_names: list[str], *, rig: str = 'robot', seed: int = 0, speed_range: tuple[float, float] = (0.7, 1.5), workspace: tuple[tuple[float, float, float], tuple[float, float, float]], effector_bounds: tuple[tuple[float, float, float], tuple[float, float, float]], effector_yaw_range: tuple[float, float] = (-pi / 2, pi / 2), max_pick_retries: int = 2, max_overrun_s: float = 10.0, gate_timeout_s: float = 3.0, max_step_translation: float = 0.05, max_step_rotation: float = 0.3, params: PickPlaceParams | None = None)
Bases: ABC
Base of the scripted demonstrators: plans, clocks and acts; a subclass picks the subgoals.
| PARAMETER | DESCRIPTION |
|---|---|
env
|
A built environment whose scene holds an arm + two-finger gripper at
TYPE:
|
object_names
|
Scene entities the subgoals refer to, by index. |
rig
|
Scene entry holding the rig.
TYPE:
|
seed
|
Seed of the controller's own random draws (plans and subgoals).
TYPE:
|
speed_range
|
Range of |
workspace
|
Low / high corners every tool target is clipped to. The low
TYPE:
|
effector_bounds
|
Low / high corners the hand retreats to after a place.
TYPE:
|
effector_yaw_range
|
Heading range [rad] of that retreat pose, about the rig's straight-down pose. |
max_pick_retries
|
Failed picks re-planned per subgoal before giving up on it.
TYPE:
|
max_overrun_s
|
A plan still running this long [s] past its own duration (its clock held back at gates) is abandoned and a new subgoal chosen. Relative to the plan, not absolute: a slow demonstrator's plan legitimately runs long, and cutting it off mid-place ends an episode with the object still in the hand.
TYPE:
|
gate_timeout_s
|
A hand held at a gate this long [s] without reaching it is re-planned from where it is.
TYPE:
|
max_step_translation
|
Largest end-effector move asked for in one control step, per axis [m] and in angle [rad]
(OCBench's
TYPE:
|
max_step_rotation
|
Largest end-effector move asked for in one control step, per axis [m] and in angle [rad]
(OCBench's
TYPE:
|
params
|
Primitive tunables; OCBench's by default.
TYPE:
|
Source code in eden/extensions/scripted_demos/controller.py
Attributes¶
driver
instance-attribute
¶
driver = RigDriver(env, rig, workspace=workspace, max_step_translation=max_step_translation, max_step_rotation=max_step_rotation)
Methods:¶
tool_pose
¶
tool_pose() -> tuple[Tensor, Tensor]
Tool-point position (N, 3) and effector rotation (N, 4) (identity = pointing straight down).
object_poses
¶
object_poses() -> tuple[Tensor, Tensor]
Positions (N, M, 3) and headings (N, M) of :attr:object_names, in their authored frames.
Source code in eden/extensions/scripted_demos/controller.py
yaw_window
¶
(N, 2) effector headings the arm's last joint can still reach (:meth:RigDriver.yaw_window).
reset
¶
Start a fresh episode for envs_idx (bool mask or long indices; all when None).
Call it for every env after the env itself has been reset — including once for all of
them before the first :meth:act.
Source code in eden/extensions/scripted_demos/controller.py
reset_task_state
¶
choose_subgoals
abstractmethod
¶
choose_subgoals(env_ids: list[int], objects: ObjectSnapshot) -> list[Subgoal | None]
Decide what each of env_ids moves next; None when its task is complete.
Called whenever an env has no plan (episode start) or has finished one.
Source code in eden/extensions/scripted_demos/controller.py
update_subgoals
¶
Retire finished plans and start the next subgoals; returns :attr:done.
:meth:act calls it first, so calling it is optional — and idempotent within a step. Call
it before :meth:act to learn which envs are complete before acting, so their episode can
end on the step that completed it rather than one hold step later.
Source code in eden/extensions/scripted_demos/controller.py
act
¶
The action dict for this control step: the IK term's (N, 6) command and the gripper term's (N, 1).
Source code in eden/extensions/scripted_demos/controller.py
Functions:¶
Block-world demonstrators¶
Block-world demonstrators: build a tower, or lift a block — with OCBench's deliberate mistakes.
Port of ocbench/oracles/controllers/block.py (BlockController) onto
:class:~eden.extensions.scripted_demos.controller.ScriptedPickPlaceController. The objects are
boxes resting on a table; a subgoal moves one of them.
- Goal subgoals do the task: the lowest block of the tower that is not yet in place goes on the one below it — after whatever covers either of them has been moved out of the way.
- Mistakes. With probability
p_mistakea subgoal is replaced by a wrong one: a random uncovered block goes to a random free spot on the table or, half the time, onto another uncovered block. The demonstrator recovers on its own (it re-plans from the resulting state), which is what makes the data suboptimal in the way human play data is: the high-level plan is noisy, not just the motion.
The decision logic is a per-env Python loop over a CPU snapshot, as in OCBench — it runs once per completed subgoal, not per step.
Attributes¶
Classes¶
BlockWorldController
¶
BlockWorldController(env: EnvBase, block_names: list[str], *, table_z: float, spot_bounds: tuple[tuple[float, float], tuple[float, float]], min_spot_distance: float = 0.08, p_mistake: float = 0.1, **kwargs)
Bases: ScriptedPickPlaceController
Shared block-world geometry: sizes, what covers what, free table spots, and mistakes.
| PARAMETER | DESCRIPTION |
|---|---|
env
|
The built environment.
TYPE:
|
block_names
|
Scene entities that are boxes (:class: |
table_z
|
Height [m] of the table top.
TYPE:
|
spot_bounds
|
Low / high xy corners of the free spots blocks are moved to. |
min_spot_distance
|
A free spot keeps at least this distance [m] from every other block.
TYPE:
|
p_mistake
|
Probability a subgoal is replaced by a mistake.
TYPE:
|
**kwargs
|
Forwarded to :class:
DEFAULT:
|
Source code in eden/extensions/scripted_demos/block_world.py
Attributes¶
spot_bounds
instance-attribute
¶
spot_bounds = (np.asarray(spot_bounds[0], dtype=np.float64), np.asarray(spot_bounds[1], dtype=np.float64))
half_heights
instance-attribute
¶
Methods:¶
goal_subgoal
¶
The next step toward the task for one env, or None when it is complete.
choose_subgoals
¶
choose_subgoals(env_ids: list[int], objects: ObjectSnapshot) -> list[Subgoal | None]
Source code in eden/extensions/scripted_demos/block_world.py
TowerController
¶
TowerController(env: EnvBase, block_names: list[str], *, table_z: float, spot_bounds: tuple[tuple[float, float], tuple[float, float]], min_spot_distance: float = 0.08, p_mistake: float = 0.1, **kwargs)
Bases: BlockWorldController
Stack block_names into one tower, bottom to top, wherever the bottom block stands.
robosuite Stack is the two-block case (["cubeB", "cubeA"]); OCBench's stack
tasks are the longer ones.
Source code in eden/extensions/scripted_demos/block_world.py
Methods:¶
goal_subgoal
¶
Source code in eden/extensions/scripted_demos/block_world.py
LiftController
¶
LiftController(env: EnvBase, block_names: list[str], *, lift_height=(0.1, 0.2), lift_xy=0.05, **kwargs)
Bases: BlockWorldController
Pick one block and hold it lift_height above the table (robosuite Lift).
The lift itself has no OCBench counterpart; mistakes are the block-world ones — the block is put down somewhere else first and the lift starts over from there.
| PARAMETER | DESCRIPTION |
|---|---|
lift_height
|
Range [m] the block's bottom is held above the table. |
lift_xy
|
Largest horizontal drift [m] of the held pose from where the block was picked.
TYPE:
|
**kwargs
|
Forwarded to :class:
DEFAULT:
|
Source code in eden/extensions/scripted_demos/block_world.py
Attributes¶
Methods:¶
goal_subgoal
¶
Source code in eden/extensions/scripted_demos/block_world.py
Functions:¶
The robot a demonstrator drives¶
The arm + two-finger gripper a scripted demonstrator drives, from whichever robot a scene holds there.
A demonstrator needs a handful of facts about the robot: which entity carries the arm DOFs and the link the IK term drives, which entity carries the fingers, and where the tool point (the pad midpoint) sits in that link's frame. Two shapes of scene entry supply them:
- a composite :class:
~eden.options.robots.ManipulatorOptions(robosuite's Panda, UR5e + Robotiq, DROID): an arm entity and a welded gripper entity, the tool point carried through the weld; - a single :class:
~eden.options.entities.RobotOptionswhose asset includes its own fingers (SIMPLER's WidowX250): the fingers are the DOFs ofopen_dofs_pos, the arm every other actuated DOF, the IK linkee_links_name[0], and the tool point the robot'stcp_offset.
:func:gripper_rig reads either into one :class:GripperRig, so nothing downstream asks which
shape it is. The action terms are found on the built env by what they drive
(:func:find_rig_actions), not by name: tasks name them robot_arm (robosuite) or
ee_controller (SIMPLER, Polaris).
:class:RigDriver is what moves the rig: it reads the tool point's pose off the built robot and
turns a target tool pose and gripper value into the two terms' commands — the task's own action
space, so a recorded action replays as-is. Every demonstrator drives the robot through it (the
keyframe controller and the tool routines alike).
Attributes¶
Classes¶
GripperRig
dataclass
¶
GripperRig(slot: str, arm_name: str, gripper_name: str, ee_link: str, arm_dofs: tuple[str, ...], gripper_dofs: tuple[str, ...], tcp_offset: tuple[float, float, float])
The facts a scripted demonstrator reads off an arm + two-finger gripper.
| ATTRIBUTE | DESCRIPTION |
|---|---|
slot |
Scene entry the rig was read from.
TYPE:
|
arm_name |
Entity carrying the arm DOFs and :attr:
TYPE:
|
gripper_name |
Entity carrying the finger DOFs — :attr:
TYPE:
|
ee_link |
Link the IK term drives, on :attr:
TYPE:
|
arm_dofs |
Arm DOFs, base to wrist; the last is the joint that turns the hand about its approach axis. |
gripper_dofs |
Finger DOFs. |
tcp_offset |
Tool point (pad midpoint, gripper open) in the :attr: |
RigActions
dataclass
¶
RigDriver
¶
RigDriver(env: EnvBase, slot: str, *, workspace: tuple[tuple[float, float, float], tuple[float, float, float]], max_step_translation: float = 0.05, max_step_rotation: float = 0.3, exclusive: bool = True)
Reads a rig's tool pose and turns target tool poses into the task's IK + gripper commands, batched over envs.
Effector rotations are relative to the rig's straight-down pose (:attr:down_quat): identity
points the approach axis at the floor with heading 0, and
:func:~eden.extensions.scripted_demos.primitives.effector_rot builds one from a heading and a tilt.
| PARAMETER | DESCRIPTION |
|---|---|
env
|
A built environment holding the rig at
TYPE:
|
slot
|
Scene entry holding the rig.
TYPE:
|
workspace
|
Low / high corners every tool target is clipped to. The low
TYPE:
|
max_step_translation
|
Largest end-effector move asked for in one control step, per axis [m] and in angle [rad]
(OCBench's
TYPE:
|
max_step_rotation
|
Largest end-effector move asked for in one control step, per axis [m] and in angle [rad]
(OCBench's
TYPE:
|
exclusive
|
Whether the rig's two action terms must be all the env has (:func:
TYPE:
|
Source code in eden/extensions/scripted_demos/rig.py
Attributes¶
workspace
instance-attribute
¶
workspace = (torch.tensor(workspace[0], device=device, dtype=torch.float32), torch.tensor(workspace[1], device=device, dtype=torch.float32))
finger_axis_yaw
cached
property
¶
finger_axis_yaw: float
Heading [rad] of the line the fingers close along, in the effector frame, in [0, pi).
Read off the built fingers the first time it is asked (the keyframe controller never asks): the contact links' box centres, projected on the plane across the approach axis — their principal direction is the closing line — relative to the effector's heading, so a target heading turns it with the hand.
Methods:¶
finger_links
¶
finger_links() -> list
The finger links with collision geometry: every such link of a separate gripper entity, or the links the finger DOFs move.
Source code in eden/extensions/scripted_demos/rig.py
contact_links
¶
contact_links() -> list
The links whose touch is the fingers': :meth:finger_links less a separate gripper's root (its palm /
mount).
Source code in eden/extensions/scripted_demos/rig.py
ee_pos
¶
ee_quat
¶
tool_pose
¶
tool_pose() -> tuple[Tensor, Tensor]
Tool-point position (N, 3) and effector rotation (N, 4) (identity = pointing straight down).
Source code in eden/extensions/scripted_demos/rig.py
gripper_closure
¶
(N,) how far the fingers are from open (0) toward their closed targets (1), averaged over finger DOFs.
Fingers closed on an object stop short of 1 — the proprioceptive "am I holding something".
Source code in eden/extensions/scripted_demos/rig.py
finger_touches
¶
finger_touches(min_force: float) -> Tensor
(N, L) bool over the scene's global link indices: which links a finger presses on with at least
min_force [N].
The robot's own links never count.
Source code in eden/extensions/scripted_demos/rig.py
finger_contact
¶
finger_contact(min_force: float) -> Tensor
(N,) bool: a finger link touches something that is not the robot, pressing at least min_force [N].
pinching
¶
(N,) bool: finger links on both sides of the closing line touch entity — it is held between them.
Two links of one finger (a 2F-85's pad and its knuckle) touching it do not count.
Source code in eden/extensions/scripted_demos/rig.py
yaw_window
¶
(N, 2) effector headings reachable before the last arm joint comes within :data:WRIST_MARGIN of a stop.
Linearized about the current pose: a heading change d psi turns the last joint by
d psi / k, where k is the vertical component of its world axis (about +-1 for a wrist
pointing down). Re-measure it at every plan, so the redundant arm's drift is accounted for.
Source code in eden/extensions/scripted_demos/rig.py
clip
¶
command
¶
command(tool_pos: Tensor, rot: Tensor, grasp: Tensor, hold: Tensor | None = None) -> dict[str, Tensor]
The action dict that moves the tool point toward tool_pos / rot with gripper grasp.
| PARAMETER | DESCRIPTION |
|---|---|
tool_pos
|
TYPE:
|
rot
|
TYPE:
|
grasp
|
TYPE:
|
hold
|
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
dict[str, Tensor]
|
The IK term's |
Source code in eden/extensions/scripted_demos/rig.py
Functions:¶
gripper_rig
¶
gripper_rig(scene_options: SceneOptions, slot: str) -> GripperRig
Read the arm + gripper at scene entry slot (a composite rig or a robot with its own fingers).
| RAISES | DESCRIPTION |
|---|---|
KeyError
|
|
TypeError
|
The entry is neither shape, or a single-body robot does not declare a fact the rig needs. |
Source code in eden/extensions/scripted_demos/rig.py
entity_entries
¶
entity_entries(scene_options: SceneOptions) -> list[str]
Names of the scene's entity entries — its attachments_dict and other plain fields left out.
Source code in eden/extensions/scripted_demos/rig.py
find_gripper_rigs
¶
find_gripper_rigs(scene_options: SceneOptions) -> list[GripperRig]
Every arm + gripper in the scene, in scene-entry order — one for a single-arm task, two for a two-arm one.
Source code in eden/extensions/scripted_demos/rig.py
find_gripper_rig
¶
find_gripper_rig(scene_options: SceneOptions) -> GripperRig
The one arm + gripper in the scene, when the caller does not name its slot.
| RAISES | DESCRIPTION |
|---|---|
TypeError
|
The scene holds none, or several — name the slot then. |
Source code in eden/extensions/scripted_demos/rig.py
find_rig_actions
¶
find_rig_actions(env: EnvBase, rig: GripperRig, *, exclusive: bool = True) -> RigActions
Find the DifferentialIKController driving rig.ee_link and the BinaryJointController on its fingers.
Matched on what each term drives — entity, link, DOFs — not on its name. With exclusive (the
default) every action term of the env must be one of the two: a demonstrator commands both each
step and has nothing to send a third. A multi-arm demonstrator passes exclusive=False for each
arm and checks that the arms' terms together cover every term
(:class:~eden.extensions.scripted_demos.multi_arm.MultiArmDemonstrator).
Source code in eden/extensions/scripted_demos/rig.py
Tool routines¶
A routine is a scripted policy written for one robot as a generator of motions (move_to, move_until_contact,
close_gripper, ...), run batched over every env.
Robot tools: the calls a scripted routine makes — motions it waits on, and readings it takes at once.
A routine (:mod:~eden.extensions.scripted_demos.routines) is written for one environment, as a
plain Python generator, against a :class:Tools handle:
- Motions (:meth:
Tools.move_to, :meth:Tools.move_until_contact, :meth:Tools.close_gripper, ...) return a :class:Motion. The routine yields it and gets an :class:Outcomeback once the motion has ended — reached, timed out, stopped by contact. Every env's motion runs batched on the env device, so a routine reads like a script for one robot and runs on thousands. - Readings return at once. The robot's own state (:meth:
Tools.tool_pos, :meth:Tools.holding) is always available. What it knows about objects comes either from the simulator (:meth:Tools.object_footprint, :meth:Tools.grasped, :meth:Tools.link_pose— privileged) or from a camera (:meth:Tools.locate). A routine run withprivileged=Falseis refused the first kind, by name, so a camera-only demonstrator cannot read the simulator state by accident.
Positions are world-frame metres. Headings are effector headings: 0 is the rig's straight-down pose
with heading 0 (:attr:~eden.extensions.scripted_demos.rig.RigDriver.down_quat).
Attributes¶
Classes¶
Motion
dataclass
¶
Motion(kind: MotionKind, label: str, target: tuple[float, float, float] | None = None, yaw: float | None = None, rot: tuple[float, float, float, float] | None = None, direction: tuple[float, float, float] | None = None, distance: float = 0.0, speed: float = 0.0, tolerance: float = 0.01, duration_s: float = 0.0, timeout_s: float = 2.0, grasp: float | None = None)
One motion a routine yields. Build it with a :class:Tools method, not by hand.
| ATTRIBUTE | DESCRIPTION |
|---|---|
kind |
What the executor does with it.
TYPE:
|
label |
How the trace names it (
TYPE:
|
target |
Tool-point position to reach ( |
yaw |
Effector heading to reach (
TYPE:
|
rot |
Full effector rotation to reach ( |
direction |
Unit direction of a guarded move. |
distance |
How far [m] a guarded move goes before giving up.
TYPE:
|
speed |
Peak tool speed [m/s] of a move, or the constant speed of a guarded one.
TYPE:
|
tolerance |
A move has reached its target once the tool point is this close [m].
TYPE:
|
duration_s |
How long a gripper motion or a wait lasts [s].
TYPE:
|
timeout_s |
How long past its own duration a move may run before it ends
TYPE:
|
grasp |
Gripper value from this motion on, 0 open / 1 closed;
TYPE:
|
Outcome
dataclass
¶
What a motion reports when it ends — the value of the routine's yield.
| ATTRIBUTE | DESCRIPTION |
|---|---|
motion |
The motion's label.
TYPE:
|
status |
One of :data:
TYPE:
|
elapsed_s |
How long the motion ran [s].
TYPE:
|
error_m |
Distance [m] from the tool point to the motion's target when it ended (0 for gripper / wait).
TYPE:
|
Footprint
dataclass
¶
Footprint(center: ndarray, yaw: float, half_length: float, half_width: float, top_z: float, bottom_z: float, top_center: ndarray, num_points: int, centroid: ndarray | None = None)
Where an object is and how it lies, from points on it — its collision mesh, or what a camera saw of it.
| ATTRIBUTE | DESCRIPTION |
|---|---|
center |
TYPE:
|
yaw |
Heading [rad] of the footprint's long axis, in
TYPE:
|
half_length, half_width |
Half extents [m] along the long and the short horizontal axis.
TYPE:
|
top_z, bottom_z |
Highest and lowest point [m].
TYPE:
|
top_center |
TYPE:
|
num_points |
How many points it was measured from.
TYPE:
|
centroid |
TYPE:
|
PrivilegedToolError
¶
Bases: PermissionError
A camera-only routine (privileged=False) called a tool that reads the simulator's state.
Tools
¶
Tools(executor: RoutineDemonstrator, env_id: int, rng: Generator)
The tools one environment's routine calls. Handed to the routine by the executor; one per env.
| PARAMETER | DESCRIPTION |
|---|---|
executor
|
Runs the motions and serves the readings, batched over envs.
TYPE:
|
env_id
|
The environment this handle speaks for.
TYPE:
|
rng
|
This env's random stream, re-seeded per episode: draw a routine's variations from it.
TYPE:
|
Source code in eden/extensions/scripted_demos/tools.py
Attributes¶
privileged
property
¶
privileged: bool
Whether this routine may read the simulator's state (:meth:object_footprint, :meth:grasped,
:meth:link_pose).
speed
property
¶
speed: float
This episode's peak tool speed [m/s]: the demonstrator's peak_speed times a per-episode draw.
tip_reach
property
¶
tip_reach: float
How far [m] the fingertips reach past the tool point (the pad midpoint) along the approach axis.
finger_axis_yaw
property
¶
finger_axis_yaw: float
Heading [rad] the fingers close along, relative to the effector heading (modulo pi).
shared
property
¶
shared: dict
A dict this env's routine keeps for the episode, shared with the other arm's routine.
Under a :class:~eden.extensions.scripted_demos.multi_arm.MultiArmDemonstrator both routines of
an env get the same one, which is how two arms hand off: one writes a key, the other waits for
it (while not tools.shared.get(key): yield tools.wait(0.1)).
Methods:¶
note
¶
note(text: str) -> None
Add a line to this env's trace (:meth:RoutineDemonstrator.trace): what the routine decided and why.
tool_pos
¶
heading_window
¶
Lowest and highest effector heading [rad] the arm's last joint can turn the hand to from here.
Linearized about the current pose (:meth:~eden.extensions.scripted_demos.rig.RigDriver.yaw_window);
re-read it after a move. A routine about to turn something it holds by a quarter turn reads it
to pick the direction there is room for — :meth:move_to would otherwise realize a turn past
the joint's stop as the equivalent turn the other way, which a two-finger grasp allows but the
held object does not.
Source code in eden/extensions/scripted_demos/tools.py
tool_rotation
¶
(4,) current effector rotation, wxyz relative to the straight-down pose — what move_to(rot=...)
asks for.
The whole rotation, where :meth:tool_yaw is its heading only: what a held object's pose is
measured against to turn it with the hand.
Source code in eden/extensions/scripted_demos/tools.py
holding
¶
holding() -> bool
The fingers are commanded closed and stopped short of closing: something is between them.
Proprioceptive — what the robot feels, not what the simulator knows — so camera-only routines
may call it. Ask it after :meth:close_gripper has finished.
Source code in eden/extensions/scripted_demos/tools.py
object_footprint
¶
Footprint of entity name from its collision geometry (privileged).
Source code in eden/extensions/scripted_demos/tools.py
grasped
¶
Fingers on both sides of the closing line touch entity name (privileged).
link_pose
¶
World position (3,) and wxyz orientation (4,) of entity name's link (privileged).
None reads the entity's root. Poses are in the frame the asset was authored in
(relative=True), so a point given in an MJCF body's frame — a site — is
pos + R(quat) @ offset. For articulated fixtures (a door's panel and handle) whose pose a
footprint cannot tell.
Source code in eden/extensions/scripted_demos/tools.py
locate
¶
Footprint of what camera sees of entity name; None if it sees too little of it.
The camera's segmentation stands in for a perfect text-prompted segmenter: the points are the
depth of the pixels showing name, so an occluded side or a hidden object reads as it would
to a real camera. camera defaults to the demonstrator's first camera.
Source code in eden/extensions/scripted_demos/tools.py
move_to
¶
move_to(pos: Sequence[float], yaw: float | None = None, *, rot: Sequence[float] | None = None, speed: float | None = None, tolerance: float = 0.01, timeout_s: float = 2.0) -> Motion
Move the tool point to pos (and heading yaw) along a straight, minimum-jerk path.
yaw is turned by a half turn (a two-finger gripper's symmetry) into the range the arm's
last joint can reach. rot instead asks for a full effector rotation (wxyz, relative to the
straight-down pose; :meth:aim builds one), reached as given. Ends REACHED within
tolerance [m], or TIMEOUT timeout_s after the path's own duration.
Source code in eden/extensions/scripted_demos/tools.py
aim
¶
The effector rotation (for :meth:move_to's rot) pointing the hand along approach and closing
along closing.
closing is made perpendicular to approach; both are world-frame directions.
Source code in eden/extensions/scripted_demos/tools.py
move_by
¶
Move the tool point by offset from where it is now, keeping its heading.
Source code in eden/extensions/scripted_demos/tools.py
move_until_contact
¶
move_until_contact(direction: Sequence[float] = (0.0, 0.0, -1.0), max_distance: float = 0.1, *, speed: float = 0.03) -> Motion
Move the tool point along direction at speed until a finger touches something.
Ends CONTACT when a finger link newly presses on anything that is not the robot (a touch
already there when it starts — a held object — does not count; lowering a held object until it
lands ends BLOCKED), BLOCKED when the hand stops short of the command without a finger
touching (the palm, the arm, the workspace floor), NO_CONTACT after max_distance [m]
with nothing touched.
Source code in eden/extensions/scripted_demos/tools.py
close_gripper
¶
Close the fingers and hold the arm still for duration_s [s].
Source code in eden/extensions/scripted_demos/tools.py
open_gripper
¶
Open the fingers and hold the arm still for duration_s [s].
Source code in eden/extensions/scripted_demos/tools.py
wait
¶
Hold the arm still for duration_s [s].
Functions:¶
footprint_from_points
¶
footprint_from_points(points: ndarray) -> Footprint
The :class:Footprint of (P, 3) points: principal horizontal axes, and the extents along them.
| RAISES | DESCRIPTION |
|---|---|
ValueError
|
Fewer than 3 points — nothing to measure an orientation from. |
Source code in eden/extensions/scripted_demos/tools.py
aim_rotation
¶
aim_rotation(approach: Sequence[float], closing: Sequence[float], finger_axis_yaw: float) -> tuple[float, float, float, float]
Effector rotation (wxyz, relative to the straight-down pose) whose approach axis is approach and closing
line closing.
In the straight-down pose with heading 0 the approach axis is world -z and the fingers close
along heading finger_axis_yaw; the rotation maps that pair onto the requested one.
| RAISES | DESCRIPTION |
|---|---|
ValueError
|
A zero direction, or |
Source code in eden/extensions/scripted_demos/tools.py
Routine demonstrators: a scripted policy written for one robot, run batched over every env.
A routine is a generator function that drives one environment's robot through its
:class:~eden.extensions.scripted_demos.tools.Tools handle. It yields motions and gets each
one's :class:~eden.extensions.scripted_demos.tools.Outcome back, so checks and retries are plain
Python::
def pick(tools):
cube = tools.object_footprint("cube")
above = cube.center + (0.0, 0.0, 0.10)
yield tools.move_to(above, yaw=cube.yaw - tools.finger_axis_yaw + math.pi / 2)
yield tools.move_to(cube.center)
yield tools.close_gripper()
yield tools.move_to(above)
if not tools.holding():
tools.note("missed the cube")
:class:RoutineDemonstrator gives each env its own instance of the routine and runs their motions
together: the routines are resumed only when an env's motion has ended (once per motion, not per
step), and every motion in flight is evaluated as batched torch on the env device and turned into
the task's own actions by :class:~eden.extensions.scripted_demos.rig.RigDriver. It has the
demonstrator interface eden datagen drives (reset / update_subgoals / act), so a
routine registered for a task in :data:~eden.extensions.scripted_demos.registry.SCRIPTED_DEMO_REGISTRY
generates datasets like the keyframe demonstrators do.
Every motion and every :meth:~eden.extensions.scripted_demos.tools.Tools.note lands in the env's
:meth:~RoutineDemonstrator.trace — the record to read when a routine fails, one line per motion
with how it ended.
Attributes¶
Classes¶
RoutineDemonstrator
¶
RoutineDemonstrator(env: EnvBase, routine: Routine, *, rig: str = 'robot', seed: int = 0, workspace: tuple[tuple[float, float, float], tuple[float, float, float]], peak_speed: float = 0.12, speed_range: tuple[float, float] = (0.8, 1.25), max_step_translation: float = 0.05, max_step_rotation: float = 0.3, privileged: bool = True, cameras: tuple[str, ...] = (), contact_force: float = 1.0, holding_closure: float = 0.9, exclusive: bool = True)
Runs one routine per environment, batched, through an arm + two-finger-gripper rig.
| PARAMETER | DESCRIPTION |
|---|---|
env
|
A built environment holding the rig (:class:
TYPE:
|
routine
|
Called with each env's :class:
TYPE:
|
rig
|
Scene entry holding the rig.
TYPE:
|
seed
|
Seed of the per-episode random streams (:attr:
TYPE:
|
workspace
|
Low / high corners every tool target is clipped to; the low
TYPE:
|
peak_speed
|
Nominal peak tool speed [m/s] of a move. Size it to what the task's action realizes per
step (
TYPE:
|
speed_range
|
Each episode's moves run at |
max_step_translation
|
Largest end-effector move per control step ([m] per axis, [rad]).
TYPE:
|
max_step_rotation
|
Largest end-effector move per control step ([m] per axis, [rad]).
TYPE:
|
privileged
|
Whether routines may read the simulator's object state.
TYPE:
|
cameras
|
Cameras :meth: |
contact_force
|
Smallest force [N] on a finger link that stops a guarded move.
TYPE:
|
holding_closure
|
Fingers commanded closed that stop below this closure (0 open, 1 closed) are holding something.
TYPE:
|
exclusive
|
Whether this rig's IK and gripper terms must be every action term of the env. A multi-arm
demonstrator runs one routine demonstrator per arm with
TYPE:
|
Source code in eden/extensions/scripted_demos/routines.py
140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 | |
Attributes¶
driver
instance-attribute
¶
driver = RigDriver(env, rig, workspace=workspace, max_step_translation=max_step_translation, max_step_rotation=max_step_rotation, exclusive=exclusive)
Methods:¶
reset
¶
Start a fresh routine for envs_idx (bool mask or long indices; all when None).
Call it for every env after the env itself has been reset — including once for all of them
before the first :meth:act, and after every env.step for the envs it auto-reset
(env.reset_buf: a task's time limit or termination). A routine left running across an
env reset keeps acting on the scene it saw before. eden datagen does both.
blackboards maps an env id to the dict its routine sees as :attr:Tools.shared this
episode; a multi-arm demonstrator hands both arms of an env the same one. A fresh, empty
dict otherwise.
Source code in eden/extensions/scripted_demos/routines.py
update_subgoals
¶
End the motions that are over, resume their routines, and start their next motions; returns :attr:done.
:meth:act calls it first; calling it before is optional and idempotent within a step.
Source code in eden/extensions/scripted_demos/routines.py
act
¶
The action dict for this control step, keyed by the task's IK and gripper term names.
Source code in eden/extensions/scripted_demos/routines.py
trace
¶
This episode's record for env_id: one line per ended motion and per note, in order.
add_note
¶
episode_time
¶
episode_speed
¶
grasp_command
¶
blackboard
¶
read
¶
read(key: str) -> ndarray
A batched robot reading as a host array, read once per step: tool_pos, tool_rot, tool_yaw,
gripper_closure, yaw_window.
Source code in eden/extensions/scripted_demos/routines.py
object_footprint
¶
Footprint of entity name in env_id from its collision vertices (the vertices read once per step).
Source code in eden/extensions/scripted_demos/routines.py
grasped
¶
grasped(name: str) -> ndarray
(N,) bool: fingers on both sides of the closing line touch entity name.
Source code in eden/extensions/scripted_demos/routines.py
link_pose
¶
Authored-frame pose of entity name's link (its root when None) in env_id, read once per step.
Source code in eden/extensions/scripted_demos/routines.py
locate
¶
Footprint of the pixels of camera showing entity name in env_id; None below
:data:MIN_LOCATE_POINTS.
Source code in eden/extensions/scripted_demos/routines.py
Functions:¶
A tool routine that puts an object on or into its target (eden datagen --task simpler_put_carrot_on_plate, …).
Written against :class:~eden.extensions.scripted_demos.tools.Tools, for one robot, as the
routines of :mod:eden.extensions.scripted_demos.routines are: look at the object, grip it from
above, carry it over the goal and lower it on, then look again. The loop is goal-directed, so the
episode's deliberate mistakes (the object put down off the goal first, with probability
p_mistake) and a missed or dropped pick are recovered from by looking again.
The goal is where the object's own centre must come to rest, from :class:Goal: over a target
entity's footprint (the towel, the plate), into a target container (a bowl, a tray), or inside a
fixed box (the sink's basket, which is part of the fixed sink). The same routine runs privileged
(footprints off the collision meshes) or camera-only
(:meth:~eden.extensions.scripted_demos.tools.Tools.locate); only :func:_look chooses.
What differs between robots is the :class:Hand: where its open fingers reach and where it moves to
clear the camera's view. The SIMPLER Bridge tasks (WidowX250) and the Polaris DROID tasks (Franka +
Robotiq 2F85) each pass their own.
Attributes¶
Classes¶
Hand
dataclass
¶
What the routine knows about the robot's hand that the rig does not measure.
| ATTRIBUTE | DESCRIPTION |
|---|---|
finger_reach |
Distances [m] from the grasp point, along the closing axis, of an open finger's inner and outer faces. |
clear_view_pos |
Where the hand goes to clear the camera's view when the object cannot be seen. |
Goal
dataclass
¶
Goal(target: str | None = None, region: tuple[tuple[float, float, float], tuple[float, float, float]] | None = None, object_yaw: float | None = None, obstacle_top: float = 0.0, mistake_bounds: tuple[tuple[float, float], tuple[float, float]] = ((0.2, -0.15), (0.35, 0.15)), mistake_floor: float = 0.0, container: bool = False, target_body: bool = False, max_reach: float | None = None, avoid: tuple[str, ...] = (), scrub: float = 0.0)
Where the object's centre must come to rest.
Exactly one of target and region is given.
A target is something to put the object on (the object counts as placed with its centre within
:data:PLACED_TOLERANCE of the target's) unless container: then the object is let go above
the target's rim and counts as placed with its centre anywhere over the target's footprint, inset
by the object's half width — where a bowl or a tray holds it.
| ATTRIBUTE | DESCRIPTION |
|---|---|
target |
Scene entity to put the object on: the goal is its footprint's centre and top.
TYPE:
|
region |
Low / high corners of a fixed box the object goes into: the goal is its centre in xy, and
its low
TYPE:
|
object_yaw |
Heading [rad] the object's long axis is turned to (modulo pi) while carried; None keeps the heading it was picked at.
TYPE:
|
obstacle_top |
Highest point [m] between the object and the goal — a rim the hand and the object pass over.
TYPE:
|
mistake_bounds |
Low / high xy corners of where a deliberate mistake puts the object down instead. |
mistake_floor |
Height [m] of the surface inside
TYPE:
|
container |
The target is a container the object is dropped into (see above).
TYPE:
|
target_body |
Aim at the round body of a target with a handle (a frying pan) instead of its footprint's centre, which the handle drags off it: the end of the footprint's long side its points crowd.
TYPE:
|
max_reach |
Container goals: the aim stays within this horizontal distance [m] of the robot's base (the world origin) where the container allows — the far side of a large one is out of the arm's reach — and otherwise comes as close to it as the container allows.
TYPE:
|
avoid |
Container goals: entities already in the container (a tomato in the tray) the aim keeps clear of. |
scrub |
Lower the object onto the goal and rub it back and forth this far [m] to either side,
:data:
TYPE:
|
Functions:¶
put_on_routine
¶
put_on_routine(source: str, goal: Goal, *, hand: Hand, p_mistake: float, camera: str | None, grasp: str = 'pinch', clutter: tuple[str, ...] = ()) -> Routine
The routine that puts source at goal; camera-only when camera is given.
grasp is how the object is gripped from above: "pinch" across its short side, or "rim"
by the wall of a hollow object too wide to pinch (a cup, a roll of tape), one finger inside it.
A pinch keeps the open fingers off the goal's target and off the clutter entities around the
object (a tilted plate next to a cube), which the hand would come down on and stop short.
Source code in eden/extensions/scripted_demos/put_on_routine.py
body_centre
¶
The (2,) centre and radius [m] of the round body of an object with a handle (a mug, a pan).
The body is the end of the footprint's long side its points crowd (a handle is thin), as wide as the footprint's short side. A footprint about as long as wide is all body.
Source code in eden/extensions/scripted_demos/put_on_routine.py
Multi-arm demonstrators: one routine per arm, run side by side in every env, handing off through a blackboard.
A two-arm task is scripted as two routines, each written for its own arm against its own
:class:~eden.extensions.scripted_demos.tools.Tools handle, exactly like a single-arm routine. They
run concurrently — each arm resumes its routine when its own motion ends — and coordinate through
:attr:Tools.shared <eden.extensions.scripted_demos.tools.Tools.shared>, a dict both routines of an
env share for the episode. A hand-off is one routine writing a key and the other waiting for it::
def giver(tools):
... # carry the object to the hand-off pose
tools.shared["offered"] = True
while not tools.shared.get("taken"):
yield tools.wait(0.1)
yield tools.open_gripper()
def taker(tools):
while not tools.shared.get("offered"):
yield tools.wait(0.1)
... # close on the object
tools.shared["taken"] = True
:class:MultiArmDemonstrator runs one
:class:~eden.extensions.scripted_demos.routines.RoutineDemonstrator per arm, merges their actions
into the task's, and has the interface eden datagen drives (reset / update_subgoals /
act / done / subgoal_counts / trace). An env's task is complete when every arm's
routine has returned; an arm that finishes first holds still.
Attributes¶
Classes¶
MultiArmDemonstrator
¶
MultiArmDemonstrator(env: EnvBase, routines: dict[str, Routine], *, seed: int = 0, **arm_options: Any)
Runs one routine per arm in every env, batched, with a blackboard per env shared between the arms.
| PARAMETER | DESCRIPTION |
|---|---|
env
|
A built environment holding each arm at its scene entry, each moved by one IK term and one gripper term, and no other action term.
TYPE:
|
routines
|
Scene entry of each arm -> the routine that drives it. Each is called with that arm's
:class: |
seed
|
Seeds every arm's per-episode random streams (each arm its own).
TYPE:
|
**arm_options
|
Passed to every arm's :class:
TYPE:
|
| RAISES | DESCRIPTION |
|---|---|
ValueError
|
Fewer than two arms, or a per-arm option that does not name exactly the arms. |
TypeError
|
An action term no arm drives. |
Source code in eden/extensions/scripted_demos/multi_arm.py
Attributes¶
arms
instance-attribute
¶
arms: dict[str, RoutineDemonstrator] = {slot: RoutineDemonstrator(env, routine, rig=slot, seed=seed + i * _ARM_SEED_STRIDE, exclusive=False, **{name: value[slot] if isinstance(value, dict) else value for name, value in arm_options.items()}) for i, (slot, routine) in enumerate(routines.items())}
done
property
¶
(N,) bool: every arm's routine has returned — this env's task is complete.
subgoal_counts
property
¶
Motions ended so far, by <arm>/<tool>:<status> — the generation report's tally.
Methods:¶
reset
¶
Start fresh routines on every arm for envs_idx, the arms of each env sharing one new blackboard.
Call it when :meth:~eden.extensions.scripted_demos.routines.RoutineDemonstrator.reset would be called.
Source code in eden/extensions/scripted_demos/multi_arm.py
update_subgoals
¶
Advance every arm's routines (see :meth:RoutineDemonstrator.update_subgoals); returns :attr:done.
act
¶
The action dict for this control step: every arm's IK and gripper commands.
Source code in eden/extensions/scripted_demos/multi_arm.py
trace
¶
This episode's record for env_id: every arm's lines, prefixed with the arm, in time order.