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 framejoint_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 ( |
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: |
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 |
joint_index |
dict
|
Mapping from joint name to its index in |
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 |
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. |
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. |
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
|
|
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_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. |
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
|
True
|
atol
|
float
|
Absolute tolerance for offset comparison (default |
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 |
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 |
None
|
Returns:
| Type | Description |
|---|---|
str
|
Signed axis string (e.g. |
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 |
None
|
Returns:
| Type | Description |
|---|---|
str
|
Signed axis string (e.g. |
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
|
centered
|
str
|
|
'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
|
centered
|
str
|
See :meth: |
'world'
|
rest_pose_coords(mode: str = 'coordinates') -> npt.NDArray[np.float64] | 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
|
|
'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'
|
centered
|
str
|
|
'world'
|
Returns:
| Type | Description |
|---|---|
dict
|
Column-name → 1-D array mapping, ready for |
hierarchy_info_as_dict() -> dict
¶
Return the skeleton hierarchy as a plain dictionary.
Returns:
| Type | Description |
|---|---|
dict
|
|
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'
|
Returns:
| Type | Description |
|---|---|
int
|
|
Raises:
| Type | Description |
|---|---|
KeyError
|
If |
ValueError
|
If |
retarget(new_skeleton: Bvh, name_mapping: dict[str, str] | None = None, strict: bool = False, inplace: bool = False) -> Bvh | None
¶
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 |
None
|
strict
|
bool
|
If True, raise |
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 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 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 |
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
¶
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
¶
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
¶
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 |
None
|
end
|
int or None
|
Slice parameters (same semantics as |
None
|
step
|
int or None
|
Slice parameters (same semantics as |
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 |
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_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
¶
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 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
¶
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 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 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
¶
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-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-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.