Parameterizing shapes with Anny¶
Instantiate the model¶
Anny is shipped as a Python package that can be easily installed (see the README for details).
from IPython.display import Markdown, display
import torch
import roma # A PyTorch library useful to deal with space transformations.
import anny # The main library for the Anny model.
import trimesh # For 3D mesh visualization.
# Instantiate the model, with all shape parameters available.
# Remark: the first instantiation may take a while. Latter calls will be faster thanks to caching.
anny_model = anny.create_fullbody_model(eyes=True, tongue=False, all_phenotypes=True, local_changes=True, remove_unattached_vertices=True)
# Use 32bit floating point precision on the CPU for this demo.
dtype = torch.float32
device = torch.device('cpu')
anny_model = anny_model.to(device=device, dtype=dtype)
# A simple transform to get a better view angle in 3D mesh visualizations.
trimesh_scene_transform = roma.Rigid(linear=roma.euler_to_rotmat('x', [-90.], degrees=True), translation=None).to_homogeneous().cpu().numpy()
Warp 1.8.1 initialized:
CUDA Toolkit 12.8, Driver 13.0
Devices:
"cpu" : "x86_64"
"cuda:0" : "NVIDIA RTX 1000 Ada Generation Laptop GPU" (6 GiB, sm_89, mempool enabled)
Kernel cache:
some_path
Template mesh¶
This is the template mesh of Anny:
display(Markdown(f"{anny_model.template_vertices.shape[0]} vertices -- {anny_model.faces.shape[0]} faces composed of {anny_model.faces.shape[1]} vertices each."))
trimesh.Trimesh(vertices=anny_model.template_vertices.cpu().numpy(), faces=anny_model.faces.cpu().numpy()).apply_transform(trimesh_scene_transform).show()
13492 vertices -- 13486 faces composed of 4 vertices each.
Shape parameterization¶
Anny can model a diversity of morphologies. We follow MakeHuman terminology, and parameterize diversity of body shapes using a set of phenotype parameters, typically between 0 and 1.
Note: the values african, caucasian and asian parameters are normalized so that they sum to 1.
Word of caution regarding phenotypes: Phenotypes are based on preconceptions of artists regarding particular human traits. As a result, they encode by design stereotypes of MakeHuman artists, and one should not expect phenotype parameters to faithfully encode identity-related characteristics, such as gender, age or ethnicity.
# List phenotype parameters
Markdown("**List of phenotype parameters**: " + ", ".join([f"{label}" for label in anny_model.phenotype_labels]))
List of phenotype parameters: gender, age, muscle, weight, height, proportions, cupsize, firmness, african, asian, caucasian
Example¶
Here we show an example of how the age parameter influences the resulting mesh.
batch_size = 5 # We can process multiple bodies at once in a batch.
phenotype_kwargs = {key : torch.full((batch_size,), fill_value=0.5, dtype=dtype, device=device) for key in anny_model.phenotype_labels}
phenotype_kwargs['age'] = torch.linspace(0., 1., batch_size, dtype=dtype, device=device) # Example: vary the age parameter across the batch.
output = anny_model(phenotype_kwargs=phenotype_kwargs)
scene = trimesh.Scene()
for i in range(batch_size):
# Create a mesh for each body in the batch.
mesh = trimesh.Trimesh(vertices=output['vertices'][i].squeeze().cpu().numpy(), faces=anny_model.faces.cpu().numpy())
transform = roma.Rigid(linear=None, translation=torch.tensor([i * 1., 0., 0.], dtype=dtype, device=device)).to_homogeneous().cpu().numpy()
scene.add_geometry(mesh, transform=transform)
scene.apply_transform(trimesh_scene_transform) # Rotate the scene to have a better view.
scene.show() # This will open a window to visualize the scene with all the bodies in it.
Module anny.skinning.warp_skinning 4408221 load on device 'cpu' took 3.16 ms (cached)
Local changes¶
Additionnally one specify some more local morphological changes. Local change parameters values are typically expected to be chosen between -1 and 1, but one can use values outside this range to extrapolate changes even further.
Note: it is easy to produce unrealistic meshes when using significant local changes.
display(Markdown("**List of local changes parameters:** " + ", ".join(anny_model.local_change_labels)))
List of local changes parameters: l-lowerarm-scale-depth-incr, r-lowerarm-scale-depth-incr, l-upperarm-scale-horiz-incr, r-upperarm-scale-horiz-incr, l-upperarm-fat-incr, r-upperarm-fat-incr, measure-upperarm-length-incr, l-lowerarm-fat-incr, r-lowerarm-fat-incr, l-lowerarm-scale-horiz-incr, r-lowerarm-scale-horiz-incr, l-lowerarm-muscle-incr, r-lowerarm-muscle-incr, l-upperarm-shoulder-muscle-incr, r-upperarm-shoulder-muscle-incr, measure-upperarm-circ-incr, l-upperarm-scale-depth-incr, r-upperarm-scale-depth-incr, l-lowerarm-scale-vert-incr, r-lowerarm-scale-vert-incr, measure-lowerarm-length-incr, l-upperarm-muscle-incr, r-upperarm-muscle-incr, l-upperarm-scale-vert-incr, r-upperarm-scale-vert-incr, breast-point-incr, nipple-point-incr, nipple-size-incr, breast-dist-incr, breast-volume-vert-up, breast-trans-up, buttocks-volume-incr, l-cheek-trans-up, r-cheek-trans-up, l-cheek-volume-incr, r-cheek-volume-incr, l-cheek-inner-incr, r-cheek-inner-incr, l-cheek-bones-incr, r-cheek-bones-incr, chin-jaw-drop-incr, chin-height-incr, chin-prominent-incr, chin-bones-incr, chin-width-incr, chin-cleft-incr, chin-prognathism-incr, l-ear-lobe-incr, r-ear-lobe-incr, l-ear-shape-round, r-ear-shape-round, l-ear-trans-forward, r-ear-trans-forward, l-ear-rot-forward, r-ear-rot-forward, l-ear-trans-up, r-ear-trans-up, l-ear-scale-vert-incr, r-ear-scale-vert-incr, l-ear-flap-incr, r-ear-flap-incr, l-ear-shape-triangle, r-ear-shape-triangle, l-ear-scale-incr, r-ear-scale-incr, l-ear-scale-depth-incr, r-ear-scale-depth-incr, l-ear-wing-incr, r-ear-wing-incr, eyebrows-angle-up, eyebrows-trans-up, eyebrows-trans-forward, l-eye-corner1-up, r-eye-corner1-up, l-eye-height1-incr, r-eye-height1-incr, l-eye-scale-incr, r-eye-scale-incr, l-eye-push1-out, r-eye-push1-out, l-eye-eyefold-up, r-eye-eyefold-up, l-eye-height3-incr, r-eye-height3-incr, l-eye-height2-incr, r-eye-height2-incr, l-eye-bag-incr, r-eye-bag-incr, l-eye-push2-out, r-eye-push2-out, l-eye-trans-up, r-eye-trans-up, l-eye-bag-height-incr, r-eye-bag-height-incr, l-eye-epicanthus-out, r-eye-epicanthus-out, l-eye-eyefold-angle-up, r-eye-eyefold-angle-up, l-eye-bag-out, r-eye-bag-out, l-eye-corner2-up, r-eye-corner2-up, l-eye-trans-out, r-eye-trans-out, l-foot-scale-horiz-incr, r-foot-scale-horiz-incr, l-foot-trans-out, r-foot-trans-out, l-foot-scale-vert-incr, r-foot-scale-vert-incr, l-foot-trans-forward, r-foot-trans-forward, l-foot-scale-depth-incr, r-foot-scale-depth-incr, l-foot-scale-incr, r-foot-scale-incr, l-foot-trans-up, r-foot-trans-up, measure-ankle-circ-incr, forehead-temple-incr, forehead-trans-forward, forehead-scale-vert-incr, forehead-nubian-incr, l-hand-trans-out, r-hand-trans-out, l-hand-fingers-length-incr, r-hand-fingers-length-incr, l-hand-scale-incr, r-hand-scale-incr, l-hand-fingers-diameter-incr, r-hand-fingers-diameter-incr, l-hand-fingers-distance-incr, r-hand-fingers-distance-incr, measure-wrist-circ-incr, head-angle-out, head-scale-vert-incr, head-trans-forward, head-trans-up, head-fat-incr, head-trans-out, head-back-scale-depth-incr, head-age-incr, head-scale-depth-incr, head-scale-horiz-incr, hip-trans-out, hip-scale-horiz-incr, hip-scale-depth-incr, hip-waist-up, hip-trans-forward, hip-scale-vert-incr, hip-trans-up, measure-calf-circ-incr, measure-lowerleg-height-incr, l-upperleg-fat-incr, r-upperleg-fat-incr, l-lowerleg-muscle-incr, r-lowerleg-muscle-incr, l-leg-valgus-incr, r-leg-valgus-incr, l-lowerleg-scale-depth-incr, r-lowerleg-scale-depth-incr, l-lowerleg-scale-vert-incr, r-lowerleg-scale-vert-incr, l-upperleg-scale-depth-incr, r-upperleg-scale-depth-incr, l-upperleg-scale-horiz-incr, r-upperleg-scale-horiz-incr, upperlegs-height-incr, l-lowerleg-fat-incr, r-lowerleg-fat-incr, l-lowerleg-scale-horiz-incr, r-lowerleg-scale-horiz-incr, lowerlegs-height-incr, measure-upperleg-height-incr, l-upperleg-muscle-incr, r-upperleg-muscle-incr, measure-knee-circ-incr, measure-thigh-circ-incr, l-upperleg-scale-vert-incr, r-upperleg-scale-vert-incr, mouth-lowerlip-height-incr, mouth-scale-horiz-incr, mouth-cupidsbow-incr, mouth-laugh-lines-out, mouth-scale-depth-incr, mouth-lowerlip-volume-incr, mouth-angles-up, mouth-upperlip-ext-up, mouth-lowerlip-width-incr, mouth-trans-forward, mouth-upperlip-width-incr, mouth-scale-vert-incr, mouth-dimples-out, mouth-upperlip-height-incr, mouth-upperlip-volume-incr, mouth-cupidsbow-width-incr, mouth-lowerlip-ext-up, mouth-upperlip-middle-up, mouth-trans-up, mouth-trans-out, mouth-lowerlip-middle-up, mouth-philtrum-volume-incr, measure-neck-circ-incr, measure-neck-height-incr, neck-scale-depth-incr, neck-trans-forward, neck-double-incr, neck-trans-out, neck-back-scale-depth-incr, neck-scale-horiz-incr, neck-scale-vert-incr, neck-trans-up, nose-nostrils-angle-up, nose-flaring-incr, nose-curve-convex, nose-scale-depth-incr, nose-scale-vert-incr, nose-point-width-incr, nose-scale-horiz-incr, nose-width1-incr, nose-trans-forward, nose-trans-up, nose-hump-incr, nose-nostrils-width-incr, nose-trans-out, nose-greek-incr, nose-point-up, nose-width2-incr, nose-width3-incr, nose-septumangle-incr, nose-base-up, nose-compression-uncompress, nose-volume-incr, bulge-incr, pelvis-tone-incr, stomach-pregnant-incr, stomach-navel-out, stomach-navel-up, stomach-tone-incr, torso-trans-forward, torso-muscle-pectoral-incr, measure-underbust-circ-incr, torso-scale-horiz-incr, torso-scale-depth-incr, measure-napetowaist-dist-incr, measure-waisttohip-dist-incr, measure-shoulder-dist-incr, measure-hips-circ-incr, torso-vshape-incr, torso-trans-up, torso-muscle-dorsi-incr, measure-waist-circ-incr, measure-frontchest-dist-incr, torso-scale-vert-incr, measure-bust-circ-incr, torso-trans-out
Local change example¶
We show here the effect of the stomach-pregnant-incr local change parameter, as an example.
batch_size = 3 # We can process multiple faces at once in a batch.
pose_parameters = roma.Rigid.identity(dim=3, batch_shape=(batch_size, anny_model.bone_count)).to_homogeneous()
phenotype_kwargs = {key : torch.full((batch_size,), fill_value=0.5) for key in anny_model.phenotype_labels}
# In this example, we start from a stereotypical young adult woman mesh
phenotype_kwargs['age'].fill_(0.67)
phenotype_kwargs['gender'].fill_(1.)
local_changes = {'stomach-pregnant-incr': torch.linspace(0, 1., batch_size)} # Example: vary the upperarm fat increment across the batch.
output = anny_model(phenotype_kwargs=phenotype_kwargs, local_changes_kwargs=local_changes)
scene = trimesh.Scene()
for i in range(batch_size):
# Create a mesh for each face in the batch.
mesh = trimesh.Trimesh(vertices=output['vertices'][i].squeeze().cpu().numpy(), faces=anny_model.faces.cpu().numpy())
transform = roma.Rigid(linear=None, translation=torch.tensor([i * 1., 0., 0.])).to_homogeneous().cpu().numpy()
scene.add_geometry(mesh, transform=transform)
scene.apply_transform(trimesh_scene_transform) # Rotate the scene to have a better view.
scene.show() # This will open a window to visualize the scene with all the faces in
Phenotype distribution¶
import anny.shape_distribution
import anny.anthropometry
import matplotlib.pyplot as plt
phenotype_distribution = anny.shape_distribution.SimpleShapeDistribution(anny_model,
morphological_age_distribution=torch.distributions.Uniform(low=0.0, high=60.0))
real_age, phenotype_kwargs = phenotype_distribution.sample(batch_size=200)
output = anny_model(phenotype_kwargs=phenotype_kwargs)
scene = trimesh.Scene()
i = -1
for u in range(4):
for v in range(5):
i += 1
assert i < output['vertices'].shape[0], "Batch size is too small for the grid."
# Create a mesh for each face in the batch.
mesh = trimesh.Trimesh(vertices=output['vertices'][i].squeeze().cpu().numpy(), faces=anny_model.faces.cpu().numpy())
transform = roma.Rigid(linear=None, translation=torch.tensor([v * 1., u * 1., 0.], dtype=dtype, device=device)).to_homogeneous().cpu().numpy()
scene.add_geometry(mesh, transform=transform)
scene.apply_transform(trimesh_scene_transform) # Rotate the scene to have a better view.
scene.show() # This will open a window to visualize the scene with all the faces in
Body measures¶
We additionnally provide a class to estimate some anthropometric measurements, assuming a body buoyancy of .98 in water.
real_age, phenotype_kwargs = phenotype_distribution.sample(batch_size=1000)
output = anny_model(phenotype_kwargs=phenotype_kwargs)
measurements = anny.anthropometry.Anthropometry(anny_model)
measures = measurements(output['rest_vertices'])
fig, axes = plt.subplots(1,3, squeeze=True, figsize=(10, 5))
axes[0].scatter(real_age.cpu().numpy(), measures['height'].cpu().numpy())
axes[0].set_xlabel('Morphological age (year)')
axes[0].set_ylabel('Height (m)')
axes[1].scatter(real_age.cpu().numpy(), measures['waist_circumference'].cpu().numpy())
axes[1].set_xlabel('Morphological age (year)')
axes[1].set_ylabel('Waist circumference (m)')
axes[2].scatter(real_age.cpu().numpy(), measures['bmi'].cpu().numpy())
axes[2].set_xlabel('Morphological age (year)')
axes[2].set_ylabel('Body Mass Index estimate')
fig.tight_layout()
plt.show()