Skip to content

instance

sleap_io.model.instance

Data structures for data associated with a single instance such as an animal.

The Instance class is a SLEAP data structure that contains a collection of points that correspond to landmarks within a Skeleton.

PredictedInstance additionally contains metadata associated with how the instance was estimated, such as confidence scores.

Classes:

Name Description
Category

Ground-truth class membership of a detection (e.g. species, sex, condition).

Embedding

A per-detection appearance / re-identification embedding vector.

Identity

Ground-truth animal identity, persistent across sessions and videos.

Instance

This class represents a ground truth instance such as an animal.

Instance3D

A 3D pose instance with keypoints in world coordinates.

Node

A landmark type within a Skeleton.

PointsArray

A specialized array for storing instance points data.

PredictedInstance

A PredictedInstance is an Instance that was predicted using a model.

PredictedInstance3D

A predicted 3D pose instance with per-keypoint confidence scores.

PredictedPointsArray

A specialized array for storing predicted instance points data with scores.

Skeleton

A description of a set of landmark types and connections between them.

Track

An object that represents the same animal/object across multiple detections.

Functions:

Name Description
to_category

Coerce a category-like value to a Category (or None).

Attributes:

Name Type Description
TYPE_CHECKING

Returns True when the argument is true, False otherwise.

__cached__

str(object='') -> str

__doc__

str(object='') -> str

__file__

str(object='') -> str

__name__

str(object='') -> str

__package__

str(object='') -> str

TYPE_CHECKING = False module-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.

__cached__ = '/home/runner/work/sleap-io/sleap-io/sleap_io/model/__pycache__/instance.cpython-313.pyc' module-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'.

__doc__ = 'Data structures for data associated with a single instance such as an animal.\n\nThe `Instance` class is a SLEAP data structure that contains a collection of points that\ncorrespond to landmarks within a `Skeleton`.\n\n`PredictedInstance` additionally contains metadata associated with how the instance was\nestimated, such as confidence scores.\n' module-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'.

__file__ = '/home/runner/work/sleap-io/sleap-io/sleap_io/model/instance.py' module-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'.

__name__ = 'sleap_io.model.instance' module-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'.

__package__ = 'sleap_io.model' module-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'.

Category

Ground-truth class membership of a detection (e.g. species, sex, condition).

Where Track is an ephemeral temporal trajectory within a single video and Identity names a specific individual across videos, Category names the class an individual belongs to -- a group of individuals that share some attribute, typically assigned by classification or retrieved via re-ID (e.g. "female_fly", "fur_shaved", "mouse"). The per-detection binding is stored on Instance.category (and the analogous slot on the other detection modalities), alongside an optional category_score (assignment confidence) and category_embedding (the appearance vector it was classified from).

Attributes:

Name Type Description
name

Human-readable name for this category (e.g., "female_fly"). Not required to be unique, but name is how categories are matched across separately-loaded files and merges.

metadata

Arbitrary string-keyed, string-valued metadata (e.g. {"color": "#e6194b", "supercategory": "insect"}). Empty by default.

Notes

Category objects use object-identity equality (eq=False), matching Track and Identity. Use matches() (default method="name") to compare categories across files, where Python object identity is not meaningful.

Methods:

Name Description
__init__

Method generated by attrs for class Category.

__repr__

Return a readable string representation.

__setattr__

Method generated by attrs for class Category.

matches

Check if this category matches another category.

Source code in sleap_io/model/category.py
@define(eq=False)
class Category:
    """Ground-truth class membership of a detection (e.g. species, sex, condition).

    Where `Track` is an ephemeral temporal trajectory within a single video and
    `Identity` names a specific individual across videos, `Category` names the
    *class* an individual belongs to -- a group of individuals that share some
    attribute, typically assigned by classification or retrieved via re-ID (e.g.
    ``"female_fly"``, ``"fur_shaved"``, ``"mouse"``). The per-detection binding is
    stored on ``Instance.category`` (and the analogous slot on the other detection
    modalities), alongside an optional ``category_score`` (assignment confidence)
    and ``category_embedding`` (the appearance vector it was classified from).

    Attributes:
        name: Human-readable name for this category (e.g., ``"female_fly"``). Not
            required to be unique, but ``name`` is how categories are matched
            across separately-loaded files and merges.
        metadata: Arbitrary string-keyed, string-valued metadata (e.g.
            ``{"color": "#e6194b", "supercategory": "insect"}``). Empty by default.

    Notes:
        `Category` objects use object-identity equality (``eq=False``), matching
        `Track` and `Identity`. Use `matches()` (default ``method="name"``) to
        compare categories across files, where Python object identity is not
        meaningful.
    """

    name: str = field(default="", validator=instance_of(str))
    metadata: dict[str, str] = field(factory=dict, validator=instance_of(dict))

    def matches(self, other: "Category", method: str = "name") -> bool:
        """Check if this category matches another category.

        Args:
            other: Another category to compare with.
            method: Matching method:

                - ``"name"`` (default): match by the `name` attribute, which
                  survives serialization and cross-file merges.
                - ``"identity"``: match by Python object identity (same object).

        Returns:
            True if the categories match according to the specified method.

        Raises:
            ValueError: If `method` is not one of the supported values.
        """
        if method == "name":
            return self.name == other.name
        elif method == "identity":
            return self is other
        else:
            raise ValueError(f"Unknown matching method: {method}")

    def __repr__(self) -> str:
        """Return a readable string representation."""
        return f'Category(name="{self.name}")'

