Simulate SMPL humans

Pose an SMPL body with witwin.radar.smpl, place radar scatterers on its surface, and simulate a moving person for micro-Doppler and point clouds.

witwin.radar.smpl wraps the SMPL body model so you can pose a human, move it over time and use it in a radar simulation. This page shows how to install it, build a body, put scatterers on it, and add its mesh to the Scene.

Install#

The SMPL helpers need the smpl extra and the SMPL model files:

pip install "witwin-radar[channel,smpl]==0.4.1"

The model files (for example basicModel_neutral_lbs_10_207_0_v1.0.0.pkl) are distributed by the SMPL authors under their own license and are not included with Radar. Download them, put them in one folder, and pass that folder as model_root.

Always pass model_root

The default model_root points to a models/smpl_models folder next to the installed witwin package, which does not exist in a pip install. Pass the folder explicitly.

Pose a body#

import torch
from witwin.radar.smpl import SMPLBody
 
body = SMPLBody(
    pose=torch.zeros(72),      # 24 joints x 3 axis-angle values, radians
    shape=torch.zeros(10),     # shape coefficients
    gender="neutral",          # must match a model file in model_root
    model_root="path/to/smpl_models",
    position=(0.0, 0.0, -4.0),
    device="cuda",
)
vertices, faces = body.to_mesh()     # posed mesh: 6890 vertices
walking = body.updated(pose=new_pose)

SMPLBody is a Core geometry. updated(**changes) returns a new body with some fields changed. SMPL's +y axis is up, which matches the radar's default up=(0, 1, 0).

Put scatterers on the body#

Radar does not place scatterers on meshes, so a body scatters only where you put targets. A practical model is a fixed set of material points on the surface that follow the pose over time. Give PointTargets a trajectory that re-poses the body at each instant:

from witwin.radar import Motion, PointTargets
 
def pose_at(t):
    pose = torch.zeros(72)
    pose[3 * 16 + 2] = 0.6 * torch.sin(torch.tensor(2 * torch.pi * 1.0 * t))   # swing one arm at 1 Hz
    return pose
 
site_index = torch.randperm(6890)[:128]              # 128 surface points, fixed for the run
 
def surface_points(t):
    vertices, _ = body.updated(pose=pose_at(t)).to_mesh()
    return vertices[site_index].to(radar.device)
 
targets = PointTargets(positions=surface_points(0.0), rcs=1.0 / 128, trajectory=surface_points)
result = radar.simulate(scene, targets, times=(0.0,), reflections=0, motion=Motion.adaptive())

The trajectory must return the same ordered points at every time. Split the body's total cross section over the points, here 1/128 m² each. The repository's walking benchmark uses this route with area-sampled points on the mesh; it is a discrete scattering model, not a full-wave model of skin.

Re-posing the body at every probe is the main cost, so use Motion.adaptive() for long sequences. With Motion.adc() the trajectory is called at every ADC sample.

Add the body as reflecting geometry#

To let the body occlude and reflect other paths, add its mesh to the Scene as a deforming structure. SmplPoseDeformation turns a body and a pose rate into a Core deformation:

from witwin.core import PhysicalMaterial, Scene, Structure
from witwin.core.dynamics import DynamicScene
from witwin.radar.smpl import SMPLBody, SmplPoseDeformation
 
rest = SMPLBody(pose=torch.zeros(72), shape=torch.zeros(10), model_root="path/to/smpl_models", device="cuda")
pose_rate = torch.zeros(72, device="cuda")
pose_rate[3 * 18 + 2] = 3.0                          # rad/s on one joint
deformation = SmplPoseDeformation(rest, pose_rate=pose_rate)
 
scene = Scene(
    structures=(Structure(geometry=deformation.rest_mesh(), material=PhysicalMaterial(name="skin", eps_r=15.0, sigma_e=1.2),
                          structure_id=7, material_id=1, assignment_id=1, surface_id=1),),
    endpoints=[],
)
world = DynamicScene(scene, structure_deformations={7: deformation})
  • The pose at time t is pose + pose_rate · (t - reference_time_s), so the motion is linear in pose space.
  • Use rest_mesh() as the Structure geometry. It builds the mesh with recenter=False, which a deformation needs, and bakes the body's position and rotation into the vertices.
  • deformation.vertices_at(t) is the posed vertex tensor at time t, and deformation.velocity_at(t) the exact vertex velocity, from forward-mode differentiation of the skinning function.

The mesh still adds no scatterers of its own. Combine it with PointTargets as above if the body itself should return energy.

Gradients#

SMPLBody itself passes a pose or shape gradient to its vertices. Through SmplPoseDeformation into a Scene, derivatives on pose, shape, pose rate, position and rotation are refused. To differentiate a body's surface points, use the PointTargets route with a differentiable position tensor.