Skip to content

colors

sleap_io.rendering.colors

Color palette utilities for pose rendering.

This module provides built-in color palettes and utilities for converting colors to Skia format for rendering pose data.

Functions:

Name Description
build_color_map

Build color mapping based on scheme.

determine_color_scheme

Determine the color scheme to use based on context.

get_palette

Get n colors from a named palette as RGB tuples.

resolve_color

Resolve a flexible color specification to an RGB tuple.

rgb_to_skia_color

Convert RGB tuple to Skia Color4f.

Attributes:

Name Type Description
ColorSpec

Represent a PEP 604 union type

NAMED_COLORS

dict() -> new empty dictionary

PALETTES

dict() -> new empty dictionary

TYPE_CHECKING

Returns True when the argument is true, False otherwise.

__annotations__

dict() -> new empty dictionary

__cached__

str(object='') -> str

__doc__

str(object='') -> str

__file__

str(object='') -> str

__name__

str(object='') -> str

__package__

str(object='') -> str

ColorSpec = tuple[int, int, int] | tuple[float, float, float] | int | float | str module-attribute

Represent a PEP 604 union type

E.g. for int | str

NAMED_COLORS = {'black': (0, 0, 0), 'white': (255, 255, 255), 'red': (255, 0, 0), 'green': (0, 255, 0), 'blue': (0, 0, 255), 'yellow': (255, 255, 0), 'cyan': (0, 255, 255), 'magenta': (255, 0, 255), 'gray': (128, 128, 128), 'grey': (128, 128, 128), 'orange': (255, 165, 0), 'purple': (128, 0, 128), 'pink': (255, 192, 203), 'brown': (139, 69, 19)} module-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)

PALETTES = {'standard': [(0, 114, 189), (217, 83, 25), (237, 177, 32), (126, 47, 142), (119, 172, 48), (77, 190, 238), (162, 20, 47)], 'distinct': [(255, 100, 100), (100, 100, 255), (100, 255, 100), (255, 255, 100), (255, 100, 255), (100, 255, 255), (255, 180, 100), (180, 100, 255), (255, 150, 150), (150, 255, 200)], 'rainbow': [(255, 0, 0), (255, 127, 0), (255, 255, 0), (127, 255, 0), (0, 255, 0), (0, 255, 127), (0, 255, 255), (0, 127, 255), (0, 0, 255), (127, 0, 255), (255, 0, 255), (255, 0, 127)], 'warm': [(255, 87, 51), (255, 140, 0), (255, 195, 0), (255, 215, 0), (255, 69, 0), (220, 20, 60)], 'cool': [(0, 150, 255), (0, 191, 255), (30, 144, 255), (65, 105, 225), (138, 43, 226), (75, 0, 130)], 'pastel': [(255, 179, 186), (255, 223, 186), (255, 255, 186), (186, 255, 201), (186, 225, 255), (218, 186, 255)], 'seaborn': [(76, 114, 176), (221, 132, 82), (85, 168, 104), (196, 78, 82), (129, 114, 179), (147, 120, 96), (218, 139, 195), (140, 140, 140), (204, 185, 116), (100, 181, 205)], 'tableau10': [(31, 119, 180), (255, 127, 14), (44, 160, 44), (214, 39, 40), (148, 103, 189), (140, 86, 75), (227, 119, 194), (127, 127, 127), (188, 189, 34), (23, 190, 207)], 'viridis': [(68, 1, 84), (72, 40, 120), (62, 73, 137), (49, 104, 142), (38, 130, 142), (31, 158, 137), (53, 183, 121), (109, 205, 89), (180, 222, 44), (253, 231, 37)]} module-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)

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.

__annotations__ = {'NAMED_COLORS': 'dict[str, tuple[int, int, int]]', 'PALETTES': 'dict[str, list[tuple[int, int, int]]]'} module-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)

__cached__ = '/home/runner/work/sleap-io/sleap-io/sleap_io/rendering/__pycache__/colors.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__ = 'Color palette utilities for pose rendering.\n\nThis module provides built-in color palettes and utilities for converting colors\nto Skia format for rendering pose data.\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/rendering/colors.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.rendering.colors' 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.rendering' 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'.

build_color_map(scheme, n_instances, n_nodes, n_tracks, track_indices=None, palette='standard', identity_indices=None, n_identities=0, category_indices=None, n_categories=0)