__annotations__ = {'name': 'str', 'metadata': 'dict[str, str]'} 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=False, 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__ = 'Ground-truth class membership of a detection (e.g. species, sex, condition).\n\nWhere `Track` is an ephemeral temporal trajectory within a single video and\n`Identity` names a specific individual across videos, `Category` names the\n*class* an individual belongs to -- a group of individuals that share some\nattribute, typically assigned by classification or retrieved via re-ID (e.g.\n``"female_fly"``, ``"fur_shaved"``, ``"mouse"``). The per-detection binding is\nstored on ``Instance.category`` (and the analogous slot on the other detection\nmodalities), alongside an optional ``category_score`` (assignment confidence)\nand ``category_embedding`` (the appearance vector it was classified from).\n\nAttributes:\n name: Human-readable name for this category (e.g., ``"female_fly"``). Not\n required to be unique, but ``name`` is how categories are matched\n across separately-loaded files and merges.\n metadata: Arbitrary string-keyed, string-valued metadata (e.g.\n ``{"color": "#e6194b", "supercategory": "insect"}``). Empty by default.\n\nNotes:\n `Category` objects use object-identity equality (``eq=False``), matching\n `Track` and `Identity`. Use `matches()` (default ``method="name"``) to\n compare categories across files, where Python object identity is not\n meaningful.\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__ = 9 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__ = ('name', 'metadata') 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.category' 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__ = ('name', 'metadata', '__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

__init__(name='', metadata=NOTHING)

Method generated by attrs for class Category.

Source code in sleap_io/model/category.py
"""Category data structure for ground-truth class membership of detections."""

from __future__ import annotations

from attrs import define, field
from attrs.validators import instance_of


@define(eq=False)
class Category:

__repr__()

Return a readable string representation.

Source code in sleap_io/model/category.py
def __repr__(self) -> str:
    """Return a readable string representation."""
    return f'Category(name="{self.name}")'

__setattr__(name, val)

Method generated by attrs for class Category.

matches(other, method='name')

Check if this category matches another category.

Parameters:

Name Type Description Default
other Category

Another category to compare with.

required
method str

Matching method:

  • "name" (default): match by the name attribute, which survives serialization and cross-file merges.
  • "identity": match by Python object identity (same object).
'name'

Returns:

Type Description
bool

True if the categories match according to the specified method.

Raises:

Type Description
ValueError

If method is not one of the supported values.

Source code in sleap_io/model/category.py
def matches(self, other: "Category", method: str = "name") -> bool:
    """Check if this category matches another category.

    Args:
        other: Another category to compare with.
        method: Matching method:

            - ``"name"`` (default): match by the `name` attribute, which
              survives serialization and cross-file merges.
            - ``"identity"``: match by Python object identity (same object).

    Returns:
        True if the categories match according to the specified method.

    Raises:
        ValueError: If `method` is not one of the supported values.
    """
    if method == "name":
        return self.name == other.name
    elif method == "identity":
        return self is other
    else:
        raise ValueError(f"Unknown matching method: {method}")

Embedding

A per-detection appearance / re-identification embedding vector.

An Embedding wraps a single feature vector describing the visual appearance of one detection (e.g. the crop around an Instance, Centroid, SegmentationMask, or BoundingBox). What the vector represents is implied by the slot it fills on the detection -- currently identity_embedding on every detection modality, used for re-identification.

Attributes:

Name Type Description
vector

1-D feature vector of shape (D,). The dtype is preserved from the input when floating (e.g. float32 from a neural network); non-floating inputs are cast to float32.

Notes

Embedding uses value equality: two embeddings are equal when their vectors are element-wise equal. It is therefore unhashable (an Embedding is only ever a field value, never a set member or dict key). Detection objects use object-identity equality, so a (potentially large) embedding vector is never compared when two detections are compared.

Methods:

Name Description
__eq__

Method generated by attrs for class Embedding.

__init__

Method generated by attrs for class Embedding.

__repr__

Method generated by attrs for class Embedding.

__setattr__

Method generated by attrs for class Embedding.

Source code in sleap_io/model/embedding.py
@define
class Embedding:
    """A per-detection appearance / re-identification embedding vector.

    An `Embedding` wraps a single feature vector describing the visual appearance
    of one detection (e.g. the crop around an `Instance`, `Centroid`,
    `SegmentationMask`, or `BoundingBox`). What the vector represents is implied by
    the slot it fills on the detection -- currently ``identity_embedding`` on every
    detection modality, used for re-identification.

    Attributes:
        vector: 1-D feature vector of shape ``(D,)``. The dtype is preserved from
            the input when floating (e.g. float32 from a neural network);
            non-floating inputs are cast to float32.

    Notes:
        `Embedding` uses value equality: two embeddings are equal when their
        vectors are element-wise equal. It is therefore unhashable (an `Embedding`
        is only ever a field value, never a set member or dict key). Detection
        objects use object-identity equality, so a (potentially large) embedding
        vector is never compared when two detections are compared.
    """

    vector: np.ndarray = field(
        converter=_as_vector,
        eq=attrs.cmp_using(eq=np.array_equal),
        repr=lambda v: f"<{v.shape[0]}-d {v.dtype}>",
    )

    @property
    def dim(self) -> int:
        """Dimensionality ``D`` of the embedding vector."""
        return int(self.vector.shape[0])

__annotations__ = {'vector': 'np.ndarray'} 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=True, added_ordering=False, hashability=<Hashability.UNHASHABLE: 'unhashable'>, 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 per-detection appearance / re-identification embedding vector.\n\nAn `Embedding` wraps a single feature vector describing the visual appearance\nof one detection (e.g. the crop around an `Instance`, `Centroid`,\n`SegmentationMask`, or `BoundingBox`). What the vector represents is implied by\nthe slot it fills on the detection -- currently ``identity_embedding`` on every\ndetection modality, used for re-identification.\n\nAttributes:\n vector: 1-D feature vector of shape ``(D,)``. The dtype is preserved from\n the input when floating (e.g. float32 from a neural network);\n non-floating inputs are cast to float32.\n\nNotes:\n `Embedding` uses value equality: two embeddings are equal when their\n vectors are element-wise equal. It is therefore unhashable (an `Embedding`\n is only ever a field value, never a set member or dict key). Detection\n objects use object-identity equality, so a (potentially large) embedding\n vector is never compared when two detections are compared.\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__ = 26 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__ = ('vector',) 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.embedding' 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__ = ('vector', '__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

dim property

Dimensionality D of the embedding vector.

__eq__(other)

Method generated by attrs for class Embedding.

Source code in sleap_io/model/embedding.py
arr = np.asarray(value)
if not np.issubdtype(arr.dtype, np.floating):
    arr = arr.astype(np.float32)
if arr.ndim != 1:
    raise ValueError(
        f"Embedding vector must be 1-dimensional, got shape {arr.shape}."

__init__(vector)

Method generated by attrs for class Embedding.

Source code in sleap_io/model/embedding.py
    )
return arr

__repr__()

Method generated by attrs for class Embedding.

Source code in sleap_io/model/embedding.py
"""Embedding data structure for per-detection appearance / re-ID vectors."""

from __future__ import annotations

import attrs
import numpy as np
from attrs import define, field


def _as_vector(value) -> np.ndarray:
    """Coerce an input to a 1-D embedding vector, preserving floating dtype.

    Floating inputs (e.g. float32 from a neural network) keep their dtype;
    non-floating inputs are cast to float32.
    """

__setattr__(name, val)

Method generated by attrs for class Embedding.

Identity

Ground-truth animal identity, persistent across sessions and videos.

Unlike Track (an ephemeral temporal trajectory within a single video), Identity represents a known animal that can be recognized across videos, sessions, and experiments. In multi-view setups, multiple per-camera Tracks may map to a single Identity. The per-detection binding is stored on Instance.identity (and the analogous slot on the other detection modalities); the triangulated multi-view binding is stored on InstanceGroup.identity.

Attributes:

Name Type Description
name

Human-readable name for this identity (e.g., "mouse_A"). Not required to be unique, but name is how identities are matched across separately-loaded files and merges.

metadata

Arbitrary string-keyed, string-valued metadata (e.g. {"color": "#e6194b", "strain": "C57BL/6"}). Empty by default.

Notes

Identity objects use object-identity equality (eq=False), matching Track. Use matches() (default method="name") to compare identities across files, where Python object identity is not meaningful.

Methods:

Name Description
__init__

Method generated by attrs for class Identity.

__repr__

Return a readable string representation.

__setattr__

Method generated by attrs for class Identity.

matches

Check if this identity matches another identity.

Source code in sleap_io/model/identity.py
@define(eq=False)
class Identity:
    """Ground-truth animal identity, persistent across sessions and videos.

    Unlike `Track` (an ephemeral temporal trajectory within a single video),
    `Identity` represents a known animal that can be recognized across videos,
    sessions, and experiments. In multi-view setups, multiple per-camera `Track`s
    may map to a single `Identity`. The per-detection binding is stored on
    ``Instance.identity`` (and the analogous slot on the other detection
    modalities); the triangulated multi-view binding is stored on
    ``InstanceGroup.identity``.

    Attributes:
        name: Human-readable name for this identity (e.g., ``"mouse_A"``). Not
            required to be unique, but ``name`` is how identities are matched
            across separately-loaded files and merges.
        metadata: Arbitrary string-keyed, string-valued metadata (e.g.
            ``{"color": "#e6194b", "strain": "C57BL/6"}``). Empty by default.

    Notes:
        `Identity` objects use object-identity equality (``eq=False``), matching
        `Track`. Use `matches()` (default ``method="name"``) to compare identities
        across files, where Python object identity is not meaningful.
    """

    name: str = field(default="", validator=instance_of(str))
    metadata: dict[str, str] = field(factory=dict, validator=instance_of(dict))

    def matches(self, other: "Identity", method: str = "name") -> bool:
        """Check if this identity matches another identity.

        Args:
            other: Another identity to compare with.
            method: Matching method:

                - ``"name"`` (default): match by the `name` attribute, which
                  survives serialization and cross-file merges.
                - ``"identity"``: match by Python object identity (same object).

        Returns:
            True if the identities match according to the specified method.

        Raises:
            ValueError: If `method` is not one of the supported values.
        """
        if method == "name":
            return self.name == other.name
        elif method == "identity":
            return self is other
        else:
            raise ValueError(f"Unknown matching method: {method}")

    def __repr__(self) -> str:
        """Return a readable string representation."""
        return f'Identity(name="{self.name}")'

__annotations__ = {'name': 'str', 'metadata': 'dict[str, str]'} 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=False, 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__ = 'Ground-truth animal identity, persistent across sessions and videos.\n\nUnlike `Track` (an ephemeral temporal trajectory within a single video),\n`Identity` represents a known animal that can be recognized across videos,\nsessions, and experiments. In multi-view setups, multiple per-camera `Track`s\nmay map to a single `Identity`. The per-detection binding is stored on\n``Instance.identity`` (and the analogous slot on the other detection\nmodalities); the triangulated multi-view binding is stored on\n``InstanceGroup.identity``.\n\nAttributes:\n name: Human-readable name for this identity (e.g., ``"mouse_A"``). Not\n required to be unique, but ``name`` is how identities are matched\n across separately-loaded files and merges.\n metadata: Arbitrary string-keyed, string-valued metadata (e.g.\n ``{"color": "#e6194b", "strain": "C57BL/6"}``). Empty by default.\n\nNotes:\n `Identity` objects use object-identity equality (``eq=False``), matching\n `Track`. Use `matches()` (default ``method="name"``) to compare identities\n across files, where Python object identity is not meaningful.\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__ = 9 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__ = ('name', 'metadata') 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.identity' 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__ = ('name', 'metadata', '__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

__init__(name='', metadata=NOTHING)

Method generated by attrs for class Identity.

Source code in sleap_io/model/identity.py
"""Identity data structure for ground-truth animal identification."""

from __future__ import annotations

from attrs import define, field
from attrs.validators import instance_of


@define(eq=False)
class Identity:

__repr__()

Return a readable string representation.

Source code in sleap_io/model/identity.py
def __repr__(self) -> str:
    """Return a readable string representation."""
    return f'Identity(name="{self.name}")'

__setattr__(name, val)

Method generated by attrs for class Identity.

matches(other, method='name')

Check if this identity matches another identity.

Parameters:

Name Type Description Default
other Identity

Another identity to compare with.

required
method str

Matching method:

  • "name" (default): match by the name attribute, which survives serialization and cross-file merges.
  • "identity": match by Python object identity (same object).
'name'

Returns:

Type Description
bool

True if the identities match according to the specified method.

Raises:

Type Description
ValueError

If method is not one of the supported values.

Source code in sleap_io/model/identity.py
def matches(self, other: "Identity", method: str = "name") -> bool:
    """Check if this identity matches another identity.

    Args:
        other: Another identity to compare with.
        method: Matching method:

            - ``"name"`` (default): match by the `name` attribute, which
              survives serialization and cross-file merges.
            - ``"identity"``: match by Python object identity (same object).

    Returns:
        True if the identities match according to the specified method.

    Raises:
        ValueError: If `method` is not one of the supported values.
    """
    if method == "name":
        return self.name == other.name
    elif method == "identity":
        return self is other
    else:
        raise ValueError(f"Unknown matching method: {method}")

Instance

This class represents a ground truth instance such as an animal.

An Instance has a set of landmarks (points) that correspond to a Skeleton. Each point is associated with a Node in the skeleton. The points are stored in a structured numpy array with columns for x, y, visible, complete and name.

The Instance may also be associated with a Track which links multiple instances together across frames or videos.

Attributes:

Name Type Description
points

A numpy structured array with columns for xy, visible and complete. The array should have shape (n_nodes,). This representation is useful for performance efficiency when working with large datasets.

skeleton

The Skeleton that describes the Nodes and Edges associated with this instance.

track

An optional Track associated with a unique animal/object across frames or videos.

tracking_score

The score associated with the Track assignment. This is typically the value from the score matrix used in an identity assignment. This is None if the instance is not associated with a track or if the track was assigned manually.

identity

An optional Identity representing the global, ground-truth animal this instance belongs to (persistent across videos/sessions). Unlike track (an ephemeral, video-local tracklet), Identity is the cross-file re-identification key. None if no global identity is assigned.

identity_score

The score associated with the identity assignment (e.g. the cosine similarity to a re-ID gallery prototype). This is None if the instance has no identity or the identity was assigned manually. Kept separate from tracking_score (short-term tracklet vs long-term identity).

from_predicted

The PredictedInstance (if any) that this instance was initialized from. This is used with human-in-the-loop workflows.

identity_embedding

An optional Embedding describing this instance's appearance for re-identification (e.g. a vector produced by a re-ID model). None by default.

category

An optional Category representing the class this instance belongs to (e.g. "female_fly", "fur_shaved"), typically assigned by classification or re-ID. Mirrors identity but groups by class rather than individual. None if no category is assigned.

category_score

The score associated with the category assignment (e.g. the classifier confidence). None if the instance has no category or the category was assigned manually.

category_embedding

An optional Embedding describing this instance's appearance for classification (the vector the category was classified from). None by default.

Methods:

Name Description
__attrs_post_init__

Convert the points array after initialization.

__getitem__

Return the point associated with a node.

__init__

Method generated by attrs for class Instance.

__len__

Return the number of points in the instance.

__repr__

Return a readable representation of the instance.

__setattr__

Method generated by attrs for class Instance.

__setitem__

Set the point associated with a node.

bounding_box

Get the bounding box of visible points.

empty

Create an empty instance with no points.

from_numpy

Create an instance object from a numpy array.

numpy

Return the instance points as a (n_nodes, 2) numpy array.

overlaps_with

Check if this instance overlaps with another based on bounding box IoU.

replace_skeleton

Replace the skeleton associated with the instance.

same_identity_as

Check if this instance has the same identity as another instance.

same_pose_as

Check if this instance has the same pose as another instance.

to_bbox

Create a bounding box from this instance.

to_centroid

Create a Centroid from this instance.

to_mask

Rasterize this instance's ROI geometry into a segmentation mask.

to_roi

Create a region-of-interest geometry from this instance.

update_skeleton

Update or replace the skeleton associated with the instance.

Source code in sleap_io/model/instance.py
@attrs.define(auto_attribs=True, slots=True, eq=False)
class Instance:
    """This class represents a ground truth instance such as an animal.

    An `Instance` has a set of landmarks (points) that correspond to a `Skeleton`. Each
    point is associated with a `Node` in the skeleton. The points are stored in a
    structured numpy array with columns for x, y, visible, complete and name.

    The `Instance` may also be associated with a `Track` which links multiple instances
    together across frames or videos.

    Attributes:
        points: A numpy structured array with columns for xy, visible and complete. The
            array should have shape `(n_nodes,)`. This representation is useful for
            performance efficiency when working with large datasets.
        skeleton: The `Skeleton` that describes the `Node`s and `Edge`s associated with
            this instance.
        track: An optional `Track` associated with a unique animal/object across frames
            or videos.
        tracking_score: The score associated with the `Track` assignment. This is
            typically the value from the score matrix used in an identity assignment.
            This is `None` if the instance is not associated with a track or if the
            track was assigned manually.
        identity: An optional `Identity` representing the global, ground-truth animal
            this instance belongs to (persistent across videos/sessions). Unlike
            `track` (an ephemeral, video-local tracklet), `Identity` is the cross-file
            re-identification key. `None` if no global identity is assigned.
        identity_score: The score associated with the `identity` assignment (e.g. the
            cosine similarity to a re-ID gallery prototype). This is `None` if the
            instance has no identity or the identity was assigned manually. Kept
            separate from `tracking_score` (short-term tracklet vs long-term identity).
        from_predicted: The `PredictedInstance` (if any) that this instance was
            initialized from. This is used with human-in-the-loop workflows.
        identity_embedding: An optional `Embedding` describing this instance's
            appearance for re-identification (e.g. a vector produced by a re-ID
            model). ``None`` by default.
        category: An optional `Category` representing the *class* this instance
            belongs to (e.g. ``"female_fly"``, ``"fur_shaved"``), typically
            assigned by classification or re-ID. Mirrors `identity` but groups by
            class rather than individual. `None` if no category is assigned.
        category_score: The score associated with the `category` assignment (e.g.
            the classifier confidence). `None` if the instance has no category or
            the category was assigned manually.
        category_embedding: An optional `Embedding` describing this instance's
            appearance for classification (the vector the `category` was
            classified from). ``None`` by default.
    """

    points: PointsArray = attrs.field(eq=attrs.cmp_using(eq=np.array_equal))
    skeleton: Skeleton
    track: Track | None = None
    tracking_score: float | None = None
    identity: Identity | None = None
    identity_score: float | None = None
    category: Category | None = attrs.field(default=None, converter=to_category)
    category_score: float | None = None
    from_predicted: "PredictedInstance | None" = None
    identity_embedding: Embedding | None = attrs.field(default=None, repr=False)
    category_embedding: Embedding | None = attrs.field(default=None, repr=False)

    @classmethod
    def empty(
        cls,
        skeleton: Skeleton,
        track: Track | None = None,
        tracking_score: float | None = None,
        identity: Identity | None = None,
        identity_score: float | None = None,
        category: Category | None = None,
        category_score: float | None = None,
        identity_embedding: Embedding | None = None,
        category_embedding: Embedding | None = None,
        from_predicted: "PredictedInstance | None" = None,
    ) -> "Instance":
        """Create an empty instance with no points.

        Args:
            skeleton: The `Skeleton` that this `Instance` is associated with.
            track: An optional `Track` associated with a unique animal/object across
                frames or videos.
            tracking_score: The score associated with the `Track` assignment. This is
                typically the value from the score matrix used in an identity
                assignment. This is `None` if the instance is not associated with a
                track or if the track was assigned manually.
            identity: An optional global `Identity` for this instance.
            identity_score: The score associated with the `identity` assignment.
            category: An optional `Category` (class) for this instance.
            category_score: The score associated with the `category` assignment.
            identity_embedding: An optional re-ID `Embedding` for this instance.
            category_embedding: An optional classification `Embedding` for this
                instance.
            from_predicted: The `PredictedInstance` (if any) that this instance was
                initialized from. This is used with human-in-the-loop workflows.

        Returns:
            An `Instance` with an empty numpy array of shape `(n_nodes,)`.
        """
        points = PointsArray.empty(len(skeleton))
        points["name"] = skeleton.node_names

        return cls(
            points=points,
            skeleton=skeleton,
            track=track,
            tracking_score=tracking_score,
            identity=identity,
            identity_score=identity_score,
            category=category,
            category_score=category_score,
            identity_embedding=identity_embedding,
            category_embedding=category_embedding,
            from_predicted=from_predicted,
        )

    @classmethod
    def _convert_points(
        cls, points_data: np.ndarray | dict | list, skeleton: Skeleton
    ) -> PointsArray:
        """Convert points to a structured numpy array if needed."""
        if isinstance(points_data, dict):
            return PointsArray.from_dict(points_data, skeleton)
        elif isinstance(points_data, (list, np.ndarray)):
            if isinstance(points_data, list):
                points_data = np.array(points_data)

            points = PointsArray.from_array(points_data)
            points["name"] = skeleton.node_names
            return points
        else:
            raise ValueError("points must be a numpy array or dictionary.")

    @classmethod
    def from_numpy(
        cls,
        points_data: np.ndarray,
        skeleton: Skeleton,
        track: Track | None = None,
        tracking_score: float | None = None,
        identity: Identity | None = None,
        identity_score: float | None = None,
        category: Category | None = None,
        category_score: float | None = None,
        identity_embedding: Embedding | None = None,
        category_embedding: Embedding | None = None,
        from_predicted: "PredictedInstance | None" = None,
    ) -> "Instance":
        """Create an instance object from a numpy array.

        Args:
            points_data: A numpy array of shape `(n_nodes, D)` corresponding to the
                points of the skeleton. Values of `np.nan` indicate "missing" nodes and
                will be reflected in the "visible" field.

                If `D == 2`, the array should have columns for x and y.
                If `D == 3`, the array should have columns for x, y and visible.
                If `D == 4`, the array should have columns for x, y, visible and
                complete.

                If this is provided as a structured array, it will be used without copy
                if it has the correct dtype. Otherwise, a new structured array will be
                created reusing the provided data.
            skeleton: The `Skeleton` that this `Instance` is associated with. It should
                have `n_nodes` nodes.
            track: An optional `Track` associated with a unique animal/object across
                frames or videos.
            tracking_score: The score associated with the `Track` assignment. This is
                typically the value from the score matrix used in an identity
                assignment. This is `None` if the instance is not associated with a
                track or if the track was assigned manually.
            identity: An optional global `Identity` for this instance.
            identity_score: The score associated with the `identity` assignment.
            category: An optional `Category` (class) for this instance.
            category_score: The score associated with the `category` assignment.
            identity_embedding: An optional re-ID `Embedding` for this instance.
            category_embedding: An optional classification `Embedding` for this
                instance.
            from_predicted: The `PredictedInstance` (if any) that this instance was
                initialized from. This is used with human-in-the-loop workflows.

        Returns:
            An `Instance` object with the specified points.
        """
        return cls(
            points=points_data,
            skeleton=skeleton,
            track=track,
            tracking_score=tracking_score,
            identity=identity,
            identity_score=identity_score,
            category=category,
            category_score=category_score,
            identity_embedding=identity_embedding,
            category_embedding=category_embedding,
            from_predicted=from_predicted,
        )

    def __attrs_post_init__(self):
        """Convert the points array after initialization."""
        if not isinstance(self.points, PointsArray):
            self.points = self._convert_points(self.points, self.skeleton)

        # Ensure points have node names
        if "name" in self.points.dtype.names and not all(self.points["name"]):
            self.points["name"] = self.skeleton.node_names

    def numpy(
        self,
        invisible_as_nan: bool = True,
    ) -> np.ndarray:
        """Return the instance points as a `(n_nodes, 2)` numpy array.

        Args:
            invisible_as_nan: If `True` (the default), points that are not visible will
                be set to `np.nan`. If `False`, they will be whatever the stored value
                of `Instance.points["xy"]` is.

        Returns:
            A numpy array of shape `(n_nodes, 2)` corresponding to the points of the
            skeleton. Values of `np.nan` indicate "missing" nodes.

        Notes:
            This will always return a copy of the array.

            If you need to avoid making a copy, just access the `Instance.points["xy"]`
            attribute directly. This will not replace invisible points with `np.nan`.
        """
        if invisible_as_nan:
            return np.where(
                self.points["visible"].reshape(-1, 1), self.points["xy"], np.nan
            )
        else:
            return self.points["xy"].copy()

    @property
    def centroid_xy(self) -> tuple[float, float] | None:
        """Mean of visible point coordinates as ``(x, y)``, or ``None``.

        Returns:
            A tuple ``(x, y)`` representing the center of mass of all visible
            points, or ``None`` if no points are visible.
        """
        pts = self.numpy(invisible_as_nan=True)
        visible = ~np.isnan(pts[:, 0])
        if not visible.any():
            return None
        return float(pts[visible, 0].mean()), float(pts[visible, 1].mean())

    def to_centroid(
        self,
        method: str = "center_of_mass",
        node: int | str | None = None,
        fallback: str | None = None,
        error_on_empty: bool = False,
        **kwargs,
    ) -> "Centroid":
        """Create a ``Centroid`` from this instance.

        Delegates to ``Centroid.from_pose()``. A ``PredictedInstance`` yields a
        ``PredictedCentroid`` carrying its ``score``; any other instance yields a
        ``UserCentroid``. Metadata (``track``, ``tracking_score``, ``identity``,
        ``identity_score``, ``identity_embedding``, ``category``,
        ``category_score``, ``category_embedding``, ``instance=self``) is
        propagated.

        Args:
            method: Computation method (``"center_of_mass"``, ``"bbox_center"``,
                ``"geometric_median"``, or ``"anchor"``).
            node: Node specification for the ``"anchor"`` method. Can be a node
                name (str) or index (int).
            fallback: For the ``"anchor"`` method, a non-anchor method to fall
                back to when the anchor node is occluded.
            error_on_empty: If ``True``, raise ``ValueError`` when there are no
                visible points instead of returning a degenerate (NaN) centroid.
            **kwargs: Additional keyword arguments passed to the centroid
                constructor.

        Returns:
            A ``UserCentroid`` or ``PredictedCentroid`` depending on the
            instance type.

        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.centroid import Centroid

        return Centroid.from_pose(
            self,
            method=method,
            node=node,
            fallback=fallback,
            error_on_empty=error_on_empty,
            **kwargs,
        )

    def to_bbox(
        self,
        mode: str = "tight",
        size: float | tuple[float, float] | None = None,
        padding: float | tuple[float, float] = 0.0,
        node: int | str | None = None,
        center_method: str = "center_of_mass",
        rotated: bool = False,
        error_on_empty: bool = False,
    ) -> "BoundingBox":
        """Create a bounding box from this instance.

        A ``PredictedInstance`` yields a ``PredictedBoundingBox`` carrying its
        ``score``; any other instance yields a ``UserBoundingBox``. Metadata
        (``track``, ``tracking_score``, ``identity``, ``identity_score``,
        ``identity_embedding``, ``category``, ``category_score``,
        ``category_embedding``, ``instance=self``) is propagated.

        Args:
            mode: ``"tight"`` to fit the visible points, or ``"centered"`` to
                build a fixed-``size`` box centered on a computed centroid.
            size: Box size for ``mode="centered"``. A scalar yields a square box;
                a ``(w, h)`` tuple sets width and height independently. Required
                for ``mode="centered"``.
            padding: Amount to inflate the box outward. Scalar applies to both
                axes; a ``(px, py)`` tuple applies per-axis. Negative values
                shrink the box.
            node: Node specification passed to the centroid computation for
                ``mode="centered"`` with ``center_method="anchor"``.
            center_method: Centroid method used to locate the box center for
                ``mode="centered"`` (see :meth:`to_centroid`).
            rotated: For ``mode="tight"``, if ``True`` fit a minimum-area oriented
                box from the convex hull of visible points; otherwise fit an
                axis-aligned box.
            error_on_empty: If ``True``, raise ``ValueError`` when there are no
                visible points instead of returning a degenerate (NaN) box.

        Returns:
            A ``BoundingBox`` enclosing the instance (or NaN corners if empty).

        Raises:
            ValueError: For an unknown ``mode``, a missing ``size`` for
                ``mode="centered"``, or (when ``error_on_empty`` is ``True``)
                when there are no visible points.
        """
        from sleap_io.model.bbox import PredictedBoundingBox, UserBoundingBox
        from sleap_io.model.roi import (
            _apply_padding,
            _geometry_to_bbox_coords,
            _pose_to_geometry,
        )

        nan = float("nan")
        angle = 0.0

        if mode == "tight":
            pts = self.numpy(invisible_as_nan=True)
            visible = ~np.isnan(pts[:, 0])
            if not visible.any():
                if error_on_empty:
                    raise ValueError("No visible points to compute bounding box.")
                x1 = y1 = x2 = y2 = nan
            elif rotated:
                hull = _pose_to_geometry(
                    pts, self.skeleton.edge_inds, method="convex_hull"
                )
                x1, y1, x2, y2, angle = _geometry_to_bbox_coords(hull, rotated=True)
                x1, y1, x2, y2 = _apply_padding(x1, y1, x2, y2, padding)
            else:
                vis = pts[visible]
                x1 = float(vis[:, 0].min())
                y1 = float(vis[:, 1].min())
                x2 = float(vis[:, 0].max())
                y2 = float(vis[:, 1].max())
                x1, y1, x2, y2 = _apply_padding(x1, y1, x2, y2, padding)
        elif mode == "centered":
            if size is None:
                raise ValueError("'size' is required for mode='centered'.")
            centroid = self.to_centroid(
                method=center_method, node=node, error_on_empty=error_on_empty
            )
            if centroid.is_empty:
                x1 = y1 = x2 = y2 = nan
            else:
                cx, cy = centroid.xy
                if isinstance(size, (tuple, list)):
                    w, h = size
                else:
                    w = h = size
                x1 = cx - w / 2
                y1 = cy - h / 2
                x2 = cx + w / 2
                y2 = cy + h / 2
                x1, y1, x2, y2 = _apply_padding(x1, y1, x2, y2, padding)
        else:
            raise ValueError(f"Unknown mode {mode!r}. Expected 'tight' or 'centered'.")

        kwargs = dict(
            x1=x1,
            y1=y1,
            x2=x2,
            y2=y2,
            angle=angle,
            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,
            instance=self,
        )
        if isinstance(self, PredictedInstance):
            return PredictedBoundingBox(score=self.score, **kwargs)
        return UserBoundingBox(**kwargs)

    def to_roi(
        self,
        method: str = "shapes",
        node_radius: float = 0.0,
        edge_radius: float = 0.0,
        radius: float = 0.0,
        quad_segs: int = 8,
        error_on_empty: bool = False,
    ) -> "ROI":
        """Create a region-of-interest geometry from this instance.

        A ``PredictedInstance`` yields a ``PredictedROI`` carrying its ``score``;
        any other instance yields a ``UserROI``. Metadata (``track``,
        ``tracking_score``, ``identity``, ``identity_score``,
        ``identity_embedding``, ``category``, ``category_score``,
        ``category_embedding``, ``instance=self``) is propagated.

        Args:
            method: ``"shapes"`` to union buffered node points and/or edge
                segments, or ``"convex_hull"`` to take the convex hull of the
                visible points.
            node_radius: Buffer radius around each visible node (``"shapes"``
                only).
            edge_radius: Buffer radius around each fully-visible edge segment
                (``"shapes"`` only).
            radius: Optional buffer applied to the convex hull
                (``"convex_hull"`` only).
            quad_segs: Number of segments used to approximate a quarter circle
                when buffering.
            error_on_empty: If ``True``, raise ``ValueError`` when the resulting
                geometry is empty instead of returning an empty-geometry ROI.

        Returns:
            A ``ROI`` whose geometry encloses the instance (an empty ``Polygon``
            if there are no visible points).

        Raises:
            ValueError: If ``method="shapes"`` with both ``node_radius`` and
                ``edge_radius`` equal to 0 (a misconfiguration, always raised),
                for an unknown ``method``, or (when ``error_on_empty`` is
                ``True``) when the resulting geometry is empty.
        """
        from sleap_io.model.roi import PredictedROI, UserROI, _pose_to_geometry

        # Misconfiguration: raise before the empty-points check so that an empty
        # instance still surfaces the error.
        if method == "shapes" and node_radius == 0 and edge_radius == 0:
            raise ValueError(
                "method='shapes' requires at least one of node_radius or "
                "edge_radius to be > 0."
            )

        geom = _pose_to_geometry(
            self.numpy(invisible_as_nan=True),
            self.skeleton.edge_inds,
            method=method,
            node_radius=node_radius,
            edge_radius=edge_radius,
            radius=radius,
            quad_segs=quad_segs,
        )

        if geom.is_empty and error_on_empty:
            raise ValueError("No visible points to compute ROI geometry.")

        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,
            category=self.category,
            category_score=self.category_score,
            category_embedding=self.category_embedding,
            instance=self,
        )
        if isinstance(self, PredictedInstance):
            return PredictedROI(score=self.score, **kwargs)
        return UserROI(**kwargs)

    def to_mask(self, height: int, width: int, **roi_kwargs) -> "SegmentationMask":
        """Rasterize this instance's ROI geometry into a segmentation mask.

        Equivalent to ``self.to_roi(**roi_kwargs).to_mask(height, width)``,
        except that a zero-area hull (``method="convex_hull"`` over fewer than
        three visible points yields a ``Point`` or ``LineString``) rasterizes to
        an all-background mask here instead of raising. A ``PredictedInstance``
        yields a ``PredictedSegmentationMask`` carrying its ``score``; any other
        instance yields a ``UserSegmentationMask``. Metadata is propagated.

        Args:
            height: Height of the output mask in pixels.
            width: Width of the output mask in pixels.
            **roi_kwargs: Keyword arguments forwarded to :meth:`to_roi` (e.g.
                ``method``, ``node_radius``, ``edge_radius``, ``radius``,
                ``quad_segs``, ``error_on_empty``).

        Returns:
            A ``SegmentationMask`` with the rasterized geometry (all background
            if the geometry is empty or has zero area).

        Raises:
            ValueError: Propagated from :meth:`to_roi` for a ``"shapes"``
                misconfiguration, an unknown method, or (when
                ``error_on_empty`` is ``True``) an empty geometry.
        """
        from shapely.geometry import MultiPolygon, Polygon

        error_on_empty = roi_kwargs.pop("error_on_empty", False)
        roi = self.to_roi(error_on_empty=error_on_empty, **roi_kwargs)

        # A non-empty but non-fillable geometry (e.g. convex_hull of <3 visible
        # points -> Point/LineString) has zero area; rasterize it as all
        # background rather than letting _rasterize_geometry raise a TypeError.
        rasterizable = isinstance(roi.geometry, (Polygon, MultiPolygon))
        if roi.geometry.is_empty or not rasterizable:
            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,
                category=self.category,
                instance=self,
            )
            if isinstance(self, PredictedInstance):
                return PredictedSegmentationMask.from_numpy(
                    empty, score=self.score, **kwargs
                )
            return UserSegmentationMask.from_numpy(empty, **kwargs)

        return roi.to_mask(height, width)

    def __getitem__(self, node: int | str | Node) -> np.ndarray:
        """Return the point associated with a node."""
        if type(node) is not int:
            node = self.skeleton.index(node)

        return self.points[node]

    def __setitem__(self, node: int | str | Node, value):
        """Set the point associated with a node.

        Args:
            node: The node to set the point for. Can be an integer index, string name,
                or Node object.
            value: A tuple or array-like of length 2 containing (x, y) coordinates.

        Notes:
            This sets the point coordinates and marks the point as visible.
        """
        if type(node) is not int:
            node = self.skeleton.index(node)

        if len(value) < 2:
            raise ValueError("Value must have at least 2 elements (x, y)")

        self.points[node]["xy"] = value[:2]
        self.points[node]["visible"] = True

    def __len__(self) -> int:
        """Return the number of points in the instance."""
        return len(self.points)

    def __repr__(self) -> str:
        """Return a readable representation of the instance."""
        pts = self.numpy().tolist()
        track = f'"{self.track.name}"' if self.track is not None else self.track

        return f"Instance(points={pts}, track={track})"

    @property
    def n_visible(self) -> int:
        """Return the number of visible points in the instance."""
        return sum(self.points["visible"])

    @property
    def is_empty(self) -> bool:
        """Return `True` if no points are visible on the instance."""
        return ~(self.points["visible"].any())

    def update_skeleton(self, names_only: bool = False):
        """Update or replace the skeleton associated with the instance.

        Args:
            names_only: If `True`, only update the node names in the points array. If
                `False`, the points array will be updated to match the new skeleton.
        """
        if names_only:
            # Update the node names.
            self.points["name"] = self.skeleton.node_names
            return

        # Find correspondences.
        new_node_inds, old_node_inds = self.skeleton.match_nodes(self.points["name"])

        # Update the points.
        new_points = PointsArray.empty(len(self.skeleton))
        new_points[new_node_inds] = self.points[old_node_inds]
        new_points["name"] = self.skeleton.node_names
        self.points = new_points

    def replace_skeleton(
        self,
        new_skeleton: Skeleton,
        node_names_map: dict[str, str] | None = None,
    ):
        """Replace the skeleton associated with the instance.

        Args:
            new_skeleton: The new `Skeleton` to associate with the instance.
            node_names_map: Dictionary mapping nodes in the old skeleton to nodes in the
                new skeleton. Keys and values should be specified as lists of strings.
                If not provided, only nodes with identical names will be mapped. Points
                associated with unmapped nodes will be removed.

        Notes:
            This method will update the `Instance.skeleton` attribute and the
            `Instance.points` attribute in place (a copy is made of the points array).

            It is recommended to use `Labels.replace_skeleton` instead of this method if
            more flexible node mapping is required.
        """
        # Update skeleton object.
        # old_skeleton = self.skeleton
        self.skeleton = new_skeleton

        # Get node names with replacements from node map if possible.
        # old_node_names = old_skeleton.node_names
        old_node_names = self.points["name"].tolist()
        if node_names_map is not None:
            old_node_names = [node_names_map.get(node, node) for node in old_node_names]

        # Find correspondences.
        new_node_inds, old_node_inds = self.skeleton.match_nodes(old_node_names)
        # old_node_inds = np.array(old_node_inds).reshape(-1, 1)
        # new_node_inds = np.array(new_node_inds).reshape(-1, 1)

        # Update the points.
        new_points = PointsArray.empty(len(self.skeleton))
        new_points[new_node_inds] = self.points[old_node_inds]
        self.points = new_points
        self.points["name"] = self.skeleton.node_names

    def same_pose_as(self, other: "Instance", tolerance: float = None) -> bool:
        """Check if this instance has the same pose as another instance.

        Args:
            other: Another instance to compare with.
            tolerance: Maximum distance (in pixels) between corresponding points
                for them to be considered the same. If None (default), uses exact
                comparison including proper NaN handling.

        Returns:
            True if the instances have the same pose within tolerance, False otherwise.

        Notes:
            Two instances are considered to have the same pose if:
            - They have the same skeleton structure
            - When tolerance is None: All coordinates match exactly (including NaN)
            - When tolerance is specified: All visible points are within tolerance
              distance and NaN patterns match exactly
        """
        # Check skeleton compatibility
        if not self.skeleton.matches(other.skeleton):
            return False

        if tolerance is None:
            # Exact comparison using numpy arrays with proper NaN handling
            return np.array_equal(self.numpy(), other.numpy(), equal_nan=True)
        else:
            # Tolerance-based comparison with proper NaN handling
            self_array = self.numpy()
            other_array = other.numpy()

            # First, check if NaN patterns match exactly
            self_nan_mask = np.isnan(self_array)
            other_nan_mask = np.isnan(other_array)
            if not np.array_equal(self_nan_mask, other_nan_mask):
                return False

            # Get mask for non-NaN values
            non_nan_mask = ~self_nan_mask

            # If all values are NaN, they're considered equal
            if not non_nan_mask.any():
                return True

            # Calculate distances only for non-NaN points
            self_pts = self_array[non_nan_mask]
            other_pts = other_array[non_nan_mask]

            # Reshape to handle the coordinate pairs properly
            self_pts = self_pts.reshape(-1, 2)
            other_pts = other_pts.reshape(-1, 2)

            distances = np.linalg.norm(self_pts - other_pts, axis=1)

            return np.all(distances <= tolerance)

    def same_identity_as(self, other: "Instance") -> bool:
        """Check if this instance has the same identity as another instance.

        Args:
            other: Another instance to compare with.

        Returns:
            True if both instances share the same identity, False otherwise.

        Notes:
            Global `Identity` takes precedence: if both instances carry an
            `Identity`, they match when their `name`s match (which survives
            serialization and cross-file merges). Otherwise this falls back to
            the ephemeral `Track`, where instances match only when they share the
            same `Track` object (by object identity, not just by name).
        """
        if self.identity is not None and other.identity is not None:
            return self.identity.matches(other.identity, method="name")
        if self.track is None or other.track is None:
            return False
        return self.track is other.track

    def overlaps_with(self, other: "Instance", iou_threshold: float = 0.5) -> bool:
        """Check if this instance overlaps with another based on bounding box IoU.

        Args:
            other: Another instance to compare with.
            iou_threshold: Minimum IoU (Intersection over Union) value to consider
                the instances as overlapping.

        Returns:
            True if the instances overlap above the threshold, False otherwise.

        Notes:
            Overlap is computed using the bounding boxes of visible points.
            If either instance has no visible points, they don't overlap.
        """
        # Get visible points for both instances
        self_visible = self.points["visible"]
        other_visible = other.points["visible"]

        if not self_visible.any() or not other_visible.any():
            return False

        # Calculate bounding boxes
        self_pts = self.points["xy"][self_visible]
        other_pts = other.points["xy"][other_visible]

        self_bbox = np.array(
            [
                [np.min(self_pts[:, 0]), np.min(self_pts[:, 1])],  # min x, y
                [np.max(self_pts[:, 0]), np.max(self_pts[:, 1])],  # max x, y
            ]
        )

        other_bbox = np.array(
            [
                [np.min(other_pts[:, 0]), np.min(other_pts[:, 1])],
                [np.max(other_pts[:, 0]), np.max(other_pts[:, 1])],
            ]
        )

        # Calculate intersection
        intersection_min = np.maximum(self_bbox[0], other_bbox[0])
        intersection_max = np.minimum(self_bbox[1], other_bbox[1])

        if np.any(intersection_min >= intersection_max):
            # No intersection
            return False

        intersection_area = np.prod(intersection_max - intersection_min)

        # Calculate union
        self_area = np.prod(self_bbox[1] - self_bbox[0])
        other_area = np.prod(other_bbox[1] - other_bbox[0])
        union_area = self_area + other_area - intersection_area

        # Calculate IoU
        iou = intersection_area / union_area if union_area > 0 else 0

        return iou >= iou_threshold

    def bounding_box(self) -> np.ndarray | None:
        """Get the bounding box of visible points.

        Returns:
            A numpy array of shape (2, 2) with [[min_x, min_y], [max_x, max_y]],
            or None if there are no visible points.
        """
        visible = self.points["visible"]
        if not visible.any():
            return None

        pts = self.points["xy"][visible]
        return np.array(
            [
                [np.min(pts[:, 0]), np.min(pts[:, 1])],
                [np.max(pts[:, 0]), np.max(pts[:, 1])],
            ]
        )

__annotations__ = {'points': 'PointsArray', 'skeleton': 'Skeleton', 'track': 'Track | None', 'tracking_score': 'float | None', 'identity': 'Identity | None', 'identity_score': 'float | None', 'category': 'Category | None', 'category_score': 'float | None', 'from_predicted': "'PredictedInstance | None'", 'identity_embedding': 'Embedding | None', 'category_embedding': 'Embedding | None'} 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=False, 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__ = 'This class represents a ground truth instance such as an animal.\n\nAn `Instance` has a set of landmarks (points) that correspond to a `Skeleton`. Each\npoint is associated with a `Node` in the skeleton. The points are stored in a\nstructured numpy array with columns for x, y, visible, complete and name.\n\nThe `Instance` may also be associated with a `Track` which links multiple instances\ntogether across frames or videos.\n\nAttributes:\n points: A numpy structured array with columns for xy, visible and complete. The\n array should have shape `(n_nodes,)`. This representation is useful for\n performance efficiency when working with large datasets.\n skeleton: The `Skeleton` that describes the `Node`s and `Edge`s associated with\n this instance.\n track: An optional `Track` associated with a unique animal/object across frames\n or videos.\n tracking_score: The score associated with the `Track` assignment. This is\n typically the value from the score matrix used in an identity assignment.\n This is `None` if the instance is not associated with a track or if the\n track was assigned manually.\n identity: An optional `Identity` representing the global, ground-truth animal\n this instance belongs to (persistent across videos/sessions). Unlike\n `track` (an ephemeral, video-local tracklet), `Identity` is the cross-file\n re-identification key. `None` if no global identity is assigned.\n identity_score: The score associated with the `identity` assignment (e.g. the\n cosine similarity to a re-ID gallery prototype). This is `None` if the\n instance has no identity or the identity was assigned manually. Kept\n separate from `tracking_score` (short-term tracklet vs long-term identity).\n from_predicted: The `PredictedInstance` (if any) that this instance was\n initialized from. This is used with human-in-the-loop workflows.\n identity_embedding: An optional `Embedding` describing this instance\'s\n appearance for re-identification (e.g. a vector produced by a re-ID\n model). ``None`` by default.\n category: An optional `Category` representing the *class* this instance\n belongs to (e.g. ``"female_fly"``, ``"fur_shaved"``), typically\n assigned by classification or re-ID. Mirrors `identity` but groups by\n class rather than individual. `None` if no category is assigned.\n category_score: The score associated with the `category` assignment (e.g.\n the classifier confidence). `None` if the instance has no category or\n the category was assigned manually.\n category_embedding: An optional `Embedding` describing this instance\'s\n appearance for classification (the vector the `category` was\n classified from). ``None`` by default.\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__ = 397 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__ = ('points', 'skeleton', 'track', 'tracking_score', 'identity', 'identity_score', 'category', 'category_score', 'from_predicted', 'identity_embedding', '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.instance' 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__ = ('points', 'skeleton', 'track', 'tracking_score', 'identity', 'identity_score', 'category', 'category_score', 'from_predicted', 'identity_embedding', 'category_embedding', '__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__ = ('points', 'skeleton') 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

centroid_xy property

Mean of visible point coordinates as (x, y), or None.

Returns:

Type Description

A tuple (x, y) representing the center of mass of all visible points, or None if no points are visible.

is_empty property

Return True if no points are visible on the instance.

n_visible property

Return the number of visible points in the instance.

__attrs_post_init__()

Convert the points array after initialization.

Source code in sleap_io/model/instance.py
def __attrs_post_init__(self):
    """Convert the points array after initialization."""
    if not isinstance(self.points, PointsArray):
        self.points = self._convert_points(self.points, self.skeleton)

    # Ensure points have node names
    if "name" in self.points.dtype.names and not all(self.points["name"]):
        self.points["name"] = self.skeleton.node_names

__getitem__(node)

Return the point associated with a node.

Source code in sleap_io/model/instance.py
def __getitem__(self, node: int | str | Node) -> np.ndarray:
    """Return the point associated with a node."""
    if type(node) is not int:
        node = self.skeleton.index(node)

    return self.points[node]

__init__(points, skeleton, track=None, tracking_score=None, identity=None, identity_score=None, category=None, category_score=None, from_predicted=None, identity_embedding=None, category_embedding=None)

Method generated by attrs for class Instance.

Source code in sleap_io/model/instance.py
"""Data structures for data associated with a single instance such as an animal.

The `Instance` class is a SLEAP data structure that contains a collection of points that
correspond to landmarks within a `Skeleton`.

`PredictedInstance` additionally contains metadata associated with how the instance was
estimated, such as confidence scores.
"""

from __future__ import annotations

from typing import TYPE_CHECKING

import attrs

__len__()

Return the number of points in the instance.

Source code in sleap_io/model/instance.py
def __len__(self) -> int:
    """Return the number of points in the instance."""
    return len(self.points)

__repr__()

Return a readable representation of the instance.

Source code in sleap_io/model/instance.py
def __repr__(self) -> str:
    """Return a readable representation of the instance."""
    pts = self.numpy().tolist()
    track = f'"{self.track.name}"' if self.track is not None else self.track

    return f"Instance(points={pts}, track={track})"

__setattr__(name, val)

Method generated by attrs for class Instance.

Source code in sleap_io/model/instance.py
if np.any(intersection_min >= intersection_max):
    # No intersection
    return False

intersection_area = np.prod(intersection_max - intersection_min)

# Calculate union
self_area = np.prod(self_bbox[1] - self_bbox[0])
other_area = np.prod(other_bbox[1] - other_bbox[0])

__setitem__(node, value)

Set the point associated with a node.

Parameters:

Name Type Description Default
node int | str | Node

The node to set the point for. Can be an integer index, string name, or Node object.

required
value

A tuple or array-like of length 2 containing (x, y) coordinates.

required
Notes

This sets the point coordinates and marks the point as visible.

Source code in sleap_io/model/instance.py
def __setitem__(self, node: int | str | Node, value):
    """Set the point associated with a node.

    Args:
        node: The node to set the point for. Can be an integer index, string name,
            or Node object.
        value: A tuple or array-like of length 2 containing (x, y) coordinates.

    Notes:
        This sets the point coordinates and marks the point as visible.
    """
    if type(node) is not int:
        node = self.skeleton.index(node)

    if len(value) < 2:
        raise ValueError("Value must have at least 2 elements (x, y)")

    self.points[node]["xy"] = value[:2]
    self.points[node]["visible"] = True

bounding_box()

Get the bounding box of visible points.

Returns:

Type Description
ndarray | None

A numpy array of shape (2, 2) with [[min_x, min_y], [max_x, max_y]], or None if there are no visible points.

Source code in sleap_io/model/instance.py
def bounding_box(self) -> np.ndarray | None:
    """Get the bounding box of visible points.

    Returns:
        A numpy array of shape (2, 2) with [[min_x, min_y], [max_x, max_y]],
        or None if there are no visible points.
    """
    visible = self.points["visible"]
    if not visible.any():
        return None

    pts = self.points["xy"][visible]
    return np.array(
        [
            [np.min(pts[:, 0]), np.min(pts[:, 1])],
            [np.max(pts[:, 0]), np.max(pts[:, 1])],
        ]
    )

empty(skeleton, track=None, tracking_score=None, identity=None, identity_score=None, category=None, category_score=None, identity_embedding=None, category_embedding=None, from_predicted=None) classmethod

Create an empty instance with no points.

Parameters:

Name Type Description Default
skeleton Skeleton

The Skeleton that this Instance is associated with.

required
track Track | None

An optional Track associated with a unique animal/object across frames or videos.

None
tracking_score float | None

The score associated with the Track assignment. This is typically the value from the score matrix used in an identity assignment. This is None if the instance is not associated with a track or if the track was assigned manually.

None
identity Identity | None

An optional global Identity for this instance.

None
identity_score float | None

The score associated with the identity assignment.

None
category Category | None

An optional Category (class) for this instance.

None
category_score float | None

The score associated with the category assignment.

None
identity_embedding Embedding | None

An optional re-ID Embedding for this instance.

None
category_embedding Embedding | None

An optional classification Embedding for this instance.

None
from_predicted PredictedInstance | None

The PredictedInstance (if any) that this instance was initialized from. This is used with human-in-the-loop workflows.

None

Returns:

Type Description
Instance

An Instance with an empty numpy array of shape (n_nodes,).

Source code in sleap_io/model/instance.py
@classmethod
def empty(
    cls,
    skeleton: Skeleton,
    track: Track | None = None,
    tracking_score: float | None = None,
    identity: Identity | None = None,
    identity_score: float | None = None,
    category: Category | None = None,
    category_score: float | None = None,
    identity_embedding: Embedding | None = None,
    category_embedding: Embedding | None = None,
    from_predicted: "PredictedInstance | None" = None,
) -> "Instance":
    """Create an empty instance with no points.

    Args:
        skeleton: The `Skeleton` that this `Instance` is associated with.
        track: An optional `Track` associated with a unique animal/object across
            frames or videos.
        tracking_score: The score associated with the `Track` assignment. This is
            typically the value from the score matrix used in an identity
            assignment. This is `None` if the instance is not associated with a
            track or if the track was assigned manually.
        identity: An optional global `Identity` for this instance.
        identity_score: The score associated with the `identity` assignment.
        category: An optional `Category` (class) for this instance.
        category_score: The score associated with the `category` assignment.
        identity_embedding: An optional re-ID `Embedding` for this instance.
        category_embedding: An optional classification `Embedding` for this
            instance.
        from_predicted: The `PredictedInstance` (if any) that this instance was
            initialized from. This is used with human-in-the-loop workflows.

    Returns:
        An `Instance` with an empty numpy array of shape `(n_nodes,)`.
    """
    points = PointsArray.empty(len(skeleton))
    points["name"] = skeleton.node_names

    return cls(
        points=points,
        skeleton=skeleton,
        track=track,
        tracking_score=tracking_score,
        identity=identity,
        identity_score=identity_score,
        category=category,
        category_score=category_score,
        identity_embedding=identity_embedding,
        category_embedding=category_embedding,
        from_predicted=from_predicted,
    )

from_numpy(points_data, skeleton, track=None, tracking_score=None, identity=None, identity_score=None, category=None, category_score=None, identity_embedding=None, category_embedding=None, from_predicted=None) classmethod

Create an instance object from a numpy array.

Parameters:

Name Type Description Default
points_data ndarray

A numpy array of shape (n_nodes, D) corresponding to the points of the skeleton. Values of np.nan indicate "missing" nodes and will be reflected in the "visible" field.

If D == 2, the array should have columns for x and y. If D == 3, the array should have columns for x, y and visible. If D == 4, the array should have columns for x, y, visible and complete.

If this is provided as a structured array, it will be used without copy if it has the correct dtype. Otherwise, a new structured array will be created reusing the provided data.

required
skeleton Skeleton

The Skeleton that this Instance is associated with. It should have n_nodes nodes.

required
track Track | None

An optional Track associated with a unique animal/object across frames or videos.

None
tracking_score float | None

The score associated with the Track assignment. This is typically the value from the score matrix used in an identity assignment. This is None if the instance is not associated with a track or if the track was assigned manually.

None
identity Identity | None

An optional global Identity for this instance.

None
identity_score float | None

The score associated with the identity assignment.

None
category Category | None

An optional Category (class) for this instance.

None
category_score float | None

The score associated with the category assignment.

None
identity_embedding Embedding | None

An optional re-ID Embedding for this instance.

None
category_embedding Embedding | None

An optional classification Embedding for this instance.

None
from_predicted PredictedInstance | None

The PredictedInstance (if any) that this instance was initialized from. This is used with human-in-the-loop workflows.

None

Returns:

Type Description
Instance

An Instance object with the specified points.

Source code in sleap_io/model/instance.py
@classmethod
def from_numpy(
    cls,
    points_data: np.ndarray,
    skeleton: Skeleton,
    track: Track | None = None,
    tracking_score: float | None = None,
    identity: Identity | None = None,
    identity_score: float | None = None,
    category: Category | None = None,
    category_score: float | None = None,
    identity_embedding: Embedding | None = None,
    category_embedding: Embedding | None = None,
    from_predicted: "PredictedInstance | None" = None,
) -> "Instance":
    """Create an instance object from a numpy array.

    Args:
        points_data: A numpy array of shape `(n_nodes, D)` corresponding to the
            points of the skeleton. Values of `np.nan` indicate "missing" nodes and
            will be reflected in the "visible" field.

            If `D == 2`, the array should have columns for x and y.
            If `D == 3`, the array should have columns for x, y and visible.
            If `D == 4`, the array should have columns for x, y, visible and
            complete.

            If this is provided as a structured array, it will be used without copy
            if it has the correct dtype. Otherwise, a new structured array will be
            created reusing the provided data.
        skeleton: The `Skeleton` that this `Instance` is associated with. It should
            have `n_nodes` nodes.
        track: An optional `Track` associated with a unique animal/object across
            frames or videos.
        tracking_score: The score associated with the `Track` assignment. This is
            typically the value from the score matrix used in an identity
            assignment. This is `None` if the instance is not associated with a
            track or if the track was assigned manually.
        identity: An optional global `Identity` for this instance.
        identity_score: The score associated with the `identity` assignment.
        category: An optional `Category` (class) for this instance.
        category_score: The score associated with the `category` assignment.
        identity_embedding: An optional re-ID `Embedding` for this instance.
        category_embedding: An optional classification `Embedding` for this
            instance.
        from_predicted: The `PredictedInstance` (if any) that this instance was
            initialized from. This is used with human-in-the-loop workflows.

    Returns:
        An `Instance` object with the specified points.
    """
    return cls(
        points=points_data,
        skeleton=skeleton,
        track=track,
        tracking_score=tracking_score,
        identity=identity,
        identity_score=identity_score,
        category=category,
        category_score=category_score,
        identity_embedding=identity_embedding,
        category_embedding=category_embedding,
        from_predicted=from_predicted,
    )

numpy(invisible_as_nan=True)

Return the instance points as a (n_nodes, 2) numpy array.

Parameters:

Name Type Description Default
invisible_as_nan bool

If True (the default), points that are not visible will be set to np.nan. If False, they will be whatever the stored value of Instance.points["xy"] is.

True

Returns:

Type Description
ndarray

A numpy array of shape (n_nodes, 2) corresponding to the points of the skeleton. Values of np.nan indicate "missing" nodes.

Notes

This will always return a copy of the array.

If you need to avoid making a copy, just access the Instance.points["xy"] attribute directly. This will not replace invisible points with np.nan.

Source code in sleap_io/model/instance.py
def numpy(
    self,
    invisible_as_nan: bool = True,
) -> np.ndarray:
    """Return the instance points as a `(n_nodes, 2)` numpy array.

    Args:
        invisible_as_nan: If `True` (the default), points that are not visible will
            be set to `np.nan`. If `False`, they will be whatever the stored value
            of `Instance.points["xy"]` is.

    Returns:
        A numpy array of shape `(n_nodes, 2)` corresponding to the points of the
        skeleton. Values of `np.nan` indicate "missing" nodes.

    Notes:
        This will always return a copy of the array.

        If you need to avoid making a copy, just access the `Instance.points["xy"]`
        attribute directly. This will not replace invisible points with `np.nan`.
    """
    if invisible_as_nan:
        return np.where(
            self.points["visible"].reshape(-1, 1), self.points["xy"], np.nan
        )
    else:
        return self.points["xy"].copy()

overlaps_with(other, iou_threshold=0.5)

Check if this instance overlaps with another based on bounding box IoU.

Parameters:

Name Type Description Default
other Instance

Another instance to compare with.

required
iou_threshold float

Minimum IoU (Intersection over Union) value to consider the instances as overlapping.

0.5

Returns:

Type Description
bool

True if the instances overlap above the threshold, False otherwise.

Notes

Overlap is computed using the bounding boxes of visible points. If either instance has no visible points, they don't overlap.

Source code in sleap_io/model/instance.py
def overlaps_with(self, other: "Instance", iou_threshold: float = 0.5) -> bool:
    """Check if this instance overlaps with another based on bounding box IoU.

    Args:
        other: Another instance to compare with.
        iou_threshold: Minimum IoU (Intersection over Union) value to consider
            the instances as overlapping.

    Returns:
        True if the instances overlap above the threshold, False otherwise.

    Notes:
        Overlap is computed using the bounding boxes of visible points.
        If either instance has no visible points, they don't overlap.
    """
    # Get visible points for both instances
    self_visible = self.points["visible"]
    other_visible = other.points["visible"]

    if not self_visible.any() or not other_visible.any():
        return False

    # Calculate bounding boxes
    self_pts = self.points["xy"][self_visible]
    other_pts = other.points["xy"][other_visible]

    self_bbox = np.array(
        [
            [np.min(self_pts[:, 0]), np.min(self_pts[:, 1])],  # min x, y
            [np.max(self_pts[:, 0]), np.max(self_pts[:, 1])],  # max x, y
        ]
    )

    other_bbox = np.array(
        [
            [np.min(other_pts[:, 0]), np.min(other_pts[:, 1])],
            [np.max(other_pts[:, 0]), np.max(other_pts[:, 1])],
        ]
    )

    # Calculate intersection
    intersection_min = np.maximum(self_bbox[0], other_bbox[0])
    intersection_max = np.minimum(self_bbox[1], other_bbox[1])

    if np.any(intersection_min >= intersection_max):
        # No intersection
        return False

    intersection_area = np.prod(intersection_max - intersection_min)

    # Calculate union
    self_area = np.prod(self_bbox[1] - self_bbox[0])
    other_area = np.prod(other_bbox[1] - other_bbox[0])
    union_area = self_area + other_area - intersection_area

    # Calculate IoU
    iou = intersection_area / union_area if union_area > 0 else 0

    return iou >= iou_threshold

replace_skeleton(new_skeleton, node_names_map=None)

Replace the skeleton associated with the instance.

Parameters:

Name Type Description Default
new_skeleton Skeleton

The new Skeleton to associate with the instance.

required
node_names_map dict[str, str] | None

Dictionary mapping nodes in the old skeleton to nodes in the new skeleton. Keys and values should be specified as lists of strings. If not provided, only nodes with identical names will be mapped. Points associated with unmapped nodes will be removed.

None
Notes

This method will update the Instance.skeleton attribute and the Instance.points attribute in place (a copy is made of the points array).

It is recommended to use Labels.replace_skeleton instead of this method if more flexible node mapping is required.

Source code in sleap_io/model/instance.py
def replace_skeleton(
    self,
    new_skeleton: Skeleton,
    node_names_map: dict[str, str] | None = None,
):
    """Replace the skeleton associated with the instance.

    Args:
        new_skeleton: The new `Skeleton` to associate with the instance.
        node_names_map: Dictionary mapping nodes in the old skeleton to nodes in the
            new skeleton. Keys and values should be specified as lists of strings.
            If not provided, only nodes with identical names will be mapped. Points
            associated with unmapped nodes will be removed.

    Notes:
        This method will update the `Instance.skeleton` attribute and the
        `Instance.points` attribute in place (a copy is made of the points array).

        It is recommended to use `Labels.replace_skeleton` instead of this method if
        more flexible node mapping is required.
    """
    # Update skeleton object.
    # old_skeleton = self.skeleton
    self.skeleton = new_skeleton

    # Get node names with replacements from node map if possible.
    # old_node_names = old_skeleton.node_names
    old_node_names = self.points["name"].tolist()
    if node_names_map is not None:
        old_node_names = [node_names_map.get(node, node) for node in old_node_names]

    # Find correspondences.
    new_node_inds, old_node_inds = self.skeleton.match_nodes(old_node_names)
    # old_node_inds = np.array(old_node_inds).reshape(-1, 1)
    # new_node_inds = np.array(new_node_inds).reshape(-1, 1)

    # Update the points.
    new_points = PointsArray.empty(len(self.skeleton))
    new_points[new_node_inds] = self.points[old_node_inds]
    self.points = new_points
    self.points["name"] = self.skeleton.node_names

same_identity_as(other)

Check if this instance has the same identity as another instance.

Parameters:

Name Type Description Default
other Instance

Another instance to compare with.

required

Returns:

Type Description
bool

True if both instances share the same identity, False otherwise.

Notes

Global Identity takes precedence: if both instances carry an Identity, they match when their names match (which survives serialization and cross-file merges). Otherwise this falls back to the ephemeral Track, where instances match only when they share the same Track object (by object identity, not just by name).

Source code in sleap_io/model/instance.py
def same_identity_as(self, other: "Instance") -> bool:
    """Check if this instance has the same identity as another instance.

    Args:
        other: Another instance to compare with.

    Returns:
        True if both instances share the same identity, False otherwise.

    Notes:
        Global `Identity` takes precedence: if both instances carry an
        `Identity`, they match when their `name`s match (which survives
        serialization and cross-file merges). Otherwise this falls back to
        the ephemeral `Track`, where instances match only when they share the
        same `Track` object (by object identity, not just by name).
    """
    if self.identity is not None and other.identity is not None:
        return self.identity.matches(other.identity, method="name")
    if self.track is None or other.track is None:
        return False
    return self.track is other.track

same_pose_as(other, tolerance=None)

Check if this instance has the same pose as another instance.

Parameters:

Name Type Description Default
other Instance

Another instance to compare with.

required
tolerance float

Maximum distance (in pixels) between corresponding points for them to be considered the same. If None (default), uses exact comparison including proper NaN handling.

None

Returns:

Type Description
bool

True if the instances have the same pose within tolerance, False otherwise.

Notes

Two instances are considered to have the same pose if: - They have the same skeleton structure - When tolerance is None: All coordinates match exactly (including NaN) - When tolerance is specified: All visible points are within tolerance distance and NaN patterns match exactly

Source code in sleap_io/model/instance.py
def same_pose_as(self, other: "Instance", tolerance: float = None) -> bool:
    """Check if this instance has the same pose as another instance.

    Args:
        other: Another instance to compare with.
        tolerance: Maximum distance (in pixels) between corresponding points
            for them to be considered the same. If None (default), uses exact
            comparison including proper NaN handling.

    Returns:
        True if the instances have the same pose within tolerance, False otherwise.

    Notes:
        Two instances are considered to have the same pose if:
        - They have the same skeleton structure
        - When tolerance is None: All coordinates match exactly (including NaN)
        - When tolerance is specified: All visible points are within tolerance
          distance and NaN patterns match exactly
    """
    # Check skeleton compatibility
    if not self.skeleton.matches(other.skeleton):
        return False

    if tolerance is None:
        # Exact comparison using numpy arrays with proper NaN handling
        return np.array_equal(self.numpy(), other.numpy(), equal_nan=True)
    else:
        # Tolerance-based comparison with proper NaN handling
        self_array = self.numpy()
        other_array = other.numpy()

        # First, check if NaN patterns match exactly
        self_nan_mask = np.isnan(self_array)
        other_nan_mask = np.isnan(other_array)
        if not np.array_equal(self_nan_mask, other_nan_mask):
            return False

        # Get mask for non-NaN values
        non_nan_mask = ~self_nan_mask

        # If all values are NaN, they're considered equal
        if not non_nan_mask.any():
            return True

        # Calculate distances only for non-NaN points
        self_pts = self_array[non_nan_mask]
        other_pts = other_array[non_nan_mask]

        # Reshape to handle the coordinate pairs properly
        self_pts = self_pts.reshape(-1, 2)
        other_pts = other_pts.reshape(-1, 2)

        distances = np.linalg.norm(self_pts - other_pts, axis=1)

        return np.all(distances <= tolerance)

to_bbox(mode='tight', size=None, padding=0.0, node=None, center_method='center_of_mass', rotated=False, error_on_empty=False)

Create a bounding box from this instance.

A PredictedInstance yields a PredictedBoundingBox carrying its score; any other instance yields a UserBoundingBox. Metadata (track, tracking_score, identity, identity_score, identity_embedding, category, category_score, category_embedding, instance=self) is propagated.

Parameters:

Name Type Description Default
mode str

"tight" to fit the visible points, or "centered" to build a fixed-size box centered on a computed centroid.

'tight'
size float | tuple[float, float] | None

Box size for mode="centered". A scalar yields a square box; a (w, h) tuple sets width and height independently. Required for mode="centered".

None
padding float | tuple[float, float]

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

0.0
node int | str | None

Node specification passed to the centroid computation for mode="centered" with center_method="anchor".

None
center_method str

Centroid method used to locate the box center for mode="centered" (see :meth:to_centroid).

'center_of_mass'
rotated bool

For mode="tight", if True fit a minimum-area oriented box from the convex hull of visible points; otherwise fit an axis-aligned box.

False
error_on_empty bool

If True, raise ValueError when there are no visible points instead of returning a degenerate (NaN) box.

False

Returns:

Type Description
BoundingBox

A BoundingBox enclosing the instance (or NaN corners if empty).

Raises:

Type Description
ValueError

For an unknown mode, a missing size for mode="centered", or (when error_on_empty is True) when there are no visible points.

Source code in sleap_io/model/instance.py
def to_bbox(
    self,
    mode: str = "tight",
    size: float | tuple[float, float] | None = None,
    padding: float | tuple[float, float] = 0.0,
    node: int | str | None = None,
    center_method: str = "center_of_mass",
    rotated: bool = False,
    error_on_empty: bool = False,
) -> "BoundingBox":
    """Create a bounding box from this instance.

    A ``PredictedInstance`` yields a ``PredictedBoundingBox`` carrying its
    ``score``; any other instance yields a ``UserBoundingBox``. Metadata
    (``track``, ``tracking_score``, ``identity``, ``identity_score``,
    ``identity_embedding``, ``category``, ``category_score``,
    ``category_embedding``, ``instance=self``) is propagated.

    Args:
        mode: ``"tight"`` to fit the visible points, or ``"centered"`` to
            build a fixed-``size`` box centered on a computed centroid.
        size: Box size for ``mode="centered"``. A scalar yields a square box;
            a ``(w, h)`` tuple sets width and height independently. Required
            for ``mode="centered"``.
        padding: Amount to inflate the box outward. Scalar applies to both
            axes; a ``(px, py)`` tuple applies per-axis. Negative values
            shrink the box.
        node: Node specification passed to the centroid computation for
            ``mode="centered"`` with ``center_method="anchor"``.
        center_method: Centroid method used to locate the box center for
            ``mode="centered"`` (see :meth:`to_centroid`).
        rotated: For ``mode="tight"``, if ``True`` fit a minimum-area oriented
            box from the convex hull of visible points; otherwise fit an
            axis-aligned box.
        error_on_empty: If ``True``, raise ``ValueError`` when there are no
            visible points instead of returning a degenerate (NaN) box.

    Returns:
        A ``BoundingBox`` enclosing the instance (or NaN corners if empty).

    Raises:
        ValueError: For an unknown ``mode``, a missing ``size`` for
            ``mode="centered"``, or (when ``error_on_empty`` is ``True``)
            when there are no visible points.
    """
    from sleap_io.model.bbox import PredictedBoundingBox, UserBoundingBox
    from sleap_io.model.roi import (
        _apply_padding,
        _geometry_to_bbox_coords,
        _pose_to_geometry,
    )

    nan = float("nan")
    angle = 0.0

    if mode == "tight":
        pts = self.numpy(invisible_as_nan=True)
        visible = ~np.isnan(pts[:, 0])
        if not visible.any():
            if error_on_empty:
                raise ValueError("No visible points to compute bounding box.")
            x1 = y1 = x2 = y2 = nan
        elif rotated:
            hull = _pose_to_geometry(
                pts, self.skeleton.edge_inds, method="convex_hull"
            )
            x1, y1, x2, y2, angle = _geometry_to_bbox_coords(hull, rotated=True)
            x1, y1, x2, y2 = _apply_padding(x1, y1, x2, y2, padding)
        else:
            vis = pts[visible]
            x1 = float(vis[:, 0].min())
            y1 = float(vis[:, 1].min())
            x2 = float(vis[:, 0].max())
            y2 = float(vis[:, 1].max())
            x1, y1, x2, y2 = _apply_padding(x1, y1, x2, y2, padding)
    elif mode == "centered":
        if size is None:
            raise ValueError("'size' is required for mode='centered'.")
        centroid = self.to_centroid(
            method=center_method, node=node, error_on_empty=error_on_empty
        )
        if centroid.is_empty:
            x1 = y1 = x2 = y2 = nan
        else:
            cx, cy = centroid.xy
            if isinstance(size, (tuple, list)):
                w, h = size
            else:
                w = h = size
            x1 = cx - w / 2
            y1 = cy - h / 2
            x2 = cx + w / 2
            y2 = cy + h / 2
            x1, y1, x2, y2 = _apply_padding(x1, y1, x2, y2, padding)
    else:
        raise ValueError(f"Unknown mode {mode!r}. Expected 'tight' or 'centered'.")

    kwargs = dict(
        x1=x1,
        y1=y1,
        x2=x2,
        y2=y2,
        angle=angle,
        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,
        instance=self,
    )
    if isinstance(self, PredictedInstance):
        return PredictedBoundingBox(score=self.score, **kwargs)
    return UserBoundingBox(**kwargs)

to_centroid(method='center_of_mass', node=None, fallback=None, error_on_empty=False, **kwargs)

Create a Centroid from this instance.

Delegates to Centroid.from_pose(). A PredictedInstance yields a PredictedCentroid carrying its score; any other instance yields a UserCentroid. Metadata (track, tracking_score, identity, identity_score, identity_embedding, category, category_score, category_embedding, instance=self) is propagated.

Parameters:

Name Type Description Default
method str

Computation method ("center_of_mass", "bbox_center", "geometric_median", or "anchor").

'center_of_mass'
node int | str | None

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

None
fallback str | None

For the "anchor" method, a non-anchor method to fall back to when the anchor node is occluded.

None
error_on_empty bool

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

False
**kwargs

Additional keyword arguments passed to the centroid constructor.

required

Returns:

Type Description
Centroid

A UserCentroid or PredictedCentroid depending on the instance type.

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/instance.py
def to_centroid(
    self,
    method: str = "center_of_mass",
    node: int | str | None = None,
    fallback: str | None = None,
    error_on_empty: bool = False,
    **kwargs,
) -> "Centroid":
    """Create a ``Centroid`` from this instance.

    Delegates to ``Centroid.from_pose()``. A ``PredictedInstance`` yields a
    ``PredictedCentroid`` carrying its ``score``; any other instance yields a
    ``UserCentroid``. Metadata (``track``, ``tracking_score``, ``identity``,
    ``identity_score``, ``identity_embedding``, ``category``,
    ``category_score``, ``category_embedding``, ``instance=self``) is
    propagated.

    Args:
        method: Computation method (``"center_of_mass"``, ``"bbox_center"``,
            ``"geometric_median"``, or ``"anchor"``).
        node: Node specification for the ``"anchor"`` method. Can be a node
            name (str) or index (int).
        fallback: For the ``"anchor"`` method, a non-anchor method to fall
            back to when the anchor node is occluded.
        error_on_empty: If ``True``, raise ``ValueError`` when there are no
            visible points instead of returning a degenerate (NaN) centroid.
        **kwargs: Additional keyword arguments passed to the centroid
            constructor.

    Returns:
        A ``UserCentroid`` or ``PredictedCentroid`` depending on the
        instance type.

    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.centroid import Centroid

    return Centroid.from_pose(
        self,
        method=method,
        node=node,
        fallback=fallback,
        error_on_empty=error_on_empty,
        **kwargs,
    )

to_mask(height, width, **roi_kwargs)

Rasterize this instance's ROI geometry into a segmentation mask.

Equivalent to self.to_roi(**roi_kwargs).to_mask(height, width), except that a zero-area hull (method="convex_hull" over fewer than three visible points yields a Point or LineString) rasterizes to an all-background mask here instead of raising. A PredictedInstance yields a PredictedSegmentationMask carrying its score; any other instance yields a UserSegmentationMask. Metadata is propagated.

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
**roi_kwargs

Keyword arguments forwarded to :meth:to_roi (e.g. method, node_radius, edge_radius, radius, quad_segs, error_on_empty).

required

Returns:

Type Description
SegmentationMask

A SegmentationMask with the rasterized geometry (all background if the geometry is empty or has zero area).

Raises:

Type Description
ValueError

Propagated from :meth:to_roi for a "shapes" misconfiguration, an unknown method, or (when error_on_empty is True) an empty geometry.

Source code in sleap_io/model/instance.py
def to_mask(self, height: int, width: int, **roi_kwargs) -> "SegmentationMask":
    """Rasterize this instance's ROI geometry into a segmentation mask.

    Equivalent to ``self.to_roi(**roi_kwargs).to_mask(height, width)``,
    except that a zero-area hull (``method="convex_hull"`` over fewer than
    three visible points yields a ``Point`` or ``LineString``) rasterizes to
    an all-background mask here instead of raising. A ``PredictedInstance``
    yields a ``PredictedSegmentationMask`` carrying its ``score``; any other
    instance yields a ``UserSegmentationMask``. Metadata is propagated.

    Args:
        height: Height of the output mask in pixels.
        width: Width of the output mask in pixels.
        **roi_kwargs: Keyword arguments forwarded to :meth:`to_roi` (e.g.
            ``method``, ``node_radius``, ``edge_radius``, ``radius``,
            ``quad_segs``, ``error_on_empty``).

    Returns:
        A ``SegmentationMask`` with the rasterized geometry (all background
        if the geometry is empty or has zero area).

    Raises:
        ValueError: Propagated from :meth:`to_roi` for a ``"shapes"``
            misconfiguration, an unknown method, or (when
            ``error_on_empty`` is ``True``) an empty geometry.
    """
    from shapely.geometry import MultiPolygon, Polygon

    error_on_empty = roi_kwargs.pop("error_on_empty", False)
    roi = self.to_roi(error_on_empty=error_on_empty, **roi_kwargs)

    # A non-empty but non-fillable geometry (e.g. convex_hull of <3 visible
    # points -> Point/LineString) has zero area; rasterize it as all
    # background rather than letting _rasterize_geometry raise a TypeError.
    rasterizable = isinstance(roi.geometry, (Polygon, MultiPolygon))
    if roi.geometry.is_empty or not rasterizable:
        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,
            category=self.category,
            instance=self,
        )
        if isinstance(self, PredictedInstance):
            return PredictedSegmentationMask.from_numpy(
                empty, score=self.score, **kwargs
            )
        return UserSegmentationMask.from_numpy(empty, **kwargs)

    return roi.to_mask(height, width)

to_roi(method='shapes', node_radius=0.0, edge_radius=0.0, radius=0.0, quad_segs=8, error_on_empty=False)

Create a region-of-interest geometry from this instance.

A PredictedInstance yields a PredictedROI carrying its score; any other instance yields a UserROI. Metadata (track, tracking_score, identity, identity_score, identity_embedding, category, category_score, category_embedding, instance=self) is propagated.

Parameters:

Name Type Description Default
method str

"shapes" to union buffered node points and/or edge segments, or "convex_hull" to take the convex hull of the visible points.

'shapes'
node_radius float

Buffer radius around each visible node ("shapes" only).

0.0
edge_radius float

Buffer radius around each fully-visible edge segment ("shapes" only).

0.0
radius float

Optional buffer applied to the convex hull ("convex_hull" only).

0.0
quad_segs int

Number of segments used to approximate a quarter circle when buffering.

8
error_on_empty bool

If True, raise ValueError when the resulting geometry is empty instead of returning an empty-geometry ROI.

False

Returns:

Type Description
ROI

A ROI whose geometry encloses the instance (an empty Polygon if there are no visible points).

Raises:

Type Description
ValueError

If method="shapes" with both node_radius and edge_radius equal to 0 (a misconfiguration, always raised), for an unknown method, or (when error_on_empty is True) when the resulting geometry is empty.

Source code in sleap_io/model/instance.py
def to_roi(
    self,
    method: str = "shapes",
    node_radius: float = 0.0,
    edge_radius: float = 0.0,
    radius: float = 0.0,
    quad_segs: int = 8,
    error_on_empty: bool = False,
) -> "ROI":
    """Create a region-of-interest geometry from this instance.

    A ``PredictedInstance`` yields a ``PredictedROI`` carrying its ``score``;
    any other instance yields a ``UserROI``. Metadata (``track``,
    ``tracking_score``, ``identity``, ``identity_score``,
    ``identity_embedding``, ``category``, ``category_score``,
    ``category_embedding``, ``instance=self``) is propagated.

    Args:
        method: ``"shapes"`` to union buffered node points and/or edge
            segments, or ``"convex_hull"`` to take the convex hull of the
            visible points.
        node_radius: Buffer radius around each visible node (``"shapes"``
            only).
        edge_radius: Buffer radius around each fully-visible edge segment
            (``"shapes"`` only).
        radius: Optional buffer applied to the convex hull
            (``"convex_hull"`` only).
        quad_segs: Number of segments used to approximate a quarter circle
            when buffering.
        error_on_empty: If ``True``, raise ``ValueError`` when the resulting
            geometry is empty instead of returning an empty-geometry ROI.

    Returns:
        A ``ROI`` whose geometry encloses the instance (an empty ``Polygon``
        if there are no visible points).

    Raises:
        ValueError: If ``method="shapes"`` with both ``node_radius`` and
            ``edge_radius`` equal to 0 (a misconfiguration, always raised),
            for an unknown ``method``, or (when ``error_on_empty`` is
            ``True``) when the resulting geometry is empty.
    """
    from sleap_io.model.roi import PredictedROI, UserROI, _pose_to_geometry

    # Misconfiguration: raise before the empty-points check so that an empty
    # instance still surfaces the error.
    if method == "shapes" and node_radius == 0 and edge_radius == 0:
        raise ValueError(
            "method='shapes' requires at least one of node_radius or "
            "edge_radius to be > 0."
        )

    geom = _pose_to_geometry(
        self.numpy(invisible_as_nan=True),
        self.skeleton.edge_inds,
        method=method,
        node_radius=node_radius,
        edge_radius=edge_radius,
        radius=radius,
        quad_segs=quad_segs,
    )

    if geom.is_empty and error_on_empty:
        raise ValueError("No visible points to compute ROI geometry.")

    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,
        category=self.category,
        category_score=self.category_score,
        category_embedding=self.category_embedding,
        instance=self,
    )
    if isinstance(self, PredictedInstance):
        return PredictedROI(score=self.score, **kwargs)
    return UserROI(**kwargs)

