Skip to content

Centroids

Spatial annotations: Centroids · Boxes · ROIs · Segmentation. These types nest per-frame on LabeledFrame — see Working with annotations in frames.

A Centroid represents a single point at the center of an object. Centroids are the primary annotation type for object detection workflows where full pose skeletons are not needed. Centroid is abstract — use UserCentroid or PredictedCentroid.

Centroids support optional 3D coordinates (z), interconversion with single-node Instance objects, and the same track/instance metadata as other annotation types.

Importing TrackMate detections

PredictedCentroid(source="trackmate") is the canonical representation for TrackMate (ImageJ/Fiji) point tracking results. Load spot exports directly with sio.load_trackmate or let sio.load_file auto-detect the format from the CSV header. See Formats → TrackMate for the full schema.

Direct construction

>>> import sleap_io as sio
>>> centroid = sio.UserCentroid(
...     x=100.0, y=200.0,
... )
>>> print(centroid.xy)
(100.0, 200.0)
>>> print(centroid.yx)
(200.0, 100.0)

From a pose Instance

Create a centroid from an existing pose Instance with Centroid.from_pose (equivalently Instance.to_centroid):

>>> import numpy as np
>>> import sleap_io as sio
>>> skeleton = sio.Skeleton(["head", "thorax", "abdomen"])
>>> inst = sio.Instance.from_numpy(
...     np.array([[10, 20], [30, 40], [50, 60]]),
...     skeleton=skeleton,
... )
>>> centroid = sio.UserCentroid.from_pose(inst)
>>> print(centroid.xy)
(30.0, 40.0)
>>> print(centroid.source)  # records the method used
center_of_mass

The method parameter controls how the center point is computed:

Method Behavior source tag
"center_of_mass" NaN-ignoring (unweighted) mean of visible points (default) "center_of_mass"
"bbox_center" Center of the bounding box of visible points "bbox_center"
"geometric_median" Weiszfeld geometric median of visible points (outlier-robust) "geometric_median"
"anchor" Coordinates of a specific node (requires node) "anchor:<node>"

For method="anchor", pass node (a node name or index). If the anchor node is occluded, supply a fallback method (one of the non-anchor methods) to compute the centroid from the visible points instead; the source tag then records the chain, e.g. "anchor:nose->center_of_mass":

>>> import numpy as np
>>> import sleap_io as sio
>>> skeleton = sio.Skeleton(["head", "thorax", "abdomen"])
>>> inst = sio.Instance.from_numpy(
...     np.array([[10, 20], [30, 40], [50, 60]]),
...     skeleton=skeleton,
... )
>>> anchor_c = sio.UserCentroid.from_pose(inst, method="anchor", node="head")
>>> print(anchor_c.xy)
(10.0, 20.0)
>>> print(anchor_c.source)
anchor:head

When there are no visible points (or an occluded anchor with no fallback), the result is a degenerate centroid (x = y = nan); pass error_on_empty=True to raise a ValueError instead. The is_empty property reports this cheaply:

>>> import sleap_io as sio
>>> print(sio.UserCentroid(x=float("nan"), y=float("nan")).is_empty)
True

Renamed from from_instance

Centroid.from_instance() was renamed to Centroid.from_pose() (the pose modality is named pose everywhere, even though the backing class is Instance). from_instance() is kept as a deprecated alias that forwards to from_pose() and emits a DeprecationWarning.

Converting back to a pose Instance

A centroid can be converted to a single-node Instance with Centroid.to_pose for interoperability with pose-based workflows:

>>> import sleap_io as sio
>>> centroid = sio.UserCentroid(x=30.0, y=40.0)
>>> inst = centroid.to_pose()
>>> print(inst.numpy())
[[30. 40.]]

Renamed from to_instance

Centroid.to_instance() was renamed to Centroid.to_pose(). to_instance() is kept as a deprecated alias that forwards to to_pose() and emits a DeprecationWarning.

Constructing boxes, ROIs, and masks

A centroid is a single point, so it can seed a fixed-size BoundingBox, a circular ROI, or a rasterized SegmentationMask around itself. These complete the conversion matrix for the centroid modality:

>>> import sleap_io as sio
>>> centroid = sio.UserCentroid(x=30.0, y=40.0)
>>> box = centroid.to_bbox(size=20)                 # 20x20 box centered on the point
>>> print(box.xyxy)
(20.0, 30.0, 40.0, 50.0)
>>> roi = centroid.to_roi(radius=5)                 # disc of radius 5
>>> print(round(roi.area, 2))
78.41
>>> mask = centroid.to_mask(100, 100, radius=5)     # rasterized disc
>>> print(mask.area)
88

to_bbox requires size (a scalar for a square box or (w, h)) and accepts padding. to_roi and to_mask require radius. A PredictedCentroid produces predicted outputs carrying its score. A degenerate (NaN) centroid yields an empty target (NaN box / empty-Polygon ROI / all-background mask) unless error_on_empty=True.

User vs. predicted centroids

UserCentroid and PredictedCentroid distinguish human annotations from model predictions. PredictedCentroid adds a score field for confidence:

>>> import sleap_io as sio
>>> user_c = sio.UserCentroid(x=10, y=20)
>>> print(user_c.is_predicted)
False
>>> pred_c = sio.PredictedCentroid(x=10, y=20, score=0.95)
>>> print(pred_c.score)
0.95
>>> print(pred_c.is_predicted)
True

When using from_pose(), the return type matches the input: PredictedInstance produces PredictedCentroid, Instance produces UserCentroid.

Metadata fields

Every centroid can carry optional metadata:

Field Type Description
track Track \| None Tracking identity across frames
tracking_score float \| None Confidence of track identity assignment
identity Identity \| None Global cross-video re-ID identity (mirrors Instance.identity); persists via /identity_links (owner_type=2)
identity_score float \| None Confidence of the identity assignment
instance Instance \| None Linked pose instance
z float \| None Optional Z-coordinate for 3D data
category str Class label (e.g., "cell")
name str Human-readable name
source str How the centroid was computed (e.g., "center_of_mass")

Rendering

Centroids compose with pose rendering in sio.render_image / sio.render_video and are listed in Rendering → Segmentation Overlays. For standalone canvases, pair sio.draw_bboxes with Centroid.to_pose() or use the pose drawing helpers directly.


See also


API reference

sleap_io.Centroid

A point representing the center of an object.

Supports optional 3D coordinates, track/instance metadata, and interconversion with single-node Instance objects.

Attributes:

Name Type Description
x

X-coordinate in pixel space.

y

Y-coordinate in pixel space.

z

Optional Z-coordinate for 3D data. None for 2D.

track

Optional tracking identity.

tracking_score

Confidence of the track identity assignment. None if unassigned or manually assigned.

identity

Optional global, ground-truth Identity for this centroid -- the persistent cross-video animal identity / re-identification key. None if no global identity is assigned. Mirrors Instance.identity.

identity_score

Score associated with the identity assignment (e.g. the re-ID match similarity). None if unassigned or assigned manually. Kept separate from tracking_score (short-term tracklet vs long-term identity).

instance

Optional linked pose instance.

category

Optional Category (class label, e.g. "lysosome", "cell") for this centroid. Promoted from the legacy free-form string; None if unset. Mirrors Instance.category.

name

Human-readable name (e.g., "ID43008").

source

How the centroid was computed (e.g., "center_of_mass", "trackmate").

identity_embedding

Optional Embedding describing this detection's appearance for re-identification. None by default.

category_score

Score associated with the category assignment (e.g. the classifier confidence). None if unassigned or assigned manually.

category_embedding

Optional Embedding describing this detection's appearance for classification. None by default.

Notes

Centroids use identity-based equality (two Centroid objects are only equal if they are the same object in memory).

This class is abstract. Use UserCentroid or PredictedCentroid instead.

Methods:

Name Description
__attrs_post_init__

Validate that this class is not instantiated directly.

__init__

Method generated by attrs for class Centroid.

__repr__

Method generated by attrs for class Centroid.

__setattr__

Method generated by attrs for class Centroid.

from_instance

Create a centroid from an Instance (deprecated).

from_pose

Create a centroid from a pose Instance.

to_bbox

Construct a fixed-size bounding box centered on this centroid.

to_instance

Convert this centroid to a single-node Instance (deprecated).

to_mask

Rasterize a circular ROI around this centroid into a mask.

to_pose

Convert this centroid to a single-node Instance.

to_roi

Construct a circular ROI centered on this centroid.