Build color mapping based on scheme.

Parameters:

Name Type Description Default
scheme Literal

Color scheme to use.

required
n_instances int

Number of instances in frame.

required
n_nodes int

Number of nodes in skeleton.

required
n_tracks int

Total number of tracks (for track coloring).

required
track_indices list[int] | None

Track index for each instance (for track coloring).

None
palette Literal | str

Color palette name.

'standard'
identity_indices list[int] | None

Global identity index for each instance (for identity coloring; a palette index into Labels.identities order).

None
n_identities int

Total number of identities (for identity coloring).

0
category_indices list[int] | None

Global category index for each instance (for category coloring; a palette index into Labels.categories order).

None
n_categories int

Total number of categories (for category coloring).

0

Returns:

Type Description
dict[str, list[tuple[int, int, int]]]

Dictionary with 'instance_colors' and/or 'node_colors' lists.

Source code in sleap_io/rendering/colors.py
def build_color_map(
    scheme: ColorScheme,
    n_instances: int,
    n_nodes: int,
    n_tracks: int,
    track_indices: list[int] | None = None,
    palette: PaletteName | str = "standard",
    identity_indices: list[int] | None = None,
    n_identities: int = 0,
    category_indices: list[int] | None = None,
    n_categories: int = 0,
) -> dict[str, list[tuple[int, int, int]]]:
    """Build color mapping based on scheme.

    Args:
        scheme: Color scheme to use.
        n_instances: Number of instances in frame.
        n_nodes: Number of nodes in skeleton.
        n_tracks: Total number of tracks (for track coloring).
        track_indices: Track index for each instance (for track coloring).
        palette: Color palette name.
        identity_indices: Global identity index for each instance (for identity
            coloring; a palette index into ``Labels.identities`` order).
        n_identities: Total number of identities (for identity coloring).
        category_indices: Global category index for each instance (for category
            coloring; a palette index into ``Labels.categories`` order).
        n_categories: Total number of categories (for category coloring).

    Returns:
        Dictionary with 'instance_colors' and/or 'node_colors' lists.
    """
    colors = {}

    if scheme == "track":
        # Colors based on track identity
        n = max(n_tracks, n_instances) if n_tracks > 0 else n_instances
        palette_colors = get_palette(palette, n)

        if track_indices is not None:
            instance_colors = [
                palette_colors[idx % len(palette_colors)] for idx in track_indices
            ]
        else:
            instance_colors = palette_colors[:n_instances]

        colors["instance_colors"] = instance_colors

    elif scheme == "identity":
        # Colors based on global identity. Mirrors the track scheme: one palette
        # color per identity index (in ``Labels.identities`` order).
        n = max(n_identities, n_instances) if n_identities > 0 else n_instances
        palette_colors = get_palette(palette, max(n, 1))

        if identity_indices is not None:
            instance_colors = [
                palette_colors[idx % len(palette_colors)] for idx in identity_indices
            ]
        else:
            instance_colors = palette_colors[:n_instances]

        colors["instance_colors"] = instance_colors

    elif scheme == "category":
        # Colors based on global category. Mirrors the identity scheme: one palette
        # color per category index (in ``Labels.categories`` order).
        n = max(n_categories, n_instances) if n_categories > 0 else n_instances
        palette_colors = get_palette(palette, max(n, 1))

        if category_indices is not None:
            instance_colors = [
                palette_colors[idx % len(palette_colors)] for idx in category_indices
            ]
        else:
            instance_colors = palette_colors[:n_instances]

        colors["instance_colors"] = instance_colors

    elif scheme == "instance":
        # Colors based on instance index
        palette_colors = get_palette(palette, n_instances)
        colors["instance_colors"] = palette_colors

    elif scheme == "node":
        # Colors based on node type
        palette_colors = get_palette(palette, n_nodes)
        colors["node_colors"] = palette_colors

    return colors

determine_color_scheme(has_tracks, is_single_image, scheme='auto')

Determine the color scheme to use based on context.

When scheme is "auto", uses smart defaults: - If tracks available: color by track (consistent across frames) - Else if single image: color by instance (distinguishes animals) - Else if multi-image: color by node type (prevents flicker)

Parameters:

Name Type Description Default
has_tracks bool

Whether the labels have track assignments.

required
is_single_image bool

Whether rendering a single image (vs video).

required
scheme Literal

Requested color scheme. "auto" uses smart defaults.

