Skip to content

SOMA

SOMA implements SOMA-X identity, pose, and corrective controls without requiring py-soma-x.

Setup

SOMA downloads automatically on first use from the abcamiletto/body-models Hugging Face repository, which records the SOMA-X Apache 2.0 provenance. To prefetch:

body-models download soma

The loader requires normalized assets, as provided by the hosted archive. To normalize upstream SOMA-X 0.2.1 assets and save their path:

body-models preprocess-soma /path/to/upstream /path/to/processed

SOMA exposes 77 public joints and uses an internal twist-joint rig for skinning. The lod options "mid", "low", and "xlo" have 18,056, 4,505, and 612 vertices, respectively.

prepare_identity() defaults to repose=True, bind_pose="fit", matching SOMA-X. Use repose=False to retain the fitted rest shape and skeleton, bind_pose="fit_detached" to stop gradients through fitting, or bind_pose="canonical" for the canonical bind pose.

API

body_models.soma.numpy.SOMA

SOMA(
    *,
    model_path=None,
    model_type="soma",
    lod="mid",
    rotation_type="axis_angle",
    simplify=1.0,
)

Bases: body_models.soma._model.SOMA

Native SOMA-X model with identity, pose, and corrective controls.

METHOD DESCRIPTION
apply_pose_correctives

Apply prepared pose correctives to identity-dependent rest vertices.

forward_points

Compute positions defined by a prepared vertex mapping.

forward_skeleton

Compute posed public-joint transforms in meters.

forward_vertices

Compute posed mesh vertices in meters.

get_rest_pose

Return zero pose controls and the model's default identity.

joint_index

Resolve a common joint to this model's native joint index.

prepare_point_regressor

Preproject a vertex mapping for repeated point forwards.

get_apose

Return the SOMA A-pose.

get_tpose

Return the SOMA T-pose.

prepare_identity

Precompute identity-dependent state for repeated forward passes.

prepare_pose

Precompute pose-dependent state for repeated forward passes.

ATTRIBUTE DESCRIPTION
common_joints

Common anatomical joints mapped to this model's native joint names.

has_face

bool(x) -> bool

has_hands

bool(x) -> bool

num_joints

Number of joints in the skeleton.

pose_joint_indices

Canonical joints whose local transforms are driven by each pose parameter.

runtime

Array runtime used by this model.

skinning_spec

Static topology, render-rig weights, and optional pose correctives.

symmetric_joints

Left/right joint pairs as (left_index, right_index), in joint order.

NUM_BODY_CONTROLS

int([x]) -> integer

NUM_HAND_CONTROLS

int([x]) -> integer

NUM_HEAD_CONTROLS

int([x]) -> integer

NUM_JOINTS

int([x]) -> integer

common_joints property

common_joints

Common anatomical joints mapped to this model's native joint names.

has_face class-attribute

has_face = False

bool(x) -> bool

Returns True when the argument x is true, False otherwise. The builtins True and False are the only two instances of the class bool. The class bool is a subclass of the class int, and cannot be subclassed.

has_hands class-attribute

has_hands = True

bool(x) -> bool

Returns True when the argument x is true, False otherwise. The builtins True and False are the only two instances of the class bool. The class bool is a subclass of the class int, and cannot be subclassed.

num_joints property

num_joints

Number of joints in the skeleton.

pose_joint_indices property

pose_joint_indices

Canonical joints whose local transforms are driven by each pose parameter.

runtime property

runtime

Array runtime used by this model.

skinning_spec property

skinning_spec

Static topology, render-rig weights, and optional pose correctives.

symmetric_joints property

symmetric_joints

Left/right joint pairs as (left_index, right_index), in joint order.

Indices address the J axis of :meth:forward_skeleton outputs and cover the whole native skeleton, including joints outside the :class:Joint vocabulary. Unpaired joints lie on the midline. Pairs describe index correspondence only, not how to mirror a pose.

RAISES DESCRIPTION
ValueError

If a sided joint name has no counterpart.

NUM_BODY_CONTROLS class-attribute

