Skip to content

Bvh Class

Bvh(nodes: list[BvhNode] | None = None, root_pos: npt.ArrayLike | None = None, joint_angles: npt.ArrayLike | None = None, frame_time: float = 0, world_up: str = 'auto', lr_mapping: dict[str, str] | None = None, source_path: str | None = None)

Container for BVH motion-capture data.

The hierarchy is stored as a list of BvhNode objects (one per joint / end-site). Motion data is stored as two structured arrays:

  • root_pos: shape (F, 3) — root translation per frame
  • joint_angles: shape (F, J, 3) — Euler angles in radians per joint per frame

Bvh is a sequence of frames: len(bvh) == frame_count and bvh[i] returns frame i as a one-frame Bvh. For joint or node counts, use bvh.joint_count or len(bvh.node_index).

Attributes:

Name Type Description
nodes list of BvhNode

Skeleton hierarchy in topological order.

root BvhRoot

The root node (nodes[0]).

root_pos (ndarray, shape(F, 3))

Root position per frame.

joint_angles (ndarray, shape(F, J, 3))

Euler angles in radians per joint per frame. (BVH files store angles in degrees; the deg↔rad conversion happens at the I/O boundary in :func:read_bvh_file / :func:write_bvh_file.)

frame_time float

Duration of one frame in seconds.

frame_count int

Number of frames (read-only).

node_index dict

Mapping from node name to its index in nodes (includes end sites). Use this to index node_positions() output (shape (F, N, 3)).

joint_index dict

Mapping from joint name to its index in joint_angles axis 1 (joint-only, excludes end sites). Use this to index joint_angles (shape (F, J, 3)).

joint_names list of str

Names of non-end-site joints in topological order (read-only).

joint_count int

Number of non-end-site joints (read-only).

source_path str or None

Path of the file this Bvh was read from, or None if it was constructed in memory. Set by :func:read_bvh_file. Preserved through copy(), slice_frames(), and rotations / transforms that don't change which file the data originated from. Cleared (set to None) when :meth:concat joins two clips whose source_path differ. Writable — callers can assign manually when constructing a Bvh from arrays.

frame_time: float property writable

Seconds between successive frames.

A value of 0 means "unset" and is the default for newly constructed empty :class:Bvh objects. Writing to a file requires a positive value — :func:~pybvh.io.write_bvh_file raises ValueError otherwise.

fps: float property writable

Frames per second — convenience inverse of :attr:frame_time.

Returns 0.0 when frame_time == 0 (the "unset" sentinel) rather than raising, mirroring the behaviour of :meth:__str__.

Example

if bvh.fps != 30: ... bvh = bvh.resample(30)

root_pos: npt.NDArray[np.float64] property writable

Root translation per frame, shape (F, 3).

Returns a read-only view of the underlying array — call bvh.root_pos.copy() if you need a writable array. To replace the whole array, assign via the setter (bvh.root_pos = new_arr); for in-place edits, copy → mutate → assign back.

joint_angles: npt.NDArray[np.float64] property writable

Per-joint Euler angles, shape (F, J, 3) (radians).

Returns a read-only view of the underlying array — call bvh.joint_angles.copy() if you need a writable array. To replace the whole array, assign via the setter (bvh.joint_angles = new_arr); for in-place edits, copy → mutate → assign back.

Read-only view protects against the common footgun of angles = b.joint_angles; angles -= angles.mean(axis=0) silently corrupting the Bvh.

frame_count: int property

Number of frames (computed from root_pos).

world_up: str property writable

Gravity axis of the BVH coordinate system.

Returned as a signed axis string ('+y', '-z', etc.). Constant per file. Auto-detected from the first animation frame's head-above-hips direction, with rest-pose topology as fallback. Issues a UserWarning if the first frame and rest pose disagree.

