Physical AI Lab

Move your hand.
Watch a robot arm respond.

From webcam hand tracking to control signals for a simulated robot arm.

What is Physical AI? Many AI examples take an input and return an output, such as a label or a sentence. Interactive systems add another step: an action can change the environment and affect what is observed next. In this lab, you will explore that idea with a simulated robot arm. In Module 4, the feedback is human-in-the-loop: you watch the arm and adjust your hand. The controller does not autonomously read the arm's state and correct its own actions.

Jim Seelan  ·  3-hour session  ·  a kickoff, five technical modules + wrap-up

Hand tracking and human-in-the-loop feedback.
Observe the arm, adjust your hand.

Webcam raw pixels Perception MediaPipe · M1 State Vector selected measurements dtype varies Sim Agent PPO / Hand · M3–4 Action torques · M4 World / Sim MuJoCo · Reacher · M2 person watches arm and adjusts hand Foundation Model reasoning Gemini · Module 5 M1 M1 M1→M4 M3/M4 M4 Webcam raw pixels Perception MediaPipe · M1 State Vector selected measurements dtype varies Sim Agent PPO / Hand · M3–4 Action torques · M4 World / Sim MuJoCo · Reacher · M2 person watches the arm and adjusts their hand Foundation model reasoning Gemini · M5
Conceptual overview, not a literal wiring diagram. In Module 4, the person watches the simulated arm and adjusts their hand; the webcam tracks the hand, not the arm.
Using this deck

Two modes. One file.

Presentation mode

  • Arrow keys or Space bar to navigate
  • S — speaker notes
  • F — fullscreen
  • B — blank screen (useful mid-session)
  • ? — all shortcuts

Handbook / self-study

  • Scroll mode — add ?view=scroll to the URL
    physical-ai.boltage.in/slides?view=scroll
  • All slides become one continuous scrollable page — read at your own pace
  • PDF export — open in Chrome, add ?print-pdf, then File → Print → Save as PDF
  • The handbook can be read after it has loaded; live Gemini API calls still require internet
What's in here
PartContentWhen to read
1 — Concept foundations11 concepts with plain-language explanations, analogies, references, self-checkBefore or after the session
2 — The labEvery module: scripts, exercises, diagrams, speaker notesDuring and after
3 — After today9 capstone projects across laptop and hardware tiersAfter the session
4 — Quick referenceKey numbers, all demo scripts, common issues, fallback behavioursKeep open during the session
Part 1 of 4

Concept Foundations

Eleven concepts that underpin everything in the lab. Each has a plain-language explanation, an analogy, and references to go deeper. Reading on your own? Start here before Part 2. In a live session? Your facilitator will tell you where to begin — usually Part 2 directly.

Self-check After reading each section, close it and try to explain the concept in one sentence. If you can't, re-read. Understanding — not memorisation — is the goal.
Concept 1 of 11

What is Physical AI?

One sentence Physical AI systems connect perception and action, often using feedback to adjust what happens next. Some rely on human input; others use autonomous controllers and sensors. This lab explores the idea through a simulated robot arm and separate perception and model demos.

Static AI

You give it an image, it returns a label. The world does not change because of what it said.

Examples: image classifier, chatbot, recommendation engine.

Physical AI

The agent acts → the world changes → those changes become the next input. A continuous conversation with the physical world.

Examples: robot arm, self-driving car, drone.

Concept 1 — Physical AI

The analogy

Reading a book is static — the book doesn't change based on what you understood. Having a conversation is dynamic — what you say changes what the other person says next. Physical AI systems are always in a conversation with the physical world.

Why it matters for Module 4 Your hand moves → MediaPipe detects it → joint angles computed → torque sent → robot arm moves → camera sees new frame. Every stage feeds the next. Remove any stage and the loop breaks. Feedback lets a controller respond to changes instead of relying on a fixed sequence of actions.
Go deeper
Concept 2 of 11

From pixels to numbers

One sentence A camera frame is a 3D array of integers (height × width × 3 colour channels). All perception work is about compressing that array into a small, meaningful list of numbers the robot can act on.

What a frame actually is

cap.read()  # returns numpy array, shape (480, 640, 3)
# 480 rows × 640 columns × 3 (B, G, R values)
# 640 × 480 × 3 colour-channel values

For this exercise, we’ll use hand landmarks and joint angles instead of feeding the raw image into the controller. The perception layer compresses this down to 3 or 4 joint angle values.

Concept 2 — Pixels to numbers

The compression pipeline

Camera frame (480×640×3 ints)
  → MediaPipe model
  → 21 landmark (x, y, z) coordinates
  → dot-product angle formula
  → 3–4 joint angles in degrees
  → numpy array shape (4,) float32  ← the state vector
Analogy Describing your hand position with every pixel = thousands of numbers. Saying "my index finger is at (0.3, 0.4) and slightly bent" = a state vector. Tiny, precise, actionable.
Go deeper
Concept 3 of 11

MediaPipe & landmarks

One sentence MediaPipe runs a pre-trained neural network on your camera frame and outputs the (x, y, z) positions of 21 specific points on your hand — called landmarks. You don't train anything.

What "pre-trained" means

Training from scratch: millions of images, weeks of GPU, deep CV expertise. We use Google's model (hand_landmarker.task — 7.5 MB) and run inference on it. The model was trained elsewhere. We just use it.

Normalised coordinates

Landmark at x=0.5, y=0.3 means: halfway across the frame, 30% down from top. Always in [0.0, 1.0] regardless of camera resolution. Code is resolution-independent.

