Skip to content

Randomization

Domain randomization and sensor noise on every backend, when to call them, and the sim to real habits the rest of the package supports.

By the end of this page you can randomize colours, lights, friction, mass and object positions with one seeded call, add encoder and camera noise to every observation, and know the ordering rule that decides whether a rollout sees the randomized scene at all.

from strands_robots.simulation import create_simulation

sim = create_simulation("mujoco", mesh=False)
sim.create_world()
sim.add_robot("so101")
sim.add_object(name="cube", shape="box", size=[0.03, 0.03, 0.03], position=[0.25, 0.0, 0.015])
clean = sim.get_observation("so101", skip_images=True)["1"]

r = sim.randomize(randomize_colors=True, randomize_lighting=True, randomize_physics=True, randomize_positions=True,
                  position_noise=0.02, friction_range=(0.7, 1.3), mass_range=(0.8, 1.2), seed=7)
print("\n".join(r["content"][0]["text"].splitlines()[:4]))
print(r["content"][0]["text"].splitlines()[-1])

sim.set_obs_noise(joint_pos_std=0.002, joint_vel_std=0.01, camera_jitter_px=1.0, seed=7)
noisy = sim.get_observation("so101", skip_images=True)["1"]
print(clean, noisy != clean, abs(noisy - clean) < 0.01)
sim.set_obs_noise()
print(sim.get_observation("so101", skip_images=True)["1"] == clean)
sim.cleanup()

You should see:

Domain Randomization applied:
Colors: 31 geoms randomized
Lighting: 2 lights randomized
Physics: 32 geoms friction-scaled, 8 bodies mass-scaled
Positions: 1 dynamic objects perturbed by +/-0.02m
0.0 True True
True

The full text also lists every friction and mass scale by geom and body name, so a run is reproducible from its log.

randomize

randomize(randomize_colors=True, randomize_lighting=True, randomize_physics=False, randomize_positions=False, position_noise=0.02, color_range=(0.1, 1.0), friction_range=(0.5, 1.5), mass_range=(0.5, 2.0), seed=None). Each flag is one axis:

axis what changes
randomize_colors every non-ground geom's RGB and its material colour, sampled in color_range; on Isaac, objects only (robot visuals are instanced)
randomize_lighting each light's position inside 0.5 m of its authored spot, and its diffuse colour
randomize_physics every geom's friction scaled in friction_range, every body's mass in mass_range
randomize_positions every dynamic object's position perturbed by position_noise metres, written to qpos0 too

Flags are strict booleans: a truthy string such as "false" or "0" is refused. An unhonoured keyword (randomize_position, position_range) is refused with the valid set. All flags off is a no-op; seed makes the draw deterministic.

When to call it

Randomization writes the compiled model and survives reset(), which is why it reaches a rollout: run_policy and eval_policy reset before an episode's first step. It does not survive a scene mutation. add_object, remove_object, add_camera, remove_camera, add_robot, remove_robot and patch_scene_mjcf rebuild the model from the authored spec and restore every value. Randomizing before one of them is a silent no-op: both calls report success and the policy's first observation is the authored scene. Build the scene, then randomize, then roll out.

set_obs_noise

set_obs_noise(joint_pos_std=0.0, joint_vel_std=0.0, camera_jitter_px=0.0, seed=None) adds Gaussian noise to every joint reading and jitters every rendered frame by up to the given pixels, on get_observation, get_robot_state and render, until reconfigured. All-zero standard deviations are an exact no-op, so an unconfigured engine returns observations byte for byte unchanged. MuJoCo, Newton and Isaac share one implementation (ObservationNoiseMixin), so one call behaves the same on each.

From randomization to sim to real

The package's habits for a policy that has to survive the transfer, each on its own page:

habit where
randomize physics and sensors per episode, seeded, after the scene is built this page
hold the state vector to the real driver's key order and units (.pos real entries, dim_policy) lerobot-local
name sim cameras after the embodiment's source keys so the same policy config runs on hardware lerobot-local
step physics for the full control period, not one dt, so a position servo tracks each target simulation
read the [sim] action value ... outside the range warning as a units mismatch: the value is written verbatim to ctrl, the joint cannot follow it, and the call still reports success MuJoCo
record sim episodes into the same LeRobot dataset format the real recorder writes data
evaluate with pass_hat_k, not the mean success rate, before a deployment decision predicates and rollouts

actuate_robot(robot_name, kp=100.0, damping=2.0, armature=0.01, gravity_compensation=True) on MuJoCo turns an actuator-less URDF import into a position-servo arm; tune kp and damping toward the real controller before trusting a transfer.

Edit page