NUM_BODY_CONTROLS = 23

int([x]) -> integer int(x, base=10) -> integer

Convert a number or string to an integer, or return 0 if no arguments are given. If x is a number, return x.int(). For floating point numbers, this truncates towards zero.

If x is not a number or if base is given, then x must be a string, bytes, or bytearray instance representing an integer literal in the given base. The literal can be preceded by '+' or '-' and be surrounded by whitespace. The base defaults to 10. Valid bases are 0 and 2-36. Base 0 means to interpret the base from the string as an integer literal.

int('0b100', base=0) 4

NUM_HAND_CONTROLS class-attribute

NUM_HAND_CONTROLS = 48

int([x]) -> integer int(x, base=10) -> integer

Convert a number or string to an integer, or return 0 if no arguments are given. If x is a number, return x.int(). For floating point numbers, this truncates towards zero.

If x is not a number or if base is given, then x must be a string, bytes, or bytearray instance representing an integer literal in the given base. The literal can be preceded by '+' or '-' and be surrounded by whitespace. The base defaults to 10. Valid bases are 0 and 2-36. Base 0 means to interpret the base from the string as an integer literal.

int('0b100', base=0) 4

NUM_HEAD_CONTROLS class-attribute

NUM_HEAD_CONTROLS = 5

int([x]) -> integer int(x, base=10) -> integer

Convert a number or string to an integer, or return 0 if no arguments are given. If x is a number, return x.int(). For floating point numbers, this truncates towards zero.

If x is not a number or if base is given, then x must be a string, bytes, or bytearray instance representing an integer literal in the given base. The literal can be preceded by '+' or '-' and be surrounded by whitespace. The base defaults to 10. Valid bases are 0 and 2-36. Base 0 means to interpret the base from the string as an integer literal.

int('0b100', base=0) 4

NUM_JOINTS class-attribute

NUM_JOINTS = 77

int([x]) -> integer int(x, base=10) -> integer

Convert a number or string to an integer, or return 0 if no arguments are given. If x is a number, return x.int(). For floating point numbers, this truncates towards zero.

If x is not a number or if base is given, then x must be a string, bytes, or bytearray instance representing an integer literal in the given base. The literal can be preceded by '+' or '-' and be surrounded by whitespace. The base defaults to 10. Valid bases are 0 and 2-36. Base 0 means to interpret the base from the string as an integer literal.

int('0b100', base=0) 4

apply_pose_correctives

apply_pose_correctives(*, identity, pose)

Apply prepared pose correctives to identity-dependent rest vertices.

Source code in src/body_models/_base.py
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
def apply_pose_correctives(
    self,
    *,
    identity: SkinningIdentity,
    pose: SkinningPose,
) -> Float[Array, "*batch V 3"]:
    """Apply prepared pose correctives to identity-dependent rest vertices."""
    vertices = identity["rest_vertices"]
    coefficients = pose.get("pose_coefficients")
    if coefficients is None:
        return vertices
    basis = self._corrective_basis
    if basis is None:
        raise RuntimeError("Prepared pose has corrective coefficients, but the model has no corrective basis.")
    return vertices + basis.apply(coefficients)

forward_points

forward_points(
    body_pose,
    head_pose,
    hand_pose,
    *,
    point_regressor,
    shape=None,
    scale_params=None,
    identity=None,
    global_rotation=None,
    global_translation=None,
)

Compute positions defined by a prepared vertex mapping.