Source code in sleap_io/model/centroid.py
@attrs.define(eq=False)
class Centroid:
    """A point representing the center of an object.

    Supports optional 3D coordinates, track/instance metadata,
    and interconversion with single-node ``Instance`` objects.

    Attributes:
        x: X-coordinate in pixel space.
        y: Y-coordinate in pixel space.
        z: Optional Z-coordinate for 3D data. ``None`` for 2D.
        track: Optional tracking identity.
        tracking_score: Confidence of the track identity assignment. ``None``
            if unassigned or manually assigned.
        identity: Optional global, ground-truth `Identity` for this centroid -- the
            persistent cross-video animal identity / re-identification key. ``None``
            if no global identity is assigned. Mirrors `Instance.identity`.
        identity_score: Score associated with the `identity` assignment (e.g. the
            re-ID match similarity). ``None`` if unassigned or assigned manually.
            Kept separate from `tracking_score` (short-term tracklet vs long-term
            identity).
        instance: Optional linked pose instance.
        category: Optional `Category` (class label, e.g. ``"lysosome"``,
            ``"cell"``) for this centroid. Promoted from the legacy free-form
            string; ``None`` if unset. Mirrors `Instance.category`.
        name: Human-readable name (e.g., ``"ID43008"``).
        source: How the centroid was computed (e.g., ``"center_of_mass"``,
            ``"trackmate"``).
        identity_embedding: Optional `Embedding` describing this detection's
            appearance for re-identification. ``None`` by default.
        category_score: Score associated with the `category` assignment (e.g. the
            classifier confidence). ``None`` if unassigned or assigned manually.
        category_embedding: Optional `Embedding` describing this detection's
            appearance for classification. ``None`` by default.

    Notes:
        Centroids use identity-based equality (two Centroid objects are only
        equal if they are the same object in memory).

        This class is abstract. Use ``UserCentroid`` or ``PredictedCentroid``
        instead.
    """

    x: float = attrs.field()
    y: float = attrs.field()
    z: float | None = attrs.field(default=None)
    track: "Track | None" = attrs.field(default=None)
    tracking_score: float | None = attrs.field(default=None)
    identity: "Identity | None" = attrs.field(default=None)
    identity_score: float | None = attrs.field(default=None)
    instance: "Instance | None" = attrs.field(default=None)
    category: "Category | None" = attrs.field(default=None, converter=to_category)
    name: str = attrs.field(default="")
    source: str = attrs.field(default="")
    identity_embedding: "Embedding | None" = attrs.field(default=None, repr=False)
    category_score: float | None = attrs.field(default=None)
    category_embedding: "Embedding | None" = attrs.field(default=None, repr=False)

    # Private: deferred instance index for lazy loading.
    _instance_idx: int = attrs.field(default=-1, repr=False, eq=False, init=False)

    def __attrs_post_init__(self):
        """Validate that this class is not instantiated directly."""
        if type(self) is Centroid:
            raise TypeError(
                "Centroid is abstract. Use UserCentroid or PredictedCentroid."
            )

    @property
    def xy(self) -> tuple[float, float]:
        """Return coordinates as ``(x, y)``."""
        return (self.x, self.y)

    @property
    def yx(self) -> tuple[float, float]:
        """Return coordinates as ``(y, x)`` (row, col order)."""
        return (self.y, self.x)

    @property
    def xyz(self) -> tuple[float, float, float | None]:
        """Return coordinates as ``(x, y, z)``."""
        return (self.x, self.y, self.z)

    @property
    def is_predicted(self) -> bool:
        """Return ``True`` if this is a ``PredictedCentroid``."""
        return isinstance(self, PredictedCentroid)

    @property
    def is_empty(self) -> bool:
        """Whether this centroid is degenerate (NaN ``x`` or ``y``)."""
        return bool(np.isnan(self.x) or np.isnan(self.y))

    def to_pose(
        self, skeleton: "Skeleton | None" = None
    ) -> "Instance | PredictedInstance":
        """Convert this centroid to a single-node ``Instance``.

        Args:
            skeleton: Skeleton to use for the instance. Must have exactly one
                node. Defaults to the shared ``CENTROID_SKELETON``.

        Returns:
            A ``PredictedInstance`` if this is a ``PredictedCentroid``,
            otherwise an ``Instance``.

        Raises:
            ValueError: If the skeleton has more than one node.
        """
        from sleap_io.model.instance import Instance, PredictedInstance

        if skeleton is None:
            skeleton = get_centroid_skeleton()

        if len(skeleton) > 1:
            raise ValueError(
                f"Skeleton must have exactly 1 node for centroid conversion, "
                f"got {len(skeleton)}."
            )

        points = np.array([[self.x, self.y]])

        if isinstance(self, PredictedCentroid):
            return PredictedInstance.from_numpy(
                points_data=points,
                skeleton=skeleton,
                score=self.score,
                track=self.track,
                tracking_score=self.tracking_score,
                identity=self.identity,
                identity_score=self.identity_score,
                identity_embedding=self.identity_embedding,
                category=self.category,
                category_score=self.category_score,
                category_embedding=self.category_embedding,
            )
        else:
            return Instance.from_numpy(
                points_data=points,
                skeleton=skeleton,
                track=self.track,
                tracking_score=self.tracking_score,
                identity=self.identity,
                identity_score=self.identity_score,
                identity_embedding=self.identity_embedding,
                category=self.category,
                category_score=self.category_score,
                category_embedding=self.category_embedding,
            )

    def to_instance(
        self, skeleton: "Skeleton | None" = None
    ) -> "Instance | PredictedInstance":
        """Convert this centroid to a single-node ``Instance`` (deprecated).

        .. deprecated::
            Use :meth:`to_pose` instead.

        Args:
            skeleton: Skeleton to use for the instance. Must have exactly one
                node. Defaults to the shared ``CENTROID_SKELETON``.

        Returns:
            A ``PredictedInstance`` if this is a ``PredictedCentroid``,
            otherwise an ``Instance``.
        """
        import warnings

        warnings.warn(
            "Centroid.to_instance() is deprecated; use Centroid.to_pose() instead.",
            DeprecationWarning,
            stacklevel=2,
        )
        return self.to_pose(skeleton=skeleton)

    @classmethod
    def from_pose(
        cls,
        instance: "Instance",
        method: str = "center_of_mass",
        node: "str | int | None" = None,
        fallback: "str | None" = None,
        error_on_empty: bool = False,
        **kwargs,
    ) -> "Centroid":
        """Create a centroid from a pose ``Instance``.

        Args:
            instance: The source instance.
            method: Computation method:
                - ``"center_of_mass"``: NaN-ignoring unweighted mean of visible
                  node coordinates.
                - ``"bbox_center"``: Center of the bounding box of visible points.
                - ``"geometric_median"``: Weiszfeld geometric median of visible
                  points (robust to outliers).
                - ``"anchor"``: Coordinates of a specific node (requires ``node``).
            node: Node specification for the ``"anchor"`` method. Can be a node
                name (str) or index (int). Required for ``"anchor"``.
            fallback: For the ``"anchor"`` method, a non-anchor method
                (``"center_of_mass"``, ``"bbox_center"``, or
                ``"geometric_median"``) to fall back to when the anchor node is
                occluded. If ``None``, an occluded anchor yields a degenerate
                centroid (or raises when ``error_on_empty`` is ``True``).
            error_on_empty: If ``True``, raise ``ValueError`` when there are no
                visible points to compute the requested centroid instead of
                returning a degenerate (NaN) centroid.
            **kwargs: Additional keyword arguments passed to the centroid
                constructor (e.g., ``video``, ``frame_idx``, ``category``).

        Returns:
            A ``PredictedCentroid`` if the instance is a ``PredictedInstance``,
            otherwise a ``UserCentroid``. The ``source`` attribute records the
            computation method (e.g. ``"center_of_mass"``, ``"anchor:nose"``, or
            ``"anchor:nose->center_of_mass"`` when a fallback was used).

        Raises:
            ValueError: For an unknown ``method``, a missing ``node`` for the
                ``"anchor"`` method, an invalid ``node`` type, or (when
                ``error_on_empty`` is ``True``) when there are no visible points.
        """
        from sleap_io.model.instance import PredictedInstance

        pts = instance.numpy(invisible_as_nan=True)
        visible = ~np.isnan(pts[:, 0])
        nan = float("nan")

        def _compute(reduce_method: str) -> tuple[float, float]:
            """Compute a non-anchor centroid; returns NaN if no visible points."""
            if reduce_method == "center_of_mass":
                if not visible.any():
                    return nan, nan
                return (
                    float(pts[visible, 0].mean()),
                    float(pts[visible, 1].mean()),
                )
            elif reduce_method == "bbox_center":
                if not visible.any():
                    return nan, nan
                return (
                    float((pts[visible, 0].min() + pts[visible, 0].max()) / 2),
                    float((pts[visible, 1].min() + pts[visible, 1].max()) / 2),
                )
            elif reduce_method == "geometric_median":
                if not visible.any():
                    return nan, nan
                return _geometric_median(pts[visible])
            else:
                raise ValueError(
                    f"Unknown method {reduce_method!r}. Expected 'center_of_mass', "
                    f"'bbox_center', 'geometric_median', or 'anchor'."
                )

        if method == "anchor":
            if node is None:
                raise ValueError("Must specify 'node' for anchor method.")
            if isinstance(node, str):
                node_idx = instance.skeleton.index(node)
            elif isinstance(node, (int, np.integer)):
                node_idx = int(node)
            else:
                raise ValueError(f"node must be str or int, got {type(node).__name__}")

            if not np.isnan(pts[node_idx, 0]):
                x = float(pts[node_idx, 0])
                y = float(pts[node_idx, 1])
                source = f"anchor:{node}"
            elif fallback is not None:
                x, y = _compute(fallback)
                source = f"anchor:{node}->{fallback}"
            else:
                x = y = nan
                source = f"anchor:{node}"
        elif method in ("center_of_mass", "bbox_center", "geometric_median"):
            x, y = _compute(method)
            source = method
        else:
            raise ValueError(
                f"Unknown method {method!r}. Expected 'center_of_mass', "
                f"'bbox_center', 'geometric_median', or 'anchor'."
            )

        if (np.isnan(x) or np.isnan(y)) and error_on_empty:
            raise ValueError(
                f"No visible points to compute centroid (method={method!r})."
            )

        # Build constructor kwargs.
        centroid_kwargs = dict(
            x=x,
            y=y,
            track=instance.track,
            tracking_score=instance.tracking_score,
            identity=instance.identity,
            identity_score=instance.identity_score,
            identity_embedding=instance.identity_embedding,
            category=instance.category,
            category_score=instance.category_score,
            category_embedding=instance.category_embedding,
            instance=instance,
            source=source,
        )
        centroid_kwargs.update(kwargs)

        if isinstance(instance, PredictedInstance):
            return PredictedCentroid(score=instance.score, **centroid_kwargs)
        else:
            return UserCentroid(**centroid_kwargs)

    @classmethod
    def from_instance(
        cls,
        instance: "Instance",
        method: str = "center_of_mass",
        node: "str | int | None" = None,
        fallback: "str | None" = None,
        error_on_empty: bool = False,
        **kwargs,
    ) -> "Centroid":
        """Create a centroid from an ``Instance`` (deprecated).

        .. deprecated::
            Use :meth:`from_pose` instead.

        Args:
            instance: The source instance.
            method: Computation method (see :meth:`from_pose`).
            node: Node specification for the ``"anchor"`` method.
            fallback: Fallback method for an occluded anchor.
            error_on_empty: Whether to raise instead of returning a degenerate
                centroid.
            **kwargs: Additional keyword arguments passed to the constructor.

        Returns:
            A ``PredictedCentroid`` or ``UserCentroid`` (see :meth:`from_pose`).
        """
        import warnings

        warnings.warn(
            "Centroid.from_instance() is deprecated; use Centroid.from_pose() instead.",
            DeprecationWarning,
            stacklevel=2,
        )
        return cls.from_pose(
            instance,
            method=method,
            node=node,
            fallback=fallback,
            error_on_empty=error_on_empty,
            **kwargs,
        )

    def to_bbox(
        self,
        size: float | tuple[float, float],
        padding: float | tuple[float, float] = 0.0,
        error_on_empty: bool = False,
    ) -> "BoundingBox":
        """Construct a fixed-size bounding box centered on this centroid.

        A ``PredictedCentroid`` produces a ``PredictedBoundingBox`` carrying its
        ``score``; any other centroid produces a ``UserBoundingBox``. Metadata
        (track, tracking_score, identity, identity_score, category, name, source,
        instance) is inherited.

        Args:
            size: Box size centered on the centroid. A scalar yields a square box
                of that side length; a ``(w, h)`` tuple sets width and height
                independently. Required.
            padding: Amount to inflate the box outward after sizing. Scalar
                applies to both axes; a ``(px, py)`` tuple applies per-axis.
                Negative values shrink the box.
            error_on_empty: If ``True``, raise ``ValueError`` when this centroid
                is degenerate (NaN) instead of returning a degenerate box.

        Returns:
            A ``BoundingBox`` centered on the centroid (or NaN corners if empty).

        Raises:
            ValueError: If ``size`` is ``None``, or if the centroid is degenerate
                and ``error_on_empty`` is ``True``.
        """
        if size is None:
            raise ValueError("'size' is required for Centroid.to_bbox().")

        from sleap_io.model.bbox import PredictedBoundingBox, UserBoundingBox
        from sleap_io.model.roi import _apply_padding

        if self.is_empty:
            if error_on_empty:
                raise ValueError(
                    "Cannot compute bounding box of a degenerate (NaN) centroid."
                )
            nan = float("nan")
            x1 = y1 = x2 = y2 = nan
        else:
            if isinstance(size, (tuple, list)):
                w, h = size
            else:
                w = h = size
            x1 = self.x - w / 2
            y1 = self.y - h / 2
            x2 = self.x + w / 2
            y2 = self.y + h / 2
            x1, y1, x2, y2 = _apply_padding(x1, y1, x2, y2, padding)

        kwargs = dict(
            x1=x1,
            y1=y1,
            x2=x2,
            y2=y2,
            angle=0.0,
            track=self.track,
            tracking_score=self.tracking_score,
            identity=self.identity,
            identity_score=self.identity_score,
            identity_embedding=self.identity_embedding,
            instance=self.instance,
            category=self.category,
            category_score=self.category_score,
            category_embedding=self.category_embedding,
            name=self.name,
            source=self.source,
        )
        if self.is_predicted:
            return PredictedBoundingBox(score=self.score, **kwargs)
        return UserBoundingBox(**kwargs)

    def to_roi(self, radius: float, error_on_empty: bool = False) -> "ROI":
        """Construct a circular ROI centered on this centroid.

        A ``PredictedCentroid`` produces a ``PredictedROI`` carrying its
        ``score``; any other centroid produces a ``UserROI``. Metadata (track,
        tracking_score, identity, identity_score, category, name, source,
        instance) is inherited.

        Args:
            radius: Radius of the circular ROI. Required.
            error_on_empty: If ``True``, raise ``ValueError`` when this centroid
                is degenerate (NaN) instead of returning an empty-geometry ROI.

        Returns:
            A ``ROI`` with a buffered-point (circular) geometry, or an empty
            ``Polygon`` geometry if the centroid is degenerate.

        Raises:
            ValueError: If the centroid is degenerate and ``error_on_empty`` is
                ``True``.
        """
        from shapely.geometry import Point, Polygon

        from sleap_io.model.roi import PredictedROI, UserROI

        if self.is_empty:
            if error_on_empty:
                raise ValueError("Cannot compute ROI of a degenerate (NaN) centroid.")
            geom = Polygon()
        else:
            geom = Point(self.x, self.y).buffer(radius)

        kwargs = dict(
            geometry=geom,
            track=self.track,
            tracking_score=self.tracking_score,
            identity=self.identity,
            identity_score=self.identity_score,
            identity_embedding=self.identity_embedding,
            instance=self.instance,
            category=self.category,
            category_score=self.category_score,
            category_embedding=self.category_embedding,
            name=self.name,
            source=self.source,
        )
        if self.is_predicted:
            return PredictedROI(score=self.score, **kwargs)
        return UserROI(**kwargs)

    def to_mask(
        self,
        height: int,
        width: int,
        radius: float,
        error_on_empty: bool = False,
    ) -> "SegmentationMask":
        """Rasterize a circular ROI around this centroid into a mask.

        Equivalent to ``self.to_roi(radius).to_mask(height, width)``. A
        ``PredictedCentroid`` produces a ``PredictedSegmentationMask`` carrying
        its ``score``; any other centroid produces a ``UserSegmentationMask``.
        Metadata is inherited.

        Args:
            height: Height of the output mask in pixels.
            width: Width of the output mask in pixels.
            radius: Radius of the circular region around the centroid. Required.
            error_on_empty: If ``True``, raise ``ValueError`` when this centroid
                is degenerate (NaN) instead of returning an all-background mask.

        Returns:
            A ``SegmentationMask`` with the rasterized circular region (all
            background if the centroid is degenerate).

        Raises:
            ValueError: If the centroid is degenerate and ``error_on_empty`` is
                ``True``.
        """
        if self.is_empty:
            if error_on_empty:
                raise ValueError("Cannot compute mask of a degenerate (NaN) centroid.")
            from sleap_io.model.mask import (
                PredictedSegmentationMask,
                UserSegmentationMask,
            )

            empty = np.zeros((height, width), dtype=bool)
            kwargs = dict(
                track=self.track,
                tracking_score=self.tracking_score,
                identity=self.identity,
                identity_score=self.identity_score,
                identity_embedding=self.identity_embedding,
                instance=self.instance,
                category=self.category,
                category_score=self.category_score,
                category_embedding=self.category_embedding,
                name=self.name,
                source=self.source,
            )
            if self.is_predicted:
                return PredictedSegmentationMask.from_numpy(
                    empty, score=self.score, **kwargs
                )
            return UserSegmentationMask.from_numpy(empty, **kwargs)

        return self.to_roi(radius).to_mask(height, width)