'auto'

Returns:

Type Description
Literal

Resolved color scheme to use.

Source code in sleap_io/rendering/colors.py
def determine_color_scheme(
    has_tracks: bool,
    is_single_image: bool,
    scheme: ColorScheme = "auto",
) -> ColorScheme:
    """Determine the color scheme to use based on context.

    When scheme is "auto", uses smart defaults:
    - If tracks available: color by track (consistent across frames)
    - Else if single image: color by instance (distinguishes animals)
    - Else if multi-image: color by node type (prevents flicker)

    Args:
        has_tracks: Whether the labels have track assignments.
        is_single_image: Whether rendering a single image (vs video).
        scheme: Requested color scheme. "auto" uses smart defaults.

    Returns:
        Resolved color scheme to use.
    """
    if scheme != "auto":
        return scheme

    # Smart defaults
    if has_tracks:
        return "track"
    elif is_single_image:
        return "instance"
    else:
        return "node"

get_palette(name, n_colors)

Get n colors from a named palette as RGB tuples.

Parameters:

Name Type Description Default
name Literal | str

Palette name. Built-in options: 'standard', 'distinct', 'rainbow', 'warm', 'cool', 'pastel', 'seaborn', 'tableau10', 'viridis'. With colorcet installed: 'glasbey', 'glasbey_hv', 'glasbey_cool', 'glasbey_warm'.

required
n_colors int

Number of colors needed.

required

Returns:

Type Description
list[tuple[int, int, int]]

List of (R, G, B) tuples.

Raises:

Type Description
ValueError

If palette name is not recognized.

Source code in sleap_io/rendering/colors.py
def get_palette(name: PaletteName | str, n_colors: int) -> list[tuple[int, int, int]]:
    """Get n colors from a named palette as RGB tuples.

    Args:
        name: Palette name. Built-in options: 'standard', 'distinct', 'rainbow',
            'warm', 'cool', 'pastel', 'seaborn', 'tableau10', 'viridis'.
            With colorcet installed: 'glasbey', 'glasbey_hv', 'glasbey_cool',
            'glasbey_warm'.
        n_colors: Number of colors needed.

    Returns:
        List of (R, G, B) tuples.

    Raises:
        ValueError: If palette name is not recognized.
    """
    # Try built-in palettes first
    if name in PALETTES:
        palette = PALETTES[name]
        return _extend_palette(palette, n_colors)

    # Try colorcet palettes
    import colorcet as cc

    if name in cc.palette:
        hex_colors = cc.palette[name]
        rgb_colors = [_hex_to_rgb(c) for c in hex_colors]
        return _extend_palette(rgb_colors, n_colors)

    # Unknown palette - raise error with available options
    raise ValueError(
        f"Unknown palette: {name}. "
        f"Available: {list(PALETTES.keys())} (built-in), "
        "or any colorcet palette (e.g., glasbey, glasbey_hv, fire, rainbow4)"
    )

resolve_color(color)

Resolve a flexible color specification to an RGB tuple.

Parameters:

Name Type Description Default
color ColorSpec

Color specification in various formats: - RGB int tuple: (255, 128, 0) - RGB float tuple: (1.0, 0.5, 0.0) - values in 0.0-1.0 range - Grayscale int: 128(128, 128, 128) - Grayscale float: 0.5(127, 127, 127) - Named color: "black", "white", "red", etc. - Hex color: "#ff8000" or "#f80" - Palette index: "tableau10[2]", "glasbey[5]"

required

Returns:

Type Description
tuple[int, int, int]

RGB tuple of integers in 0-255 range.

Raises:

Type Description
ValueError

If color specification is invalid.

Examples:

