Skip to content

label_image

sleap_io.model.label_image

Data structure for integer label image annotations.

Label images represent per-pixel object segmentation for a single video frame, where each pixel value encodes which object occupies that pixel. This is the standard output format of instance segmentation tools like Cellpose and StarDist.

Unlike binary SegmentationMask objects (one mask per object), a single LabelImage efficiently stores all objects for a frame in one dense integer array.

When to use LabelImage vs SegmentationMask:

  • Use LabelImage when you have a dense integer array from a segmentation tool (Cellpose, StarDist, COCO panoptic) where each pixel value identifies an object. One LabelImage per frame stores all objects at once.
  • Use SegmentationMask when you have individual binary masks per object (e.g., from Mask R-CNN, manual annotation, or ROI-based workflows). Each mask is stored separately with RLE compression.
  • Use LabelImage.from_binary_masks() to create a label image directly from per-object binary numpy arrays (e.g., from SAM or Mask R-CNN output).
  • To convert between them, use LabelImage.to_masks() and LabelImage.from_masks().

For TIFF I/O of label images, see sleap_io.load_label_images() and sleap_io.save_label_images().

See Also

sleap_io.model.mask: Binary segmentation masks (one per object).

Classes:

Name Description
LabelImage

Per-pixel object segmentation for a single video frame.

PredictedLabelImage

Model-predicted label image with confidence score.

UserLabelImage

Human-annotated label image.

Functions:

Name Description
normalize_label_ids

Remap label IDs so each group gets a globally consistent ID.

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__/label_image.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 structure for integer label image annotations.\n\nLabel images represent per-pixel object segmentation for a single video frame,\nwhere each pixel value encodes which object occupies that pixel. This is the\nstandard output format of instance segmentation tools like Cellpose and StarDist.\n\nUnlike binary ``SegmentationMask`` objects (one mask per object), a single\n``LabelImage`` efficiently stores all objects for a frame in one dense integer\narray.\n\n**When to use LabelImage vs SegmentationMask:**\n\n- Use ``LabelImage`` when you have a dense integer array from a segmentation\n tool (Cellpose, StarDist, COCO panoptic) where each pixel value identifies\n an object. One ``LabelImage`` per frame stores all objects at once.\n- Use ``SegmentationMask`` when you have individual binary masks per object\n (e.g., from Mask R-CNN, manual annotation, or ROI-based workflows). Each\n mask is stored separately with RLE compression.\n- Use ``LabelImage.from_binary_masks()`` to create a label image directly from\n per-object binary numpy arrays (e.g., from SAM or Mask R-CNN output).\n- To convert between them, use ``LabelImage.to_masks()`` and\n ``LabelImage.from_masks()``.\n\nFor TIFF I/O of label images, see ``sleap_io.load_label_images()`` and\n``sleap_io.save_label_images()``.\n\nSee Also:\n ``sleap_io.model.mask``: Binary segmentation masks (one per object).\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/label_image.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.label_image' 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'.

LabelImage

Per-pixel object segmentation for a single video frame.

Each pixel is either background (0) or belongs to a tracked, categorized object. The integer values in data are an internal encoding. Use the track-centric API (__getitem__, tracks, items) to query objects without dealing with raw label IDs.

Attributes:

Name Type Description
data

Integer array of shape (H, W) with dtype int32. 0 is background, positive values are object IDs.

objects

Mapping from label ID to object metadata. Defines what each non-zero pixel value represents. Label IDs not in this dict are treated as having default (empty) metadata.

source

Source identifier string.

scale

Resolution ratio (sx, sy) where sx = label_width / image_width and sy = label_height / image_height. (1.0, 1.0) means full resolution. (0.5, 0.5) means half resolution. Coordinate mapping: image_coord = label_coord / scale + offset.

offset

Origin (x, y) of the label image in image pixel coordinates.

See Also

SegmentationMask: Per-object binary masks (one mask per object). sleap_io.load_label_images: Load label images from TIFF files. sleap_io.save_label_images: Save label images to TIFF files.

Classes:

Name Description
Info

Metadata for one segmented object within a LabelImage.

Methods:

Name Description
__attrs_post_init__

Validate and normalize data array on construction.

__contains__

Whether this track has pixels in this frame.

__deepcopy__

Deep copy that materializes lazy data before copying.

__getitem__

Get binary (H, W) mask for a tracked object.

__init__

Method generated by attrs for class LabelImage.

__repr__

Method generated by attrs for class LabelImage.

from_binary_masks

Create a LabelImage from per-object binary mask arrays.

from_masks

Compose from binary SegmentationMasks.

from_numpy

Create from an integer array.

from_stack

Create label images from a stack of frames.

get_category_mask

Union mask of all objects matching a category.

get_track_mask

Get binary (H, W) mask for a tracked object. Same as getitem.

items

Iterate over objects as (track, category, mask) tuples.

resampled

Return a new label image resampled to the target dimensions.

to_bboxes

Extract tight bounding boxes for each object in the label image.

to_masks

Decompose into per-object binary SegmentationMasks.