Source code in src/body_models/soma/_model.py
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
def forward_points(
    self,
    body_pose: Float[Array, "*batch 23 N"] | Float[Array, "*batch 23 3 3"],
    head_pose: Float[Array, "*batch 5 N"] | Float[Array, "*batch 5 3 3"],
    hand_pose: Float[Array, "*batch 48 N"] | Float[Array, "*batch 48 3 3"],
    *,
    point_regressor: PointRegressor,
    shape: Float[Array, "*batch I"] | None = None,
    scale_params: Float[Array, "*batch K"] | None = None,
    identity: SomaIdentity | None = None,
    global_rotation: Float[Array, "*batch N"] | Float[Array, "*batch 3 3"] | None = None,
    global_translation: Float[Array, "*batch 3"] | None = None,
) -> Float[Array, "*batch P 3"]:
    """Compute positions defined by a prepared vertex mapping."""
    xp = self._runtime.xp
    self._validate_identity_arguments(identity, shape=shape, scale_params=scale_params)
    batch_shape = body_pose.shape[: -(self._num_rot_dims + 1)]
    if identity is None:
        resolved = self._resolve_identity_coefficients(batch_shape, shape=shape)
        if scale_params is not None:
            scale_params = xp.broadcast_to(scale_params, (*batch_shape, scale_params.shape[-1]))
        identity = self.prepare_identity(*resolved, scale_params=scale_params)

    pose = self.prepare_pose(body_pose, head_pose, hand_pose, identity=identity)
    return self._deform_points(point_regressor, identity, pose, global_rotation, global_translation)

forward_skeleton

forward_skeleton(
    body_pose,
    head_pose,
    hand_pose,
    *,
    shape=None,
    scale_params=None,
    identity=None,
    global_rotation=None,
    global_translation=None,
    joint_indices=None,
)

Compute posed public-joint transforms in meters.

Source code in src/body_models/soma/_model.py
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
def forward_skeleton(
    self,
    body_pose: Float[Array, "*batch 23 N"] | Float[Array, "*batch 23 3 3"],
    head_pose: Float[Array, "*batch 5 N"] | Float[Array, "*batch 5 3 3"],
    hand_pose: Float[Array, "*batch 48 N"] | Float[Array, "*batch 48 3 3"],
    *,
    shape: Float[Array, "*batch I"] | None = None,
    scale_params: Float[Array, "*batch K"] | None = None,
    identity: SomaIdentity | None = None,
    global_rotation: Float[Array, "*batch N"] | Float[Array, "*batch 3 3"] | None = None,
    global_translation: Float[Array, "*batch 3"] | None = None,
    joint_indices: Sequence[int] | None = None,
) -> Float[Array, "*batch 77 4 4"]:
    """Compute posed public-joint transforms in meters."""
    xp = self._runtime.xp
    self._validate_identity_arguments(identity, shape=shape, scale_params=scale_params)
    batch_shape = body_pose.shape[: -(self._num_rot_dims + 1)]
    if identity is None:
        resolved = self._resolve_identity_coefficients(batch_shape, shape=shape)
        if scale_params is not None:
            scale_params = xp.broadcast_to(scale_params, (*batch_shape, scale_params.shape[-1]))
        skeleton_identity = self._prepare_skeleton_identity(*resolved, scale_params=scale_params)
    else:
        skeleton_identity = identity

    root_rotation = SO3.identity_as(
        body_pose,
        batch_dims=batch_shape,
        rotation_type=self.rotation_type,
        xp=xp,
    )
    pose = pose_utils.pack_pose(xp, root_rotation, body_pose, head_pose, hand_pose)
    skeleton = core.prepare_skeleton(
        self._runtime,
        self._assets,
        pose,
        self.rotation_type,
        local_joint_translations=skeleton_identity["local_joint_translations"],
        joint_indices=joint_indices,
    )
    return skinning.transform_skeleton(
        skeleton,
        global_rotation,
        global_translation,
        self.rotation_type,
        xp=xp,
    )

forward_vertices

forward_vertices(
    body_pose,
    head_pose,
    hand_pose,
    *,
    shape=None,
    scale_params=None,
    identity=None,
    global_rotation=None,
    global_translation=None,
    vertex_indices=None,
)

Compute posed mesh vertices in meters.