>>> resolve_color((255, 128, 0))
(255, 128, 0)
>>> resolve_color((1.0, 0.5, 0.0))
(255, 127, 0)
>>> resolve_color("red")
(255, 0, 0)
>>> resolve_color("#ff8000")
(255, 128, 0)
>>> resolve_color("#f80")
(255, 136, 0)
>>> resolve_color("tableau10[2]")
(44, 160, 44)
>>> resolve_color(128)
(128, 128, 128)
>>> resolve_color(0.5)
(127, 127, 127)
Source code in sleap_io/rendering/colors.py
def resolve_color(color: ColorSpec) -> tuple[int, int, int]:
    """Resolve a flexible color specification to an RGB tuple.

    Args:
        color: Color specification in various formats:
            - RGB int tuple: ``(255, 128, 0)``
            - RGB float tuple: ``(1.0, 0.5, 0.0)`` - values in 0.0-1.0 range
            - Grayscale int: ``128`` → ``(128, 128, 128)``
            - Grayscale float: ``0.5`` → ``(127, 127, 127)``
            - Named color: ``"black"``, ``"white"``, ``"red"``, etc.
            - Hex color: ``"#ff8000"`` or ``"#f80"``
            - Palette index: ``"tableau10[2]"``, ``"glasbey[5]"``

    Returns:
        RGB tuple of integers in 0-255 range.

    Raises:
        ValueError: If color specification is invalid.

    Examples:
        >>> resolve_color((255, 128, 0))
        (255, 128, 0)

        >>> resolve_color((1.0, 0.5, 0.0))
        (255, 127, 0)

        >>> resolve_color("red")
        (255, 0, 0)

        >>> resolve_color("#ff8000")
        (255, 128, 0)

        >>> resolve_color("#f80")
        (255, 136, 0)

        >>> resolve_color("tableau10[2]")
        (44, 160, 44)

        >>> resolve_color(128)
        (128, 128, 128)

        >>> resolve_color(0.5)
        (127, 127, 127)
    """
    # Grayscale int
    if isinstance(color, int):
        value = max(0, min(255, color))
        return (value, value, value)

    # Grayscale float (0.0-1.0 range)
    if isinstance(color, float):
        value = int(max(0.0, min(1.0, color)) * 255)
        return (value, value, value)

    # Tuple (RGB)
    if isinstance(color, tuple):
        if len(color) != 3:
            raise ValueError(f"RGB tuple must have 3 elements, got {len(color)}")

        r, g, b = color

        # Detect float vs int by Python type
        if isinstance(r, float) or isinstance(g, float) or isinstance(b, float):
            # Float tuple: 0.0-1.0 range
            r_int = int(max(0.0, min(1.0, float(r))) * 255)
            g_int = int(max(0.0, min(1.0, float(g))) * 255)
            b_int = int(max(0.0, min(1.0, float(b))) * 255)
            return (r_int, g_int, b_int)
        else:
            # Int tuple: 0-255 range
            r_int = max(0, min(255, int(r)))
            g_int = max(0, min(255, int(g)))
            b_int = max(0, min(255, int(b)))
            return (r_int, g_int, b_int)

    # String
    if isinstance(color, str):
        color_lower = color.lower().strip()

        # Named color
        if color_lower in NAMED_COLORS:
            return NAMED_COLORS[color_lower]

        # Hex color
        if color.startswith("#"):
            hex_part = color[1:]
            if len(hex_part) == 3 or len(hex_part) == 6:
                return _hex_to_rgb(color)
            raise ValueError(f"Invalid hex color: {color}")

        # Palette index: "palette_name[index]"
        palette_match = re.match(r"^(\w+)\[(\d+)\]$", color)
        if palette_match:
            palette_name = palette_match.group(1)
            index = int(palette_match.group(2))

            # Get palette colors
            try:
                palette_colors = get_palette(palette_name, index + 1)
                if index < len(palette_colors):
                    return palette_colors[index]
            except ValueError:
                pass

            raise ValueError(f"Invalid palette index: {color}")

        raise ValueError(
            f"Unknown color: {color!r}. Valid named colors: {list(NAMED_COLORS.keys())}"
        )

    raise TypeError(f"Invalid color type: {type(color).__name__}")

rgb_to_skia_color(rgb, alpha=255)

Convert RGB tuple to Skia Color4f.

Parameters:

Name Type Description Default
rgb tuple[int, int, int]

(R, G, B) tuple with values 0-255.

required
alpha int

Alpha value 0-255. Defaults to 255 (opaque).

255

Returns:

Type Description
Color4f

Skia Color4f object.

Source code in sleap_io/rendering/colors.py
def rgb_to_skia_color(rgb: tuple[int, int, int], alpha: int = 255) -> "skia.Color4f":
    """Convert RGB tuple to Skia Color4f.

    Args:
        rgb: (R, G, B) tuple with values 0-255.
        alpha: Alpha value 0-255. Defaults to 255 (opaque).

    Returns:
        Skia Color4f object.
    """
    import skia

    return skia.Color(rgb[0], rgb[1], rgb[2], alpha)