Can be overridden manually via the setter when auto-detection produces the wrong answer (e.g. authored BVH files where the rest pose convention differs from the animation's world orientation):

>>> bvh.world_up = '+y'

The override is preserved through copy(), slice_frames(), and transforms that don't change the world coordinate system (mirror, rotate_vertical, scale, translate_root). retarget() re-infers from the new skeleton.

Note: BVH files do not store a world-up field, so manual overrides are lost on write→read round trips and must be re-applied.

world_up_inferred: str property

What the auto heuristic would pick, regardless of any override.

Useful for auditing whether a manual bvh.world_up = '+x' override was necessary, or for diagnosing skeletons whose animation and rest-pose conventions disagree. Always runs the inference fresh; doesn't consult or write the cache.

Compare against :attr:world_up to see whether an override is in effect:

>>> bvh.world_up_inferred  # '+y'  (auto's guess)
>>> bvh.world_up           # '+z'  (user override)

rest_up: str property

Skeleton's topological up axis, derived from the rest pose only.

Read-only. Inspects rest-pose joint offsets ("head", "neck", "chest", "spine" in priority order; falls back to the axis with the largest offset spread) and returns the dominant signed axis. Pose-independent — the animation data is never touched.

Contrast with :attr:world_up, which is animation-derived (inferred from the first frame's head-above-hips direction). On clean files the two agree; when they disagree, the BVH was authored with the rest pose in one convention and animated in another, and :meth:reorient_rest_up can fix it in place.

Returns:

Type Description
str

Signed axis string (e.g. '+y', '+z').

rest_forward: str property

Skeleton's topological forward axis, derived from the rest pose only.

Read-only. Computes forward from the rest-pose L/R lateral geometry crossed with :attr:world_up. Pose-independent — the animation data is never touched. Complements :attr:rest_up (rest-pose up axis) and parallels :meth:forward_at (animation- derived forward at a given frame).

Use this to check whether a skeleton's rest-pose facing matches a dataset convention without having to call :func:reorient_rest_forward and compare results.

Returns:

Type Description
str

Signed axis string (e.g. '+z', '-x').

lr_mapping: dict[str, str] | None property writable

Left/right joint pair mapping for this skeleton (bidirectional).

A dict describing the skeleton's bilateral symmetry pairs. None if no pairs could be auto-detected and no explicit mapping was provided.

The dict is symmetric: both directions of each pair are present, so mapping['LeftArm'] == 'RightArm' AND mapping['RightArm'] == 'LeftArm'. Useful for mirroring-based data augmentation, where a lookup can come from either side.

Detection at construction time runs the extended name heuristic (Left/Right substring, L/R prefix, .L/.R suffix, _l/_r suffix, Mixamo mixamorig: namespace, numbered .001 duplicates). Skeletons with conventions the heuristic can't parse have lr_mapping = None — in that case, set it explicitly:

>>> bvh.lr_mapping = {'arm.L': 'arm.R', 'leg.L': 'leg.R'}

The assigned dict is one-directional; pybvh symmetrizes it internally. Either form works on assignment.

or pass lr_mapping= at load time:

>>> bvh = read_bvh_file('weird.bvh', lr_mapping={...})

Consumers: mirror(), forward_at(), _rest_leftward, _compute_forward_at, reorient_rest_forward.

Note: BVH files don't store L/R pair info, so user-set mappings are lost on bvh.write() round-trips — same wart as world_up. Re-apply after reading.

lr_pairs: list[tuple[int, int]] | None property

Left/right joint pairs as index tuples in joint_angles space.

Index-space counterpart of :attr:lr_mapping, derived from the same cache. Returns None when no mapping is available (matches the lr_mapping sentinel — one "no pairs" convention across both surfaces).

Useful for graph construction and array-level ops that index joints by position rather than by name.

node_index: dict[str, int] property

Mapping from node name to its integer index in nodes.

Indexes the output of :meth:node_positions (shape (F, N, 3)), which includes end sites. For indexing :attr:joint_angles (shape (F, J, 3), excludes end sites), use :attr:joint_index instead.

.. warning:: joint_index and node_index share keys for every non-end-site joint but return different integers once any end site has appeared earlier in the hierarchy. Indexing joint_angles with node_index (or :meth:node_positions with joint_index) produces silently misaligned data — no shape mismatch, just the wrong limb. Pick one consistently per array, or use :meth:Bvh.index to make the intent explicit at the call site.

Returns:

Type Description
dict

{node_name: int} for every node (joints and end sites).

joint_index: dict[str, int] property

Mapping from joint name to its integer index in joint_angles axis 1.

Excludes end sites. Use this instead of bvh.joint_names.index(name) for joint-axis lookups.

.. warning:: joint_index and node_index share keys for every non-end-site joint but return different integers once any end site has appeared earlier in the hierarchy. Indexing :meth:node_positions with joint_index (or joint_angles with node_index) produces silently misaligned data. Pick one consistently per array, or use :meth:Bvh.index to make the intent explicit at the call site.

Returns:

Type Description
dict

{joint_name: int} for every non-end-site joint. Values cover range(joint_count).

joint_names: list[str] property

Names of non-end-site joints in topological order.

Returns:

Type Description
list of str

joint_count: int property

Number of non-end-site joints.

Returns:

Type Description
int

euler_orders: list[str] property

Per-joint Euler rotation orders as strings.

Returns:

Type Description
list of str

e.g. ['ZYX', 'ZYX', ...], one per non-end-site joint. Order matches joint_names and joint_angles axis 1.

edges: list[tuple[int, int]] property

Skeleton edge list as (child_idx, parent_idx) tuples.

Indices use joint_angles index space (non-end-site joints only, matching joint_names order). The root joint has no parent and produces no edge, so a skeleton with J joints yields J - 1 edges.

See Also

node_edges : Same list but in nodes index space (includes end sites) — what graph models over the full visual skeleton typically want.

node_edges: list[tuple[int, int]] property

Skeleton edge list as (child_idx, parent_idx) tuples in nodes index space (includes end sites).

Parallels :attr:edges (joint-axis only); use node_edges when the downstream graph treats end sites as real leaves (visual skeleton, per-bone styling, GCN inputs over the full topology). node_edges has one more edge per end site than edges.

matches_hierarchy(other: Bvh, match_offsets: bool = True, atol: float = 1e-06) -> bool

Whether self and other share the same skeleton hierarchy.

Hierarchy is defined as: same node names in topological order (including end sites), same parent-child structure, and — when match_offsets=True (default) — same rest-pose offsets within atol. Motion data, Euler rotation orders, and frame timing are NOT compared.

Use this when you need to know that two clips describe the same skeleton in the same rest pose — e.g. before batching to a rotation-invariant representation (6d / quaternion / rotmat) whose channel layout doesn't depend on Euler order.

Pass match_offsets=False when the caller is about to overwrite rest offsets anyway (e.g. retargeting): it loosens the check to the skeleton graph alone — joint names and parent structure — accepting two characters of different bone proportions.

Parameters:

Name Type Description Default
other Bvh
required
match_offsets bool

If True (default), require rest-pose offsets to agree within atol. If False, ignore offsets and check only the skeleton graph (names + parent structure).

True
atol float

Absolute tolerance for offset comparison (default 1e-6). Ignored when match_offsets=False.

1e-06

Returns:

Type Description
bool
See Also

matches_channels : Compare per-joint Euler rotation orders. matches_topology : Conjunction of hierarchy + channels.

matches_channels(other: Bvh) -> bool

Whether self and other share the same channel layout.

Compares per-joint Euler rotation orders and the root's position-channel order. This is a serialization property: clips with identical underlying rotations but different stored Euler orders have different channel layouts.

Use this in addition to :meth:matches_hierarchy when batching to a representation whose channel layout depends on the source Euler order (euler / axisangle).

Parameters:

Name Type Description Default
other Bvh
required

Returns:

Type Description
bool
See Also

matches_hierarchy : Compare joint hierarchy and rest offsets. matches_topology : Conjunction of hierarchy + channels.

matches_topology(other: Bvh) -> bool

Whether self and other share both hierarchy and channel layout.

Convenience for matches_hierarchy(other) and matches_channels(other). Two Bvhs that satisfy this predicate can be batched together for any representation (euler, axisangle, 6d, quaternion, rotmat) without conversion.

.. note:: Prior to 0.7.0, matches_topology checked only joint_names and euler_orders — it did not catch differences in parent structure or rest offsets. The current definition is stricter: clips with identical names but differing rest offsets no longer match.

Parameters:

Name Type Description Default
other Bvh
required

Returns:

Type Description
bool
See Also

matches_hierarchy : The hierarchy half (joints, parents, offsets). matches_channels : The channel-layout half (Euler orders, root pos channels).

__len__() -> int

Number of frames. Equivalent to self.frame_count.

__getitem__(key: int | slice) -> Bvh

Return a new Bvh containing the selected frame(s).

Integer keys return a single-frame (F=1) Bvh; slice keys delegate to :meth:slice_frames. Negative indices and reversed slices (bvh[::-1]) work as expected. frame_time is scaled by abs(step) when |step| > 1; reversed playback at |step|=1 preserves frame_time.

Parameters:

Name Type Description Default
key int or slice

Frame index or slice. Fancy indexing (ndarray, list, boolean mask) and tuple keys are not supported.

required

Raises:

Type Description
IndexError

Integer key outside [-frame_count, frame_count).

TypeError

Key of unsupported type.

__add__(other: object) -> Bvh

Concatenate two Bvh clips. Sugar for :meth:concat.

__iadd__(other: object) -> Bvh

In-place concatenation. Grows self by appending other's frames.

Validates skeleton compatibility and warns on frame_time mismatch just like :meth:concat. Mutates self.root_pos and self.joint_angles via their setters so _world_up_cached is invalidated.

__setitem__(key: int | slice, value: Bvh) -> None

Splice frames from another Bvh into self in place.

The skeleton of value must match self, value.frame_time must equal self.frame_time (raises ValueError otherwise — use :meth:resample first if they differ), and the slice length must equal value.frame_count. Integer keys require value.frame_count == 1.

Assignment goes through the root_pos and joint_angles setters so the world_up cache is invalidated.

forward_at(frame: int = 0, coords: npt.NDArray[np.float64] | None = None) -> str

Character's world-space forward (facing) direction at a given frame.

Computed from actual joint positions at the given frame — the leftward axis is derived by averaging (left − right) across matching L/R joint pairs in world space, then crossed with world_up to produce the forward direction (forward = leftward × up). This tracks the character's actual facing as they rotate through the animation.

Parameters:

Name Type Description Default
frame int

Frame index (default 0). Must be within the animation range.

0
coords (ndarray, shape(F, N, 3))

Pre-computed spatial coordinates for all frames. When provided, skips the per-call forward kinematics — useful for computing facing direction across many frames in a hot loop (e.g. dataset uniformity diagnostics). The selected frame's slice is taken via coords[frame].

None

Returns:

Type Description
str

Signed axis string (e.g. '-z') pointing in the character's facing direction in world coordinates at the given frame.

left_at(frame: int = 0, coords: npt.NDArray[np.float64] | None = None) -> str

Character's world-space leftward direction at a given frame.

Returns the signed axis along which a positive step moves from the character's right side toward their left side (e.g. right-shoulder → left-shoulder direction). Follows the right-hand-rule convention leftward = world_up × forward so the triple (world_up, :meth:forward_at, left_at) forms a consistent orthonormal frame in every axis convention pybvh supports.

Computed from joint positions at the given frame, so it tracks hip twist and shoulder rotation as the character moves.

Parameters:

Name Type Description Default
frame int

Frame index (default 0). Must be within the animation range.

0
coords (ndarray, shape(F, N, 3))

Pre-computed spatial coordinates for all frames. When provided, skips the per-call forward kinematics. The selected frame's slice is taken via coords[frame].

None

Returns:

Type Description
str

Signed axis string (e.g. '-x') pointing toward the character's left in world coordinates at the given frame.

See Also

forward_at : Facing direction. world_up : World vertical axis.

write(new_filepath: str | Path, verbose: bool = False) -> None

Write the Bvh object to a .bvh file. See :func:pybvh.io.write_bvh_file.

node_positions(frame_num: int = -1, centered: str = 'world') -> npt.NDArray[np.float64]

Per-node 3D positions (joints + end sites) — shape (F, N, 3).

Returns an ndarray of shape (N, 3) for a single frame or (F, N, 3) for all frames, where N is the total number of nodes (joints + end sites). Use :attr:node_index to look up rows by name.

For the joint-axis subset (excluding end sites) that aligns with :attr:joint_angles and :meth:joint_velocities, use :meth:joint_positions instead.

Parameters:

Name Type Description Default
frame_num int

Frame index to return. -1 (default) returns all frames.

-1
centered str

"world" – root at actual position. "skeleton" – root at origin for all frames. "first" – first-frame root at origin, then moves normally.

'world'

joint_positions(frame_num: int = -1, centered: str = 'world') -> npt.NDArray[np.float64]

Per-joint 3D positions (end sites excluded) — shape (F, J, 3).

Joint-axis subset of :meth:node_positions. Index-aligns with :attr:joint_angles and :meth:joint_velocities — use :attr:joint_index to look up rows by name.

Parameters:

Name Type Description Default
frame_num int

Frame index to return. -1 (default) returns all frames.

-1
centered str

See :meth:node_positions.

'world'

rest_pose_coords(mode: str = 'coordinates') -> npt.NDArray[np.float64] | tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]]

