Skip to content

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) take pts shaped (..., P, 3) and reduce over the point axis P, keeping any leading batch axes (e.g. a frame axis F) — so they vectorize over time with no Python frame loop.
  • Trajectory kernels (path_length, directness, curvature, torsion, movement_phase, ground_path) take traj shaped (F, 3) or (F, N, 3) — the first axis F is 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. node_positions output (F, N, 3)).

required
pairs (array_like, shape(Q, 2))

Integer index pairs into the point axis P.

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

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 seg and axis.

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

required

Returns:

Type Description
BoundingBox

Named tuple (min, max, extent, volume)min/max/ extent shaped (..., 3), volume shaped (...). Vectorizes over the leading axes (no per-frame loop).

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

required

Returns:

Type Description
BoundingSphere

Named tuple (center, radius)center shaped (..., 3), radius shaped (...).

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

required

Returns:

Type Description
BoundingEllipsoid

Named tuple (center, radii, axes)center (..., 3), radii (..., 3) (semi-axis lengths, ascending eigenvalue order), axes (..., 3, 3) (principal directions as columns).

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

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 weights has no positive total (zero, sub-epsilon, negative, or NaN sum) — the weighted mean would be all-NaN (or sign-flipped) for every frame. Individual negative weights are allowed as long as the total stays positive.

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, (F, 3)).

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

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

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 (F, 3); shape (N,) for (F, N, 3).

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 (F, 3); shape (N,) for (F, N, 3).

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:pybvh.signal.finite_difference). Default "central" / "edge" keeps the output length F.

'central'
pad optional

Finite-difference convention, shared with the kinematics ladder (see :func:pybvh.signal.finite_difference). Default "central" / "edge" keeps the output length F.

'central'

Returns:

Type Description
ndarray

Curvature per frame: (F,) for (F, 3) input, (F, N) for (F, N, 3) (trimmed along the frame axis when pad="none").

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

'central'
pad optional

Finite-difference convention (see :func:curvature).

'central'

Returns:

Type Description
ndarray

Torsion per frame (shape as in :func:curvature).

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

'central'
pad optional

Finite-difference convention (see :func:curvature).

'central'

Returns:

Type Description
ndarray

The speed · curvature signal (shape as in :func:curvature).

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 (distance, area) — scalars for (F, 3) input, shape (N,) for (F, N, 3).

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. (N, 3) single poses, or (F, N, 3) sequences).

required
pose_b (ndarray, shape(..., N, 3))

Poses (e.g. (N, 3) single poses, or (F, N, 3) sequences).

required
reduction ('frobenius', 'mpjpe')

How the trailing (N, 3) axes reduce to a scalar (see above). Default "frobenius".

"frobenius"

Returns:

Type Description
(ndarray, shape(...))

Distance, reduced over the trailing (N, 3) axes.

Raises:

Type Description
ValueError

If reduction is not one of the two options.

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