update_skeleton(names_only=False)

Update or replace the skeleton associated with the instance.

Parameters:

Name Type Description Default
names_only bool

If True, only update the node names in the points array. If False, the points array will be updated to match the new skeleton.

False
Source code in sleap_io/model/instance.py
def update_skeleton(self, names_only: bool = False):
    """Update or replace the skeleton associated with the instance.

    Args:
        names_only: If `True`, only update the node names in the points array. If
            `False`, the points array will be updated to match the new skeleton.
    """
    if names_only:
        # Update the node names.
        self.points["name"] = self.skeleton.node_names
        return

    # Find correspondences.
    new_node_inds, old_node_inds = self.skeleton.match_nodes(self.points["name"])

    # Update the points.
    new_points = PointsArray.empty(len(self.skeleton))
    new_points[new_node_inds] = self.points[old_node_inds]
    new_points["name"] = self.skeleton.node_names
    self.points = new_points

Instance3D

A 3D pose instance with keypoints in world coordinates.

Stores triangulated (or otherwise derived) 3D keypoints. Always associated with an InstanceGroup that contains the source 2D instances.

Attributes:

Name Type Description
points

3D keypoint coordinates as (N, 3) float64 array. NaN values indicate missing/unresolved keypoints.

skeleton

The skeleton defining keypoint semantics.