__annotations__ = {'x': 'float', 'y': 'float', 'z': 'float | None', 'track': "'Track | None'", 'tracking_score': 'float | None', 'identity': "'Identity | None'", 'identity_score': 'float | None', 'instance': "'Instance | None'", 'category': "'Category | None'", 'name': 'str', 'source': 'str', 'identity_embedding': "'Embedding | None'", 'category_score': 'float | None', 'category_embedding': "'Embedding | None'", '_instance_idx': 'int'} class-attribute

dict() -> new empty dictionary dict(mapping) -> new dictionary initialized from a mapping object's (key, value) pairs dict(iterable) -> new dictionary initialized as if via: d = {} for k, v in iterable: d[k] = v dict(**kwargs) -> new dictionary initialized with the name=value pairs in the keyword argument list. For example: dict(one=1, two=2)

__attrs_own_setattr__ = True class-attribute

Returns True when the argument is true, False otherwise. The builtins True and False are the only two instances of the class bool. The class bool is a subclass of the class int, and cannot be subclassed.

__attrs_props__ = ClassProps(is_exception=False, is_slotted=True, has_weakref_slot=True, is_frozen=False, kw_only=<KeywordOnly.NO: 'no'>, collected_fields_by_mro=True, added_init=True, added_repr=True, added_eq=False, added_ordering=False, hashability=<Hashability.LEAVE_ALONE: 'leave_alone'>, added_match_args=True, added_str=False, added_pickling=True, on_setattr_hook=<function pipe.<locals>.wrapped_pipe at 0x7f08471ce840>, field_transformer=None) class-attribute