Source code in sleap_io/model/label_image.py
@attrs.define(eq=False)
class LabelImage:
    """Per-pixel object segmentation for a single video frame.

    Each pixel is either background (0) or belongs to a tracked, categorized
    object. The integer values in ``data`` are an internal encoding. Use the
    track-centric API (``__getitem__``, ``tracks``, ``items``) to query objects
    without dealing with raw label IDs.

    Attributes:
        data: Integer array of shape ``(H, W)`` with dtype int32. ``0`` is
            background, positive values are object IDs.
        objects: Mapping from label ID to object metadata. Defines what each
            non-zero pixel value represents. Label IDs not in this dict are
            treated as having default (empty) metadata.
        source: Source identifier string.
        scale: Resolution ratio ``(sx, sy)`` where ``sx = label_width / image_width``
            and ``sy = label_height / image_height``. ``(1.0, 1.0)`` means full
            resolution. ``(0.5, 0.5)`` means half resolution. Coordinate mapping:
            ``image_coord = label_coord / scale + offset``.
        offset: Origin ``(x, y)`` of the label image in image pixel coordinates.

    See Also:
        ``SegmentationMask``: Per-object binary masks (one mask per object).
        ``sleap_io.load_label_images``: Load label images from TIFF files.
        ``sleap_io.save_label_images``: Save label images to TIFF files.
    """

    @attrs.define
    class Info:
        """Metadata for one segmented object within a ``LabelImage``.

        Attributes:
            track: Track identity for cross-frame association. ``None`` if
                untracked.
            tracking_score: Confidence of the track identity assignment.
                ``None`` if unassigned or manually assigned.
            category: Semantic class label (e.g., ``"neuron"``, ``"glia"``).
            name: Human-readable name (e.g., ``"cell_042"``).
            instance: Linked pose ``Instance``, if any.
        """

        track: "Track | None" = None
        tracking_score: float | None = None
        category: str = ""
        name: str = ""
        instance: "Instance | None" = None
        score: float | None = None

        # Private: deferred instance index for lazy loading. When label images
        # are read from a file without materialized instances (e.g., lazy mode),
        # this stores the raw instance_idx so it can be resolved later or
        # written back as-is.
        _instance_idx: int = attrs.field(default=-1, repr=False, eq=False, init=False)

    _data: "np.ndarray | None" = attrs.field(default=None, alias="data")
    objects: dict[int, Info] = Factory(dict)
    source: str = attrs.field(default="")
    scale: tuple[float, float] = attrs.field(default=(1.0, 1.0))
    offset: tuple[float, float] = attrs.field(default=(0.0, 0.0))

    # Private: lazy loading support. When set, data is decompressed on first
    # access via the .data property and cached. The loader is cleared after use.
    _lazy_loader: "Callable[[], np.ndarray] | None" = attrs.field(
        default=None, init=False, repr=False, eq=False
    )
    # Private: cached dimensions from metadata (avoids triggering lazy load for
    # height/width queries). Set by the I/O layer after construction.
    _height: int = attrs.field(default=0, init=False, repr=False, eq=False)
    _width: int = attrs.field(default=0, init=False, repr=False, eq=False)

    @property
    def data(self) -> np.ndarray:
        """Integer array of shape ``(H, W)`` with dtype int32.

        ``0`` is background, positive values are object IDs. When the label
        image was loaded lazily, the pixel data is decompressed on first access
        and cached for subsequent reads.
        """
        if self._data is None:
            if self._lazy_loader is not None:
                self._data = self._lazy_loader()
                self._lazy_loader = None
                self._validate_data()
            else:
                raise ValueError("LabelImage has no data and no lazy loader.")
        return self._data

    @data.setter
    def data(self, value: np.ndarray) -> None:
        self._data = value
        self._lazy_loader = None
        if value is not None:
            self._height = value.shape[0]
            self._width = value.shape[1]

    @property
    def is_predicted(self) -> bool:
        """Whether this label image is a model prediction."""
        return isinstance(self, PredictedLabelImage)

    def _validate_data(self) -> None:
        """Validate and normalize the data array.

        Called eagerly from ``__attrs_post_init__`` when data is provided at
        construction time, or lazily on first ``.data`` access when a
        ``_lazy_loader`` is used.
        """
        if self._data.ndim != 2:
            raise ValueError(
                f"LabelImage data must be 2D, got shape {self._data.shape}"
            )
        if np.any(self._data < 0):
            raise ValueError("LabelImage data must not contain negative values.")
        if self._data.dtype != np.int32:
            self._data = self._data.astype(np.int32)
        self._height = self._data.shape[0]
        self._width = self._data.shape[1]

    def __attrs_post_init__(self):
        """Validate and normalize data array on construction."""
        if type(self) is LabelImage:
            raise TypeError(
                "LabelImage is abstract. Use UserLabelImage or PredictedLabelImage."
            )
        if self._data is not None:
            self._validate_data()
        # When _data is None, validation is deferred to first .data access.
        if self.scale[0] <= 0 or self.scale[1] <= 0:
            raise ValueError(f"Scale values must be positive, got {self.scale}.")

    def __deepcopy__(self, memo: dict) -> "LabelImage":
        """Deep copy that materializes lazy data before copying.

        This is necessary because lazy loaders capture h5py dataset references
        which cannot be pickled/deepcopied.
        """
        # Materialize lazy data before copying (h5py refs can't survive deepcopy).
        if self._data is not None:
            data = self._data.copy()
        elif self._lazy_loader is not None:
            data = self.data.copy()  # Triggers lazy load, then copy
        else:
            data = None
        objects = {lid: copy.deepcopy(info, memo) for lid, info in self.objects.items()}

        kwargs: dict = dict(
            data=data,
            objects=objects,
            source=self.source,
            scale=self.scale,
            offset=self.offset,
        )
        if isinstance(self, PredictedLabelImage):
            sm = self.score_map
            kwargs["score"] = self.score
            kwargs["score_map"] = sm.copy() if sm is not None else None
            kwargs["score_map_scale"] = self.score_map_scale
            kwargs["score_map_offset"] = self.score_map_offset
        result = type(self)(**kwargs)
        memo[id(self)] = result
        return result

    @property
    def height(self) -> int:
        """Height of the label image in pixels."""
        if self._height > 0:
            return self._height
        return self.data.shape[0]

    @property
    def width(self) -> int:
        """Width of the label image in pixels."""
        if self._width > 0:
            return self._width
        return self.data.shape[1]

    @property
    def has_spatial_transform(self) -> bool:
        """Whether this label image has non-default scale or offset."""
        return self.scale != (1.0, 1.0) or self.offset != (0.0, 0.0)

    @property
    def image_extent(self) -> tuple[int, int]:
        """Image-space ``(height, width)`` this label image covers (excluding offset).

        Computed as ``(int(height / scale_y), int(width / scale_x))``.
        """
        return (
            int(self.height / self.scale[1]),
            int(self.width / self.scale[0]),
        )

    def resampled(self, target_height: int, target_width: int) -> Self:
        """Return a new label image resampled to the target dimensions.

        The returned label image has ``scale=(1.0, 1.0)`` and
        ``offset=(0.0, 0.0)`` with the data resized using nearest-neighbor
        interpolation to preserve label IDs.

        Args:
            target_height: Target height in pixels.
            target_width: Target width in pixels.

        Returns:
            A new label image of the same concrete type with resampled data.
        """
        from sleap_io.model.mask import _resize_nearest

        resized = _resize_nearest(self.data, target_height, target_width)
        objects: dict[int, LabelImage.Info] = {}
        for lid, info in self.objects.items():
            new_info = attrs.evolve(info)
            # Carry the deferred instance index through (init=False, so it is not
            # reproduced by attrs.evolve and must be set after construction;
            # mirrors how __deepcopy__ preserves the lazy association).
            new_info._instance_idx = info._instance_idx
            objects[lid] = new_info
        kwargs: dict = dict(
            data=resized,
            objects=objects,
            source=self.source,
            scale=(1.0, 1.0),
            offset=(0.0, 0.0),
        )
        if isinstance(self, PredictedLabelImage):
            kwargs["score"] = self.score
            if self.score_map is not None:
                kwargs["score_map"] = _resize_nearest(
                    self.score_map, target_height, target_width
                )
            kwargs["score_map_scale"] = (1.0, 1.0)
            kwargs["score_map_offset"] = (0.0, 0.0)
        return type(self)(**kwargs)

    @property
    def n_objects(self) -> int:
        """Number of unique non-zero labels present in data."""
        return len(self.label_ids)

    @property
    def label_ids(self) -> np.ndarray:
        """Sorted array of unique non-zero label values in data."""
        ids = np.unique(self.data)
        return ids[ids > 0]

    @property
    def tracks(self) -> list["Track"]:
        """Tracks present in this frame (from objects with non-None track)."""
        return [
            self.objects[lid].track
            for lid in sorted(self.objects)
            if self.objects[lid].track is not None
        ]

    @property
    def categories(self) -> set[str]:
        """Unique non-empty category strings present."""
        return {info.category for info in self.objects.values() if info.category != ""}

    def __getitem__(self, track: "Track") -> np.ndarray:
        """Get binary (H, W) mask for a tracked object.

        Args:
            track: The Track to look up.

        Returns:
            Boolean array of shape (H, W).

        Raises:
            KeyError: If the track is not present in this frame.
        """
        for label_id, info in self.objects.items():
            if info.track is track:
                return self.data == label_id
        raise KeyError(f"Track {track} not found in this LabelImage.")

    def __contains__(self, track: "Track") -> bool:
        """Whether this track has pixels in this frame."""
        return any(info.track is track for info in self.objects.values())

    def get_track_mask(self, track: "Track") -> np.ndarray:
        """Get binary (H, W) mask for a tracked object. Same as __getitem__."""
        return self[track]

    def get_category_mask(self, category: str) -> np.ndarray:
        """Union mask of all objects matching a category.

        Args:
            category: Semantic class label to filter by.

        Returns:
            Boolean array of shape (H, W). All-False if no objects match.
        """
        label_ids = [
            lid for lid, info in self.objects.items() if info.category == category
        ]
        if not label_ids:
            return np.zeros((self.height, self.width), dtype=bool)
        return np.isin(self.data, label_ids)

    def items(self) -> Iterator[tuple["Track | None", str, np.ndarray]]:
        """Iterate over objects as (track, category, mask) tuples.

        Yields one tuple per unique non-zero label ID, in sorted label order.
        """
        for label_id in np.sort(self.label_ids):
            lid = int(label_id)
            info = self.objects.get(lid, LabelImage.Info())
            yield info.track, info.category, self.data == lid

    @classmethod
    def from_numpy(
        cls,
        data: np.ndarray,
        tracks: "dict[int, Track] | list[Track] | None" = None,
        categories: dict[int, str] | list[str] | None = None,
        create_tracks: bool = False,
        **kwargs,
    ) -> "LabelImage":
        """Create from an integer array.

        Args:
            data: (H, W) integer array. Cast to int32.
            tracks: Maps label IDs to Tracks.

                - ``None``: no tracks unless ``create_tracks=True``.
                - ``list``: positional — ``tracks[i]`` maps to label ``i + 1``.
                - ``dict``: explicit ``{label_id: Track}`` mapping. When
                  combined with ``create_tracks=True``, the dict is used as
                  a shared accumulator — existing entries are reused and new
                  entries are added for unseen label IDs (mutated in place).
            categories: Same pattern as tracks, for category strings.

                - ``None``: no categories set.
                - ``list``: positional — ``categories[i]`` maps to label ``i + 1``.
                - ``dict``: explicit ``{label_id: category}`` mapping.
            create_tracks: If ``True`` and ``tracks`` is ``None``, auto-create
                one Track per unique non-zero label with Track.name set to the
                string of the label ID. If ``True`` and ``tracks`` is a dict,
                create new Tracks for any label IDs not already in the dict
                (the dict is mutated in place to accumulate mappings across
                calls). Default is ``False``.
            **kwargs: Passed to the LabelImage constructor (
                source).

        Returns:
            A ``LabelImage`` with populated ``objects`` dict.
        """
        from sleap_io.model.instance import Track

        data = np.asarray(data, dtype=np.int32)
        unique_ids = np.unique(data)
        unique_ids = unique_ids[unique_ids > 0]

        # Build track mapping
        track_map: dict[int, Track] = {}
        if tracks is None:
            if create_tracks:
                for lid in unique_ids:
                    track_map[int(lid)] = Track(name=str(int(lid)))
        elif isinstance(tracks, dict):
            track_map = dict(tracks)
            if create_tracks:
                # Accumulate: create new tracks for unseen IDs, mutate
                # the caller's dict in place so it stays in sync.
                for lid in unique_ids:
                    lid_int = int(lid)
                    if lid_int not in track_map:
                        new_track = Track(name=str(lid_int))
                        track_map[lid_int] = new_track
                        tracks[lid_int] = new_track
        elif isinstance(tracks, list):
            for i, t in enumerate(tracks):
                track_map[i + 1] = t

        # Build category mapping
        cat_map: dict[int, str] = {}
        if categories is None:
            pass  # No categories
        elif isinstance(categories, list):
            for i, c in enumerate(categories):
                cat_map[i + 1] = c
        else:
            cat_map = dict(categories)

        # Build objects dict
        objects: dict[int, LabelImage.Info] = {}
        all_ids = set(int(lid) for lid in unique_ids) | set(track_map) | set(cat_map)
        for lid in sorted(all_ids):
            objects[lid] = LabelImage.Info(
                track=track_map.get(lid),
                category=cat_map.get(lid, ""),
            )

        return cls(data=data, objects=objects, **kwargs)

    @classmethod
    def from_masks(
        cls,
        masks: list["SegmentationMask"],
        **kwargs,
    ) -> "LabelImage":
        """Compose from binary SegmentationMasks.

        Each mask becomes one object with a unique label ID. Track, category,
        and name are inherited from each mask's metadata. Overlapping pixels
        are assigned to the last mask in the list.

        All masks must share the same ``scale`` and ``offset``. The resulting
        ``LabelImage`` inherits the shared spatial metadata (unless overridden
        via ``**kwargs``).

        Args:
            masks: Binary masks. Must all have the same height, width, scale,
                and offset.
            **kwargs: Passed to the LabelImage constructor.

        Returns:
            A ``LabelImage`` composing all masks.

        Raises:
            ValueError: If masks have inconsistent shapes, scale/offset, or the
                list is empty.
        """
        if not masks:
            raise ValueError("Cannot create LabelImage from empty mask list.")

        height, width = masks[0].height, masks[0].width
        for m in masks[1:]:
            if m.height != height or m.width != width:
                raise ValueError(
                    f"All masks must have the same shape. "
                    f"Expected ({height}, {width}), got ({m.height}, {m.width})."
                )

        scales = {m.scale for m in masks}
        offsets = {m.offset for m in masks}
        if len(scales) > 1 or len(offsets) > 1:
            raise ValueError(
                "All masks must share the same scale and offset. "
                "Use mask.resampled() to align them first."
            )

        # Inherit spatial metadata from masks unless explicitly overridden.
        if "scale" not in kwargs:
            kwargs["scale"] = masks[0].scale
        if "offset" not in kwargs:
            kwargs["offset"] = masks[0].offset

        data = np.zeros((height, width), dtype=np.int32)
        objects: dict[int, LabelImage.Info] = {}

        for i, mask in enumerate(masks):
            label_id = i + 1
            data[mask.data] = label_id
            objects[label_id] = LabelImage.Info(
                track=mask.track,
                category=mask.category.name if mask.category else "",
                name=mask.name,
                instance=mask.instance,
            )

        return cls(data=data, objects=objects, **kwargs)

    @classmethod
    def from_binary_masks(
        cls,
        masks: "np.ndarray | list[np.ndarray]",
        label_ids: list[int] | None = None,
        tracks: "list[Track] | None" = None,
        categories: list[str] | None = None,
        names: list[str] | None = None,
        scores: list[float] | None = None,
        create_tracks: bool = False,
        **kwargs,
    ) -> "LabelImage":
        """Create a LabelImage from per-object binary mask arrays.

        This is a convenience constructor for workflows that produce per-object
        binary masks, such as SAM, Mask R-CNN, or other instance segmentation
        tools. Each binary mask becomes one object in the composited label image
        with a unique label ID (1, 2, ..., N, unless ``label_ids`` is provided).
        Overlapping pixels are assigned to the last mask in the list.

        Unlike ``from_masks()``, this takes raw numpy arrays instead of
        ``SegmentationMask`` objects, avoiding RLE encoding overhead.

        Args:
            masks: Per-object binary masks as an ``(N, H, W)`` array or a list
                of ``(H, W)`` arrays. Values are cast to bool (nonzero = True).
            label_ids: Optional list of positive integer label IDs, one per
                mask. ``label_ids[i]`` sets the pixel value for mask ``i``. If
                ``None`` (default), masks are numbered 1, 2, ..., N. All values
                must be positive (0 is background) and unique.
            tracks: List of ``Track`` objects, one per mask. ``tracks[i]`` is
                assigned to mask ``i`` (label ID ``label_ids[i]`` or ``i + 1``
                by default).
            categories: List of category strings, one per mask.
            names: List of human-readable name strings, one per mask.
            scores: List of per-object confidence scores, one per mask. Stored
                in ``Info.score`` for each object.
            create_tracks: If ``True`` and ``tracks`` is ``None``, auto-create
                a ``Track`` per mask with ``name=str(label_id)``.
            **kwargs: Passed to the ``LabelImage`` constructor (e.g.,
                ``source``, ``scale``, ``offset``). For
                ``PredictedLabelImage``, also accepts ``score``, ``score_map``.

        Returns:
            A ``LabelImage`` compositing all masks.

        Raises:
            ValueError: If ``masks`` is empty, shapes are inconsistent, or any
                parallel array has the wrong length.

        Example:
            Create a label image from SAM output::

                li = PredictedLabelImage.from_binary_masks(
                    sam_masks,          # (N, H, W) bool
                    tracks=[t1, t2],    # per-object tracks
                    scores=[0.95, 0.87],# per-object confidence
                    score=0.9,          # image-level confidence
                )

        See Also:
            :meth:`from_masks`: Create from ``SegmentationMask`` objects.
            :meth:`from_numpy`: Create from a pre-composited integer array.
        """
        from sleap_io.model.instance import Track

        # Normalize input to list of 2D arrays.
        if isinstance(masks, np.ndarray):
            if masks.ndim == 3:
                mask_list = [masks[i] for i in range(masks.shape[0])]
            elif masks.ndim == 2:
                mask_list = [masks]
            else:
                raise ValueError(
                    f"Expected 2D or 3D array, got {masks.ndim}D with shape "
                    f"{masks.shape}."
                )
        else:
            mask_list = list(masks)

        if not mask_list:
            raise ValueError("Cannot create LabelImage from empty mask list.")

        # Validate consistent shapes.
        height, width = mask_list[0].shape[0], mask_list[0].shape[1]
        for i, m in enumerate(mask_list[1:], 1):
            if m.shape[0] != height or m.shape[1] != width:
                raise ValueError(
                    f"All masks must have the same shape. "
                    f"Expected ({height}, {width}), got ({m.shape[0]}, {m.shape[1]}) "
                    f"at index {i}."
                )

        n = len(mask_list)

        # Validate parallel array lengths.
        for param_name, param in [
            ("label_ids", label_ids),
            ("tracks", tracks),
            ("categories", categories),
            ("names", names),
            ("scores", scores),
        ]:
            if param is not None and len(param) != n:
                raise ValueError(
                    f"{param_name} length ({len(param)}) must match number of "
                    f"masks ({n})."
                )

        # Validate label_ids semantics.
        if label_ids is not None:
            if any(lid <= 0 for lid in label_ids):
                raise ValueError(
                    "All label_ids must be positive (0 is reserved for background)."
                )
            if len(set(label_ids)) != len(label_ids):
                raise ValueError("label_ids must contain unique values.")

        # Build track list.
        if tracks is not None:
            track_list = tracks
        elif create_tracks:
            track_list = [
                Track(name=str(label_ids[i] if label_ids is not None else i + 1))
                for i in range(n)
            ]
        else:
            track_list = [None] * n

        # Composite masks and build objects dict.
        data = np.zeros((height, width), dtype=np.int32)
        objects: dict[int, LabelImage.Info] = {}

        for i, mask in enumerate(mask_list):
            label_id = label_ids[i] if label_ids is not None else i + 1
            data[np.asarray(mask, dtype=bool)] = label_id
            objects[label_id] = LabelImage.Info(
                track=track_list[i],
                category=categories[i] if categories is not None else "",
                name=names[i] if names is not None else "",
                score=scores[i] if scores is not None else None,
            )

        return cls(data=data, objects=objects, **kwargs)

    @classmethod
    def from_stack(
        cls,
        data: "np.ndarray | list[np.ndarray]",
        tracks: "dict[int, Track] | list[Track] | None" = None,
        categories: dict[int, str] | list[str] | None = None,
        create_tracks: bool = False,
        score: "float | list[float] | None" = None,
        score_map: np.ndarray | None = None,
        **kwargs,
    ) -> "list[LabelImage]":
        """Create label images from a stack of frames.

        This is the batch equivalent of ``from_numpy()``. It accepts a
        ``(T, H, W)`` array (or list of 2D arrays) and returns one
        ``LabelImage`` per frame with consistent ``Track`` objects shared
        across frames.

        Args:
            data: Integer label data as a 3D ``(T, H, W)`` array or a list
                of 2D ``(H, W)`` arrays. Cast to int32.
            tracks: Maps label IDs to Tracks (shared across all frames).

                - ``None``: no tracks unless ``create_tracks=True``.
                - ``list``: positional — ``tracks[i]`` maps to label
                  ``i + 1``.
                - ``dict``: explicit ``{label_id: Track}`` mapping.
            categories: Same pattern as tracks, for category strings.
            create_tracks: If ``True`` and ``tracks`` is ``None``,
                auto-create one ``Track`` per unique non-zero label ID
                found across all frames. The same ``Track`` object is
                shared across frames. Default is ``False``.
            score: Confidence score(s) for ``PredictedLabelImage``. A
                single float is broadcast to all frames; a list must have
                length ``T``. Defaults to ``0.0`` for all frames if
                ``None``. Ignored for ``UserLabelImage``.
            score_map: Optional ``(T, H, W)`` float32 array of per-pixel
                confidence maps. Sliced per frame. Ignored for
                ``UserLabelImage``.
            **kwargs: Passed to every frame's constructor (``source``,
                ``scale``, ``offset``).

        Returns:
            A list of ``LabelImage`` objects, one per frame.

        Raises:
            ValueError: If ``data`` is not 3D (or a list), or if
                ``score`` lengths don't match.

        Note:
            For loading label images from TIFF files (single, multi-page,
            or directory), use ``sleap_io.load_label_images()`` which
            handles file I/O and sidecar metadata. ``from_stack()`` is
            for converting in-memory numpy arrays (e.g., direct Cellpose
            output).

        Example::

            masks = np.stack(cellpose_masks)  # (T, H, W) int32
            label_images = sio.PredictedLabelImage.from_stack(
                masks,
                source="cellpose:nuclei",
                create_tracks=True,
                score=1.0,
            )
        """
        from sleap_io.model.instance import Track

        # Normalize input to list of 2D arrays
        if isinstance(data, np.ndarray):
            if data.ndim != 3:
                raise ValueError(
                    f"from_stack expects a (T, H, W) array, got shape "
                    f"{data.shape}. Use from_numpy() for a single frame."
                )
            frames = [data[t] for t in range(data.shape[0])]
        elif isinstance(data, list):
            frames = data
        else:
            raise ValueError(
                f"data must be a (T, H, W) numpy array or list of 2D "
                f"arrays, got {type(data).__name__}."
            )

        n_frames = len(frames)
        if n_frames == 0:
            return []

        # Collect unique non-zero IDs across all frames
        all_ids: set[int] = set()
        for frame in frames:
            ids = np.unique(frame)
            all_ids.update(int(i) for i in ids if i > 0)

        # Build global track map (shared across frames)
        track_map: dict[int, Track] = {}
        if tracks is None:
            if create_tracks:
                for lid in sorted(all_ids):
                    track_map[lid] = Track(name=str(lid))
        elif isinstance(tracks, list):
            for i, t in enumerate(tracks):
                track_map[i + 1] = t
        else:
            track_map = dict(tracks)

        # Build global category map
        cat_map: dict[int, str] = {}
        if categories is None:
            pass
        elif isinstance(categories, list):
            for i, c in enumerate(categories):
                cat_map[i + 1] = c
        else:
            cat_map = dict(categories)

        # Handle PredictedLabelImage-specific parameters
        is_predicted = issubclass(cls, PredictedLabelImage)
        scores: list[float] = []
        if is_predicted:
            if score is None:
                scores = [0.0] * n_frames
            elif isinstance(score, (int, float)):
                scores = [float(score)] * n_frames
            else:
                if len(score) != n_frames:
                    raise ValueError(
                        f"score list length ({len(score)}) must match "
                        f"number of frames ({n_frames})."
                    )
                scores = [float(s) for s in score]

        score_maps: list[np.ndarray | None] = [None] * n_frames
        if is_predicted and score_map is not None:
            if score_map.ndim == 3 and score_map.shape[0] == n_frames:
                score_maps = [score_map[t] for t in range(n_frames)]
            else:
                raise ValueError(
                    f"score_map must be (T, H, W) with T={n_frames}, "
                    f"got shape {score_map.shape}."
                )

        # Build per-frame LabelImages with shared Track objects
        result: list[LabelImage] = []
        for t, frame in enumerate(frames):
            frame_data = np.asarray(frame, dtype=np.int32)
            frame_ids = np.unique(frame_data)
            frame_ids = frame_ids[frame_ids > 0]

            objects: dict[int, LabelImage.Info] = {}
            for lid in frame_ids:
                lid_int = int(lid)
                objects[lid_int] = LabelImage.Info(
                    track=track_map.get(lid_int),
                    category=cat_map.get(lid_int, ""),
                )

            frame_kwargs = dict(kwargs)
            if is_predicted:
                frame_kwargs["score"] = scores[t]
                frame_kwargs["score_map"] = score_maps[t]

            result.append(cls(data=frame_data, objects=objects, **frame_kwargs))

        return result

    def to_masks(self) -> list["SegmentationMask"]:
        """Decompose into per-object binary SegmentationMasks.

        Returns one SegmentationMask per unique non-zero label. Each mask
        inherits track, category, name, instance, source, scale, and offset
        from the ``LabelImage``.

        Returns:
            A list of ``SegmentationMask`` objects, one per object.
        """
        from sleap_io.model.mask import UserSegmentationMask

        result = []
        for label_id in np.sort(self.label_ids):
            lid = int(label_id)
            info = self.objects.get(lid, LabelImage.Info())
            binary_mask = self.data == lid
            result.append(
                UserSegmentationMask.from_numpy(
                    binary_mask,
                    name=info.name,
                    category=info.category,
                    track=info.track,
                    instance=info.instance,
                    source=self.source,
                    scale=self.scale,
                    offset=self.offset,
                )
            )
        return result

    def to_bboxes(self) -> list["BoundingBox"]:
        """Extract tight bounding boxes for each object in the label image.

        Returns a list of ``BoundingBox`` objects (``UserBoundingBox`` or
        ``PredictedBoundingBox`` depending on whether this label image is
        predicted), one per non-zero label. Each bounding box inherits track,
        category, name, instance, and score from the corresponding
        ``self.objects`` entry.

        Bounding boxes are in image coordinates (respecting scale/offset).
        Label IDs present in ``objects`` but with no pixels in the data are
        skipped.

        Returns:
            A list of ``BoundingBox`` objects, one per object.
        """
        from sleap_io.model.bbox import PredictedBoundingBox, UserBoundingBox

        data = self.data
        cls = PredictedBoundingBox if self.is_predicted else UserBoundingBox
        sx, sy = self.scale
        ox, oy = self.offset

        # Single-pass: find all foreground pixels at once.
        fg_rows, fg_cols = np.where(data > 0)
        if len(fg_rows) == 0:
            return []

        # Map sparse label IDs to dense indices and compute per-label bounds.
        fg_labels = data[fg_rows, fg_cols]
        unique_labels, inverse = np.unique(fg_labels, return_inverse=True)
        n = len(unique_labels)

        row_min = np.full(n, np.iinfo(np.intp).max, dtype=np.intp)
        row_max = np.full(n, np.iinfo(np.intp).min, dtype=np.intp)
        col_min = np.full(n, np.iinfo(np.intp).max, dtype=np.intp)
        col_max = np.full(n, np.iinfo(np.intp).min, dtype=np.intp)

        np.minimum.at(row_min, inverse, fg_rows)
        np.maximum.at(row_max, inverse, fg_rows)
        np.minimum.at(col_min, inverse, fg_cols)
        np.maximum.at(col_max, inverse, fg_cols)

        label_to_idx = {int(lid): i for i, lid in enumerate(unique_labels)}

        # Build BoundingBox objects using precomputed bounds.
        bboxes = []
        for lid, info in self.objects.items():
            idx = label_to_idx.get(lid)
            if idx is None:
                continue

            x1 = float(col_min[idx] / sx + ox)
            y1 = float(row_min[idx] / sy + oy)
            x2 = float((col_max[idx] + 1) / sx + ox)
            y2 = float((row_max[idx] + 1) / sy + oy)

            kwargs: dict = dict(
                track=info.track,
                instance=info.instance,
                category=info.category,
                name=info.name,
                source=self.source,
            )
            if self.is_predicted:
                kwargs["score"] = info.score if info.score is not None else self.score

            bboxes.append(cls.from_xyxy(x1, y1, x2, y2, **kwargs))

        return bboxes