rest_pose_coords(mode: Literal['coordinates'] = ...) -> npt.NDArray[np.float64]
rest_pose_coords(mode: Literal['euler']) -> tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]]

Return the rest pose of the skeleton (all angles zero, root at origin).

Parameters:

Name Type Description Default
mode str

'euler' – return a tuple (root_pos, joint_angles) of zeros matching the structured shapes. 'coordinates' – return spatial coordinates as (N, 3).

'coordinates'

to_df_dict(mode: str = 'euler', centered: str = 'world') -> dict[str, npt.NDArray[np.float64]]

Return a dict of arrays for pd.DataFrame(result).

Each key is a column name, each value a 1-D NumPy array of length frame_count.

Parameters:

Name Type Description Default
mode str

'euler' — columns are 'JointName_X_rot' etc. (default). 'coordinates' — columns are 'JointName_X' etc., including end sites.

'euler'
centered str

"world" (default), "skeleton", or "first". Only used when mode='coordinates'.

'world'

Returns:

Type Description
dict

Column-name → 1-D array mapping, ready for pd.DataFrame().

hierarchy_info_as_dict() -> dict

Return the skeleton hierarchy as a plain dictionary.

Returns:

Type Description
dict

{name: {'offset': [...], 'parent': str|None, 'rot_channels': [...], 'children': [...]}, ...}. Root entries also include 'pos_channels'. The returned dict is a deep copy (safe to mutate).

