Geometry¶
geometry
¶
Position descriptors for BVH motion — points in R³.
The position half of pybvh's geometry surface (the orientation half lives in
:mod:pybvh.rotations). Every function here is array-pure: it takes plain
NumPy point arrays and returns NumPy arrays, with no :class:~pybvh.bvh.Bvh
dependency, so downstream libraries can build on these kernels directly.
Two shape conventions run through the module:
- Point-set kernels (
bounding_box,bounding_sphere,bounding_ellipsoid,center_of_mass,verticality) takeptsshaped(..., P, 3)and reduce over the point axisP, keeping any leading batch axes (e.g. a frame axisF) — so they vectorize over time with no Python frame loop. - Trajectory kernels (
path_length,directness,curvature,torsion,movement_phase,ground_path) taketrajshaped(F, 3)or(F, N, 3)— the first axisFis time.
Derivatives (curvature, torsion, movement_phase) route through
:func:pybvh.signal.finite_difference, the same convention used by the
kinematics ladder, so geometry and velocity derivatives stay consistent.
Zero-denominator policy. Every ratio kernel (curvature,
directness, verticality) returns np.nan at samples where its
denominator vanishes (a stationary joint, a perfectly vertical pose). nan
is used deliberately over 0.0 so an undefined value is never confused
with a genuine zero (e.g. the real zero curvature of a straight segment).
The nan policy covers data degeneracy — values the motion itself made undefined. Invalid arguments (e.g. a weights vector with no positive total in center_of_mass) are caller mistakes and raise ValueError instead of silently propagating nan.
inter_joint_distance(pos: npt.NDArray[np.float64], pairs: npt.ArrayLike) -> npt.NDArray[np.float64]
¶
Euclidean distance between pairs of points — ‖p_a − p_b‖.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
pos
|
(ndarray, shape(..., P, 3))
|
Point positions (e.g. |
required |
pairs
|
(array_like, shape(Q, 2))
|
Integer index pairs into the point axis |
required |
Returns:
| Type | Description |
|---|---|
(ndarray, shape(..., Q))
|
Distance for each pair, vectorized over the leading axes. |
joint_angle(a: npt.NDArray[np.float64], vertex: npt.NDArray[np.float64], b: npt.NDArray[np.float64], degrees: bool = False) -> npt.NDArray[np.float64]
¶
Angle at vertex in the triangle a–vertex–b.
Uses the numerically stable form atan2(‖u×v‖, u·v) with
u = a − vertex, v = b − vertex — accurate across the whole
[0, π] range (unlike arccos of a normalized dot, which loses
precision near 0 and π). Symmetric: joint_angle(a, v, b) equals
joint_angle(b, v, a).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
a
|
(ndarray, shape(..., 3))
|
The two outer points and the shared vertex. |
required |
vertex
|
(ndarray, shape(..., 3))
|
The two outer points and the shared vertex. |
required |
b
|
(ndarray, shape(..., 3))
|
The two outer points and the shared vertex. |
required |
degrees
|
bool
|
Return degrees instead of radians (default radians). |
False
|
Returns:
| Type | Description |
|---|---|
(ndarray, shape(...))
|
The angle at |
Notes
Source: ubiquitous; see Saha et al., Crenn et al. 2016, Basak et al.
segment_axis_angle(seg: npt.NDArray[np.float64], axis: npt.NDArray[np.float64], degrees: bool = False) -> npt.NDArray[np.float64]
¶
Angle between a segment vector and a reference axis, in [0, π].
atan2(‖seg×axis‖, seg·axis) — e.g. the inclination of a bone
relative to world_up.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
seg
|
(ndarray, shape(..., 3))
|
Segment / bone direction vectors (need not be unit length). |
required |
axis
|
(ndarray, shape(3) or (..., 3))
|
Reference axis (need not be unit length). |
required |
degrees
|
bool
|
Return degrees instead of radians (default radians). |
False
|
Returns:
| Type | Description |
|---|---|
(ndarray, shape(...))
|
The angle between |
Notes
Source: Barliya et al., Gross et al., Truong et al.
triangle_area(a: npt.NDArray[np.float64], b: npt.NDArray[np.float64], c: npt.NDArray[np.float64]) -> npt.NDArray[np.float64]
¶
Area of triangle (a, b, c) — ½‖(b−a)×(c−a)‖.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
a
|
(ndarray, shape(..., 3))
|
Triangle vertices. |
required |
b
|
(ndarray, shape(..., 3))
|
Triangle vertices. |
required |
c
|
(ndarray, shape(..., 3))
|
Triangle vertices. |
required |
Returns:
| Type | Description |
|---|---|
(ndarray, shape(...))
|
Triangle area, vectorized over the leading axes. |
Notes
Source: Bhattacharya et al. (walk descriptors), Crenn et al. 2016.
point_to_plane_distance(point: npt.NDArray[np.float64], plane_point: npt.NDArray[np.float64], normal: npt.NDArray[np.float64], signed: bool = True) -> npt.NDArray[np.float64]
¶
Distance from point to the plane through plane_point.
(point − plane_point) · n̂, where n̂ is the unit normal.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
point
|
(ndarray, shape(..., 3))
|
Query point(s) and a point on the plane. |
required |
plane_point
|
(ndarray, shape(..., 3))
|
Query point(s) and a point on the plane. |
required |
normal
|
(ndarray, shape(..., 3))
|
Plane normal (need not be unit length). |
required |
signed
|
bool
|
If True (default), the sign encodes which side of the plane the point is on; if False, return the absolute distance. |
True
|
Returns:
| Type | Description |
|---|---|
(ndarray, shape(...))
|
Signed (or absolute) distance. |
Notes
Source: Müller et al. (motion templates), Kapadia et al.
point_to_segment_distance(point: npt.NDArray[np.float64], seg_a: npt.NDArray[np.float64], seg_b: npt.NDArray[np.float64]) -> npt.NDArray[np.float64]
¶
Shortest distance from point to the segment [seg_a, seg_b].
The projection parameter is clamped to [0, 1] so the nearest
point is on the segment, not its infinite line. A degenerate segment
(seg_a == seg_b) reduces to the point-to-point distance.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
point
|
(ndarray, shape(..., 3))
|
Query point and the two segment endpoints. |
required |
seg_a
|
(ndarray, shape(..., 3))
|
Query point and the two segment endpoints. |
required |
seg_b
|
(ndarray, shape(..., 3))
|
Query point and the two segment endpoints. |
required |
Returns:
| Type | Description |
|---|---|
(ndarray, shape(...))
|
Distance to the segment. |
Notes
Source: Müller et al., Kapadia et al.
bounding_box(pts: npt.NDArray[np.float64]) -> BoundingBox
¶
Axis-aligned bounding box of a point set.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
pts
|
(ndarray, shape(..., P, 3))
|
Points; reduced over the point axis |
required |
Returns:
| Type | Description |
|---|---|
BoundingBox
|
Named tuple |
Notes
Source: ubiquitous (gesture/gait bounding-region descriptors).
bounding_sphere(pts: npt.NDArray[np.float64]) -> BoundingSphere
¶
Approximate enclosing sphere via Ritter's two-pass heuristic.
Pass 1 finds a near-diameter pair (farthest point from an arbitrary seed, then farthest from that) to seat the centre; pass 2 grows the radius to the maximum distance from that centre, guaranteeing all points are enclosed. The result is approximate (not the minimal enclosing sphere) but fully vectorized over the leading axes — exact Welzl is recursive/randomized and would force a Python per-frame loop, which the library avoids.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
pts
|
(ndarray, shape(..., P, 3))
|
Points; reduced over the point axis |
required |
Returns:
| Type | Description |
|---|---|
BoundingSphere
|
Named tuple |
Notes
Source: Ritter (1990); Larboulette & Gibet, Noroozi et al.
bounding_ellipsoid(pts: npt.NDArray[np.float64]) -> BoundingEllipsoid
¶
PCA-aligned bounding ellipsoid of a point set.
The principal axes are the eigenvectors of the point covariance
(via batched :func:numpy.linalg.eigh). The semi-axis radii start
from the maximum absolute projection of the centred points onto each
axis and are then grown by one shared factor — the worst point's
ellipsoidal norm — so every point satisfies
Σ_k (x_k / r_k)² ≤ 1: the per-axis maxima alone only bound the
points' box, and a point projecting strongly onto two axes at once
would sit outside that inscribed ellipsoid. Approximate (not the
minimal-volume Löwner–John ellipsoid), but vectorized over the
leading axes.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
pts
|
(ndarray, shape(..., P, 3))
|
Points; reduced over the point axis |
required |
Returns:
| Type | Description |
|---|---|
BoundingEllipsoid
|
Named tuple |
Notes
Source: Larboulette & Gibet (motion descriptors).
center_of_mass(pts: npt.NDArray[np.float64], weights: npt.NDArray[np.float64] | None = None) -> npt.NDArray[np.float64]
¶
Centre of mass of a point set — Σ wₖ pₖ / Σ wₖ.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
pts
|
(ndarray, shape(..., P, 3))
|
Points; reduced over the point axis |
required |
weights
|
(ndarray, shape(P))
|
Per-point weights. Default is uniform (the plain centroid) — pybvh ships no body-segment mass model; pass anatomical masses explicitly for a true centre of mass. |
None
|
Returns:
| Type | Description |
|---|---|
(ndarray, shape(..., 3))
|
The (weighted) centre of mass. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Notes
Source: Larboulette & Gibet, Kapadia et al., Piana et al.
com_displacement(com: npt.NDArray[np.float64], com_ref: npt.NDArray[np.float64]) -> npt.NDArray[np.float64]
¶
Distance of a centre of mass from a reference — ‖com − com_ref‖.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
com
|
(ndarray, shape(..., 3))
|
Centre-of-mass position(s) (e.g. per-frame, |
required |
com_ref
|
(ndarray, shape(3) or (..., 3))
|
Reference centre of mass (e.g. the first-frame or mean CoM). Must be
in the same coordinate frame as |
required |
Returns:
| Type | Description |
|---|---|
(ndarray, shape(...))
|
Displacement magnitude. |
Notes
Source: Larboulette & Gibet, Kapadia et al.
verticality(pts: npt.NDArray[np.float64], up: npt.NDArray[np.float64]) -> npt.NDArray[np.float64]
¶
Height-to-width ratio of a point set along up.
The vertical extent (spread along up) divided by the horizontal
extent (the diagonal of the bounding box in the plane orthogonal to
up). > 1 is a tall/upright posture, < 1 a wide/crouched
one. Returns np.nan when the horizontal extent is ~0 (a perfectly
vertical configuration).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
pts
|
(ndarray, shape(..., P, 3))
|
Points; reduced over the point axis |
required |
up
|
(ndarray, shape(3))
|
Up axis (need not be unit length). |
required |
Returns:
| Type | Description |
|---|---|
(ndarray, shape(...))
|
The height/width ratio. |
Notes
Source: Larboulette & Gibet.
path_length(traj: npt.NDArray[np.float64]) -> npt.NDArray[np.float64]
¶
Arc length travelled — Σ ‖p_{t+1} − p_t‖ over the frame axis.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
traj
|
(ndarray, shape(F, 3) or (F, N, 3))
|
Trajectory; the first axis is time. |
required |
Returns:
| Type | Description |
|---|---|
ndarray
|
Scalar for |
Notes
Source: ubiquitous (trajectory / effort descriptors).
directness(traj: npt.NDArray[np.float64]) -> npt.NDArray[np.float64]
¶
Directness — ‖p_T − p_0‖ / path_length, in [0, 1].
The net start→end displacement as a fraction of the total distance
travelled: 1 for a path straight to its destination, approaching
0 for one that nets little progress — note an out-and-back returns
0 (zero net displacement), since this measures directness of
travel, not per-segment straightness. Returns np.nan for a
stationary trajectory (zero path length).
Also known as the straightness index (Camurri's "Directness Index").
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
traj
|
(ndarray, shape(F, 3) or (F, N, 3))
|
Trajectory; the first axis is time. |
required |
Returns:
| Type | Description |
|---|---|
ndarray
|
Scalar for |
Notes
Source: Camurri et al., Samadani et al., Ajili et al.
curvature(traj: npt.NDArray[np.float64], frame_time: float, stencil: str = 'central', pad: str = 'edge') -> npt.NDArray[np.float64]
¶
Trajectory curvature κ = ‖ṗ × p̈‖ / ‖ṗ‖³ per frame.
The radius of curvature is 1 / κ. Returns np.nan where the
speed ‖ṗ‖ is ~0 (a momentarily stationary joint, where curvature
is undefined). Note this is distinct from the genuine κ = 0 of a
straight segment.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
traj
|
(ndarray, shape(F, 3) or (F, N, 3))
|
Trajectory; the first axis is time. |
required |
frame_time
|
float
|
Seconds between frames. |
required |
stencil
|
optional
|
Finite-difference convention, shared with the kinematics ladder
(see :func: |
'central'
|
pad
|
optional
|
Finite-difference convention, shared with the kinematics ladder
(see :func: |
'central'
|
Returns:
| Type | Description |
|---|---|
ndarray
|
Curvature per frame: |
Notes
Source: Larboulette & Gibet, Gibet et al.
torsion(traj: npt.NDArray[np.float64], frame_time: float, stencil: str = 'central', pad: str = 'edge') -> npt.NDArray[np.float64]
¶
Trajectory torsion τ = (ṗ × p̈) · p⃛ / ‖ṗ × p̈‖² per frame.
Torsion measures how sharply the trajectory twists out of its
instantaneous plane; it is ~0 for a planar curve. Returns np.nan
where ‖ṗ × p̈‖ is ~0 (straight or stationary, where torsion is
undefined).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
traj
|
(ndarray, shape(F, 3) or (F, N, 3))
|
Trajectory; the first axis is time. |
required |
frame_time
|
float
|
Seconds between frames. |
required |
stencil
|
optional
|
Finite-difference convention (see :func: |
'central'
|
pad
|
optional
|
Finite-difference convention (see :func: |
'central'
|
Returns:
| Type | Description |
|---|---|
ndarray
|
Torsion per frame (shape as in :func: |
Notes
Source: Bouchard & Badler, Zhao & Badler.
movement_phase(traj: npt.NDArray[np.float64], frame_time: float, stencil: str = 'central', pad: str = 'edge') -> npt.NDArray[np.float64]
¶
Movement-phase signal speed · curvature = ‖ṗ × p̈‖ / ‖ṗ‖² per frame.
Peaks mark the fast, sharply-turning instants that segment a
trajectory into ballistic phases. np.nan where speed is ~0
(curvature is undefined there), matching :func:curvature.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
traj
|
(ndarray, shape(F, 3) or (F, N, 3))
|
Trajectory; the first axis is time. |
required |
frame_time
|
float
|
Seconds between frames. |
required |
stencil
|
optional
|
Finite-difference convention (see :func: |
'central'
|
pad
|
optional
|
Finite-difference convention (see :func: |
'central'
|
Returns:
| Type | Description |
|---|---|
ndarray
|
The |
Notes
Source: Larboulette & Gibet, Gibet et al.
ground_path(traj: npt.NDArray[np.float64], up: npt.NDArray[np.float64]) -> GroundPath
¶
Trajectory projected onto the ground plane (orthogonal to up).
Returns the projected path length and the signed-area magnitude of the projected polygon (via the shoelace formula — not a convex hull), a compact measure of how much ground a joint sweeps over.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
traj
|
(ndarray, shape(F, 3) or (F, N, 3))
|
Trajectory; the first axis is time. |
required |
up
|
(ndarray, shape(3))
|
Up axis (need not be unit length). |
required |
Returns:
| Type | Description |
|---|---|
GroundPath
|
Named tuple |
Notes
Source: Aristidou et al., Larboulette & Gibet.
pose_distance(pose_a: npt.NDArray[np.float64], pose_b: npt.NDArray[np.float64], reduction: str = 'frobenius') -> npt.NDArray[np.float64]
¶
Distance between two poses — Frobenius norm or MPJPE.
Two standard reductions of the per-joint position errors, selected
by reduction:
"frobenius"(default):‖X₁ − X₂‖— the root of the summed squared differences over the joint and coordinate axes (a pose-similarity kernel for nearest-neighbour / alignment work). A true metric — square it if a squared-distance kernel is wanted."mpjpe": mean per-joint position error — the mean over joints of each joint's Euclidean error, the near-universal pose-error metric of the pose-estimation and motion-reconstruction literature. Also a true metric.
For a uniform per-joint error e over N joints the two differ
by the constant factor √N (frobenius = √N·e, mpjpe = e);
when per-joint errors are unequal they diverge beyond any constant
— Frobenius weights large per-joint errors quadratically, MPJPE
linearly — so a published MPJPE figure cannot be recovered from the
Frobenius value.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
pose_a
|
(ndarray, shape(..., N, 3))
|
Poses (e.g. |
required |
pose_b
|
(ndarray, shape(..., N, 3))
|
Poses (e.g. |
required |
reduction
|
('frobenius', 'mpjpe')
|
How the trailing |
"frobenius"
|
Returns:
| Type | Description |
|---|---|
(ndarray, shape(...))
|
Distance, reduced over the trailing |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Notes
Source: trajectory-basis pose models (Torresani-era) for the Frobenius form; standard 3D pose-estimation evaluation (e.g. the Human3.6M protocol) for MPJPE.
mean_pose_subtract(seq: npt.NDArray[np.float64]) -> npt.NDArray[np.float64]
¶
Centre a sequence on its mean pose — p − mean_t p.
Removes the per-joint temporal mean, leaving only motion about the average posture.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
seq
|
(ndarray, shape(F, N, 3))
|
Pose sequence; the first axis is time. |
required |
Returns:
| Type | Description |
|---|---|
(ndarray, shape(F, N, 3))
|
The mean-subtracted sequence. |
Notes
Source: frame-operation primitive (PCA / trajectory-basis prep).