__annotations__ = {'_data': "'np.ndarray | None'", 'objects': 'dict[int, Info]', 'source': 'str', 'scale': 'tuple[float, float]', 'offset': 'tuple[float, float]', '_lazy_loader': "'Callable[[], np.ndarray] | None'", '_height': 'int', '_width': '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__ = 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__ = 'Per-pixel object segmentation for a single video frame.\n\nEach pixel is either background (0) or belongs to a tracked, categorized\nobject. The integer values in ``data`` are an internal encoding. Use the\ntrack-centric API (``__getitem__``, ``tracks``, ``items``) to query objects\nwithout dealing with raw label IDs.\n\nAttributes:\n data: Integer array of shape ``(H, W)`` with dtype int32. ``0`` is\n background, positive values are object IDs.\n objects: Mapping from label ID to object metadata. Defines what each\n non-zero pixel value represents. Label IDs not in this dict are\n treated as having default (empty) metadata.\n source: Source identifier string.\n scale: Resolution ratio ``(sx, sy)`` where ``sx = label_width / image_width``\n and ``sy = label_height / image_height``. ``(1.0, 1.0)`` means full\n resolution. ``(0.5, 0.5)`` means half resolution. Coordinate mapping:\n ``image_coord = label_coord / scale + offset``.\n offset: Origin ``(x, y)`` of the label image in image pixel coordinates.\n\nSee Also:\n ``SegmentationMask``: Per-object binary masks (one mask per object).\n ``sleap_io.load_label_images``: Load label images from TIFF files.\n ``sleap_io.save_label_images``: Save label images to TIFF files.\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__ = 52 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__ = ('_data', 'objects', 'source', 'scale', 'offset') 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.label_image' 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__ = ('_data', 'objects', 'source', 'scale', 'offset', '_lazy_loader', '_height', '_width', '__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__ = ('_data', '_height', '_lazy_loader', '_width') 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

categories property

Unique non-empty category strings present.

data property

Integer array of shape (H, W) with dtype int32.

0 is background, positive values are object IDs. When the label image was loaded lazily, the pixel data is decompressed on first access and cached for subsequent reads.

has_spatial_transform property

Whether this label image has non-default scale or offset.

height property

Height of the label image in pixels.

image_extent property

Image-space (height, width) this label image covers (excluding offset).

Computed as (int(height / scale_y), int(width / scale_x)).

is_predicted property

Whether this label image is a model prediction.

label_ids property

Sorted array of unique non-zero label values in data.

n_objects property

Number of unique non-zero labels present in data.

tracks property

Tracks present in this frame (from objects with non-None track).

width property

Width of the label image in pixels.

Info

Metadata for one segmented object within a LabelImage.

Attributes:

Name Type Description
track

Track identity for cross-frame association. None if untracked.

tracking_score

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

category

Semantic class label (e.g., "neuron", "glia").

name

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

instance

Linked pose Instance, if any.

Methods:

Name Description
__eq__

Method generated by attrs for class LabelImage.Info.

__init__

Method generated by attrs for class LabelImage.Info.

__repr__

Method generated by attrs for class LabelImage.Info.

Source code in sleap_io/model/label_image.py
@attrs.define
class Info:
    """Metadata for one segmented object within a ``LabelImage``.

    Attributes:
        track: Track identity for cross-frame association. ``None`` if
            untracked.
        tracking_score: Confidence of the track identity assignment.
            ``None`` if unassigned or manually assigned.
        category: Semantic class label (e.g., ``"neuron"``, ``"glia"``).
        name: Human-readable name (e.g., ``"cell_042"``).
        instance: Linked pose ``Instance``, if any.
    """

    track: "Track | None" = None
    tracking_score: float | None = None
    category: str = ""
    name: str = ""
    instance: "Instance | None" = None
    score: float | None = None

    # Private: deferred instance index for lazy loading. When label images
    # are read from a file without materialized instances (e.g., lazy mode),
    # this stores the raw instance_idx so it can be resolved later or
    # written back as-is.
    _instance_idx: int = attrs.field(default=-1, repr=False, eq=False, init=False)
__annotations__ = {'track': "'Track | None'", 'tracking_score': 'float | None', 'category': 'str', 'name': 'str', 'instance': "'Instance | None'", 'score': 'float | None', '_instance_idx': 'int'} class-attribute

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

__attrs_own_setattr__ = 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=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__ = 'Metadata for one segmented object within a ``LabelImage``.\n\nAttributes:\n track: Track identity for cross-frame association. ``None`` if\n untracked.\n tracking_score: Confidence of the track identity assignment.\n ``None`` if unassigned or manually assigned.\n category: Semantic class label (e.g., ``"neuron"``, ``"glia"``).\n name: Human-readable name (e.g., ``"cell_042"``).\n instance: Linked pose ``Instance``, if any.\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__ = 80 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__ = ('track', 'tracking_score', 'category', 'name', 'instance', 'score') class-attribute

Built-in immutable sequence.

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

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

__module__ = 'sleap_io.model.label_image' 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__ = ('track', 'tracking_score', 'category', 'name', 'instance', 'score', '_instance_idx', '__weakref__') class-attribute

Built-in immutable sequence.

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

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

__static_attributes__ = () class-attribute

Built-in immutable sequence.

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

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

__weakref__ property

list of weak references to the object

__eq__(other)

Method generated by attrs for class LabelImage.Info.

Source code in sleap_io/model/label_image.py
- Use ``SegmentationMask`` when you have individual binary masks per object
  (e.g., from Mask R-CNN, manual annotation, or ROI-based workflows). Each
  mask is stored separately with RLE compression.
- Use ``LabelImage.from_binary_masks()`` to create a label image directly from
  per-object binary numpy arrays (e.g., from SAM or Mask R-CNN output).
- To convert between them, use ``LabelImage.to_masks()`` and
  ``LabelImage.from_masks()``.

For TIFF I/O of label images, see ``sleap_io.load_label_images()`` and
``sleap_io.save_label_images()``.
__init__(track=None, tracking_score=None, category='', name='', instance=None, score=None)

Method generated by attrs for class LabelImage.Info.

Source code in sleap_io/model/label_image.py
See Also:
    ``sleap_io.model.mask``: Binary segmentation masks (one per object).
"""

from __future__ import annotations

import copy
import sys
__repr__()

Method generated by attrs for class LabelImage.Info.

Source code in sleap_io/model/label_image.py
"""Data structure for integer label image annotations.

Label images represent per-pixel object segmentation for a single video frame,
where each pixel value encodes which object occupies that pixel. This is the
standard output format of instance segmentation tools like Cellpose and StarDist.

Unlike binary ``SegmentationMask`` objects (one mask per object), a single
``LabelImage`` efficiently stores all objects for a frame in one dense integer
array.

**When to use LabelImage vs SegmentationMask:**

- Use ``LabelImage`` when you have a dense integer array from a segmentation
  tool (Cellpose, StarDist, COCO panoptic) where each pixel value identifies
  an object. One ``LabelImage`` per frame stores all objects at once.

__attrs_post_init__()

Validate and normalize data array on construction.

Source code in sleap_io/model/label_image.py
def __attrs_post_init__(self):
    """Validate and normalize data array on construction."""
    if type(self) is LabelImage:
        raise TypeError(
            "LabelImage is abstract. Use UserLabelImage or PredictedLabelImage."
        )
    if self._data is not None:
        self._validate_data()
    # When _data is None, validation is deferred to first .data access.
    if self.scale[0] <= 0 or self.scale[1] <= 0:
        raise ValueError(f"Scale values must be positive, got {self.scale}.")

__contains__(track)

Whether this track has pixels in this frame.

Source code in sleap_io/model/label_image.py
def __contains__(self, track: "Track") -> bool:
    """Whether this track has pixels in this frame."""
    return any(info.track is track for info in self.objects.values())

__deepcopy__(memo)

Deep copy that materializes lazy data before copying.

This is necessary because lazy loaders capture h5py dataset references which cannot be pickled/deepcopied.

Source code in sleap_io/model/label_image.py
def __deepcopy__(self, memo: dict) -> "LabelImage":
    """Deep copy that materializes lazy data before copying.

    This is necessary because lazy loaders capture h5py dataset references
    which cannot be pickled/deepcopied.
    """
    # Materialize lazy data before copying (h5py refs can't survive deepcopy).
    if self._data is not None:
        data = self._data.copy()
    elif self._lazy_loader is not None:
        data = self.data.copy()  # Triggers lazy load, then copy
    else:
        data = None
    objects = {lid: copy.deepcopy(info, memo) for lid, info in self.objects.items()}

    kwargs: dict = dict(
        data=data,
        objects=objects,
        source=self.source,
        scale=self.scale,
        offset=self.offset,
    )
    if isinstance(self, PredictedLabelImage):
        sm = self.score_map
        kwargs["score"] = self.score
        kwargs["score_map"] = sm.copy() if sm is not None else None
        kwargs["score_map_scale"] = self.score_map_scale
        kwargs["score_map_offset"] = self.score_map_offset
    result = type(self)(**kwargs)
    memo[id(self)] = result
    return result

__getitem__(track)

Get binary (H, W) mask for a tracked object.

Parameters:

Name Type Description Default
track Track

The Track to look up.

required

Returns:

Type Description
ndarray

Boolean array of shape (H, W).

Raises:

Type Description
KeyError

If the track is not present in this frame.

Source code in sleap_io/model/label_image.py
def __getitem__(self, track: "Track") -> np.ndarray:
    """Get binary (H, W) mask for a tracked object.

    Args:
        track: The Track to look up.

    Returns:
        Boolean array of shape (H, W).

    Raises:
        KeyError: If the track is not present in this frame.
    """
    for label_id, info in self.objects.items():
        if info.track is track:
            return self.data == label_id
    raise KeyError(f"Track {track} not found in this LabelImage.")

__init__(data=None, objects=NOTHING, source='', scale=(1.0, 1.0), offset=(0.0, 0.0))

Method generated by attrs for class LabelImage.

Source code in sleap_io/model/label_image.py
- Use ``SegmentationMask`` when you have individual binary masks per object
  (e.g., from Mask R-CNN, manual annotation, or ROI-based workflows). Each
  mask is stored separately with RLE compression.
- Use ``LabelImage.from_binary_masks()`` to create a label image directly from
  per-object binary numpy arrays (e.g., from SAM or Mask R-CNN output).
- To convert between them, use ``LabelImage.to_masks()`` and
  ``LabelImage.from_masks()``.

For TIFF I/O of label images, see ``sleap_io.load_label_images()`` and
``sleap_io.save_label_images()``.

See Also:
    ``sleap_io.model.mask``: Binary segmentation masks (one per object).

__repr__()

Method generated by attrs for class LabelImage.

Source code in sleap_io/model/label_image.py
"""Data structure for integer label image annotations.

Label images represent per-pixel object segmentation for a single video frame,
where each pixel value encodes which object occupies that pixel. This is the
standard output format of instance segmentation tools like Cellpose and StarDist.

Unlike binary ``SegmentationMask`` objects (one mask per object), a single
``LabelImage`` efficiently stores all objects for a frame in one dense integer
array.

**When to use LabelImage vs SegmentationMask:**

- Use ``LabelImage`` when you have a dense integer array from a segmentation
  tool (Cellpose, StarDist, COCO panoptic) where each pixel value identifies
  an object. One ``LabelImage`` per frame stores all objects at once.

from_binary_masks(masks, label_ids=None, tracks=None, categories=None, names=None, scores=None, create_tracks=False, **kwargs) classmethod

Create a LabelImage from per-object binary mask arrays.

This is a convenience constructor for workflows that produce per-object binary masks, such as SAM, Mask R-CNN, or other instance segmentation tools. Each binary mask becomes one object in the composited label image with a unique label ID (1, 2, ..., N, unless label_ids is provided). Overlapping pixels are assigned to the last mask in the list.

Unlike from_masks(), this takes raw numpy arrays instead of SegmentationMask objects, avoiding RLE encoding overhead.

Parameters:

Name Type Description Default
masks ndarray | list[ndarray]

Per-object binary masks as an (N, H, W) array or a list of (H, W) arrays. Values are cast to bool (nonzero = True).

required
label_ids list[int] | None

Optional list of positive integer label IDs, one per mask. label_ids[i] sets the pixel value for mask i. If None (default), masks are numbered 1, 2, ..., N. All values must be positive (0 is background) and unique.

None
tracks list[Track] | None

List of Track objects, one per mask. tracks[i] is assigned to mask i (label ID label_ids[i] or i + 1 by default).

None
categories list[str] | None

List of category strings, one per mask.

None
names list[str] | None

List of human-readable name strings, one per mask.

None
scores list[float] | None

List of per-object confidence scores, one per mask. Stored in Info.score for each object.

None
create_tracks bool

If True and tracks is None, auto-create a Track per mask with name=str(label_id).

False
**kwargs

Passed to the LabelImage constructor (e.g., source, scale, offset). For PredictedLabelImage, also accepts score, score_map.

required

Returns:

Type Description
LabelImage

A LabelImage compositing all masks.

Raises:

Type Description
ValueError

If masks is empty, shapes are inconsistent, or any parallel array has the wrong length.

Example

Create a label image from SAM output::

li = PredictedLabelImage.from_binary_masks(
    sam_masks,          # (N, H, W) bool
    tracks=[t1, t2],    # per-object tracks
    scores=[0.95, 0.87],# per-object confidence
    score=0.9,          # image-level confidence
)
See Also

:meth:from_masks: Create from SegmentationMask objects. :meth:from_numpy: Create from a pre-composited integer array.

Source code in sleap_io/model/label_image.py
@classmethod
def from_binary_masks(
    cls,
    masks: "np.ndarray | list[np.ndarray]",
    label_ids: list[int] | None = None,
    tracks: "list[Track] | None" = None,
    categories: list[str] | None = None,
    names: list[str] | None = None,
    scores: list[float] | None = None,
    create_tracks: bool = False,
    **kwargs,
) -> "LabelImage":
    """Create a LabelImage from per-object binary mask arrays.

    This is a convenience constructor for workflows that produce per-object
    binary masks, such as SAM, Mask R-CNN, or other instance segmentation
    tools. Each binary mask becomes one object in the composited label image
    with a unique label ID (1, 2, ..., N, unless ``label_ids`` is provided).
    Overlapping pixels are assigned to the last mask in the list.

    Unlike ``from_masks()``, this takes raw numpy arrays instead of
    ``SegmentationMask`` objects, avoiding RLE encoding overhead.

    Args:
        masks: Per-object binary masks as an ``(N, H, W)`` array or a list
            of ``(H, W)`` arrays. Values are cast to bool (nonzero = True).
        label_ids: Optional list of positive integer label IDs, one per
            mask. ``label_ids[i]`` sets the pixel value for mask ``i``. If
            ``None`` (default), masks are numbered 1, 2, ..., N. All values
            must be positive (0 is background) and unique.
        tracks: List of ``Track`` objects, one per mask. ``tracks[i]`` is
            assigned to mask ``i`` (label ID ``label_ids[i]`` or ``i + 1``
            by default).
        categories: List of category strings, one per mask.
        names: List of human-readable name strings, one per mask.
        scores: List of per-object confidence scores, one per mask. Stored
            in ``Info.score`` for each object.
        create_tracks: If ``True`` and ``tracks`` is ``None``, auto-create
            a ``Track`` per mask with ``name=str(label_id)``.
        **kwargs: Passed to the ``LabelImage`` constructor (e.g.,
            ``source``, ``scale``, ``offset``). For
            ``PredictedLabelImage``, also accepts ``score``, ``score_map``.

    Returns:
        A ``LabelImage`` compositing all masks.

    Raises:
        ValueError: If ``masks`` is empty, shapes are inconsistent, or any
            parallel array has the wrong length.

    Example:
        Create a label image from SAM output::

            li = PredictedLabelImage.from_binary_masks(
                sam_masks,          # (N, H, W) bool
                tracks=[t1, t2],    # per-object tracks
                scores=[0.95, 0.87],# per-object confidence
                score=0.9,          # image-level confidence
            )

    See Also:
        :meth:`from_masks`: Create from ``SegmentationMask`` objects.
        :meth:`from_numpy`: Create from a pre-composited integer array.
    """
    from sleap_io.model.instance import Track

    # Normalize input to list of 2D arrays.
    if isinstance(masks, np.ndarray):
        if masks.ndim == 3:
            mask_list = [masks[i] for i in range(masks.shape[0])]
        elif masks.ndim == 2:
            mask_list = [masks]
        else:
            raise ValueError(
                f"Expected 2D or 3D array, got {masks.ndim}D with shape "
                f"{masks.shape}."
            )
    else:
        mask_list = list(masks)

    if not mask_list:
        raise ValueError("Cannot create LabelImage from empty mask list.")

    # Validate consistent shapes.
    height, width = mask_list[0].shape[0], mask_list[0].shape[1]
    for i, m in enumerate(mask_list[1:], 1):
        if m.shape[0] != height or m.shape[1] != width:
            raise ValueError(
                f"All masks must have the same shape. "
                f"Expected ({height}, {width}), got ({m.shape[0]}, {m.shape[1]}) "
                f"at index {i}."
            )

    n = len(mask_list)

    # Validate parallel array lengths.
    for param_name, param in [
        ("label_ids", label_ids),
        ("tracks", tracks),
        ("categories", categories),
        ("names", names),
        ("scores", scores),
    ]:
        if param is not None and len(param) != n:
            raise ValueError(
                f"{param_name} length ({len(param)}) must match number of "
                f"masks ({n})."
            )

    # Validate label_ids semantics.
    if label_ids is not None:
        if any(lid <= 0 for lid in label_ids):
            raise ValueError(
                "All label_ids must be positive (0 is reserved for background)."
            )
        if len(set(label_ids)) != len(label_ids):
            raise ValueError("label_ids must contain unique values.")

    # Build track list.
    if tracks is not None:
        track_list = tracks
    elif create_tracks:
        track_list = [
            Track(name=str(label_ids[i] if label_ids is not None else i + 1))
            for i in range(n)
        ]
    else:
        track_list = [None] * n

    # Composite masks and build objects dict.
    data = np.zeros((height, width), dtype=np.int32)
    objects: dict[int, LabelImage.Info] = {}

    for i, mask in enumerate(mask_list):
        label_id = label_ids[i] if label_ids is not None else i + 1
        data[np.asarray(mask, dtype=bool)] = label_id
        objects[label_id] = LabelImage.Info(
            track=track_list[i],
            category=categories[i] if categories is not None else "",
            name=names[i] if names is not None else "",
            score=scores[i] if scores is not None else None,
        )

    return cls(data=data, objects=objects, **kwargs)

from_masks(masks, **kwargs) classmethod

Compose from binary SegmentationMasks.

Each mask becomes one object with a unique label ID. Track, category, and name are inherited from each mask's metadata. Overlapping pixels are assigned to the last mask in the list.

All masks must share the same scale and offset. The resulting LabelImage inherits the shared spatial metadata (unless overridden via **kwargs).

Parameters:

Name Type Description Default
masks list[SegmentationMask]

Binary masks. Must all have the same height, width, scale, and offset.

required
**kwargs

Passed to the LabelImage constructor.

required

Returns:

Type Description
LabelImage

A LabelImage composing all masks.

Raises:

Type Description
ValueError

If masks have inconsistent shapes, scale/offset, or the list is empty.

Source code in sleap_io/model/label_image.py
@classmethod
def from_masks(
    cls,
    masks: list["SegmentationMask"],
    **kwargs,
) -> "LabelImage":
    """Compose from binary SegmentationMasks.

    Each mask becomes one object with a unique label ID. Track, category,
    and name are inherited from each mask's metadata. Overlapping pixels
    are assigned to the last mask in the list.

    All masks must share the same ``scale`` and ``offset``. The resulting
    ``LabelImage`` inherits the shared spatial metadata (unless overridden
    via ``**kwargs``).

    Args:
        masks: Binary masks. Must all have the same height, width, scale,
            and offset.
        **kwargs: Passed to the LabelImage constructor.

    Returns:
        A ``LabelImage`` composing all masks.

    Raises:
        ValueError: If masks have inconsistent shapes, scale/offset, or the
            list is empty.
    """
    if not masks:
        raise ValueError("Cannot create LabelImage from empty mask list.")

    height, width = masks[0].height, masks[0].width
    for m in masks[1:]:
        if m.height != height or m.width != width:
            raise ValueError(
                f"All masks must have the same shape. "
                f"Expected ({height}, {width}), got ({m.height}, {m.width})."
            )

    scales = {m.scale for m in masks}
    offsets = {m.offset for m in masks}
    if len(scales) > 1 or len(offsets) > 1:
        raise ValueError(
            "All masks must share the same scale and offset. "
            "Use mask.resampled() to align them first."
        )

    # Inherit spatial metadata from masks unless explicitly overridden.
    if "scale" not in kwargs:
        kwargs["scale"] = masks[0].scale
    if "offset" not in kwargs:
        kwargs["offset"] = masks[0].offset

    data = np.zeros((height, width), dtype=np.int32)
    objects: dict[int, LabelImage.Info] = {}

    for i, mask in enumerate(masks):
        label_id = i + 1
        data[mask.data] = label_id
        objects[label_id] = LabelImage.Info(
            track=mask.track,
            category=mask.category.name if mask.category else "",
            name=mask.name,
            instance=mask.instance,
        )

    return cls(data=data, objects=objects, **kwargs)

from_numpy(data, tracks=None, categories=None, create_tracks=False, **kwargs) classmethod

Create from an integer array.

Parameters:

Name Type Description Default
data ndarray

(H, W) integer array. Cast to int32.

required
tracks dict[int, Track] | list[Track] | None

Maps label IDs to Tracks.

  • None: no tracks unless create_tracks=True.
  • list: positional — tracks[i] maps to label i + 1.
  • dict: explicit {label_id: Track} mapping. When combined with create_tracks=True, the dict is used as a shared accumulator — existing entries are reused and new entries are added for unseen label IDs (mutated in place).
None
categories dict[int, str] | list[str] | None

Same pattern as tracks, for category strings.

  • None: no categories set.
  • list: positional — categories[i] maps to label i + 1.
  • dict: explicit {label_id: category} mapping.
None
create_tracks bool

If True and tracks is None, auto-create one Track per unique non-zero label with Track.name set to the string of the label ID. If True and tracks is a dict, create new Tracks for any label IDs not already in the dict (the dict is mutated in place to accumulate mappings across calls). Default is False.

False
**kwargs

Passed to the LabelImage constructor ( source).

required

Returns:

Type Description
LabelImage

A LabelImage with populated objects dict.

Source code in sleap_io/model/label_image.py
@classmethod
def from_numpy(
    cls,
    data: np.ndarray,
    tracks: "dict[int, Track] | list[Track] | None" = None,
    categories: dict[int, str] | list[str] | None = None,
    create_tracks: bool = False,
    **kwargs,
) -> "LabelImage":
    """Create from an integer array.

    Args:
        data: (H, W) integer array. Cast to int32.
        tracks: Maps label IDs to Tracks.

            - ``None``: no tracks unless ``create_tracks=True``.
            - ``list``: positional — ``tracks[i]`` maps to label ``i + 1``.
            - ``dict``: explicit ``{label_id: Track}`` mapping. When
              combined with ``create_tracks=True``, the dict is used as
              a shared accumulator — existing entries are reused and new
              entries are added for unseen label IDs (mutated in place).
        categories: Same pattern as tracks, for category strings.

            - ``None``: no categories set.
            - ``list``: positional — ``categories[i]`` maps to label ``i + 1``.
            - ``dict``: explicit ``{label_id: category}`` mapping.
        create_tracks: If ``True`` and ``tracks`` is ``None``, auto-create
            one Track per unique non-zero label with Track.name set to the
            string of the label ID. If ``True`` and ``tracks`` is a dict,
            create new Tracks for any label IDs not already in the dict
            (the dict is mutated in place to accumulate mappings across
            calls). Default is ``False``.
        **kwargs: Passed to the LabelImage constructor (
            source).

    Returns:
        A ``LabelImage`` with populated ``objects`` dict.
    """
    from sleap_io.model.instance import Track

    data = np.asarray(data, dtype=np.int32)
    unique_ids = np.unique(data)
    unique_ids = unique_ids[unique_ids > 0]

    # Build track mapping
    track_map: dict[int, Track] = {}
    if tracks is None:
        if create_tracks:
            for lid in unique_ids:
                track_map[int(lid)] = Track(name=str(int(lid)))
    elif isinstance(tracks, dict):
        track_map = dict(tracks)
        if create_tracks:
            # Accumulate: create new tracks for unseen IDs, mutate
            # the caller's dict in place so it stays in sync.
            for lid in unique_ids:
                lid_int = int(lid)
                if lid_int not in track_map:
                    new_track = Track(name=str(lid_int))
                    track_map[lid_int] = new_track
                    tracks[lid_int] = new_track
    elif isinstance(tracks, list):
        for i, t in enumerate(tracks):
            track_map[i + 1] = t

    # Build category mapping
    cat_map: dict[int, str] = {}
    if categories is None:
        pass  # No categories
    elif isinstance(categories, list):
        for i, c in enumerate(categories):
            cat_map[i + 1] = c
    else:
        cat_map = dict(categories)

    # Build objects dict
    objects: dict[int, LabelImage.Info] = {}
    all_ids = set(int(lid) for lid in unique_ids) | set(track_map) | set(cat_map)
    for lid in sorted(all_ids):
        objects[lid] = LabelImage.Info(
            track=track_map.get(lid),
            category=cat_map.get(lid, ""),
        )

    return cls(data=data, objects=objects, **kwargs)

from_stack(data, tracks=None, categories=None, create_tracks=False, score=None, score_map=None, **kwargs) classmethod

Create label images from a stack of frames.

This is the batch equivalent of from_numpy(). It accepts a (T, H, W) array (or list of 2D arrays) and returns one LabelImage per frame with consistent Track objects shared across frames.

Parameters:

Name Type Description Default
data ndarray | list[ndarray]

Integer label data as a 3D (T, H, W) array or a list of 2D (H, W) arrays. Cast to int32.

required
tracks dict[int, Track] | list[Track] | None

Maps label IDs to Tracks (shared across all frames).

  • None: no tracks unless create_tracks=True.
  • list: positional — tracks[i] maps to label i + 1.
  • dict: explicit {label_id: Track} mapping.
None
categories dict[int, str] | list[str] | None

Same pattern as tracks, for category strings.

None
create_tracks bool

If True and tracks is None, auto-create one Track per unique non-zero label ID found across all frames. The same Track object is shared across frames. Default is False.

False
score float | list[float] | None

Confidence score(s) for PredictedLabelImage. A single float is broadcast to all frames; a list must have length T. Defaults to 0.0 for all frames if None. Ignored for UserLabelImage.

None
score_map ndarray | None

Optional (T, H, W) float32 array of per-pixel confidence maps. Sliced per frame. Ignored for UserLabelImage.

None
**kwargs

Passed to every frame's constructor (source, scale, offset).

required

Returns:

Type Description
list[LabelImage]

A list of LabelImage objects, one per frame.

Raises:

Type Description
ValueError

If data is not 3D (or a list), or if score lengths don't match.

Note

For loading label images from TIFF files (single, multi-page, or directory), use sleap_io.load_label_images() which handles file I/O and sidecar metadata. from_stack() is for converting in-memory numpy arrays (e.g., direct Cellpose output).

Example::

masks = np.stack(cellpose_masks)  # (T, H, W) int32
label_images = sio.PredictedLabelImage.from_stack(
    masks,
    source="cellpose:nuclei",
    create_tracks=True,
    score=1.0,
)
Source code in sleap_io/model/label_image.py
@classmethod
def from_stack(
    cls,
    data: "np.ndarray | list[np.ndarray]",
    tracks: "dict[int, Track] | list[Track] | None" = None,
    categories: dict[int, str] | list[str] | None = None,
    create_tracks: bool = False,
    score: "float | list[float] | None" = None,
    score_map: np.ndarray | None = None,
    **kwargs,
) -> "list[LabelImage]":
    """Create label images from a stack of frames.

    This is the batch equivalent of ``from_numpy()``. It accepts a
    ``(T, H, W)`` array (or list of 2D arrays) and returns one
    ``LabelImage`` per frame with consistent ``Track`` objects shared
    across frames.

    Args:
        data: Integer label data as a 3D ``(T, H, W)`` array or a list
            of 2D ``(H, W)`` arrays. Cast to int32.
        tracks: Maps label IDs to Tracks (shared across all frames).

            - ``None``: no tracks unless ``create_tracks=True``.
            - ``list``: positional — ``tracks[i]`` maps to label
              ``i + 1``.
            - ``dict``: explicit ``{label_id: Track}`` mapping.
        categories: Same pattern as tracks, for category strings.
        create_tracks: If ``True`` and ``tracks`` is ``None``,
            auto-create one ``Track`` per unique non-zero label ID
            found across all frames. The same ``Track`` object is
            shared across frames. Default is ``False``.
        score: Confidence score(s) for ``PredictedLabelImage``. A
            single float is broadcast to all frames; a list must have
            length ``T``. Defaults to ``0.0`` for all frames if
            ``None``. Ignored for ``UserLabelImage``.
        score_map: Optional ``(T, H, W)`` float32 array of per-pixel
            confidence maps. Sliced per frame. Ignored for
            ``UserLabelImage``.
        **kwargs: Passed to every frame's constructor (``source``,
            ``scale``, ``offset``).

    Returns:
        A list of ``LabelImage`` objects, one per frame.

    Raises:
        ValueError: If ``data`` is not 3D (or a list), or if
            ``score`` lengths don't match.

    Note:
        For loading label images from TIFF files (single, multi-page,
        or directory), use ``sleap_io.load_label_images()`` which
        handles file I/O and sidecar metadata. ``from_stack()`` is
        for converting in-memory numpy arrays (e.g., direct Cellpose
        output).

    Example::

        masks = np.stack(cellpose_masks)  # (T, H, W) int32
        label_images = sio.PredictedLabelImage.from_stack(
            masks,
            source="cellpose:nuclei",
            create_tracks=True,
            score=1.0,
        )
    """
    from sleap_io.model.instance import Track

    # Normalize input to list of 2D arrays
    if isinstance(data, np.ndarray):
        if data.ndim != 3:
            raise ValueError(
                f"from_stack expects a (T, H, W) array, got shape "
                f"{data.shape}. Use from_numpy() for a single frame."
            )
        frames = [data[t] for t in range(data.shape[0])]
    elif isinstance(data, list):
        frames = data
    else:
        raise ValueError(
            f"data must be a (T, H, W) numpy array or list of 2D "
            f"arrays, got {type(data).__name__}."
        )

    n_frames = len(frames)
    if n_frames == 0:
        return []

    # Collect unique non-zero IDs across all frames
    all_ids: set[int] = set()
    for frame in frames:
        ids = np.unique(frame)
        all_ids.update(int(i) for i in ids if i > 0)

    # Build global track map (shared across frames)
    track_map: dict[int, Track] = {}
    if tracks is None:
        if create_tracks:
            for lid in sorted(all_ids):
                track_map[lid] = Track(name=str(lid))
    elif isinstance(tracks, list):
        for i, t in enumerate(tracks):
            track_map[i + 1] = t
    else:
        track_map = dict(tracks)

    # Build global category map
    cat_map: dict[int, str] = {}
    if categories is None:
        pass
    elif isinstance(categories, list):
        for i, c in enumerate(categories):
            cat_map[i + 1] = c
    else:
        cat_map = dict(categories)

    # Handle PredictedLabelImage-specific parameters
    is_predicted = issubclass(cls, PredictedLabelImage)
    scores: list[float] = []
    if is_predicted:
        if score is None:
            scores = [0.0] * n_frames
        elif isinstance(score, (int, float)):
            scores = [float(score)] * n_frames
        else:
            if len(score) != n_frames:
                raise ValueError(
                    f"score list length ({len(score)}) must match "
                    f"number of frames ({n_frames})."
                )
            scores = [float(s) for s in score]

    score_maps: list[np.ndarray | None] = [None] * n_frames
    if is_predicted and score_map is not None:
        if score_map.ndim == 3 and score_map.shape[0] == n_frames:
            score_maps = [score_map[t] for t in range(n_frames)]
        else:
            raise ValueError(
                f"score_map must be (T, H, W) with T={n_frames}, "
                f"got shape {score_map.shape}."
            )

    # Build per-frame LabelImages with shared Track objects
    result: list[LabelImage] = []
    for t, frame in enumerate(frames):
        frame_data = np.asarray(frame, dtype=np.int32)
        frame_ids = np.unique(frame_data)
        frame_ids = frame_ids[frame_ids > 0]

        objects: dict[int, LabelImage.Info] = {}
        for lid in frame_ids:
            lid_int = int(lid)
            objects[lid_int] = LabelImage.Info(
                track=track_map.get(lid_int),
                category=cat_map.get(lid_int, ""),
            )

        frame_kwargs = dict(kwargs)
        if is_predicted:
            frame_kwargs["score"] = scores[t]
            frame_kwargs["score_map"] = score_maps[t]

        result.append(cls(data=frame_data, objects=objects, **frame_kwargs))

    return result

get_category_mask(category)

Union mask of all objects matching a category.

Parameters:

Name Type Description Default
category str

Semantic class label to filter by.

required

Returns:

Type Description
ndarray

Boolean array of shape (H, W). All-False if no objects match.

Source code in sleap_io/model/label_image.py
def get_category_mask(self, category: str) -> np.ndarray:
    """Union mask of all objects matching a category.

    Args:
        category: Semantic class label to filter by.

    Returns:
        Boolean array of shape (H, W). All-False if no objects match.
    """
    label_ids = [
        lid for lid, info in self.objects.items() if info.category == category
    ]
    if not label_ids:
        return np.zeros((self.height, self.width), dtype=bool)
    return np.isin(self.data, label_ids)

get_track_mask(track)

Get binary (H, W) mask for a tracked object. Same as getitem.

Source code in sleap_io/model/label_image.py
def get_track_mask(self, track: "Track") -> np.ndarray:
    """Get binary (H, W) mask for a tracked object. Same as __getitem__."""
    return self[track]

items()

Iterate over objects as (track, category, mask) tuples.

Yields one tuple per unique non-zero label ID, in sorted label order.

Source code in sleap_io/model/label_image.py
def items(self) -> Iterator[tuple["Track | None", str, np.ndarray]]:
    """Iterate over objects as (track, category, mask) tuples.

    Yields one tuple per unique non-zero label ID, in sorted label order.
    """
    for label_id in np.sort(self.label_ids):
        lid = int(label_id)
        info = self.objects.get(lid, LabelImage.Info())
        yield info.track, info.category, self.data == lid

resampled(target_height, target_width)

Return a new label image resampled to the target dimensions.

The returned label image has scale=(1.0, 1.0) and offset=(0.0, 0.0) with the data resized using nearest-neighbor interpolation to preserve label IDs.

Parameters:

Name Type Description Default
target_height int

Target height in pixels.

required
target_width int

Target width in pixels.

required

Returns:

Type Description
Self

A new label image of the same concrete type with resampled data.

Source code in sleap_io/model/label_image.py
def resampled(self, target_height: int, target_width: int) -> Self:
    """Return a new label image resampled to the target dimensions.

    The returned label image has ``scale=(1.0, 1.0)`` and
    ``offset=(0.0, 0.0)`` with the data resized using nearest-neighbor
    interpolation to preserve label IDs.

    Args:
        target_height: Target height in pixels.
        target_width: Target width in pixels.

    Returns:
        A new label image of the same concrete type with resampled data.
    """
    from sleap_io.model.mask import _resize_nearest

    resized = _resize_nearest(self.data, target_height, target_width)
    objects: dict[int, LabelImage.Info] = {}
    for lid, info in self.objects.items():
        new_info = attrs.evolve(info)
        # Carry the deferred instance index through (init=False, so it is not
        # reproduced by attrs.evolve and must be set after construction;
        # mirrors how __deepcopy__ preserves the lazy association).
        new_info._instance_idx = info._instance_idx
        objects[lid] = new_info
    kwargs: dict = dict(
        data=resized,
        objects=objects,
        source=self.source,
        scale=(1.0, 1.0),
        offset=(0.0, 0.0),
    )
    if isinstance(self, PredictedLabelImage):
        kwargs["score"] = self.score
        if self.score_map is not None:
            kwargs["score_map"] = _resize_nearest(
                self.score_map, target_height, target_width
            )
        kwargs["score_map_scale"] = (1.0, 1.0)
        kwargs["score_map_offset"] = (0.0, 0.0)
    return type(self)(**kwargs)

to_bboxes()

Extract tight bounding boxes for each object in the label image.

Returns a list of BoundingBox objects (UserBoundingBox or PredictedBoundingBox depending on whether this label image is predicted), one per non-zero label. Each bounding box inherits track, category, name, instance, and score from the corresponding self.objects entry.

Bounding boxes are in image coordinates (respecting scale/offset). Label IDs present in objects but with no pixels in the data are skipped.

Returns:

Type Description
list[BoundingBox]

A list of BoundingBox objects, one per object.

Source code in sleap_io/model/label_image.py
def to_bboxes(self) -> list["BoundingBox"]:
    """Extract tight bounding boxes for each object in the label image.

    Returns a list of ``BoundingBox`` objects (``UserBoundingBox`` or
    ``PredictedBoundingBox`` depending on whether this label image is
    predicted), one per non-zero label. Each bounding box inherits track,
    category, name, instance, and score from the corresponding
    ``self.objects`` entry.

    Bounding boxes are in image coordinates (respecting scale/offset).
    Label IDs present in ``objects`` but with no pixels in the data are
    skipped.

    Returns:
        A list of ``BoundingBox`` objects, one per object.
    """
    from sleap_io.model.bbox import PredictedBoundingBox, UserBoundingBox

    data = self.data
    cls = PredictedBoundingBox if self.is_predicted else UserBoundingBox
    sx, sy = self.scale
    ox, oy = self.offset

    # Single-pass: find all foreground pixels at once.
    fg_rows, fg_cols = np.where(data > 0)
    if len(fg_rows) == 0:
        return []

    # Map sparse label IDs to dense indices and compute per-label bounds.
    fg_labels = data[fg_rows, fg_cols]
    unique_labels, inverse = np.unique(fg_labels, return_inverse=True)
    n = len(unique_labels)

    row_min = np.full(n, np.iinfo(np.intp).max, dtype=np.intp)
    row_max = np.full(n, np.iinfo(np.intp).min, dtype=np.intp)
    col_min = np.full(n, np.iinfo(np.intp).max, dtype=np.intp)
    col_max = np.full(n, np.iinfo(np.intp).min, dtype=np.intp)

    np.minimum.at(row_min, inverse, fg_rows)
    np.maximum.at(row_max, inverse, fg_rows)
    np.minimum.at(col_min, inverse, fg_cols)
    np.maximum.at(col_max, inverse, fg_cols)

    label_to_idx = {int(lid): i for i, lid in enumerate(unique_labels)}

    # Build BoundingBox objects using precomputed bounds.
    bboxes = []
    for lid, info in self.objects.items():
        idx = label_to_idx.get(lid)
        if idx is None:
            continue

        x1 = float(col_min[idx] / sx + ox)
        y1 = float(row_min[idx] / sy + oy)
        x2 = float((col_max[idx] + 1) / sx + ox)
        y2 = float((row_max[idx] + 1) / sy + oy)

        kwargs: dict = dict(
            track=info.track,
            instance=info.instance,
            category=info.category,
            name=info.name,
            source=self.source,
        )
        if self.is_predicted:
            kwargs["score"] = info.score if info.score is not None else self.score

        bboxes.append(cls.from_xyxy(x1, y1, x2, y2, **kwargs))

    return bboxes

to_masks()

Decompose into per-object binary SegmentationMasks.

Returns one SegmentationMask per unique non-zero label. Each mask inherits track, category, name, instance, source, scale, and offset from the LabelImage.

Returns:

Type Description
list[SegmentationMask]

A list of SegmentationMask objects, one per object.

Source code in sleap_io/model/label_image.py
def to_masks(self) -> list["SegmentationMask"]:
    """Decompose into per-object binary SegmentationMasks.

    Returns one SegmentationMask per unique non-zero label. Each mask
    inherits track, category, name, instance, source, scale, and offset
    from the ``LabelImage``.

    Returns:
        A list of ``SegmentationMask`` objects, one per object.
    """
    from sleap_io.model.mask import UserSegmentationMask

    result = []
    for label_id in np.sort(self.label_ids):
        lid = int(label_id)
        info = self.objects.get(lid, LabelImage.Info())
        binary_mask = self.data == lid
        result.append(
            UserSegmentationMask.from_numpy(
                binary_mask,
                name=info.name,
                category=info.category,
                track=info.track,
                instance=info.instance,
                source=self.source,
                scale=self.scale,
                offset=self.offset,
            )
        )
    return result

PredictedLabelImage

Bases: sleap_io.model.label_image.LabelImage

Model-predicted label image with confidence score.

Attributes:

Name Type Description
score

Image-level confidence score (0-1).

score_map

Optional dense pixel-level confidence map of shape (H, W) as float32. This can be large and is stored separately in the SLP format. If None, only per-object scores in Info are available. When loaded lazily, decompressed on first access and cached.

score_map_scale

Resolution ratio (sx, sy) for the score map, independent of the label image's own scale.

score_map_offset

Origin (x, y) of the score map in image pixel coordinates.

Methods:

Name Description
__init__

Method generated by attrs for class PredictedLabelImage.

__repr__

Method generated by attrs for class PredictedLabelImage.

Source code in sleap_io/model/label_image.py
@attrs.define(eq=False)
class PredictedLabelImage(LabelImage):
    """Model-predicted label image with confidence score.

    Attributes:
        score: Image-level confidence score (0-1).
        score_map: Optional dense pixel-level confidence map of shape (H, W)
            as float32. This can be large and is stored separately in the SLP
            format. If ``None``, only per-object scores in ``Info`` are available.
            When loaded lazily, decompressed on first access and cached.
        score_map_scale: Resolution ratio ``(sx, sy)`` for the score map,
            independent of the label image's own ``scale``.
        score_map_offset: Origin ``(x, y)`` of the score map in image pixel
            coordinates.
    """

    score: float = attrs.field(default=0.0)
    _score_map: "np.ndarray | None" = attrs.field(default=None, alias="score_map")
    score_map_scale: tuple[float, float] = attrs.field(default=(1.0, 1.0))
    score_map_offset: tuple[float, float] = attrs.field(default=(0.0, 0.0))

    # Private: lazy loading support for score maps.
    _score_map_lazy_loader: "Callable[[], np.ndarray] | None" = attrs.field(
        default=None, init=False, repr=False, eq=False
    )

    @property
    def score_map(self) -> np.ndarray | None:
        """Optional dense pixel-level confidence map of shape ``(H, W)``."""
        if self._score_map is None and self._score_map_lazy_loader is not None:
            self._score_map = self._score_map_lazy_loader()
            self._score_map_lazy_loader = None
        return self._score_map

    @score_map.setter
    def score_map(self, value: np.ndarray | None) -> None:
        self._score_map = value
        self._score_map_lazy_loader = None

__annotations__ = {'score': 'float', '_score_map': "'np.ndarray | None'", 'score_map_scale': 'tuple[float, float]', 'score_map_offset': 'tuple[float, float]', '_score_map_lazy_loader': "'Callable[[], np.ndarray] | 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__ = 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__ = "Model-predicted label image with confidence score.\n\nAttributes:\n score: Image-level confidence score (0-1).\n score_map: Optional dense pixel-level confidence map of shape (H, W)\n as float32. This can be large and is stored separately in the SLP\n format. If ``None``, only per-object scores in ``Info`` are available.\n When loaded lazily, decompressed on first access and cached.\n score_map_scale: Resolution ratio ``(sx, sy)`` for the score map,\n independent of the label image's own ``scale``.\n score_map_offset: Origin ``(x, y)`` of the score map in image pixel\n coordinates.\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__ = 938 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__ = ('_data', 'objects', 'source', 'scale', 'offset', 'score', '_score_map', 'score_map_scale', 'score_map_offset') 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.label_image' 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', '_score_map', 'score_map_scale', 'score_map_offset', '_score_map_lazy_loader') 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__ = ('_score_map', '_score_map_lazy_loader') 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.

score_map property

Optional dense pixel-level confidence map of shape (H, W).

__init__(data=None, objects=NOTHING, source='', scale=(1.0, 1.0), offset=(0.0, 0.0), score=0.0, score_map=None, score_map_scale=(1.0, 1.0), score_map_offset=(0.0, 0.0))

Method generated by attrs for class PredictedLabelImage.

Source code in sleap_io/model/label_image.py
- Use ``SegmentationMask`` when you have individual binary masks per object
  (e.g., from Mask R-CNN, manual annotation, or ROI-based workflows). Each
  mask is stored separately with RLE compression.
- Use ``LabelImage.from_binary_masks()`` to create a label image directly from
  per-object binary numpy arrays (e.g., from SAM or Mask R-CNN output).
- To convert between them, use ``LabelImage.to_masks()`` and
  ``LabelImage.from_masks()``.

For TIFF I/O of label images, see ``sleap_io.load_label_images()`` and
``sleap_io.save_label_images()``.

See Also:
    ``sleap_io.model.mask``: Binary segmentation masks (one per object).
"""