index(name: str, axis: Literal['joint', 'node']) -> int

Look up the integer index for name on the requested axis.

Unambiguous alternative to picking between :attr:joint_index and :attr:node_index at the call site. Use axis='joint' when indexing :attr:joint_angles / :meth:joint_velocities / :meth:joint_accelerations / :meth:joint_positions / :meth:angular_velocities (any (F, J, ...) array). Use axis='node' when indexing :meth:node_positions / :meth:node_velocities / :meth:node_accelerations (any (F, N, ...) array).

Parameters:

Name Type Description Default
name str

Joint or node name.

required
axis ('joint', 'node')

Which index space to look up. 'joint' excludes end sites.

'joint'

Returns:

Type Description
int

Raises:

Type Description
KeyError

If name is not present in the requested axis (e.g. an end-site name with axis='joint').

ValueError

If axis is not 'joint' or 'node'.

retarget(new_skeleton: Bvh, name_mapping: dict[str, str] | None = None, strict: bool = False, inplace: bool = False) -> Bvh | None

retarget(new_skeleton: Bvh, name_mapping: dict[str, str] | None = ..., strict: bool = ..., *, inplace: Literal[True]) -> None
retarget(new_skeleton: Bvh, name_mapping: dict[str, str] | None = ..., strict: bool = ..., inplace: Literal[False] = ...) -> Bvh

