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, 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 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(), frame slicing (bvh[a:b]), and rotations / transforms that don't change which file the data originated from. Cleared (set to None) when concatenation (a + b) joins two clips whose source_path differ. Writable — callers can assign manually when constructing a Bvh from arrays.

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. ['ZYX', 'ZYX', ...], one per non-end-site joint. Order matches joint_names and joint_angles axis 1.

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).

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' excludes end sites.

'joint'

Returns:

Type Description
int

Raises:

Type Description
KeyError

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

ValueError

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

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

{joint_name: int | None} for every non-end-site joint, in topological (joint_names) order. A fresh dict per access — mutate freely.

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

joint_idx counts up in :attr:nodes order, so euler_orders matches :attr:euler_orders element for element.

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 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, 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

{name: {'offset': [...], 'parent': str|None, 'rot_channels': [...], 'children': [...]}, ...}. Root entries also include 'pos_channels'. All values are copies (safe to mutate).

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" — see :meth:node_positions for their semantics. Only used when mode='coordinates'.

'world'

Returns:

Type Description
dict

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

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.0 or -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: '+x', '-x', '+y', '-y', '+z', '-z'. The axis letter is case-insensitive.

required
allow_unsigned bool

If True, also accept a bare letter ('x'), reading it as the positive direction. Default False — a bare letter states no direction, and defaulting it to positive invents one the caller did not ask for. Use it only where the sign is genuinely irrelevant, as for mirror's lateral_axis.

False

Returns:

Type Description
Axis

The parsed (index, sign, vector) triple.

Raises:

Type Description
ValueError

If axis is not one of the accepted strings.

Examples:

>>> parse_axis('-z').index, parse_axis('-z').sign
(2, -1.0)
>>> heights = coords[..., up.index] * up.sign   # up = parse_axis('-y')

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. '+y', '+z'), or None when the rest pose is degenerate (single-node skeletons or all-zero offsets) and carries no directional information.

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. '+z', '-x').

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 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.

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 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. 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

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.

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 (default) returns all frames. Negative indices count from the end (NumPy semantics), so -1 is the last frame.

None
centered str

"world" – root at actual position. "skeleton" – root at origin for all frames. "first" – ground-plane centering: the first frame's root position is subtracted in the two axes perpendicular to :attr:world_up; the up coordinate is untouched, so the motion starts above the origin at its original height.

'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 (default) returns all frames; negative indices count from the end (-1 = last frame).

None
centered str

See :meth:node_positions.

'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 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_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 (w >= 0).

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

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

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

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_quat(root_pos: npt.ArrayLike, joint_quats: npt.ArrayLike, inplace: bool = False) -> Bvh | None

from_quat(root_pos: npt.ArrayLike, joint_quats: npt.ArrayLike, *, inplace: Literal[True]) -> None
from_quat(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.

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.

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 target_fps is not positive.

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 joint_names.

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, inplace: bool = False) -> Bvh | None

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

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(*, lr_mapping: dict[str, str] | None = ..., lateral_axis: str | None = ..., inplace: Literal[True]) -> None
mirror(lr_mapping: dict[str, str] | None = ..., lateral_axis: str | None = ..., inplace: Literal[False] = ...) -> Bvh

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_vertical(angle: float, *, up_axis: str | None = ..., degrees: bool = ..., pivot: str | npt.ArrayLike = ..., inplace: Literal[True]) -> None
rotate_vertical(angle: float, up_axis: str | None = ..., degrees: bool = ..., pivot: str | npt.ArrayLike = ..., inplace: Literal[False] = ...) -> Bvh

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

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_rotation_noise(sigma: float, rng: np.random.Generator | None = None, inplace: bool = False, wrap: bool = False, degrees: bool = False) -> Bvh | None

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

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_position_noise(sigma: float, *, rng: np.random.Generator | None = ..., inplace: Literal[True]) -> None
add_position_noise(sigma: float, rng: np.random.Generator | None = ..., inplace: Literal[False] = ...) -> Bvh

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

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.

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.