strands-arm frame_dolly: the whole leader arm keeps a subject framed and drives the omni base as its dolly

Six servo deltas per 0.1 s (ids 1..6, clipped +-5 deg) plus three base velocity commands (vx, vy, wz in [-5, 5], mapped onto the drive guard caps 0.15 m/s, 0.15 m/s, 0.6 rad/s) from what the Nicla reports (FOMO box centre, size, visible, ToF), the six servo readings, the last command and a shot spec (where in the frame, how big), plus the commanded base velocity (vx, vy, wz). Trained with PPO in a MuJoCo twin of this arm (SO-101 leader + printed head, lens as a camera at the firmware FOV, identified STS3215 servo model, calibrated limits) with occlusion rays, one-frame detector delay, pixel noise and randomized FOV, and the base following the commanded velocity through a ramp; the size spec is a dolly offset the base can deliver (the arm alone cannot: 0.002 size per servo degree, see rl/sim/sensitivity.py). Baseline: the head-only P step watch uses today, arm frozen at the fold.

frame_dolly

Top: head-only P. Bottom: this policy, same subject. Left = the Nicla's view (green = shot spec, yellow = detection), right = third person. MP4.

Eval (rl/eval_frame.py, 4 seeds x 64 envs x 15 s, 256 episodes)

controller visible framed centre err size err contacts/step travel deg/step
zero action 0.27 0.00 1.010 0.080 0.004 0.10
head-only P (watch.py) 0.34 0.02 0.664 0.078 0.503 1.91
this policy 0.87 0.70 0.120 0.045 0.014 5.32
this policy, arm frozen 0.59 0.11 0.469 0.073 0.940 1.36
frame_mobile (arm only, never drives) 0.72 0.30 0.240 0.080 0.138 7.76

PASS: keeps the subject visible more often than the head-only P step. framed = centre error < 0.15 and size error < 0.1. The arm-frozen row is the same network with servos 1..4 held: the difference is what the arm buys. The frame_mobile row is the shipped arm-only cameraman dropped into the same env with the base parked: the difference is what driving buys. Ship rule for this policy: size err < 0.12 and visible > frame_mobile.

Contract

Input obs [1, 33] float32, output act [1, 9] float32 = servo deg per 0.1 s step, ids 1..6; base_*_cmd in [-5, 5] -> [-cap, cap] (0.15 m/s, 0.15 m/s, 0.6 rad/s).

i obs
0 s1_n
1 s2_n
2 s3_n
3 s4_n
4 s5_n
5 s6_n
6 g1_n
7 g2_n
8 g3_n
9 g4_n
10 g5_n
11 g6_n
12 ex
13 ey
14 size
15 vis
16 last_ex
17 last_ey
18 last_size
19 age_n
20 ex_star
21 ey_star
22 size_star
23 tof_n
24 a1_prev
25 a2_prev
26 a3_prev
27 a4_prev
28 a5_prev
29 a6_prev
30 base_vx_n
31 base_vy_n
32 base_wz_n

Runner: strands_arm.policy.FramePolicy (image ey flipped to the sim's up-positive frame, size = box height / half frame, ToF mm -> m, targets clamped through kine.clamp_pose). Never run on the real arm yet; shadow mode (real pose, detections and ToF, nothing written) is logged in the repo's RL.md. Source: github.com/cagataycali/strands-arm 56caa5b, rl/envs/frame_env.py, rl/sim/. PPO: {"n_envs": 256, "steps_per_env": 50, "epochs": 4, "minibatches": 8, "lr": 0.0003, "gamma": 0.98, "lam": 0.95, "clip": 0.2, "ent_coef": 0.002, "vf_coef": 0.5, "max_grad_norm": 0.5, "hidden": 128, "act_scale": 5.0, "act_dim": 9, "total_updates": 2500, "device": "cpu", "seed": 0} Env overrides: {"w_act": 0.1, "w_jerk": 0.1, "w_size": 2.0}

Downloads last month
5
Video Preview
loading