Copy joint offsets from a reference skeleton.

Parameters:

Name Type Description Default
new_skeleton Bvh

Reference skeleton whose offsets will be copied.

required
name_mapping dict

Maps self's joint names to new_skeleton's joint names, e.g. {'Hips': 'mixamorig:Hips', ...}. Joints not in the mapping are matched by identical name. If None (default), all joints are matched by name.

None
strict bool

If True, raise ValueError when a joint in self has no match in new_skeleton. If False (default), unmapped joints keep their original offsets.

False
inplace bool

If True, modify self and return None. If False (default), return a modified copy.

False

Returns:

Type Description
None or Bvh

scale(scale: float | npt.ArrayLike, inplace: bool = False) -> Bvh | None

scale(scale: float | npt.ArrayLike, *, inplace: Literal[True]) -> None
scale(scale: float | npt.ArrayLike, inplace: Literal[False] = ...) -> Bvh

Scale all node offsets by a factor.

Parameters:

Name Type Description Default
scale float or array_like of shape (3,)

Uniform scalar or per-axis scale factors.

required
inplace bool

If True, modify self and return None. If False (default), return a modified copy.

False

Returns:

Type Description
None or Bvh

change_euler_order(order: Union[str, Sequence[str]], joint: str | BvhNode | None = None, inplace: bool = False) -> Bvh | None

change_euler_order(order: Union[str, Sequence[str]], joint: str | BvhNode | None = ..., *, inplace: Literal[True]) -> None
change_euler_order(order: Union[str, Sequence[str]], joint: str | BvhNode | None = ..., inplace: Literal[False] = ...) -> Bvh

Change the Euler angle order of one or all joints.

Converts rotation data via rotation matrices so the resulting Euler angles use the new order but represent the same physical rotations.

Parameters:

Name Type Description Default
order str or list of 3 chars

New rotation order, e.g. 'XYZ' or ['X', 'Y', 'Z'].

required
joint str, BvhNode, or None

If a joint name or node is given, only that joint is changed. If None (default), all joints are changed to the new order.

None
inplace bool

If True, modify self and return None. If False, return a modified copy while leaving self unchanged.

False

Returns:

Type Description
None or Bvh

None if inplace, otherwise a new Bvh object.

to_rotmat() -> tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]]

Convert all per-joint Euler angles in self.frames to rotation matrices.

Returns:

Name Type Description
root_pos (ndarray, shape(num_frames, 3))

Root position for each frame.

joint_rotmats (ndarray, shape(num_frames, num_joints, 3, 3))

Rotation matrix for each joint in each frame. Joint order matches self.joint_names / self.joint_index.

Notes

When multiple rotation representations are needed (e.g. 6D for the model and quaternions for runtime SLERP), call to_rotmat once and apply the relevant rotations.rotmat_to_* primitives directly — forward kinematics runs once instead of per representation.

to_6d() -> tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]]

Convert all per-joint Euler angles to 6D rotation representation.

The 6D representation (Zhou et al., CVPR 2019) is continuous and well-suited for neural network training.

Returns:

Name Type Description
root_pos (ndarray, shape(num_frames, 3))

Root position for each frame.

joint_rot6d (ndarray, shape(num_frames, num_joints, 6))

6D rotation for each joint in each frame.

Notes

When multiple representations are needed, call :meth:to_rotmat once and apply :func:pybvh.rotations.rotmat_to_rot6d / rotmat_to_quat / rotmat_to_axisangle directly to avoid running FK more than once.