score

Optional instance-level confidence score.

metadata

Arbitrary metadata dictionary.

Methods:

Name Description
__init__

Method generated by attrs for class Instance3D.

__repr__

Return a readable representation of the 3D instance.

__setattr__

Method generated by attrs for class Instance3D.

numpy

Return 3D points as (N, 3) float64 array.

Source code in sleap_io/model/instance.py
@attrs.define(eq=False)
class Instance3D:
    """A 3D pose instance with keypoints in world coordinates.

    Stores triangulated (or otherwise derived) 3D keypoints. Always associated
    with an InstanceGroup that contains the source 2D instances.

    Attributes:
        points: 3D keypoint coordinates as (N, 3) float64 array.
            NaN values indicate missing/unresolved keypoints.
        skeleton: The skeleton defining keypoint semantics.
        score: Optional instance-level confidence score.
        metadata: Arbitrary metadata dictionary.
    """

    points: np.ndarray = attrs.field(
        converter=lambda x: np.array(x, dtype="float64") if x is not None else None
    )
    skeleton: Skeleton = attrs.field()
    score: float | None = attrs.field(
        default=None, converter=attrs.converters.optional(float)
    )
    metadata: dict = attrs.field(
        factory=dict, validator=attrs.validators.instance_of(dict)
    )

    def __repr__(self) -> str:
        """Return a readable representation of the 3D instance."""
        n_valid = 0
        if self.points is not None:
            n_valid = int(np.sum(~np.isnan(self.points).any(axis=1)))
        n_total = len(self.skeleton.nodes)
        return f"Instance3D(n_points={n_valid}/{n_total})"

    @property
    def n_visible(self) -> int:
        """Number of non-NaN 3D keypoints."""
        if self.points is None:
            return 0
        return int(np.sum(~np.isnan(self.points).any(axis=1)))

    @property
    def is_empty(self) -> bool:
        """Whether all keypoints are NaN or points is None."""
        return self.n_visible == 0

    def numpy(self) -> np.ndarray:
        """Return 3D points as (N, 3) float64 array."""
        if self.points is None:
            return np.full((len(self.skeleton.nodes), 3), np.nan, dtype="float64")
        return self.points.copy()

__annotations__ = {'points': 'np.ndarray', 'skeleton': 'Skeleton', 'score': 'float | None', 'metadata': 'dict'} 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=False, 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 3D pose instance with keypoints in world coordinates.\n\nStores triangulated (or otherwise derived) 3D keypoints. Always associated\nwith an InstanceGroup that contains the source 2D instances.\n\nAttributes:\n points: 3D keypoint coordinates as (N, 3) float64 array.\n NaN values indicate missing/unresolved keypoints.\n skeleton: The skeleton defining keypoint semantics.\n score: Optional instance-level confidence score.\n metadata: Arbitrary metadata dictionary.\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__ = 1497 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__ = ('points', 'skeleton', 'score', 'metadata') 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.instance' 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__ = ('points', 'skeleton', 'score', 'metadata', '__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 all keypoints are NaN or points is None.

n_visible property

Number of non-NaN 3D keypoints.

__init__(points, skeleton, score=None, metadata=NOTHING)

Method generated by attrs for class Instance3D.

Source code in sleap_io/model/instance.py
"""Data structures for data associated with a single instance such as an animal.

The `Instance` class is a SLEAP data structure that contains a collection of points that
correspond to landmarks within a `Skeleton`.

`PredictedInstance` additionally contains metadata associated with how the instance was
estimated, such as confidence scores.
"""

from __future__ import annotations

__repr__()

Return a readable representation of the 3D instance.

Source code in sleap_io/model/instance.py
def __repr__(self) -> str:
    """Return a readable representation of the 3D instance."""
    n_valid = 0
    if self.points is not None:
        n_valid = int(np.sum(~np.isnan(self.points).any(axis=1)))
    n_total = len(self.skeleton.nodes)
    return f"Instance3D(n_points={n_valid}/{n_total})"

__setattr__(name, val)

Method generated by attrs for class Instance3D.

Source code in sleap_io/model/instance.py
if np.any(intersection_min >= intersection_max):
    # No intersection
    return False

intersection_area = np.prod(intersection_max - intersection_min)

# Calculate union
self_area = np.prod(self_bbox[1] - self_bbox[0])
other_area = np.prod(other_bbox[1] - other_bbox[0])

numpy()

Return 3D points as (N, 3) float64 array.

Source code in sleap_io/model/instance.py
def numpy(self) -> np.ndarray:
    """Return 3D points as (N, 3) float64 array."""
    if self.points is None:
        return np.full((len(self.skeleton.nodes), 3), np.nan, dtype="float64")
    return self.points.copy()

Node

A landmark type within a Skeleton.

This typically corresponds to a unique landmark within a skeleton, such as the "left eye".

Attributes:

Name Type Description
name

Descriptive label for the landmark.

Methods:

Name Description
__init__

Method generated by attrs for class Node.

__repr__

Method generated by attrs for class Node.

Source code in sleap_io/model/skeleton.py
@define(eq=False)
class Node:
    """A landmark type within a `Skeleton`.

    This typically corresponds to a unique landmark within a skeleton, such as the "left
    eye".

    Attributes:
        name: Descriptive label for the landmark.
    """

    name: str

__annotations__ = {'name': 'str'} 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__ = False 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 landmark type within a `Skeleton`.\n\nThis typically corresponds to a unique landmark within a skeleton, such as the "left\neye".\n\nAttributes:\n name: Descriptive label for the landmark.\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__ = 18 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__ = ('name',) 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.skeleton' 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__ = ('name', '__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

__init__(name)

Method generated by attrs for class Node.

Source code in sleap_io/model/skeleton.py

__repr__()

Method generated by attrs for class Node.

Source code in sleap_io/model/skeleton.py
"""Data model for skeletons.

Skeletons are collections of nodes and edges which describe the landmarks associated
with a pose model. The edges represent the connections between them and may be used
differently depending on the underlying pose model.
"""

from __future__ import annotations

import re
import typing
from functools import lru_cache

import numpy as np
from attrs import define, field

PointsArray

Bases: numpy.ndarray

A specialized array for storing instance points data.

This class ensures that the array always uses the correct dtype and provides convenience methods for working with point data.

The structured dtype includes the following fields
  • xy: A float64 array of shape (2,) containing the x, y coordinates
  • visible: A boolean indicating if the point is visible
  • complete: A boolean indicating if the point is complete
  • name: An object dtype containing the name of the node

Methods:

Name Description
empty

Create an empty points array with the appropriate dtype.

from_array

Convert an existing array to a PointsArray with the appropriate dtype.

from_dict

Create a PointsArray from a dictionary of node points.

Attributes:

Name Type Description
__dict__

Read-only proxy of a mapping.

__doc__

str(object='') -> str

__firstlineno__

int([x]) -> integer

__module__

str(object='') -> str

__static_attributes__

Built-in immutable sequence.

Source code in sleap_io/model/instance.py
class PointsArray(np.ndarray):
    """A specialized array for storing instance points data.

    This class ensures that the array always uses the correct dtype and provides
    convenience methods for working with point data.

    The structured dtype includes the following fields:
        - xy: A float64 array of shape (2,) containing the x, y coordinates
        - visible: A boolean indicating if the point is visible
        - complete: A boolean indicating if the point is complete
        - name: An object dtype containing the name of the node
    """

    @classmethod
    def _get_dtype(cls):
        """Get the dtype for points array.

        Returns:
            np.dtype: A structured numpy dtype with fields for xy coordinates,
                visible flag, complete flag, and node names.
        """
        # Cache the dtype at the class level for performance
        # Use cls.__dict__ to check if defined on this class (not inherited)
        if "_cached_dtype" not in cls.__dict__:
            cls._cached_dtype = np.dtype(
                [
                    ("xy", "<f8", (2,)),  # 64-bit (8-byte) little-endian double, ndim=2
                    ("visible", "bool"),
                    ("complete", "bool"),
                    (
                        "name",
                        "O",
                    ),  # object dtype to store pointers to python string objects
                ]
            )
        return cls._cached_dtype

    @classmethod
    def empty(cls, length: int) -> "PointsArray":
        """Create an empty points array with the appropriate dtype.

        Args:
            length: The number of points (nodes) to allocate in the array.

        Returns:
            PointsArray: An empty array of the specified length with the appropriate
                dtype.
        """
        dtype = cls._get_dtype()
        arr = np.empty(length, dtype=dtype).view(cls)
        return arr

    @classmethod
    def from_array(cls, array: np.ndarray) -> "PointsArray":
        """Convert an existing array to a PointsArray with the appropriate dtype.

        Args:
            array: A numpy array to convert. Can be a structured array or a regular
                array. If a regular array, it is assumed to have columns for x, y
                coordinates and optionally visible and complete flags.

        Returns:
            PointsArray: A structured array view of the input data with the appropriate
                dtype.

        Notes:
            If the input is a structured array with fields matching the target dtype,
            those fields will be copied. Otherwise, a best-effort conversion is made:

            - First two columns (or first 2D element) are interpreted as x, y coords
            - Third column (if present) is interpreted as visible flag
            - Fourth column (if present) is interpreted as complete flag

            If visibility is not provided, it is inferred from NaN values in the x
            coordinate.
        """
        dtype = cls._get_dtype()

        # If already the right type, just view as PointsArray
        if isinstance(array, np.ndarray) and array.dtype == dtype:
            return array.view(cls)

        # Otherwise, create a new array with the right dtype
        new_array = np.empty(len(array), dtype=dtype).view(cls)

        # Copy available fields
        if isinstance(array, np.ndarray) and array.dtype.fields is not None:
            # Structured array, copy matching fields
            for field_name in dtype.names:
                if field_name in array.dtype.names:
                    new_array[field_name] = array[field_name]
        elif isinstance(array, np.ndarray):
            # Regular array, assume x, y coordinates
            new_array["xy"] = array[:, 0:2]

            # Default visibility based on NaN
            new_array["visible"] = ~np.isnan(array[:, 0])

            # If there are more columns, assume they are visible and complete
            if array.shape[1] >= 3:
                new_array["visible"] = array[:, 2].astype(bool)

            if array.shape[1] >= 4:
                new_array["complete"] = array[:, 3].astype(bool)

        return new_array

    @classmethod
    def from_dict(cls, points_dict: dict, skeleton: Skeleton) -> "PointsArray":
        """Create a PointsArray from a dictionary of node points.

        Args:
            points_dict: A dictionary mapping nodes (as Node objects, indices, or
                strings) to point data. Each point should be an array-like with at least
                2 elements for x, y coordinates, and optionally visible and complete
                flags.
            skeleton: The Skeleton object that defines the nodes.

        Returns:
            PointsArray: A structured array with the appropriate dtype containing the
                point data from the dictionary.

        Notes:
            For each entry in the points_dict:
            - First two values are treated as x, y coordinates
            - Third value (if present) is treated as visible flag
            - Fourth value (if present) is treated as complete flag

            If visibility is not provided, it is inferred from NaN values in the x
            coordinate.
        """
        points = cls.empty(len(skeleton))

        for node, data in points_dict.items():
            if isinstance(node, (Node, str)):
                node = skeleton.index(node)

            points[node]["xy"] = data[:2]

            idx = 2
            if len(data) > idx:
                points[node]["visible"] = data[idx]
            else:
                points[node]["visible"] = ~np.isnan(data[0])

            idx += 1
            if len(data) > idx:
                points[node]["complete"] = data[idx]

        return points

__dict__ = mappingproxy({'__module__': 'sleap_io.model.instance', '__firstlineno__': 29, '__doc__': 'A specialized array for storing instance points data.\n\nThis class ensures that the array always uses the correct dtype and provides\nconvenience methods for working with point data.\n\nThe structured dtype includes the following fields:\n - xy: A float64 array of shape (2,) containing the x, y coordinates\n - visible: A boolean indicating if the point is visible\n - complete: A boolean indicating if the point is complete\n - name: An object dtype containing the name of the node\n', '_get_dtype': <classmethod(<function PointsArray._get_dtype at 0x7f0842d13c40>)>, 'empty': <classmethod(<function PointsArray.empty at 0x7f0842d73ce0>)>, 'from_array': <classmethod(<function PointsArray.from_array at 0x7f0842d73d80>)>, 'from_dict': <classmethod(<function PointsArray.from_dict at 0x7f0842d73e20>)>, '__static_attributes__': (), '__dict__': <attribute '__dict__' of 'PointsArray' objects>}) class-attribute

Read-only proxy of a mapping.

__doc__ = 'A specialized array for storing instance points data.\n\nThis class ensures that the array always uses the correct dtype and provides\nconvenience methods for working with point data.\n\nThe structured dtype includes the following fields:\n - xy: A float64 array of shape (2,) containing the x, y coordinates\n - visible: A boolean indicating if the point is visible\n - complete: A boolean indicating if the point is complete\n - name: An object dtype containing the name of the node\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__ = 29 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

__module__ = 'sleap_io.model.instance' 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'.

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

empty(length) classmethod

Create an empty points array with the appropriate dtype.

Parameters:

Name Type Description Default
length int

The number of points (nodes) to allocate in the array.

required

Returns:

Name Type Description
PointsArray PointsArray

An empty array of the specified length with the appropriate dtype.

Source code in sleap_io/model/instance.py
@classmethod
def empty(cls, length: int) -> "PointsArray":
    """Create an empty points array with the appropriate dtype.

    Args:
        length: The number of points (nodes) to allocate in the array.

    Returns:
        PointsArray: An empty array of the specified length with the appropriate
            dtype.
    """
    dtype = cls._get_dtype()
    arr = np.empty(length, dtype=dtype).view(cls)
    return arr

from_array(array) classmethod

Convert an existing array to a PointsArray with the appropriate dtype.

Parameters:

Name Type Description Default
array ndarray

A numpy array to convert. Can be a structured array or a regular array. If a regular array, it is assumed to have columns for x, y coordinates and optionally visible and complete flags.

required

Returns:

Name Type Description
PointsArray PointsArray

A structured array view of the input data with the appropriate dtype.

Notes

If the input is a structured array with fields matching the target dtype, those fields will be copied. Otherwise, a best-effort conversion is made:

  • First two columns (or first 2D element) are interpreted as x, y coords
  • Third column (if present) is interpreted as visible flag
  • Fourth column (if present) is interpreted as complete flag

If visibility is not provided, it is inferred from NaN values in the x coordinate.

Source code in sleap_io/model/instance.py
@classmethod
def from_array(cls, array: np.ndarray) -> "PointsArray":
    """Convert an existing array to a PointsArray with the appropriate dtype.

    Args:
        array: A numpy array to convert. Can be a structured array or a regular
            array. If a regular array, it is assumed to have columns for x, y
            coordinates and optionally visible and complete flags.

    Returns:
        PointsArray: A structured array view of the input data with the appropriate
            dtype.

    Notes:
        If the input is a structured array with fields matching the target dtype,
        those fields will be copied. Otherwise, a best-effort conversion is made:

        - First two columns (or first 2D element) are interpreted as x, y coords
        - Third column (if present) is interpreted as visible flag
        - Fourth column (if present) is interpreted as complete flag

        If visibility is not provided, it is inferred from NaN values in the x
        coordinate.
    """
    dtype = cls._get_dtype()

    # If already the right type, just view as PointsArray
    if isinstance(array, np.ndarray) and array.dtype == dtype:
        return array.view(cls)

    # Otherwise, create a new array with the right dtype
    new_array = np.empty(len(array), dtype=dtype).view(cls)

    # Copy available fields
    if isinstance(array, np.ndarray) and array.dtype.fields is not None:
        # Structured array, copy matching fields
        for field_name in dtype.names:
            if field_name in array.dtype.names:
                new_array[field_name] = array[field_name]
    elif isinstance(array, np.ndarray):
        # Regular array, assume x, y coordinates
        new_array["xy"] = array[:, 0:2]

        # Default visibility based on NaN
        new_array["visible"] = ~np.isnan(array[:, 0])

        # If there are more columns, assume they are visible and complete
        if array.shape[1] >= 3:
            new_array["visible"] = array[:, 2].astype(bool)

        if array.shape[1] >= 4:
            new_array["complete"] = array[:, 3].astype(bool)

    return new_array