from __future__ import annotations

import copy

__repr__()

Method generated by attrs for class PredictedLabelImage.

Source code in sleap_io/model/label_image.py
"""Data structure for integer label image annotations.

Label images represent per-pixel object segmentation for a single video frame,
where each pixel value encodes which object occupies that pixel. This is the
standard output format of instance segmentation tools like Cellpose and StarDist.

Unlike binary ``SegmentationMask`` objects (one mask per object), a single
``LabelImage`` efficiently stores all objects for a frame in one dense integer
array.

**When to use LabelImage vs SegmentationMask:**

- Use ``LabelImage`` when you have a dense integer array from a segmentation
  tool (Cellpose, StarDist, COCO panoptic) where each pixel value identifies
  an object. One ``LabelImage`` per frame stores all objects at once.

UserLabelImage

Bases: sleap_io.model.label_image.LabelImage

Human-annotated label image.

Methods:

Name Description
__init__

Method generated by attrs for class UserLabelImage.

__repr__

Method generated by attrs for class UserLabelImage.

Attributes:

Name Type Description
__annotations__

dict() -> new empty dictionary

__attrs_own_setattr__

Returns True when the argument is true, False otherwise.

__attrs_props__

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

__doc__

str(object='') -> str

__firstlineno__

int([x]) -> integer

__match_args__

Built-in immutable sequence.

__module__

str(object='') -> str

__slots__

Built-in immutable sequence.

__static_attributes__

Built-in immutable sequence.

Source code in sleap_io/model/label_image.py
@attrs.define(eq=False)
class UserLabelImage(LabelImage):
    """Human-annotated label image."""

    pass

__annotations__ = {} class-attribute

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

__attrs_own_setattr__ = 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__ = 'Human-annotated label image.' 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__ = 931 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__ = ('_data', 'objects', 'source', 'scale', 'offset') 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.label_image' class-attribute

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

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

__slots__ = () class-attribute

Built-in immutable sequence.

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

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

__static_attributes__ = () class-attribute

Built-in immutable sequence.

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

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

__init__(data=None, objects=NOTHING, source='', scale=(1.0, 1.0), offset=(0.0, 0.0))

Method generated by attrs for class UserLabelImage.

Source code in sleap_io/model/label_image.py
- Use ``SegmentationMask`` when you have individual binary masks per object
  (e.g., from Mask R-CNN, manual annotation, or ROI-based workflows). Each
  mask is stored separately with RLE compression.
- Use ``LabelImage.from_binary_masks()`` to create a label image directly from
  per-object binary numpy arrays (e.g., from SAM or Mask R-CNN output).
- To convert between them, use ``LabelImage.to_masks()`` and
  ``LabelImage.from_masks()``.

For TIFF I/O of label images, see ``sleap_io.load_label_images()`` and
``sleap_io.save_label_images()``.

See Also:
    ``sleap_io.model.mask``: Binary segmentation masks (one per object).

__repr__()

Method generated by attrs for class UserLabelImage.

Source code in sleap_io/model/label_image.py
"""Data structure for integer label image annotations.