to_quaternions() -> tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]]

Convert all per-joint Euler angles to quaternions.

Returns:

Name Type Description
root_pos (ndarray, shape(num_frames, 3))

Root position for each frame.

joint_quats (ndarray, shape(num_frames, num_joints, 4))

Quaternion (w, x, y, z) for each joint in each frame.

Notes

See :meth:to_rotmat for the multi-representation reuse pattern.

to_axisangle() -> tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]]

Convert all per-joint Euler angles to axis-angle vectors.

The axis-angle representation is the unit rotation axis scaled by the rotation angle in radians. Used in SMPL/SMPL-X body models and many pose estimation pipelines.

Returns:

Name Type Description
root_pos (ndarray, shape(num_frames, 3))

Root position for each frame.

joint_aa (ndarray, shape(num_frames, num_joints, 3))

Axis-angle vector for each joint in each frame.

Notes

See :meth:to_rotmat for the multi-representation reuse pattern.

from_6d(root_pos: npt.ArrayLike, joint_rot6d: npt.ArrayLike, inplace: bool = False) -> Bvh | None

from_6d(root_pos: npt.ArrayLike, joint_rot6d: npt.ArrayLike, *, inplace: Literal[True]) -> None
from_6d(root_pos: npt.ArrayLike, joint_rot6d: npt.ArrayLike, inplace: Literal[False] = ...) -> Bvh

Set motion data from root positions and 6D rotation data.

Converts 6D rotations back to Euler angles using each joint's rot_channels order, then writes into root_pos and joint_angles.

Parameters:

Name Type Description Default
root_pos (array_like, shape(num_frames, 3))

Root position per frame.

required
joint_rot6d (array_like, shape(num_frames, num_joints, 6))

6D rotation per joint per frame. Joint order must match self.nodes (end sites excluded).

required
inplace bool

If True, modify self and return None. If False, return a modified copy while leaving self unchanged.

False

Returns:

Type Description
None or Bvh

None if inplace, otherwise a new Bvh object.

from_quaternions(root_pos: npt.ArrayLike, joint_quats: npt.ArrayLike, inplace: bool = False) -> Bvh | None

from_quaternions(root_pos: npt.ArrayLike, joint_quats: npt.ArrayLike, *, inplace: Literal[True]) -> None
from_quaternions(root_pos: npt.ArrayLike, joint_quats: npt.ArrayLike, inplace: Literal[False] = ...) -> Bvh

Set motion data from root positions and quaternion data.

Converts quaternions back to Euler angles using each joint's rot_channels order, then writes into root_pos and joint_angles.

Parameters:

Name Type Description Default
root_pos (array_like, shape(num_frames, 3))

Root position per frame.

required
joint_quats (array_like, shape(num_frames, num_joints, 4))

Quaternion (w, x, y, z) per joint per frame. Joint order must match self.nodes (end sites excluded).

required
inplace bool

If True, modify self and return None. If False, return a modified copy while leaving self unchanged.

False

Returns:

Type Description
None or Bvh

None if inplace, otherwise a new Bvh object.

from_axisangle(root_pos: npt.ArrayLike, joint_aa: npt.ArrayLike, inplace: bool = False) -> Bvh | None

from_axisangle(root_pos: npt.ArrayLike, joint_aa: npt.ArrayLike, *, inplace: Literal[True]) -> None
from_axisangle(root_pos: npt.ArrayLike, joint_aa: npt.ArrayLike, inplace: Literal[False] = ...) -> Bvh

Set motion data from root positions and axis-angle data.

Converts axis-angle vectors back to Euler angles using each joint's rot_channels order, then writes into root_pos and joint_angles.

Parameters:

Name Type Description Default
root_pos (array_like, shape(num_frames, 3))

Root position per frame.

required
joint_aa (array_like, shape(num_frames, num_joints, 3))

Axis-angle vector per joint per frame. Joint order must match self.nodes (end sites excluded).

required
inplace bool

If True, modify self and return None. If False, return a modified copy while leaving self unchanged.

False

Returns:

Type Description
None or Bvh

None if inplace, otherwise a new Bvh object.

slice_frames(start: int | None = None, end: int | None = None, step: int | None = None) -> Bvh

Return a new Bvh with a slice of frames.

Equivalent to bvh[start:end:step] (the sequence-protocol form). Use this functional form when you want explicit kwargs; use the slice form for natural Python syntax.

Parameters:

Name Type Description Default
start int or None

Slice parameters (same semantics as array[start:end:step]).

None
end int or None

Slice parameters (same semantics as array[start:end:step]).

None
step int or None

Slice parameters (same semantics as array[start:end:step]).

None