Concept 3 — MediaPipe

LIVE_STREAM mode — why async

The pattern Feed frames via detect_async() → model calls your on_result callback when done. Main loop keeps reading camera while model processes the previous frame in a background thread. queue.Queue(maxsize=1) safely passes results between threads.
Analogy Like sending photos to a photo lab. You drop off a photo (detect_async), keep taking more, the lab calls you (callback) when each one is developed. You don't wait around.
Go deeper
Concept 4 of 11

State vectors

One sentence In this lab, a state vector is a fixed-size array of selected measurements that a policy or controller can use to choose an action. It is an observation representation; it does not always contain the system's complete underlying state.

Why fixed size?

Neural networks require fixed-size inputs. The state vector must always have the same number of elements. CartPole: always (4,). Reacher: always (11,). Non-negotiable.

What goes in it?

Include the measurements the agent needs for this task, and keep the observation format consistent. CartPole: cart position, cart velocity, pole angle, pole angular velocity. Not the pixel render. Not the time elapsed. Just those 4 numbers.

Concept 4 — State vectors

State vectors in this lab

ContextShapeContents
M1 exercise result(4,) float32Index, middle, ring, thumb MCP (knuckle) angles
CartPole observation(4,) float32cart pos, cart vel, pole angle, pole vel
Reacher observation(11,) float64Joint cos/sin, target pos, velocities, fingertip pos + dist
M4 action(2,) float32Joint 0 torque, joint 1 torque
Analogy A chess player's state isn't the tablecloth colour or who's watching. Just the piece positions. A compact description of the information chosen for this task. Whether it is sufficient depends on the environment and controller.
Observation design — the most important decision you make Choosing what to include in a state vector is called observation design. Leave out something important: the agent can never learn. Include irrelevant noise: training slows and generalisation suffers. The examples above each reflect careful observation design — not arbitrary choices.
Why float32, not float64? float32 is common in neural-network training and inference because it often offers a useful trade-off between memory use and numerical precision. It is not automatically faster in every workload, and the correct dtype depends on the library and environment. For example, this lab's Reacher observation space uses float64.
Go deeper
Concept 5 of 11

Gymnasium & MuJoCo

One sentence Gymnasium is a Python API for physics simulations an RL agent can interact with. MuJoCo is the physics engine that makes those simulations realistic. We simulate because training on real hardware is slow, expensive, and dangerous.

Why simulate at all?

  • RL needs millions of trials
  • Real hardware: slow, expensive, can break
  • Simulation lets you run many trials without risking physical hardware; speed depends on the environment and machine
  • The catch: the sim-to-real gap (Concept 11)

What Gymnasium provides

A standardised interface — the same three calls — for hundreds of different environments. Whether you're training on CartPole or a humanoid robot, your RL code stays the same. Only the environment changes.

obs, info = env.reset()
obs, reward, done, trunc, info = env.step(action)
env.close()
Concept 5 — Gymnasium/MuJoCo

The episode loop

Gymnasium episode loop — runs every timestep (~200 Hz for Reacher) env.reset() (obs, info) Agent / Policy obs → action env.step(action) (obs, r, done…) env.close() when done obs action next obs → agent (episode continues)
render_mode="rgb_array" — how the lab hub streams simulation frames When you request render_mode="rgb_array", each env.render() call returns the simulation frame as a numpy array (height × width × 3 RGB) instead of opening a display window. The FastAPI server in api/server.py uses this: render → encode to JPEG → stream over HTTP to your browser. No display hardware, GPU, or desktop required.
Go deeper
Concept 6 of 11

Reinforcement Learning

One sentence RL is a way of training an agent by letting it try things, measuring how well it does with a reward signal, and adjusting its behaviour to get more reward over time — without ever telling it the right answer.

The core loop

  1. Agent observes current state ("pole tilted 5° right, moving at 0.3 rad/s")
  2. Agent picks an action ("push cart right")
  3. Environment transitions to new state (cart moves, pole responds)
  4. Agent receives reward (+1 because pole still upright)
  5. Repeat millions of times
What is a policy? The policy is the agent's decision-making function — it maps an observation to an action. Implemented as a neural network: observations go in, probabilities over possible actions come out. Training RL is just the process of improving this function until it produces good actions.
Concept 6 — RL

RL vs supervised learning

Supervised learning You have labelled examples: this image IS a cat. The correct answer is provided for every training example.
Reinforcement learning We don’t provide the correct action for every state. The agent learns from rewards; whether reinforcement learning is the right approach depends on the task.
Analogy Teaching a dog to sit. You don't explain what "sit" means. You give a treat when it does something resembling sitting. The dog figures out what behaviour produces treats. RL is the formal mathematical version of this.
Go deeper
Concept 7 of 11

Markov Decision Processes

One sentence An MDP is a mathematical framework for describing any sequential decision-making problem — it formalises "who decides, what they can decide, what happens next, and what reward they get."
SymbolNameCartPole example
SState spaceAll possible (cart_pos, cart_vel, pole_angle, pole_vel) combinations
AAction space{0, 1} — push left or push right
R(s,a)Reward function+1 if pole upright; episode ends if it falls or pole tilts beyond 12° or cart moves beyond 2.4 m
T(s′|s,a)Transition functionPhysics of the system — deterministic for CartPole
γDiscount factor0.99 — near rewards worth slightly more than far rewards
Concept 7 — MDPs

The Markov property — why it matters

The property The next state depends only on the current state and action — not on the history of how you got there. P(s′ | s, a, all_previous_states) = P(s′ | s, a).