Effective class properties as derived from parameters to attr.s() or define() decorators.

This is the same data structure that attrs uses internally to decide how to construct the final class.

Warning:

This feature is currently **experimental** and is not covered by our
strict backwards-compatibility guarantees.

Attributes:

Name Type Description
is_exception bool

Whether the class is treated as an exception class.

is_slotted bool

Whether the class is slotted <slotted classes>.

has_weakref_slot bool

Whether the class has a slot for weak references.

is_frozen bool

Whether the class is frozen.

kw_only KeywordOnly

Whether / how the class enforces keyword-only arguments on the __init__ method.

collected_fields_by_mro bool

Whether the class fields were collected by method resolution order. That is, correctly but unlike dataclasses.

added_init bool

Whether the class has an attrs-generated __init__ method.

added_repr bool

Whether the class has an attrs-generated __repr__ method.

added_eq bool

Whether the class has attrs-generated equality methods.

added_ordering bool

Whether the class has attrs-generated ordering methods.

hashability Hashability

How hashable <hashing> the class is.

added_match_args bool

Whether the class supports positional match <match> over its fields.

added_str bool

Whether the class has an attrs-generated __str__ method.

added_pickling bool

Whether the class has attrs-generated __getstate__ and __setstate__ methods for pickle.

on_setattr_hook Callable[[Any, Attribute[Any], Any], Any] | None

The class's __setattr__ hook.

field_transformer Callable[[Attribute[Any]], Attribute[Any]] | None

The class's field transformers <transform-fields>.

.. versionadded:: 25.4.0

__doc__ = 'A point representing the center of an object.\n\nSupports optional 3D coordinates, track/instance metadata,\nand interconversion with single-node ``Instance`` objects.\n\nAttributes:\n x: X-coordinate in pixel space.\n y: Y-coordinate in pixel space.\n z: Optional Z-coordinate for 3D data. ``None`` for 2D.\n track: Optional tracking identity.\n tracking_score: Confidence of the track identity assignment. ``None``\n if unassigned or manually assigned.\n identity: Optional global, ground-truth `Identity` for this centroid -- the\n persistent cross-video animal identity / re-identification key. ``None``\n if no global identity is assigned. Mirrors `Instance.identity`.\n identity_score: Score associated with the `identity` assignment (e.g. the\n re-ID match similarity). ``None`` if unassigned or assigned manually.\n Kept separate from `tracking_score` (short-term tracklet vs long-term\n identity).\n instance: Optional linked pose instance.\n category: Optional `Category` (class label, e.g. ``"lysosome"``,\n ``"cell"``) for this centroid. Promoted from the legacy free-form\n string; ``None`` if unset. Mirrors `Instance.category`.\n name: Human-readable name (e.g., ``"ID43008"``).\n source: How the centroid was computed (e.g., ``"center_of_mass"``,\n ``"trackmate"``).\n identity_embedding: Optional `Embedding` describing this detection\'s\n appearance for re-identification. ``None`` by default.\n category_score: Score associated with the `category` assignment (e.g. the\n classifier confidence). ``None`` if unassigned or assigned manually.\n category_embedding: Optional `Embedding` describing this detection\'s\n appearance for classification. ``None`` by default.\n\nNotes:\n Centroids use identity-based equality (two Centroid objects are only\n equal if they are the same object in memory).\n\n This class is abstract. Use ``UserCentroid`` or ``PredictedCentroid``\n instead.\n' class-attribute

str(object='') -> str str(bytes_or_buffer[, encoding[, errors]]) -> str

Create a new string object from the given object. If encoding or errors is specified, then the object must expose a data buffer that will be decoded using the given encoding and error handler. Otherwise, returns the result of object.str() (if defined) or repr(object). encoding defaults to 'utf-8'. errors defaults to 'strict'.

__firstlineno__ = 101 class-attribute

int([x]) -> integer int(x, base=10) -> integer

Convert a number or string to an integer, or return 0 if no arguments are given. If x is a number, return x.int(). For floating-point numbers, this truncates towards zero.

If x is not a number or if base is given, then x must be a string, bytes, or bytearray instance representing an integer literal in the given base. The literal can be preceded by '+' or '-' and be surrounded by whitespace. The base defaults to 10. Valid bases are 0 and 2-36. Base 0 means to interpret the base from the string as an integer iteral.

int('0b100', base=0) 4

__match_args__ = ('x', 'y', 'z', 'track', 'tracking_score', 'identity', 'identity_score', 'instance', 'category', 'name', 'source', 'identity_embedding', 'category_score', 'category_embedding') class-attribute

Built-in immutable sequence.

If no argument is given, the constructor returns an empty tuple. If iterable is specified the tuple is initialized from iterable's items.

If the argument is a tuple, the return value is the same object.

__module__ = 'sleap_io.model.centroid' class-attribute

str(object='') -> str str(bytes_or_buffer[, encoding[, errors]]) -> str

Create a new string object from the given object. If encoding or errors is specified, then the object must expose a data buffer that will be decoded using the given encoding and error handler. Otherwise, returns the result of object.str() (if defined) or repr(object). encoding defaults to 'utf-8'. errors defaults to 'strict'.

__slots__ = ('x', 'y', 'z', 'track', 'tracking_score', 'identity', 'identity_score', 'instance', 'category', 'name', 'source', 'identity_embedding', 'category_score', 'category_embedding', '_instance_idx', '__weakref__') class-attribute

Built-in immutable sequence.

If no argument is given, the constructor returns an empty tuple. If iterable is specified the tuple is initialized from iterable's items.

If the argument is a tuple, the return value is the same object.

__static_attributes__ = () class-attribute

Built-in immutable sequence.

If no argument is given, the constructor returns an empty tuple. If iterable is specified the tuple is initialized from iterable's items.

If the argument is a tuple, the return value is the same object.

__weakref__ property

list of weak references to the object

is_empty property

Whether this centroid is degenerate (NaN x or y).

is_predicted property

Return True if this is a PredictedCentroid.

xy property

Return coordinates as (x, y).

xyz property

Return coordinates as (x, y, z).

yx property

Return coordinates as (y, x) (row, col order).

__attrs_post_init__()

Validate that this class is not instantiated directly.

Source code in sleap_io/model/centroid.py
def __attrs_post_init__(self):
    """Validate that this class is not instantiated directly."""
    if type(self) is Centroid:
        raise TypeError(
            "Centroid is abstract. Use UserCentroid or PredictedCentroid."
        )

__init__(x, y, z=None, track=None, tracking_score=None, identity=None, identity_score=None, instance=None, category=None, name='', source='', identity_embedding=None, category_score=None, category_embedding=None)

Method generated by attrs for class Centroid.

Source code in sleap_io/model/centroid.py
from __future__ import annotations

from typing import TYPE_CHECKING

import attrs
import numpy as np

from sleap_io.model.category import to_category

if TYPE_CHECKING:
    from sleap_io.model.bbox import BoundingBox
    from sleap_io.model.category import Category
    from sleap_io.model.embedding import Embedding
    from sleap_io.model.identity import Identity
    from sleap_io.model.instance import Instance, PredictedInstance, Track
    from sleap_io.model.mask import SegmentationMask
    from sleap_io.model.roi import ROI
    from sleap_io.model.skeleton import Skeleton

__repr__()

Method generated by attrs for class Centroid.