Returns:

Type Description
Bvh

New Bvh object with the sliced frames and same skeleton.

concat(other: Bvh) -> Bvh

Concatenate frames from another Bvh with the same skeleton.

Parameters:

Name Type Description Default
other Bvh

Must have the same skeleton (same node names and rotation orders).

required

Returns:

Type Description
Bvh

New Bvh with frames from self followed by other.

Raises:

Type Description
ValueError

If skeletons are incompatible (different node count, names, or rotation orders).

resample(target_fps: float) -> Bvh

Resample frames to a new frame rate via interpolation.

Root position is linearly interpolated. Joint rotations are converted to quaternions and interpolated with SLERP for smooth, gimbal-lock-free results. This is the rotation-aware alternative to naive per-channel linear interpolation on Euler angles, which produces wobble and gimbal-lock artifacts.

Parameters:

Name Type Description Default
target_fps float

Target frames per second.

required

Returns:

Type Description
Bvh

New Bvh with resampled frames.

extract_joints(joint_names: list[str]) -> Bvh

Extract a subset of joints into a new Bvh.

Removed joints' offsets are collapsed into their nearest kept descendant via vector addition (valid at rest pose). Their rotation contribution during animation is lost.

Parameters:

Name Type Description Default
joint_names list of str

Names of joints to keep. The root must be included. End sites are handled automatically (kept if their parent is kept, otherwise removed).

required

Returns:

Type Description
Bvh

New Bvh with the reduced skeleton and corresponding motion data.

Raises:

Type Description
ValueError

If the root joint is not in joint_names.

joint_velocities(centered: str = 'world', in_frames: bool = False, coords: npt.NDArray[np.float64] | None = None, stencil: str = 'central', pad: str = 'edge') -> npt.NDArray[np.float64]

Per-joint position velocities — shape (F, J, 3). See :func:pybvh.features.joint_velocities.

node_velocities(centered: str = 'world', in_frames: bool = False, coords: npt.NDArray[np.float64] | None = None, stencil: str = 'central', pad: str = 'edge') -> npt.NDArray[np.float64]

Per-node position velocities (joints + end sites) — shape (F, N, 3). See :func:pybvh.features.node_velocities.

joint_accelerations(centered: str = 'world', in_frames: bool = False, coords: npt.NDArray[np.float64] | None = None, stencil: str = 'central', pad: str = 'edge') -> npt.NDArray[np.float64]

Per-joint position accelerations — shape (F, J, 3). See :func:pybvh.features.joint_accelerations.

node_accelerations(centered: str = 'world', in_frames: bool = False, coords: npt.NDArray[np.float64] | None = None, stencil: str = 'central', pad: str = 'edge') -> npt.NDArray[np.float64]

Per-node position accelerations (joints + end sites) — shape (F, N, 3). See :func:pybvh.features.node_accelerations.

angular_velocities(in_frames: bool = False, stencil: str = 'central', pad: str = 'edge', degrees: bool = False) -> npt.NDArray[np.float64]

Compute per-joint angular velocities. See :func:pybvh.features.angular_velocities.

root_trajectory(up_axis: str | None = None, include_velocities: bool = False, stencil: str = 'central', pad: str = 'edge', degrees: bool = False) -> npt.NDArray[np.float64]

Extract root trajectory features. See :func:pybvh.features.root_trajectory.

foot_contacts(foot_joints: list[str] | None = None, method: str = 'combined', centered: str = 'world', coords: npt.NDArray[np.float64] | None = None, *, vel_threshold: float | None = None, height_threshold: float | None = None, floor: float | str = 'auto', min_contact_duration: float = 0.1, min_gap_duration: float = 0.1, return_info: bool = False) -> npt.NDArray[np.float64] | tuple[npt.NDArray[np.float64], dict]

Detect foot contact labels. See :func:pybvh.features.foot_contacts.

auto_detect_foot_joints() -> list[str]

Auto-detect foot joint names from skeleton topology. See :func:pybvh.features.auto_detect_foot_joints.

to_feature_array(representation: str = '6d', include_root_pos: bool = True, include_velocities: bool = False, include_foot_contacts: bool = False, centered: str = 'world', foot_joints: list[str] | None = None, stencil: str = 'central', pad: str = 'edge') -> npt.NDArray[np.float64]

Export motion as a flat feature array. See :func:pybvh.features.to_feature_array.

feature_array_layout(*, num_feet: int = 0, representation: str = '6d', include_root_pos: bool = True, include_velocities: bool = False, include_foot_contacts: bool = False) -> dict[str, slice]

Column layout of :meth:to_feature_array output. See :func:pybvh.features.feature_array_layout.

translate_root(offset: npt.ArrayLike, inplace: bool = False) -> Bvh | None