For CartPole: knowing current (position, velocity, angle, angular_velocity) is sufficient to predict the next state. No history needed. This is what makes the problem mathematically tractable.

What the agent maximises G = R₁ + γ·R₂ + γ²·R₃ + … (expected cumulative discounted reward)
Go deeper
Concept 8 of 11

PPO — Proximal Policy Optimisation

One sentence PPO trains a neural network (the policy) by collecting experience, estimating which actions were better, and making small, stable updates to the network weights — clipping the update to prevent collapse.

The problem it solves

Naive policy gradient methods: update too aggressively → policy "collapses" → unrecoverable. PPO's fix: a clipped objective function that constrains how much the policy can change in one update. "Proximal" = stay close to where you were.

Steps per update

  1. Run policy for n_steps timesteps, collect (s, a, r, s′)
  2. Compute advantage via GAE (Generalised Advantage Estimation) — was this action better or worse than average? Advantage = actual return − estimated value of that state. GAE smooths this estimate across multiple timesteps to reduce variance.
  3. Update policy toward high-advantage actions — but clip the update
  4. Repeat
Why PPO and not SAC? SAC (Soft Actor-Critic) is often better for purely continuous action spaces at scale. PPO is a widely used baseline and works well for the discrete CartPole exercise. It is a practical teaching choice because the implementation is accessible and its learning curve is easy to inspect. SAC is another common option for continuous action spaces; neither algorithm is best for every task.
Key SB3 hyperparameters
ParameterWhat it controlsDefault
n_stepsTimesteps collected per rollout before an update2048
batch_sizeMini-batch size for each gradient update64
clip_rangeMax policy change per update (the "proximal" constraint)0.2
gammaDiscount factor γ — how much future rewards are worth0.99
Concept 8 — PPO

The reward curve

0 125 300 500 0 25k 50k Timesteps max agent discovers balance ~20–30k steps exploration converging
The flat phase is random exploration — the agent hasn't yet found a sequence of actions that consistently keeps the pole upright, so rewards stay near zero. The sharp climb is the moment it discovers the balancing behaviour. The curve then levels off as the policy converges near the 500-step maximum.
Go deeper
Concept 9 of 11

Closing the loop

One sentence The loop is "closed" when the agent's action changes the world, and that changed world feeds back into the agent's perception — creating a continuous cycle of sense → act → sense → act.
Open loop Acts without feedback. Washing machine runs a fixed cycle regardless of whether clothes are clean. Many familiar AI tools return a result without directly changing the environment. That is different from a control system that observes the effects of an action and adjusts what it does next.
Closed loop Continuously adjusts based on what it observes. Thermostat: measures temperature → turns on heat → measures again. Output (heat) changes input (temperature).
Concept 9 — Closed loop

The exact loop you build in Module 4

Your hand moves slightly
  → camera captures new frame
  → MediaPipe detects landmark 8 at (x=0.6, y=0.4)
  → scale_landmark_to_action → action = [0.2, -0.2]
  → env.step([0.2, -0.2]) → Reacher arm moves
  → env.render() → new frame sent to browser
  → you see the arm respond
  → your hand adjusts
  → the person sees the result and adjusts their hand

The update rate and end-to-end latency depend on the laptop, camera, rendering path and browser. Treat any frame-rate or latency figure as an estimate unless it has been measured on the setup being used. Here, the person closes the feedback loop by watching the simulated arm and adjusting their hand.

Go deeper
Concept 10 of 11

Foundation Models & VLMs

One sentence A Foundation Model is a large neural network trained on massive diverse data that develops broad general capabilities and can be prompted to reason about specific tasks without task-specific retraining.

Three characteristics

  • Very large scale — billions of parameters, internet-scale data
  • Emergent capabilities — abilities not explicitly trained for
  • Adaptable via prompting or fine-tuning without retraining from scratch

VLM — Vision-Language Model

Gemini is a vision-language model (VLM) that accepts images and text. It can return an interpretation or structured response based on the image and prompt, but its outputs can be wrong and need checking.

In this lab: camera image + prompt → proposed JSON response (not executed by Reacher)

Concept 10 — Foundation Models

VLA — Vision-Language-Action models

How research systems differ Vision-language-action (VLA) systems are designed to connect visual and language inputs to robot actions. RT-2, OpenVLA and π0 are examples, but their architectures, training methods and action representations differ. In this lab, Gemini returns a proposed structured JSON response for inspection. The script does not execute the proposed action on the simulated Reacher arm; this is a simpler teaching example.
SystemOrganisationWhat it does
RT-2Google DeepMindVLA on real robot hardware
OpenVLAStanford / BerkeleyOpen-source VLA model
π0Physical IntelligenceDexterous manipulation VLA
Our M5This labCamera → Gemini → proposed JSON response (not executed)
Go deeper
Concept 11 of 11

The sim-to-real gap

One sentence Policies trained purely in simulation often fail on real hardware because the physics, sensors, and appearance of the real world differ from the simulation.

Three sources

  • Physics mismatch — simulated friction, contacts, and inertia are approximations. Real motors have backlash, thermal drift.
  • Sensor noise — real cameras have blur, distortion, lighting variation. Simulated cameras are noiseless.
  • Appearance gap — MuJoCo renders look nothing like reality. A policy trained on sim visuals won't recognise real scenes.
Concept 11 — Sim-to-real

How to bridge the gap

