Skip to content

Feature Metadata

metadata

Feature column descriptors for packed arrays.

Enables programmatic access to feature slices without hardcoded column indices.

FeatureDescriptor(ranges: dict[str, tuple[int, int]] = dict(), total_dim: int = 0) dataclass

Maps feature names to (start_col, end_col) ranges.

Attributes:

Name Type Description
ranges dict

{feature_name: (start, end)} column index ranges.

total_dim int

Total number of feature columns.

slice(key: str) -> slice

Return a :class:slice for the named feature.

GraphDescriptor(num_channels: int = 0, num_vertices: int = 0, packs_root: bool = False, first_vertex: int = 0, channel_ranges: dict[str, tuple[int, int]] = dict()) dataclass

The (C, V) geometry of a graph-layout pack.

What :func:~pybvh_ml.pack_to_ctv and :func:~pybvh_ml.pack_to_tvc will produce for a given streams, without packing anything.

Attributes:

Name Type Description
num_channels int

C.

num_vertices int

V.

packs_root bool

Whether vertex 0 is the root. When True the root's position occupies channels 0:3 of vertex 0 only, and any channels beyond that on that vertex are zero padding; the per-vertex streams live on vertices 1:.

first_vertex int

Index of the first per-vertex-stream vertex — 1 when the root is packed, 0 otherwise. The offset to add to skeleton_info["edges"] to index packed vertices.

channel_ranges dict

{block_name: (start, end)} along C, for the per-vertex streams in packing order. root_pos is deliberately not a key: it is a vertex, not a channel block, which is the rule most often lost when predicting these shapes by hand.

slice(key: str) -> slice

Return a :class:slice along C for the named block.

shape(num_frames: int, layout: str = 'ctv') -> tuple[int, ...]

The packed shape for num_frames frames in layout.

"ctv" gives (C, T, V), "tvc" gives (T, V, C) — the same geometry, transposed.

describe_features(num_joints: int, representation: str = '6d', include_root_pos: bool = True, *, streams: tuple[str, ...] | list[str] | None = None, num_nodes: int | None = None) -> FeatureDescriptor

Build a :class:FeatureDescriptor for a flat (T, D) layout.

Describes the layout :func:pybvh_ml.pack_to_flat produces for the same streams. For a layout that also covers velocities and foot contacts (as written by pybvh.Bvh.to_feature_array), use :meth:pybvh.Bvh.feature_array_layout instead — it returns a {block_name: slice} dict covering the full feature array.

Parameters:

Name Type Description Default
num_joints int

Number of joints (excluding end sites).

required
representation str

Rotation representation name. One of "euler", "quat", "6d", "axisangle", "rotmat". Read only when "joint_rot" is among the streams.

'6d'
include_root_pos bool

Whether root position occupies the first 3 columns. The shorthand for the two default stream lists; pass streams instead to describe anything else.

True
streams tuple of str

The streams= a :func:~pybvh_ml.pack_to_flat call was given — column order, same vocabulary, including the derived "joint_vel" / "joint_acc" / "node_vel" / "node_acc". None (default) derives it from include_root_pos, which is the pre-0.6.0 behaviour.

None
num_nodes int

Node count N, required when any node-space stream is among them ("node_pos", "node_vel", "node_acc"). Nodes are joints plus end sites, so it cannot be derived from num_joints.

None

Returns:

Type Description
FeatureDescriptor

Block names: root_pos, joint_rotations, joint_positions, node_positions, joint_velocities, joint_accelerations, node_velocities, node_accelerations.

Examples:

>>> layout = describe_features(31, streams=("joint_pos", "joint_rot"))
>>> layout.slice("joint_positions")
slice(0, 93, None)

describe_graph_features(num_joints: int, representation: str = '6d', *, streams: tuple[str, ...] | list[str] | None = None, num_nodes: int | None = None) -> GraphDescriptor

Predict the (C, V) geometry of a graph-layout pack.

The counterpart of :func:describe_features, which describes the flat (T, D) layout. Answers "given these streams, what will :func:~pybvh_ml.pack_to_ctv produce?" without building an array, so a config check does not have to restate the packer's vertex and channel rules — the restatement being the thing that drifts.

The rules it saves you reproducing: "root_pos" adds a vertex and never a channel block; C is the per-vertex streams' widths floored by the root's 3, so ("root_pos",) alone is still C = 3; derived streams are always 3 channels wide; and node space cannot share a vertex axis with joint space, which raises here exactly as it does in the packer.

Parameters:

Name Type Description Default
num_joints int

Number of joints, excluding end sites — J.

required
representation str

Rotation representation, read only when "joint_rot" is among the streams. One of "euler", "quat", "6d", "axisangle", "rotmat".

'6d'
streams tuple of str

Same vocabulary and order as the packers'. None (default) means ("root_pos", "joint_rot"), the packers' default.

None
num_nodes int

Node count N, required when any node-space stream is named.

None

Returns:

Type Description
GraphDescriptor

Raises:

Type Description
ValueError

For an unknown or repeated stream name, a node-space stream beside a joint-space one, an unknown representation, or a node-space stream without num_nodes — the same refusals, from the same code, as the packer's.

Examples:

>>> desc = describe_graph_features(24, streams=("joint_pos", "joint_vel"))
>>> desc.num_channels, desc.num_vertices
(6, 24)
>>> desc.shape(64)
(6, 64, 24)
>>> desc.slice("joint_velocities")
slice(3, 6, None)
See Also

describe_features : The flat (T, D) layout's column ranges. pybvh_ml.pack_to_ctv : What this predicts.