from_dict(points_dict, skeleton) classmethod

Create a PointsArray from a dictionary of node points.

Parameters:

Name Type Description Default
points_dict dict

A dictionary mapping nodes (as Node objects, indices, or strings) to point data. Each point should be an array-like with at least 2 elements for x, y coordinates, and optionally visible and complete flags.

required
skeleton Skeleton

The Skeleton object that defines the nodes.

required

Returns:

Name Type Description
PointsArray PointsArray

A structured array with the appropriate dtype containing the point data from the dictionary.

Notes

For each entry in the points_dict: - First two values are treated as x, y coordinates - Third value (if present) is treated as visible flag - Fourth value (if present) is treated as complete flag

If visibility is not provided, it is inferred from NaN values in the x coordinate.

Source code in sleap_io/model/instance.py
@classmethod
def from_dict(cls, points_dict: dict, skeleton: Skeleton) -> "PointsArray":
    """Create a PointsArray from a dictionary of node points.

    Args:
        points_dict: A dictionary mapping nodes (as Node objects, indices, or
            strings) to point data. Each point should be an array-like with at least
            2 elements for x, y coordinates, and optionally visible and complete
            flags.
        skeleton: The Skeleton object that defines the nodes.

    Returns:
        PointsArray: A structured array with the appropriate dtype containing the
            point data from the dictionary.

    Notes:
        For each entry in the points_dict:
        - First two values are treated as x, y coordinates
        - Third value (if present) is treated as visible flag
        - Fourth value (if present) is treated as complete flag

        If visibility is not provided, it is inferred from NaN values in the x
        coordinate.
    """
    points = cls.empty(len(skeleton))

    for node, data in points_dict.items():
        if isinstance(node, (Node, str)):
            node = skeleton.index(node)

        points[node]["xy"] = data[:2]

        idx = 2
        if len(data) > idx:
            points[node]["visible"] = data[idx]
        else:
            points[node]["visible"] = ~np.isnan(data[0])

        idx += 1
        if len(data) > idx:
            points[node]["complete"] = data[idx]

    return points

PredictedInstance

Bases: sleap_io.model.instance.Instance

A PredictedInstance is an Instance that was predicted using a model.

Attributes:

Name Type Description
skeleton

The Skeleton that this Instance is associated with.

points

A dictionary where keys are Skeleton nodes and values are Points.

track

An optional Track associated with a unique animal/object across frames or videos.

from_predicted

Not applicable in PredictedInstances (must be set to None).

score

The instance detection or part grouping prediction score. This is a scalar that represents the confidence with which this entire instance was predicted. This may not always be applicable depending on the model type.

tracking_score

The score associated with the Track assignment. This is typically the value from the score matrix used in an identity assignment.

identity

An optional global Identity (see Instance.identity).

identity_score

The score associated with the identity assignment (see Instance.identity_score).

identity_embedding

An optional re-ID Embedding (see Instance.identity_embedding).

category

An optional Category (class) (see Instance.category).

category_score

The score associated with the category assignment (see Instance.category_score).

category_embedding

An optional classification Embedding (see Instance.category_embedding).

Methods:

Name Description
__getitem__

Return the point associated with a node.

__init__

Method generated by attrs for class PredictedInstance.

__repr__

Return a readable representation of the instance.

__setattr__

Method generated by attrs for class PredictedInstance.

__setitem__

Set the point associated with a node.

empty

Create an empty instance with no points.

from_numpy

Create a predicted instance object from a numpy array.

numpy

Return the instance points as a (n_nodes, 2) numpy array.

replace_skeleton

Replace the skeleton associated with the instance.

update_skeleton

Update or replace the skeleton associated with the instance.

Source code in sleap_io/model/instance.py
@attrs.define(eq=False)
class PredictedInstance(Instance):
    """A `PredictedInstance` is an `Instance` that was predicted using a model.

    Attributes:
        skeleton: The `Skeleton` that this `Instance` is associated with.
        points: A dictionary where keys are `Skeleton` nodes and values are `Point`s.
        track: An optional `Track` associated with a unique animal/object across frames
            or videos.
        from_predicted: Not applicable in `PredictedInstance`s (must be set to `None`).
        score: The instance detection or part grouping prediction score. This is a
            scalar that represents the confidence with which this entire instance was
            predicted. This may not always be applicable depending on the model type.
        tracking_score: The score associated with the `Track` assignment. This is
            typically the value from the score matrix used in an identity assignment.
        identity: An optional global `Identity` (see `Instance.identity`).
        identity_score: The score associated with the `identity` assignment (see
            `Instance.identity_score`).
        identity_embedding: An optional re-ID `Embedding` (see
            `Instance.identity_embedding`).
        category: An optional `Category` (class) (see `Instance.category`).
        category_score: The score associated with the `category` assignment (see
            `Instance.category_score`).
        category_embedding: An optional classification `Embedding` (see
            `Instance.category_embedding`).
    """

    points: PredictedPointsArray = attrs.field(eq=attrs.cmp_using(eq=np.array_equal))
    skeleton: Skeleton
    score: float = 0.0
    track: Track | None = None
    tracking_score: float | None = 0
    identity: Identity | None = None
    identity_score: float | None = None
    category: Category | None = attrs.field(default=None, converter=to_category)
    category_score: float | None = None
    from_predicted: "PredictedInstance | None" = None
    identity_embedding: Embedding | None = attrs.field(default=None, repr=False)
    category_embedding: Embedding | None = attrs.field(default=None, repr=False)

    def __repr__(self) -> str:
        """Return a readable representation of the instance."""
        pts = self.numpy().tolist()
        track = f'"{self.track.name}"' if self.track is not None else self.track

        score = str(self.score) if self.score is None else f"{self.score:.2f}"
        tracking_score = (
            str(self.tracking_score)
            if self.tracking_score is None
            else f"{self.tracking_score:.2f}"
        )
        return (
            f"PredictedInstance(points={pts}, track={track}, "
            f"score={score}, tracking_score={tracking_score})"
        )

    @classmethod
    def empty(
        cls,
        skeleton: Skeleton,
        score: float = 0.0,
        track: Track | None = None,
        tracking_score: float | None = None,
        identity: Identity | None = None,
        identity_score: float | None = None,
        category: Category | None = None,
        category_score: float | None = None,
        identity_embedding: Embedding | None = None,
        category_embedding: Embedding | None = None,
        from_predicted: "PredictedInstance | None" = None,
    ) -> "PredictedInstance":
        """Create an empty instance with no points."""
        points = PredictedPointsArray.empty(len(skeleton))
        points["name"] = skeleton.node_names

        return cls(
            points=points,
            skeleton=skeleton,
            score=score,
            track=track,
            tracking_score=tracking_score,
            identity=identity,
            identity_score=identity_score,
            category=category,
            category_score=category_score,
            identity_embedding=identity_embedding,
            category_embedding=category_embedding,
            from_predicted=from_predicted,
        )

    @classmethod
    def _convert_points(
        cls, points_data: np.ndarray | dict | list, skeleton: Skeleton
    ) -> PredictedPointsArray:
        """Convert points to a structured numpy array if needed."""
        if isinstance(points_data, dict):
            return PredictedPointsArray.from_dict(points_data, skeleton)
        elif isinstance(points_data, (list, np.ndarray)):
            if isinstance(points_data, list):
                points_data = np.array(points_data)

            points = PredictedPointsArray.from_array(points_data)
            points["name"] = skeleton.node_names
            return points
        else:
            raise ValueError("points must be a numpy array or dictionary.")

    @classmethod
    def from_numpy(
        cls,
        points_data: np.ndarray,
        skeleton: Skeleton,
        point_scores: np.ndarray | None = None,
        score: float = 0.0,
        track: Track | None = None,
        tracking_score: float | None = None,
        identity: Identity | None = None,
        identity_score: float | None = None,
        category: Category | None = None,
        category_score: float | None = None,
        identity_embedding: Embedding | None = None,
        category_embedding: Embedding | None = None,
        from_predicted: "PredictedInstance | None" = None,
    ) -> "PredictedInstance":
        """Create a predicted instance object from a numpy array."""
        points = cls._convert_points(points_data, skeleton)
        if point_scores is not None:
            points["score"] = point_scores

        return cls(
            points=points,
            skeleton=skeleton,
            score=score,
            track=track,
            tracking_score=tracking_score,
            identity=identity,
            identity_score=identity_score,
            category=category,
            category_score=category_score,
            identity_embedding=identity_embedding,
            category_embedding=category_embedding,
            from_predicted=from_predicted,
        )

    def numpy(
        self,
        invisible_as_nan: bool = True,
        scores: bool = False,
    ) -> np.ndarray:
        """Return the instance points as a `(n_nodes, 2)` numpy array.

        Args:
            invisible_as_nan: If `True` (the default), points that are not visible will
                be set to `np.nan`. If `False`, they will be whatever the stored value
                of `PredictedInstance.points["xy"]` is.
            scores: If `True`, the score associated with each point will be
                included in the output.

        Returns:
            A numpy array of shape `(n_nodes, 2)` corresponding to the points of the
            skeleton. Values of `np.nan` indicate "missing" nodes.

            If `scores` is `True`, the array will have shape `(n_nodes, 3)` with the
            third column containing the score associated with each point.

        Notes:
            This will always return a copy of the array.

            If you need to avoid making a copy, just access the
            `PredictedInstance.points["xy"]` attribute directly. This will not replace
            invisible points with `np.nan`.
        """
        if invisible_as_nan:
            pts = np.where(
                self.points["visible"].reshape(-1, 1), self.points["xy"], np.nan
            )
        else:
            pts = self.points["xy"].copy()

        if scores:
            return np.column_stack((pts, self.points["score"]))
        else:
            return pts

    def update_skeleton(self, names_only: bool = False):
        """Update or replace the skeleton associated with the instance.

        Args:
            names_only: If `True`, only update the node names in the points array. If
                `False`, the points array will be updated to match the new skeleton.
        """
        if names_only:
            # Update the node names.
            self.points["name"] = self.skeleton.node_names
            return

        # Find correspondences.
        new_node_inds, old_node_inds = self.skeleton.match_nodes(self.points["name"])

        # Update the points.
        new_points = PredictedPointsArray.empty(len(self.skeleton))
        new_points[new_node_inds] = self.points[old_node_inds]
        new_points["name"] = self.skeleton.node_names
        self.points = new_points

    def replace_skeleton(
        self,
        new_skeleton: Skeleton,
        node_names_map: dict[str, str] | None = None,
    ):
        """Replace the skeleton associated with the instance.

        Args:
            new_skeleton: The new `Skeleton` to associate with the instance.
            node_names_map: Dictionary mapping nodes in the old skeleton to nodes in the
                new skeleton. Keys and values should be specified as lists of strings.
                If not provided, only nodes with identical names will be mapped. Points
                associated with unmapped nodes will be removed.

        Notes:
            This method will update the `PredictedInstance.skeleton` attribute and the
            `PredictedInstance.points` attribute in place (a copy is made of the points
            array).

            It is recommended to use `Labels.replace_skeleton` instead of this method if
            more flexible node mapping is required.
        """
        # Update skeleton object.
        self.skeleton = new_skeleton

        # Get node names with replacements from node map if possible.
        old_node_names = self.points["name"].tolist()
        if node_names_map is not None:
            old_node_names = [node_names_map.get(node, node) for node in old_node_names]

        # Find correspondences.
        new_node_inds, old_node_inds = self.skeleton.match_nodes(old_node_names)

        # Update the points.
        new_points = PredictedPointsArray.empty(len(self.skeleton))
        new_points[new_node_inds] = self.points[old_node_inds]
        self.points = new_points
        self.points["name"] = self.skeleton.node_names

    def __getitem__(self, node: int | str | Node) -> np.ndarray:
        """Return the point associated with a node."""
        # Inherit from Instance.__getitem__
        return super().__getitem__(node)

    def __setitem__(self, node: int | str | Node, value):
        """Set the point associated with a node.

        Args:
            node: The node to set the point for. Can be an integer index, string name,
                or Node object.
            value: A tuple or array-like of length 2 or 3 containing (x, y) coordinates
                and optionally a confidence score. If the score is not provided, it
                defaults to 1.0.

        Notes:
            This sets the point coordinates, score, and marks the point as visible.
        """
        if type(node) is not int:
            node = self.skeleton.index(node)

        if len(value) < 2:
            raise ValueError("Value must have at least 2 elements (x, y)")

        self.points[node]["xy"] = value[:2]

        # Set score if provided, otherwise default to 1.0
        if len(value) >= 3:
            self.points[node]["score"] = value[2]
        else:
            self.points[node]["score"] = 1.0

        self.points[node]["visible"] = True