Label images represent per-pixel object segmentation for a single video frame,
where each pixel value encodes which object occupies that pixel. This is the
standard output format of instance segmentation tools like Cellpose and StarDist.

Unlike binary ``SegmentationMask`` objects (one mask per object), a single
``LabelImage`` efficiently stores all objects for a frame in one dense integer
array.

**When to use LabelImage vs SegmentationMask:**

- Use ``LabelImage`` when you have a dense integer array from a segmentation
  tool (Cellpose, StarDist, COCO panoptic) where each pixel value identifies
  an object. One ``LabelImage`` per frame stores all objects at once.

normalize_label_ids(label_images, by='track')

Remap label IDs so each group gets a globally consistent ID.

Rewrites .data arrays and .objects dicts in place so that the same Track (or category) always maps to the same pixel value across all frames. IDs are assigned 1, 2, 3, ... in order of first appearance.

Parameters:

Name Type Description Default
label_images list[LabelImage]

Label images to normalize. Modified in place.

required
by Literal['track', 'category']

Grouping key.

  • "track": Each unique Track object gets one ID. Identity is by Python object reference (is), not by name — ensure the same Track instance is shared across frames. Objects with track=None each get a unique ID.
  • "category": Each unique category string gets one ID. Within a frame, multiple objects with the same category merge into one pixel value (semantic segmentation).
