LocoTrack / viz_utils.py
Seokju Cho
initial commit
f1586f7
"""Visualization utility functions."""
import colorsys
import random
from typing import List, Optional, Sequence, Tuple
import numpy as np
# Generate random colormaps for visualizing different points.
def get_colors(num_colors: int) -> List[Tuple[int, int, int]]:
"""Gets colormap for points."""
colors = []
for i in np.arange(0.0, 360.0, 360.0 / num_colors):
hue = i / 360.0
lightness = (50 + np.random.rand() * 10) / 100.0
saturation = (90 + np.random.rand() * 10) / 100.0
color = colorsys.hls_to_rgb(hue, lightness, saturation)
colors.append(
(int(color[0] * 255), int(color[1] * 255), int(color[2] * 255))
)
random.shuffle(colors)
return colors
def paint_point_track(
frames: np.ndarray,
point_tracks: np.ndarray,
visibles: np.ndarray,
colormap: Optional[List[Tuple[int, int, int]]] = None,
) -> np.ndarray:
"""Converts a sequence of points to color code video.
Args:
frames: [num_frames, height, width, 3], np.uint8, [0, 255]
point_tracks: [num_points, num_frames, 2], np.float32, [0, width / height]
visibles: [num_points, num_frames], bool
colormap: colormap for points, each point has a different RGB color.
Returns:
video: [num_frames, height, width, 3], np.uint8, [0, 255]
"""
num_points, num_frames = point_tracks.shape[0:2]
if colormap is None:
colormap = get_colors(num_colors=num_points)
height, width = frames.shape[1:3]
dot_size_as_fraction_of_min_edge = 0.015
radius = int(round(min(height, width) * dot_size_as_fraction_of_min_edge))
diam = radius * 2 + 1
quadratic_y = np.square(np.arange(diam)[:, np.newaxis] - radius - 1)
quadratic_x = np.square(np.arange(diam)[np.newaxis, :] - radius - 1)
icon = (quadratic_y + quadratic_x) - (radius**2) / 2.0
sharpness = 0.15
icon = np.clip(icon / (radius * 2 * sharpness), 0, 1)
icon = 1 - icon[:, :, np.newaxis]
icon1 = np.pad(icon, [(0, 1), (0, 1), (0, 0)])
icon2 = np.pad(icon, [(1, 0), (0, 1), (0, 0)])
icon3 = np.pad(icon, [(0, 1), (1, 0), (0, 0)])
icon4 = np.pad(icon, [(1, 0), (1, 0), (0, 0)])
video = frames.copy()
for t in range(num_frames):
# Pad so that points that extend outside the image frame don't crash us
image = np.pad(
video[t],
[
(radius + 1, radius + 1),
(radius + 1, radius + 1),
(0, 0),
],
)
for i in range(num_points):
# The icon is centered at the center of a pixel, but the input coordinates
# are raster coordinates. Therefore, to render a point at (1,1) (which
# lies on the corner between four pixels), we need 1/4 of the icon placed
# centered on the 0'th row, 0'th column, etc. We need to subtract
# 0.5 to make the fractional position come out right.
x, y = point_tracks[i, t, :] + 0.5
x = min(max(x, 0.0), width)
y = min(max(y, 0.0), height)
if visibles[i, t]:
x1, y1 = np.floor(x).astype(np.int32), np.floor(y).astype(np.int32)
x2, y2 = x1 + 1, y1 + 1
# bilinear interpolation
patch = (
icon1 * (x2 - x) * (y2 - y)
+ icon2 * (x2 - x) * (y - y1)
+ icon3 * (x - x1) * (y2 - y)
+ icon4 * (x - x1) * (y - y1)
)
x_ub = x1 + 2 * radius + 2
y_ub = y1 + 2 * radius + 2
image[y1:y_ub, x1:x_ub, :] = (1 - patch) * image[
y1:y_ub, x1:x_ub, :
] + patch * np.array(colormap[i])[np.newaxis, np.newaxis, :]
# Remove the pad
video[t] = image[
radius + 1 : -radius - 1, radius + 1 : -radius - 1
].astype(np.uint8)
return video