frame
sleap_io.transform.frame
¶
Frame transformation functions using PIL.
This module provides frame-level transformation operations for cropping, scaling, rotating, and padding video frames.
Functions:
| Name | Description |
|---|---|
crop_frame |
Crop a frame to the specified region. |
flip_h_frame |
Flip a frame horizontally (mirror left-right). |
flip_v_frame |
Flip a frame vertically (mirror top-bottom). |
pad_frame |
Add padding around a frame. |
rotate_frame |
Rotate a frame by the given angle. |
scale_frame |
Scale a frame by the given factors. |
transform_frame |
Apply a sequence of transformations to a frame. |
Attributes:
| Name | Type | Description |
|---|---|---|
QUALITY_TO_RESAMPLE |
dict() -> new empty dictionary |
|
__cached__ |
str(object='') -> str |
|
__doc__ |
str(object='') -> str |
|
__file__ |
str(object='') -> str |
|
__name__ |
str(object='') -> str |
|
__package__ |
str(object='') -> str |
QUALITY_TO_RESAMPLE = {'nearest': <Resampling.NEAREST: 0>, 'bilinear': <Resampling.BILINEAR: 2>, 'bicubic': <Resampling.BICUBIC: 3>}
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/transform/__pycache__/frame.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__ = 'Frame transformation functions using PIL.\n\nThis module provides frame-level transformation operations for cropping,\nscaling, rotating, and padding video frames.\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/transform/frame.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.transform.frame'
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.transform'
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'.
crop_frame(frame, crop, fill=0)
¶
Crop a frame to the specified region.
If the crop region extends beyond the frame bounds, the out-of-bounds area is filled with the fill value.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
frame
|
ndarray
|
Input frame as numpy array with shape (H, W) or (H, W, C). |
required |
crop
|
tuple[int, int, int, int]
|
Crop region as (x1, y1, x2, y2) pixel coordinates. |
required |
fill
|
tuple[int, ...] | int
|
Fill value for out-of-bounds regions. |
0
|
Returns:
| Type | Description |
|---|---|
ndarray
|
Cropped frame as numpy array. |
Source code in sleap_io/transform/frame.py
def crop_frame(
frame: np.ndarray,
crop: tuple[int, int, int, int],
fill: tuple[int, ...] | int = 0,
) -> np.ndarray:
"""Crop a frame to the specified region.
If the crop region extends beyond the frame bounds, the out-of-bounds area
is filled with the fill value.
Args:
frame: Input frame as numpy array with shape (H, W) or (H, W, C).
crop: Crop region as (x1, y1, x2, y2) pixel coordinates.
fill: Fill value for out-of-bounds regions.
Returns:
Cropped frame as numpy array.
"""
x1, y1, x2, y2 = crop
h, w = frame.shape[:2]
crop_w, crop_h = x2 - x1, y2 - y1
# Compute valid source region. Clamp the upper bounds to the lower bounds so a
# crop that lies wholly beyond the frame on an axis yields an empty (not
# negative-extent) source slice, which pastes cleanly into an all-fill output
# instead of raising a broadcast error.
src_x1 = max(0, x1)
src_y1 = max(0, y1)
src_x2 = max(src_x1, min(w, x2))
src_y2 = max(src_y1, min(h, y2))
# Extract source region
cropped = frame[src_y1:src_y2, src_x1:src_x2]
# Check if padding is needed
if x1 < 0 or y1 < 0 or x2 > w or y2 > h:
# Create output array with fill value
if frame.ndim == 3:
output_shape = (crop_h, crop_w, frame.shape[2])
else:
output_shape = (crop_h, crop_w)
output = np.full(output_shape, fill, dtype=frame.dtype)
# Compute paste region
paste_x1 = src_x1 - x1
paste_y1 = src_y1 - y1
paste_x2 = paste_x1 + (src_x2 - src_x1)
paste_y2 = paste_y1 + (src_y2 - src_y1)
output[paste_y1:paste_y2, paste_x1:paste_x2] = cropped
return output
return cropped
flip_h_frame(frame)
¶
Flip a frame horizontally (mirror left-right).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
frame
|
ndarray
|
Input frame as numpy array with shape (H, W) or (H, W, C). |
required |
Returns:
| Type | Description |
|---|---|
ndarray
|
Horizontally flipped frame as numpy array with same dimensions. |
Source code in sleap_io/transform/frame.py
flip_v_frame(frame)
¶
Flip a frame vertically (mirror top-bottom).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
frame
|
ndarray
|
Input frame as numpy array with shape (H, W) or (H, W, C). |
required |
Returns:
| Type | Description |
|---|---|
ndarray
|
Vertically flipped frame as numpy array with same dimensions. |
Source code in sleap_io/transform/frame.py
pad_frame(frame, padding, fill=0)
¶
Add padding around a frame.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
frame
|
ndarray
|
Input frame as numpy array with shape (H, W) or (H, W, C). |
required |
padding
|
tuple[int, int, int, int]
|
Padding as (top, right, bottom, left) in pixels. |
required |
fill
|
tuple[int, ...] | int
|
Fill value for padded regions. |
0
|
Returns:
| Type | Description |
|---|---|
ndarray
|
Padded frame as numpy array. |
Source code in sleap_io/transform/frame.py
def pad_frame(
frame: np.ndarray,
padding: tuple[int, int, int, int],
fill: tuple[int, ...] | int = 0,
) -> np.ndarray:
"""Add padding around a frame.
Args:
frame: Input frame as numpy array with shape (H, W) or (H, W, C).
padding: Padding as (top, right, bottom, left) in pixels.
fill: Fill value for padded regions.
Returns:
Padded frame as numpy array.
"""
top, right, bottom, left = padding
if top == 0 and right == 0 and bottom == 0 and left == 0:
return frame
h, w = frame.shape[:2]
new_h = h + top + bottom
new_w = w + left + right
if frame.ndim == 3:
output_shape = (new_h, new_w, frame.shape[2])
else:
output_shape = (new_h, new_w)
output = np.full(output_shape, fill, dtype=frame.dtype)
output[top : top + h, left : left + w] = frame
return output
rotate_frame(frame, angle, quality='bilinear', fill=0, expand=True)
¶
Rotate a frame by the given angle.
The frame is rotated about its center. Positive angles rotate clockwise.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
frame
|
ndarray
|
Input frame as numpy array with shape (H, W) or (H, W, C). |
required |
angle
|
float
|
Rotation angle in degrees. Positive is clockwise. |
required |
quality
|
str
|
Interpolation quality. One of "nearest", "bilinear", "bicubic". |
'bilinear'
|
fill
|
tuple[int, ...] | int
|
Fill value for areas outside the rotated image. |
0
|
expand
|
bool
|
If True (default), expand canvas to fit entire rotated image. If False, keep original dimensions (clips corners). |
True
|
Returns:
| Type | Description |
|---|---|
ndarray
|
Rotated frame as numpy array. If expand=True, dimensions may change. If expand=False, dimensions match input. |
Source code in sleap_io/transform/frame.py
def rotate_frame(
frame: np.ndarray,
angle: float,
quality: str = "bilinear",
fill: tuple[int, ...] | int = 0,
expand: bool = True,
) -> np.ndarray:
"""Rotate a frame by the given angle.
The frame is rotated about its center. Positive angles rotate clockwise.
Args:
frame: Input frame as numpy array with shape (H, W) or (H, W, C).
angle: Rotation angle in degrees. Positive is clockwise.
quality: Interpolation quality. One of "nearest", "bilinear", "bicubic".
fill: Fill value for areas outside the rotated image.
expand: If True (default), expand canvas to fit entire rotated image.
If False, keep original dimensions (clips corners).
Returns:
Rotated frame as numpy array. If expand=True, dimensions may change.
If expand=False, dimensions match input.
"""
if angle == 0:
return frame
resample = QUALITY_TO_RESAMPLE.get(quality, Image.Resampling.BILINEAR)
# Convert fill to tuple if needed
if isinstance(fill, int):
if frame.ndim == 3:
fill_color = (fill,) * frame.shape[2]
else:
fill_color = fill
else:
fill_color = fill
# Handle grayscale images with shape (H, W, 1)
pil_frame, was_squeezed = _to_pil_compatible(frame)
pil_img = Image.fromarray(pil_frame)
# PIL rotates counter-clockwise, so negate angle for clockwise
pil_img = pil_img.rotate(
-angle, resample=resample, expand=expand, fillcolor=fill_color
)
result = np.array(pil_img)
return _from_pil_result(result, was_squeezed)
scale_frame(frame, scale, quality='bilinear')
¶
Scale a frame by the given factors.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
frame
|
ndarray
|
Input frame as numpy array with shape (H, W) or (H, W, C). |
required |
scale
|
tuple[float, float]
|
Scale factors as (scale_x, scale_y). |
required |
quality
|
str
|
Interpolation quality. One of "nearest", "bilinear", "bicubic". |
'bilinear'
|
Returns:
| Type | Description |
|---|---|
ndarray
|
Scaled frame as numpy array. |
Source code in sleap_io/transform/frame.py
def scale_frame(
frame: np.ndarray,
scale: tuple[float, float],
quality: str = "bilinear",
) -> np.ndarray:
"""Scale a frame by the given factors.
Args:
frame: Input frame as numpy array with shape (H, W) or (H, W, C).
scale: Scale factors as (scale_x, scale_y).
quality: Interpolation quality. One of "nearest", "bilinear", "bicubic".
Returns:
Scaled frame as numpy array.
"""
scale_x, scale_y = scale
if scale_x == 1.0 and scale_y == 1.0:
return frame
h, w = frame.shape[:2]
new_w = int(round(w * scale_x))
new_h = int(round(h * scale_y))
if new_w <= 0 or new_h <= 0:
raise ValueError(f"Invalid output dimensions: {new_w}x{new_h}")
resample = QUALITY_TO_RESAMPLE.get(quality, Image.Resampling.BILINEAR)
# Handle grayscale images with shape (H, W, 1)
pil_frame, was_squeezed = _to_pil_compatible(frame)
pil_img = Image.fromarray(pil_frame)
pil_img = pil_img.resize((new_w, new_h), resample)
result = np.array(pil_img)
return _from_pil_result(result, was_squeezed)
transform_frame(frame, crop=None, scale=None, rotate=None, pad=None, quality='bilinear', fill=0, expand_rotation=True, flip_h=False, flip_v=False)
¶
Apply a sequence of transformations to a frame.
Transforms are applied in order: crop -> scale -> rotate -> pad -> flip.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
frame
|
ndarray
|
Input frame as numpy array with shape (H, W) or (H, W, C). |
required |
crop
|
tuple[int, int, int, int] | None
|
Crop region as (x1, y1, x2, y2) pixel coordinates. |
None
|
scale
|
tuple[float, float] | None
|
Scale factors as (scale_x, scale_y). |
None
|
rotate
|
float | None
|
Rotation angle in degrees. Positive is clockwise. |
None
|
pad
|
tuple[int, int, int, int] | None
|
Padding as (top, right, bottom, left) in pixels. |
None
|
quality
|
str
|
Interpolation quality. One of "nearest", "bilinear", "bicubic". |
'bilinear'
|
fill
|
tuple[int, ...] | int
|
Fill value for out-of-bounds and padded regions. |
0
|
expand_rotation
|
bool
|
If True (default), expand canvas to fit rotated image. If False, keep original dimensions (clips corners). |
True
|
flip_h
|
bool
|
If True, flip horizontally (mirror left-right). |
False
|
flip_v
|
bool
|
If True, flip vertically (mirror top-bottom). |
False
|
Returns:
| Type | Description |
|---|---|
ndarray
|
Transformed frame as numpy array. |
Source code in sleap_io/transform/frame.py
def transform_frame(
frame: np.ndarray,
crop: tuple[int, int, int, int] | None = None,
scale: tuple[float, float] | None = None,
rotate: float | None = None,
pad: tuple[int, int, int, int] | None = None,
quality: str = "bilinear",
fill: tuple[int, ...] | int = 0,
expand_rotation: bool = True,
flip_h: bool = False,
flip_v: bool = False,
) -> np.ndarray:
"""Apply a sequence of transformations to a frame.
Transforms are applied in order: crop -> scale -> rotate -> pad -> flip.
Args:
frame: Input frame as numpy array with shape (H, W) or (H, W, C).
crop: Crop region as (x1, y1, x2, y2) pixel coordinates.
scale: Scale factors as (scale_x, scale_y).
rotate: Rotation angle in degrees. Positive is clockwise.
pad: Padding as (top, right, bottom, left) in pixels.
quality: Interpolation quality. One of "nearest", "bilinear", "bicubic".
fill: Fill value for out-of-bounds and padded regions.
expand_rotation: If True (default), expand canvas to fit rotated image.
If False, keep original dimensions (clips corners).
flip_h: If True, flip horizontally (mirror left-right).
flip_v: If True, flip vertically (mirror top-bottom).
Returns:
Transformed frame as numpy array.
"""
result = frame
if crop is not None:
result = crop_frame(result, crop, fill=fill)
if scale is not None:
result = scale_frame(result, scale, quality=quality)
if rotate is not None and rotate != 0:
result = rotate_frame(
result, rotate, quality=quality, fill=fill, expand=expand_rotation
)
if pad is not None:
result = pad_frame(result, pad, fill=fill)
if flip_h:
result = flip_h_frame(result)
if flip_v:
result = flip_v_frame(result)
return result