Domain randomisation Randomise everything during training: lighting, colours, friction, masses. The policy learns to be robust to variation. The real world becomes "just another sample in the distribution."
Sim-to-real transfer Train in simulation (fast, cheap), then fine-tune on real robot data (slow, expensive, but much less needed).
High-fidelity simulation Use better simulators that model real-world physics more accurately. NVIDIA Isaac Sim / Isaac Lab — the industry standard for sim-to-real robotics research, with domain randomisation at scale. Genesis — a newer, fast, differentiable physics engine. Runs significantly faster than MuJoCo for some tasks and supports gradient-based policy optimisation directly through the simulator. Both are the logical next step after this lab.
Go deeper
Part 1 — Self-check

Can you answer these without looking?

  • What is an observation, and what shape is CartPole's?
  • What does MediaPipe's LIVE_STREAM mode do differently from synchronous inference?
  • What happens if the pole in CartPole tilts beyond 12°?
  • Why does the PPO reward curve stay flat before it climbs?
  • What does 2x − 1 do, and why is it the right formula for Module 4?
  • What is the Markov property, and why does it matter for RL?
  • What is the sim-to-real gap? Name one technique to bridge it.
  • What's the difference between a VLM and a VLA model?
  • What is observation design, and why does it matter more than model architecture?
  • Why do observation dtypes vary between environments? What is GAE?
  • What does render_mode="rgb_array" do, and why does the lab hub need it?
  • A MediaPipe landmark returns x=0.5 — what does that mean physically, and why is it resolution-independent?
  • What is the difference between terminated and truncated in env.step()?
Part 2 of 4

The Lab

A kickoff, five technical modules, and a wrap-up. Each technical module introduces a concept or tool used in the lab. In Module 4, your hand controls a simulated robot arm in real time. That's the point.

By the end of Part 2 you will have
  • Built a hand-tracking perception pipeline using MediaPipe
  • Stepped a physics simulation environment (CartPole and Reacher)
  • Ran a PPO training exercise and inspected how the CartPole reward curve changes
  • Used hand tracking to control a simulated robot arm, with the person providing visual feedback
  • Explored a separate Gemini demo that returns structured JSON from an image; it does not control the simulated Reacher arm
ModuleDurationWhat you build
M0 — Kickoff15 minDraw the pipeline. Print your first state vector.
M1 — Perception30 minWebcam → 21 landmarks → 4 joint angles → state vector
M2 — Simulation20 minStep environments, explore Reacher's observation space
M3 — RL30 minRun PPO training on CartPole and inspect the reward curve; learning varies by run.
M4 — Perception to action40 minUse hand tracking to control a simulated robot arm.
M5 — Foundation Models25 minGemini returns a proposed structured response from a camera image
M6 — Wrap-up15 minMap everything to real-world research tools
Module 0 · Kickoff

The pipeline. Everything builds toward this. 15 min

We're not building from scratch. We're wiring together pre-trained pieces. The glue code is what you're writing today.

modules/00_kickoff/overview.ipynb   # kernel: Physical AI Lab — run all cells
Core concept — State Vector A fixed-length array of selected measurements used as an observation by a policy or controller. Module 1 builds a small example; CartPole has a four-value observation and Reacher an eleven-value observation. Their dtypes differ. Module 4 maps hand landmark coordinates to torque commands; it does not feed a learned policy's state vector into the arm.
M0 — Exercise

Print your first state vector

import numpy as np
print(np.array([145.2, 163.8, 171.4, 158.0], dtype=np.float32))
# [145.2 163.8 171.4 158.0]
# That printed line is a state vector.
What these numbers mean Four joint angles, in degrees. Index finger MCP, middle MCP, ring MCP, thumb MCP. For this exercise, four measurements are enough to demonstrate how a state vector represents an observation.

Try it yourself first. Then refer back to the pipeline diagram (slide 2) and name which module handles each stage.

Module 1 · Perception

From webcam image to useful measurements. 30 min

A webcam frame contains many pixel values. MediaPipe’s hand-landmark model returns 21 landmarks; selected landmarks can then be used to calculate joint angles. The angle formula compresses that to 4 joint angles. That's your state vector.

python modules/01_perception/01_webcam_basics.py
# Prints frame shape: (480, 640, 3)

python modules/01_perception/02_hand_tracking.py
# 21 green dots appear. Hold your hand up.

python modules/01_perception/03_joint_angles.py
# Prints [145.2, 163.8, 171.4] once per second
# "That is a state vector."
The MediaPipe Tasks API We use mediapipe.tasks.vision.HandLandmarker. The older mp.solutions.hands still appears in most tutorials but is deprecated. Key: coordinates already normalised to [0,1].
Fallback If the camera is unavailable, the demo may use fallback_hand_demo.mp4 instead of live input. This keeps the walkthrough moving, but the landmarks no longer represent the current camera scene.
M1 — Hand landmarks

21 landmarks. Normalised to [0, 1].

0 wrist 1 2 3 4 thumb tip ← M4 5 MCP (knuckle) 6 PIP (mid-joint) 7 8 index tip ← M4 9 10 11 12 13 ring MCP ← exercise 14 ring PIP ← exercise 15 16 17 18 19 20 M4 control points (lm 4, 8) Exercise (lm 13, 14)
Coordinates already normalised to [0,1] — no pixel division needed. Red filled = M4 control points. Amber filled = exercise landmarks (M1). Numbers 1–20 label the remaining landmarks. Colour AND size differ so the diagram is readable without colour vision.
M1 — Angle formula

The dot-product angle

