rendering
sleap_io.rendering
¶
Rendering module for visualizing pose data using skia-python.
This module provides high-performance pose rendering with: - Multiple color schemes (by track, instance, or node type) - Various marker shapes (circle, square, diamond, triangle, cross) - Configurable aesthetics (size, width, alpha) - Custom rendering callbacks for overlays - Video export with optional scaling
Example
import sleap_io as sio labels = sio.load_slp("predictions.slp") sio.render_video(labels, "output.mp4") sio.render_image(labels.labeled_frames[0], "frame.png")
Modules:
| Name | Description |
|---|---|
callbacks |
Callback context classes for custom rendering. |
colors |
Color palette utilities for pose rendering. |
core |
Core rendering functions for pose visualization. |
shapes |
Marker shape drawing functions for pose rendering. |
Classes:
| Name | Description |
|---|---|
InstanceContext |
Context passed to per-instance callbacks. |
RenderContext |
Context passed to pre/post render callbacks. |
Functions:
| Name | Description |
|---|---|
get_palette |
Get n colors from a named palette as RGB tuples. |
render_image |
Render single frame with pose overlays. |
render_video |
Render video with pose overlays. |
resolve_color |
Resolve a flexible color specification to an RGB tuple. |
Attributes:
| Name | Type | Description |
|---|---|---|
ColorSpec |
Represent a PEP 604 union type |
|
NAMED_COLORS |
dict() -> new empty dictionary |
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)
__all__ = ['render_video', 'render_image', 'get_palette', 'resolve_color', 'ColorSpec', 'NAMED_COLORS', 'RenderContext', 'InstanceContext']
module-attribute
¶
Built-in mutable sequence.
If no argument is given, the constructor creates a new empty list. The argument must be an iterable if specified.
__cached__ = '/home/runner/work/sleap-io/sleap-io/sleap_io/rendering/__pycache__/__init__.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__ = 'Rendering module for visualizing pose data using skia-python.\n\nThis module provides high-performance pose rendering with:\n- Multiple color schemes (by track, instance, or node type)\n- Various marker shapes (circle, square, diamond, triangle, cross)\n- Configurable aesthetics (size, width, alpha)\n- Custom rendering callbacks for overlays\n- Video export with optional scaling\n\nExample:\n >>> import sleap_io as sio\n >>> labels = sio.load_slp("predictions.slp")\n >>> sio.render_video(labels, "output.mp4")\n >>> sio.render_image(labels.labeled_frames[0], "frame.png")\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/__init__.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'
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'.
__path__ = ['/home/runner/work/sleap-io/sleap-io/sleap_io/rendering']
module-attribute
¶
Built-in mutable sequence.
If no argument is given, the constructor creates a new empty list. The argument must be an iterable if specified.
InstanceContext
¶
Context passed to per-instance callbacks.
This context provides access to the Skia canvas and instance-level metadata for drawing custom overlays after each instance is rendered.
Attributes:
| Name | Type | Description |
|---|---|---|
canvas |
Skia canvas for drawing. |
|
instance_idx |
Index of this instance within the frame. |
|
points |
(n_nodes, 2) array of keypoint coordinates. |
|
track_id |
Track ID if assigned, else None. |
|
track_name |
Track name string if available. |
|
confidence |
Instance confidence score if available. |
|
skeleton_edges |
Edge connectivity as list of (src, dst) tuples. |
|
node_names |
List of node name strings. |
|
scale |
Current scale factor for rendering. |
|
offset |
Current offset (x, y) for cropped/zoomed views. |
Methods:
| Name | Description |
|---|---|
__eq__ |
Method generated by attrs for class InstanceContext. |
__init__ |
Method generated by attrs for class InstanceContext. |
__replace__ |
Method generated by attrs for class InstanceContext. |
__repr__ |
Method generated by attrs for class InstanceContext. |
get_bbox |
Get bounding box of valid points. |
get_centroid |
Get centroid of valid points. |
world_to_canvas |
Transform world coordinates to canvas coordinates. |
Source code in sleap_io/rendering/callbacks.py
@define
class InstanceContext:
"""Context passed to per-instance callbacks.
This context provides access to the Skia canvas and instance-level metadata
for drawing custom overlays after each instance is rendered.
Attributes:
canvas: Skia canvas for drawing.
instance_idx: Index of this instance within the frame.
points: (n_nodes, 2) array of keypoint coordinates.
track_id: Track ID if assigned, else None.
track_name: Track name string if available.
confidence: Instance confidence score if available.
skeleton_edges: Edge connectivity as list of (src, dst) tuples.
node_names: List of node name strings.
scale: Current scale factor for rendering.
offset: Current offset (x, y) for cropped/zoomed views.
"""
canvas: "skia.Canvas"
instance_idx: int
points: np.ndarray
skeleton_edges: list[tuple[int, int]]
node_names: list[str]
track_id: int | None = None
track_name: str | None = None
confidence: float | None = None
scale: float = 1.0
offset: tuple[float, float] = (0.0, 0.0)
def world_to_canvas(self, x: float, y: float) -> tuple[float, float]:
"""Transform world coordinates to canvas coordinates.
Args:
x: X coordinate in world/frame space.
y: Y coordinate in world/frame space.
Returns:
(x, y) coordinates in canvas space.
"""
return (
(x - self.offset[0]) * self.scale,
(y - self.offset[1]) * self.scale,
)
def get_centroid(self) -> tuple[float, float] | None:
"""Get centroid of valid points.
Returns:
(x, y) mean of valid (non-NaN) points, or None if all invalid.
"""
valid_mask = np.isfinite(self.points).all(axis=1)
valid_points = self.points[valid_mask]
if len(valid_points) == 0:
return None
mean_pt = valid_points.mean(axis=0)
return (float(mean_pt[0]), float(mean_pt[1]))
def get_bbox(self) -> tuple[float, float, float, float] | None:
"""Get bounding box of valid points.
Returns:
(x1, y1, x2, y2) bounding box, or None if no valid points.
"""
valid_mask = np.isfinite(self.points).all(axis=1)
valid_points = self.points[valid_mask]
if len(valid_points) == 0:
return None
return (
float(valid_points[:, 0].min()),
float(valid_points[:, 1].min()),
float(valid_points[:, 0].max()),
float(valid_points[:, 1].max()),
)
__annotations__ = {'canvas': "'skia.Canvas'", 'instance_idx': 'int', 'points': 'np.ndarray', 'skeleton_edges': 'list[tuple[int, int]]', 'node_names': 'list[str]', 'track_id': 'int | None', 'track_name': 'str | None', 'confidence': 'float | None', 'scale': 'float', 'offset': 'tuple[float, float]'}
class-attribute
¶
dict() -> new empty dictionary dict(mapping) -> new dictionary initialized from a mapping object's (key, value) pairs dict(iterable) -> new dictionary initialized as if via: d = {} for k, v in iterable: d[k] = v dict(**kwargs) -> new dictionary initialized with the name=value pairs in the keyword argument list. For example: dict(one=1, two=2)
__attrs_own_setattr__ = 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 0x7f41e68fca40>, 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 |
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
|
collected_fields_by_mro |
bool
|
Whether the class fields were collected by method resolution order.
That is, correctly but unlike |
added_init |
bool
|
Whether the class has an attrs-generated |
added_repr |
bool
|
Whether the class has an attrs-generated |
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 |
added_match_args |
bool
|
Whether the class supports positional |
added_str |
bool
|
Whether the class has an attrs-generated |
added_pickling |
bool
|
Whether the class has attrs-generated |
on_setattr_hook |
Callable[[Any, Attribute[Any], Any], Any] | None
|
The class's |
field_transformer |
Callable[[Attribute[Any]], Attribute[Any]] | None
|
The class's |
.. versionadded:: 25.4.0
__doc__ = 'Context passed to per-instance callbacks.\n\nThis context provides access to the Skia canvas and instance-level metadata\nfor drawing custom overlays after each instance is rendered.\n\nAttributes:\n canvas: Skia canvas for drawing.\n instance_idx: Index of this instance within the frame.\n points: (n_nodes, 2) array of keypoint coordinates.\n track_id: Track ID if assigned, else None.\n track_name: Track name string if available.\n confidence: Instance confidence score if available.\n skeleton_edges: Edge connectivity as list of (src, dst) tuples.\n node_names: List of node name strings.\n scale: Current scale factor for rendering.\n offset: Current offset (x, y) for cropped/zoomed views.\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__ = 61
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 literal.
int('0b100', base=0) 4
__match_args__ = ('canvas', 'instance_idx', 'points', 'skeleton_edges', 'node_names', 'track_id', 'track_name', 'confidence', '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.rendering.callbacks'
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__ = ('canvas', 'instance_idx', 'points', 'skeleton_edges', 'node_names', 'track_id', 'track_name', 'confidence', 'scale', 'offset', '__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 InstanceContext.
Source code in sleap_io/rendering/callbacks.py
@define
class RenderContext:
"""Context passed to pre/post render callbacks.
This context provides access to the Skia canvas and frame-level metadata
for drawing custom overlays before or after pose rendering.
Attributes:
canvas: Skia canvas for drawing.
frame_idx: Current frame index.
frame_size: (width, height) tuple of original frame dimensions.
instances: List of instances in this frame.
skeleton_edges: Edge connectivity as list of (src, dst) tuples.
__init__(canvas, instance_idx, points, skeleton_edges, node_names, track_id=None, track_name=None, confidence=None, scale=1.0, offset=(0.0, 0.0))
¶
Method generated by attrs for class InstanceContext.
Source code in sleap_io/rendering/callbacks.py
__replace__(**changes)
¶
Method generated by attrs for class InstanceContext.
__repr__()
¶
Method generated by attrs for class InstanceContext.
Source code in sleap_io/rendering/callbacks.py
"""Callback context classes for custom rendering.
This module provides context objects that are passed to user-defined callbacks
during rendering, giving access to the Skia canvas and rendering metadata.
"""
from __future__ import annotations
from typing import TYPE_CHECKING
import numpy as np
from attrs import define
if TYPE_CHECKING:
import skia
get_bbox()
¶
Get bounding box of valid points.
Returns:
| Type | Description |
|---|---|
tuple[float, float, float, float] | None
|
(x1, y1, x2, y2) bounding box, or None if no valid points. |
Source code in sleap_io/rendering/callbacks.py
def get_bbox(self) -> tuple[float, float, float, float] | None:
"""Get bounding box of valid points.
Returns:
(x1, y1, x2, y2) bounding box, or None if no valid points.
"""
valid_mask = np.isfinite(self.points).all(axis=1)
valid_points = self.points[valid_mask]
if len(valid_points) == 0:
return None
return (
float(valid_points[:, 0].min()),
float(valid_points[:, 1].min()),
float(valid_points[:, 0].max()),
float(valid_points[:, 1].max()),
)
get_centroid()
¶
Get centroid of valid points.
Returns:
| Type | Description |
|---|---|
tuple[float, float] | None
|
(x, y) mean of valid (non-NaN) points, or None if all invalid. |
Source code in sleap_io/rendering/callbacks.py
def get_centroid(self) -> tuple[float, float] | None:
"""Get centroid of valid points.
Returns:
(x, y) mean of valid (non-NaN) points, or None if all invalid.
"""
valid_mask = np.isfinite(self.points).all(axis=1)
valid_points = self.points[valid_mask]
if len(valid_points) == 0:
return None
mean_pt = valid_points.mean(axis=0)
return (float(mean_pt[0]), float(mean_pt[1]))
world_to_canvas(x, y)
¶
Transform world coordinates to canvas coordinates.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
float
|
X coordinate in world/frame space. |
required |
y
|
float
|
Y coordinate in world/frame space. |
required |
Returns:
| Type | Description |
|---|---|
tuple[float, float]
|
(x, y) coordinates in canvas space. |
Source code in sleap_io/rendering/callbacks.py
def world_to_canvas(self, x: float, y: float) -> tuple[float, float]:
"""Transform world coordinates to canvas coordinates.
Args:
x: X coordinate in world/frame space.
y: Y coordinate in world/frame space.
Returns:
(x, y) coordinates in canvas space.
"""
return (
(x - self.offset[0]) * self.scale,
(y - self.offset[1]) * self.scale,
)
RenderContext
¶
Context passed to pre/post render callbacks.
This context provides access to the Skia canvas and frame-level metadata for drawing custom overlays before or after pose rendering.
Attributes:
| Name | Type | Description |
|---|---|---|
canvas |
Skia canvas for drawing. |
|
frame_idx |
Current frame index. |
|
frame_size |
(width, height) tuple of original frame dimensions. |
|
instances |
List of instances in this frame. |
|
skeleton_edges |
Edge connectivity as list of (src, dst) tuples. |
|
node_names |
List of node name strings. |
|
scale |
Current scale factor for rendering. |
|
offset |
Current offset (x, y) for cropped/zoomed views. |
Methods:
| Name | Description |
|---|---|
__eq__ |
Method generated by attrs for class RenderContext. |
__init__ |
Method generated by attrs for class RenderContext. |
__replace__ |
Method generated by attrs for class RenderContext. |
__repr__ |
Method generated by attrs for class RenderContext. |
world_to_canvas |
Transform world coordinates to canvas coordinates. |
Source code in sleap_io/rendering/callbacks.py
@define
class RenderContext:
"""Context passed to pre/post render callbacks.
This context provides access to the Skia canvas and frame-level metadata
for drawing custom overlays before or after pose rendering.
Attributes:
canvas: Skia canvas for drawing.
frame_idx: Current frame index.
frame_size: (width, height) tuple of original frame dimensions.
instances: List of instances in this frame.
skeleton_edges: Edge connectivity as list of (src, dst) tuples.
node_names: List of node name strings.
scale: Current scale factor for rendering.
offset: Current offset (x, y) for cropped/zoomed views.
"""
canvas: "skia.Canvas"
frame_idx: int
frame_size: tuple[int, int]
instances: list
skeleton_edges: list[tuple[int, int]]
node_names: list[str]
scale: float = 1.0
offset: tuple[float, float] = (0.0, 0.0)
def world_to_canvas(self, x: float, y: float) -> tuple[float, float]:
"""Transform world coordinates to canvas coordinates.
Args:
x: X coordinate in world/frame space.
y: Y coordinate in world/frame space.
Returns:
(x, y) coordinates in canvas space.
"""
return (
(x - self.offset[0]) * self.scale,
(y - self.offset[1]) * self.scale,
)
__annotations__ = {'canvas': "'skia.Canvas'", 'frame_idx': 'int', 'frame_size': 'tuple[int, int]', 'instances': 'list', 'skeleton_edges': 'list[tuple[int, int]]', 'node_names': 'list[str]', 'scale': 'float', 'offset': 'tuple[float, float]'}
class-attribute
¶
dict() -> new empty dictionary dict(mapping) -> new dictionary initialized from a mapping object's (key, value) pairs dict(iterable) -> new dictionary initialized as if via: d = {} for k, v in iterable: d[k] = v dict(**kwargs) -> new dictionary initialized with the name=value pairs in the keyword argument list. For example: dict(one=1, two=2)
__attrs_own_setattr__ = 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 0x7f41e68fca40>, 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 |
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
|
collected_fields_by_mro |
bool
|
Whether the class fields were collected by method resolution order.
That is, correctly but unlike |
added_init |
bool
|
Whether the class has an attrs-generated |
added_repr |
bool
|
Whether the class has an attrs-generated |
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 |
added_match_args |
bool
|
Whether the class supports positional |
added_str |
bool
|
Whether the class has an attrs-generated |
added_pickling |
bool
|
Whether the class has attrs-generated |
on_setattr_hook |
Callable[[Any, Attribute[Any], Any], Any] | None
|
The class's |
field_transformer |
Callable[[Attribute[Any]], Attribute[Any]] | None
|
The class's |
.. versionadded:: 25.4.0
__doc__ = 'Context passed to pre/post render callbacks.\n\nThis context provides access to the Skia canvas and frame-level metadata\nfor drawing custom overlays before or after pose rendering.\n\nAttributes:\n canvas: Skia canvas for drawing.\n frame_idx: Current frame index.\n frame_size: (width, height) tuple of original frame dimensions.\n instances: List of instances in this frame.\n skeleton_edges: Edge connectivity as list of (src, dst) tuples.\n node_names: List of node name strings.\n scale: Current scale factor for rendering.\n offset: Current offset (x, y) for cropped/zoomed views.\n'
class-attribute
¶
str(object='') -> str str(bytes_or_buffer[, encoding[, errors]]) -> str
Create a new string object from the given object. If encoding or errors is specified, then the object must expose a data buffer that will be decoded using the given encoding and error handler. Otherwise, returns the result of object.str() (if defined) or repr(object). encoding defaults to 'utf-8'. errors defaults to 'strict'.
__firstlineno__ = 18
class-attribute
¶
int([x]) -> integer int(x, base=10) -> integer
Convert a number or string to an integer, or return 0 if no arguments are given. If x is a number, return x.int(). For floating-point numbers, this truncates towards zero.
If x is not a number or if base is given, then x must be a string, bytes, or bytearray instance representing an integer literal in the given base. The literal can be preceded by '+' or '-' and be surrounded by whitespace. The base defaults to 10. Valid bases are 0 and 2-36. Base 0 means to interpret the base from the string as an integer literal.
int('0b100', base=0) 4
__match_args__ = ('canvas', 'frame_idx', 'frame_size', 'instances', 'skeleton_edges', 'node_names', '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.rendering.callbacks'
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__ = ('canvas', 'frame_idx', 'frame_size', 'instances', 'skeleton_edges', 'node_names', 'scale', 'offset', '__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 RenderContext.
Source code in sleap_io/rendering/callbacks.py
@define
class RenderContext:
"""Context passed to pre/post render callbacks.
This context provides access to the Skia canvas and frame-level metadata
for drawing custom overlays before or after pose rendering.
Attributes:
canvas: Skia canvas for drawing.
frame_idx: Current frame index.
frame_size: (width, height) tuple of original frame dimensions.
__init__(canvas, frame_idx, frame_size, instances, skeleton_edges, node_names, scale=1.0, offset=(0.0, 0.0))
¶
Method generated by attrs for class RenderContext.
Source code in sleap_io/rendering/callbacks.py
__replace__(**changes)
¶
Method generated by attrs for class RenderContext.
__repr__()
¶
Method generated by attrs for class RenderContext.
Source code in sleap_io/rendering/callbacks.py
"""Callback context classes for custom rendering.
This module provides context objects that are passed to user-defined callbacks
during rendering, giving access to the Skia canvas and rendering metadata.
"""
from __future__ import annotations
from typing import TYPE_CHECKING
import numpy as np
from attrs import define
if TYPE_CHECKING:
import skia
world_to_canvas(x, y)
¶
Transform world coordinates to canvas coordinates.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
float
|
X coordinate in world/frame space. |
required |
y
|
float
|
Y coordinate in world/frame space. |
required |
Returns:
| Type | Description |
|---|---|
tuple[float, float]
|
(x, y) coordinates in canvas space. |
Source code in sleap_io/rendering/callbacks.py
def world_to_canvas(self, x: float, y: float) -> tuple[float, float]:
"""Transform world coordinates to canvas coordinates.
Args:
x: X coordinate in world/frame space.
y: Y coordinate in world/frame space.
Returns:
(x, y) coordinates in canvas space.
"""
return (
(x - self.offset[0]) * self.scale,
(y - self.offset[1]) * self.scale,
)
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)"
)
render_image(source, save_path=None, *, lf_ind=None, video=None, frame_idx=None, image=None, crop=None, color_by='auto', palette='standard', marker_shape='circle', marker_size=4.0, line_width=2.0, alpha=1.0, show_nodes=True, show_edges=True, scale=1.0, background='video', pre_render_callback=None, post_render_callback=None, per_instance_callback=None)
¶
Render single frame with pose overlays.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source
|
Labels | LabeledFrame | list[Instance | PredictedInstance]
|
LabeledFrame, Labels (with frame specifier), or list of instances. |
required |
save_path
|
str | Path | None
|
Output image path (PNG/JPEG). If None, only returns array. |
None
|
lf_ind
|
int | None
|
LabeledFrame index within Labels.labeled_frames (when source is Labels). |
None
|
video
|
Video | int | None
|
Video object or video index (used with frame_idx when source is Labels). |
None
|
frame_idx
|
int | None
|
Video frame index (0-based, used with video when source is Labels). |
None
|
image
|
ndarray | None
|
Override image array (H, W) or (H, W, C) uint8. Fetched from LabeledFrame if not provided. |
None
|
crop
|
CropSpec
|
Crop specification. Bounds are (x1, y1, x2, y2) where (x1, y1) is the top-left corner and (x2, y2) is the bottom-right (exclusive). Origin (0, 0) is at the image top-left. Can be:
|
None
|
color_by
|
Literal
|
Color scheme - 'track', 'instance', 'node', or 'auto'. |
'auto'
|
palette
|
Literal | str
|
Color palette name. |
'standard'
|
marker_shape
|
Literal
|
Node marker shape. |
'circle'
|
marker_size
|
float
|
Node marker radius in pixels. |
4.0
|
line_width
|
float
|
Edge line width in pixels. |
2.0
|
alpha
|
float
|
Global transparency (0.0-1.0). |
1.0
|
show_nodes
|
bool
|
Whether to draw node markers. |
True
|
show_edges
|
bool
|
Whether to draw skeleton edges. |
True
|
scale
|
float
|
Output scale factor. Applied after cropping. |
1.0
|
background
|
Literal['video'] | ColorSpec
|
Background control. Can be:
- |
'video'
|
pre_render_callback
|
Callable[[RenderContext], None] | None
|
Called before poses are drawn. |
None
|
post_render_callback
|
Callable[[RenderContext], None] | None
|
Called after poses are drawn. |
None
|
per_instance_callback
|
Callable[[InstanceContext], None] | None
|
Called after each instance is drawn. |
None
|
Returns:
| Type | Description |
|---|---|
ndarray
|
Rendered numpy array (H, W, 3) uint8. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If background="video" and video unavailable. |
Examples:
Render a single labeled frame:
>>> import sleap_io as sio
>>> labels = sio.load_slp("predictions.slp")
>>> lf = labels.labeled_frames[0]
>>> img = sio.render_image(lf)
Render with solid color background (no video required):
>>> img = sio.render_image(lf, background="black")
>>> img = sio.render_image(lf, background=(40, 40, 40))
>>> img = sio.render_image(lf, background="#404040")
>>> img = sio.render_image(lf, background=0.25)
Crop to a region (pixel coordinates):
Normalized crop (center 50% of frame):
Render and save to file:
>>> sio.render_image(labels, lf_ind=0, save_path="frame.png")
>>> sio.render_image(labels, video=0, frame_idx=42, save_path="frame.png")
Source code in sleap_io/rendering/core.py
def render_image(
source: "Labels | LabeledFrame | list[Instance | PredictedInstance]",
save_path: str | Path | None = None,
*,
# Frame specification (for Labels input)
lf_ind: int | None = None,
video: "Video | int | None" = None,
frame_idx: int | None = None,
# Image override
image: np.ndarray | None = None,
# Cropping
crop: CropSpec = None,
# Appearance
color_by: ColorScheme = "auto",
palette: PaletteName | str = "standard",
marker_shape: MarkerShape = "circle",
marker_size: float = 4.0,
line_width: float = 2.0,
alpha: float = 1.0,
show_nodes: bool = True,
show_edges: bool = True,
scale: float = 1.0,
# Background control
background: Literal["video"] | ColorSpec = "video",
# Callbacks
pre_render_callback: Callable[[RenderContext], None] | None = None,
post_render_callback: Callable[[RenderContext], None] | None = None,
per_instance_callback: Callable[[InstanceContext], None] | None = None,
) -> np.ndarray:
"""Render single frame with pose overlays.
Args:
source: LabeledFrame, Labels (with frame specifier), or list of instances.
save_path: Output image path (PNG/JPEG). If None, only returns array.
lf_ind: LabeledFrame index within Labels.labeled_frames (when source is Labels).
video: Video object or video index (used with frame_idx when source is Labels).
frame_idx: Video frame index (0-based, used with video when source is Labels).
image: Override image array (H, W) or (H, W, C) uint8. Fetched from
LabeledFrame if not provided.
crop: Crop specification. Bounds are (x1, y1, x2, y2) where (x1, y1) is
the top-left corner and (x2, y2) is the bottom-right (exclusive).
Origin (0, 0) is at the image top-left. Can be:
- **Pixel coordinates** (int tuple): ``(100, 100, 300, 300)`` crops
from pixel (100, 100) to (300, 300).
- **Normalized coordinates** (float tuple in [0.0, 1.0]):
``(0.25, 0.25, 0.75, 0.75)`` crops the center 50% of the frame.
Detection is type-based: all values must be ``float`` and in range.
- ``None``: No cropping (default).
color_by: Color scheme - 'track', 'instance', 'node', or 'auto'.
palette: Color palette name.
marker_shape: Node marker shape.
marker_size: Node marker radius in pixels.
line_width: Edge line width in pixels.
alpha: Global transparency (0.0-1.0).
show_nodes: Whether to draw node markers.
show_edges: Whether to draw skeleton edges.
scale: Output scale factor. Applied after cropping.
background: Background control. Can be:
- ``"video"``: Load video frame (default). Raises error if unavailable.
- Any color spec: Use solid color background, skip video loading entirely.
Supports RGB tuples ``(255, 128, 0)``, float tuples ``(1.0, 0.5, 0.0)``,
grayscale ``128`` or ``0.5``, named colors ``"black"``, hex ``"#ff8000"``,
or palette index ``"tableau10[2]"``.
pre_render_callback: Called before poses are drawn.
post_render_callback: Called after poses are drawn.
per_instance_callback: Called after each instance is drawn.
Returns:
Rendered numpy array (H, W, 3) uint8.
Raises:
ValueError: If background="video" and video unavailable.
Examples:
Render a single labeled frame:
>>> import sleap_io as sio
>>> labels = sio.load_slp("predictions.slp")
>>> lf = labels.labeled_frames[0]
>>> img = sio.render_image(lf)
Render with solid color background (no video required):
>>> img = sio.render_image(lf, background="black")
>>> img = sio.render_image(lf, background=(40, 40, 40))
>>> img = sio.render_image(lf, background="#404040")
>>> img = sio.render_image(lf, background=0.25)
Crop to a region (pixel coordinates):
>>> img = sio.render_image(lf, crop=(100, 100, 300, 300))
Normalized crop (center 50% of frame):
>>> img = sio.render_image(lf, crop=(0.25, 0.25, 0.75, 0.75))
Render and save to file:
>>> sio.render_image(labels, lf_ind=0, save_path="frame.png")
>>> sio.render_image(labels, video=0, frame_idx=42, save_path="frame.png")
"""
import skia # noqa: F401
from sleap_io.model.instance import Instance, PredictedInstance
from sleap_io.model.labeled_frame import LabeledFrame
from sleap_io.model.labels import Labels
# Handle background parameter
use_video = background == "video"
background_color: tuple[int, int, int] | None = None
if not use_video:
background_color = resolve_color(background)
# Resolve source to LabeledFrame or instances
if isinstance(source, Labels):
if video is not None and frame_idx is not None:
# Render by video + frame_idx
target_video = source.videos[video] if isinstance(video, int) else video
lf_list = source.find(target_video, frame_idx)
if not lf_list:
raise ValueError(
f"No labeled frame found for video {target_video} "
f"at frame {frame_idx}"
)
lf = lf_list[0]
elif lf_ind is not None:
# Render by labeled frame index
lf = source.labeled_frames[lf_ind]
else:
# Default to first labeled frame
lf = source.labeled_frames[0]
instances = list(lf.instances)
skeleton = instances[0].skeleton if instances else source.skeletons[0]
edge_inds = skeleton.edge_inds
node_names = [n.name for n in skeleton.nodes]
fidx_for_callback = lf.frame_idx
# Get track info
track_indices = []
n_tracks = len(source.tracks)
for inst in instances:
if inst.track is not None and inst.track in source.tracks:
track_indices.append(source.tracks.index(inst.track))
else:
track_indices.append(0)
has_tracks = n_tracks > 0
# Convert instances to point arrays (needed for both image size and rendering)
instances_points = [inst.numpy() for inst in instances]
# Get image if not provided
if image is None:
if background_color is not None:
# Solid color background - skip video loading entirely
video_obj = lf.video
if hasattr(video_obj, "shape") and video_obj.shape is not None:
h, w = video_obj.shape[1:3]
else:
# Estimate from points
h, w = _estimate_frame_size(instances_points)
image = _create_blank_frame(h, w, background_color)[:, :, :3]
else:
# Load video frame
try:
image = lf.image
if image is None:
raise ValueError("No image available")
except Exception:
raise ValueError(
"Video unavailable. Specify a background color to render "
"without video, e.g., background='black' or "
"background=(40, 40, 40)."
)
elif isinstance(source, LabeledFrame):
lf = source
instances = list(lf.instances)
skeleton = instances[0].skeleton if instances else None
if skeleton is None:
raise ValueError("LabeledFrame has no instances with skeleton")
edge_inds = skeleton.edge_inds
node_names = [n.name for n in skeleton.nodes]
fidx_for_callback = lf.frame_idx
track_indices = None
n_tracks = 0
has_tracks = False
# Convert instances to point arrays (needed for both image size and rendering)
instances_points = [inst.numpy() for inst in instances]
# Get image if not provided
if image is None:
if background_color is not None:
# Solid color background - skip video loading entirely
video_obj = lf.video
if hasattr(video_obj, "shape") and video_obj.shape is not None:
h, w = video_obj.shape[1:3]
else:
# Estimate from points
h, w = _estimate_frame_size(instances_points)
image = _create_blank_frame(h, w, background_color)[:, :, :3]
else:
# Load video frame
try:
image = lf.image
if image is None:
raise ValueError("No image available")
except Exception:
raise ValueError(
"Video unavailable. Specify a background color to render "
"without video, e.g., background='black' or "
"background=(40, 40, 40)."
)
elif isinstance(source, list) and all(
isinstance(x, (Instance, PredictedInstance)) for x in source
):
instances = source
if not instances:
raise ValueError("Empty instances list")
skeleton = instances[0].skeleton
edge_inds = skeleton.edge_inds
node_names = [n.name for n in skeleton.nodes]
fidx_for_callback = 0
track_indices = None
n_tracks = 0
has_tracks = False
# Convert instances to point arrays
instances_points = [inst.numpy() for inst in instances]
if image is None:
raise ValueError(
"image parameter required when source is list of instances"
)
else:
raise TypeError(
f"source must be Labels, LabeledFrame, or list of instances, "
f"got {type(source)}"
)
# Apply cropping if specified
render_image_data = image
render_points = instances_points
crop_offset: tuple[float, float] = (0.0, 0.0)
if crop is not None:
h, w = image.shape[:2]
# Resolve normalized or pixel coordinates
crop_bounds = _resolve_crop(crop, (h, w))
crop_offset = (float(crop_bounds[0]), float(crop_bounds[1]))
render_image_data, render_points, _ = _apply_crop(
image, instances_points, crop_bounds
)
# Build instance metadata for callbacks
instance_metadata = []
for inst in instances:
meta = {}
if hasattr(inst, "track") and inst.track is not None:
meta["track_name"] = inst.track.name
if hasattr(inst, "score"):
meta["confidence"] = inst.score
instance_metadata.append(meta)
# Determine color scheme
resolved_scheme = determine_color_scheme(
has_tracks=has_tracks,
is_single_image=True,
scheme=color_by,
)
# Render
rendered = render_frame(
frame=render_image_data,
instances_points=render_points,
edge_inds=edge_inds,
node_names=node_names,
color_by=resolved_scheme,
palette=palette,
marker_shape=marker_shape,
marker_size=marker_size,
line_width=line_width,
alpha=alpha,
show_nodes=show_nodes,
show_edges=show_edges,
scale=scale,
track_indices=track_indices,
n_tracks=n_tracks,
pre_render_callback=pre_render_callback,
post_render_callback=post_render_callback,
per_instance_callback=per_instance_callback,
frame_idx=fidx_for_callback,
instance_metadata=instance_metadata,
crop_offset=crop_offset,
)
# Save if save_path provided
if save_path is not None:
from PIL import Image
save_path_ = Path(save_path)
save_path_.parent.mkdir(parents=True, exist_ok=True)
Image.fromarray(rendered).save(save_path_)
return rendered
render_video(source, save_path=None, *, video=None, frame_inds=None, start=None, end=None, include_unlabeled=False, crop=None, preset=None, scale=1.0, color_by='auto', palette='standard', marker_shape='circle', marker_size=4.0, line_width=2.0, alpha=1.0, show_nodes=True, show_edges=True, fps=None, codec='libx264', crf=25, x264_preset='superfast', background='video', pre_render_callback=None, post_render_callback=None, per_instance_callback=None, progress_callback=None, show_progress=True)
¶
Render video with pose overlays.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source
|
Labels | list[LabeledFrame]
|
Labels object or list of LabeledFrames to render. |
required |
save_path
|
str | Path | None
|
Output video path. If None, returns list of rendered arrays. |
None
|
video
|
Video | int | None
|
Video to render from (default: first video in Labels). |
None
|
frame_inds
|
list[int] | None
|
Specific frame indices to render. |
None
|
start
|
int | None
|
Start frame index (inclusive). |
None
|
end
|
int | None
|
End frame index (exclusive). |
None
|
include_unlabeled
|
bool
|
If True, render all frames in range even if they have no LabeledFrame (just shows video frame without poses). Default False. |
False
|
crop
|
CropSpec
|
Static crop applied uniformly to all frames. Bounds are (x1, y1, x2, y2) where (x1, y1) is the top-left corner and (x2, y2) is the bottom-right (exclusive). Supports:
|
None
|
preset
|
Literal['preview', 'draft', 'final'] | None
|
Quality preset ('preview'=0.25x, 'draft'=0.5x, 'final'=1.0x). |
None
|
scale
|
float
|
Scale factor (overrides preset if both provided). |
1.0
|
color_by
|
Literal
|
Color scheme - 'track', 'instance', 'node', or 'auto'. |
'auto'
|
palette
|
Literal | str
|
Color palette name. |
'standard'
|
marker_shape
|
Literal
|
Node marker shape. |
'circle'
|
marker_size
|
float
|
Node marker radius in pixels. |
4.0
|
line_width
|
float
|
Edge line width in pixels. |
2.0
|
alpha
|
float
|
Global transparency (0.0-1.0). |
1.0
|
show_nodes
|
bool
|
Whether to draw node markers. |
True
|
show_edges
|
bool
|
Whether to draw skeleton edges. |
True
|
fps
|
float | None
|
Output frame rate (default: source video fps). |
None
|
codec
|
str
|
Video codec for encoding. |
'libx264'
|
crf
|
int
|
Constant rate factor for quality (2-32, lower=better). Default 25. |
25
|
x264_preset
|
str
|
H.264 encoding preset (ultrafast, superfast, fast, medium, slow). |
'superfast'
|
background
|
Literal['video'] | ColorSpec
|
Background control. Can be:
- |
'video'
|
pre_render_callback
|
Callable[[RenderContext], None] | None
|
Called before each frame's poses are drawn. |
None
|
post_render_callback
|
Callable[[RenderContext], None] | None
|
Called after each frame's poses are drawn. |
None
|
per_instance_callback
|
Callable[[InstanceContext], None] | None
|
Called after each instance is drawn. |
None
|
progress_callback
|
Callable[[int, int], bool] | None
|
Called with (current, total), return False to cancel. |
None
|
show_progress
|
bool
|
Show tqdm progress bar. |
True
|
Returns:
| Type | Description |
|---|---|
Video | list[ndarray]
|
If save_path provided: Video object pointing to output file. If save_path is None: List of rendered numpy arrays (H, W, 3) uint8. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If background="video" and video unavailable. |
Examples:
Render full video with pose overlays:
>>> import sleap_io as sio
>>> labels = sio.load_slp("predictions.slp")
>>> sio.render_video(labels, "output.mp4")
Fast preview at reduced resolution:
Get rendered frames as numpy arrays:
Source code in sleap_io/rendering/core.py
def render_video(
source: "Labels | list[LabeledFrame]",
save_path: str | Path | None = None,
*,
# Video selection
video: "Video | int | None" = None,
# Frame selection
frame_inds: list[int] | None = None,
start: int | None = None,
end: int | None = None,
include_unlabeled: bool = False,
# Cropping
crop: CropSpec = None,
# Quality/scale
preset: Literal["preview", "draft", "final"] | None = None,
scale: float = 1.0,
# Appearance
color_by: ColorScheme = "auto",
palette: PaletteName | str = "standard",
marker_shape: MarkerShape = "circle",
marker_size: float = 4.0,
line_width: float = 2.0,
alpha: float = 1.0,
show_nodes: bool = True,
show_edges: bool = True,
# Video encoding
fps: float | None = None,
codec: str = "libx264",
crf: int = 25,
x264_preset: str = "superfast",
# Background control
background: Literal["video"] | ColorSpec = "video",
# Callbacks
pre_render_callback: Callable[[RenderContext], None] | None = None,
post_render_callback: Callable[[RenderContext], None] | None = None,
per_instance_callback: Callable[[InstanceContext], None] | None = None,
# Progress
progress_callback: Callable[[int, int], bool] | None = None,
show_progress: bool = True,
) -> "Video | list[np.ndarray]":
"""Render video with pose overlays.
Args:
source: Labels object or list of LabeledFrames to render.
save_path: Output video path. If None, returns list of rendered arrays.
video: Video to render from (default: first video in Labels).
frame_inds: Specific frame indices to render.
start: Start frame index (inclusive).
end: End frame index (exclusive).
include_unlabeled: If True, render all frames in range even if they have
no LabeledFrame (just shows video frame without poses). Default False.
crop: Static crop applied uniformly to all frames. Bounds are
(x1, y1, x2, y2) where (x1, y1) is the top-left corner and (x2, y2)
is the bottom-right (exclusive). Supports:
- **Pixel coordinates** (int tuple): ``(100, 100, 300, 300)``
- **Normalized coordinates** (float tuple in [0.0, 1.0]):
``(0.25, 0.25, 0.75, 0.75)`` crops the center 50%.
- ``None``: No cropping (default).
preset: Quality preset ('preview'=0.25x, 'draft'=0.5x, 'final'=1.0x).
scale: Scale factor (overrides preset if both provided).
color_by: Color scheme - 'track', 'instance', 'node', or 'auto'.
palette: Color palette name.
marker_shape: Node marker shape.
marker_size: Node marker radius in pixels.
line_width: Edge line width in pixels.
alpha: Global transparency (0.0-1.0).
show_nodes: Whether to draw node markers.
show_edges: Whether to draw skeleton edges.
fps: Output frame rate (default: source video fps).
codec: Video codec for encoding.
crf: Constant rate factor for quality (2-32, lower=better). Default 25.
x264_preset: H.264 encoding preset (ultrafast, superfast, fast, medium, slow).
background: Background control. Can be:
- ``"video"``: Load video frame (default). Raises error if unavailable.
- Any color spec: Use solid color background, skip video loading entirely.
Supports RGB tuples ``(255, 128, 0)``, float tuples ``(1.0, 0.5, 0.0)``,
grayscale ``128`` or ``0.5``, named colors ``"black"``, hex ``"#ff8000"``,
or palette index ``"tableau10[2]"``.
pre_render_callback: Called before each frame's poses are drawn.
post_render_callback: Called after each frame's poses are drawn.
per_instance_callback: Called after each instance is drawn.
progress_callback: Called with (current, total), return False to cancel.
show_progress: Show tqdm progress bar.
Returns:
If save_path provided: Video object pointing to output file.
If save_path is None: List of rendered numpy arrays (H, W, 3) uint8.
Raises:
ValueError: If background="video" and video unavailable.
Examples:
Render full video with pose overlays:
>>> import sleap_io as sio
>>> labels = sio.load_slp("predictions.slp")
>>> sio.render_video(labels, "output.mp4")
Fast preview at reduced resolution:
>>> sio.render_video(labels, "preview.mp4", preset="preview")
Get rendered frames as numpy arrays:
>>> frames = sio.render_video(labels)
"""
import skia # noqa: F401
from sleap_io.model.labeled_frame import LabeledFrame
from sleap_io.model.labels import Labels
from sleap_io.model.video import Video as VideoModel
# Handle background parameter
use_video = background == "video"
background_color: tuple[int, int, int] | None = None
if not use_video:
background_color = resolve_color(background)
# Handle preset
if preset is not None and preset in PRESETS:
scale = PRESETS[preset]["scale"]
# Resolve source
if isinstance(source, Labels):
labels = source
# Resolve video
if video is None:
if not labels.videos:
raise ValueError("Labels has no videos")
target_video = labels.videos[0]
elif isinstance(video, int):
target_video = labels.videos[video]
else:
target_video = video
# Get labeled frames for this video
labeled_frames = labels.find(target_video)
if not labeled_frames:
raise ValueError(f"No labeled frames found for video {target_video}")
# Sort by frame index
labeled_frames = sorted(labeled_frames, key=lambda lf: lf.frame_idx)
# Get skeleton info
skeleton = labels.skeletons[0] if labels.skeletons else None
if skeleton is None and labeled_frames:
for lf in labeled_frames:
for inst in lf.instances:
skeleton = inst.skeleton
break
if skeleton:
break
if skeleton is None:
raise ValueError("No skeleton found in labels")
edge_inds = skeleton.edge_inds
node_names = [n.name for n in skeleton.nodes]
n_tracks = len(labels.tracks)
has_tracks = n_tracks > 0
elif isinstance(source, list) and all(isinstance(x, LabeledFrame) for x in source):
labeled_frames = source
if not labeled_frames:
raise ValueError("Empty labeled frames list")
target_video = labeled_frames[0].video
skeleton = None
for lf in labeled_frames:
for inst in lf.instances:
skeleton = inst.skeleton
break
if skeleton:
break
if skeleton is None:
raise ValueError("No skeleton found in labeled frames")
edge_inds = skeleton.edge_inds
node_names = [n.name for n in skeleton.nodes]
n_tracks = 0
has_tracks = False
labels = None
else:
raise TypeError(
f"source must be Labels or list of LabeledFrame, got {type(source)}"
)
# Create frame index mapping
frame_idx_to_lf = {lf.frame_idx: lf for lf in labeled_frames}
# Get video frame count for include_unlabeled mode
n_video_frames = None
if include_unlabeled:
if hasattr(target_video, "shape") and target_video.shape is not None:
n_video_frames = target_video.shape[0]
# Determine frame indices to render
if frame_inds is not None:
render_indices = frame_inds
elif start is not None or end is not None:
labeled_indices = sorted(frame_idx_to_lf.keys())
if include_unlabeled and n_video_frames is not None:
# Render all frames in range, not just labeled ones
start_idx = start if start is not None else 0
end_idx = end if end is not None else n_video_frames
render_indices = list(range(start_idx, end_idx))
else:
# Only render labeled frames in range
start_idx = start if start is not None else min(labeled_indices, default=0)
end_idx = end if end is not None else max(labeled_indices, default=0) + 1
render_indices = [i for i in labeled_indices if start_idx <= i < end_idx]
else:
if include_unlabeled and n_video_frames is not None:
# Render entire video
render_indices = list(range(n_video_frames))
else:
# Only render labeled frames
render_indices = sorted(frame_idx_to_lf.keys())
if not render_indices:
raise ValueError("No frames to render")
# Determine FPS
if fps is None:
# Try to get from video
if hasattr(target_video, "backend") and target_video.backend is not None:
try:
fps = target_video.backend.fps
except Exception:
fps = 30.0
else:
fps = 30.0
# Determine color scheme
resolved_scheme = determine_color_scheme(
has_tracks=has_tracks,
is_single_image=False,
scheme=color_by,
)
# Resolve crop bounds once (before the loop)
# We need the video shape to resolve normalized coordinates
crop_bounds: tuple[int, int, int, int] | None = None
crop_offset: tuple[float, float] = (0.0, 0.0)
if crop is not None:
if hasattr(target_video, "shape") and target_video.shape is not None:
h, w = target_video.shape[1:3]
else:
# Fallback: try to get from first frame
h, w = 480, 640 # reasonable default
crop_bounds = _resolve_crop(crop, (h, w))
crop_offset = (float(crop_bounds[0]), float(crop_bounds[1]))
# Setup progress
if show_progress:
try:
from tqdm import tqdm
iterator = tqdm(render_indices, desc="Rendering", unit="frame")
except ImportError:
iterator = render_indices
else:
iterator = render_indices
# Render frames
rendered_frames = []
total_frames = len(render_indices)
for i, fidx in enumerate(iterator):
# Check for cancellation
if progress_callback is not None:
if progress_callback(i, total_frames) is False:
break
lf = frame_idx_to_lf.get(fidx)
# Handle frames without LabeledFrame
if lf is None:
if not include_unlabeled:
continue
# Render just the video frame without poses
if background_color is not None:
# Solid color background - skip video loading entirely
if hasattr(target_video, "shape") and target_video.shape is not None:
h, w = target_video.shape[1:3]
else:
# No video metadata and no points - use minimum default
h, w = 64, 64
image = _create_blank_frame(h, w, background_color)[:, :, :3]
else:
try:
image = target_video[fidx]
if image is None:
raise ValueError("No image")
except Exception:
raise ValueError(
f"Video unavailable at frame {fidx}. "
"Specify a background color to render without video."
)
# Apply cropping if specified
render_image_data = image
if crop_bounds is not None:
render_image_data, _, _ = _apply_crop(image, [], crop_bounds)
# Render frame without poses
rendered = render_frame(
frame=render_image_data,
instances_points=[],
edge_inds=edge_inds,
node_names=node_names,
color_by=resolved_scheme,
palette=palette,
marker_shape=marker_shape,
marker_size=marker_size,
line_width=line_width,
alpha=alpha,
show_nodes=show_nodes,
show_edges=show_edges,
scale=scale,
track_indices=None,
n_tracks=n_tracks,
pre_render_callback=pre_render_callback,
post_render_callback=post_render_callback,
per_instance_callback=None,
frame_idx=fidx,
instance_metadata=[],
crop_offset=crop_offset,
)
rendered_frames.append(rendered)
continue
instances = list(lf.instances)
instances_points = [inst.numpy() for inst in instances]
# Get track indices
track_indices = None
if labels is not None and has_tracks:
track_indices = []
for inst in instances:
if inst.track is not None and inst.track in labels.tracks:
track_indices.append(labels.tracks.index(inst.track))
else:
track_indices.append(0)
# Build instance metadata
instance_metadata = []
for inst in instances:
meta = {}
if hasattr(inst, "track") and inst.track is not None:
meta["track_name"] = inst.track.name
if hasattr(inst, "score"):
meta["confidence"] = inst.score
instance_metadata.append(meta)
# Get image
if background_color is not None:
# Solid color background - skip video loading entirely
if hasattr(target_video, "shape") and target_video.shape is not None:
h, w = target_video.shape[1:3]
else:
# Estimate from points
h, w = _estimate_frame_size(instances_points)
image = _create_blank_frame(h, w, background_color)[:, :, :3]
else:
try:
image = lf.image
if image is None:
raise ValueError("No image")
except Exception:
raise ValueError(
f"Video unavailable at frame {fidx}. "
"Specify a background color to render without video."
)
# Apply cropping if specified
render_image_data = image
render_points = instances_points
if crop_bounds is not None:
render_image_data, render_points, _ = _apply_crop(
image, instances_points, crop_bounds
)
# Render frame
rendered = render_frame(
frame=render_image_data,
instances_points=render_points,
edge_inds=edge_inds,
node_names=node_names,
color_by=resolved_scheme,
palette=palette,
marker_shape=marker_shape,
marker_size=marker_size,
line_width=line_width,
alpha=alpha,
show_nodes=show_nodes,
show_edges=show_edges,
scale=scale,
track_indices=track_indices,
n_tracks=n_tracks,
pre_render_callback=pre_render_callback,
post_render_callback=post_render_callback,
per_instance_callback=per_instance_callback,
frame_idx=fidx,
instance_metadata=instance_metadata,
crop_offset=crop_offset,
)
rendered_frames.append(rendered)
# Write video or return frames
if save_path is not None:
from sleap_io.io.video_writing import VideoWriter
save_path_ = Path(save_path)
save_path_.parent.mkdir(parents=True, exist_ok=True)
with VideoWriter(
filename=save_path_,
fps=fps,
codec=codec,
crf=crf,
preset=x264_preset,
) as writer:
for frame in rendered_frames:
writer(frame)
# Return Video object pointing to output
return VideoModel.from_filename(str(save_path_))
return rendered_frames
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: |
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:
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__}")