Source code in sleap_io/model/centroid.py
"""Data structures for centroid annotations.

Centroids are lightweight point annotations representing the center of an object.
They support user/predicted distinction and interconversion with single-node
``Instance`` objects.

The class hierarchy:
    - ``Centroid`` — abstract base with coordinates, video/frame/track/instance metadata
    - ``UserCentroid`` — human-annotated or derived centroid
    - ``PredictedCentroid`` — model-predicted centroid with confidence score

A module-level ``CENTROID_SKELETON`` is provided for creating single-node
``Instance`` objects from centroids.
"""

__setattr__(name, val)

Method generated by attrs for class Centroid.

from_instance(instance, method='center_of_mass', node=None, fallback=None, error_on_empty=False, **kwargs) classmethod

Create a centroid from an Instance (deprecated).

.. deprecated:: Use :meth:from_pose instead.

Parameters:

Name Type Description Default
instance Instance

The source instance.

required
method str

Computation method (see :meth:from_pose).

'center_of_mass'
node str | int | None

Node specification for the "anchor" method.

None
fallback str | None

Fallback method for an occluded anchor.

None
error_on_empty bool

Whether to raise instead of returning a degenerate centroid.

False
**kwargs

Additional keyword arguments passed to the constructor.

required

Returns:

Type Description
Centroid

A PredictedCentroid or UserCentroid (see :meth:from_pose).

Source code in sleap_io/model/centroid.py
@classmethod
def from_instance(
    cls,
    instance: "Instance",
    method: str = "center_of_mass",
    node: "str | int | None" = None,
    fallback: "str | None" = None,
    error_on_empty: bool = False,
    **kwargs,
) -> "Centroid":
    """Create a centroid from an ``Instance`` (deprecated).

    .. deprecated::
        Use :meth:`from_pose` instead.

    Args:
        instance: The source instance.
        method: Computation method (see :meth:`from_pose`).
        node: Node specification for the ``"anchor"`` method.
        fallback: Fallback method for an occluded anchor.
        error_on_empty: Whether to raise instead of returning a degenerate
            centroid.
        **kwargs: Additional keyword arguments passed to the constructor.

    Returns:
        A ``PredictedCentroid`` or ``UserCentroid`` (see :meth:`from_pose`).
    """
    import warnings

    warnings.warn(
        "Centroid.from_instance() is deprecated; use Centroid.from_pose() instead.",
        DeprecationWarning,
        stacklevel=2,
    )
    return cls.from_pose(
        instance,
        method=method,
        node=node,
        fallback=fallback,
        error_on_empty=error_on_empty,
        **kwargs,
    )

from_pose(instance, method='center_of_mass', node=None, fallback=None, error_on_empty=False, **kwargs) classmethod

Create a centroid from a pose Instance.

Parameters:

Name Type Description Default
instance Instance

The source instance.

required
method str

Computation method: - "center_of_mass": NaN-ignoring unweighted mean of visible node coordinates. - "bbox_center": Center of the bounding box of visible points. - "geometric_median": Weiszfeld geometric median of visible points (robust to outliers). - "anchor": Coordinates of a specific node (requires node).

'center_of_mass'
node str | int | None

Node specification for the "anchor" method. Can be a node name (str) or index (int). Required for "anchor".

None
fallback str | None

For the "anchor" method, a non-anchor method ("center_of_mass", "bbox_center", or "geometric_median") to fall back to when the anchor node is occluded. If None, an occluded anchor yields a degenerate centroid (or raises when error_on_empty is True).

None
error_on_empty bool

If True, raise ValueError when there are no visible points to compute the requested centroid instead of returning a degenerate (NaN) centroid.

False
**kwargs

Additional keyword arguments passed to the centroid constructor (e.g., video, frame_idx, category).

required

Returns:

Type Description
Centroid

A PredictedCentroid if the instance is a PredictedInstance, otherwise a UserCentroid. The source attribute records the computation method (e.g. "center_of_mass", "anchor:nose", or "anchor:nose->center_of_mass" when a fallback was used).

Raises:

Type Description
ValueError

For an unknown method, a missing node for the "anchor" method, an invalid node type, or (when error_on_empty is True) when there are no visible points.

Source code in sleap_io/model/centroid.py
@classmethod
def from_pose(
    cls,
    instance: "Instance",
    method: str = "center_of_mass",
    node: "str | int | None" = None,
    fallback: "str | None" = None,
    error_on_empty: bool = False,
    **kwargs,
) -> "Centroid":
    """Create a centroid from a pose ``Instance``.

    Args:
        instance: The source instance.
        method: Computation method:
            - ``"center_of_mass"``: NaN-ignoring unweighted mean of visible
              node coordinates.
            - ``"bbox_center"``: Center of the bounding box of visible points.
            - ``"geometric_median"``: Weiszfeld geometric median of visible
              points (robust to outliers).
            - ``"anchor"``: Coordinates of a specific node (requires ``node``).
        node: Node specification for the ``"anchor"`` method. Can be a node
            name (str) or index (int). Required for ``"anchor"``.
        fallback: For the ``"anchor"`` method, a non-anchor method
            (``"center_of_mass"``, ``"bbox_center"``, or
            ``"geometric_median"``) to fall back to when the anchor node is
            occluded. If ``None``, an occluded anchor yields a degenerate
            centroid (or raises when ``error_on_empty`` is ``True``).
        error_on_empty: If ``True``, raise ``ValueError`` when there are no
            visible points to compute the requested centroid instead of
            returning a degenerate (NaN) centroid.
        **kwargs: Additional keyword arguments passed to the centroid
            constructor (e.g., ``video``, ``frame_idx``, ``category``).

    Returns:
        A ``PredictedCentroid`` if the instance is a ``PredictedInstance``,
        otherwise a ``UserCentroid``. The ``source`` attribute records the
        computation method (e.g. ``"center_of_mass"``, ``"anchor:nose"``, or
        ``"anchor:nose->center_of_mass"`` when a fallback was used).

    Raises:
        ValueError: For an unknown ``method``, a missing ``node`` for the
            ``"anchor"`` method, an invalid ``node`` type, or (when
            ``error_on_empty`` is ``True``) when there are no visible points.
    """
    from sleap_io.model.instance import PredictedInstance

    pts = instance.numpy(invisible_as_nan=True)
    visible = ~np.isnan(pts[:, 0])
    nan = float("nan")

    def _compute(reduce_method: str) -> tuple[float, float]:
        """Compute a non-anchor centroid; returns NaN if no visible points."""
        if reduce_method == "center_of_mass":
            if not visible.any():
                return nan, nan
            return (
                float(pts[visible, 0].mean()),
                float(pts[visible, 1].mean()),
            )
        elif reduce_method == "bbox_center":
            if not visible.any():
                return nan, nan
            return (
                float((pts[visible, 0].min() + pts[visible, 0].max()) / 2),
                float((pts[visible, 1].min() + pts[visible, 1].max()) / 2),
            )
        elif reduce_method == "geometric_median":
            if not visible.any():
                return nan, nan
            return _geometric_median(pts[visible])
        else:
            raise ValueError(
                f"Unknown method {reduce_method!r}. Expected 'center_of_mass', "
                f"'bbox_center', 'geometric_median', or 'anchor'."
            )

    if method == "anchor":
        if node is None:
            raise ValueError("Must specify 'node' for anchor method.")
        if isinstance(node, str):
            node_idx = instance.skeleton.index(node)
        elif isinstance(node, (int, np.integer)):
            node_idx = int(node)
        else:
            raise ValueError(f"node must be str or int, got {type(node).__name__}")

        if not np.isnan(pts[node_idx, 0]):
            x = float(pts[node_idx, 0])
            y = float(pts[node_idx, 1])
            source = f"anchor:{node}"
        elif fallback is not None:
            x, y = _compute(fallback)
            source = f"anchor:{node}->{fallback}"
        else:
            x = y = nan
            source = f"anchor:{node}"
    elif method in ("center_of_mass", "bbox_center", "geometric_median"):
        x, y = _compute(method)
        source = method
    else:
        raise ValueError(
            f"Unknown method {method!r}. Expected 'center_of_mass', "
            f"'bbox_center', 'geometric_median', or 'anchor'."
        )

    if (np.isnan(x) or np.isnan(y)) and error_on_empty:
        raise ValueError(
            f"No visible points to compute centroid (method={method!r})."
        )

    # Build constructor kwargs.
    centroid_kwargs = dict(
        x=x,
        y=y,
        track=instance.track,
        tracking_score=instance.tracking_score,
        identity=instance.identity,
        identity_score=instance.identity_score,
        identity_embedding=instance.identity_embedding,
        category=instance.category,
        category_score=instance.category_score,
        category_embedding=instance.category_embedding,
        instance=instance,
        source=source,
    )
    centroid_kwargs.update(kwargs)

    if isinstance(instance, PredictedInstance):
        return PredictedCentroid(score=instance.score, **centroid_kwargs)
    else:
        return UserCentroid(**centroid_kwargs)

to_bbox(size, padding=0.0, error_on_empty=False)

Construct a fixed-size bounding box centered on this centroid.

A PredictedCentroid produces a PredictedBoundingBox carrying its score; any other centroid produces a UserBoundingBox. Metadata (track, tracking_score, identity, identity_score, category, name, source, instance) is inherited.

