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, warn_on_disagreement: bool = True)
¶
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 |
Construction & I/O¶
Create a Bvh from a file, a DataFrame, or another instance; write it back losslessly.
from_file(filepath: str | Path, world_up: str = 'auto', warn_on_world_up_disagreement: bool = True, lr_mapping: dict[str, str] | None = None) -> Bvh
classmethod
¶
Read a Bvh from a .bvh file — the constructor counterpart of :meth:write.
Delegates to :func:pybvh.io.read_bvh_file; see it for parameter
details.
from_df(hier: list[BvhNode] | dict[str, dict], df) -> Bvh
classmethod
¶
Build a Bvh from a hierarchy description and a motion DataFrame.
The constructor counterpart of :meth:to_df_dict /
:meth:to_hierarchy_dict. Delegates to :func:pybvh.df_to_bvh;
see it for the expected column naming and hierarchy formats.
write(filepath: str | Path, verbose: bool = False, overwrite: bool = True) -> None
¶
Write the Bvh object to a .bvh file.
Pass overwrite=False to raise FileExistsError rather than
replace an existing file. See :func:pybvh.io.write_bvh_file.
copy() -> Bvh
¶
Data, metadata & skeleton introspection¶
The raw motion arrays, the joint hierarchy, and the two index spaces (see the Core Concepts guide).
nodes: list[BvhNode]
property
writable
¶
root: BvhRoot
property
writable
¶
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).
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)
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. |
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
|
|
index(name: str, space: Literal['joint', 'node']) -> int
¶
Look up the integer index for name in the requested index space.
Unambiguous alternative to picking between :attr:joint_index
and :attr:node_index at the call site. Use space='joint'
when indexing :attr:joint_angles / :meth:joint_velocities /
:meth:joint_accelerations / :meth:joint_positions /
:meth:angular_velocities (any (F, J, ...) array). Use
space='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 |
space
|
('joint', 'node')
|
Which index space to look up. |
'joint'
|
Returns:
| Type | Description |
|---|---|
int
|
|
Raises:
| Type | Description |
|---|---|
KeyError
|
If |
ValueError
|
If |
joint_tips: dict[str, int | None]
property
¶
Mapping from joint name to its end-site node index, or None.
For every non-end-site joint (root included): the node-space index of the joint's end-site child — a row of :meth:node_positions output — or None for interior joints whose children are all joints. The tip is the bone's far end, so bvh.node_positions()[:, bvh.joint_tips["LeftFoot"]] is the toe-tip trajectory without knowing the end site's generated display name.
Resolution is identity-based on the node tree, never by name: end-site display names are cosmetic (the parser generates 'EndSite' + parent name) and may even collide with a real joint's name, which would make a name-keyed lookup through :attr:node_index silently pick the wrong node. A joint with several end-site children (nonstandard, but the parser accepts it) maps to the first one in file order.
Returns:
| Type | Description |
|---|---|
dict
|
|
See Also
node_index : Name → node index for every node (joints and end sites). nodes : The flat depth-first node list these indices point into.
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.
Parents are resolved by node identity (via
:attr:fk_topology), never by name — see :attr:node_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.
Parents are resolved by node identity (via
:attr:fk_topology), never by name. Node names are not unique in
general — the parser derives end-site display names from the
parent joint's name, so two end sites under one joint collide —
and a name-keyed lookup would silently emit an edge pointing at
the wrong parent, yielding a graph of the right length that
encodes the wrong skeleton.
fk_topology: FkTopology
property
¶
The skeleton as plain arrays — everything forward kinematics reads.
Bone offsets, parent indices, joint-column indices and Euler
orders, with no node objects: see :class:~pybvh.FkTopology.
Pass it to :func:~pybvh.frames_to_node_positions to run FK from
a preprocessed dataset, where the source .bvh is long closed.
It is also this class's single derivation of skeleton topology —
:attr:edges, :attr:node_edges and the bone list the
:mod:~pybvh.bvhplot backends draw are all views of its
parent_idx, so they cannot disagree with the geometry FK
produces.
Recomputed per access rather than cached: it is an O(N) walk over
a few dozen nodes, and a cache would need invalidating on every
operation that touches offsets or channel order (:meth:retarget,
:meth:scale, :meth:mirror, :meth:change_euler_order) for a
saving that does not show up in a profile.
Returns:
| Type | Description |
|---|---|
FkTopology
|
|
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 / quat /
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, quat,
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).
to_hierarchy_dict() -> dict
¶
Return the skeleton hierarchy as a plain dictionary.
The inverse-direction counterpart of :meth:from_df's hier
argument.
Returns:
| Type | Description |
|---|---|
dict
|
|
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 |
Orientation & frames of reference¶
Up axis, facing direction, L/R pairs, and the reorientation family (see the World Up guide).
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.
The detection specifics, since they decide which answer you get:
"head" is the first exact lowercase name match among
head, neck, chest, spine (so a namespaced
mixamorig:Head does not match), "hips" is always the root
node whatever it is called, and the frame-0 reading is accepted
only when its largest component exceeds twice the second-largest
— a crouched, lying or leaning first frame is treated as
ambiguous and silently defers to :attr:rest_up. When the rest
pose is degenerate too, the property warns and returns '+y'.
Use :attr:world_up_inferred to see what the heuristic picks
while an override is in effect.
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(), frame slicing
(bvh[a:b]), and transforms that don't change the world
coordinate system (mirror, rotate_vertical, scale,
translate_root). retarget() re-infers from the new skeleton.
Assign 'auto' or None to clear a previous override and
return to auto-detection.
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)
up_axis: Axis
property
¶
:attr:world_up parsed into numeric form — Axis(index, sign, vector).
The three fields are the machine-usable views of the same signed axis string (always derived from the resolved :attr:world_up, so manual overrides are respected):
index(int): the up coordinate's column (0 = x, 1 = y, 2 = z) in any(..., 3)position array.sign(float):+1.0or-1.0— multiply the raw coordinate by it to get an up-positive height.vector(ndarray, shape(3,)): the unit vector along the up direction, sign included (e.g.[0, -1, 0]for'-y'). A fresh array on every access — mutating it never corrupts the Bvh.
Example — up-positive heights of every node in every frame:
>>> coords = bvh.node_positions()
>>> heights = coords[:, :, bvh.up_axis.index] * bvh.up_axis.sign
See Also
world_up : The signed axis string this is parsed from (settable). forward_axis, rest_up_axis : The other two parsed axis properties. floor_height : The estimated ground level along this axis, in raw (unsigned) coordinates.
Axis = namedtuple('Axis', ['index', 'sign', 'vector'])
module-attribute
¶
parse_axis(axis: str, *, allow_unsigned: bool = False) -> Axis
¶
Parse a signed axis string into an :class:Axis.
The public form of the parser behind :attr:Bvh.up_axis and friends,
for code that holds an axis string of its own — a dataset convention
from a config file, or one of the axis strings pybvh returns.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
axis
|
str
|
Signed axis string: |
required |
allow_unsigned
|
bool
|
If True, also accept a bare letter ( |
False
|
Returns:
| Type | Description |
|---|---|
Axis
|
The parsed |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Examples:
rest_up: str | None
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 or None
|
Signed axis string (e.g. |
rest_up_axis: Axis | None
property
¶
:attr:rest_up parsed into numeric form — Axis(index, sign, vector), or None.
None exactly when :attr:rest_up is None — a degenerate rest pose (single-node skeleton, or all joints coincident) that carries no directional information. Each parsed axis property mirrors the nullability of the string it parses, so bvh.rest_up is None and bvh.rest_up_axis is None always agree.
Note this is the topological up axis. For the world vertical — animation-derived, and always defined — use :attr:up_axis.
See Also
rest_up : The signed axis string this is parsed from. up_axis, forward_axis : The other two parsed axis properties.
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. |
forward_axis: Axis
property
¶
:attr:rest_forward parsed into numeric form — Axis(index, sign, vector).
Never None: :attr:rest_forward always resolves, because forward is defined relative to :attr:world_up and falls back to an arbitrary-but-stable horizontal axis when the skeleton carries no usable L/R geometry. That fallback is indistinguishable here from a measured result — see :attr:rest_forward for the chain.
See Also
rest_forward : The signed axis string this is parsed from. up_axis, rest_up_axis : The other two parsed axis properties.
floor_height: float
property
¶
Estimated ground-plane height, in raw world coordinates along world_up.
A single scalar: the floor level in the BVH's own coordinate system,
signed along the raw up axis (so for world_up='-y' a floor at raw
y≈5 returns ≈5). It is the 2nd-percentile of the per-frame
minimum foot height over auto-detected feet (all nodes for footless
rigs); see :func:pybvh.analysis._compute_floor_height. The 2nd
percentile is the canonical robust estimate — resistant to
occasional glitched-low frames; for the true minimum, or any other
convention, call foot_contacts(floor="min") / pass an explicit
float per call (this property stays 2nd-percentile). This is the
scene's ground plane — foot_contacts layers a per-foot stance hover on
top of it.
Lazily computed and cached; the cache is invalidated whenever
root_pos or joint_angles is reassigned.
:func:~pybvh.analysis.foot_contacts fills/serves this cache on its
default world-coords + auto-detected-feet path (with explicit
coords= or foot_joints= it estimates its own per-call floor).
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.
This is the snapped classification — the continuous facing
vector is quantized to the nearest of the six signed world
axes. See :meth:facing_frame for the continuous per-frame
basis as unit vectors.
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. |
See Also
facing_frame : The continuous per-frame basis (vectors, all frames at once). left_at : Leftward direction.
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.
This is the snapped classification — see :meth:facing_frame
for the continuous per-frame basis as unit vectors.
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. facing_frame : The continuous per-frame basis (vectors, all frames at once). world_up : World vertical axis.
facing_frame(coords: npt.NDArray[np.float64] | None = None)
¶
Per-frame facing basis as continuous unit vectors.
Returns a FacingFrame(forward, left, up, valid) named tuple:
three (F, 3) arrays — the yaw-only, gravity-aligned
orthonormal basis that :meth:forward_at / :meth:left_at
snap to axis labels — plus a (F,) bool array, False on
frames whose basis is the constant fallback rather than a
measurement. See :func:pybvh.analysis.facing_frame for the
full construction, conventions, and fallback policy.
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(), facing_frame(),
_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_lr_pairs: list[tuple[int, int]] | None
property
¶
Left/right node pairs as index tuples in nodes index space.
Node-space counterpart of :attr:lr_pairs, covering joints and
their end sites — so a mirror over :meth:node_positions output
can swap every paired vertex, including the fingertips, toe tips
and head top that only exist in node space. None when no L/R
mapping is available, the same sentinel as :attr:lr_pairs and
:attr:lr_mapping.
Order is deterministic: every joint pair in :attr:lr_mapping
order first, then the end-site pairs in that same order, so
metadata built from it reproduces across runs.
Left and right joints are matched by name (that is what
:attr:lr_mapping is), but looked up among joints only, so an
end site whose generated display name collides with a joint's
cannot shadow it. The end sites themselves are matched
positionally within each joint and resolved by identity.
A joint pair whose two sides carry different numbers of end
sites has no well-defined tip correspondence. The pair itself is
still returned and its end sites are dropped, matching
:attr:lr_pairs' habit of filtering what it cannot resolve rather
than raising from a property. :func:~pybvh.transforms.mirror
raises on the same condition instead, because a half-swapped
skeleton is a wrong answer rather than a partial one.
Returns:
| Type | Description |
|---|---|
list of (int, int) or None
|
|
See Also
lr_pairs : The same pairing in joint_angles index space.
joint_tips : Each joint's first end site, for tip trajectories.
has_lr_geometry: bool
property
¶
Whether the rest pose carries usable left/right direction.
True when the skeleton's L/R joint pairs give a lateral axis
that is neither degenerate nor parallel to :attr:world_up —
i.e. when the orientation properties are measuring this
skeleton rather than falling back to a default. The check walks
the same chain :attr:rest_forward computes with, so it is
False exactly when deriving a facing from the rest pose
emits the fallback UserWarning. In particular, on a file
whose rest-pose and animation up axes disagree (the case
:attr:world_up inference warns about), an L/R axis parallel
to :attr:world_up is unusable and reports False even
though the pairs themselves exist.
This is the check :attr:rest_forward (and so
:attr:forward_axis, :meth:forward_at, :meth:left_at and
facing_frame) cannot express in its own return value. Those
always yield an axis: with no usable L/R geometry they return an
arbitrary-but-stable horizontal axis chosen from
:attr:world_up alone, which is indistinguishable from a
measured result. When it matters whether a facing was derived
from the data — comparing a skeleton against a dataset
convention, say, where a fallback would "match" every time —
check this first.
Assigning :attr:lr_mapping explicitly is what fixes a False
on a skeleton whose joints simply are not named recognizably.
This property describes the rest-pose chain. The per-frame
:meth:forward_at / :meth:facing_frame measure each frame's
coordinates first and only fall back to this chain on frames
where that fails, so on a skeleton with zero rest offsets but
animated L/R separation they can still measure while this
reports False.
Example
>>> if bvh.has_lr_geometry:
... assert bvh.rest_forward == dataset_convention
... else:
... ... # facing is a default, not a measurement
See Also
rest_forward : The axis whose fallback this reports. lr_mapping : The pairs the measurement is derived from.
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.
Forward kinematics¶
From joint angles to 3D positions — the centered modes are drawn in the Gallery.
node_positions(frame: int | None = None, 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.
World-frame forward kinematics is cached across calls (invalidated
whenever motion data changes), so repeated calls — including with
different centered modes — only pay for FK once.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
frame
|
int or None
|
Frame index to return. |
None
|
centered
|
str
|
|
'world'
|
joint_positions(frame: int | None = None, 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
|
int or None
|
Frame index to return. |
None
|
centered
|
str
|
See :meth: |
'world'
|
rest_pose_positions() -> npt.NDArray[np.float64]
¶
Rest-pose node positions (all angles zero, root at origin) — (N, 3).
Derived from the skeleton offsets alone, so it works on Bvh objects
with no motion data. Use :attr:node_index to look up rows by name.
rest_pose_angles() -> npt.NDArray[np.float64]
¶
Rest-pose joint angles — zeros of shape (J, 3) (radians).
Companion of :meth:rest_pose_positions in joint_angles space
(one single-frame row, matching :attr:joint_index).
Rotation representations¶
Conversions to and from every representation (see Choosing a Representation).
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_quat() -> 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, in
canonical form ( |
Notes
The canonical form is applied per frame, so the returned
sequence is not guaranteed to be temporally continuous: a joint
rotating through 180° flips sign between adjacent frames even
though the motion is smooth. Every quaternion is still exactly
the right rotation; it is the representation that jumps. Wrap
with :func:pybvh.rotations.quat_unwrap when feeding the array
to anything that differences or measures distance on the raw
values.
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_rotmat(root_pos: npt.ArrayLike, joint_rotmats: npt.ArrayLike, inplace: bool = False) -> Bvh | None
¶
Set motion data from root positions and rotation matrices.
Converts rotation matrices back to Euler angles using each joint's
rot_channels order, then writes into root_pos and joint_angles.
Inverse of :meth:to_rotmat.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
root_pos
|
(array_like, shape(num_frames, 3))
|
Root position per frame. |
required |
joint_rotmats
|
(array_like, shape(num_frames, num_joints, 3, 3))
|
Rotation matrix 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_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_quat(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. |
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. |
Frame & skeleton operations¶
Timeline and skeleton editing (see the Skeleton Operations guide). Slicing and concatenation use plain Python syntax: bvh[10:50] returns the frame range as a new Bvh, and bvh_a + bvh_b concatenates two clips with matching skeletons.
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.
The new timestamps are 0, 1/fps, 2/fps, … up to the original
clip's duration — anchored at t = 0, and the last sample is
the largest multiple of the new period that still fits. The
original final frame is reproduced only when the duration is an
exact multiple of that period; otherwise the clip is shortened
by up to one new frame period. The alternative convention —
stretch the grid to land exactly on the final frame, giving
round(duration · fps) + 1 samples and an irregular last
interval — keeps the endpoint at the cost of an inexact rate.
Clips shorter than two frames have nothing to interpolate and
simply adopt the new frame_time.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
target_fps
|
float
|
Target frames per second. |
required |
Returns:
| Type | Description |
|---|---|
Bvh
|
New Bvh with resampled frames. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
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.
source_path, a manual world_up override, and a user-set
lr_mapping (filtered to pairs whose joints are both kept) are
preserved on the result.
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 |
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, inplace: bool = False) -> Bvh | None
¶
Uniformly scale all node offsets and the root translation.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
scale
|
float
|
Uniform scale factor. Only scalars are accepted: per-axis world factors applied to parent-local offsets are not geometrically meaningful once joints rotate during animation. |
required |
inplace
|
bool
|
If True, modify self and return None. If False (default), return a modified copy. |
False
|
Returns:
| Type | Description |
|---|---|
None or Bvh
|
|
Kinematics, trajectory & contacts¶
The velocity ladder, the root-trajectory features, and foot-contact detection (see the Feature Export guide).
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.analysis.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.analysis.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.analysis.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.analysis.node_accelerations.
joint_speed_derivative(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 rate of change of speed d‖v‖/dt — shape (F, J). See :func:pybvh.analysis.joint_speed_derivative.
node_speed_derivative(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 rate of change of speed d‖v‖/dt (joints + end sites) — shape (F, N). See :func:pybvh.analysis.node_speed_derivative.
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.analysis.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.analysis.root_trajectory.
foot_contacts(foot_joints: list[str] | None = None, method: str = 'combined', coords: npt.NDArray[np.float64] | None = None, *, vel_threshold: float | None = None, vel_smooth_duration: float = 1.0 / 30.0, height_threshold: float | None = None, floor: float | str = 'auto', min_contact_duration: float = 0.1, min_gap_duration: float = 0.1, hysteresis: float = 0.25, adaptive: bool = False, height_reference: str = 'velocity', return_info: bool = False) -> npt.NDArray[np.float64] | tuple[npt.NDArray[np.float64], dict]
¶
Detect foot contact labels. See :func:pybvh.analysis.foot_contacts.
ground_contacts(joints: Sequence[str | int], method: str = 'combined', coords: npt.NDArray[np.float64] | None = None, *, vel_threshold: float | None = None, vel_smooth_duration: float = 1.0 / 30.0, height_threshold: float | None = None, floor: float | str = 'auto', min_contact_duration: float = 0.1, min_gap_duration: float = 0.1, hysteresis: float = 0.25, adaptive: bool = False, height_reference: str = 'floor', return_info: bool = False) -> npt.NDArray[np.float64] | tuple[npt.NDArray[np.float64], dict]
¶
Detect ground contacts for an arbitrary joint set. See :func:pybvh.analysis.ground_contacts.
auto_detect_foot_joints() -> list[str]
¶
Auto-detect foot joint names from skeleton topology. See :func:pybvh.analysis.auto_detect_foot_joints.
Feature export¶
The one-stop flat (F, D) array for ML pipelines.
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.
Trajectory & pose geometry¶
Position descriptors — each drawn in the Gallery; array-pure kernels in pybvh.geometry.
curvature(joint: str, stencil: str = 'central', pad: str = 'edge', *, coords: npt.NDArray[np.float64] | None = None) -> npt.NDArray[np.float64]
¶
Per-frame trajectory curvature of joint. See :func:pybvh.geometry.curvature.
torsion(joint: str, stencil: str = 'central', pad: str = 'edge', *, coords: npt.NDArray[np.float64] | None = None) -> npt.NDArray[np.float64]
¶
Per-frame trajectory torsion of joint. See :func:pybvh.geometry.torsion.
movement_phase(joint: str, stencil: str = 'central', pad: str = 'edge', *, coords: npt.NDArray[np.float64] | None = None) -> npt.NDArray[np.float64]
¶
Per-frame movement phase (speed · curvature) of joint.
See :func:pybvh.geometry.movement_phase.
path_length(joint: str, *, coords: npt.NDArray[np.float64] | None = None) -> float
¶
Arc length travelled by joint. See :func:pybvh.geometry.path_length.
directness(joint: str, *, coords: npt.NDArray[np.float64] | None = None) -> float
¶
Directness of joint's path (net displacement ÷ path length).
See :func:pybvh.geometry.directness.
ground_path(joint: str, *, coords: npt.NDArray[np.float64] | None = None) -> 'geometry.GroundPath'
¶
Ground-plane path of joint (uses world_up). See :func:pybvh.geometry.ground_path.
inter_joint_distance(pairs: list[tuple[str, str]], *, coords: npt.NDArray[np.float64] | None = None) -> npt.NDArray[np.float64]
¶
Per-frame distances between node pairs. See :func:pybvh.geometry.inter_joint_distance.
joint_angle(a: str, vertex: str, b: str, degrees: bool = False, *, coords: npt.NDArray[np.float64] | None = None) -> npt.NDArray[np.float64]
¶
Per-frame angle at vertex in a–vertex–b. See :func:pybvh.geometry.joint_angle.
triangle_area(a: str, b: str, c: str, *, coords: npt.NDArray[np.float64] | None = None) -> npt.NDArray[np.float64]
¶
Per-frame area of triangle (a, b, c). See :func:pybvh.geometry.triangle_area.
segment_axis_angle(joint_a: str, joint_b: str, degrees: bool = False, *, coords: npt.NDArray[np.float64] | None = None) -> npt.NDArray[np.float64]
¶
Per-frame angle of the bone joint_a→joint_b to world_up.
See :func:pybvh.geometry.segment_axis_angle.
bounding_box(*, coords: npt.NDArray[np.float64] | None = None) -> 'geometry.BoundingBox'
¶
Per-frame axis-aligned bounding box of all nodes. See :func:pybvh.geometry.bounding_box.
bounding_sphere(*, coords: npt.NDArray[np.float64] | None = None) -> 'geometry.BoundingSphere'
¶
Per-frame approximate enclosing sphere of all nodes. See :func:pybvh.geometry.bounding_sphere.
bounding_ellipsoid(*, coords: npt.NDArray[np.float64] | None = None) -> 'geometry.BoundingEllipsoid'
¶
Per-frame PCA-aligned bounding ellipsoid of all nodes. See :func:pybvh.geometry.bounding_ellipsoid.
center_of_mass(weights: npt.NDArray[np.float64] | None = None, *, coords: npt.NDArray[np.float64] | None = None) -> npt.NDArray[np.float64]
¶
Per-frame centre of mass of all nodes (uniform by default; pass per-node masses).
See :func:pybvh.geometry.center_of_mass.
com_displacement(weights: npt.NDArray[np.float64] | None = None, com_ref: npt.NDArray[np.float64] | None = None, *, coords: npt.NDArray[np.float64] | None = None) -> npt.NDArray[np.float64]
¶
Per-frame centre-of-mass travel from a reference.
com_ref defaults to the first frame's centre of mass, in the
same world frame as :meth:center_of_mass, so the result is how far
the CoM has travelled since the start (0 at frame 0). Pass an
explicit com_ref (e.g. center_of_mass().mean(0)) for a
different baseline. See :func:pybvh.geometry.com_displacement.
verticality(*, coords: npt.NDArray[np.float64] | None = None) -> npt.NDArray[np.float64]
¶
Per-frame height/width ratio along world_up. See :func:pybvh.geometry.verticality.
Dynamics, smoothness & gait¶
Dynamic descriptors (see the Motion Descriptors guide); array-pure kernels in pybvh.analysis.
node_jerk(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 jerk — (F, N, 3). See :func:pybvh.analysis.node_jerk.
joint_jerk(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 jerk — (F, J, 3). See :func:pybvh.analysis.joint_jerk.
smoothness(joint: str, metric: str = 'sparc', *, coords: npt.NDArray[np.float64] | None = None, **kwargs: Any) -> float
¶
Smoothness of joint's speed profile. See :func:pybvh.analysis.smoothness.
Computes the joint's per-frame speed ‖velocity‖ and passes it
to the chosen metric at sampling rate 1 / frame_time.
metric is one of "sparc" (default),
"dimensionless_jerk", "log_dimensionless_jerk",
"integrated_squared_jerk", "mean_squared_jerk",
"rms_squared_jerk", "number_of_peaks", "speed_metric".
Metric-specific options are forwarded as keyword arguments —
"sparc" accepts padlevel (FFT zero-padding exponent,
default 4), fc (max cutoff frequency in Hz, default
10.0) and amp_th (amplitude threshold, default 0.05);
"dimensionless_jerk" and "log_dimensionless_jerk" accept
normalize ("peak_speed" default, "mean_speed" or
"amplitude") and amplitude; "number_of_peaks"
accepts min_height (minimum height for a maximum to count;
default counts all). The remaining metrics take none.
velocity_reductions(joint: str, *, coords: npt.NDArray[np.float64] | None = None)
¶
Scalar reductions of joint's speed profile (peak, mean, …).
Computes the joint's per-frame speed ‖velocity‖ and reduces it
at sampling rate 1 / frame_time. See
:func:pybvh.analysis.velocity_reductions.
kinetic_energy(masses: npt.NDArray[np.float64] | Mapping[str, float] | None = None, centered: str = 'world', stencil: str = 'central', pad: str = 'edge') -> npt.NDArray[np.float64]
¶
Per-frame kinetic energy over joints. masses may be a (J,) array
or a {joint_name: mass} mapping. See :func:pybvh.analysis.kinetic_energy.
skeleton_size(foot_joints: list[str] | None = None) -> float
¶
Absolute skeleton scale — mean rest-pose root-to-foot distance.
Raises ValueError for a skeleton whose size cannot be
measured (no feet found, or all feet on the root) rather than
returning a substitute. See :func:pybvh.analysis.skeleton_size.
cadence(foot_joints: list[str] | None = None, *, contacts: npt.NDArray[np.float64] | None = None) -> float
¶
Step rate (onsets/second). See :func:pybvh.analysis.cadence.
stride_length(foot_joints: list[str] | None = None, *, contacts: npt.NDArray[np.float64] | None = None) -> float
¶
Mean stride length. See :func:pybvh.analysis.stride_length.
walking_pace() -> float
¶
Mean horizontal speed. See :func:pybvh.analysis.walking_pace.
gait_parameters(foot_joints: list[str] | None = None, *, contacts: npt.NDArray[np.float64] | None = None)
¶
Spatiotemporal gait parameters. See :func:pybvh.analysis.gait_parameters.
range_of_motion(joint: str) -> npt.NDArray[np.float64]
¶
Peak-to-peak range of joint's Euler angles — (3,) per channel.
Indexes in JOINT space (rotations exist only on joints). See
:func:pybvh.analysis.range_of_motion.
Augmentation transforms¶
Seeded data augmentation (see the Data Augmentation guide); array-level functions in pybvh.transforms.
mirror(lr_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.
rotate_vertical(angle: float, up_axis: str | None = None, degrees: bool = False, pivot: str | npt.ArrayLike = 'origin', inplace: bool = False) -> Bvh | None
¶
Rotate entire motion around the vertical axis (angle in radians), about the world origin or pivot=. See :func:pybvh.transforms.rotate_vertical.
translate_root(offset: npt.ArrayLike, inplace: bool = False) -> Bvh | None
¶
Shift root position by a constant offset. See :func:pybvh.transforms.translate_root.
add_rotation_noise(sigma: float, rng: np.random.Generator | None = None, inplace: bool = False, wrap: bool = False, degrees: bool = False) -> Bvh | None
¶
Add Gaussian noise (sigma in radians, or degrees with degrees=True) to joint angles. See :func:pybvh.transforms.add_rotation_noise.
add_position_noise(sigma: float, rng: np.random.Generator | None = None, inplace: bool = False) -> Bvh | None
¶
Add Gaussian noise (sigma in the skeleton's length unit) to the root translation. See :func:pybvh.transforms.add_position_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.
random_translate_root(offset_range: 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] = (-np.pi, np.pi), up_axis: str | None = None, degrees: bool = False, pivot: str | npt.ArrayLike = 'origin', rng: np.random.Generator | None = None) -> Bvh
¶
Rotate motion by a random angle around the vertical axis (radians). 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.
Visualization¶
Quick-look plotting on the object; multi-skeleton comparison lives in pybvh.bvhplot.
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(filepath: str | Path = Path('./anim.mp4'), **kwargs)
¶
Render animation to file. See :func:pybvh.bvhplot.render.
play(**kwargs)
¶
Interactive playback. See :func:pybvh.bvhplot.play.