Cinema robotics ◆ Mechatronics ◆ Imitation learning ◆ MEng thesis, University of Toronto ◆ CRV 2026

IRIS

The Intelligent Robotic Imaging System: a six-axis cinema robot arm built for under a thousand dollars, and a visuomotor policy that learned camera moves from demonstrations. This page is the studio version of the thesis: the arm's real kinematic model bolted to a desk in your browser, a Sony α7R III with a motorised 16–150 mm zoom on its wrist, twenty-two shots from a pan to a dolly zoom to object tracking, hand control in joint and task space, and a data-collection and training loop you can run yourself. Every shot was planned against the arm's joint limits, speeds and motor torques, then recorded from the same simulator.

Showreel from the studio: the arm on the desk, the camera feed with its viewfinder, and the shots one after another. The framing you see is what the lens sees in the simulation, depth of field included.

6
axes
Unitree GO-M8010-6 FOC actuators, 23.7 N·m peak, timing-belt wrist; 23 printed parts, ~8.5 kg
0.7m
reach
1.4 m sweep, 1.5 kg payload, ±5 mm repeatability, $800–1000 to build
23
preset shots
pans, dollies, orbits, top-to-side arcs, dolly zooms, rack focus, object tracking
16–150mm
servo zoom
motorised zoom and focus rings, thin-lens depth of field on a full-frame α7R III
—
peak torque used
across every planned shot, from MuJoCo inverse dynamics with the camera payload; limit 23.7 N·m
17.9k
training samples
the thesis policy (VisionJointPlanner): 5 frames in, 5 joint steps out; here, train your own in the tab

In one strip

The IRIS arm built in black filament on a desk
1 · Build

A cinema arm for the price of a lens

Six FOC motors, an offset-axis layout for unobstructed sweeps, belt transmissions that move the motors toward the base, and a body that prints in 23 parts. The thesis covers the sizing, the DH table and the iterations that broke.

The IRIS arm on a studio desk in the browser simulator
2 · Fly

The same kinematics, on a desk, in a browser

The MuJoCo body tree of the thesis drives the arm; a damped-least-squares solver aims the lens; every preset is a parametric camera path plus a lens track, checked by the planner before it ever plays.

Imitation-learning pipeline of the thesis
3 · Learn

Record, train, let it fly

The thesis learned camera moves from 17,897 demonstration samples with a compact temporal head. The studio lets you record your own episodes at 10 Hz, train a small action-chunking policy in the tab, and hand it the controls.

The studio

The studio is built for someone framing a shot, not for someone commanding a manipulator. A shot is a row of stops along the strip at the bottom; each one is drawn in the set as a little floating frame showing what the camera would see there, and clicking it moves the playhead and, if you ask, puts you behind the lens to reframe. Drag the ball on the camera to move it, switch to Aim to point it, and the arm solves its own inverse kinematics to follow. The panel on the left is the camera: zoom, how much blur sits behind the subject, where focus is, and whether the rig can reach every stop. Focus can find the subject on its own — the camera measures how far away it is from its own view and racks there. Nothing is pre-rendered. Open it full screen ↗; it needs WebGL and a laptop-class GPU.

▶ Load the studio

Keyboard: space play or pause, k add a stop, f stand behind the camera, 1/2/3 set / picture / both. The studio is heavy on the first load (a 3.5 MB arm and about 9 MB of product models); the real lens blur can be switched off under The set on slower machines.

The shot library, recorded

Each preset, recorded from the simulator at 30 fps: the studio view on the left, the camera feed on the right. Camera positions are the lens's entrance pupil; a look-at orientation keeps the subject on the optical axis; the zoom and focus tracks are commanded to the servos. The table under the clips is the planner's verdict for every shot.

shotwhat happensduration [s]lens [mm]max joint speed [°/s]peak torque [N·m]IK residual [mm]closest to a limit [°]
← swipe the table →

A motorised zoom, and the shots only a robot can do

The thesis flew an Intel RealSense. The next IRIS carries a real camera: a full-frame Sony α7R III (35.9 × 24 mm) and a 16–150 mm zoom whose zoom and focus rings are driven by servos on a bracket under the lens, so a shot can command focal length and focus distance as continuous tracks, slew-rate limited like any other axis. The simulator models the field of view, the barrel extension, the depth of field from the thin-lens circle of confusion, and the two effects that need the arm and the lens to move together.