Parameters:

Name Type Description Default
size float | tuple[float, float]

Box size centered on the centroid. A scalar yields a square box of that side length; a (w, h) tuple sets width and height independently. Required.

required
padding float | tuple[float, float]

Amount to inflate the box outward after sizing. Scalar applies to both axes; a (px, py) tuple applies per-axis. Negative values shrink the box.

0.0
error_on_empty bool

If True, raise ValueError when this centroid is degenerate (NaN) instead of returning a degenerate box.

False

Returns:

Type Description
BoundingBox

A BoundingBox centered on the centroid (or NaN corners if empty).

Raises:

Type Description
ValueError

If size is None, or if the centroid is degenerate and error_on_empty is True.

Source code in sleap_io/model/centroid.py
def to_bbox(
    self,
    size: float | tuple[float, float],
    padding: float | tuple[float, float] = 0.0,
    error_on_empty: bool = False,
) -> "BoundingBox":
    """Construct a fixed-size bounding box centered on this centroid.

    A ``PredictedCentroid`` produces a ``PredictedBoundingBox`` carrying its
    ``score``; any other centroid produces a ``UserBoundingBox``. Metadata
    (track, tracking_score, identity, identity_score, category, name, source,
    instance) is inherited.

    Args:
        size: Box size centered on the centroid. A scalar yields a square box
            of that side length; a ``(w, h)`` tuple sets width and height
            independently. Required.
        padding: Amount to inflate the box outward after sizing. Scalar
            applies to both axes; a ``(px, py)`` tuple applies per-axis.
            Negative values shrink the box.
        error_on_empty: If ``True``, raise ``ValueError`` when this centroid
            is degenerate (NaN) instead of returning a degenerate box.

    Returns:
        A ``BoundingBox`` centered on the centroid (or NaN corners if empty).

    Raises:
        ValueError: If ``size`` is ``None``, or if the centroid is degenerate
            and ``error_on_empty`` is ``True``.
    """
    if size is None:
        raise ValueError("'size' is required for Centroid.to_bbox().")

    from sleap_io.model.bbox import PredictedBoundingBox, UserBoundingBox
    from sleap_io.model.roi import _apply_padding

    if self.is_empty:
        if error_on_empty:
            raise ValueError(
                "Cannot compute bounding box of a degenerate (NaN) centroid."
            )
        nan = float("nan")
        x1 = y1 = x2 = y2 = nan
    else:
        if isinstance(size, (tuple, list)):
            w, h = size
        else:
            w = h = size
        x1 = self.x - w / 2
        y1 = self.y - h / 2
        x2 = self.x + w / 2
        y2 = self.y + h / 2
        x1, y1, x2, y2 = _apply_padding(x1, y1, x2, y2, padding)

    kwargs = dict(
        x1=x1,
        y1=y1,
        x2=x2,
        y2=y2,
        angle=0.0,
        track=self.track,
        tracking_score=self.tracking_score,
        identity=self.identity,
        identity_score=self.identity_score,
        identity_embedding=self.identity_embedding,
        instance=self.instance,
        category=self.category,
        category_score=self.category_score,
        category_embedding=self.category_embedding,
        name=self.name,
        source=self.source,
    )
    if self.is_predicted:
        return PredictedBoundingBox(score=self.score, **kwargs)
    return UserBoundingBox(**kwargs)

to_instance(skeleton=None)

Convert this centroid to a single-node Instance (deprecated).

.. deprecated:: Use :meth:to_pose instead.

Parameters:

Name Type Description Default
skeleton Skeleton | None

Skeleton to use for the instance. Must have exactly one node. Defaults to the shared CENTROID_SKELETON.

None

Returns:

Type Description
Instance | PredictedInstance

A PredictedInstance if this is a PredictedCentroid, otherwise an Instance.

Source code in sleap_io/model/centroid.py
def to_instance(
    self, skeleton: "Skeleton | None" = None
) -> "Instance | PredictedInstance":
    """Convert this centroid to a single-node ``Instance`` (deprecated).

    .. deprecated::
        Use :meth:`to_pose` instead.

    Args:
        skeleton: Skeleton to use for the instance. Must have exactly one
            node. Defaults to the shared ``CENTROID_SKELETON``.

    Returns:
        A ``PredictedInstance`` if this is a ``PredictedCentroid``,
        otherwise an ``Instance``.
    """
    import warnings

    warnings.warn(
        "Centroid.to_instance() is deprecated; use Centroid.to_pose() instead.",
        DeprecationWarning,
        stacklevel=2,
    )
    return self.to_pose(skeleton=skeleton)

to_mask(height, width, radius, error_on_empty=False)

Rasterize a circular ROI around this centroid into a mask.

Equivalent to self.to_roi(radius).to_mask(height, width). A PredictedCentroid produces a PredictedSegmentationMask carrying its score; any other centroid produces a UserSegmentationMask. Metadata is inherited.

Parameters:

Name Type Description Default
height int

Height of the output mask in pixels.

required
width int

Width of the output mask in pixels.

required
radius float

Radius of the circular region around the centroid. Required.

required
error_on_empty bool

If True, raise ValueError when this centroid is degenerate (NaN) instead of returning an all-background mask.

False

Returns:

Type Description
SegmentationMask

A SegmentationMask with the rasterized circular region (all background if the centroid is degenerate).

Raises:

Type Description
ValueError

If the centroid is degenerate and error_on_empty is True.

Source code in sleap_io/model/centroid.py
def to_mask(
    self,
    height: int,
    width: int,
    radius: float,
    error_on_empty: bool = False,
) -> "SegmentationMask":
    """Rasterize a circular ROI around this centroid into a mask.

    Equivalent to ``self.to_roi(radius).to_mask(height, width)``. A
    ``PredictedCentroid`` produces a ``PredictedSegmentationMask`` carrying
    its ``score``; any other centroid produces a ``UserSegmentationMask``.
    Metadata is inherited.

    Args:
        height: Height of the output mask in pixels.
        width: Width of the output mask in pixels.
        radius: Radius of the circular region around the centroid. Required.
        error_on_empty: If ``True``, raise ``ValueError`` when this centroid
            is degenerate (NaN) instead of returning an all-background mask.

    Returns:
        A ``SegmentationMask`` with the rasterized circular region (all
        background if the centroid is degenerate).

    Raises:
        ValueError: If the centroid is degenerate and ``error_on_empty`` is
            ``True``.
    """
    if self.is_empty:
        if error_on_empty:
            raise ValueError("Cannot compute mask of a degenerate (NaN) centroid.")
        from sleap_io.model.mask import (
            PredictedSegmentationMask,
            UserSegmentationMask,
        )

        empty = np.zeros((height, width), dtype=bool)
        kwargs = dict(
            track=self.track,
            tracking_score=self.tracking_score,
            identity=self.identity,
            identity_score=self.identity_score,
            identity_embedding=self.identity_embedding,
            instance=self.instance,
            category=self.category,
            category_score=self.category_score,
            category_embedding=self.category_embedding,
            name=self.name,
            source=self.source,
        )
        if self.is_predicted:
            return PredictedSegmentationMask.from_numpy(
                empty, score=self.score, **kwargs
            )
        return UserSegmentationMask.from_numpy(empty, **kwargs)

    return self.to_roi(radius).to_mask(height, width)

to_pose(skeleton=None)

Convert this centroid to a single-node Instance.

Parameters:

Name Type Description Default
skeleton Skeleton | None

Skeleton to use for the instance. Must have exactly one node. Defaults to the shared CENTROID_SKELETON.

None

Returns:

Type Description
Instance | PredictedInstance

A PredictedInstance if this is a PredictedCentroid, otherwise an Instance.

Raises:

Type Description
ValueError

If the skeleton has more than one node.

Source code in sleap_io/model/centroid.py
def to_pose(
    self, skeleton: "Skeleton | None" = None
) -> "Instance | PredictedInstance":
    """Convert this centroid to a single-node ``Instance``.

    Args:
        skeleton: Skeleton to use for the instance. Must have exactly one
            node. Defaults to the shared ``CENTROID_SKELETON``.

    Returns:
        A ``PredictedInstance`` if this is a ``PredictedCentroid``,
        otherwise an ``Instance``.

    Raises:
        ValueError: If the skeleton has more than one node.
    """
    from sleap_io.model.instance import Instance, PredictedInstance

    if skeleton is None:
        skeleton = get_centroid_skeleton()

    if len(skeleton) > 1:
        raise ValueError(
            f"Skeleton must have exactly 1 node for centroid conversion, "
            f"got {len(skeleton)}."
        )

    points = np.array([[self.x, self.y]])

    if isinstance(self, PredictedCentroid):
        return PredictedInstance.from_numpy(
            points_data=points,
            skeleton=skeleton,
            score=self.score,
            track=self.track,
            tracking_score=self.tracking_score,
            identity=self.identity,
            identity_score=self.identity_score,
            identity_embedding=self.identity_embedding,
            category=self.category,
            category_score=self.category_score,
            category_embedding=self.category_embedding,
        )
    else:
        return Instance.from_numpy(
            points_data=points,
            skeleton=skeleton,
            track=self.track,
            tracking_score=self.tracking_score,
            identity=self.identity,
            identity_score=self.identity_score,
            identity_embedding=self.identity_embedding,
            category=self.category,
            category_score=self.category_score,
            category_embedding=self.category_embedding,
        )

