BVH reader/writer and kinematics utilities
Project description
pybvh
A lightweight Python library for reading, writing, and manipulating BVH motion capture files. Built for researchers and developers working with skeletal animation and motion data.
Features
- Read & write BVH files with full hierarchy and motion data preservation
- Rotation conversions between Euler angles, rotation matrices, quaternions, 6D (Zhou et al.), and axis-angle — all vectorized with NumPy
- Forward kinematics to compute 3D joint positions from angles
- Skeleton operations: retargeting, scaling, joint extraction, Euler order changes
- Frame operations: slicing, concatenation, resampling to different frame rates
- Spatial transforms: mirroring, vertical rotation, speed perturbation, joint noise, root translation, frame dropout — all with seeded randomization
- Motion analysis: joint velocities/accelerations, root trajectory, foot contact detection, normalization utilities, and a one-stop
to_feature_array()export - Batch loading of entire directories with optional parallel I/O
- NumPy export in any rotation representation — ready for any downstream workflow
- Pandas ready via an export option ready to become a DataFrame
- 3D visualization with multiple backends (matplotlib, OpenCV, k3d, vedo)
Philosophy
pybvh is framework-agnostic and outputs pure NumPy arrays. It understands motion capture data but does not assume what you'll do with it — the same library serves ML researchers, biomechanics scientists, and game developers. For ML-specific features (tensor packing, PyTorch Datasets, augmentation pipelines), see the companion library pybvh-ml.
Installation
pip install pybvh
Quick Start
import pybvh
# Load a BVH file
bvh = pybvh.read_bvh_file("walk.bvh")
print(bvh) # "24 elements in the Hierarchy, 75 frames at 30.0 fps (frame_time=0.033333s)"
# Access motion data as NumPy arrays
bvh.root_pos # (F, 3) root translation per frame
bvh.joint_angles # (F, J, 3) Euler angles in degrees
bvh.joint_names # ['Hips', 'Spine', ...] (excludes end sites)
# Get 3D joint positions via forward kinematics
coords = bvh.spatial_coords() # (F, N, 3)
# Convert to other rotation representations
root_pos, quats = bvh.to_quaternions() # (F, 3), (F, J, 4)
root_pos, rot6d = bvh.to_6d() # (F, 3), (F, J, 6)
# Write back to file
bvh.write("output.bvh")
Batch Loading
Load an entire directory of BVH files and convert to NumPy arrays in one call:
from pybvh import read_bvh_directory, batch_to_numpy
# Load all BVH files from a directory
clips = read_bvh_directory("dataset/", parallel=True)
# Convert to padded NumPy array
data = batch_to_numpy(clips, representation="6d", pad=True)
# shape: (batch, max_frames, features)
Supported representations: "euler", "quaternion", "6d", "axisangle", "rotmat".
Motion Analysis
Compute motion derivatives, foot contacts, and export everything in a single array:
# Joint velocities and accelerations (finite differences of FK positions).
# Defaults: central stencil + edge padding — output has the same leading
# dimension as the input. Pass stencil="forward", pad="none" for the
# traditional (F-1, ...) / (F-2, ...) forward-difference shapes.
vel = bvh.joint_velocities() # (F, N, 3) in units/second
acc = bvh.joint_accelerations() # (F, N, 3)
ang_vel = bvh.angular_velocities() # (F, J, 3) in radians/second
# Skeleton-centered positions and trajectory
rel_pos = bvh.spatial_coords(centered='skeleton') # (F, N, 3)
traj = bvh.root_trajectory() # (F, 4) ground pos + heading
# Foot contact detection (auto-detects foot joints)
contacts = bvh.foot_contacts() # (F, num_feet) binary indicators
# One-stop export — flat feature array
features = bvh.to_feature_array(
representation="6d",
include_velocities=True,
include_foot_contacts=True,
) # (F, D)
Normalize across a dataset:
from pybvh import compute_normalization_stats, normalize_array, denormalize_array
stats = compute_normalization_stats(clips, representation="6d")
normalized = normalize_array(data, stats)
# stats are plain dicts — save with np.savez("stats.npz", **stats)
Spatial Transforms
Standard motion transforms — all support seeded randomization for reproducibility:
from pybvh import transforms
# Left-right mirroring (auto-detects joint pairs and lateral axis)
bvh_mirrored = transforms.mirror(bvh)
# Vertical rotation (auto-detects up axis)
bvh_rotated = transforms.rotate_vertical(bvh, angle_deg=90)
bvh_rotated = transforms.random_rotate_vertical(bvh, rng=np.random.default_rng(42))
# Speed perturbation (factor > 1 = faster, < 1 = slower)
bvh_fast = transforms.perturb_speed(bvh, factor=1.5)
# Joint noise injection
bvh_noisy = transforms.add_noise(bvh, sigma_deg=1.0, sigma_pos=0.5, rng=rng)
# Root translation
bvh_shifted = transforms.translate_root(bvh, offset=[100, 0, 0])
# Frame dropout with SLERP interpolation (same frame count)
bvh_dropped = transforms.drop_frames(bvh, drop_rate=0.1, rng=rng)
All transforms also available as Bvh methods: bvh.mirror(), bvh.rotate_vertical(90), etc.
Skeleton Operations
# Change Euler rotation order for all joints (or a single joint via `joint=`)
bvh_xyz = bvh.change_euler_order("XYZ")
bvh_hips_xyz = bvh.change_euler_order("XYZ", joint="Hips")
# Scale the skeleton
bvh_scaled = bvh.scale(0.01) # meters to centimeters
# Retarget motion to a different skeleton
bvh_retarget = bvh.retarget(reference_bvh)
# Extract a subset of joints
bvh_upper = bvh.extract_joints(["Hips", "Spine", "Neck", "Head"])
# Slice and concatenate frames
clip = bvh.slice_frames(10, 50)
combined = bvh.concat(other_bvh)
# Resample to a different frame rate
bvh_30fps = bvh.resample(30)
Rotation Utilities
All functions are batch-vectorized and work on arbitrary batch dimensions:
from pybvh import rotations
# Convert between any pair of representations
R = rotations.euler_to_rotmat(angles, order="ZYX", degrees=True)
q = rotations.rotmat_to_quat(R)
aa = rotations.quat_to_euler(q, order="ZYX", degrees=True)
# Quaternion SLERP interpolation
q_mid = rotations.quat_slerp(q1, q2, t=0.5)
Visualization
# Single-skeleton calls are methods on the Bvh object
bvh.plot_rest_pose() # T-pose
bvh.plot_frame(frame=0, camera="front") # also "side", "top", (azim, elev)
bvh.plot_trajectory() # 2D top-down root path
bvh.render("walk.mp4") # video/GIF/HTML export
bvh.render("walk.mp4", follow=True) # camera tracks character as it turns
bvh.play() # interactive playback (auto-detects backend)
# Multi-skeleton comparisons go through the module functions
from pybvh import bvhplot
bvhplot.frame([bvh1, bvh2], frame=0, labels=["Original", "Generated"])
bvhplot.render([bvh1, bvh2], "compare.mp4", sync="pad")
# Orientation API — (world_up, forward_at, left_at) form an orthonormal
# right-hand-rule triple (left = world_up × forward_at)
bvh.world_up # '+y' / '+z' — animation-derived gravity axis
bvh.forward_at(0) # character's facing direction at a given frame
bvh.left_at(0) # character's leftward direction at a given frame
bvh.world_up = '+y' # manual override if auto-detect is wrong for your file
# Pose-independent companions (read from rest pose only)
bvh.rest_up # rest-pose up axis — compare to world_up to spot mismatches
bvh.rest_forward # rest-pose facing direction
Install optional visualization backends for best performance:
pip install pybvh[opencv] # Fast video rendering
pip install pybvh[interactive] # k3d for Jupyter notebooks
pip install pybvh[viewer] # vedo for desktop interactive viewer
pip install pybvh[all-viz] # All of the above
Pandas Integration
import pandas as pd
# BVH to DataFrame
df = pd.DataFrame(bvh.to_df_dict(mode="euler"))
# DataFrame back to BVH
from pybvh import df_to_bvh
bvh_from_df = df_to_bvh(bvh.hierarchy_info_as_dict(), df)
Tutorials
The repository includes Jupyter notebooks with detailed walkthroughs:
- Introduction to pybvh — reading, writing, and basic operations
- Spatial coordinates — forward kinematics and 3D positions
- Rotations — rotation representations and conversions
- Visualization — static frames, video export, and interactive playback
- Transforms — mirroring, rotation, speed perturbation, noise
- Features — velocities, foot contacts, feature-array export
- Batch processing — directory loading, normalization, harmonization
Each tutorial is committed as a Jupytext-paired .ipynb + .py (Percent format) so the source is reviewable as plain Python. See the tutorials docs for the contributor workflow.
Requirements
- Python >= 3.9
- NumPy >= 1.21
- Matplotlib >= 3.7
Pandas is optional (pip install "pybvh[pandas]") - only used in the tutorials, not part of pybvh library.
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pybvh-0.6.0.tar.gz.
File metadata
- Download URL: pybvh-0.6.0.tar.gz
- Upload date:
- Size: 116.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3814588d7f2c43644e3c4faa23022500535ccc2c21973e65cc6690d2ca333fce
|
|
| MD5 |
a45559a1b125e59ad50f5d6dab4a5bf2
|
|
| BLAKE2b-256 |
b1e4883e83b02a13b6bb19a54c03aaa90e0681dc2e75051bcdb44e9eadbab6f6
|
Provenance
The following attestation bundles were made for pybvh-0.6.0.tar.gz:
Publisher:
publish.yml on VictorS-67/pybvh
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pybvh-0.6.0.tar.gz -
Subject digest:
3814588d7f2c43644e3c4faa23022500535ccc2c21973e65cc6690d2ca333fce - Sigstore transparency entry: 1341475116
- Sigstore integration time:
-
Permalink:
VictorS-67/pybvh@a99bdedcb2158b18d16631978e43e5b98faead27 -
Branch / Tag:
refs/tags/v0.6.0 - Owner: https://github.com/VictorS-67
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@a99bdedcb2158b18d16631978e43e5b98faead27 -
Trigger Event:
release
-
Statement type:
File details
Details for the file pybvh-0.6.0-py3-none-any.whl.
File metadata
- Download URL: pybvh-0.6.0-py3-none-any.whl
- Upload date:
- Size: 123.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b425cab559eeb2f196e946d147c62feaded5d769e4a2993efc6a3ecfa495bf5f
|
|
| MD5 |
e1320d8e6b9d1aff604cc9f2b5499acf
|
|
| BLAKE2b-256 |
2f9d29bf7ae8650eac9b92406b318642f2dabed61ff9c6839fa7201d2bbd5792
|
Provenance
The following attestation bundles were made for pybvh-0.6.0-py3-none-any.whl:
Publisher:
publish.yml on VictorS-67/pybvh
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pybvh-0.6.0-py3-none-any.whl -
Subject digest:
b425cab559eeb2f196e946d147c62feaded5d769e4a2993efc6a3ecfa495bf5f - Sigstore transparency entry: 1341475186
- Sigstore integration time:
-
Permalink:
VictorS-67/pybvh@a99bdedcb2158b18d16631978e43e5b98faead27 -
Branch / Tag:
refs/tags/v0.6.0 - Owner: https://github.com/VictorS-67
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@a99bdedcb2158b18d16631978e43e5b98faead27 -
Trigger Event:
release
-
Statement type: