Skip to content

Convex Hull

Overview

Compute convex hull features for spatial root system analysis. Useful for measuring root system spread and spatial distribution.

Key functions: - get_convhull - Compute convex hull polygon - get_chull_area - Hull area - get_chull_perimeter - Hull perimeter

Quick Example

import sleap_roots as sr

series = sr.Series.load("plant", primary_path="primary.slp", lateral_path="lateral.slp")
primary_pts = series.get_primary_points()
lateral_pts_list = series.get_lateral_points()

# Get all points
from sleap_roots import join_pts
all_pts = join_pts([primary_pts] + lateral_pts_list)

# Compute convex hull
hull = sr.convhull.get_convhull(all_pts)

# Compute hull traits
area = sr.convhull.get_chull_area(all_pts)
perimeter = sr.convhull.get_chull_perimeter(all_pts)

print(f"Hull area: {area:.2f} px²")
print(f"Hull perimeter: {perimeter:.2f} px")

API Reference

get_convhull

get_convhull

get_convhull(pts: ndarray) -> Optional[ConvexHull]

Compute the convex hull for the points per frame.

Parameters:

Name Type Description Default
pts ndarray

Root landmarks as an array of shape (..., 2).

required

Returns:

Type Description
Optional[ConvexHull]

An object representing the convex hull or None if a hull can't be formed.

Source code in sleap_roots/convhull.py
def get_convhull(pts: np.ndarray) -> Optional[ConvexHull]:
    """Compute the convex hull for the points per frame.

    Args:
        pts: Root landmarks as an array of shape (..., 2).

    Returns:
        An object representing the convex hull or None if a hull can't be formed.
    """
    # Ensure the input is an array of shape (..., 2)
    if pts.ndim < 2 or pts.shape[-1] != 2:
        raise ValueError("Input points should be of shape (..., 2).")

    # Reshape and filter out NaN values
    pts = pts.reshape(-1, 2)
    pts = pts[~np.isnan(pts).any(axis=-1)]

    # Check for infinite values
    if np.isinf(pts).any():
        logging.info("Cannot compute convex hull: input contains infinite values.")
        return None

    # Ensure there are at least 3 unique non-collinear points
    unique_pts = np.unique(pts, axis=0)
    if len(unique_pts) < 3:
        logging.info("Cannot compute convex hull: not enough unique points.")
        return None

    try:
        # Compute and return the convex hull
        return ConvexHull(unique_pts)
    except Exception as e:
        logging.info(f"Cannot compute convex hull: {e}")
        return None

get_chull_area

get_chull_area

get_chull_area(hull: Union[ndarray, ConvexHull]) -> float

Calculate the area of the convex hull formed by the given points.

Parameters:

Name Type Description Default
hull Union[ndarray, ConvexHull]

Either an array of landmark points or a pre-computed convex hull.

required

Returns:

Type Description
float

Scalar value representing the area of the convex hull. Returns NaN if unable

float

to compute the convex hull.

Source code in sleap_roots/convhull.py
def get_chull_area(hull: Union[np.ndarray, ConvexHull]) -> float:
    """Calculate the area of the convex hull formed by the given points.

    Args:
        hull: Either an array of landmark points or a pre-computed convex hull.

    Returns:
        Scalar value representing the area of the convex hull. Returns NaN if unable
        to compute the convex hull.
    """
    # If the input hull is None, return NaN
    if hull is None:
        return np.nan

    # If the input is an array, compute its convex hull
    if isinstance(hull, np.ndarray):
        hull = get_convhull(hull)

    # If hull becomes None after attempting to compute the convex hull, return NaN
    if hull is None:
        return np.nan

    # Ensure that the hull is of type ConvexHull
    if not isinstance(hull, ConvexHull):
        raise TypeError("After processing, the input must be a ConvexHull object.")

    # If hull couldn't be formed, return NaN
    if hull is None:
        return np.nan

    # Return the area of the convex hull
    return hull.volume

get_chull_perimeter

get_chull_perimeter

get_chull_perimeter(
    hull: Union[ndarray, ConvexHull, None],
) -> float

Calculate the perimeter of the convex hull formed by the given points.

Parameters:

Name Type Description Default
hull Union[ndarray, ConvexHull, None]

Either an array of landmark points, a pre-computed convex hull, or None.

required

Returns:

Type Description
float

Scalar value representing the perimeter of the convex hull. Returns NaN if

float

unable to compute the convex hull or if the input is None.

Source code in sleap_roots/convhull.py
def get_chull_perimeter(hull: Union[np.ndarray, ConvexHull, None]) -> float:
    """Calculate the perimeter of the convex hull formed by the given points.

    Args:
        hull: Either an array of landmark points, a pre-computed convex hull, or None.

    Returns:
        Scalar value representing the perimeter of the convex hull. Returns NaN if
        unable to compute the convex hull or if the input is None.
    """
    # If the input hull is None, return NaN
    if hull is None:
        return np.nan

    # If the input is an array, compute its convex hull
    if isinstance(hull, np.ndarray):
        hull = get_convhull(hull)

    # If hull becomes None after attempting to compute the convex hull, return NaN
    if hull is None:
        return np.nan

    # Ensure that the hull is of type ConvexHull
    if not isinstance(hull, ConvexHull):
        raise TypeError("After processing, the input must be a ConvexHull object.")

    # Compute the perimeter of the convex hull
    return hull.area