to_roi(radius, error_on_empty=False)

Construct a circular ROI centered on this centroid.

A PredictedCentroid produces a PredictedROI carrying its score; any other centroid produces a UserROI. Metadata (track, tracking_score, identity, identity_score, category, name, source, instance) is inherited.

Parameters:

Name Type Description Default
radius float

Radius of the circular ROI. Required.

required
error_on_empty bool

If True, raise ValueError when this centroid is degenerate (NaN) instead of returning an empty-geometry ROI.

False

Returns:

Type Description
ROI

A ROI with a buffered-point (circular) geometry, or an empty Polygon geometry if the centroid is degenerate.

Raises:

Type Description
ValueError

If the centroid is degenerate and error_on_empty is True.

Source code in sleap_io/model/centroid.py
def to_roi(self, radius: float, error_on_empty: bool = False) -> "ROI":
    """Construct a circular ROI centered on this centroid.

    A ``PredictedCentroid`` produces a ``PredictedROI`` carrying its
    ``score``; any other centroid produces a ``UserROI``. Metadata (track,
    tracking_score, identity, identity_score, category, name, source,
    instance) is inherited.

    Args:
        radius: Radius of the circular ROI. Required.
        error_on_empty: If ``True``, raise ``ValueError`` when this centroid
            is degenerate (NaN) instead of returning an empty-geometry ROI.

    Returns:
        A ``ROI`` with a buffered-point (circular) geometry, or an empty
        ``Polygon`` geometry if the centroid is degenerate.

    Raises:
        ValueError: If the centroid is degenerate and ``error_on_empty`` is
            ``True``.
    """
    from shapely.geometry import Point, Polygon

    from sleap_io.model.roi import PredictedROI, UserROI

    if self.is_empty:
        if error_on_empty:
            raise ValueError("Cannot compute ROI of a degenerate (NaN) centroid.")
        geom = Polygon()
    else:
        geom = Point(self.x, self.y).buffer(radius)

    kwargs = dict(
        geometry=geom,
        track=self.track,
        tracking_score=self.tracking_score,
        identity=self.identity,
        identity_score=self.identity_score,
        identity_embedding=self.identity_embedding,
        instance=self.instance,
        category=self.category,
        category_score=self.category_score,
        category_embedding=self.category_embedding,
        name=self.name,
        source=self.source,
    )
    if self.is_predicted:
        return PredictedROI(score=self.score, **kwargs)
    return UserROI(**kwargs)

sleap_io.UserCentroid

Bases: sleap_io.model.centroid.Centroid

A human-annotated or derived centroid.

Inherits all fields from Centroid. Has no additional fields.

See Centroid for attribute documentation.

Methods:

Name Description
__init__

Method generated by attrs for class UserCentroid.

__repr__

Method generated by attrs for class UserCentroid.

__setattr__

Method generated by attrs for class UserCentroid.

Attributes:

Name Type Description
__annotations__

dict() -> new empty dictionary

__attrs_own_setattr__

Returns True when the argument is true, False otherwise.

__attrs_props__

Effective class properties as derived from parameters to attr.s() or

__doc__

str(object='') -> str

__firstlineno__

int([x]) -> integer

__match_args__

Built-in immutable sequence.

__module__

str(object='') -> str

__slots__

Built-in immutable sequence.

__static_attributes__

Built-in immutable sequence.

Source code in sleap_io/model/centroid.py
@attrs.define(eq=False)
class UserCentroid(Centroid):
    """A human-annotated or derived centroid.

    Inherits all fields from ``Centroid``. Has no additional fields.

    See ``Centroid`` for attribute documentation.
    """

    pass

__annotations__ = {} class-attribute

dict() -> new empty dictionary dict(mapping) -> new dictionary initialized from a mapping object's (key, value) pairs dict(iterable) -> new dictionary initialized as if via: d = {} for k, v in iterable: d[k] = v dict(**kwargs) -> new dictionary initialized with the name=value pairs in the keyword argument list. For example: dict(one=1, two=2)

__attrs_own_setattr__ = True class-attribute

Returns True when the argument is true, False otherwise. The builtins True and False are the only two instances of the class bool. The class bool is a subclass of the class int, and cannot be subclassed.

__attrs_props__ = ClassProps(is_exception=False, is_slotted=True, has_weakref_slot=True, is_frozen=False, kw_only=<KeywordOnly.NO: 'no'>, collected_fields_by_mro=True, added_init=True, added_repr=True, added_eq=False, added_ordering=False, hashability=<Hashability.LEAVE_ALONE: 'leave_alone'>, added_match_args=True, added_str=False, added_pickling=True, on_setattr_hook=<function pipe.<locals>.wrapped_pipe at 0x7f08471ce840>, field_transformer=None) class-attribute

Effective class properties as derived from parameters to attr.s() or define() decorators.

This is the same data structure that attrs uses internally to decide how to construct the final class.

Warning:

This feature is currently **experimental** and is not covered by our
strict backwards-compatibility guarantees.

Attributes:

Name Type Description
is_exception bool

Whether the class is treated as an exception class.

is_slotted bool

Whether the class is slotted <slotted classes>.

has_weakref_slot bool

Whether the class has a slot for weak references.

is_frozen bool

Whether the class is frozen.

kw_only KeywordOnly

Whether / how the class enforces keyword-only arguments on the __init__ method.

collected_fields_by_mro bool

Whether the class fields were collected by method resolution order. That is, correctly but unlike dataclasses.

added_init bool

Whether the class has an attrs-generated __init__ method.

added_repr bool

Whether the class has an attrs-generated __repr__ method.

added_eq bool

Whether the class has attrs-generated equality methods.

added_ordering bool

Whether the class has attrs-generated ordering methods.

hashability Hashability

How hashable <hashing> the class is.

added_match_args bool

Whether the class supports positional match <match> over its fields.

added_str bool

Whether the class has an attrs-generated __str__ method.

added_pickling bool

Whether the class has attrs-generated __getstate__ and __setstate__ methods for pickle.

on_setattr_hook Callable[[Any, Attribute[Any], Any], Any] | None

The class's __setattr__ hook.

field_transformer Callable[[Attribute[Any]], Attribute[Any]] | None

The class's field transformers <transform-fields>.

.. versionadded:: 25.4.0

__doc__ = 'A human-annotated or derived centroid.\n\nInherits all fields from ``Centroid``. Has no additional fields.\n\nSee ``Centroid`` for attribute documentation.\n' class-attribute

str(object='') -> str str(bytes_or_buffer[, encoding[, errors]]) -> str

Create a new string object from the given object. If encoding or errors is specified, then the object must expose a data buffer that will be decoded using the given encoding and error handler. Otherwise, returns the result of object.str() (if defined) or repr(object). encoding defaults to 'utf-8'. errors defaults to 'strict'.

__firstlineno__ = 638 class-attribute

int([x]) -> integer int(x, base=10) -> integer

Convert a number or string to an integer, or return 0 if no arguments are given. If x is a number, return x.int(). For floating-point numbers, this truncates towards zero.

If x is not a number or if base is given, then x must be a string, bytes, or bytearray instance representing an integer literal in the given base. The literal can be preceded by '+' or '-' and be surrounded by whitespace. The base defaults to 10. Valid bases are 0 and 2-36. Base 0 means to interpret the base from the string as an integer iteral.

int('0b100', base=0) 4

__match_args__ = ('x', 'y', 'z', 'track', 'tracking_score', 'identity', 'identity_score', 'instance', 'category', 'name', 'source', 'identity_embedding', 'category_score', 'category_embedding') class-attribute

Built-in immutable sequence.

If no argument is given, the constructor returns an empty tuple. If iterable is specified the tuple is initialized from iterable's items.

If the argument is a tuple, the return value is the same object.

__module__ = 'sleap_io.model.centroid' class-attribute

str(object='') -> str str(bytes_or_buffer[, encoding[, errors]]) -> str

Create a new string object from the given object. If encoding or errors is specified, then the object must expose a data buffer that will be decoded using the given encoding and error handler. Otherwise, returns the result of object.str() (if defined) or repr(object). encoding defaults to 'utf-8'. errors defaults to 'strict'.

__slots__ = () class-attribute

Built-in immutable sequence.

If no argument is given, the constructor returns an empty tuple. If iterable is specified the tuple is initialized from iterable's items.

If the argument is a tuple, the return value is the same object.

__static_attributes__ = () class-attribute

Built-in immutable sequence.

If no argument is given, the constructor returns an empty tuple. If iterable is specified the tuple is initialized from iterable's items.

If the argument is a tuple, the return value is the same object.

__init__(x, y, z=None, track=None, tracking_score=None, identity=None, identity_score=None, instance=None, category=None, name='', source='', identity_embedding=None, category_score=None, category_embedding=None)

Method generated by attrs for class UserCentroid.

Source code in sleap_io/model/centroid.py
from __future__ import annotations

from typing import TYPE_CHECKING

import attrs
import numpy as np

from sleap_io.model.category import to_category

if TYPE_CHECKING:
    from sleap_io.model.bbox import BoundingBox
    from sleap_io.model.category import Category
    from sleap_io.model.embedding import Embedding
    from sleap_io.model.identity import Identity
    from sleap_io.model.instance import Instance, PredictedInstance, Track
    from sleap_io.model.mask import SegmentationMask
    from sleap_io.model.roi import ROI
    from sleap_io.model.skeleton import Skeleton

__repr__()

Method generated by attrs for class UserCentroid.

Source code in sleap_io/model/centroid.py
"""Data structures for centroid annotations.

Centroids are lightweight point annotations representing the center of an object.
They support user/predicted distinction and interconversion with single-node
``Instance`` objects.

The class hierarchy:
    - ``Centroid`` — abstract base with coordinates, video/frame/track/instance metadata
    - ``UserCentroid`` — human-annotated or derived centroid
    - ``PredictedCentroid`` — model-predicted centroid with confidence score

A module-level ``CENTROID_SKELETON`` is provided for creating single-node
``Instance`` objects from centroids.
"""

__setattr__(name, val)

Method generated by attrs for class UserCentroid.

sleap_io.PredictedCentroid

Bases: sleap_io.model.centroid.Centroid

A model-predicted centroid with a confidence score.

Attributes:

Name Type Description
score

Detection confidence score (0-1).

See Centroid for other attribute documentation.

Methods:

Name Description
__init__

Method generated by attrs for class PredictedCentroid.

__repr__

Method generated by attrs for class PredictedCentroid.

__setattr__

Method generated by attrs for class PredictedCentroid.

Source code in sleap_io/model/centroid.py
@attrs.define(eq=False)
class PredictedCentroid(Centroid):
    """A model-predicted centroid with a confidence score.

    Attributes:
        score: Detection confidence score (0-1).

    See ``Centroid`` for other attribute documentation.
    """

    score: float = attrs.field(default=0.0)

__annotations__ = {'score': 'float'} class-attribute

dict() -> new empty dictionary dict(mapping) -> new dictionary initialized from a mapping object's (key, value) pairs dict(iterable) -> new dictionary initialized as if via: d = {} for k, v in iterable: d[k] = v dict(**kwargs) -> new dictionary initialized with the name=value pairs in the keyword argument list. For example: dict(one=1, two=2)

__attrs_own_setattr__ = True class-attribute

Returns True when the argument is true, False otherwise. The builtins True and False are the only two instances of the class bool. The class bool is a subclass of the class int, and cannot be subclassed.

__attrs_props__ = ClassProps(is_exception=False, is_slotted=True, has_weakref_slot=True, is_frozen=False, kw_only=<KeywordOnly.NO: 'no'>, collected_fields_by_mro=True, added_init=True, added_repr=True, added_eq=False, added_ordering=False, hashability=<Hashability.LEAVE_ALONE: 'leave_alone'>, added_match_args=True, added_str=False, added_pickling=True, on_setattr_hook=<function pipe.<locals>.wrapped_pipe at 0x7f08471ce840>, field_transformer=None) class-attribute

Effective class properties as derived from parameters to attr.s() or define() decorators.

This is the same data structure that attrs uses internally to decide how to construct the final class.

Warning:

This feature is currently **experimental** and is not covered by our
strict backwards-compatibility guarantees.

Attributes:

Name Type Description
is_exception bool

Whether the class is treated as an exception class.

is_slotted bool

Whether the class is slotted <slotted classes>.

has_weakref_slot bool

Whether the class has a slot for weak references.

is_frozen bool

Whether the class is frozen.

kw_only KeywordOnly

Whether / how the class enforces keyword-only arguments on the __init__ method.

collected_fields_by_mro bool

Whether the class fields were collected by method resolution order. That is, correctly but unlike dataclasses.

added_init bool

Whether the class has an attrs-generated __init__ method.

added_repr bool

Whether the class has an attrs-generated __repr__ method.

added_eq bool

Whether the class has attrs-generated equality methods.

added_ordering bool

Whether the class has attrs-generated ordering methods.

hashability Hashability

How hashable <hashing> the class is.

added_match_args bool

Whether the class supports positional match <match> over its fields.

added_str bool

Whether the class has an attrs-generated __str__ method.

added_pickling bool

Whether the class has attrs-generated __getstate__ and __setstate__ methods for pickle.

on_setattr_hook Callable[[Any, Attribute[Any], Any], Any] | None

The class's __setattr__ hook.

field_transformer Callable[[Attribute[Any]], Attribute[Any]] | None

The class's field transformers <transform-fields>.

.. versionadded:: 25.4.0

__doc__ = 'A model-predicted centroid with a confidence score.\n\nAttributes:\n score: Detection confidence score (0-1).\n\nSee ``Centroid`` for other attribute documentation.\n' class-attribute

str(object='') -> str str(bytes_or_buffer[, encoding[, errors]]) -> str

Create a new string object from the given object. If encoding or errors is specified, then the object must expose a data buffer that will be decoded using the given encoding and error handler. Otherwise, returns the result of object.str() (if defined) or repr(object). encoding defaults to 'utf-8'. errors defaults to 'strict'.

__firstlineno__ = 650 class-attribute

int([x]) -> integer int(x, base=10) -> integer

Convert a number or string to an integer, or return 0 if no arguments are given. If x is a number, return x.int(). For floating-point numbers, this truncates towards zero.

If x is not a number or if base is given, then x must be a string, bytes, or bytearray instance representing an integer literal in the given base. The literal can be preceded by '+' or '-' and be surrounded by whitespace. The base defaults to 10. Valid bases are 0 and 2-36. Base 0 means to interpret the base from the string as an integer iteral.

int('0b100', base=0) 4

__match_args__ = ('x', 'y', 'z', 'track', 'tracking_score', 'identity', 'identity_score', 'instance', 'category', 'name', 'source', 'identity_embedding', 'category_score', 'category_embedding', 'score') class-attribute

Built-in immutable sequence.

If no argument is given, the constructor returns an empty tuple. If iterable is specified the tuple is initialized from iterable's items.

If the argument is a tuple, the return value is the same object.

__module__ = 'sleap_io.model.centroid' class-attribute

str(object='') -> str str(bytes_or_buffer[, encoding[, errors]]) -> str

Create a new string object from the given object. If encoding or errors is specified, then the object must expose a data buffer that will be decoded using the given encoding and error handler. Otherwise, returns the result of object.str() (if defined) or repr(object). encoding defaults to 'utf-8'. errors defaults to 'strict'.

__slots__ = ('score',) class-attribute

Built-in immutable sequence.

If no argument is given, the constructor returns an empty tuple. If iterable is specified the tuple is initialized from iterable's items.

If the argument is a tuple, the return value is the same object.

__static_attributes__ = () class-attribute

Built-in immutable sequence.

If no argument is given, the constructor returns an empty tuple. If iterable is specified the tuple is initialized from iterable's items.

If the argument is a tuple, the return value is the same object.

__init__(x, y, z=None, track=None, tracking_score=None, identity=None, identity_score=None, instance=None, category=None, name='', source='', identity_embedding=None, category_score=None, category_embedding=None, score=0.0)

Method generated by attrs for class PredictedCentroid.

Source code in sleap_io/model/centroid.py
from __future__ import annotations

from typing import TYPE_CHECKING

import attrs
import numpy as np

from sleap_io.model.category import to_category

if TYPE_CHECKING:
    from sleap_io.model.bbox import BoundingBox
    from sleap_io.model.category import Category
    from sleap_io.model.embedding import Embedding
    from sleap_io.model.identity import Identity
    from sleap_io.model.instance import Instance, PredictedInstance, Track
    from sleap_io.model.mask import SegmentationMask
    from sleap_io.model.roi import ROI
    from sleap_io.model.skeleton import Skeleton

__repr__()

Method generated by attrs for class PredictedCentroid.

Source code in sleap_io/model/centroid.py
"""Data structures for centroid annotations.

Centroids are lightweight point annotations representing the center of an object.
They support user/predicted distinction and interconversion with single-node
``Instance`` objects.

The class hierarchy:
    - ``Centroid`` — abstract base with coordinates, video/frame/track/instance metadata
    - ``UserCentroid`` — human-annotated or derived centroid
    - ``PredictedCentroid`` — model-predicted centroid with confidence score

A module-level ``CENTROID_SKELETON`` is provided for creating single-node
``Instance`` objects from centroids.
"""

__setattr__(name, val)

Method generated by attrs for class PredictedCentroid.