__annotations__ = {'points': 'PredictedPointsArray', 'skeleton': 'Skeleton', 'score': 'float', 'track': 'Track | None', 'tracking_score': 'float | None', 'identity': 'Identity | None', 'identity_score': 'float | None', 'category': 'Category | None', 'category_score': 'float | None', 'from_predicted': "'PredictedInstance | None'", 'identity_embedding': 'Embedding | None', 'category_embedding': 'Embedding | None'} 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=False, 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 `PredictedInstance` is an `Instance` that was predicted using a model.\n\nAttributes:\n skeleton: The `Skeleton` that this `Instance` is associated with.\n points: A dictionary where keys are `Skeleton` nodes and values are `Point`s.\n track: An optional `Track` associated with a unique animal/object across frames\n or videos.\n from_predicted: Not applicable in `PredictedInstance`s (must be set to `None`).\n score: The instance detection or part grouping prediction score. This is a\n scalar that represents the confidence with which this entire instance was\n predicted. This may not always be applicable depending on the model type.\n tracking_score: The score associated with the `Track` assignment. This is\n typically the value from the score matrix used in an identity assignment.\n identity: An optional global `Identity` (see `Instance.identity`).\n identity_score: The score associated with the `identity` assignment (see\n `Instance.identity_score`).\n identity_embedding: An optional re-ID `Embedding` (see\n `Instance.identity_embedding`).\n category: An optional `Category` (class) (see `Instance.category`).\n category_score: The score associated with the `category` assignment (see\n `Instance.category_score`).\n category_embedding: An optional classification `Embedding` (see\n `Instance.category_embedding`).\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__ = 1218 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__ = ('points', 'skeleton', 'score', 'track', 'tracking_score', 'identity', 'identity_score', 'category', 'category_score', 'from_predicted', 'identity_embedding', '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.instance' 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__ = ('points', 'skeleton') 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.

__getitem__(node)

Return the point associated with a node.

Source code in sleap_io/model/instance.py
def __getitem__(self, node: int | str | Node) -> np.ndarray:
    """Return the point associated with a node."""
    # Inherit from Instance.__getitem__
    return super().__getitem__(node)

__init__(points, skeleton, score=0.0, track=None, tracking_score=0, identity=None, identity_score=None, category=None, category_score=None, from_predicted=None, identity_embedding=None, category_embedding=None)

Method generated by attrs for class PredictedInstance.

Source code in sleap_io/model/instance.py
"""Data structures for data associated with a single instance such as an animal.

The `Instance` class is a SLEAP data structure that contains a collection of points that
correspond to landmarks within a `Skeleton`.

`PredictedInstance` additionally contains metadata associated with how the instance was
estimated, such as confidence scores.
"""

from __future__ import annotations

from typing import TYPE_CHECKING

import attrs
import numpy as np

__repr__()

Return a readable representation of the instance.

Source code in sleap_io/model/instance.py
def __repr__(self) -> str:
    """Return a readable representation of the instance."""
    pts = self.numpy().tolist()
    track = f'"{self.track.name}"' if self.track is not None else self.track

    score = str(self.score) if self.score is None else f"{self.score:.2f}"
    tracking_score = (
        str(self.tracking_score)
        if self.tracking_score is None
        else f"{self.tracking_score:.2f}"
    )
    return (
        f"PredictedInstance(points={pts}, track={track}, "
        f"score={score}, tracking_score={tracking_score})"
    )

__setattr__(name, val)

Method generated by attrs for class PredictedInstance.

Source code in sleap_io/model/instance.py
if np.any(intersection_min >= intersection_max):
    # No intersection
    return False

intersection_area = np.prod(intersection_max - intersection_min)

# Calculate union
self_area = np.prod(self_bbox[1] - self_bbox[0])
other_area = np.prod(other_bbox[1] - other_bbox[0])

__setitem__(node, value)

Set the point associated with a node.

Parameters:

Name Type Description Default
node int | str | Node

The node to set the point for. Can be an integer index, string name, or Node object.

required
value

A tuple or array-like of length 2 or 3 containing (x, y) coordinates and optionally a confidence score. If the score is not provided, it defaults to 1.0.

required
Notes

This sets the point coordinates, score, and marks the point as visible.

Source code in sleap_io/model/instance.py
def __setitem__(self, node: int | str | Node, value):
    """Set the point associated with a node.

    Args:
        node: The node to set the point for. Can be an integer index, string name,
            or Node object.
        value: A tuple or array-like of length 2 or 3 containing (x, y) coordinates
            and optionally a confidence score. If the score is not provided, it
            defaults to 1.0.

    Notes:
        This sets the point coordinates, score, and marks the point as visible.
    """
    if type(node) is not int:
        node = self.skeleton.index(node)

    if len(value) < 2:
        raise ValueError("Value must have at least 2 elements (x, y)")

    self.points[node]["xy"] = value[:2]

    # Set score if provided, otherwise default to 1.0
    if len(value) >= 3:
        self.points[node]["score"] = value[2]
    else:
        self.points[node]["score"] = 1.0

    self.points[node]["visible"] = True

empty(skeleton, score=0.0, track=None, tracking_score=None, identity=None, identity_score=None, category=None, category_score=None, identity_embedding=None, category_embedding=None, from_predicted=None) classmethod

Create an empty instance with no points.

Source code in sleap_io/model/instance.py
@classmethod
def empty(
    cls,
    skeleton: Skeleton,
    score: float = 0.0,
    track: Track | None = None,
    tracking_score: float | None = None,
    identity: Identity | None = None,
    identity_score: float | None = None,
    category: Category | None = None,
    category_score: float | None = None,
    identity_embedding: Embedding | None = None,
    category_embedding: Embedding | None = None,
    from_predicted: "PredictedInstance | None" = None,
) -> "PredictedInstance":
    """Create an empty instance with no points."""
    points = PredictedPointsArray.empty(len(skeleton))
    points["name"] = skeleton.node_names

    return cls(
        points=points,
        skeleton=skeleton,
        score=score,
        track=track,
        tracking_score=tracking_score,
        identity=identity,
        identity_score=identity_score,
        category=category,
        category_score=category_score,
        identity_embedding=identity_embedding,
        category_embedding=category_embedding,
        from_predicted=from_predicted,
    )

from_numpy(points_data, skeleton, point_scores=None, score=0.0, track=None, tracking_score=None, identity=None, identity_score=None, category=None, category_score=None, identity_embedding=None, category_embedding=None, from_predicted=None) classmethod

Create a predicted instance object from a numpy array.

Source code in sleap_io/model/instance.py
@classmethod
def from_numpy(
    cls,
    points_data: np.ndarray,
    skeleton: Skeleton,
    point_scores: np.ndarray | None = None,
    score: float = 0.0,
    track: Track | None = None,
    tracking_score: float | None = None,
    identity: Identity | None = None,
    identity_score: float | None = None,
    category: Category | None = None,
    category_score: float | None = None,
    identity_embedding: Embedding | None = None,
    category_embedding: Embedding | None = None,
    from_predicted: "PredictedInstance | None" = None,
) -> "PredictedInstance":
    """Create a predicted instance object from a numpy array."""
    points = cls._convert_points(points_data, skeleton)
    if point_scores is not None:
        points["score"] = point_scores

    return cls(
        points=points,
        skeleton=skeleton,
        score=score,
        track=track,
        tracking_score=tracking_score,
        identity=identity,
        identity_score=identity_score,
        category=category,
        category_score=category_score,
        identity_embedding=identity_embedding,
        category_embedding=category_embedding,
        from_predicted=from_predicted,
    )

numpy(invisible_as_nan=True, scores=False)

Return the instance points as a (n_nodes, 2) numpy array.

Parameters:

Name Type Description Default
invisible_as_nan bool

If True (the default), points that are not visible will be set to np.nan. If False, they will be whatever the stored value of PredictedInstance.points["xy"] is.

True
scores bool

If True, the score associated with each point will be included in the output.

False

Returns:

Type Description
ndarray

A numpy array of shape (n_nodes, 2) corresponding to the points of the skeleton. Values of np.nan indicate "missing" nodes.

If scores is True, the array will have shape (n_nodes, 3) with the third column containing the score associated with each point.

Notes

This will always return a copy of the array.

If you need to avoid making a copy, just access the PredictedInstance.points["xy"] attribute directly. This will not replace invisible points with np.nan.

Source code in sleap_io/model/instance.py
def numpy(
    self,
    invisible_as_nan: bool = True,
    scores: bool = False,
) -> np.ndarray:
    """Return the instance points as a `(n_nodes, 2)` numpy array.

    Args:
        invisible_as_nan: If `True` (the default), points that are not visible will
            be set to `np.nan`. If `False`, they will be whatever the stored value
            of `PredictedInstance.points["xy"]` is.
        scores: If `True`, the score associated with each point will be
            included in the output.

    Returns:
        A numpy array of shape `(n_nodes, 2)` corresponding to the points of the
        skeleton. Values of `np.nan` indicate "missing" nodes.

        If `scores` is `True`, the array will have shape `(n_nodes, 3)` with the
        third column containing the score associated with each point.

    Notes:
        This will always return a copy of the array.

        If you need to avoid making a copy, just access the
        `PredictedInstance.points["xy"]` attribute directly. This will not replace
        invisible points with `np.nan`.
    """
    if invisible_as_nan:
        pts = np.where(
            self.points["visible"].reshape(-1, 1), self.points["xy"], np.nan
        )
    else:
        pts = self.points["xy"].copy()

    if scores:
        return np.column_stack((pts, self.points["score"]))
    else:
        return pts

replace_skeleton(new_skeleton, node_names_map=None)

Replace the skeleton associated with the instance.

Parameters:

Name Type Description Default
new_skeleton Skeleton

The new Skeleton to associate with the instance.

required
node_names_map dict[str, str] | None

Dictionary mapping nodes in the old skeleton to nodes in the new skeleton. Keys and values should be specified as lists of strings. If not provided, only nodes with identical names will be mapped. Points associated with unmapped nodes will be removed.

None
Notes

This method will update the PredictedInstance.skeleton attribute and the PredictedInstance.points attribute in place (a copy is made of the points array).

It is recommended to use Labels.replace_skeleton instead of this method if more flexible node mapping is required.

Source code in sleap_io/model/instance.py
def replace_skeleton(
    self,
    new_skeleton: Skeleton,
    node_names_map: dict[str, str] | None = None,
):
    """Replace the skeleton associated with the instance.

    Args:
        new_skeleton: The new `Skeleton` to associate with the instance.
        node_names_map: Dictionary mapping nodes in the old skeleton to nodes in the
            new skeleton. Keys and values should be specified as lists of strings.
            If not provided, only nodes with identical names will be mapped. Points
            associated with unmapped nodes will be removed.

    Notes:
        This method will update the `PredictedInstance.skeleton` attribute and the
        `PredictedInstance.points` attribute in place (a copy is made of the points
        array).

        It is recommended to use `Labels.replace_skeleton` instead of this method if
        more flexible node mapping is required.
    """
    # Update skeleton object.
    self.skeleton = new_skeleton

    # Get node names with replacements from node map if possible.
    old_node_names = self.points["name"].tolist()
    if node_names_map is not None:
        old_node_names = [node_names_map.get(node, node) for node in old_node_names]

    # Find correspondences.
    new_node_inds, old_node_inds = self.skeleton.match_nodes(old_node_names)

    # Update the points.
    new_points = PredictedPointsArray.empty(len(self.skeleton))
    new_points[new_node_inds] = self.points[old_node_inds]
    self.points = new_points
    self.points["name"] = self.skeleton.node_names

update_skeleton(names_only=False)

Update or replace the skeleton associated with the instance.

Parameters:

Name Type Description Default
names_only bool

If True, only update the node names in the points array. If False, the points array will be updated to match the new skeleton.

False
Source code in sleap_io/model/instance.py
def update_skeleton(self, names_only: bool = False):
    """Update or replace the skeleton associated with the instance.

    Args:
        names_only: If `True`, only update the node names in the points array. If
            `False`, the points array will be updated to match the new skeleton.
    """
    if names_only:
        # Update the node names.
        self.points["name"] = self.skeleton.node_names
        return

    # Find correspondences.
    new_node_inds, old_node_inds = self.skeleton.match_nodes(self.points["name"])

    # Update the points.
    new_points = PredictedPointsArray.empty(len(self.skeleton))
    new_points[new_node_inds] = self.points[old_node_inds]
    new_points["name"] = self.skeleton.node_names
    self.points = new_points

PredictedInstance3D

Bases: sleap_io.model.instance.Instance3D

A predicted 3D pose instance with per-keypoint confidence scores.

Extends Instance3D with per-point scores from triangulation confidence or other prediction methods.

Attributes:

Name Type Description
point_scores

Per-keypoint confidence scores as (N,) float64 array. NaN values for missing keypoints.

Methods:

Name Description
__init__

Method generated by attrs for class PredictedInstance3D.

__repr__

Return a readable representation of the predicted 3D instance.

__setattr__

Method generated by attrs for class PredictedInstance3D.

Source code in sleap_io/model/instance.py
@attrs.define(eq=False)
class PredictedInstance3D(Instance3D):
    """A predicted 3D pose instance with per-keypoint confidence scores.

    Extends Instance3D with per-point scores from triangulation confidence
    or other prediction methods.

    Attributes:
        point_scores: Per-keypoint confidence scores as (N,) float64 array.
            NaN values for missing keypoints.
    """

    point_scores: np.ndarray = attrs.field(
        default=None,
        converter=lambda x: np.array(x, dtype="float64") if x is not None else None,
    )

    def __repr__(self) -> str:
        """Return a readable representation of the predicted 3D instance."""
        n_valid = 0
        if self.points is not None:
            n_valid = int(np.sum(~np.isnan(self.points).any(axis=1)))
        n_total = len(self.skeleton.nodes)
        score_str = f", score={self.score:.3f}" if self.score is not None else ""
        return f"PredictedInstance3D(n_points={n_valid}/{n_total}{score_str})"

__annotations__ = {'point_scores': 'np.ndarray'} 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=False, 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 predicted 3D pose instance with per-keypoint confidence scores.\n\nExtends Instance3D with per-point scores from triangulation confidence\nor other prediction methods.\n\nAttributes:\n point_scores: Per-keypoint confidence scores as (N,) float64 array.\n NaN values for missing keypoints.\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__ = 1550 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__ = ('points', 'skeleton', 'score', 'metadata', 'point_scores') 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.instance' 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__ = ('point_scores',) 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__(points, skeleton, score=None, metadata=NOTHING, point_scores=None)

Method generated by attrs for class PredictedInstance3D.

Source code in sleap_io/model/instance.py
"""Data structures for data associated with a single instance such as an animal.

The `Instance` class is a SLEAP data structure that contains a collection of points that
correspond to landmarks within a `Skeleton`.

`PredictedInstance` additionally contains metadata associated with how the instance was
estimated, such as confidence scores.
"""

from __future__ import annotations

from typing import TYPE_CHECKING

__repr__()

Return a readable representation of the predicted 3D instance.

Source code in sleap_io/model/instance.py
def __repr__(self) -> str:
    """Return a readable representation of the predicted 3D instance."""
    n_valid = 0
    if self.points is not None:
        n_valid = int(np.sum(~np.isnan(self.points).any(axis=1)))
    n_total = len(self.skeleton.nodes)
    score_str = f", score={self.score:.3f}" if self.score is not None else ""
    return f"PredictedInstance3D(n_points={n_valid}/{n_total}{score_str})"

__setattr__(name, val)

Method generated by attrs for class PredictedInstance3D.

Source code in sleap_io/model/instance.py
if np.any(intersection_min >= intersection_max):
    # No intersection
    return False

intersection_area = np.prod(intersection_max - intersection_min)

# Calculate union
self_area = np.prod(self_bbox[1] - self_bbox[0])
other_area = np.prod(other_bbox[1] - other_bbox[0])

PredictedPointsArray

Bases: sleap_io.model.instance.PointsArray

A specialized array for storing predicted instance points data with scores.

This extends the PointsArray class to include score information for each point.

The structured dtype includes the following fields
  • xy: A float64 array of shape (2,) containing the x, y coordinates
  • score: A float64 containing the confidence score for the point
  • visible: A boolean indicating if the point is visible
  • complete: A boolean indicating if the point is complete
  • name: An object dtype containing the name of the node

Methods:

Name Description
from_array

Convert an existing array to a PredictedPointsArray with appropriate dtype.

from_dict

Create a PredictedPointsArray from a dictionary of node points.

Attributes:

Name Type Description
__doc__

str(object='') -> str

__firstlineno__

int([x]) -> integer

__module__

str(object='') -> str

__static_attributes__

Built-in immutable sequence.

Source code in sleap_io/model/instance.py
class PredictedPointsArray(PointsArray):
    """A specialized array for storing predicted instance points data with scores.

    This extends the PointsArray class to include score information for each point.

    The structured dtype includes the following fields:
        - xy: A float64 array of shape (2,) containing the x, y coordinates
        - score: A float64 containing the confidence score for the point
        - visible: A boolean indicating if the point is visible
        - complete: A boolean indicating if the point is complete
        - name: An object dtype containing the name of the node
    """

    @classmethod
    def _get_dtype(cls):
        """Get the dtype for predicted points array with scores.

        Returns:
            np.dtype: A structured numpy dtype with fields for xy coordinates,
                score, visible flag, complete flag, and node names.
        """
        # Cache the dtype at the class level for performance
        # Use cls.__dict__ to check if defined on this class (not inherited)
        if "_cached_dtype" not in cls.__dict__:
            cls._cached_dtype = np.dtype(
                [
                    ("xy", "<f8", (2,)),  # 64-bit (8-byte) little-endian double, ndim=2
                    ("score", "<f8"),  # 64-bit (8-byte) little-endian double
                    ("visible", "bool"),
                    ("complete", "bool"),
                    (
                        "name",
                        "O",
                    ),  # object dtype to store pointers to python string objects
                ]
            )
        return cls._cached_dtype

    @classmethod
    def from_array(cls, array: np.ndarray) -> "PredictedPointsArray":
        """Convert an existing array to a PredictedPointsArray with appropriate dtype.

        Args:
            array: A numpy array to convert. Can be a structured array or a regular
                array. If a regular array, it is assumed to have columns for x, y
                coordinates, scores, and optionally visible and complete flags.

        Returns:
            PredictedPointsArray: A structured array view of the input data with the
                appropriate dtype.

        Notes:
            If the input is a structured array with fields matching the target dtype,
            those fields will be copied. Otherwise, a best-effort conversion is made:

            - First two columns (or first 2D element) are interpreted as x, y coords
            - Third column (if present) is interpreted as the score
            - Fourth column (if present) is interpreted as visible flag
            - Fifth column (if present) is interpreted as complete flag

            If visibility is not provided, it is inferred from NaN values in the x
            coordinate.
        """
        dtype = cls._get_dtype()

        # If already the right type, just view as PredictedPointsArray
        if isinstance(array, np.ndarray) and array.dtype == dtype:
            return array.view(cls)

        # Otherwise, create a new array with the right dtype
        new_array = np.empty(len(array), dtype=dtype).view(cls)

        # Copy available fields
        if isinstance(array, np.ndarray) and array.dtype.fields is not None:
            # Structured array, copy matching fields
            for field_name in dtype.names:
                if field_name in array.dtype.names:
                    new_array[field_name] = array[field_name]
        elif isinstance(array, np.ndarray):
            # Regular array, assume x, y coordinates
            new_array["xy"] = array[:, 0:2]

            # Default visibility based on NaN
            new_array["visible"] = ~np.isnan(array[:, 0])

            # If there's a third column, assume it's the score
            if array.shape[1] >= 3:
                new_array["score"] = array[:, 2]

            # If there are more columns, assume they are visible and complete
            if array.shape[1] >= 4:
                new_array["visible"] = array[:, 3].astype(bool)

            if array.shape[1] >= 5:
                new_array["complete"] = array[:, 4].astype(bool)

        return new_array

    @classmethod
    def from_dict(cls, points_dict: dict, skeleton: Skeleton) -> "PredictedPointsArray":
        """Create a PredictedPointsArray from a dictionary of node points.

        Args:
            points_dict: A dictionary mapping nodes (as Node objects, indices, or
                strings) to point data. Each point should be an array-like with at least
                2 elements for x, y coordinates, and optionally score, visible, and
                complete flags.
            skeleton: The Skeleton object that defines the nodes.

        Returns:
            PredictedPointsArray: A structured array with the appropriate dtype
                containing the point data from the dictionary.

        Notes:
            For each entry in the points_dict:
            - First two values are treated as x, y coordinates
            - Third value (if present) is treated as score
            - Fourth value (if present) is treated as visible flag
            - Fifth value (if present) is treated as complete flag

            If visibility is not provided, it is inferred from NaN values in the x
            coordinate.
        """
        points = cls.empty(len(skeleton))

        for node, data in points_dict.items():
            if isinstance(node, (Node, str)):
                node = skeleton.index(node)

            points[node]["xy"] = data[:2]

            # Score is the third element
            idx = 2
            if len(data) > idx:
                points[node]["score"] = data[idx]
                idx += 1

            # Visibility is the fourth element (or third if no score)
            if len(data) > idx:
                points[node]["visible"] = data[idx]
            else:
                points[node]["visible"] = ~np.isnan(data[0])

            idx += 1
            # Completeness is the fifth element (or fourth if no score)
            if len(data) > idx:
                points[node]["complete"] = data[idx]

        return points

__doc__ = 'A specialized array for storing predicted instance points data with scores.\n\nThis extends the PointsArray class to include score information for each point.\n\nThe structured dtype includes the following fields:\n - xy: A float64 array of shape (2,) containing the x, y coordinates\n - score: A float64 containing the confidence score for the point\n - visible: A boolean indicating if the point is visible\n - complete: A boolean indicating if the point is complete\n - name: An object dtype containing the name of the node\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__ = 181 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

__module__ = 'sleap_io.model.instance' 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'.

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

from_array(array) classmethod

Convert an existing array to a PredictedPointsArray with appropriate dtype.

Parameters:

Name Type Description Default
array ndarray

A numpy array to convert. Can be a structured array or a regular array. If a regular array, it is assumed to have columns for x, y coordinates, scores, and optionally visible and complete flags.

required

Returns:

Name Type Description
PredictedPointsArray PredictedPointsArray

A structured array view of the input data with the appropriate dtype.

Notes

If the input is a structured array with fields matching the target dtype, those fields will be copied. Otherwise, a best-effort conversion is made:

  • First two columns (or first 2D element) are interpreted as x, y coords
  • Third column (if present) is interpreted as the score
  • Fourth column (if present) is interpreted as visible flag
  • Fifth column (if present) is interpreted as complete flag

If visibility is not provided, it is inferred from NaN values in the x coordinate.

Source code in sleap_io/model/instance.py
@classmethod
def from_array(cls, array: np.ndarray) -> "PredictedPointsArray":
    """Convert an existing array to a PredictedPointsArray with appropriate dtype.

    Args:
        array: A numpy array to convert. Can be a structured array or a regular
            array. If a regular array, it is assumed to have columns for x, y
            coordinates, scores, and optionally visible and complete flags.

    Returns:
        PredictedPointsArray: A structured array view of the input data with the
            appropriate dtype.

    Notes:
        If the input is a structured array with fields matching the target dtype,
        those fields will be copied. Otherwise, a best-effort conversion is made:

        - First two columns (or first 2D element) are interpreted as x, y coords
        - Third column (if present) is interpreted as the score
        - Fourth column (if present) is interpreted as visible flag
        - Fifth column (if present) is interpreted as complete flag

        If visibility is not provided, it is inferred from NaN values in the x
        coordinate.
    """
    dtype = cls._get_dtype()

    # If already the right type, just view as PredictedPointsArray
    if isinstance(array, np.ndarray) and array.dtype == dtype:
        return array.view(cls)

    # Otherwise, create a new array with the right dtype
    new_array = np.empty(len(array), dtype=dtype).view(cls)

    # Copy available fields
    if isinstance(array, np.ndarray) and array.dtype.fields is not None:
        # Structured array, copy matching fields
        for field_name in dtype.names:
            if field_name in array.dtype.names:
                new_array[field_name] = array[field_name]
    elif isinstance(array, np.ndarray):
        # Regular array, assume x, y coordinates
        new_array["xy"] = array[:, 0:2]

        # Default visibility based on NaN
        new_array["visible"] = ~np.isnan(array[:, 0])

        # If there's a third column, assume it's the score
        if array.shape[1] >= 3:
            new_array["score"] = array[:, 2]

        # If there are more columns, assume they are visible and complete
        if array.shape[1] >= 4:
            new_array["visible"] = array[:, 3].astype(bool)

        if array.shape[1] >= 5:
            new_array["complete"] = array[:, 4].astype(bool)

    return new_array

from_dict(points_dict, skeleton) classmethod

Create a PredictedPointsArray from a dictionary of node points.

Parameters:

Name Type Description Default
points_dict dict

A dictionary mapping nodes (as Node objects, indices, or strings) to point data. Each point should be an array-like with at least 2 elements for x, y coordinates, and optionally score, visible, and complete flags.

required
skeleton Skeleton

The Skeleton object that defines the nodes.

required

Returns:

Name Type Description
PredictedPointsArray PredictedPointsArray

A structured array with the appropriate dtype containing the point data from the dictionary.

Notes

For each entry in the points_dict: - First two values are treated as x, y coordinates - Third value (if present) is treated as score - Fourth value (if present) is treated as visible flag - Fifth value (if present) is treated as complete flag

If visibility is not provided, it is inferred from NaN values in the x coordinate.

Source code in sleap_io/model/instance.py
@classmethod
def from_dict(cls, points_dict: dict, skeleton: Skeleton) -> "PredictedPointsArray":
    """Create a PredictedPointsArray from a dictionary of node points.

    Args:
        points_dict: A dictionary mapping nodes (as Node objects, indices, or
            strings) to point data. Each point should be an array-like with at least
            2 elements for x, y coordinates, and optionally score, visible, and
            complete flags.
        skeleton: The Skeleton object that defines the nodes.

    Returns:
        PredictedPointsArray: A structured array with the appropriate dtype
            containing the point data from the dictionary.

    Notes:
        For each entry in the points_dict:
        - First two values are treated as x, y coordinates
        - Third value (if present) is treated as score
        - Fourth value (if present) is treated as visible flag
        - Fifth value (if present) is treated as complete flag

        If visibility is not provided, it is inferred from NaN values in the x
        coordinate.
    """
    points = cls.empty(len(skeleton))

    for node, data in points_dict.items():
        if isinstance(node, (Node, str)):
            node = skeleton.index(node)

        points[node]["xy"] = data[:2]

        # Score is the third element
        idx = 2
        if len(data) > idx:
            points[node]["score"] = data[idx]
            idx += 1

        # Visibility is the fourth element (or third if no score)
        if len(data) > idx:
            points[node]["visible"] = data[idx]
        else:
            points[node]["visible"] = ~np.isnan(data[0])

        idx += 1
        # Completeness is the fifth element (or fourth if no score)
        if len(data) > idx:
            points[node]["complete"] = data[idx]

    return points

Skeleton

A description of a set of landmark types and connections between them.

Skeletons are represented by a directed graph composed of a set of Nodes (landmark types such as body parts) and Edges (connections between parts).

Attributes:

Name Type Description
nodes

A list of Nodes. May be specified as a list of strings to create new nodes from their names.

edges

A list of Edges. May be specified as a list of 2-tuples of string names or integer indices of nodes. Each edge corresponds to a pair of source and destination nodes forming a directed edge.

symmetries

A list of Symmetrys. Each symmetry corresponds to symmetric body parts, such as "left eye", "right eye". This is used when applying flip (reflection) augmentation to images in order to appropriately swap the indices of symmetric landmarks.

name

A descriptive name for the Skeleton.

Methods:

Name Description
__attrs_post_init__

Ensure nodes are Nodes, edges are Edges, and Node map is updated.

__contains__

Check if a node is in the skeleton.

__getitem__

Return a Node when indexing by name or integer.

__init__

Method generated by attrs for class Skeleton.

__len__

Return the number of nodes in the skeleton.

__repr__

Return a readable representation of the skeleton.

__setattr__

Method generated by attrs for class Skeleton.

add_edge

Add an Edge to the skeleton.

add_edges

Add multiple Edges to the skeleton.

add_node

Add a Node to the skeleton.

add_nodes

Add multiple Nodes to the skeleton.

add_symmetries

Add multiple Symmetry relationships to the skeleton.

add_symmetry

Add a symmetry relationship to the skeleton.

get_flipped_node_inds

Returns node indices that should be switched when horizontally flipping.

index

Return the index of a node specified as a Node or string name.

infer_symmetries_by_name

Infer left/right symmetric node pairs from node names.

match_nodes

Return the order of nodes in the skeleton.

matches

Check if this skeleton matches another skeleton's structure.

node_similarities

Calculate node overlap metrics with another skeleton.

rebuild_cache

Rebuild the node name/index to Node map caches.

remove_node

Remove a single node from the skeleton.

remove_nodes

Remove nodes from the skeleton.

rename_node

Rename a single node in the skeleton.

rename_nodes

Rename nodes in the skeleton.

reorder_nodes

Reorder nodes in the skeleton.

require_node

Return a Node object, handling indexing and adding missing nodes.

Source code in sleap_io/model/skeleton.py
@define(eq=False)
class Skeleton:
    """A description of a set of landmark types and connections between them.

    Skeletons are represented by a directed graph composed of a set of `Node`s (landmark
    types such as body parts) and `Edge`s (connections between parts).

    Attributes:
        nodes: A list of `Node`s. May be specified as a list of strings to create new
            nodes from their names.
        edges: A list of `Edge`s. May be specified as a list of 2-tuples of string names
            or integer indices of `nodes`. Each edge corresponds to a pair of source and
            destination nodes forming a directed edge.
        symmetries: A list of `Symmetry`s. Each symmetry corresponds to symmetric body
            parts, such as `"left eye", "right eye"`. This is used when applying flip
            (reflection) augmentation to images in order to appropriately swap the
            indices of symmetric landmarks.
        name: A descriptive name for the `Skeleton`.
    """

    def _nodes_on_setattr(self, attr, new_nodes):
        """Callback to update caches when nodes are set."""
        self.rebuild_cache(nodes=new_nodes)
        return new_nodes

    nodes: list[Node] = field(
        factory=list,
        on_setattr=_nodes_on_setattr,
    )
    edges: list[Edge] = field(factory=list)
    symmetries: list[Symmetry] = field(factory=list)
    name: str | None = None
    _name_to_node_cache: dict[str, Node] = field(init=False, repr=False, eq=False)
    _node_to_ind_cache: dict[Node, int] = field(init=False, repr=False, eq=False)

    def __attrs_post_init__(self):
        """Ensure nodes are `Node`s, edges are `Edge`s, and `Node` map is updated."""
        self._convert_nodes()
        self._convert_edges()
        self._convert_symmetries()
        self.rebuild_cache()

    def _convert_nodes(self):
        """Convert nodes to `Node` objects if needed."""
        if isinstance(self.nodes, np.ndarray):
            object.__setattr__(self, "nodes", self.nodes.tolist())
        for i, node in enumerate(self.nodes):
            if type(node) is str:
                self.nodes[i] = Node(node)

    def _convert_edges(self):
        """Convert list of edge names or integers to `Edge` objects if needed."""
        if isinstance(self.edges, np.ndarray):
            self.edges = self.edges.tolist()
        node_names = self.node_names
        for i, edge in enumerate(self.edges):
            if type(edge) is Edge:
                continue
            src, dst = edge
            if type(src) is str:
                try:
                    src = node_names.index(src)
                except ValueError:
                    raise ValueError(
                        f"Node '{src}' specified in the edge list is not in the nodes."
                    )
            if type(src) is int or (
                np.isscalar(src) and np.issubdtype(src.dtype, np.integer)
            ):
                src = self.nodes[src]

            if type(dst) is str:
                try:
                    dst = node_names.index(dst)
                except ValueError:
                    raise ValueError(
                        f"Node '{dst}' specified in the edge list is not in the nodes."
                    )
            if type(dst) is int or (
                np.isscalar(dst) and np.issubdtype(dst.dtype, np.integer)
            ):
                dst = self.nodes[dst]

            self.edges[i] = Edge(src, dst)

    def _convert_symmetries(self):
        """Convert list of symmetric node names or integers to `Symmetry` objects."""
        if isinstance(self.symmetries, np.ndarray):
            self.symmetries = self.symmetries.tolist()

        node_names = self.node_names
        for i, symmetry in enumerate(self.symmetries):
            if type(symmetry) is Symmetry:
                continue
            node1, node2 = symmetry
            if type(node1) is str:
                try:
                    node1 = node_names.index(node1)
                except ValueError:
                    raise ValueError(
                        f"Node '{node1}' specified in the symmetry list is not in the "
                        "nodes."
                    )
            if type(node1) is int or (
                np.isscalar(node1) and np.issubdtype(node1.dtype, np.integer)
            ):
                node1 = self.nodes[node1]

            if type(node2) is str:
                try:
                    node2 = node_names.index(node2)
                except ValueError:
                    raise ValueError(
                        f"Node '{node2}' specified in the symmetry list is not in the "
                        "nodes."
                    )
            if type(node2) is int or (
                np.isscalar(node2) and np.issubdtype(node2.dtype, np.integer)
            ):
                node2 = self.nodes[node2]

            self.symmetries[i] = Symmetry({node1, node2})

    def rebuild_cache(self, nodes: list[Node] | None = None):
        """Rebuild the node name/index to `Node` map caches.

        Args:
            nodes: A list of `Node` objects to update the cache with. If not provided,
                the cache will be updated with the current nodes in the skeleton. If
                nodes are provided, the cache will be updated with the provided nodes,
                but the current nodes in the skeleton will not be updated. Default is
                `None`.

        Notes:
            This function should be called when nodes or node list is mutated to update
            the lookup caches for indexing nodes by name or `Node` object.

            This is done automatically when nodes are added or removed from the skeleton
            using the convenience methods in this class.

            This method only needs to be used when manually mutating nodes or the node
            list directly.
        """
        if nodes is None:
            nodes = self.nodes
        self._name_to_node_cache = {node.name: node for node in nodes}
        self._node_to_ind_cache = {node: i for i, node in enumerate(nodes)}

    @property
    def node_names(self) -> list[str]:
        """Names of the nodes associated with this skeleton as a list of strings."""
        return [node.name for node in self.nodes]

    @property
    def edge_inds(self) -> list[tuple[int, int]]:
        """Edges indices as a list of 2-tuples."""
        return [
            (self.nodes.index(edge.source), self.nodes.index(edge.destination))
            for edge in self.edges
        ]

    @property
    def edge_names(self) -> list[str, str]:
        """Edge names as a list of 2-tuples with string node names."""
        return [(edge.source.name, edge.destination.name) for edge in self.edges]

    @property
    def symmetry_inds(self) -> list[tuple[int, int]]:
        """Symmetry indices as a list of 2-tuples."""
        return [
            tuple(sorted((self.index(symmetry[0]), self.index(symmetry[1]))))
            for symmetry in self.symmetries
        ]

    @property
    def symmetry_names(self) -> list[str, str]:
        """Symmetry names as a list of 2-tuples with string node names."""
        return [
            (self.nodes[i].name, self.nodes[j].name) for (i, j) in self.symmetry_inds
        ]

    def get_flipped_node_inds(self) -> list[int]:
        """Returns node indices that should be switched when horizontally flipping.

        This is useful as a lookup table for flipping the landmark coordinates when
        doing data augmentation.

        Example:
            >>> skel = Skeleton(["A", "B_left", "B_right", "C", "D_left", "D_right"])
            >>> skel.add_symmetry("B_left", "B_right")
            >>> skel.add_symmetry("D_left", "D_right")
            >>> skel.flipped_node_inds
            [0, 2, 1, 3, 5, 4]
            >>> pose = np.array([[0, 0], [1, 1], [2, 2], [3, 3], [4, 4], [5, 5]])
            >>> pose[skel.flipped_node_inds]
            array([[0, 0],
                   [2, 2],
                   [1, 1],
                   [3, 3],
                   [5, 5],
                   [4, 4]])
        """
        flip_idx = np.arange(len(self.nodes))
        if len(self.symmetries) > 0:
            symmetry_inds = np.array(
                [(self.index(a), self.index(b)) for a, b in self.symmetries]
            )
            flip_idx[symmetry_inds[:, 0]] = symmetry_inds[:, 1]
            flip_idx[symmetry_inds[:, 1]] = symmetry_inds[:, 0]

        flip_idx = flip_idx.tolist()
        return flip_idx

    def __len__(self) -> int:
        """Return the number of nodes in the skeleton."""
        return len(self.nodes)

    def __repr__(self) -> str:
        """Return a readable representation of the skeleton."""
        nodes = ", ".join([f'"{node}"' for node in self.node_names])
        return f"Skeleton(nodes=[{nodes}], edges={self.edge_inds})"

    def index(self, node: Node | str) -> int:
        """Return the index of a node specified as a `Node` or string name."""
        if type(node) is str:
            return self.index(self._name_to_node_cache[node])
        elif type(node) is Node:
            return self._node_to_ind_cache[node]
        else:
            raise IndexError(f"Invalid indexing argument for skeleton: {node}")

    def __getitem__(self, idx: NodeOrIndex) -> Node:
        """Return a `Node` when indexing by name or integer."""
        if type(idx) is int:
            return self.nodes[idx]
        elif type(idx) is str:
            return self._name_to_node_cache[idx]
        else:
            raise IndexError(f"Invalid indexing argument for skeleton: {idx}")

    def __contains__(self, node: NodeOrIndex) -> bool:
        """Check if a node is in the skeleton."""
        if type(node) is str:
            return node in self._name_to_node_cache
        elif type(node) is Node:
            return node in self.nodes
        elif type(node) is int:
            return 0 <= node < len(self.nodes)
        else:
            raise ValueError(f"Invalid node type for skeleton: {node}")

    def add_node(self, node: Node | str):
        """Add a `Node` to the skeleton.

        Args:
            node: A `Node` object or a string name to create a new node.

        Raises:
            ValueError: If the node already exists in the skeleton or if the node is
                not specified as a `Node` or string.
        """
        if node in self:
            raise ValueError(f"Node '{node}' already exists in the skeleton.")

        if type(node) is str:
            node = Node(node)

        if type(node) is not Node:
            raise ValueError(f"Invalid node type: {node} ({type(node)})")

        self.nodes.append(node)

        # Atomic update of the cache.
        self._name_to_node_cache[node.name] = node
        self._node_to_ind_cache[node] = len(self.nodes) - 1

    def add_nodes(self, nodes: list[Node | str]):
        """Add multiple `Node`s to the skeleton.

        Args:
            nodes: A list of `Node` objects or string names to create new nodes.
        """
        for node in nodes:
            self.add_node(node)

    def require_node(self, node: NodeOrIndex, add_missing: bool = True) -> Node:
        """Return a `Node` object, handling indexing and adding missing nodes.

        Args:
            node: A `Node` object, name or index.
            add_missing: If `True`, missing nodes will be added to the skeleton. If
                `False`, an error will be raised if the node is not found. Default is
                `True`.

        Returns:
            The `Node` object.

        Raises:
            IndexError: If the node is not found in the skeleton and `add_missing` is
                `False`.
        """
        if node not in self:
            if add_missing:
                self.add_node(node)
            else:
                raise IndexError(f"Node '{node}' not found in the skeleton.")

        if type(node) is Node:
            return node

        return self[node]

    def add_edge(
        self,
        src: NodeOrIndex | Edge | tuple[NodeOrIndex, NodeOrIndex],
        dst: NodeOrIndex | None = None,
    ):
        """Add an `Edge` to the skeleton.

        Args:
            src: The source node specified as a `Node`, name or index.
            dst: The destination node specified as a `Node`, name or index.
        """
        edge = None
        if type(src) is tuple:
            src, dst = src

        if is_node_or_index(src):
            if not is_node_or_index(dst):
                raise ValueError("Destination node must be specified.")

            src = self.require_node(src)
            dst = self.require_node(dst)
            edge = Edge(src, dst)

        if type(src) is Edge:
            edge = src

        if edge not in self.edges:
            self.edges.append(edge)

    def add_edges(self, edges: list[Edge | tuple[NodeOrIndex, NodeOrIndex]]):
        """Add multiple `Edge`s to the skeleton.

        Args:
            edges: A list of `Edge` objects or 2-tuples of source and destination nodes.
        """
        for edge in edges:
            self.add_edge(edge)

    def add_symmetry(
        self, node1: Symmetry | NodeOrIndex = None, node2: NodeOrIndex | None = None
    ):
        """Add a symmetry relationship to the skeleton.

        Args:
            node1: The first node specified as a `Node`, name or index. If a `Symmetry`
                object is provided, it will be added directly to the skeleton.
            node2: The second node specified as a `Node`, name or index.
        """
        symmetry = None
        if type(node1) is Symmetry:
            symmetry = node1
            node1, node2 = symmetry

        node1 = self.require_node(node1)
        node2 = self.require_node(node2)

        if symmetry is None:
            symmetry = Symmetry({node1, node2})

        if symmetry not in self.symmetries:
            self.symmetries.append(symmetry)

    def add_symmetries(
        self, symmetries: list[Symmetry | tuple[NodeOrIndex, NodeOrIndex]]
    ):
        """Add multiple `Symmetry` relationships to the skeleton.

        Args:
            symmetries: A list of `Symmetry` objects or 2-tuples of symmetric nodes.
        """
        for symmetry in symmetries:
            self.add_symmetry(*symmetry)

    def infer_symmetries_by_name(
        self,
        token_pairs: list[tuple[str, str]] | None = None,
    ) -> list[tuple[int, int]]:
        """Infer left/right symmetric node pairs from node names.

        Useful when a skeleton has no symmetries defined (e.g. imported from a
        format that does not carry symmetry metadata) but its node names encode
        laterality, so that flip-dependent tooling (augmentation, QC) still
        works. Names are matched by splitting on separators (`_`, `-`, `.`,
        space), camelCase boundaries, and letter/digit boundaries, then pairing
        nodes that share a stem but differ by a single left/right token. For
        example, `Ear_L`/`Ear_R`, `left_eye`/`right_eye`, `LeftPaw`/`RightPaw`,
        and `L1`/`R1` all pair up.

        This is intentionally **non-mutating** and conservative: it returns
        suggested pairs rather than writing them onto the skeleton, since a wrong
        guess would silently corrupt flip augmentation. Apply the result
        explicitly if desired, e.g.
        `skel.add_symmetries(skel.infer_symmetries_by_name())`. Node names
        without a delimited or camelCase/digit token boundary (e.g. `larm`) and
        truly non-semantic pairings (e.g. `L1`/`L2`) cannot be inferred and must
        be declared with `add_symmetry`.

        Args:
            token_pairs: List of `(left_token, right_token)` string pairs used to
                recognize laterality, matched case-insensitively against whole
                name segments. Defaults to `[("left", "right"), ("l", "r")]`.

        Returns:
            A list of `(left_index, right_index)` node-index pairs, ordered by
            left index. Each node appears in at most one pair, and only stems
            with exactly one left and one right member are paired (ambiguous
            groups are skipped).

        Example:
            >>> skel = Skeleton(["nose", "eye_L", "eye_R", "ear_L", "ear_R"])
            >>> skel.infer_symmetries_by_name()
            [(1, 2), (3, 4)]
            >>> skel.add_symmetries(skel.infer_symmetries_by_name())
            >>> skel.symmetry_names
            [('eye_L', 'eye_R'), ('ear_L', 'ear_R')]
        """
        return infer_symmetry_pairs_by_name(self.node_names, token_pairs=token_pairs)

    def rename_nodes(self, name_map: dict[NodeOrIndex, str] | list[str]):
        """Rename nodes in the skeleton.

        Args:
            name_map: A dictionary mapping old node names to new node names. Keys can be
                specified as `Node` objects, integer indices, or string names. Values
                must be specified as string names.

                If a list of strings is provided of the same length as the current
                nodes, the nodes will be renamed to the names in the list in order.

        Raises:
            ValueError: If the new node names exist in the skeleton or if the old node
                names are not found in the skeleton.

        Notes:
            This method should always be used when renaming nodes in the skeleton as it
            handles updating the lookup caches necessary for indexing nodes by name.

            After renaming, instances using this skeleton **do NOT need to be updated**
            as the nodes are stored by reference in the skeleton, so changes are
            reflected automatically.

        Example:
            >>> skel = Skeleton(["A", "B", "C"], edges=[("A", "B"), ("B", "C")])
            >>> skel.rename_nodes({"A": "X", "B": "Y", "C": "Z"})
            >>> skel.node_names
            ["X", "Y", "Z"]
            >>> skel.rename_nodes(["a", "b", "c"])
            >>> skel.node_names
            ["a", "b", "c"]
        """
        if type(name_map) is list:
            if len(name_map) != len(self.nodes):
                raise ValueError(
                    "List of new node names must be the same length as the current "
                    "nodes."
                )
            name_map = {node: name for node, name in zip(self.nodes, name_map)}

        for old_name, new_name in name_map.items():
            if type(old_name) is Node:
                old_name = old_name.name
            if type(old_name) is int:
                old_name = self.nodes[old_name].name

            if old_name not in self._name_to_node_cache:
                raise ValueError(f"Node '{old_name}' not found in the skeleton.")
            if new_name in self._name_to_node_cache:
                raise ValueError(f"Node '{new_name}' already exists in the skeleton.")

            node = self._name_to_node_cache[old_name]
            node.name = new_name
            self._name_to_node_cache[new_name] = node
            del self._name_to_node_cache[old_name]

    def rename_node(self, old_name: NodeOrIndex, new_name: str):
        """Rename a single node in the skeleton.

        Args:
            old_name: The name of the node to rename. Can also be specified as an
                integer index or `Node` object.
            new_name: The new name for the node.
        """
        self.rename_nodes({old_name: new_name})

    def remove_nodes(self, nodes: list[NodeOrIndex]):
        """Remove nodes from the skeleton.

        Args:
            nodes: A list of node names, indices, or `Node` objects to remove.

        Notes:
            This method handles updating the lookup caches necessary for indexing nodes
            by name.

            Any edges and symmetries that are connected to the removed nodes will also
            be removed.

        Warning:
            **This method does NOT update instances** that use this skeleton to reflect
            changes.

            It is recommended to use the `Labels.remove_nodes()` method which will
            update all contained to reflect the changes made to the skeleton.

            To manually update instances after this method is called, call
            `instance.update_nodes()` on each instance that uses this skeleton.
        """
        # Standardize input and make a pre-mutation copy before keys are changed.
        rm_node_objs = [self.require_node(node, add_missing=False) for node in nodes]

        # Remove nodes from the skeleton.
        for node in rm_node_objs:
            self.nodes.remove(node)
            del self._name_to_node_cache[node.name]

        # Remove edges connected to the removed nodes.
        self.edges = [
            edge
            for edge in self.edges
            if edge.source not in rm_node_objs and edge.destination not in rm_node_objs
        ]

        # Remove symmetries connected to the removed nodes.
        self.symmetries = [
            symmetry
            for symmetry in self.symmetries
            if symmetry.nodes.isdisjoint(rm_node_objs)
        ]

        # Update node index map.
        self.rebuild_cache()

    def remove_node(self, node: NodeOrIndex):
        """Remove a single node from the skeleton.

        Args:
            node: The node to remove. Can be specified as a string name, integer index,
                or `Node` object.

        Notes:
            This method handles updating the lookup caches necessary for indexing nodes
            by name.

            Any edges and symmetries that are connected to the removed node will also be
            removed.

        Warning:
            **This method does NOT update instances** that use this skeleton to reflect
            changes.

            It is recommended to use the `Labels.remove_nodes()` method which will
            update all contained instances to reflect the changes made to the skeleton.

            To manually update instances after this method is called, call
            `Instance.update_skeleton()` on each instance that uses this skeleton.
        """
        self.remove_nodes([node])

    def reorder_nodes(self, new_order: list[NodeOrIndex]):
        """Reorder nodes in the skeleton.

        Args:
            new_order: A list of node names, indices, or `Node` objects specifying the
                new order of the nodes.

        Raises:
            ValueError: If the new order of nodes is not the same length as the current
                nodes.

        Notes:
            This method handles updating the lookup caches necessary for indexing nodes
            by name.

        Warning:
            After reordering, instances using this skeleton do not need to be updated as
            the nodes are stored by reference in the skeleton.

            However, the order that points are stored in the instances will not be
            updated to match the new order of the nodes in the skeleton. This should not
            matter unless the ordering of the keys in the `Instance.points` dictionary
            is used instead of relying on the skeleton node order.

            To make sure these are aligned, it is recommended to use the
            `Labels.reorder_nodes()` method which will update all contained instances to
            reflect the changes made to the skeleton.

            To manually update instances after this method is called, call
            `Instance.update_skeleton()` on each instance that uses this skeleton.
        """
        if len(new_order) != len(self.nodes):
            raise ValueError(
                "New order of nodes must be the same length as the current nodes."
            )

        new_nodes = [self.require_node(node, add_missing=False) for node in new_order]
        self.nodes = new_nodes

    def match_nodes(self, other_nodes: list[str, Node]) -> tuple[list[int], list[int]]:
        """Return the order of nodes in the skeleton.

        Args:
            other_nodes: A list of node names or `Node` objects.

        Returns:
            A tuple of `skeleton_inds, `other_inds`.

            `skeleton_inds` contains the indices of the nodes in the skeleton that match
            the input nodes.

            `other_inds` contains the indices of the input nodes that match the nodes in
            the skeleton.

            These can be used to reorder point data to match the order of nodes in the
            skeleton.

        See also: match_nodes_cached
        """
        if isinstance(other_nodes, np.ndarray):
            other_nodes = other_nodes.tolist()
        if type(other_nodes) is not tuple:
            other_nodes = [x.name if type(x) is Node else x for x in other_nodes]

        skeleton_inds, other_inds = match_nodes_cached(
            tuple(self.node_names), tuple(other_nodes)
        )

        return list(skeleton_inds), list(other_inds)

    def matches(self, other: "Skeleton", require_same_order: bool = False) -> bool:
        """Check if this skeleton matches another skeleton's structure.

        Args:
            other: Another skeleton to compare with.
            require_same_order: If True, nodes must be in the same order.
                If False, only the node names and edges need to match.

        Returns:
            True if the skeletons match, False otherwise.

        Notes:
            Two skeletons match if they have the same nodes (by name) and edges.
            If require_same_order is True, the nodes must also be in the same order.
        """
        # Check if we have the same number of nodes
        if len(self.nodes) != len(other.nodes):
            return False

        # Check node names
        if require_same_order:
            if self.node_names != other.node_names:
                return False
        else:
            if set(self.node_names) != set(other.node_names):
                return False

        # Check edges (considering node name mapping if order differs)
        if len(self.edges) != len(other.edges):
            return False

        # Create edge sets for comparison
        self_edge_set = {
            (edge.source.name, edge.destination.name) for edge in self.edges
        }
        other_edge_set = {
            (edge.source.name, edge.destination.name) for edge in other.edges
        }

        if self_edge_set != other_edge_set:
            return False

        # Check symmetries
        if len(self.symmetries) != len(other.symmetries):
            return False

        self_sym_set = {
            frozenset(node.name for node in sym.nodes) for sym in self.symmetries
        }
        other_sym_set = {
            frozenset(node.name for node in sym.nodes) for sym in other.symmetries
        }

        return self_sym_set == other_sym_set

    def node_similarities(self, other: "Skeleton") -> dict[str, float]:
        """Calculate node overlap metrics with another skeleton.

        Args:
            other: Another skeleton to compare with.

        Returns:
            A dictionary with similarity metrics:
            - 'n_common': Number of nodes in common
            - 'n_self_only': Number of nodes only in this skeleton
            - 'n_other_only': Number of nodes only in the other skeleton
            - 'jaccard': Jaccard similarity (intersection/union)
            - 'dice': Dice coefficient (2*intersection/(n_self + n_other))
        """
        self_nodes = set(self.node_names)
        other_nodes = set(other.node_names)

        n_common = len(self_nodes & other_nodes)
        n_self_only = len(self_nodes - other_nodes)
        n_other_only = len(other_nodes - self_nodes)
        n_union = len(self_nodes | other_nodes)

        jaccard = n_common / n_union if n_union > 0 else 0
        dice = (
            2 * n_common / (len(self_nodes) + len(other_nodes))
            if (len(self_nodes) + len(other_nodes)) > 0
            else 0
        )

        return {
            "n_common": n_common,
            "n_self_only": n_self_only,
            "n_other_only": n_other_only,
            "jaccard": jaccard,
            "dice": dice,
        }

__annotations__ = {'nodes': 'list[Node]', 'edges': 'list[Edge]', 'symmetries': 'list[Symmetry]', 'name': 'str | None', '_name_to_node_cache': 'dict[str, Node]', '_node_to_ind_cache': 'dict[Node, 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=False, 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 description of a set of landmark types and connections between them.\n\nSkeletons are represented by a directed graph composed of a set of `Node`s (landmark\ntypes such as body parts) and `Edge`s (connections between parts).\n\nAttributes:\n nodes: A list of `Node`s. May be specified as a list of strings to create new\n nodes from their names.\n edges: A list of `Edge`s. May be specified as a list of 2-tuples of string names\n or integer indices of `nodes`. Each edge corresponds to a pair of source and\n destination nodes forming a directed edge.\n symmetries: A list of `Symmetry`s. Each symmetry corresponds to symmetric body\n parts, such as `"left eye", "right eye"`. This is used when applying flip\n (reflection) augmentation to images in order to appropriately swap the\n indices of symmetric landmarks.\n name: A descriptive name for the `Skeleton`.\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__ = 97 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__ = ('nodes', 'edges', 'symmetries', 'name') 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.skeleton' 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__ = ('nodes', 'edges', 'symmetries', 'name', '_name_to_node_cache', '_node_to_ind_cache', '__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__ = ('_name_to_node_cache', '_node_to_ind_cache', 'edges', 'nodes', 'symmetries') 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

edge_inds property

Edges indices as a list of 2-tuples.

edge_names property

Edge names as a list of 2-tuples with string node names.

node_names property

Names of the nodes associated with this skeleton as a list of strings.

symmetry_inds property

Symmetry indices as a list of 2-tuples.

symmetry_names property

Symmetry names as a list of 2-tuples with string node names.

__attrs_post_init__()

Ensure nodes are Nodes, edges are Edges, and Node map is updated.

Source code in sleap_io/model/skeleton.py
def __attrs_post_init__(self):
    """Ensure nodes are `Node`s, edges are `Edge`s, and `Node` map is updated."""
    self._convert_nodes()
    self._convert_edges()
    self._convert_symmetries()
    self.rebuild_cache()

__contains__(node)

Check if a node is in the skeleton.

Source code in sleap_io/model/skeleton.py
def __contains__(self, node: NodeOrIndex) -> bool:
    """Check if a node is in the skeleton."""
    if type(node) is str:
        return node in self._name_to_node_cache
    elif type(node) is Node:
        return node in self.nodes
    elif type(node) is int:
        return 0 <= node < len(self.nodes)
    else:
        raise ValueError(f"Invalid node type for skeleton: {node}")

__getitem__(idx)

Return a Node when indexing by name or integer.

Source code in sleap_io/model/skeleton.py
def __getitem__(self, idx: NodeOrIndex) -> Node:
    """Return a `Node` when indexing by name or integer."""
    if type(idx) is int:
        return self.nodes[idx]
    elif type(idx) is str:
        return self._name_to_node_cache[idx]
    else:
        raise IndexError(f"Invalid indexing argument for skeleton: {idx}")

__init__(nodes=NOTHING, edges=NOTHING, symmetries=NOTHING, name=None)

Method generated by attrs for class Skeleton.

Source code in sleap_io/model/skeleton.py
"""Data model for skeletons.

Skeletons are collections of nodes and edges which describe the landmarks associated
with a pose model. The edges represent the connections between them and may be used
differently depending on the underlying pose model.
"""

from __future__ import annotations

import re
import typing
from functools import lru_cache

import numpy as np
from attrs import define, field

__len__()

Return the number of nodes in the skeleton.

Source code in sleap_io/model/skeleton.py
def __len__(self) -> int:
    """Return the number of nodes in the skeleton."""
    return len(self.nodes)

__repr__()

Return a readable representation of the skeleton.

Source code in sleap_io/model/skeleton.py
def __repr__(self) -> str:
    """Return a readable representation of the skeleton."""
    nodes = ", ".join([f'"{node}"' for node in self.node_names])
    return f"Skeleton(nodes=[{nodes}], edges={self.edge_inds})"

__setattr__(name, val)

Method generated by attrs for class Skeleton.

add_edge(src, dst=None)

Add an Edge to the skeleton.

Parameters:

Name Type Description Default
src Union | Edge | tuple[Union, Union]

The source node specified as a Node, name or index.

required
dst Union | None

The destination node specified as a Node, name or index.

None
Source code in sleap_io/model/skeleton.py
def add_edge(
    self,
    src: NodeOrIndex | Edge | tuple[NodeOrIndex, NodeOrIndex],
    dst: NodeOrIndex | None = None,
):
    """Add an `Edge` to the skeleton.

    Args:
        src: The source node specified as a `Node`, name or index.
        dst: The destination node specified as a `Node`, name or index.
    """
    edge = None
    if type(src) is tuple:
        src, dst = src

    if is_node_or_index(src):
        if not is_node_or_index(dst):
            raise ValueError("Destination node must be specified.")

        src = self.require_node(src)
        dst = self.require_node(dst)
        edge = Edge(src, dst)

    if type(src) is Edge:
        edge = src

    if edge not in self.edges:
        self.edges.append(edge)

add_edges(edges)

Add multiple Edges to the skeleton.

Parameters:

Name Type Description Default
edges list[Edge | tuple[Union, Union]]

A list of Edge objects or 2-tuples of source and destination nodes.

required
Source code in sleap_io/model/skeleton.py
def add_edges(self, edges: list[Edge | tuple[NodeOrIndex, NodeOrIndex]]):
    """Add multiple `Edge`s to the skeleton.

    Args:
        edges: A list of `Edge` objects or 2-tuples of source and destination nodes.
    """
    for edge in edges:
        self.add_edge(edge)

add_node(node)

Add a Node to the skeleton.

Parameters:

Name Type Description Default
node Node | str

A Node object or a string name to create a new node.

required

Raises:

Type Description
ValueError

If the node already exists in the skeleton or if the node is not specified as a Node or string.

Source code in sleap_io/model/skeleton.py
def add_node(self, node: Node | str):
    """Add a `Node` to the skeleton.

    Args:
        node: A `Node` object or a string name to create a new node.

    Raises:
        ValueError: If the node already exists in the skeleton or if the node is
            not specified as a `Node` or string.
    """
    if node in self:
        raise ValueError(f"Node '{node}' already exists in the skeleton.")

    if type(node) is str:
        node = Node(node)

    if type(node) is not Node:
        raise ValueError(f"Invalid node type: {node} ({type(node)})")

    self.nodes.append(node)

    # Atomic update of the cache.
    self._name_to_node_cache[node.name] = node
    self._node_to_ind_cache[node] = len(self.nodes) - 1

add_nodes(nodes)

Add multiple Nodes to the skeleton.

Parameters:

Name Type Description Default
nodes list[Node | str]

A list of Node objects or string names to create new nodes.

required
Source code in sleap_io/model/skeleton.py
def add_nodes(self, nodes: list[Node | str]):
    """Add multiple `Node`s to the skeleton.

    Args:
        nodes: A list of `Node` objects or string names to create new nodes.
    """
    for node in nodes:
        self.add_node(node)

add_symmetries(symmetries)

Add multiple Symmetry relationships to the skeleton.

Parameters:

Name Type Description Default
symmetries list[Symmetry | tuple[Union, Union]]

A list of Symmetry objects or 2-tuples of symmetric nodes.

required
Source code in sleap_io/model/skeleton.py
def add_symmetries(
    self, symmetries: list[Symmetry | tuple[NodeOrIndex, NodeOrIndex]]
):
    """Add multiple `Symmetry` relationships to the skeleton.

    Args:
        symmetries: A list of `Symmetry` objects or 2-tuples of symmetric nodes.
    """
    for symmetry in symmetries:
        self.add_symmetry(*symmetry)

add_symmetry(node1=None, node2=None)

Add a symmetry relationship to the skeleton.

Parameters:

Name Type Description Default
node1 Symmetry | Union

The first node specified as a Node, name or index. If a Symmetry object is provided, it will be added directly to the skeleton.

None
node2 Union | None

The second node specified as a Node, name or index.

None
Source code in sleap_io/model/skeleton.py
def add_symmetry(
    self, node1: Symmetry | NodeOrIndex = None, node2: NodeOrIndex | None = None
):
    """Add a symmetry relationship to the skeleton.

    Args:
        node1: The first node specified as a `Node`, name or index. If a `Symmetry`
            object is provided, it will be added directly to the skeleton.
        node2: The second node specified as a `Node`, name or index.
    """
    symmetry = None
    if type(node1) is Symmetry:
        symmetry = node1
        node1, node2 = symmetry

    node1 = self.require_node(node1)
    node2 = self.require_node(node2)

    if symmetry is None:
        symmetry = Symmetry({node1, node2})

    if symmetry not in self.symmetries:
        self.symmetries.append(symmetry)

get_flipped_node_inds()

Returns node indices that should be switched when horizontally flipping.

This is useful as a lookup table for flipping the landmark coordinates when doing data augmentation.

Example

skel = Skeleton(["A", "B_left", "B_right", "C", "D_left", "D_right"]) skel.add_symmetry("B_left", "B_right") skel.add_symmetry("D_left", "D_right") skel.flipped_node_inds [0, 2, 1, 3, 5, 4] pose = np.array([[0, 0], [1, 1], [2, 2], [3, 3], [4, 4], [5, 5]]) pose[skel.flipped_node_inds] array([[0, 0], [2, 2], [1, 1], [3, 3], [5, 5], [4, 4]])

Source code in sleap_io/model/skeleton.py
def get_flipped_node_inds(self) -> list[int]:
    """Returns node indices that should be switched when horizontally flipping.

    This is useful as a lookup table for flipping the landmark coordinates when
    doing data augmentation.

    Example:
        >>> skel = Skeleton(["A", "B_left", "B_right", "C", "D_left", "D_right"])
        >>> skel.add_symmetry("B_left", "B_right")
        >>> skel.add_symmetry("D_left", "D_right")
        >>> skel.flipped_node_inds
        [0, 2, 1, 3, 5, 4]
        >>> pose = np.array([[0, 0], [1, 1], [2, 2], [3, 3], [4, 4], [5, 5]])
        >>> pose[skel.flipped_node_inds]
        array([[0, 0],
               [2, 2],
               [1, 1],
               [3, 3],
               [5, 5],
               [4, 4]])
    """
    flip_idx = np.arange(len(self.nodes))
    if len(self.symmetries) > 0:
        symmetry_inds = np.array(
            [(self.index(a), self.index(b)) for a, b in self.symmetries]
        )
        flip_idx[symmetry_inds[:, 0]] = symmetry_inds[:, 1]
        flip_idx[symmetry_inds[:, 1]] = symmetry_inds[:, 0]

    flip_idx = flip_idx.tolist()
    return flip_idx

index(node)

Return the index of a node specified as a Node or string name.

Source code in sleap_io/model/skeleton.py
def index(self, node: Node | str) -> int:
    """Return the index of a node specified as a `Node` or string name."""
    if type(node) is str:
        return self.index(self._name_to_node_cache[node])
    elif type(node) is Node:
        return self._node_to_ind_cache[node]
    else:
        raise IndexError(f"Invalid indexing argument for skeleton: {node}")

infer_symmetries_by_name(token_pairs=None)

Infer left/right symmetric node pairs from node names.

Useful when a skeleton has no symmetries defined (e.g. imported from a format that does not carry symmetry metadata) but its node names encode laterality, so that flip-dependent tooling (augmentation, QC) still works. Names are matched by splitting on separators (_, -, ., space), camelCase boundaries, and letter/digit boundaries, then pairing nodes that share a stem but differ by a single left/right token. For example, Ear_L/Ear_R, left_eye/right_eye, LeftPaw/RightPaw, and L1/R1 all pair up.

This is intentionally non-mutating and conservative: it returns suggested pairs rather than writing them onto the skeleton, since a wrong guess would silently corrupt flip augmentation. Apply the result explicitly if desired, e.g. skel.add_symmetries(skel.infer_symmetries_by_name()). Node names without a delimited or camelCase/digit token boundary (e.g. larm) and truly non-semantic pairings (e.g. L1/L2) cannot be inferred and must be declared with add_symmetry.

Parameters:

Name Type Description Default
token_pairs list[tuple[str, str]] | None

List of (left_token, right_token) string pairs used to recognize laterality, matched case-insensitively against whole name segments. Defaults to [("left", "right"), ("l", "r")].

None

Returns:

Type Description
list[tuple[int, int]]

A list of (left_index, right_index) node-index pairs, ordered by left index. Each node appears in at most one pair, and only stems with exactly one left and one right member are paired (ambiguous groups are skipped).

Example

skel = Skeleton(["nose", "eye_L", "eye_R", "ear_L", "ear_R"]) skel.infer_symmetries_by_name() [(1, 2), (3, 4)] skel.add_symmetries(skel.infer_symmetries_by_name()) skel.symmetry_names [('eye_L', 'eye_R'), ('ear_L', 'ear_R')]

Source code in sleap_io/model/skeleton.py
def infer_symmetries_by_name(
    self,
    token_pairs: list[tuple[str, str]] | None = None,
) -> list[tuple[int, int]]:
    """Infer left/right symmetric node pairs from node names.

    Useful when a skeleton has no symmetries defined (e.g. imported from a
    format that does not carry symmetry metadata) but its node names encode
    laterality, so that flip-dependent tooling (augmentation, QC) still
    works. Names are matched by splitting on separators (`_`, `-`, `.`,
    space), camelCase boundaries, and letter/digit boundaries, then pairing
    nodes that share a stem but differ by a single left/right token. For
    example, `Ear_L`/`Ear_R`, `left_eye`/`right_eye`, `LeftPaw`/`RightPaw`,
    and `L1`/`R1` all pair up.

    This is intentionally **non-mutating** and conservative: it returns
    suggested pairs rather than writing them onto the skeleton, since a wrong
    guess would silently corrupt flip augmentation. Apply the result
    explicitly if desired, e.g.
    `skel.add_symmetries(skel.infer_symmetries_by_name())`. Node names
    without a delimited or camelCase/digit token boundary (e.g. `larm`) and
    truly non-semantic pairings (e.g. `L1`/`L2`) cannot be inferred and must
    be declared with `add_symmetry`.

    Args:
        token_pairs: List of `(left_token, right_token)` string pairs used to
            recognize laterality, matched case-insensitively against whole
            name segments. Defaults to `[("left", "right"), ("l", "r")]`.

    Returns:
        A list of `(left_index, right_index)` node-index pairs, ordered by
        left index. Each node appears in at most one pair, and only stems
        with exactly one left and one right member are paired (ambiguous
        groups are skipped).

    Example:
        >>> skel = Skeleton(["nose", "eye_L", "eye_R", "ear_L", "ear_R"])
        >>> skel.infer_symmetries_by_name()
        [(1, 2), (3, 4)]
        >>> skel.add_symmetries(skel.infer_symmetries_by_name())
        >>> skel.symmetry_names
        [('eye_L', 'eye_R'), ('ear_L', 'ear_R')]
    """
    return infer_symmetry_pairs_by_name(self.node_names, token_pairs=token_pairs)

match_nodes(other_nodes)

Return the order of nodes in the skeleton.

Parameters:

Name Type Description Default
other_nodes list[str, Node]

A list of node names or Node objects.

required

Returns:

Type Description
tuple[list[int], list[int]]

A tuple of skeleton_inds,other_inds`.

skeleton_inds contains the indices of the nodes in the skeleton that match the input nodes.

other_inds contains the indices of the input nodes that match the nodes in the skeleton.

These can be used to reorder point data to match the order of nodes in the skeleton.

See also: match_nodes_cached

Source code in sleap_io/model/skeleton.py
def match_nodes(self, other_nodes: list[str, Node]) -> tuple[list[int], list[int]]:
    """Return the order of nodes in the skeleton.

    Args:
        other_nodes: A list of node names or `Node` objects.

    Returns:
        A tuple of `skeleton_inds, `other_inds`.

        `skeleton_inds` contains the indices of the nodes in the skeleton that match
        the input nodes.

        `other_inds` contains the indices of the input nodes that match the nodes in
        the skeleton.

        These can be used to reorder point data to match the order of nodes in the
        skeleton.

    See also: match_nodes_cached
    """
    if isinstance(other_nodes, np.ndarray):
        other_nodes = other_nodes.tolist()
    if type(other_nodes) is not tuple:
        other_nodes = [x.name if type(x) is Node else x for x in other_nodes]

    skeleton_inds, other_inds = match_nodes_cached(
        tuple(self.node_names), tuple(other_nodes)
    )

    return list(skeleton_inds), list(other_inds)

matches(other, require_same_order=False)

Check if this skeleton matches another skeleton's structure.

Parameters:

Name Type Description Default
other Skeleton

Another skeleton to compare with.

required
require_same_order bool

If True, nodes must be in the same order. If False, only the node names and edges need to match.

False

Returns:

Type Description
bool

True if the skeletons match, False otherwise.

Notes

Two skeletons match if they have the same nodes (by name) and edges. If require_same_order is True, the nodes must also be in the same order.

Source code in sleap_io/model/skeleton.py
def matches(self, other: "Skeleton", require_same_order: bool = False) -> bool:
    """Check if this skeleton matches another skeleton's structure.

    Args:
        other: Another skeleton to compare with.
        require_same_order: If True, nodes must be in the same order.
            If False, only the node names and edges need to match.

    Returns:
        True if the skeletons match, False otherwise.

    Notes:
        Two skeletons match if they have the same nodes (by name) and edges.
        If require_same_order is True, the nodes must also be in the same order.
    """
    # Check if we have the same number of nodes
    if len(self.nodes) != len(other.nodes):
        return False

    # Check node names
    if require_same_order:
        if self.node_names != other.node_names:
            return False
    else:
        if set(self.node_names) != set(other.node_names):
            return False

    # Check edges (considering node name mapping if order differs)
    if len(self.edges) != len(other.edges):
        return False

    # Create edge sets for comparison
    self_edge_set = {
        (edge.source.name, edge.destination.name) for edge in self.edges
    }
    other_edge_set = {
        (edge.source.name, edge.destination.name) for edge in other.edges
    }

    if self_edge_set != other_edge_set:
        return False

    # Check symmetries
    if len(self.symmetries) != len(other.symmetries):
        return False

    self_sym_set = {
        frozenset(node.name for node in sym.nodes) for sym in self.symmetries
    }
    other_sym_set = {
        frozenset(node.name for node in sym.nodes) for sym in other.symmetries
    }

    return self_sym_set == other_sym_set

node_similarities(other)

Calculate node overlap metrics with another skeleton.

Parameters:

Name Type Description Default
other Skeleton

Another skeleton to compare with.

required

Returns:

Type Description
dict[str, float]

A dictionary with similarity metrics: - 'n_common': Number of nodes in common - 'n_self_only': Number of nodes only in this skeleton - 'n_other_only': Number of nodes only in the other skeleton - 'jaccard': Jaccard similarity (intersection/union) - 'dice': Dice coefficient (2*intersection/(n_self + n_other))

Source code in sleap_io/model/skeleton.py
def node_similarities(self, other: "Skeleton") -> dict[str, float]:
    """Calculate node overlap metrics with another skeleton.

    Args:
        other: Another skeleton to compare with.

    Returns:
        A dictionary with similarity metrics:
        - 'n_common': Number of nodes in common
        - 'n_self_only': Number of nodes only in this skeleton
        - 'n_other_only': Number of nodes only in the other skeleton
        - 'jaccard': Jaccard similarity (intersection/union)
        - 'dice': Dice coefficient (2*intersection/(n_self + n_other))
    """
    self_nodes = set(self.node_names)
    other_nodes = set(other.node_names)

    n_common = len(self_nodes & other_nodes)
    n_self_only = len(self_nodes - other_nodes)
    n_other_only = len(other_nodes - self_nodes)
    n_union = len(self_nodes | other_nodes)

    jaccard = n_common / n_union if n_union > 0 else 0
    dice = (
        2 * n_common / (len(self_nodes) + len(other_nodes))
        if (len(self_nodes) + len(other_nodes)) > 0
        else 0
    )

    return {
        "n_common": n_common,
        "n_self_only": n_self_only,
        "n_other_only": n_other_only,
        "jaccard": jaccard,
        "dice": dice,
    }

rebuild_cache(nodes=None)

Rebuild the node name/index to Node map caches.

Parameters:

Name Type Description Default
nodes list[Node] | None

A list of Node objects to update the cache with. If not provided, the cache will be updated with the current nodes in the skeleton. If nodes are provided, the cache will be updated with the provided nodes, but the current nodes in the skeleton will not be updated. Default is None.

None
Notes

This function should be called when nodes or node list is mutated to update the lookup caches for indexing nodes by name or Node object.

This is done automatically when nodes are added or removed from the skeleton using the convenience methods in this class.

This method only needs to be used when manually mutating nodes or the node list directly.

Source code in sleap_io/model/skeleton.py
def rebuild_cache(self, nodes: list[Node] | None = None):
    """Rebuild the node name/index to `Node` map caches.

    Args:
        nodes: A list of `Node` objects to update the cache with. If not provided,
            the cache will be updated with the current nodes in the skeleton. If
            nodes are provided, the cache will be updated with the provided nodes,
            but the current nodes in the skeleton will not be updated. Default is
            `None`.

    Notes:
        This function should be called when nodes or node list is mutated to update
        the lookup caches for indexing nodes by name or `Node` object.

        This is done automatically when nodes are added or removed from the skeleton
        using the convenience methods in this class.

        This method only needs to be used when manually mutating nodes or the node
        list directly.
    """
    if nodes is None:
        nodes = self.nodes
    self._name_to_node_cache = {node.name: node for node in nodes}
    self._node_to_ind_cache = {node: i for i, node in enumerate(nodes)}

remove_node(node)

Remove a single node from the skeleton.

Parameters:

Name Type Description Default
node Union

The node to remove. Can be specified as a string name, integer index, or Node object.

required
Notes

This method handles updating the lookup caches necessary for indexing nodes by name.

Any edges and symmetries that are connected to the removed node will also be removed.

Warning

This method does NOT update instances that use this skeleton to reflect changes.

It is recommended to use the Labels.remove_nodes() method which will update all contained instances to reflect the changes made to the skeleton.

To manually update instances after this method is called, call Instance.update_skeleton() on each instance that uses this skeleton.

Source code in sleap_io/model/skeleton.py
def remove_node(self, node: NodeOrIndex):
    """Remove a single node from the skeleton.

    Args:
        node: The node to remove. Can be specified as a string name, integer index,
            or `Node` object.

    Notes:
        This method handles updating the lookup caches necessary for indexing nodes
        by name.

        Any edges and symmetries that are connected to the removed node will also be
        removed.

    Warning:
        **This method does NOT update instances** that use this skeleton to reflect
        changes.

        It is recommended to use the `Labels.remove_nodes()` method which will
        update all contained instances to reflect the changes made to the skeleton.

        To manually update instances after this method is called, call
        `Instance.update_skeleton()` on each instance that uses this skeleton.
    """
    self.remove_nodes([node])

remove_nodes(nodes)

Remove nodes from the skeleton.

Parameters:

Name Type Description Default
nodes list[Union]

A list of node names, indices, or Node objects to remove.

required
Notes

This method handles updating the lookup caches necessary for indexing nodes by name.

Any edges and symmetries that are connected to the removed nodes will also be removed.

Warning

This method does NOT update instances that use this skeleton to reflect changes.

It is recommended to use the Labels.remove_nodes() method which will update all contained to reflect the changes made to the skeleton.

To manually update instances after this method is called, call instance.update_nodes() on each instance that uses this skeleton.

Source code in sleap_io/model/skeleton.py
def remove_nodes(self, nodes: list[NodeOrIndex]):
    """Remove nodes from the skeleton.

    Args:
        nodes: A list of node names, indices, or `Node` objects to remove.

    Notes:
        This method handles updating the lookup caches necessary for indexing nodes
        by name.

        Any edges and symmetries that are connected to the removed nodes will also
        be removed.

    Warning:
        **This method does NOT update instances** that use this skeleton to reflect
        changes.

        It is recommended to use the `Labels.remove_nodes()` method which will
        update all contained to reflect the changes made to the skeleton.

        To manually update instances after this method is called, call
        `instance.update_nodes()` on each instance that uses this skeleton.
    """
    # Standardize input and make a pre-mutation copy before keys are changed.
    rm_node_objs = [self.require_node(node, add_missing=False) for node in nodes]

    # Remove nodes from the skeleton.
    for node in rm_node_objs:
        self.nodes.remove(node)
        del self._name_to_node_cache[node.name]

    # Remove edges connected to the removed nodes.
    self.edges = [
        edge
        for edge in self.edges
        if edge.source not in rm_node_objs and edge.destination not in rm_node_objs
    ]

    # Remove symmetries connected to the removed nodes.
    self.symmetries = [
        symmetry
        for symmetry in self.symmetries
        if symmetry.nodes.isdisjoint(rm_node_objs)
    ]

    # Update node index map.
    self.rebuild_cache()

rename_node(old_name, new_name)

Rename a single node in the skeleton.

Parameters:

Name Type Description Default
old_name Union

The name of the node to rename. Can also be specified as an integer index or Node object.

required
new_name str

The new name for the node.

required
Source code in sleap_io/model/skeleton.py
def rename_node(self, old_name: NodeOrIndex, new_name: str):
    """Rename a single node in the skeleton.

    Args:
        old_name: The name of the node to rename. Can also be specified as an
            integer index or `Node` object.
        new_name: The new name for the node.
    """
    self.rename_nodes({old_name: new_name})

rename_nodes(name_map)

Rename nodes in the skeleton.

Parameters:

Name Type Description Default
name_map dict[Union, str] | list[str]

A dictionary mapping old node names to new node names. Keys can be specified as Node objects, integer indices, or string names. Values must be specified as string names.

If a list of strings is provided of the same length as the current nodes, the nodes will be renamed to the names in the list in order.

required

Raises:

Type Description
ValueError

If the new node names exist in the skeleton or if the old node names are not found in the skeleton.

Notes

This method should always be used when renaming nodes in the skeleton as it handles updating the lookup caches necessary for indexing nodes by name.

After renaming, instances using this skeleton do NOT need to be updated as the nodes are stored by reference in the skeleton, so changes are reflected automatically.

Example

skel = Skeleton(["A", "B", "C"], edges=[("A", "B"), ("B", "C")]) skel.rename_nodes({"A": "X", "B": "Y", "C": "Z"}) skel.node_names ["X", "Y", "Z"] skel.rename_nodes(["a", "b", "c"]) skel.node_names ["a", "b", "c"]

Source code in sleap_io/model/skeleton.py
def rename_nodes(self, name_map: dict[NodeOrIndex, str] | list[str]):
    """Rename nodes in the skeleton.

    Args:
        name_map: A dictionary mapping old node names to new node names. Keys can be
            specified as `Node` objects, integer indices, or string names. Values
            must be specified as string names.

            If a list of strings is provided of the same length as the current
            nodes, the nodes will be renamed to the names in the list in order.

    Raises:
        ValueError: If the new node names exist in the skeleton or if the old node
            names are not found in the skeleton.

    Notes:
        This method should always be used when renaming nodes in the skeleton as it
        handles updating the lookup caches necessary for indexing nodes by name.

        After renaming, instances using this skeleton **do NOT need to be updated**
        as the nodes are stored by reference in the skeleton, so changes are
        reflected automatically.

    Example:
        >>> skel = Skeleton(["A", "B", "C"], edges=[("A", "B"), ("B", "C")])
        >>> skel.rename_nodes({"A": "X", "B": "Y", "C": "Z"})
        >>> skel.node_names
        ["X", "Y", "Z"]
        >>> skel.rename_nodes(["a", "b", "c"])
        >>> skel.node_names
        ["a", "b", "c"]
    """
    if type(name_map) is list:
        if len(name_map) != len(self.nodes):
            raise ValueError(
                "List of new node names must be the same length as the current "
                "nodes."
            )
        name_map = {node: name for node, name in zip(self.nodes, name_map)}

    for old_name, new_name in name_map.items():
        if type(old_name) is Node:
            old_name = old_name.name
        if type(old_name) is int:
            old_name = self.nodes[old_name].name

        if old_name not in self._name_to_node_cache:
            raise ValueError(f"Node '{old_name}' not found in the skeleton.")
        if new_name in self._name_to_node_cache:
            raise ValueError(f"Node '{new_name}' already exists in the skeleton.")

        node = self._name_to_node_cache[old_name]
        node.name = new_name
        self._name_to_node_cache[new_name] = node
        del self._name_to_node_cache[old_name]

reorder_nodes(new_order)

Reorder nodes in the skeleton.

Parameters:

Name Type Description Default
new_order list[Union]

A list of node names, indices, or Node objects specifying the new order of the nodes.

required

Raises:

Type Description
ValueError

If the new order of nodes is not the same length as the current nodes.

Notes

This method handles updating the lookup caches necessary for indexing nodes by name.

Warning

After reordering, instances using this skeleton do not need to be updated as the nodes are stored by reference in the skeleton.

However, the order that points are stored in the instances will not be updated to match the new order of the nodes in the skeleton. This should not matter unless the ordering of the keys in the Instance.points dictionary is used instead of relying on the skeleton node order.

To make sure these are aligned, it is recommended to use the Labels.reorder_nodes() method which will update all contained instances to reflect the changes made to the skeleton.

To manually update instances after this method is called, call Instance.update_skeleton() on each instance that uses this skeleton.

Source code in sleap_io/model/skeleton.py
def reorder_nodes(self, new_order: list[NodeOrIndex]):
    """Reorder nodes in the skeleton.

    Args:
        new_order: A list of node names, indices, or `Node` objects specifying the
            new order of the nodes.

    Raises:
        ValueError: If the new order of nodes is not the same length as the current
            nodes.

    Notes:
        This method handles updating the lookup caches necessary for indexing nodes
        by name.

    Warning:
        After reordering, instances using this skeleton do not need to be updated as
        the nodes are stored by reference in the skeleton.

        However, the order that points are stored in the instances will not be
        updated to match the new order of the nodes in the skeleton. This should not
        matter unless the ordering of the keys in the `Instance.points` dictionary
        is used instead of relying on the skeleton node order.

        To make sure these are aligned, it is recommended to use the
        `Labels.reorder_nodes()` method which will update all contained instances to
        reflect the changes made to the skeleton.

        To manually update instances after this method is called, call
        `Instance.update_skeleton()` on each instance that uses this skeleton.
    """
    if len(new_order) != len(self.nodes):
        raise ValueError(
            "New order of nodes must be the same length as the current nodes."
        )

    new_nodes = [self.require_node(node, add_missing=False) for node in new_order]
    self.nodes = new_nodes

require_node(node, add_missing=True)

Return a Node object, handling indexing and adding missing nodes.

Parameters:

Name Type Description Default
node Union

A Node object, name or index.

required
add_missing bool

If True, missing nodes will be added to the skeleton. If False, an error will be raised if the node is not found. Default is True.

True

Returns:

Type Description
Node

The Node object.

Raises:

Type Description
IndexError

If the node is not found in the skeleton and add_missing is False.

Source code in sleap_io/model/skeleton.py
def require_node(self, node: NodeOrIndex, add_missing: bool = True) -> Node:
    """Return a `Node` object, handling indexing and adding missing nodes.

    Args:
        node: A `Node` object, name or index.
        add_missing: If `True`, missing nodes will be added to the skeleton. If
            `False`, an error will be raised if the node is not found. Default is
            `True`.

    Returns:
        The `Node` object.

    Raises:
        IndexError: If the node is not found in the skeleton and `add_missing` is
            `False`.
    """
    if node not in self:
        if add_missing:
            self.add_node(node)
        else:
            raise IndexError(f"Node '{node}' not found in the skeleton.")

    if type(node) is Node:
        return node

    return self[node]

Track

An object that represents the same animal/object across multiple detections.

This allows tracking of unique entities in the video over time and space.

A Track may also be used to refer to unique identity classes that span multiple videos, such as "female mouse".

Attributes:

Name Type Description
name

A name given to this track for identification purposes.

Notes

Tracks are compared by identity. This means that unique track objects with the same name are considered to be different.

Methods:

Name Description
__init__

Method generated by attrs for class Track.

__repr__

Method generated by attrs for class Track.

matches

Check if this track matches another track.

similarity_to

Calculate similarity metrics with another track.

Source code in sleap_io/model/instance.py
@attrs.define(eq=False)
class Track:
    """An object that represents the same animal/object across multiple detections.

    This allows tracking of unique entities in the video over time and space.

    A `Track` may also be used to refer to unique identity classes that span multiple
    videos, such as `"female mouse"`.

    Attributes:
        name: A name given to this track for identification purposes.

    Notes:
        `Track`s are compared by identity. This means that unique track objects with the
        same name are considered to be different.
    """

    name: str = ""

    def matches(self, other: "Track", method: str = "name") -> bool:
        """Check if this track matches another track.

        Args:
            other: Another track to compare with.
            method: Matching method - "name" (match by name) or "identity"
                (match by object identity).

        Returns:
            True if the tracks match according to the specified method.
        """
        if method == "name":
            return self.name == other.name
        elif method == "identity":
            return self is other
        else:
            raise ValueError(f"Unknown matching method: {method}")

    def similarity_to(self, other: "Track") -> dict[str, any]:
        """Calculate similarity metrics with another track.

        Args:
            other: Another track to compare with.

        Returns:
            A dictionary with similarity metrics:
            - 'same_name': Whether the tracks have the same name
            - 'same_identity': Whether the tracks are the same object
            - 'name_similarity': Simple string similarity score (0-1)
        """
        # Calculate simple string similarity
        if self.name and other.name:
            # Simple character overlap similarity
            common_chars = set(self.name.lower()) & set(other.name.lower())
            all_chars = set(self.name.lower()) | set(other.name.lower())
            name_similarity = len(common_chars) / len(all_chars) if all_chars else 0
        else:
            name_similarity = 1.0 if self.name == other.name else 0.0

        return {
            "same_name": self.name == other.name,
            "same_identity": self is other,
            "name_similarity": name_similarity,
        }

__annotations__ = {'name': 'str'} 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__ = False 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__ = 'An object that represents the same animal/object across multiple detections.\n\nThis allows tracking of unique entities in the video over time and space.\n\nA `Track` may also be used to refer to unique identity classes that span multiple\nvideos, such as `"female mouse"`.\n\nAttributes:\n name: A name given to this track for identification purposes.\n\nNotes:\n `Track`s are compared by identity. This means that unique track objects with the\n same name are considered to be different.\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__ = 332 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__ = ('name',) 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.instance' 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__ = ('name', '__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

__init__(name='')

Method generated by attrs for class Track.

Source code in sleap_io/model/instance.py
from sleap_io.model.category import Category, to_category

__repr__()

Method generated by attrs for class Track.

Source code in sleap_io/model/instance.py
"""Data structures for data associated with a single instance such as an animal.

The `Instance` class is a SLEAP data structure that contains a collection of points that
correspond to landmarks within a `Skeleton`.

`PredictedInstance` additionally contains metadata associated with how the instance was
estimated, such as confidence scores.
"""

from __future__ import annotations

from typing import TYPE_CHECKING

import attrs
import numpy as np

matches(other, method='name')

Check if this track matches another track.

Parameters:

Name Type Description Default
other Track

Another track to compare with.

required
method str

Matching method - "name" (match by name) or "identity" (match by object identity).

'name'

Returns:

Type Description
bool

True if the tracks match according to the specified method.

Source code in sleap_io/model/instance.py
def matches(self, other: "Track", method: str = "name") -> bool:
    """Check if this track matches another track.

    Args:
        other: Another track to compare with.
        method: Matching method - "name" (match by name) or "identity"
            (match by object identity).

    Returns:
        True if the tracks match according to the specified method.
    """
    if method == "name":
        return self.name == other.name
    elif method == "identity":
        return self is other
    else:
        raise ValueError(f"Unknown matching method: {method}")

similarity_to(other)

Calculate similarity metrics with another track.

Parameters:

Name Type Description Default
other Track

Another track to compare with.

required

Returns:

Type Description
dict[str, any]

A dictionary with similarity metrics: - 'same_name': Whether the tracks have the same name - 'same_identity': Whether the tracks are the same object - 'name_similarity': Simple string similarity score (0-1)

Source code in sleap_io/model/instance.py
def similarity_to(self, other: "Track") -> dict[str, any]:
    """Calculate similarity metrics with another track.

    Args:
        other: Another track to compare with.

    Returns:
        A dictionary with similarity metrics:
        - 'same_name': Whether the tracks have the same name
        - 'same_identity': Whether the tracks are the same object
        - 'name_similarity': Simple string similarity score (0-1)
    """
    # Calculate simple string similarity
    if self.name and other.name:
        # Simple character overlap similarity
        common_chars = set(self.name.lower()) & set(other.name.lower())
        all_chars = set(self.name.lower()) | set(other.name.lower())
        name_similarity = len(common_chars) / len(all_chars) if all_chars else 0
    else:
        name_similarity = 1.0 if self.name == other.name else 0.0

    return {
        "same_name": self.name == other.name,
        "same_identity": self is other,
        "name_similarity": name_similarity,
    }

to_category(value)

Coerce a category-like value to a Category (or None).

Promotes the legacy free-form category: str field (the object-detection class label) to a first-class Category, keeping existing category="mouse" call sites working:

  • None or the empty string "" (the old "unset" sentinel) -> None.
  • a non-empty str -> Category(name=value).
  • an existing Category -> returned unchanged.

Parameters:

Name Type Description Default
value Category | str | None

A Category, a class-label string, "", or None.

required

Returns:

Type Description
Category | None

A Category, or None if the input was None / "".

Raises:

Type Description
TypeError

If value is not a Category, str, or None.

Source code in sleap_io/model/category.py
def to_category(value: "Category | str | None") -> "Category | None":
    """Coerce a category-like value to a `Category` (or ``None``).

    Promotes the legacy free-form ``category: str`` field (the object-detection
    class label) to a first-class `Category`, keeping existing ``category="mouse"``
    call sites working:

    - ``None`` or the empty string ``""`` (the old "unset" sentinel) -> ``None``.
    - a non-empty ``str`` -> ``Category(name=value)``.
    - an existing `Category` -> returned unchanged.

    Args:
        value: A `Category`, a class-label string, ``""``, or ``None``.

    Returns:
        A `Category`, or ``None`` if the input was ``None`` / ``""``.

    Raises:
        TypeError: If `value` is not a `Category`, `str`, or ``None``.
    """
    if value is None:
        return None
    if isinstance(value, Category):
        return value
    if isinstance(value, str):
        if value == "":
            return None
        return Category(name=value)
    raise TypeError(
        f"category must be a Category, str, or None, got {type(value).__name__}."
    )