V (lm 5 — MCP) A (lm 0 — wrist) B (lm 6 — PIP) θ θ = acos ( VA · VB |VA| × |VB| ) VA VB open hand ≈ 170° · closed fist ≈ 30° · clamp cosine to [−1, 1] before acos
Exercise — add ring finger In 03_joint_angles.py, add landmarks 13→14 to produce a 4-element state vector. Allow ~10 min. Ask 1–2 volunteers to share output.
M1 — How it works

The code skeleton — MediaPipe async pattern

import queue
import mediapipe as mp
from mediapipe.tasks import python as mp_python
from mediapipe.tasks.python import vision as mp_vision

result_queue = queue.Queue(maxsize=1)  # holds the latest result only

def on_result(result, image, timestamp):
    # Called by MediaPipe's background thread when a frame is processed
    # We put it in the queue — the main loop picks it up
    if result_queue.full():
        result_queue.get_nowait()   # drop the stale result
    result_queue.put_nowait(result)

# Build the landmarker once — reused for every frame
options = mp_vision.HandLandmarkerOptions(
    base_options=mp_python.BaseOptions(model_asset_path="assets/hand_landmarker.task"),
    running_mode=mp_vision.RunningMode.LIVE_STREAM,
    result_callback=on_result          # ← our function gets called here
)
landmarker = mp_vision.HandLandmarker.create_from_options(options)

cap = cv2.VideoCapture(0)             # open webcam
while True:
    ret, frame = cap.read()           # BGR frame from camera
    mp_image = mp.Image(image_format=mp.ImageFormat.SRGB,
                        data=cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))
    landmarker.detect_async(mp_image, timestamp_ms)  # non-blocking — returns immediately

    # Main loop continues here — doesn't wait for MediaPipe
    if not result_queue.empty():
        result = result_queue.get_nowait()
        lm = result.hand_landmarks[0]         # list of 21 landmarks
        x = lm[8].x                          # index fingertip x, in [0, 1]
        # → compute angles, build state vector, etc.
Why the queue? MediaPipe runs in a background thread. The main loop runs at camera speed (~30 fps). queue.Queue(maxsize=1) bridges the two threads safely — it always holds the latest result, never more than one, so you never act on a stale frame.
Module 2 · Simulation

We never write the physics. 20 min

CartPole: 4 observations, 2 discrete actions. Reacher: 11 observations, 2 continuous torques. Those torques are what your hand controls in 20 minutes.

python modules/02_simulation/01_gym_intro.py
# Observation space: Box(-4.8, 4.8, (4,), float32)
# Action space: Discrete(2)

python modules/02_simulation/02_mujoco_reacher.py
# Observation space: Box(-inf, inf, (11,), float64)
# Action space: Box(-1.0, 1.0, (2,), float32)
Exercise Print observation_space.low and observation_space.high for Reacher. Understand the bounds before you try to send actions. ~8 min.
Before the session — 03_pretrained_agent.py Run python modules/02_simulation/03_pretrained_agent.py before the lab starts. It loads a pre-trained policy and runs HalfCheetah (a MuJoCo humanoid) for 5 seconds. When participants ask "what does a million training steps look like?" — that video is the answer. Don't run this live; it takes 30 seconds to load and derails the pace.
M2 — Reacher state vector

11 observations. You'll control 2 of them.

0–1 2–3 4–5 6–7 8–9 10 cos θ₀ sin θ₀ joint 0 cos θ₁ sin θ₁ joint 1 target x target y ω₀ ω₁ ang. vel. finger x finger y dist to target joint angles (cos/sin avoids ±180° wrap) target velocities fingertip + dist
In Module 4, we bypass the RL agent — we compute the action directly from the hand landmark position.
M2 — How it works

The episode loop in code

import gymnasium as gym
import numpy as np

env = gym.make("CartPole-v1")       # create the environment
obs, info = env.reset()             # reset to start state, get first observation
# obs is a numpy array: [cart_pos, cart_vel, pole_angle, pole_vel]
print(obs.shape)                    # → (4,)

for step in range(200):             # run for up to 200 timesteps
    action = env.action_space.sample()  # random action: 0 (left) or 1 (right)
    obs, reward, terminated, truncated, info = env.step(action)
    # obs    : new state after the action
    # reward : +1.0 if the pole is still upright
    # terminated : True if pole fell or cart went out of bounds
    # truncated  : True if we hit the max episode length (500 steps for CartPole)

    if terminated or truncated:
        obs, info = env.reset()     # start a new episode

env.close()
Random agent = your baseline A random agent scores ~20–25 steps on CartPole before the pole falls. A successful trained policy can reach reward 500, the environment's cap; results vary with the run and training settings. If a trained agent can't beat random, something went wrong in training.
Module 3 · Reinforcement Learning

It starts random. Watch it figure it out. 30 min

python modules/03_rl/02_train_cartpole.py
# ~2–3 min. Keep matplotlib window visible.
# Walk the MDP cycle diagram while it trains.
# STOP TALKING when the curve climbs — let everyone watch.
What PPO does in plain English Collect experience → estimate which actions led to better outcomes → update policy weights → clip the update so it doesn't change too drastically. Most stable RL algorithm for continuous control. Used in real robot locomotion controllers today.
Exercise Change total_timesteps and re-run. ~10 min. Watch the curve shape change.
M3 — The MDP cycle

State → Policy → Environment → Reward → repeat

State s [pos, vel, θ, ω] CartPole obs (4,) Policy π(a|s) PPO — MlpPolicy 2 hidden layers × 64 Environment T(s′|s,a) physics CartPole / MuJoCo Reward R(s,a) +1 per upright step 0 when pole falls obs s action a r, s′ update next state s′ (Markov property)
M3 — How it works