Source code in src/body_models/soma/_model.py
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
def forward_vertices(
    self,
    body_pose: Float[Array, "*batch 23 N"] | Float[Array, "*batch 23 3 3"],
    head_pose: Float[Array, "*batch 5 N"] | Float[Array, "*batch 5 3 3"],
    hand_pose: Float[Array, "*batch 48 N"] | Float[Array, "*batch 48 3 3"],
    *,
    shape: Float[Array, "*batch I"] | None = None,
    scale_params: Float[Array, "*batch K"] | None = None,
    identity: SomaIdentity | None = None,
    global_rotation: Float[Array, "*batch N"] | Float[Array, "*batch 3 3"] | None = None,
    global_translation: Float[Array, "*batch 3"] | None = None,
    vertex_indices: Sequence[int] | None = None,
) -> Float[Array, "*batch V 3"]:
    """Compute posed mesh vertices in meters."""
    xp = self._runtime.xp
    self._validate_identity_arguments(identity, shape=shape, scale_params=scale_params)
    batch_shape = body_pose.shape[: -(self._num_rot_dims + 1)]
    if identity is None:
        resolved = self._resolve_identity_coefficients(batch_shape, shape=shape)
        if scale_params is not None:
            scale_params = xp.broadcast_to(scale_params, (*batch_shape, scale_params.shape[-1]))
        identity = self.prepare_identity(*resolved, scale_params=scale_params)

    pose = self.prepare_pose(body_pose, head_pose, hand_pose, identity=identity)
    vertices = self._runtime._skin_vertices(
        self.apply_pose_correctives(identity=identity, pose=pose),
        pose["skinning_transforms"],
        skinning=self._assets.compact_skinning,
        vertex_indices=vertex_indices,
    )
    return skinning.apply_global_transform(
        vertices,
        global_rotation,
        global_translation,
        self.rotation_type,
        xp=xp,
    )

get_rest_pose

get_rest_pose(*, batch_dims=(), dtype=None, hands='default')

Return zero pose controls and the model's default identity.

Source code in src/body_models/soma/_model.py
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
def get_rest_pose(
    self,
    *,
    batch_dims: tuple[int, ...] = (),
    dtype: Any | None = None,
    hands: Literal["default", "flat", "rest"] = "default",
) -> dict[str, Float[Array, "..."]]:
    """Return zero pose controls and the model's default identity."""
    if hands not in ("default", "flat", "rest"):
        raise ValueError(f"Invalid hands: {hands!r}. Expected 'default', 'flat', or 'rest'.")

    params = super().get_rest_pose(batch_dims=batch_dims, dtype=dtype)
    if hands != "default":
        runtime = self.runtime
        axis_angle = runtime.asarray(SOMA_HAND_PRESETS[hands], like=params["hand_pose"]).reshape(-1, 3)
        axis_angle = runtime.xp.broadcast_to(axis_angle, (*batch_dims, *axis_angle.shape))
        params["hand_pose"] = SO3.convert(
            axis_angle,
            src="axis_angle",
            dst=self.rotation_type,
            xp=runtime.xp,
        )
    return params

joint_index

joint_index(joint)

Resolve a common joint to this model's native joint index.

Source code in src/body_models/_base.py
173
174
175
176
177
178
179
180
181
def joint_index(self, joint: Joint) -> int:
    """Resolve a common joint to this model's native joint index."""
    if not isinstance(joint, Joint):
        raise TypeError("joint_index() expects a body_models.Joint; use joint_names.index(...) for native names.")
    try:
        native_name = self.common_joints[joint]
    except KeyError as exc:
        raise KeyError(f"{self.__class__.__name__} has no common joint {joint.value!r}") from exc
    return self.joint_names.index(native_name)

prepare_point_regressor

prepare_point_regressor(mapping)

Preproject a vertex mapping for repeated point forwards.

For Torch, call this after moving the model to its target device.

Source code in src/body_models/_base.py
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
def prepare_point_regressor(
    self,
    mapping: Float[Array, "K V"],
) -> PointRegressor:
    """Preproject a vertex mapping for repeated point forwards.

    For Torch, call this after moving the model to its target device.
    """
    if mapping.ndim != 2 or mapping.shape[0] < 1 or mapping.shape[1] != self.num_vertices:
        raise ValueError(
            f"mapping must have shape [K, {self.num_vertices}] with K >= 1, got {tuple(mapping.shape)}"
        )
    mapping = self._runtime.asarray(mapping, like=self.rest_vertices)
    return point_regression.prepare_point_regressor(
        mapping,
        self._skinning_weights,
        self._corrective_basis,
        runtime=self._runtime,
    )

