In [1]:
# Anny
# Copyright (C) 2025 NAVER Corp.
# Apache License, Version 2.0
Playing with texture coordinates¶
Basic imports
In [2]:
import numpy as np
import torch
import anny
import PIL.Image
import PIL.ImageDraw
from anny.paths import get_anny_root_dir
import trimesh
import yaml
from IPython.display import display
Instanciate the body model.
In [3]:
anny_model = anny.Anny()
trimesh.Trimesh(
anny_model.template_vertices.cpu().numpy(), faces=anny_model.faces.cpu().numpy()
).show()
Warp 1.13.0 initialized:
CUDA Toolkit 12.9, Driver 12.9
Devices:
"cpu" : "x86_64"
"cuda:0" : "NVIDIA A100 80GB PCIe" (79 GiB, sm_80, mempool enabled)
Kernel cache:
some_path
Out[3]:
Each vertex of each face of the model is associated with some 2D ST texture coordinates. It enables to unwrap the mesh onto a 2D image, as illustrated here.
In [4]:
# Create an empty image with white background
width, height = 1024, 1024
uv_unwrap_image = PIL.Image.new("RGB", (width, height), (0, 0, 0))
# Draw face contours on the texture image
faces = anny_model.faces.cpu().numpy()
face_texture_coordinates_indices = anny_model.face_texture_coordinate_indices.numpy()
st = anny_model.texture_coordinates.numpy()
vertex_absolute_texture_coordinates = (
np.array([0, height])[None] + st * np.array([width, -height])[None]
)
draw = PIL.ImageDraw.Draw(uv_unwrap_image)
for face_texture_ids in face_texture_coordinates_indices:
u0, v0 = vertex_absolute_texture_coordinates[face_texture_ids[-1]]
for i in face_texture_ids:
u, v = vertex_absolute_texture_coordinates[i]
draw.line(((u0, v0), (u, v)), fill=(128, 128, 128), width=1)
u0, v0 = u, v # Update the starting point for the next line
display(uv_unwrap_image)
Body part segmentation¶
We provide a basic segmentation of the mesh of Anny into different semantic body parts.
In [5]:
path = get_anny_root_dir() / "data/segmentation/body_parts_segmentation.png"
body_parts_segmentation_image = PIL.Image.open(path).convert("RGB")
overlay_image = body_parts_segmentation_image.copy()
mask = PIL.Image.fromarray(np.all(np.asarray(uv_unwrap_image) != 0, axis=-1))
overlay_image.paste(uv_unwrap_image, mask=mask)
display(overlay_image)
with open(
get_anny_root_dir() / "data/segmentation/body_parts_segmentation.yaml", "r"
) as f:
body_parts_segmentation = yaml.safe_load(f)
display(f"Body parts: {list(body_parts_segmentation['colors'].keys())}")
"Body parts: ['body', 'head', 'hand.R', 'hand.L', 'eye_back.L', 'eye_front.L', 'eye_back.R', 'eye_front.R', 'eye_cavity.R', 'eye_cavity.L', 'mouth_cavity', 'foot.L', 'foot.R', 'tongue']"
3D visualization¶
Note: we need to duplicate vertices as trimesh expects one texture coordinate per vertex.
In [6]:
vertices = anny_model.template_vertices.detach().cpu().numpy()
faces = faces
uv = anny_model.texture_coordinates.cpu().numpy()
duplicated_vertices = vertices[faces.flatten()]
duplicated_faces = np.arange(3 * len(faces)).reshape(-1, 3)
duplicated_uvs = uv[anny_model.face_texture_coordinate_indices.cpu().numpy().flatten()]
mesh = trimesh.Trimesh(
vertices=duplicated_vertices,
faces=duplicated_faces,
process=False,
maintain_order=True,
)
material = trimesh.visual.material.PBRMaterial(
baseColorFactor=np.ones(4),
baseColorTexture=body_parts_segmentation_image,
metallicFactor=0.5,
doubleSided=True,
)
import trimesh.visual
mesh.visual = trimesh.visual.texture.TextureVisuals(
uv=duplicated_uvs, material=material
)
mesh.show()
Out[6]:
In [7]:
# Retrieve the central color of each face
body_parts_segmentation_array = np.asarray(body_parts_segmentation_image)
face_center_texture_coordinates = anny_model.texture_coordinates[
anny_model.face_texture_coordinate_indices
].mean(dim=1)
u = (
torch.round(
face_center_texture_coordinates[:, 0] * body_parts_segmentation_array.shape[1]
)
.to(dtype=torch.int64)
.clamp_max(body_parts_segmentation_array.shape[0] - 1)
.detach()
.cpu()
.numpy()
)
v = (
torch.round(
(1 - face_center_texture_coordinates[:, 1])
* body_parts_segmentation_array.shape[0]
)
.to(dtype=torch.int64)
.clamp_max(body_parts_segmentation_array.shape[1] - 1)
.detach()
.cpu()
.numpy()
)
face_colors = body_parts_segmentation_array[v, u]
In [8]:
# Segment the head based on face colors
face_mask = np.zeros(len(faces), dtype=bool)
labels = [
"head",
"eye_cavity.R",
"eye_cavity.L",
"mouth_cavity",
"eye_front.L",
"eye_back.L",
"eye_front.R",
"eye_back.L",
"tongue",
]
for label in labels:
face_mask |= np.all(
face_colors == np.asarray(body_parts_segmentation["colors"][label]), axis=-1
)
trimesh.Trimesh(
vertices=vertices,
faces=faces[face_mask],
).show()
Out[8]: