Analysis¶
analysis
¶
Motion analysis for BVH data.
Velocities, accelerations, angular velocities, root trajectory, and foot
contacts. Every function takes a :class:~pybvh.bvh.Bvh object as its
first argument; thin wrapper methods on the Bvh class delegate here.
Feature-array export for ML pipelines lives in :mod:pybvh.features.
Velocities & accelerations¶
The finite-difference kinematics ladder over FK positions and rotations.
node_velocities(bvh: Bvh, centered: str = 'world', in_frames: bool = False, coords: npt.NDArray[np.float64] | None = None, stencil: str = 'central', pad: str = 'edge') -> npt.NDArray[np.float64]
¶
Compute per-node position velocities (joints + end sites).
Two orthogonal choices:
stencilpicks the finite-difference method — central (second-order accurate, symmetric) or forward (first-order, causal).padpicks the boundary-handling convention —"edge"fills the boundary so the output has the same shape as the input;"none"drops the boundary frames that the stencil can't define.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
bvh
|
Bvh
|
Input motion. |
required |
centered
|
str
|
Coordinate centering mode (default |
'world'
|
in_frames
|
bool
|
If True, return velocity in units/frame. If False (default), return velocity in units/second. |
False
|
coords
|
(ndarray, shape(F, N, 3))
|
Pre-computed spatial coordinates. If None, computed
internally via :meth: |
None
|
stencil
|
('central', 'forward')
|
|
"central"
|
pad
|
('edge', 'none')
|
|
"edge"
|
Returns:
| Type | Description |
|---|---|
ndarray
|
Shape depends on ========= ====== ================
stencil pad shape
========= ====== ================
central edge |
See Also
joint_velocities : Same data restricted to non-end-site joints
((F, J, 3)). Use that when the output should index-align
with :attr:Bvh.joint_angles / :func:angular_velocities.
Raises:
| Type | Description |
|---|---|
ValueError
|
If the clip is too short for the chosen combination,
|
joint_velocities(bvh: Bvh, centered: str = 'world', in_frames: bool = False, coords: npt.NDArray[np.float64] | None = None, stencil: str = 'central', pad: str = 'edge') -> npt.NDArray[np.float64]
¶
Compute per-joint position velocities (end sites excluded).
Returns the joint-axis subset of :func:node_velocities — same
finite-difference math, but restricted to non-end-site joints so
the output indexes match :attr:Bvh.joint_angles and
:func:angular_velocities. Output shape is (F, J, 3) (or the
appropriate trimmed variant per stencil × pad).
See :func:node_velocities for the full parameter / shape docs.
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
node_accelerations(bvh: Bvh, centered: str = 'world', in_frames: bool = False, coords: npt.NDArray[np.float64] | None = None, stencil: str = 'central', pad: str = 'edge') -> npt.NDArray[np.float64]
¶
Compute per-node position accelerations (joints + end sites).
Applies the chosen stencil twice to the input positions.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
bvh
|
Bvh
|
Input motion. |
required |
centered
|
str
|
Coordinate centering mode (default |
'world'
|
in_frames
|
bool
|
If True, return acceleration in units/frame^2. If False (default), return in units/second^2. |
False
|
coords
|
(ndarray, shape(F, N, 3))
|
Pre-computed spatial coordinates. If None, computed
internally via :meth: |
None
|
stencil
|
('central', 'forward')
|
Finite-difference method applied twice. Default |
"central"
|
pad
|
('edge', 'none')
|
Boundary handling. |
"edge"
|
Returns:
| Type | Description |
|---|---|
ndarray
|
Shape depends on ========= ====== ================
stencil pad shape
========= ====== ================
central edge |
Composition identity: ``np.gradient(node_velocities(), dt)`` equals
|
|
``node_accelerations()`` exactly under the defaults
|
|
(``stencil="central"``, ``pad="edge"``). Not guaranteed for other
|
|
combinations.
|
|
See Also
joint_accelerations : Same data restricted to non-end-site joints
((F, J, 3)).
Raises:
| Type | Description |
|---|---|
ValueError
|
If the clip is too short for the chosen combination,
|
joint_accelerations(bvh: Bvh, centered: str = 'world', in_frames: bool = False, coords: npt.NDArray[np.float64] | None = None, stencil: str = 'central', pad: str = 'edge') -> npt.NDArray[np.float64]
¶
Compute per-joint position accelerations (end sites excluded).
Returns the joint-axis subset of :func:node_accelerations — same
twice-applied finite-difference math, restricted to non-end-site
joints so output indexes match :attr:Bvh.joint_angles. Output
shape is (F, J, 3) (or the appropriate trimmed variant per
stencil × pad).
See :func:node_accelerations for the full parameter / shape docs.
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
node_speed_derivative(bvh: Bvh, 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).
Computes the per-node speed ‖v‖ from :func:node_velocities, then
applies the same finite-difference stencil once more to that scalar
series. Positive values mean the node is speeding up, negative
values mean it is slowing down — the natural "is the movement
accelerating or braking" signal, independent of direction changes.
This is the tangential acceleration a_t = d‖v‖/dt of the
node's trajectory. The complementary normal (centripetal) component
is a_n = ‖v‖² · κ with κ the trajectory curvature
(:func:pybvh.geometry.curvature), and the two decompose the full
acceleration vector: a_t² + a_n² = ‖a‖².
It is not recoverable from :func:node_accelerations: this is
the difference of the norm (Δ‖v⃗‖), not the norm of the
difference (‖Δv⃗‖ = ‖a⃗‖). A direction change at constant speed
gives ‖a⃗‖ > 0 but d‖v‖/dt = 0.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
bvh
|
Bvh
|
Input motion. |
required |
centered
|
str
|
Coordinate centering mode (default |
'world'
|
in_frames
|
bool
|
If True, return the rate in units/frame^2. If False (default), return in units/second^2. |
False
|
coords
|
(ndarray, shape(F, N, 3))
|
Pre-computed spatial coordinates. If None, computed
internally via :meth: |
None
|
stencil
|
('central', 'forward')
|
Finite-difference method, applied at both stages (positions →
velocities, speed → its derivative). Default |
"central"
|
pad
|
('edge', 'none')
|
Boundary handling, applied at both stages. |
"edge"
|
Returns:
| Type | Description |
|---|---|
ndarray
|
Shape depends on ========= ====== ================
stencil pad shape
========= ====== ================
central edge |
See Also
joint_speed_derivative : Same data restricted to non-end-site
joints ((F, J)).
node_accelerations : The vector second derivative — its norm is the
full ‖a‖, of which this is the tangential component.
velocity_reductions : peak_acceleration / peak_deceleration
are the extrema of this series (stencil="forward",
pad="none").
Raises:
| Type | Description |
|---|---|
ValueError
|
If the clip is too short for the chosen combination,
|
Notes
Source: Hachimura et al. 2005 (Time Effort).
joint_speed_derivative(bvh: Bvh, 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 (end sites excluded).
Returns the joint-axis subset of :func:node_speed_derivative —
same two-stage finite-difference math, restricted to non-end-site
joints so output indexes match :attr:Bvh.joint_angles. Output
shape is (F, J) (or the appropriate trimmed variant per
stencil × pad).
See :func:node_speed_derivative for the full parameter / shape /
sign-semantics docs.
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
angular_velocities(bvh: Bvh, in_frames: bool = False, stencil: str = 'central', pad: str = 'edge', degrees: bool = False) -> npt.NDArray[np.float64]
¶
Compute per-joint angular velocities via rotation matrix log map.
These are the rates of each joint's parent-relative (local)
rotation — the BVH channels themselves — expressed in the joint's
own (body) frame: ω[i] = log(R_i^T @ R_{i+1}) / dt on the local
rotation matrices, no forward kinematics involved. Two published
alternatives differ: the world-frame angular velocity of a
segment (the biomechanics convention) composes rotations down the
chain first and diverges for every joint whose parent is itself
rotating; and the spatial-frame rate log(R_{i+1} @ R_i^T)
differs from the body-frame rate by conjugation with R — same
magnitude, rotated axis. The local convention is what BVH data
natively parameterizes; compose Bvh.to_rotmat output through
the hierarchy yourself if you need segment rates.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
bvh
|
Bvh
|
Input motion. |
required |
in_frames
|
bool
|
If True, return angular velocity in radians/frame (or
degrees/frame if |
False
|
degrees
|
bool
|
If True, convert the final output from radians to degrees.
Default False (radians). Consistent with the |
False
|
stencil
|
('central', 'forward')
|
|
"central"
|
pad
|
('edge', 'none')
|
|
"edge"
|
Returns:
| Type | Description |
|---|---|
ndarray
|
Shape depends on ========= ====== ================
stencil pad shape
========= ====== ================
central edge Direction is the rotation axis; magnitude is the rotation
angle (radians or radians/second). Angles are clamped to
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If fewer than 2 frames ( |
Root trajectory & foot contacts¶
Ground-plane root features and binary contact labels — the contact signals are drawn in the Gallery.
root_trajectory(bvh: Bvh, 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 commonly used in motion ML.
Returns the root's ground-plane position and heading angle (as
sin/cos pair). Optionally appends ground-plane and
heading velocities.
The heading reference is the rest-pose forward direction —
derived from the skeleton's L/R lateral geometry crossed with
world_up (see :func:pybvh.tools._compute_forward_at). This
means "heading = rest-pose forward" at any frame whose root
rotation is identity, regardless of what rotation the clip
starts with.
The heading is orientation-derived — the root bone's rotation
applied to the rest forward, projected to the ground plane — not
the direction of travel (a side-stepping character keeps its
heading). It therefore inherits pelvis twist, and its ground-plane
projection shrinks toward numerical ambiguity when the character
bends far forward; :func:facing_frame is the whole-body, yaw-only
alternative that avoids both. For the direction of motion,
differentiate the ground position (the ground_*_vel columns).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
bvh
|
Bvh
|
Input motion. |
required |
up_axis
|
str or None
|
Signed axis string (e.g. |
None
|
include_velocities
|
bool
|
If True, append |
False
|
stencil
|
optional
|
Only used with |
'central'
|
pad
|
optional
|
Only used with |
'central'
|
degrees
|
bool
|
If True, convert the |
False
|
Returns:
| Type | Description |
|---|---|
ndarray
|
Shape Columns: |
foot_contacts(bvh: Bvh, 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 binary foot contact labels per frame.
The default combines a velocity check (foot not moving) and a
height check (foot near the ground) following the HuMoR heuristic
— each signal catches a different failure mode of the other.
method="velocity" and method="height" remain available as
single-signal escape hatches.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
bvh
|
Bvh
|
Input motion. |
required |
foot_joints
|
list of str or None
|
Explicit foot joints (recommended). If None, falls back to
:func: |
None
|
method
|
('combined', 'velocity', 'height')
|
|
"combined"
|
coords
|
(ndarray, shape(F, N, 3))
|
Pre-computed spatial coordinates. Must be world-frame
positions or a constant translation thereof (e.g.
|
None
|
vel_threshold
|
(float or None, keyword - only)
|
Speed threshold in world units per second. Defaults to
|
None
|
vel_smooth_duration
|
(float, keyword - only)
|
Physical time span (seconds) the foot-speed estimator is
conditioned over before thresholding. Displacement vectors are
box-averaged over |
1.0 / 30.0
|
height_threshold
|
(float or None, keyword - only)
|
Clearance above the estimated floor, in world units. Defaults
to |
None
|
floor
|
float, ``"auto"`` or ``"min"``, keyword-only
|
Floor height along the raw |
'auto'
|
min_contact_duration
|
(float, keyword - only)
|
Morphological open: contact runs shorter than this many
seconds are set to 0. Default |
0.1
|
min_gap_duration
|
(float, keyword - only)
|
Morphological close: non-contact gaps shorter than this many
seconds are filled (set to 1). Default |
0.1
|
hysteresis
|
(float, keyword - only)
|
Schmitt-trigger band fraction (default |
0.25
|
adaptive
|
(bool, keyword - only)
|
If True, derive each foot's thresholds from its own signal
distribution (Otsu's bimodal split between the stance and swing
clusters) instead of the fixed scale fraction, falling back to the
fixed default for any foot whose signal is not convincingly
bimodal (e.g. standing, or a foot that never clearly swings).
Default |
False
|
height_reference
|
('velocity', 'floor')
|
How the default |
"velocity"
|
return_info
|
(bool, keyword - only)
|
If True, return |
False
|
Returns:
| Type | Description |
|---|---|
ndarray of shape ``(F, num_foot_joints)``, or
|
|
tuple ``(ndarray, dict)`` when ``return_info=True``.
|
Binary contact labels (1.0 = contact, 0.0 = no contact).
For |
Raises:
| Type | Description |
|---|---|
ValueError
|
|
See Also
ground_contacts : The same detection engine for arbitrary (non-foot) joint sets — hands, knees, props.
auto_detect_foot_joints : The detection used when foot_joints is None.
ground_contacts(bvh: Bvh, 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-contact labels for an arbitrary set of joints.
The same detection engine as :func:foot_contacts — velocity and/or clearance-above-floor thresholding with hysteresis and duration filters — but for any joint set: hands during floor work, knees in a crawl, a prop bone. Because the joints are not assumed to be feet, three foot-specific behaviors are dropped: there is no rest-pose "feet below hips" sanity check, the call never reads or writes the cached :attr:Bvh.floor_height (the floor estimated from an arbitrary joint set describes those joints, not the scene), and height_reference defaults to "floor" instead of :func:foot_contacts' "velocity" — the per-joint stance calibration behind "velocity" presumes regular ground contact, which feet in locomotion have and a hand that touches the floor twice does not.
.. warning::
With the default floor="auto" the floor is estimated from the given joints' own trajectories (2nd percentile of their per-frame minimum height). A joint set that never actually grounds makes its lowest hover point the "floor" and fabricates contacts there. Unless the joints genuinely reach the ground for a meaningful fraction of the clip, pass floor=bvh.floor_height (the scene floor from the feet) or an explicit float.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
bvh
|
Bvh
|
Input motion. |
required |
joints
|
sequence of str or int
|
The joints to test, as node names and/or node-space indices (rows of :meth: |
required |
method
|
('combined', 'velocity', 'height')
|
Same meaning as in :func: |
"combined"
|
coords
|
(ndarray, shape(F, N, 3))
|
Pre-computed world-frame positions (or a constant translation thereof), as in :func: |
None
|
vel_threshold
|
keyword - only
|
Same meaning and defaults as in :func: |
None
|
vel_smooth_duration
|
keyword - only
|
Same meaning and defaults as in :func: |
None
|
height_threshold
|
keyword - only
|
Same meaning and defaults as in :func: |
None
|
floor
|
keyword - only
|
Same meaning and defaults as in :func: |
None
|
min_contact_duration
|
keyword - only
|
Same meaning and defaults as in :func: |
None
|
min_gap_duration
|
keyword - only
|
Same meaning and defaults as in :func: |
None
|
hysteresis
|
keyword - only
|
Same meaning and defaults as in :func: |
None
|
adaptive
|
keyword - only
|
Same meaning and defaults as in :func: |
None
|
height_reference
|
('floor', 'velocity')
|
Default |
"floor"
|
return_info
|
(bool, keyword - only)
|
As in :func: |
False
|
Returns:
| Type | Description |
|---|---|
ndarray of shape ``(F, num_joints)``, or tuple ``(ndarray, dict)`` when ``return_info=True``.
|
Binary contact labels (1.0 = contact); columns follow |
Raises:
| Type | Description |
|---|---|
ValueError
|
|
TypeError
|
If a |
IndexError
|
If a node index is out of range for |
See Also
foot_contacts : The foot-specialized entry point (auto-detection, rest-pose sanity check, floor-height caching, height_reference="velocity" default).
auto_detect_foot_joints(bvh: Bvh, *, _rest_coords: npt.NDArray[np.float64] | None = None) -> list[str]
¶
Auto-detect foot joint names by topology.
Algorithm:
- Substring match: candidates are joints whose names contain
"foot"or"toe"(case-insensitive). - Tip-descendant filter: keep only candidates that have an end site or a toe-named child. This drops IK helpers, which typically have no children.
- Most-distal filter: drop candidates whose subtree (any depth)
contains another candidate. On a rig with
Foot → ToeBase → EndSite, this keeps onlyToeBase— the more distal, ground-contacting joint. - Deterministic order: sort by rest-pose height along
bvh.world_up(lowest first); alphabetical name within ties so the output is stable across runs.
If step 1 produces matches but step 2 drops all of them, the
tip filter is skipped with a UserWarning (better than
returning nothing for unusual rigs).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
bvh
|
Bvh
|
Input skeleton. |
required |
Returns:
| Type | Description |
|---|---|
list of str
|
Joint names in deterministic order. Empty if no candidates.
Returning |
Notes
This is the same detection used internally by
:func:foot_contacts when foot_joints=None. Call it
directly to preview the detection or to feed an explicit list
back in.
An empty result is a report, not an error: this function never raises on footless rigs. The contact detectors are where emptiness becomes fatal — :func:foot_contacts and :func:ground_contacts both raise ValueError when handed an empty joint list, so a silent [] cannot flow into a zero-column contact array.
Facing frame¶
The continuous per-frame facing basis — the vector form of the snapped Bvh.forward_at / Bvh.left_at axis labels.
FacingFrame = namedtuple('FacingFrame', ['forward', 'left', 'up', 'valid'])
module-attribute
¶
facing_frame(bvh: Bvh, coords: npt.NDArray[np.float64] | None = None) -> FacingFrame
¶
Per-frame facing basis of the character, as continuous unit vectors.
Returns the orthonormal right-handed triple (forward, left, up)
for every frame — the continuous form of the axis-label pair
:meth:Bvh.forward_at / :meth:Bvh.left_at, which snap exactly
this basis to the nearest signed world axis. Use the labels for
rest-pose canonicalization and dataset-convention checks; use this
function whenever the actual facing direction matters — the snapped
label stays constant while a character turns through less than 90°,
the vectors track the rotation frame by frame.
This is a yaw-only, gravity-aligned facing frame: up is the
exact :attr:Bvh.world_up unit vector on every frame, so the basis
only ever rotates about the vertical — deliberately not a pelvis
orientation (no roll or pitch; a character bending forward keeps
their facing frame level). It is also distinct from
:func:root_trajectory's heading, which is a second facing
estimate built differently — the root bone's rotation applied to
the rest forward — not a direction of motion: that heading inherits
pelvis twist and collapses toward numerical ambiguity when the
character bends far forward, while this basis measures the
whole-body L/R geometry and stays yaw-only. The two diverge
whenever the pelvis turns or tilts relative to the body. Neither
is velocity-based; for the direction of travel, differentiate
ground position (root_trajectory's ground_*_vel columns).
Construction (per frame, all in world space): the leftward
direction is the average of (left_pos - right_pos) over the
L/R joint pairs in :attr:Bvh.lr_mapping, projected onto the
horizontal plane and normalized; forward = leftward × up;
left = up × forward (re-orthogonalized). The triple satisfies
forward × left = up.
Fallback policy (mirrors :meth:Bvh.forward_at): frames with no
usable L/R direction — the skeleton has no lr_mapping, or the
frame's horizontal (left - right) average nearly vanishes /
is nearly parallel to world_up (norm below 1e-6) — receive
a constant fallback basis instead: forward from the rest-pose
leftward crossed with world_up, or the arbitrary-but-stable
per-up-axis default ('+z' for a y-up world) when no
rest-pose L/R geometry exists either; left = up × forward
keeps the triple orthonormal. The valid field reports exactly
those frames: False where the basis is the fallback, not a
measurement (all-False when the skeleton has no L/R pairs at
all — the per-frame refinement of :attr:Bvh.has_lr_geometry).
The fallback rows still carry the usable constant basis rather
than nan, so rendering-style consumers can ignore valid;
anything measuring facing should mask by it. The bvhplot follow
camera applies the same policy — it holds its orientation on such
frames rather than inventing a rotation.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
bvh
|
Bvh
|
Input motion. |
required |
coords
|
(ndarray, shape(F, N, 3))
|
Pre-computed spatial coordinates for all frames (as returned
by |
None
|
Returns:
| Type | Description |
|---|---|
FacingFrame
|
Named tuple |
See Also
Bvh.forward_at, Bvh.left_at : The categorical (snapped axis label) form of the same construction. root_trajectory : Root-bone-orientation heading (a different facing estimate — see Notes above; not a travel direction).
Jerk¶
The third rung of the velocity → acceleration → jerk ladder.
node_jerk(bvh: Bvh, centered: str = 'world', in_frames: bool = False, coords: npt.NDArray[np.float64] | None = None, stencil: str = 'central', pad: str = 'edge') -> npt.NDArray[np.float64]
¶
Compute per-node position jerk (third derivative) — (F, N, 3).
Applies the chosen stencil three times to the positions, the
next rung of the velocity → acceleration → jerk ladder. The jerk
magnitude np.linalg.norm(node_jerk(...), axis=-1) is the usual
smoothness signal.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
bvh
|
Bvh
|
Input motion. |
required |
centered
|
str
|
Coordinate centering mode (default |
'world'
|
in_frames
|
bool
|
If True, units/frame³; else units/second³ (default). |
False
|
coords
|
(ndarray, shape(F, N, 3))
|
Pre-computed positions; computed via :meth: |
None
|
stencil
|
('central', 'forward')
|
Finite-difference method applied three times. Default
|
"central"
|
pad
|
('edge', 'none')
|
|
"edge"
|
Returns:
| Type | Description |
|---|---|
ndarray
|
Per-node jerk. Composition identity:
|
Raises:
| Type | Description |
|---|---|
ValueError
|
Too-short clip, |
joint_jerk(bvh: Bvh, 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 (end sites excluded) — (F, J, 3).
The joint-axis subset of :func:node_jerk, index-aligned with
:attr:Bvh.joint_angles. See :func:node_jerk for full docs.
Raises ValueError if coords is not node-shaped (F, N, 3).
Smoothness metrics¶
Array-pure kernels on a speed profile — (T,) for a scalar, or (T, K) reduced per column — plus the smoothness(metric=…) dispatcher.
sparc(speed: npt.NDArray[np.float64], fs: float, padlevel: int = 4, fc: float = 10.0, amp_th: float = 0.05) -> float | npt.NDArray[np.float64]
¶
Spectral arc length (SPARC) smoothness of a speed profile.
The negative arc length of the normalized Fourier magnitude spectrum
over [0, fc] Hz — a smoothness measure that is robust to noise and
invariant to amplitude/duration. Values are ≤ 0; closer to 0
is smoother.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
speed
|
(ndarray, shape(T) or (T, K))
|
Speed profile, or a signed scalar velocity — the spectrum is
taken of the values as given, not their magnitude, so the two
are genuinely different inputs here (matching the reference
implementation). A 2-D input is |
required |
fs
|
float
|
Sampling rate in Hz. |
required |
padlevel
|
int
|
Zero-padding exponent: |
4
|
fc
|
float
|
Upper cutoff frequency in Hz (default 10.0). |
10.0
|
amp_th
|
float
|
Normalized amplitude threshold selecting the spectral band (default 0.05). |
0.05
|
Returns:
| Type | Description |
|---|---|
float or ndarray
|
The spectral arc length (SAL) — a scalar for |
Notes
Source: Balasubramanian et al. 2015, "On the analysis of movement
smoothness." Reimplemented in NumPy; validated against the authors'
reference output (see tests/test_smoothness_golden.py).
dimensionless_jerk(speed: npt.NDArray[np.float64], fs: float, normalize: str = 'peak_speed', amplitude: float | npt.NDArray[np.float64] | None = None) -> float | npt.NDArray[np.float64]
¶
Dimensionless jerk (DLJ) smoothness of a speed profile.
Integrated squared jerk made scale-invariant by dividing out movement
duration and size — -(duration³ / peak²) · ∫ (d²v/dt²)² dt at the
default. More negative is less smooth.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
speed
|
(ndarray, shape(T) or (T, K))
|
Speed profile, or a signed scalar velocity (Hogan & Sternad
define the measure on |
required |
fs
|
float
|
Sampling rate in Hz. |
required |
normalize
|
('peak_speed', 'mean_speed', 'amplitude')
|
Which published normalizer to apply to the jerk integral
(default |
"peak_speed"
|
amplitude
|
float or ndarray
|
The movement extent |
None
|
Returns:
| Type | Description |
|---|---|
float or ndarray
|
The dimensionless jerk ( |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Notes
Hogan & Sternad 2009 give the measure in three forms::
∫(d²v/dt²)² dt · D⁵/A² ≡ · D³/v_mean² | variant: · D³/v_peak²
The first two are the same measure — A = v_mean · D — so
"mean_speed" is exactly "amplitude" evaluated at the arc
length the speed profile itself implies, A = ∫|v| dt. Passing
amplitude= is only a different measure when the extent comes
from somewhere else, most usefully the endpoint displacement
‖p_T − p_0‖, which is smaller than the arc length for any path
that is not straight. (For the arc length of a trajectory, prefer
"mean_speed" over amplitude=geometry.path_length(traj): the
chord sum uses a different quadrature than this function's integral
and will not agree exactly.)
"peak_speed" — the default, and what the widely used reference
implementation computes — is genuinely distinct from the other two.
It differs by a factor of (v_mean / v_peak)², which depends on the
shape of the speed profile rather than being constant, so a
bell-shaped reach and a plateaued sustained movement of equal extent
and duration rank differently under it. That factor is
:func:speed_metric, so dimensionless_jerk(v, fs, "mean_speed")
equals dimensionless_jerk(v, fs) / speed_metric(v)**2.
Reproducing a published DLJ figure therefore means matching its convention; the default is unchanged from earlier pybvh releases.
Source: Hogan & Sternad 2009; Balasubramanian et al. (the
"peak_speed" form). Validated against the reference output.
log_dimensionless_jerk(speed: npt.NDArray[np.float64], fs: float, normalize: str = 'peak_speed', amplitude: float | npt.NDArray[np.float64] | None = None) -> float | npt.NDArray[np.float64]
¶
Log dimensionless jerk (LDLJ) — -ln|DLJ|.
The log transform of :func:dimensionless_jerk, the form most used in
practice. More negative is less smooth.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
speed
|
(ndarray, shape(T) or (T, K))
|
Speed profile; a 2-D input is |
required |
fs
|
float
|
Sampling rate in Hz. |
required |
normalize
|
('peak_speed', 'mean_speed', 'amplitude')
|
Normalizer forwarded to :func: |
"peak_speed"
|
amplitude
|
float or ndarray
|
Movement extent forwarded to :func: |
None
|
Returns:
| Type | Description |
|---|---|
float or ndarray
|
|
Notes
Source: Balasubramanian et al. Validated against the reference output.
number_of_peaks(speed: npt.NDArray[np.float64], min_height: float | None = None) -> int | npt.NDArray[np.int_]
¶
Number of local maxima in a speed profile.
A simple smoothness proxy — a single smooth movement has one velocity peak; more peaks mean more sub-movements.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
speed
|
(ndarray, shape(T) or (T, K))
|
Speed profile; a 2-D input is |
required |
min_height
|
float or None
|
Minimum height a maximum must reach to be counted, in the same
units as |
None
|
Returns:
| Type | Description |
|---|---|
int or ndarray
|
Count of qualifying strict interior local maxima — a scalar for
|
Notes
Strictness. A sample counts when it is strictly greater than both neighbours. The alternative, allowing ties, would count every sample of a flat-topped peak separately, which is worse; the cost of strictness is that an exactly flat maximum — two or more equal adjacent samples — counts as zero peaks rather than one. Exact ties are rare in float data but reachable after quantization or box-filter smoothing.
Sign. The comparison is on the values as given, not their
magnitude, so for a signed velocity this counts maxima of the signed
signal — velocity peaks in one direction — and a trough at -5
beside neighbours at -6 is a maximum. That is the right reading
for a scalar velocity and a no-op for a speed profile; use
min_height if you want only the positive-going peaks.
Height threshold. With no threshold, every micro-fluctuation is a
"sub-movement", so on noisy data the count tracks the noise rather
than the movement — Hogan & Sternad 2009 warn about exactly this
(peak counting is "prone to spurious peaks", and separately blind to
arrests). The default is unthresholded because that is what the
metric means in its source literature, not because it is the better
choice for real data; set min_height when the profile is noisy.
It is absolute rather than a fraction of the peak so it composes with
whatever normalization you have already applied — for a relative
threshold, pass min_height=0.05 * speed.max().
Source: Balasubramanian et al. (number-of-peaks metric); Hogan &
Sternad 2009 (its noise sensitivity). Note the pinned SPARC reference
implementation ships no peak-counting function, so unlike
:func:sparc and :func:dimensionless_jerk this metric has no
golden-reference test — only the properties asserted in
tests/test_analysis_primitives.py.
speed_metric(speed: npt.NDArray[np.float64]) -> float | npt.NDArray[np.float64]
¶
Mean-to-peak speed ratio — mean|v| / max|v|, in [0, 1].
A bell-shaped (smooth) speed profile has a low ratio; a flat plateau approaches 1.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
speed
|
(ndarray, shape(T) or (T, K))
|
Speed profile, or a signed scalar velocity; a 2-D input is |
required |
Returns:
| Type | Description |
|---|---|
float or ndarray
|
The mean/peak ratio — a scalar for |
Notes
Both reductions take the magnitude. For a non-negative speed profile
that is simply mean(v) / max(v); for a signed velocity it is what
keeps the documented [0, 1] range true, since a raw mean cancels
toward zero on an out-and-back movement and would yield a ratio at or
below zero against a positive peak.
The same magnitude convention is used by
:func:dimensionless_jerk's "mean_speed" normalizer, which makes
this the exact conversion factor between its two distinct
normalizers: dimensionless_jerk(v, fs, "mean_speed") ==
dimensionless_jerk(v, fs) / speed_metric(v)**2.
Source: Balasubramanian et al. (speed-metric); Flash & Hogan.
integrated_squared_jerk(speed: npt.NDArray[np.float64], fs: float) -> float | npt.NDArray[np.float64]
¶
Integrated squared jerk — ∫ (d²v/dt²)² dt (dimensional).
Accepts (T,) (scalar out) or (T, K) ((K,) out, reduced
per column).
mean_squared_jerk(speed: npt.NDArray[np.float64], fs: float) -> float | npt.NDArray[np.float64]
¶
Mean squared jerk — mean((d²v/dt²)²).
Accepts (T,) (scalar out) or (T, K) ((K,) out, reduced
per column).
rms_squared_jerk(speed: npt.NDArray[np.float64], fs: float) -> float | npt.NDArray[np.float64]
¶
Root-mean-square jerk — sqrt(mean((d²v/dt²)²)).
Accepts (T,) (scalar out) or (T, K) ((K,) out, reduced
per column).
smoothness(speed: npt.NDArray[np.float64], fs: float, metric: str = 'sparc', **kwargs: Any) -> float | npt.NDArray[np.float64]
¶
Dispatch to a named smoothness metric on a speed profile.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
speed
|
(ndarray, shape(T) or (T, K))
|
Speed profile; a 2-D input is |
required |
fs
|
float
|
Sampling rate in Hz. |
required |
metric
|
str
|
One of |
'sparc'
|
**kwargs
|
Any
|
Metric-specific options: |
{}
|
Returns:
| Type | Description |
|---|---|
float or ndarray
|
The selected smoothness value — a scalar for |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Signal reductions¶
Scalar summaries of a speed or activity signal.
VelocityReductions = namedtuple('VelocityReductions', ['peak', 'mean', 'peak_to_mean', 'peak_acceleration', 'peak_deceleration'])
module-attribute
¶
velocity_reductions(speed: npt.NDArray[np.float64], fs: float) -> VelocityReductions
¶
Scalar reductions of a speed profile.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
speed
|
(ndarray, shape(T) or (T, K))
|
Speed profile; a 2-D input is |
required |
fs
|
float
|
Sampling rate in Hz; scales |
required |
Returns:
| Type | Description |
|---|---|
VelocityReductions
|
Named tuple |
Notes
All fields reduce the values as given — none takes a magnitude
first. For a genuine (non-negative) speed profile that is the only
reading; for a signed scalar velocity it means peak is the
signed maximum (not the excursion max|v|) and peak_to_mean
is unbounded (a raw mean cancels toward zero on an out-and-back
movement, going nan at exactly zero). This differs from
:func:speed_metric, whose documented [0, 1] range forces the
magnitude convention mean|v| / max|v| — so for signed input
peak_to_mean is not 1 / speed_metric. Take np.abs
first if you want magnitude reductions of a signed velocity.
peak_acceleration and peak_deceleration are the positive and
negative extrema (clamped at 0) of the per-frame speed-derivative
series d‖v‖/dt: when speed is the norm of
:func:node_velocities with stencil="forward", pad="none",
they equal the extrema of :func:node_speed_derivative under the
same convention (asserted in the test suite so the two can't drift).
Source: Pollick et al., Halovic & Kroos, Samadani et al.
zero_crossings(signal: npt.NDArray[np.float64], axis: int = 0) -> npt.NDArray[np.int_]
¶
Count sign changes of a signal along an axis.
Strict crossings only — consecutive samples with a product < 0;
exact zeros are not counted as crossings. The consequence: a sign
change that passes through an exact zero sample (+1, 0, -1)
counts zero crossings, not one. The alternative convention —
sign-change counting, np.diff(np.sign(x)) != 0 — counts it
(twice, unless zeros are carried forward). Exact zeros are rare in
float data but reachable after quantization, rectification, or
box-filter smoothing.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
signal
|
ndarray
|
Input signal. |
required |
axis
|
int
|
Axis along which to count (default 0). |
0
|
Returns:
| Type | Description |
|---|---|
ndarray
|
Crossing counts with |
Notes
Source: Zhao & Badler (motion feature counts).
active_segments(speed: npt.NDArray[np.float64], threshold: float) -> npt.NDArray[np.bool_]
¶
Boolean mask of "active" (above-threshold) samples.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
speed
|
ndarray
|
Speed (or any non-negative activity) signal. |
required |
threshold
|
float
|
Activity threshold; samples strictly above it are active. No hidden default — the caller picks the threshold, keeping this a theory-neutral primitive. |
required |
Returns:
| Type | Description |
|---|---|
ndarray of bool
|
|
Notes
Source: Pollick et al., Bernhardt & Robinson.
active_duration(speed: npt.NDArray[np.float64], threshold: float, fs: float) -> float | npt.NDArray[np.float64]
¶
Total time spent active — active sample count / fs.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
speed
|
(ndarray, shape(T) or (T, K))
|
Speed signal; a 2-D input is |
required |
threshold
|
float
|
Activity threshold (see :func: |
required |
fs
|
float
|
Sampling rate in Hz. Required — the time base must be stated explicitly, matching the other array-pure kernels. |
required |
Returns:
| Type | Description |
|---|---|
float or ndarray
|
Active duration in seconds — a scalar for |
Energy, gait & range of motion¶
Kinetic energy and the spatiotemporal gait parameters (all computed in one pass by gait_parameters).
kinetic_energy(bvh: Bvh, 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 summed over joints.
With masses, Σ_j ½ m_j ‖v_j‖² (true kinetic energy). Without,
Σ_j ‖v_j‖² (unit-mass energy proxy) — pybvh ships no segment-mass
model, so pass anatomical masses for physical energy. This is a
point-mass-at-joints model; rigid-body energy (segment-CoM masses,
rotational inertia) is not supported.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
bvh
|
Bvh
|
Input motion. |
required |
masses
|
ndarray of shape (J,), or mapping {joint_name: mass}
|
Per-joint masses (end sites excluded). A mapping keyed by joint name
is validated for exact coverage and is the safer form — an array
relies on matching |
None
|
centered
|
str
|
Centering mode for the velocities (default |
'world'
|
stencil
|
optional
|
Velocity finite-difference convention (see
:func: |
'central'
|
pad
|
optional
|
Velocity finite-difference convention (see
:func: |
'central'
|
Returns:
| Type | Description |
|---|---|
ndarray
|
Per-frame energy; leading length follows the velocity
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If a |
Notes
Source: Głowinski et al., Piana et al., Lu et al. 2025.
cadence(bvh: Bvh, foot_joints: list[str] | None = None, *, contacts: npt.NDArray[np.float64] | None = None) -> float
¶
Step rate — foot-contact onsets per second.
A projection of :func:gait_parameters (the single definition of
every gait scalar).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
bvh
|
Bvh
|
Input motion. |
required |
foot_joints
|
list of str
|
Foot joints; auto-detected if None. |
None
|
contacts
|
(ndarray, shape(F, n_feet))
|
Pre-computed contact labels (see :func: |
None
|
Returns:
| Type | Description |
|---|---|
float
|
Steps per second ( |
Notes
Unit. Steps per second, consistent with every other rate in pybvh (Hz, units/s). The clinical gait literature — including the sources below — reports cadence in steps per minute; multiply by 60 to compare against published figures. Onsets are pooled over all contact columns, so passing more than two contact-bearing joints per foot inflates the count proportionally.
Source: Crane & Gross, Gross et al. 2012, Karg et al. 2010.
stride_length(bvh: Bvh, foot_joints: list[str] | None = None, *, contacts: npt.NDArray[np.float64] | None = None) -> float
¶
Mean stride length — distance between successive same-foot landings.
The standard, foot-measured stride: for each foot, the horizontal
distance between its position at consecutive contact onsets, pooled
over feet and averaged. A projection of :func:gait_parameters —
see that for the full spatiotemporal set (variability, step length,
stance, double-support, asymmetry) computed in one pass.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
bvh
|
Bvh
|
Input motion. |
required |
foot_joints
|
list of str
|
Foot joints; auto-detected if None. |
None
|
contacts
|
(ndarray, shape(F, n_feet))
|
Pre-computed contact labels (see :func: |
None
|
Returns:
| Type | Description |
|---|---|
float
|
Mean stride length in skeleton units ( |
Notes
Source: Crane & Gross, Gross et al. 2012, Karg et al. 2010.
walking_pace(bvh: Bvh) -> float
¶
Mean horizontal speed — root ground-path length per second.
Same definition as the walking_pace field of
:func:gait_parameters, but computed directly from the root path so
it needs no foot joints or contact detection. Note it only
approximates stride_length × cadence / 2 (exact for straight,
steady, symmetric gait; it diverges on curved or irregular walking
because the root path and the foot landings measure different
things).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
bvh
|
Bvh
|
Input motion. |
required |
Returns:
| Type | Description |
|---|---|
float
|
Horizontal units per second ( |
Notes
Source: Crane & Gross, Gross et al. 2012.
GaitParameters = namedtuple('GaitParameters', ['cadence', 'walking_pace', 'stride_length', 'stride_cv', 'step_length', 'stance_fraction', 'double_support_fraction', 'asymmetry'])
module-attribute
¶
gait_parameters(bvh: Bvh, foot_joints: list[str] | None = None, *, contacts: npt.NDArray[np.float64] | None = None) -> GaitParameters
¶
Spatiotemporal gait parameters in one pass.
Bundles the foot-measured gait analysis: cadence (onsets/s),
walking_pace (root ground speed), stride_length and its
coefficient of variation stride_cv (landing→next-same-foot-landing),
step_length (forward advance between successive any-foot landings,
measured along the direction of travel so step width is excluded),
stance_fraction (mean fraction of a cycle a foot is planted),
double_support_fraction (fraction of frames with ≥2 feet planted), and
asymmetry (left/right stride difference). Underdetermined fields are
nan — uniformly, across all eight (e.g. asymmetry without one
identifiable left and right foot, stride_length if no foot completes
two contacts, step_length if there is no net travel, cadence and
walking_pace on a zero-duration clip).
These are kinematic — computed from foot positions and contact timing alone. Dynamic gait analysis (joint torques, ground-reaction force, mechanical work) needs a physical model and is out of scope.
Conventions worth naming, since published gait figures differ on all three:
stride_cvpools each foot's deviations from that same foot's mean before taking the standard deviation, then divides by the overall mean stride. The plainer alternative — the CV of the pooled stride sample — folds left/right asymmetry into "variability"; here the two are separate fields, and they diverge exactly in proportion toasymmetry.asymmetryis|mean_L − mean_R| / (½(mean_L + mean_R)): unsigned and unitless. Robinson's Symmetry Index, standard in the gait literature, is the same ratio kept signed and ×100 — multiply by 100 for SI magnitude; the direction of the asymmetry is not recoverable from this field.step_lengthprojects onto a single whole-clip progression chord (the root's first→last horizontal displacement), not a per-step heading. On a curved walk this systematically shortens the value, and a closed loop has no net travel and returnsnan.stride_lengthby contrast is the full Euclidean distance between same-foot landings, so it includes lateral step width whilestep_lengthexcludes it — the two are not the 2:1 pair a clinical report would use on curved or wide-stance gait.
Two fields pick one convention among published ones. stride_cv
is a within-foot CV: each foot's deviations from its own mean
stride are pooled, and their std is divided by the overall mean —
so a steady but asymmetric gait reads as low-variability, with the
L/R difference reported separately in asymmetry. The plain
pooled CV (std / mean over all strides regardless of foot), the
other common definition, folds as
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
bvh
|
Bvh
|
Input motion. |
required |
foot_joints
|
list of str
|
Foot joints; auto-detected if None. |
None
|
contacts
|
(ndarray, shape(F, n_feet))
|
Pre-computed contact labels (column order matching |
None
|
Returns:
| Type | Description |
|---|---|
GaitParameters
|
Named tuple of the eight parameters above. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If no foot joints can be found. |
Notes
Source: Crane & Gross, Gross et al. 2012, Karg et al. 2010.
range_of_motion(signal: npt.NDArray[np.float64], axis: int = 0) -> npt.NDArray[np.float64]
¶
Peak-to-peak range of a signal — max − min along an axis.
For a joint-angle channel this is its range of motion over the clip.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
signal
|
ndarray
|
Input signal (e.g. a joint-angle time series). |
required |
axis
|
int
|
Axis to reduce over (default 0, the frame axis). |
0
|
Returns:
| Type | Description |
|---|---|
ndarray
|
The peak-to-peak range with |
Notes
Source: gait / biomechanics range-of-motion descriptors.
Scale & covariance descriptors¶
Skeleton-size normalization and fixed-size sequence statistics.
skeleton_size(bvh: Bvh, foot_joints: list[str] | None = None) -> float
¶
Absolute skeleton scale — mean rest-pose root-to-foot distance.
A size proxy that scales linearly with the whole skeleton (≈ half the
standing height for a humanoid). Only the leg chain contributes, so
finger/spine subdivision does not affect it. This is the public name for
the scale foot_contacts uses internally to set its thresholds; use it
for size normalization. For the relative scale between two skeletons,
see :func:relative_scale_factor.
A skeleton whose size cannot be measured — auto-detection finds no
feet, or every foot sits exactly on the root — raises rather than
returning a substitute: any fabricated number (1.0 reads as a
plausible metre-scale humanoid, nan poisons everything scaled by
it) would be indistinguishable from, or worse than, a measurement.
Catch the error and choose your own scale if a total function is
needed. (foot_contacts' internal threshold scale keeps a
private 1.0 fallback for degenerate rigs — a threshold must
exist even where a measurement does not.)
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
bvh
|
Bvh
|
Input skeleton. |
required |
foot_joints
|
list of str
|
Foot joints; auto-detected from topology if None. Explicitly
passed names must exist in the skeleton ( |
None
|
Returns:
| Type | Description |
|---|---|
float
|
Mean rest-pose distance from the root to the foot joints
(always |
Raises:
| Type | Description |
|---|---|
ValueError
|
If an explicitly passed foot joint name is not in the skeleton; if auto-detection finds no foot joints; or if all foot joints coincide with the root in the rest pose (no measurable size). |
Notes
Source: gait/biomech normalization (Troje, Karg et al.).
relative_scale_factor(reference: npt.NDArray[np.float64], target: npt.NDArray[np.float64], *, centered: bool = False) -> float
¶
Least-squares uniform scale matching target to reference.
The scalar s minimizing ‖reference − s·target‖² over all
coordinates — i.e. s = ⟨reference, target⟩ / ⟨target, target⟩ (Troje-
style size normalization between two skeletons or poses). Both arrays must
share shape (e.g. two (N, 3) rest poses, or (F, N, 3) sequences).
This is the relative scale between skeletons; for a single skeleton's
absolute size, see :func:skeleton_size.
By default the fit is taken about the coordinate origin — neither
array is mean-centered first. The Procrustes/Umeyama convention
centers both point sets on their centroids before fitting scale; the
two agree only when the poses are already centered, and diverge in
proportion to the centroid offset. That makes the origin form right
for rest poses (root at the origin) and wrong for world-frame
sequences with a translated root — pass centered=True for those.
Note centered=True is the scale-only Procrustes fit: no
rotation is estimated, so it is not the full Umeyama similarity
estimate, whose scale differs once a rotation is jointly fitted.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
reference
|
ndarray
|
The pose/sequence to match. |
required |
target
|
ndarray
|
The pose/sequence being scaled. Same shape as |
required |
centered
|
(bool, keyword - only)
|
If True, subtract each array's centroid — the mean over every axis except the last (i.e. over all points, and all frames for a sequence) — before fitting. Default False (fit about the origin). |
False
|
Returns:
| Type | Description |
|---|---|
float
|
The optimal scale |
Notes
Source: Troje 2002 (pose normalization); Umeyama 1991 for the centered convention.
cov3dj(pos: npt.NDArray[np.float64]) -> npt.NDArray[np.float64]
¶
Covariance of 3D joint positions over time (Cov3DJ).
Flattens each frame's joints to a 3N vector and returns the
(3N, 3N) population covariance across frames (divides by F,
not F − 1) — a fixed-size pose descriptor independent of
sequence length.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
pos
|
(ndarray, shape(F, N, 3))
|
Per-frame joint positions. |
required |
Returns:
| Type | Description |
|---|---|
ndarray, shape (3N, 3N)
|
The population covariance matrix. |
Notes
Source: Hussein et al. (Cov3DJ).
lagged_covariance(signal: npt.NDArray[np.float64], lag: int) -> npt.NDArray[np.float64]
¶
Lagged covariance matrix — M(l) = (1/(T−l)) Σ_t (v_{t+l} − v̄)(v_t − v̄)ᵀ.
Captures temporal structure between channels at a fixed lag: the
covariance between the signal and itself lag samples earlier,
averaged over the T − l overlapping sample pairs (so every entry
is a mean, independent of the lag). The signal is centered on its
temporal mean v̄ first — a true covariance, so a constant offset
contributes nothing. lag=0 reduces to the ordinary population
covariance of the channels (cf. :func:cov3dj).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
signal
|
(ndarray, shape(T, D))
|
Multichannel signal (time × channels). |
required |
lag
|
int
|
Non-negative lag in samples. |
required |
Returns:
| Type | Description |
|---|---|
(ndarray, shape(D, D))
|
The lagged covariance. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Notes
Source: Venture et al. (lagged covariance descriptors).