'track'

Returns:

Type Description
dict[Track, int] | dict[str, int]

Mapping of group key to assigned label ID. Keys are Track objects when by="track" or category strings when by="category". Objects with track=None or empty category are not included.

Raises:

Type Description
ValueError

If by is not "track" or "category".

Source code in sleap_io/model/label_image.py
def normalize_label_ids(
    label_images: list[LabelImage],
    by: "Literal['track', 'category']" = "track",
) -> "dict[Track, int] | dict[str, int]":
    """Remap label IDs so each group gets a globally consistent ID.

    Rewrites ``.data`` arrays and ``.objects`` dicts in place so that the same
    Track (or category) always maps to the same pixel value across all frames.
    IDs are assigned 1, 2, 3, ... in order of first appearance.

    Args:
        label_images: Label images to normalize. Modified in place.
        by: Grouping key.

            - ``"track"``: Each unique ``Track`` object gets one ID.
              Identity is by Python object reference (``is``), not by
              name — ensure the same ``Track`` instance is shared across
              frames. Objects with ``track=None`` each get a unique ID.
            - ``"category"``: Each unique category string gets one ID.
              Within a frame, multiple objects with the same category
              merge into one pixel value (semantic segmentation).

    Returns:
        Mapping of group key to assigned label ID. Keys are ``Track`` objects
        when ``by="track"`` or category strings when ``by="category"``.
        Objects with ``track=None`` or empty category are not included.

    Raises:
        ValueError: If ``by`` is not ``"track"`` or ``"category"``.
    """
    if by not in ("track", "category"):
        raise ValueError(f"by must be 'track' or 'category', got {by!r}.")

    if not label_images:
        return {}

    if by == "track":
        return _normalize_by_track(label_images)
    else:
        return _normalize_by_category(label_images)