PPO training — what the code actually does

import gymnasium as gym
from stable_baselines3 import PPO
from stable_baselines3.common.callbacks import BaseCallback

env = gym.make("CartPole-v1")

# Create the PPO agent
# "MlpPolicy" = a Multi-Layer Perceptron network (2 hidden layers of 64 neurons)
# The network takes the 4-element CartPole state and outputs action probabilities
model = PPO(
    "MlpPolicy",
    env,
    verbose=1,           # print training progress every n_steps
    n_steps=2048,        # collect this many timesteps before each update
    batch_size=64,       # mini-batch size for gradient updates
    gamma=0.99,          # discount factor
)

# .learn() runs the entire training loop:
#   1. Run policy for n_steps, collect (obs, action, reward, next_obs) tuples
#   2. Compute advantage (GAE) for each action
#   3. Update network weights using the clipped objective
#   4. Repeat until total_timesteps reached
model.learn(total_timesteps=50_000)

model.save("models/ppo-CartPole-trained")  # save for later
Exercise guidance — what to try
  • total_timesteps=10_000 — curve barely climbs. Pole falls after ~50 steps.
  • total_timesteps=50_000 — default. Curve climbs around 20–30k. Pole holds for 500 steps.
  • total_timesteps=200_000 — converges faster and more reliably. Diminishing returns after ~80k.
The shape of the curve can help you reason about learning progress, but it varies with the task, random seed, training settings and number of steps.
Module 4 · Perception → Action

From perception to action, in real time. 40 min

python modules/04_perception_to_action/01_hand_to_reacher.py
# Two windows: camera + Reacher sim.
# Move your hand slowly.
# x position → joint 0 torque. y position → joint 1 torque.
The mapping is one line action = 2 * norm_x − 1 That's the entire "intelligence" of the Module 4 controller. Landmark x in [0, 1] → torque in [−1, +1].
Exercise — third torque channel (15 min) Add landmark 4 (thumb tip) as a third torque channel. ActionSliceWrapper is provided. It wraps Reacher's 2-element action space so you can pass a 3-element array and it slices the first two elements for the actual env.step() call — your extra channel controls a visual indicator without breaking the environment interface.
M4 — The mapping

Landmark x → robot torque

Landmark x (MediaPipe Tasks API) 0.0 0.5 1.0 left edge centre right edge f(x) = 2x − 1 Reacher joint torque −1 0 +1 full left no torque full right One line of code. This is the entire "intelligence" of the Module 4 controller.
Why 2x − 1 and not something else? It's the simplest function that maps [0, 1] to [−1, +1] linearly. MediaPipe returns x in [0, 1] (fraction of frame width). Reacher expects torques in [−1, +1]. Any linear function between these ranges works — this one has no extra parameters, no dead zone, and is easy to reason about. One number in, one number out.
Why this is imitation learning data collection A human drives via hand position while (state, action) pairs could be recorded. Those recordings become the training dataset for Behaviour Cloning, ACT, or Diffusion Policy. Behaviour Cloning trains a neural network to replicate the recorded (state → action) mapping. The teleoperation pattern could be extended into a data-collection rig, but this exercise does not itself record a complete demonstration dataset.
Torques, not positions We send torques (rotational forces) to the joints — not target angles. The arm has inertia: it overshoots and oscillates. That's physics, not a bug. Move your hand slowly and smoothly to make the control easier to observe. Torque control is one way to expose the system's dynamics; learning-based methods can also use other action representations, such as target positions or velocities.
M4 — How it works

The human-in-the-loop feedback path

import queue, cv2, gymnasium as gym
# (MediaPipe setup identical to M1 — same async pattern)

result_queue = queue.Queue(maxsize=1)

def on_result(result, image, timestamp):
    if result_queue.full(): result_queue.get_nowait()
    result_queue.put_nowait(result)

# ── MediaPipe setup (same 4 lines as M1 — see "M1 — How it works") ───────────
options = mp_vision.HandLandmarkerOptions(
    base_options=mp_python.BaseOptions(model_asset_path="assets/hand_landmarker.task"),
    running_mode=mp_vision.RunningMode.LIVE_STREAM,
    result_callback=on_result)
landmarker = mp_vision.HandLandmarker.create_from_options(options)  # async
env = gym.make("Reacher-v5", render_mode="rgb_array")
obs, _ = env.reset()
cap = cv2.VideoCapture(0)

# ── The closed loop ─────────────────────────────────────────
while True:
    ret, frame = cap.read()
    landmarker.detect_async(mp_image_from(frame), timestamp_ms)  # non-blocking

    if not result_queue.empty():
        result = result_queue.get_nowait()
        lm = result.hand_landmarks[0]

        # The one line that closes the loop:
        action = [2 * lm[8].x - 1,   # landmark 8 x → joint 0 torque
                  2 * lm[8].y - 1]   # landmark 8 y → joint 1 torque

        obs, reward, terminated, truncated, _ = env.step(action)
        # env.step() advances the simulation by one timestep
        # The robot arm moves. env.render() will show the new frame.

    if terminated or truncated:
        obs, _ = env.reset()

    # Display both windows, handle quit key...
Where the feedback comes from The output of env.step(action) changes the Reacher simulation. The new frame is rendered and shown to you; you adjust your hand in response, producing a new landmark position and torque command. The person supplies the feedback in this exercise — the script does not independently measure the arm state and correct its own action.
Module 5 · Foundation Models

A separate image-to-JSON model demo. 25 min