get_apose

get_apose(*, batch_dims=(), dtype=None, hands='default')

Return the SOMA A-pose.

Source code in src/body_models/soma/_model.py
415
416
417
418
419
420
421
422
423
424
425
426
427
428
def get_apose(
    self,
    *,
    batch_dims: tuple[int, ...] = (),
    dtype: Any | None = None,
    hands: Literal["default", "flat", "rest"] = "default",
) -> dict[str, Float[Array, "..."]]:
    """Return the SOMA A-pose."""
    params = self.get_rest_pose(batch_dims=batch_dims, dtype=dtype, hands=hands)
    xp = self._runtime.xp
    axis_angle = self._runtime.asarray(SOMA_BODY_PRESETS["a_pose"], like=params["body_pose"])
    axis_angle = xp.broadcast_to(axis_angle, (*batch_dims, *axis_angle.shape))
    params["body_pose"] = SO3.convert(axis_angle, src="axis_angle", dst=self.rotation_type, xp=xp)
    return params

get_tpose

get_tpose(*, batch_dims=(), dtype=None, hands='default')

Return the SOMA T-pose.

Source code in src/body_models/soma/_model.py
405
406
407
408
409
410
411
412
413
def get_tpose(
    self,
    *,
    batch_dims: tuple[int, ...] = (),
    dtype: Any | None = None,
    hands: Literal["default", "flat", "rest"] = "default",
) -> dict[str, Float[Array, "..."]]:
    """Return the SOMA T-pose."""
    return self.get_rest_pose(batch_dims=batch_dims, dtype=dtype, hands=hands)

prepare_identity

prepare_identity(
    shape, *, scale_params=None, repose=True, bind_pose="fit"
)

Precompute identity-dependent state for repeated forward passes.

Source code in src/body_models/soma/_model.py
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
def prepare_identity(
    self,
    shape: Float[Array, "*batch I"],
    *,
    scale_params: Float[Array, "*batch K"] | None = None,
    repose: bool = True,
    bind_pose: core.BindPoseMode = "fit",
) -> SomaIdentity:
    """Precompute identity-dependent state for repeated forward passes."""
    rest_shape_full, rest_shape_active = self._rest_shapes(shape, scale_params)
    return core.prepare_identity_from_rest_shape(
        self._runtime,
        data=self._assets,
        rest_shape_full=rest_shape_full,
        rest_shape_active=rest_shape_active,
        repose=repose,
        bind_pose=bind_pose,
    )

prepare_pose

prepare_pose(body_pose, head_pose, hand_pose, *, identity)

Precompute pose-dependent state for repeated forward passes.

Source code in src/body_models/soma/_model.py
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
def prepare_pose(
    self,
    body_pose: Float[Array, "*batch 23 N"] | Float[Array, "*batch 23 3 3"],
    head_pose: Float[Array, "*batch 5 N"] | Float[Array, "*batch 5 3 3"],
    hand_pose: Float[Array, "*batch 48 N"] | Float[Array, "*batch 48 3 3"],
    *,
    identity: SomaIdentity,
) -> SkinningPose:
    """Precompute pose-dependent state for repeated forward passes."""
    xp = self._runtime.xp
    batch_shape = body_pose.shape[: -(self._num_rot_dims + 1)]
    root_rotation = SO3.identity_as(
        body_pose,
        batch_dims=batch_shape,
        rotation_type=self.rotation_type,
        xp=xp,
    )
    pose = pose_utils.pack_pose(xp, root_rotation, body_pose, head_pose, hand_pose)
    return core.prepare_pose(
        self._runtime,
        self._assets,
        pose,
        rotation_type=self.rotation_type,
        local_joint_translations=identity["local_joint_translations"],
        inverse_bind_transforms=identity["inverse_bind_transforms"],
    )