field of view: 2·atan(24 / 2f) vertical  ·  circle of confusion: c = (f/N)·|d − S|/d · f/(S − f)  ·  dolly zoom, subject of radius r: f(t) = f₀ · √(d(t)² − r²) / √(d₀² − r²)  →  r = 0 gives the textbook f₀·d/d₀

Dolly zoom. The arm pulls the lens back along the optical axis while the zoom servo pushes in, holding the product's size in the frame; the room behind it collapses. The reverse, pushing in while zooming out, makes it rush away. Two details decide whether that reads on screen. The first: the size that has to stay fixed is the silhouette of a body with a radius, not a point at the subject distance — a sphere of radius r at distance d subtends a half-width r/√(d² − r²), so the focal track carries the same square root. Under the point law the width instead drifts as 1/√(1 − (r/d)²) ≈ 1 + ½(r/d)², which is why every reference gives the point law and is right to: at a portrait distance, r/d ≈ 0.05, the error is under 0.15 % and nobody could see it. This is a macro shot — r/d runs from 0.43 to 0.27 — and there it costs 6.9 % of the subject's width across the move. The second: the subject has to fit in the frame. The carrot cake is 225 mm across (measured in the scene, not the 98 mm the spec's height field lists), so the old 70 mm preset framed it at 1.5 frame widths — it ran off both sides and only a 2 mm slice of it was in focus. Both shots now run the arm from 0.26 m to 0.42 m along the optical axis with the cake at 65 % of the frame width, 24 → 42 mm at f/16.

The planner's coefficient of variation on the subject's width is — for the dolly zooms against — for a plain dolly over the same travel. As executed — servo lag, joint tracking and all — the telemetry gives 0.1 % for each dolly zoom. And segmenting the cake out of the rendered frames themselves, the width the audience sees holds inside a 2.4 % band across the shot, while the plain dolly's swings 38 %.

Rack focus. Only the focus servo moves: near product, far product, back, with the aperture open to f/2.8 so the pull is visible. Object tracking. A toy car laps the desk and the wrist keeps it centred, either from the car's true position or from a pixel tracker running on the feed itself, the same loop the studio's policy tab uses to read images.

Field of view, depth of field and the dolly-zoom law
The lens model: field of view over the zoom range, depth of field for two focus distances and apertures, and the focal length that keeps a subject's size as the lens moves.
Focal length, distance and subject size along the dolly zooms
Size constancy, as planned and as executed: focal length, lens-to-subject distance and the subject's width in the frame along both dolly zooms and a plain dolly over the same travel. The dolly zooms hold 65 % of the frame width from 0.26 m to 0.42 m; the plain dolly at a fixed 35 mm runs from 57 % to 82 %.
Subject width measured in pixels from the recorded clips
Measured off the rendered frames, not the model: the cake is segmented out of the camera feed in every sixth frame of each clip and its silhouette width taken in pixels. The dolly zooms stay inside a 2.4 % band; the plain dolly spreads 38 %. The occasional single-frame spike is the segmentation losing a fragment to the specular band across the cake, not the camera.

From a camera path to six motors

The arm in the studio is the thesis's MuJoCo model, body for body: the fixed pose of each link in its parent, the hinge axis, the joint range. A forward-kinematics and Jacobian twin in Python is checked against MuJoCo to 10⁻⁹ m; the browser runs the same equations. The tool frame is the lens's entrance pupil, 14 cm ahead of the mount on the optical axis, so a shot's camera position means the lens, not the flange.

Each frame the solver takes the target lens pose (position and a look-at orientation with a level horizon) and runs damped least squares from the previous joint vector; when a pose is at the edge of reach it retries from eight canonical postures and keeps the closest. Joints 4 and 6 are continuous-rotation motors, so paths through ±180° are taken the short way round. A 1.5 rad/s per-joint cap keeps the motion cinematic; the whip pan is allowed 3 rad/s.

The planner samples every preset at 30 Hz, solves the IK along it and asks MuJoCo for the joint torques by inverse dynamics with the 1.5 kg camera payload. It found the shots the first drafts could not do: a low 360° orbit folds the wrist past its ±100° pitch, a straight-down top view sits on the look-at singularity, and any lens position closer than about 30 cm to the base column folds the arm onto itself. The library on this page is what survived, with the numbers in the table above.

Then the framing was solved rather than guessed. A subject of width W and height H at distance d fills (f/1000)·max(H/0.024, W/0.0359)/d of a full-frame image, so once the arm decides how far away the lens can be, the focal length is the only free variable. Every preset was re-placed against the products' measured sizes — the carrot cake is 225 mm across, the cup 142 mm tall — with four constraints checked along the whole path: the arm reaches every pose, no joint exceeds 95°/s, the lens never comes closer than 100 mm to anything on the desk, and it never focuses nearer than the lens's 200 mm minimum. An earlier draft ended its hero push-in 75 mm from the coffee cup, inside both limits, and the orbits swept the lens 80 mm past it. The widest horizontal orbit that survives all four is 100°: past that the wrist runs out of twist while holding the horizon level, which is why there is no 180° orbit in the library and why a full turn is done on a motorised turntable instead.

Reachable lens positions in the vertical plane while looking at the far product
Where the lens can be while looking at the far product: IK convergence on a grid in the vertical plane of the desk, with four of the planned paths drawn in.
Frame fraction, distance to subject and clearance to products, for every preset before and after the re-solve
Framing and clearance, before and after. Nine of the 23 presets broke one of their own limits: five swept the lens through a product (negative clearance), two came closer to the subject than the lens can focus, and the hero push-in ended with the cup at 2.1 frame widths. After the re-solve, none do.
Median luminance, clipped fraction and colour cast of the camera picture for every preset
The picture was as wrong as the framing. A quarter of the average frame was clipped to white and the whole library carried an orange cast; nothing in the joint-space telemetry says so. Two numbers per frame turned it into something to drive down: 11.6 % clipped to 3.7 %, red-minus-blue 83 to 41.
Joint angles, speeds and torques for four presets
Four presets through the planner: joint angles, speeds against the comfort cap, and inverse-dynamics torques against the motors' 23.7 N·m peak.
Per-shot maxima of joint speed, torque and limit margin
Every preset: maximum joint speed, peak torque and the closest approach to a joint limit.
Lens position error and aim offset while the videos were recorded
What the studio executed while the videos above were recorded: the lens position error of the live solver and how far the aim point sits from the frame centre.

The thesis, in short

1

Design

Four actuator families compared, torque sizing at the shoulder (25.8 N·m), elbow (10.6) and wrist (4.0), and the Unitree GO-M8010-6 chosen at $70 each. Offset-axis kinematics for full sweeps, GT2 belts to move the motors toward the base, a spherical wrist so IK splits into position and orientation. Repeatability ±5 mm, 60–100 W.

2

Control and simulation

MuJoCo model with the real meshes; inverse-dynamics tracking of circles and lines with the camera's optical axis pinned on a subject (the pan, dolly and crane presets of the thesis); numerical and analytic IK; a ROS Noetic stack over RS-485 at 200 Hz with encoder calibration and a real-to-sim visualiser.

3

Learning

VisionJointPlanner: a ResNet-18 feature per frame, five frames of RGB and joints and a goal point in, the next five joint states out, trained on 17,897 samples with an MSE plus continuity loss. Deployed at 30 Hz with a goal-directed start and a stagnation detector to beat the zero-action attractor; the policy generalised zero-shot across backgrounds and around obstacles.

CAD of the IRIS arm
Final design: actuator placement, elbow belt, wrist differential.
System diagram of IRIS
The stack: actuators, RS-485, ROS, perception, policy.
Learned path plans versus expert paths
Learned path plans against expert trajectories.
THE REAL ARM

Thesis teaser

The printed arm in motion.

DEPLOYMENT

The learned policy on the robot

Path planning around an obstacle, from the CRV paper.

Collect data and train a policy in the tab

The Record & train tab of the studio reproduces the thesis's loop at toy scale. Record saves an episode at 10 Hz: joint angles, focal length, focus distance and a 24 × 16 grey thumbnail of the feed, tagged with the shot and the product positions. Demo every preset records the whole library in one go at triple speed. Train fits a VisionJointPlanner-lite in TensorFlow.js: a five-frame window of states and the thumbnail through a tiny CNN, a goal token, a 256-wide MLP, and the next five joint and lens deltas out (action chunking); training takes seconds. Run hands the policy the arm with ACT-style temporal ensembling over the chunks, until it finishes or stalls.

It is honest about what it is: a few thousand samples and a few hundred thousand parameters, enough to reproduce a shot it has seen and to show the failure modes the thesis met at full scale, the zero-action attractor above all. Export the dataset as JSON to train something bigger offline.

The record and train tab of the studio
The tab: dataset counter, loss curve, and the policy status while it flies.

Two experiments, run in the browser

Both of these run inside the studio itself, driven headlessly at a fixed 30 Hz step so they are repeatable: the tracking loop reads the rendered camera feed, and the policy is trained on demonstrations the studio recorded of its own presets. The numbers below are measured, not asserted.

Object tracking from the feed

The toy car laps the desk and the wrist keeps it centred, either from its true position or from a colour tracker reading the feed: segment saturated red, take connected components of a 132 × 88 read-back, score them by redness rather than by size, and steer the optical axis with a proportional law applied to the camera's measured pose.

Scoring by size was the first bug: the carrot cake's brown crust covers more pixels than the car, so the arm politely tracked the cake. Chromaticity separates them cleanly (car ≈ 0.85, crust ≈ 0.65). Two attempts to lead the target with an estimated angular rate made things worse, because the rate had to be estimated from the controller's own corrections and the loop amplified them.

Result. With ground truth the car stays within a fraction of a percent of frame centre. The tracker holds it to 13.4 % of the frame at 28 mm and 15.5 % at 45 mm, with residuals (what it still sees off-centre) of 9.5 % and 6.0 %, and it sees nothing in 16–18 % of frames. At 85 mm the field of view is 16°, the car leaves the frame, and a reactive tracker with no search behaviour cannot get it back: lost in 62 % of frames.

Tracking error over time for ground truth and the pixel tracker at three focal lengths
Tracking: the car's offset from frame centre, the tracker's own residual, and how often it sees nothing.
Policy training curve and rollout errors
The in-tab policy: training and held-out loss, and how far each rollout ends from the demonstration it imitates.

Learning a shot from demonstrations

The studio recorded 80 episodes (every non-tracking preset on all four product sets) at 10 Hz, and trained the VisionJointPlanner-lite in the tab: five past states plus a 24 × 16 thumbnail of the feed and a goal token in, the next five joint and lens deltas out, with temporal ensembling at run time.

It learns the shape of a move and then drifts. TensorFlow.js validates on the tail of the data, so the held-out set is whole shots the model never saw: the training loss reaches 0.0025 against 0.22 held out. Where it starts decides everything. Rolled out from the home pose, a state no demonstration ever visits, it ends on average 752 mm from where the demonstration ended. Started at the shot's own first pose, as a deployment would after the thesis's goal-directed start, that falls to 89 mm and the zoom lands within a few millimetres of the right focal length. That order of magnitude is the distribution shift the thesis fixed with a goal-directed start and a stagnation detector.

It is a working data-collection and training loop at toy scale, not a result: the thesis needed 17,897 samples where this has a few thousand. Record your own, export the JSON, and train something bigger offline.

Reproduce

# IRIS_studio/experiments
python -m pytest -q tests          # FK, Jacobian vs MuJoCo; look-at IK
python plan_shots.py               # every preset -> out/shots_plan.json + site copy
node record_videos.js              # drive iris_studio.html headless (GPU), 30 fps -> mp4 + telemetry
python make_figures.py             # docs/figures/*.pdf, site PNGs, numbers
cd ../docs && make                 # iris_studio.pdf

The spec that both sides read is assets/data/iris_studio.json: the desk, the product sets, the car's track and the 22 shots. The Python package irisstudio (kinematics, shots) and the browser modules assets/js/iris/{kin,lens,shots,scene,dof,policy,app}.js implement the same rules, so what the planner validates is what the studio plays.

Assets. Arm links: the thesis's STL meshes, decimated to 1.7 MB. Products: CC0 models from Poly Haven (apple, bananas, lemon, lime, kiwi, avocado, pomegranate, pears, two cakes, wine bottles, a vase, a succulent, two watches) plus procedural props (a takeaway coffee cup, a perfume bottle, a lipstick, a cream jar, the toy car). Camera and lens are procedural, to scale.

Not modelled. Motor dynamics and belt compliance (the arm follows the planned joints under a speed cap), self-collision, the real lens's breathing and minimum-focus curve, and the camera's exposure. The report lists the limitations.

Studio report (PDF): kinematics and the tool frame, the IK, the lens model, the planner's tables, the executed telemetry.

Credits

IRIS: Intelligent Robotic Imaging System. Jerry (Qilong) Cheng, MEng thesis, University of Toronto, 2025; the CRV 2026 paper covers the learned path planning. The browser studio, the servo-zoom camera model, the shot library, the planner and the report on this page are new work (September 2026) on top of the thesis code. Product models are CC0 from Poly Haven; three.js renders the scene; TensorFlow.js trains the tab policy.