python modules/05_foundation_models/01_gemini_vision.py
# Captures one frame, sends "Describe this scene." ~2 sec.

python modules/05_foundation_models/02_gemini_robot_brain.py
# Returns a JSON-style dict with an action, observation, reason and next step
Exercise — prompt engineering (10 min) Write your own MY_PROMPT. Different prompts → different responses. How you phrase the question dramatically changes reliability of the output.
No Gemini API key? If the API key is missing or the request fails, this demo can return a cached example. That keeps the exercise moving; it does not verify that the suggested action is correct or safe.
A teaching fallback, not a safety controller Network calls to cloud models introduce latency, rate limits, and failure modes. Systems that depend on network calls need a plan for failures. Depending on the application, that may mean a local controller, a safe stop or a fallback policy. Our cached_responses.json lets the exercise continue when the API is unavailable, but a cached response is not a safety-oriented fallback controller. Real systems need failure handling appropriate to their hardware and application.
M5 — VLM pipeline

Camera image → Gemini → structured JSON response

① Camera BGR frame cv2.read() ② Encode cv2.imencode() → base64 JPEG ③ API call Part.from_bytes() generate_content() ④ Gemini gemini-2.5-flash VLM reasoning returns JSON text ⑤ Parse response strip ```json fences {"action":"WAIT"} any exception → cached_responses.json
The script parses the model's JSON-style response into a dictionary for inspection. A valid schema or confidence field does not make the proposed action reliable or safe.
M5 — How it works

Writing your own prompt — the template

# This mirrors the current demo's action vocabulary and response fields.
# It is a teaching prompt, not a dependable assistive-robot policy.
SYSTEM_PROMPT = (
    "You are ARIA, an assistive robot in a workspace. "
    "Observe the image and respond ONLY in JSON with: "
    '{"action":"APPROACH|WAIT|ALERT|RETREAT","confidence":0.0,'
    '"observation":"...","reason":"...","next_step":"..."}'
)

# Exercise: change one instruction or action definition at a time.
# Compare the returned JSON fields and discuss where the model may be wrong.
# Do not treat the confidence field as calibrated certainty or use this
# prompt to make real decisions about a person's wellbeing.
What to observe in the exercise
  • Does changing the action definitions change the response distribution?
  • Does the model return valid JSON every time?
  • Do the observation and reason match what is actually visible?
  • What happens when the image is ambiguous or the person is not clearly visible?
Prompt engineering is not magic — it is constraint specification. The model is trying to satisfy your description.
Module 6 · Wrap-up

Established tools, small examples. 15 min

ModulePipeline stageReal-world equivalent
M1 — PerceptionWebcam → State VectorIndustrial vision systems; MediaPipe in production robot demos
M2 — SimulationState → Action → WorldMuJoCo used in Boston Dynamics, DeepMind, academic labs
M3 — RLTraining the policyPPO in real locomotion controllers and game-playing agents
M4 — Human-in-the-loop teleoperationHand tracking to simulated joint torquesCan support demonstration collection when actions and observations are recorded
M5 — Foundation ModelsImage → Gemini → proposed JSON actionA simplified prompt-based example, distinct from trained VLA systems such as RT-2, OpenVLA and π0
M6 — The sim-to-real gap

Every policy you trained today lives in simulation.

Three sources of failure on real hardware (1) Physics mismatch — friction, inertia, contacts are approximations. (2) Sensor noise — real cameras have blur, distortion, lighting variation. (3) Appearance gap — MuJoCo renders look nothing like real scenes.
Domain randomisation Randomise physics parameters during training. Real world becomes "just another sample."
Where to go next NVIDIA Isaac Lab — high-fidelity simulation. Hugging Face LeRobot — real robot imitation learning.
Part 3 of 4

After Today

Nine project ideas that explore feedback between perception and action. Choose one that fits your available hardware and experience, then start by getting the perception step working.

The Physical AI test For each project, identify what the system observes after an action and how that observation affects the next action. A static classifier and a controller that changes its environment solve different kinds of problems.
Projects 1–6 · Laptop only

No extra hardware needed

#ProjectLevelExtends
1 Adaptive Posture Coach — Webcam detects shoulder/neck landmarks. Agent adapts alert timing from your correction history — not a fixed threshold. Beginner M1 · M3
2 Sim-to-Sim Transfer — Train PPO on CartPole, deploy into modified env (heavier pole, wind noise). Measure degradation. Apply domain randomisation. Intermediate M2 · M3
3 Behaviour Cloning Agent — Use hand control (M4) to play a Pygame environment. Record (obs, action) pairs. Train an MLP clone. Observe covariate shift.
Pygame is a simple Python game library — easier than MuJoCo for custom control tasks. Covariate shift: the cloned agent encounters states it never saw during your demonstrations and fails unpredictably. Record where the system fails and use those cases to understand its limitations.
Intermediate M1 · M4 · M5
4 Sign Language Recogniser + Corrective Loop — Real-time letter recognition from landmarks. Model confidence feeds back to prompt the user to re-sign ambiguous letters. Beginner–Int M1 · M5
5 PID Tuner via RL — Simulated pendulum or water level. Use RL to learn PID gain parameters as the action space. Compare learned vs hand-tuned. Intermediate M2 · M3
6 Multi-Step VLA Planner — Give Gemini a verbal instruction. It decomposes into sub-goals. Each executed via the M4 pipeline. Chain 3+ steps. Intermediate M4 · M5
Projects 7–9 · Pi + servo

With a Raspberry Pi and two SG90 servos

Hardware cost is an estimate and depends on what you already own; check the bill of materials before buying. Projects 7–8 are starting points for experimenting with sensors and actuators on real hardware.

#ProjectLevel
7 Gaze-Tracked Servo Turret — Pi Camera + MediaPipe detects face or hand. Two SG90 servos pan/tilt to track. Apply the M4 mapping (2x−1) directly to servo angle.

Servo moves → camera points somewhere new → perception updates → next command. Physically broken without the loop.

Intermediate
8 Imitation Learning Arm — 2-servo arm on Pi. Record (landmark state, servo angle) demonstrations. Train small MLP to replicate. Deploy. Document where it drifts.

Covariate shift is physically observable — the arm drifts into states it never saw. The failure mode is the educational content.

Int–Advanced
9 RL Balancer — Train PPO on MuJoCo pendulum. Deploy onto physical pendulum with Pi + servo + encoder. Measure the sim-to-real gap quantitatively. 4–6 week project.

This project introduces control and sim-to-real challenges studied in robotics research.

Advanced
After today — next steps

The tools, the platforms, the papers

Simulation & training

Real robot data + imitation

Courses — start here

VLA models to study next

Part 4 of 4

Quick Reference

Key numbers, demo scripts, exercises at a glance, common issues. Keep this open during the session.

Quick Reference

Key numbers

# Key landmarks
lm 0  = wrist
lm 4  = thumb tip    (M4 exercise)
lm 8  = index tip    (M4 control)
lm 13 = ring MCP     (M1 exercise)
lm 14 = ring PIP     (M1 exercise)

# State shapes
M1 exercise  : (4,)  float32
CartPole obs : (4,)  float32
Reacher obs  : (11,) float64
M4 action    : (2,)  float32

# Training
50k steps ≈ 2–3 min on laptop
CartPole learning varies by run; inspect the reward curve rather than expecting a fixed step count
# CartPole terminal conditions
|pole angle| > 12°  → episode ends
|cart pos|  > 2.4m  → episode ends
max reward  = 500   (500 steps)

# Reacher action space
torque ∈ [−1, +1] for each joint

# M4 mapping
action = 2 * landmark_x − 1

# MDP components
S  = state space
A  = action space
R  = reward function
T  = transition function
γ  = discount factor (0.99)
Quick Reference

Demo scripts

# Start the hub
.venv\Scripts\activate
python api/server.py
# → http://localhost:8000

# Verify install
python verify_install.py
# All 14 should be PASS or WARN

# M1 — Perception
python modules/01_perception/01_webcam_basics.py
python modules/01_perception/02_hand_tracking.py
python modules/01_perception/03_joint_angles.py

# M2 — Simulation
python modules/02_simulation/01_gym_intro.py
python modules/02_simulation/02_mujoco_reacher.py
# M3 — RL
python modules/03_rl/02_train_cartpole.py

# M4 — Loop closes
python modules/04_perception_to_action/01_hand_to_reacher.py

# M5 — Foundation Models
python modules/05_foundation_models/01_gemini_vision.py
python modules/05_foundation_models/02_gemini_robot_brain.py

# Wrap-up / concepts map
# Open in VS Code or: jupyter notebook modules/06_wrapup/concepts_map.ipynb
Quick Reference

Exercises at a glance

ModuleExerciseTime
M0Print a state vector: np.array([145.2, 163.8, 171.4, 158.0], dtype=np.float32)3 min
M1Add ring finger angle (landmarks 13→14) to make a 4-element vector10 min
M2Print observation_space.low and observation_space.high for Reacher8 min
M3Change total_timesteps and re-run. Watch the curve shape change.10 min
M4Add landmark 4 (thumb tip) as a 3rd torque channel via ActionSliceWrapper15 min
M5Write your own MY_PROMPT. Observe how phrasing changes output reliability.10 min
Quick Reference

Common issues

SymptomFix
MediaPipe window blackassets/hand_landmarker.task missing — re-run setup.bat
MuJoCo window doesn't openNormal — render fallback active. Check terminal for rgb_array message.
Reward curve never climbsCheck the training log and let the run finish. Learning curves vary with seed, settings and machine.
Port 8000 in usenetstat -ano | findstr :8000 → taskkill /PID [n] /F
Hub page blankChrome or Edge — not Firefox.
Gemini 429 rate limitCache fallback activates automatically. Continue.
ARM64 / mujoco failsWSL2: wsl --install (restart) → run setup.sh inside Ubuntu
[WARN] Camera unavailableA prerecorded video may be used instead of live input; perception results no longer reflect the current camera scene.
Quick Reference

Fallback behaviours

What failsWhat happens automatically
Webcam unavailableUses a prerecorded demo video instead of live camera input; perception is no longer live
OpenGL / displaymake_env tries "human" → "rgb_array" → None
Gemini key missingRandom entry from cached_responses.json
Gemini API call failsAny exception → cache fallback
No internet during sessionCore exercises use bundled assets; Gemini calls are unavailable and the exercise uses cached responses where configured
Philosophy Some dependencies have demo fallbacks, but a fallback is not equivalent to live perception or a safe action. When you see [WARN], explain which behaviour is being substituted and what is no longer being tested.

Your hand now controls the simulation.

You’ve worked through several building blocks of a Physical AI system: perception, simulation, reinforcement learning and a model-based controller. Each is a small example of a much larger field.

Physical AI Lab → GitHub Repository → Jim Seelan on LinkedIn →

Drag to see the rest of the diagram. Turning your phone sideways shows more of it.