This repository provides an end-to-end pipeline that takes an AMASS .npz file and produces a single unified .csv with:
- DOF positions
- DOF velocities
- DOF accelerations
- DOF torques
- Ground reaction forces (per foot + total)
- Binary contact codes (
0/1) per foot
knee_angle_r_beta and knee_angle_l_beta are removed from the final output (they are always NaN), so the final CSV contains 49 effective DOFs.
Main entrypoint:
- run_amass_to_bsm_csv.py
--trial allruns every trial found inside a multi-trial.npzand exports one CSV per trial.
Additional NTU entrypoints:
- convert_ntu_skeleton_to_smplx_npz.py: converts NTU RGB+D
.skeletonfiles to AMASS-like SMPL-X.npz. - run_ntu_skeleton_batch_slurm.py: SLURM helper for NTU
.skeleton -> .npzconversion. - convert_humanml3d_joints_to_smplx_npz.py: fits SMPL-X params from HumanML3D
joints/*.npy52-joint files. - run_humanml3d_joints_batch_slurm.py: SLURM helper for HumanML3D
joints/*.npy -> .npzconversion.
- AMASS input file (example):
data/A3-_Swing_arms_stageii.npz - SMPL-X model file:
model/smpl/SMPLX_NEUTRAL.npz - BSM OpenSim model:
model/bsm/bsm.osim - BSM geometry folder:
model/bsm/Geometry/ - Marker map:
assets/smpl2ab/bsm_markers_smplx.yaml - Local AddBiomechanics checkout (see setup below)
AMASS compatibility note:
- The loader supports both Stage-II style
.npzfiles and legacy AMASS files withposes/trans. - If present, sibling
shape.npzis used automatically as fallback forgenderandbetas.
NTU compatibility note:
- NTU RGB+D
.skeletonfiles contain 25 3D joints in camera coordinates. - The NTU converter fits an AMASS-like SMPL-X parameter file with
trans,root_orient,pose_body, zero hand/face pose,betas,gender,mocap_frame_rate, and NTU metadata. - Raw NTU skeletons are treated as Y-up; by default the converter exports Z-up motion (
--target-frame z-up) to match this repo's BSM/Nimble/OpenSim gravity convention. - NTU uses a viewer-compatible 180-degree root yaw by default; left/right labels are not swapped unless
--swap-left-rightis passed. - Subject size is handled by fitting SMPL-X
betasfrom metric 3D limb lengths.
HumanML3D compatibility note:
new_joint_vecs/*.npyare feature vectors and are not direct SMPL parameters.new_joints/*.npyare normalized 22-joint positions.joints/*.npyare the preferred source for this repo because they contain 52 SMPL/SMPL-H-style joints, including hands.- HumanML3D motion is 20 fps by default.
- Install system dependencies:
sudo apt update
sudo apt install -y git wget- Install Conda (Miniconda), if needed:
wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh -O miniconda.sh
bash miniconda.sh -b -p "$HOME/miniconda3"
eval "$("$HOME/miniconda3/bin/conda" shell.bash hook)"- Create and activate the environment:
conda env create -f environment.yml
conda activate opensim-torque- Clone AddBiomechanics:
git clone https://github.com/keenon/AddBiomechanics.git "$HOME/AddBiomechanics"- Install Conda (Miniconda or Miniforge).
- Create and activate the environment:
conda env create -f environment.yml
conda activate opensim-torque- Clone AddBiomechanics:
git clone https://github.com/keenon/AddBiomechanics.git "$HOME/AddBiomechanics"Notes for macOS:
- The pipeline script sets
KMP_DUPLICATE_LIB_OK=TRUEautomatically to avoid OpenMP runtime conflicts. - If
pythonis not available in your shell, usepython3(inside the activated conda env,pythonis usually available).
Run the full pipeline with one Python command:
python scripts/run_amass_to_bsm_csv.py \
--input data/A3-_Swing_arms_stageii.npz \
--trial A3_swing_full \
--output-dir outputs/bsm \
--smplx-model-dir model/smpl \
--bsm-model model/bsm/bsm.osim \
--addbio-root "$HOME/AddBiomechanics" \
--id-grf-mode estimated \
--cleanup-intermediateIf your shell does not resolve python, run the same command with python3.
To export one CSV per trial from a multi-trial .npz, use:
python scripts/run_amass_to_bsm_csv.py \
--input data/your_multitrial_file.npz \
--trial all \
--output-dir outputs/bsm \
--smplx-model-dir model/smpl \
--bsm-model model/bsm/bsm.osim \
--addbio-root "$HOME/AddBiomechanics" \
--id-grf-mode estimated \
--cleanup-intermediateFinal output:
outputs/bsm/A3_swing_full.csv
With --cleanup-intermediate, all temporary files under outputs/bsm/<trial>/ are removed after a successful run, leaving only the final CSV.
NTU RGB+D 60/120 skeleton files can be converted to AMASS-like SMPL-X .npz files, then passed to run_amass_to_bsm_csv.py.
Single-file conversion:
python scripts/convert_ntu_skeleton_to_smplx_npz.py \
--input-file data/ntu/S001C001P001R001A001.skeleton \
--output-dir data/ntu_smplx_npz \
--smplx-model-dir model/smpl \
--target-frame z-upFolder conversion:
python scripts/convert_ntu_skeleton_to_smplx_npz.py \
--input-dir data/ntu \
--output-dir data/ntu_smplx_npz \
--smplx-model-dir model/smpl \
--recursive \
--target-frame z-upOutput example:
data/ntu_smplx_npz/S001C001P001R001A001.npzdata/ntu_smplx_npz/conversion_summary.jsondata/ntu_smplx_npz/_shape_cache/P001.npy
Important options:
--target-frame z-upis the default and should be used before BSM/OpenSim dynamics.--target-frame y-uppreserves raw NTU vertical and is mainly for debugging.--actor-mode primarykeeps the main tracked body per file.--actor-mode allexports additional tracked bodies as_bodyXX.--performer P001filters conversion to one NTU performer.--swap-left-rightis disabled by default. Use it only when visual inspection shows left/right are reversed for a specific source.- The NTU fitter is conservative by default:
--input-smooth-passes 1,--center-horizontal,--root-yaw-degrees 180,--root-up-prior-weight 50,--torso-frame-prior-weight 3,--spine-upright-prior-weight 8,--foot-flat-prior-weight 25,--temporal-joint-smooth-weight 350, and--temporal-joint-jerk-weight 35. These defaults center Kinect camera-depth coordinates around the actor, align SMPL-X anatomical front with NTU camera-front, keep the torso upright while preserving shoulder/hip yaw, suppress short-frame flicker, and keep SMPL-X heel/toe joints flat during contact. --fit-shapes-onlyfits/writes performer beta cache, then stops before pose fitting.--forcerecomputes existing outputs and shape cache entries.
The generated .npz can be fed directly into the existing BSM pipeline:
python scripts/run_amass_to_bsm_csv.py \
--input data/ntu_smplx_npz/S001C001P001R001A001.npz \
--trial S001C001P001R001A001 \
--output-dir outputs/ntu_bsm \
--smplx-model-dir model/smpl \
--bsm-model model/bsm/bsm.osim \
--addbio-root "$HOME/AddBiomechanics" \
--id-grf-mode estimated \
--cleanup-intermediatePreferred HPC mode is one SLURM array task per NTU performer:
python scripts/run_ntu_skeleton_batch_slurm.py submit-subjects \
--input-root data/ntu \
--output-dir data/ntu_smplx_npz \
--smplx-model-dir model/smpl \
--python-exe python \
--slurm-setup-cmd 'source "$HOME/miniconda3/etc/profile.d/conda.sh"' \
--slurm-setup-cmd 'conda activate opensim-torque' \
--slurm-array-parallelism 32 \
--submitWhat submit-subjects does:
- groups input files by NTU performer key (
P001,P002, ...) - launches one SLURM array task per performer
- inside each task, fits one common SMPL-X shape/beta vector for that performer
- converts all that performer's
.skeletonfiles to.npz - writes logs under
data/ntu_smplx_npz/logs/ - writes SLURM manifests and result JSON files under
data/ntu_smplx_npz/slurm/
This is usually cleaner than one job per file because each performer gets a shared shape estimate before pose fitting.
Dry run:
python scripts/run_ntu_skeleton_batch_slurm.py submit-subjects \
--input-root data/ntu \
--output-dir data/ntu_smplx_npz \
--smplx-model-dir model/smpl \
--limit 1 \
--dry-runConstrain the job to a specific node:
python scripts/run_ntu_skeleton_batch_slurm.py submit-subjects \
--input-root data/ntu \
--output-dir data/ntu_smplx_npz \
--smplx-model-dir model/smpl \
--slurm-node node001 \
--slurm-array-parallelism 32 \
--slurm-setup-cmd 'source "$HOME/miniconda3/etc/profile.d/conda.sh"' \
--slurm-setup-cmd 'conda activate opensim-torque' \
--submit--slurm-node node001 is an alias for --slurm-nodelist node001 and writes #SBATCH -w node001.
Alternative two-step mode:
python scripts/run_ntu_skeleton_batch_slurm.py prefit-shapes \
--input-root data/ntu \
--output-dir data/ntu_smplx_npz \
--smplx-model-dir model/smpl \
--python-exe python
python scripts/run_ntu_skeleton_batch_slurm.py submit \
--input-root data/ntu \
--output-dir data/ntu_smplx_npz \
--smplx-model-dir model/smpl \
--python-exe python \
--slurm-setup-cmd 'source "$HOME/miniconda3/etc/profile.d/conda.sh"' \
--slurm-setup-cmd 'conda activate opensim-torque' \
--slurm-array-parallelism 32 \
--submitUse this only if you explicitly want global prefit first and then one array task per file.
For HumanML3D, use the joints/ folder, not new_joint_vecs/. The joints/*.npy files have shape (T, 52, 3) and provide the richest direct joint target.
python scripts/convert_humanml3d_joints_to_smplx_npz.py \
--input data/HumanML3D/joints/000000.npy \
--output-dir data/humanml3d_smplx_npz \
--smplx-model-dir model/smpl \
--target-frame z-upOutput example:
data/humanml3d_smplx_npz/000000.npz
On HPC/SLURM, submit the full joints/ folder with:
python scripts/run_humanml3d_joints_batch_slurm.py submit \
--input-root data/HumanML3D \
--output-dir data/humanml3d_smplx_npz \
--smplx-model-dir model/smpl \
--python-exe python \
--slurm-nodelist 'node[001-002],blade[010-012]' \
--slurm-setup-cmd 'source "$HOME/miniconda3/etc/profile.d/conda.sh"' \
--slurm-setup-cmd 'conda activate opensim-torque' \
--slurm-max-array-size 1000 \
--slurm-array-parallelism 32 \
--submitThe script automatically uses --input-root/joints when that folder exists. For large datasets, for example 17k HumanML3D files, it follows the same scheduling pattern as run_amass_batch_slurm.py: it splits the task list into multiple SLURM array chunks with --slurm-max-array-size (default 1000) and submits them one at a time with retry/backoff. Use --slurm-nodelist or the alias --slurm-node to constrain execution to a node or node range.
HumanML3D fitting defaults are conservative for visual stability: --input-smooth-passes 1, --pose-iters 2600, --no-fit-hands, --root-up-prior-weight 10, --torso-frame-prior-weight 2, --pose-prior-weight 0.05, --temporal-smooth-weight 0.03, --temporal-joint-smooth-weight 500, --temporal-joint-jerk-weight 50, and --foot-flat-prior-weight 25. The hand fit is disabled by default because the HumanML3D 52-joint hands come from SMPL/SMPL-H-style joints and can create unnatural SMPL-X finger/body twists. The torso-frame prior matches hip, spine, collar, and shoulder directions from the source joints, the joint smooth/jerk priors suppress short flicker bursts, and the foot-flat prior keeps SMPL-X ankle, heel, and toe joints coplanar during detected contact frames.
It writes:
data/humanml3d_smplx_npz/slurm/manifest.jsonldata/humanml3d_smplx_npz/slurm/run_humanml3d_joints_to_npz.sbatch- additional chunk scripts such as
run_humanml3d_joints_to_npz_0001.sbatchwhen needed data/humanml3d_smplx_npz/slurm/results/task_*.json- per-file logs under
data/humanml3d_smplx_npz/logs/
The generated .npz contains trans, root_orient, pose_body, pose_hand, pose_jaw, pose_eye, betas, gender, and mocap_frame_rate, so it can be passed to run_amass_to_bsm_csv.py:
python scripts/run_amass_to_bsm_csv.py \
--input data/humanml3d_smplx_npz/000000.npz \
--trial humanml3d_000000 \
--output-dir outputs/humanml3d_bsm \
--smplx-model-dir model/smpl \
--bsm-model model/bsm/bsm.osim \
--addbio-root "$HOME/AddBiomechanics" \
--id-grf-mode estimated \
--cleanup-intermediateTo process an entire AMASS folder in parallel, use:
python scripts/run_amass_batch_parallel.py \
--input-root /path/to/AMASS \
--output-dir outputs/bsm_batch \
--workers 8 \
--smplx-model-dir model/smpl \
--bsm-model model/bsm/bsm.osim \
--addbio-root "$HOME/AddBiomechanics" \
--id-grf-mode estimated \
--cleanup-intermediateWhat it does:
- recursively scans
--input-rootfor.npzfiles - runs the full pipeline in parallel (
--workers N) - writes one final CSV per input
.npz(preserving folder structure in--output-dir) - writes per-file logs under
outputs/bsm_batch/logs/ - writes a global summary JSON at
outputs/bsm_batch/batch_summary.json
Useful options:
--dry-runto preview discovered files and planned outputs--limit Nto run only the firstNdiscovered files--skip-existing-csv(default) skips files when the destination CSV already exists and is non-empty--no-skip-existing-csvto force re-run even when output CSV already exists--no-cleanup-intermediateto keep intermediate artifacts
Note on SMB paths:
- If your dataset path is like
smb://parconas.di.univr.it/MAEVE/dataset/AMASS, mount it first (for example to/Volumes/AMASS) and pass the mounted local path to--input-root.
For HPC clusters, use the SLURM helper script:
python scripts/run_amass_batch_slurm.py submit \
--smplx-model-dir model/smpl \
--bsm-model model/bsm/bsm.osim \
--addbio-root "$HOME/AddBiomechanics" \
--id-grf-mode estimated \
--cleanup-intermediate \
--slurm-job-name amass_bsm \
--slurm-partition cpu \
--slurm-time 08:00:00 \
--slurm-cpus-per-task 4 \
--slurm-mem 16G \
--slurm-array-parallelism 32 \
--slurm-setup-cmd 'source "$HOME/miniconda3/etc/profile.d/conda.sh"' \
--slurm-setup-cmd 'conda activate opensim-torque' \
--submitOn the UniVR HPC layout described in this project, the script now defaults to:
- input root:
~/storage/emartini/AMASS/AMASS - output root:
~/storage/emartini/AMASS_torque
Assuming ~/storage is a symlink to /storage-large/shared-folders/gr_bombieri/emartini, the shortest working submit command is:
python scripts/run_amass_batch_slurm.py submit \
--addbio-root "$HOME/AddBiomechanics" \
--slurm-job-name amass_bsm \
--slurm-nodelist 'node[001-002],blade[010-012]' \
--slurm-time 08:00:00 \
--slurm-cpus-per-task 4 \
--slurm-mem 16G \
--slurm-array-parallelism 32 \
--slurm-setup-cmd 'source "$HOME/miniconda3/etc/profile.d/conda.sh"' \
--slurm-setup-cmd 'conda activate opensim-torque' \
--submitIf your cluster requires an explicit partition or account, add --slurm-partition ... and/or --slurm-account ....
If admins ask you to constrain jobs to a subset of hosts, add --slurm-nodelist 'node[001-002],blade[010-012]'.
If the preferred queue is overloaded, consider switching some batches to --slurm-partition low.
What this script does:
- discovers
.npzfiles recursively - skips existing destination CSVs by default (
--skip-existing-csv) - writes a task manifest at
outputs/bsm_batch/slurm/manifest.jsonl - writes an SBATCH script at
outputs/bsm_batch/slurm/run_batch.sbatch - launches a SLURM array job (if
--submitis passed) - stores per-task worker status JSON files under
outputs/bsm_batch/slurm/results/
Useful SLURM workflow:
- use
--dry-runfirst to generate and inspect manifest + SBATCH script without submitting - omit
--submitif you want to manually run the printedsbatch ...command - use
--no-skip-existing-csvwhen you intentionally want to recompute all outputs - tune
--slurm-array-parallelismto cap concurrent jobs (for example%32)
The unified CSV includes:
frame,time- Subject metadata:
subject_mass_kg,subject_height_m - Body scaling metadata:
<body>_scale_x,<body>_scale_y,<body>_scale_z(one triplet for each model body) - For each DOF:
<dof>,<dof>_vel,<dof>_acc,<dof>_tau - GRF columns (for every detected contact body and total)
- Contact code columns (for every detected contact body, for example
hand_l_contact,tibia_r_contact,calcn_l_contact)
GRF values are explicitly encoded with zeros when no contact is detected, so CSV schemas stay consistent across motions (including jumps/flight phases).
For native AMASS input:
- Load AMASS (
SMPL-X) from.npz. - Run SMPL-X forward pass.
- Extract BSM virtual markers from SMPL-X vertices.
- Export
markers.trc. - Run AddBiomechanics scaling + kinematic fit.
- Export fitted
.osimand.mot. - Compute DOF kinematics (
q,qdot,qddot). - Estimate contact wrenches/GRF from motion.
- Run inverse dynamics in OpenSim.
- Merge kinematics + torques + GRF/contact into one final CSV.
For NTU input, prepend:
- Read NTU RGB+D
.skeleton25-joint tracks. - Convert raw NTU Y-up camera coordinates to Z-up world coordinates.
- Smooth the metric joint targets and fit SMPL-X
betasfrom segment lengths. - Fit SMPL-X root/body pose over time with torso-frame, foot-flat, and joint smoothness priors.
- Export AMASS-like
.npz, then run the same AMASS/BSM pipeline above.
For HumanML3D joints/*.npy input, prepend:
- Read HumanML3D 52-joint SMPL/SMPL-H-style positions.
- Convert HumanML3D Y-up coordinates to Z-up world coordinates.
- Fit SMPL-X
betasfrom 52-joint segment lengths. - Fit SMPL-X root and body pose over time, with hands neutral by default.
- Export AMASS-like
.npz, then run the same AMASS/BSM pipeline above.
--addbio-root /path/to/AddBiomechanics--id-grf-mode {estimated,none}(default:estimated)--id-contact-bodies all(default; uses all model body nodes as contact candidates)--id-friction-coeff 0.8(default)--id-filter-mode {auto,walking,dynamic,none}(default:auto)--id-cutoff-hz <float>(optional custom cutoff)--final-csv-path /custom/output.csv(optional)--cleanup-intermediate(keep only final CSV)
You can generate a full multi-page PDF report from any unified output CSV with:
python scripts/csv_explorer.py \
--input-csv outputs/bsm/A3-_Swing_arms_stageii.csv \
--output-pdf outputs/bsm/A3-_Swing_arms_stageii_report.pdfThe report includes:
- global summary (frames, duration, sample rate, detected DOFs and GRFs)
- motion overview statistics
- one page per DOF with position/velocity/acceleration/(torque if present)
- GRF pages for each contact body and total GRF
The previous pipelines are still available: