Convert SEED / SOMA (MHR) BVH motion capture into AMASS-style SMPL-X .npz files.
SOMA is a canonical 78-joint rig, not SMPL-X, so this is a retarget rather than a format rewrite:
BVH -> SOMA rig -> SMPL-X surface -> SMPL-X parameters -> AMASS npz
- Parse the BVH into SOMA joint rotations (77 axis-angle, absolute poses) plus the Hips translation.
SOMALayer(mhr).prepare_identity(actor).pose(absolute_pose=True)→ a mesh in SOMA topology.SMPLFamilyTopologyBridge→ the same surface in SMPL-X topology (10 475 vertices).smplfitter.BodyFitter.fit()→ SMPL-X{pose, betas, trans}.- Rotate from SOMA's +y-up frame into AMASS's +z-up frame and write the AMASS layout.
# PyTorch first, matching your CUDA (SOMA-X needs CUDA for its Warp kernels)
pip install torch --index-url https://download.pytorch.org/whl/cu128
pip install -r requirements.txtpy-soma-x downloads its rig assets from the Hugging Face model nvidia/soma-x on first
use; pass --soma_data_root to point at an existing local snapshot instead.
| What | Where it goes | Notes |
|---|---|---|
SMPLX_NEUTRAL.npz |
--smplx_model_path |
from the SMPL-X project (registration required) |
body_models/smplx/SMPLX_NEUTRAL.* |
--smplx_model_root |
model root used by smplfitter |
| SEED BVH files | --src |
e.g. the extracted soma_proportional/bvh/{date}/*.bvh |
| Per-actor SOMA shapes | --shape_dir |
A<id>.npz files, matched from the __A<id> tag in each filename |
These model/data files are not redistributed here — obtain them from their original sources and follow their licences (see References).
python soma_to_amass.py \
--src /path/to/soma_proportional/bvh \
--dst /path/to/output_amass \
--shape_dir /path/to/soma_shapes/soma_proportion_fit_mhr_params \
--smplx_model_path /path/to/SMPLX_NEUTRAL.npz \
--smplx_model_root /path/to/body_models/smplx \
--target_fps 30Relative paths under --src are preserved in --dst. Re-running skips outputs that
already exist, so an interrupted run resumes.
| Flag | Default | Meaning |
|---|---|---|
--src_fps |
120 |
source frame rate (SEED BVH is 120 fps) |
--target_fps |
(none) | downsample target; omitted / >= source keeps the source rate (never upsamples) |
--num_betas |
10 |
SMPL-X shape coefficients to fit and store |
--fit_iters |
1 |
smplfitter iterations (more does not help this cross-rig fit) |
--frame_batch |
1000 |
frames per chunk through SOMA/bridge/fit — lower it if you run out of VRAM |
--min_seg |
10 |
shortest valid segment (in source frames) kept when a clip is split around corrupt NaN gaps — see Corrupt (NaN) frames in SEED |
--include_mirror |
off | also convert mirrored ..._M.bvh clips |
--soma_data_root |
(auto) | local SOMA-X asset snapshot instead of downloading |
--device |
cuda |
SOMA-X requires CUDA |
One .npz per clip, using the AMASS SMPL-X field layout:
| Field | Shape | Notes |
|---|---|---|
gender |
— | 'neutral' (the fit targets the neutral SMPL-X body) |
surface_model_type |
— | 'smplx' |
mocap_frame_rate |
— | output fps |
betas |
(num_betas,) |
mean of the per-frame fitted betas |
root_orient |
(T, 3) |
global orientation, axis-angle |
pose_body |
(T, 63) |
21 body joints |
pose_hand |
(T, 90) |
left (45) + right (45) |
trans |
(T, 3) |
metres |
Coordinate frame: +z up, metres — matching AMASS. SOMA retargets to a +y-up
SMPL-X frame, so a Rot_x(90°) is applied. Because SMPL's global rotation acts about the
pelvis rather than the origin, the root is mapped as
R' = C · R C = Rot_x(90°)
t' = C · (t + j0) − j0 j0 = pelvis rest joint from betas
The SEED release contains corrupt motion data, and any converter reading it has to deal with this — otherwise the corruption silently spreads into the whole output file.
Measured on the soma_proportional set: 353 of 71 132 non-mirrored BVH files (≈0.5 %)
contain literal nan tokens in their MOTION block. The corruption comes in two shapes:
- a trailing run of NaN frames (a truncated export), and — more awkwardly —
- a large interior block with perfectly valid motion on both sides. One measured
example,
230301/dancing_routine_V002_003__A232.bvh, has 292 consecutive NaN frames (frames 708–999 of 1592, ≈2.4 s at 120 fps) sandwiched between good motion.
Affected files cluster by capture session / actor rather than being scattered at random (e.g. an entire door-interaction session, and several object-manipulation sessions).
The SMPL-X fit uses share_beta=True, i.e. a single shape vector is solved for the
whole clip. A NaN in any frame propagates into that shared solve, so the fitted betas
come out NaN — and since the root translation is derived from betas (via the pelvis rest
joint j0), every frame of the output becomes NaN, not just the corrupt ones. Dropping
only the bad frames is therefore not optional; it has to happen before fitting.
parse_bvhreturns a per-frame finite mask instead of silently passing NaN through.- The clip is split into contiguous all-finite segments. Trailing NaN becomes a clean truncation; an interior gap yields two independent segments, so the good motion on both sides is kept and the two halves are never spliced together (which would fabricate a jump and skew all timing after the gap).
- Segments shorter than
--min_segsource frames (default10) are discarded — a handful of frames around a corruption boundary is not a usable motion, and it would otherwise produce near-empty files and an ill-conditioned shape fit. This is what--min_segis for. Raise it if you only want substantial clips; lower it to keep short fragments. - Multi-segment clips are written as
<name>__seg00.npz,<name>__seg01.npz, …; a clip that needed no splitting keeps its plain name. A clip with no usable segment is skipped. - As a backstop, a fitted result that still contains NaN is never written.
- Every affected file is recorded in
<dst>/_nan_report.csvwith columnskind,file,dropped_frames,kept_frames,n_segments, wherekindis one oftruncated/split/unusable/nan_in_fit.
Timing stays correct: frames are uniformly spaced within each segment, and the resample
to --target_fps is applied per segment.
- Absolute poses. SEED BVH joint rotations are absolute, not relative to the canonical
T-pose, so the SOMA layer is driven with
absolute_pose=True. Getting this wrong yields a folded, lying-down body. - Global translation lives on the Hips position channels (the
Rootjoint stays at 0), so every position channel on the root chain is summed. - Corrective shapes are off. The SOMA↔SMPL-X residual is ~23 mm mean surface distance and is intrinsic to the two body models — it is not reduced by more fit iterations, more betas, or correctives. Pose and root trajectory are captured well, which is what a motion dataset needs.
- Corrupt NaN frames in the source are split out rather than propagated — this is a real property of the SEED release, see Corrupt (NaN) frames in SEED.
- Mirrored clips (
..._M.bvh) are skipped by default, since left/right mirroring is usually applied uniformly as a later augmentation step.
This tool only glues together other people's work. If you use it, please cite the underlying datasets, models and libraries:
The output follows the AMASS SMPL-X field layout and +z-up convention.
Naureen Mahmood, Nima Ghorbani, Nikolaus F. Troje, Gerard Pons-Moll, Michael J. Black. AMASS: Archive of Motion Capture as Surface Shapes. ICCV 2019. https://amass.is.tue.mpg.de/
@inproceedings{AMASS2019,
title = {{AMASS}: Archive of Motion Capture as Surface Shapes},
author = {Mahmood, Naureen and Ghorbani, Nima and Troje, Nikolaus F.
and Pons-Moll, Gerard and Black, Michael J.},
booktitle = {ICCV},
year = {2019}
}The input motions are the SEED capture set, distributed as SOMA/MHR-skeleton BVH. Use of the data is governed by the SEED dataset's own licence and terms — obtain it from its official distribution and cite it as its authors request. (Add the official SEED citation here for your fork; it is intentionally not guessed at in this README.)
The SOMA (MHR) rig, the SMPL-X layer and the topology bridge come from NVIDIA's SOMA-X.
- Code: https://github.com/NVlabs/SOMA-X (
pip install "py-soma-x[smpl]") - Rig assets: https://huggingface.co/nvidia/soma-x
Please follow the citation and licence stated in the SOMA-X repository.
Fitting SMPL-X parameters (including betas) to the bridged surface is done with
smplfitter by István Sárándi.
Please use the citation given in that repository.
Georgios Pavlakos, Vasileios Choutas, Nima Ghorbani, Timo Bolkart, Ahmed A. A. Osman, Dimitrios Tzionas, Michael J. Black. Expressive Body Capture: 3D Hands, Face, and Body from a Single Image. CVPR 2019. https://smpl-x.is.tue.mpg.de/
@inproceedings{SMPL-X:2019,
title = {Expressive Body Capture: {3D} Hands, Face, and Body from a Single Image},
author = {Pavlakos, Georgios and Choutas, Vasileios and Ghorbani, Nima
and Bolkart, Timo and Osman, Ahmed A. A. and Tzionas, Dimitrios
and Black, Michael J.},
booktitle = {CVPR},
year = {2019}
}The SMPL-X model files are distributed under the SMPL-X licence and are not included in this repository.
The code in this repository is provided as-is. The datasets and body models it consumes (SEED, SOMA-X assets, SMPL-X) each carry their own, more restrictive licences — this repository neither redistributes nor relicenses them.