translate_root(offset: npt.ArrayLike, *, inplace: Literal[True]) -> None
translate_root(offset: npt.ArrayLike, inplace: Literal[False] = ...) -> Bvh

Shift root position by a constant offset. See :func:pybvh.transforms.translate_root.

add_noise(sigma_deg: float, sigma_pos: float = 0.0, rng: np.random.Generator | None = None, inplace: bool = False, wrap: bool = True) -> Bvh | None

add_noise(sigma_deg: float, *, sigma_pos: float = ..., rng: np.random.Generator | None = ..., inplace: Literal[True], wrap: bool = ...) -> None
add_noise(sigma_deg: float, sigma_pos: float = ..., rng: np.random.Generator | None = ..., inplace: Literal[False] = ..., wrap: bool = ...) -> Bvh

Add Gaussian noise to joint angles. See :func:pybvh.transforms.add_noise.

perturb_speed(factor: float) -> Bvh

Change motion speed by resampling. See :func:pybvh.transforms.perturb_speed.

drop_frames(drop_rate: float, rng: np.random.Generator | None = None, inplace: bool = False) -> Bvh | None

drop_frames(drop_rate: float, *, rng: np.random.Generator | None = ..., inplace: Literal[True]) -> None
drop_frames(drop_rate: float, rng: np.random.Generator | None = ..., inplace: Literal[False] = ...) -> Bvh

Replace dropped frames with SLERP interpolation. See :func:pybvh.transforms.drop_frames.

rotate_vertical(angle_deg: float, up_axis: str | None = None, inplace: bool = False) -> Bvh | None

rotate_vertical(angle_deg: float, *, up_axis: str | None = ..., inplace: Literal[True]) -> None
rotate_vertical(angle_deg: float, up_axis: str | None = ..., inplace: Literal[False] = ...) -> Bvh

Rotate entire motion around the vertical axis. See :func:pybvh.transforms.rotate_vertical.

mirror(left_right_mapping: dict[str, str] | None = None, lateral_axis: str | None = None, inplace: bool = False) -> Bvh | None

mirror(*, left_right_mapping: dict[str, str] | None = ..., lateral_axis: str | None = ..., inplace: Literal[True]) -> None
mirror(left_right_mapping: dict[str, str] | None = ..., lateral_axis: str | None = ..., inplace: Literal[False] = ...) -> Bvh

Mirror motion across the lateral plane. See :func:pybvh.transforms.mirror.

random_translate_root(range_xyz: tuple[float, float] = (-100.0, 100.0), rng: np.random.Generator | None = None) -> Bvh

Translate root by a random offset. See :func:pybvh.transforms.random_translate_root.

random_rotate_vertical(angle_range: tuple[float, float] = (-180.0, 180.0), up_axis: str | None = None, rng: np.random.Generator | None = None) -> Bvh

Rotate motion by a random angle around the vertical axis. See :func:pybvh.transforms.random_rotate_vertical.

random_perturb_speed(factor_range: tuple[float, float] = (0.8, 1.2), rng: np.random.Generator | None = None) -> Bvh

Apply a random speed change. See :func:pybvh.transforms.random_perturb_speed.

reorient_world_up(new_up: str, inplace: bool = False) -> Bvh | None

reorient_world_up(new_up: str, *, inplace: Literal[True]) -> None
reorient_world_up(new_up: str, inplace: Literal[False] = ...) -> Bvh

Change the world coordinate system's up axis. See :func:pybvh.transforms.reorient_world_up.

reorient_rest_up(new_up: str, inplace: bool = False) -> Bvh | None

reorient_rest_up(new_up: str, *, inplace: Literal[True]) -> None
reorient_rest_up(new_up: str, inplace: Literal[False] = ...) -> Bvh

Reorient rest-pose up axis without changing FK positions. See :func:pybvh.transforms.reorient_rest_up.

reorient_rest_forward(new_forward: str, inplace: bool = False) -> Bvh | None

reorient_rest_forward(new_forward: str, *, inplace: Literal[True]) -> None
reorient_rest_forward(new_forward: str, inplace: Literal[False] = ...) -> Bvh

Reorient rest-pose forward direction without changing FK positions. See :func:pybvh.transforms.reorient_rest_forward.

plot_rest_pose(**kwargs)

Plot the rest pose. See :func:pybvh.bvhplot.rest_pose.

plot_frame(frame=0, **kwargs)

Plot a single frame. See :func:pybvh.bvhplot.frame.

plot_trajectory(**kwargs)

Plot the root trajectory. See :func:pybvh.bvhplot.trajectory.

render(output_path, **kwargs)

Render animation to file. See :func:pybvh.bvhplot.render.

play(**kwargs)

Interactive playback. See :func:pybvh.bvhplot.play.