207 lines
5.4 KiB
Python
207 lines
5.4 KiB
Python
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
# All rights reserved.
|
|
#
|
|
# This source code is licensed under the license found in the
|
|
# LICENSE file in the root directory of this source tree.
|
|
|
|
"""
|
|
Visualization utility functions for colorization and color bars.
|
|
"""
|
|
|
|
import dataclasses
|
|
from typing import Optional, Tuple
|
|
|
|
import numpy as np
|
|
import torch
|
|
import cv2
|
|
import matplotlib.cm as cm
|
|
|
|
|
|
@dataclasses.dataclass
|
|
class CameraState:
|
|
"""Camera state for rendering."""
|
|
fov: float
|
|
aspect: float
|
|
c2w: np.ndarray
|
|
|
|
def get_K(self, img_wh: Tuple[int, int]) -> np.ndarray:
|
|
"""Get camera intrinsic matrix from FOV and image size."""
|
|
W, H = img_wh
|
|
focal_length = H / 2.0 / np.tan(self.fov / 2.0)
|
|
K = np.array([
|
|
[focal_length, 0.0, W / 2.0],
|
|
[0.0, focal_length, H / 2.0],
|
|
[0.0, 0.0, 1.0],
|
|
])
|
|
return K
|
|
|
|
|
|
def get_vertical_colorbar(
|
|
h: int,
|
|
vmin: float,
|
|
vmax: float,
|
|
cmap_name: str = "jet",
|
|
label: Optional[str] = None,
|
|
cbar_precision: int = 2
|
|
) -> np.ndarray:
|
|
"""
|
|
Create a vertical colorbar image.
|
|
|
|
Args:
|
|
h: Height in pixels
|
|
vmin: Minimum value
|
|
vmax: Maximum value
|
|
cmap_name: Colormap name
|
|
label: Optional label for the colorbar
|
|
cbar_precision: Decimal precision for tick labels
|
|
|
|
Returns:
|
|
Colorbar image as numpy array (H, W, 3)
|
|
"""
|
|
from matplotlib.figure import Figure
|
|
from matplotlib.backends.backend_agg import FigureCanvasAgg
|
|
import matplotlib as mpl
|
|
|
|
fig = Figure(figsize=(2, 8), dpi=100)
|
|
fig.subplots_adjust(right=1.5)
|
|
canvas = FigureCanvasAgg(fig)
|
|
|
|
ax = fig.add_subplot(111)
|
|
cmap = cm.get_cmap(cmap_name)
|
|
norm = mpl.colors.Normalize(vmin=vmin, vmax=vmax)
|
|
|
|
tick_cnt = 6
|
|
tick_loc = np.linspace(vmin, vmax, tick_cnt)
|
|
cb1 = mpl.colorbar.ColorbarBase(
|
|
ax, cmap=cmap, norm=norm, ticks=tick_loc, orientation="vertical"
|
|
)
|
|
|
|
tick_label = [str(np.round(x, cbar_precision)) for x in tick_loc]
|
|
if cbar_precision == 0:
|
|
tick_label = [x[:-2] for x in tick_label]
|
|
|
|
cb1.set_ticklabels(tick_label)
|
|
cb1.ax.tick_params(labelsize=18, rotation=0)
|
|
if label is not None:
|
|
cb1.set_label(label)
|
|
|
|
canvas.draw()
|
|
s, (width, height) = canvas.print_to_buffer()
|
|
|
|
im = np.frombuffer(s, np.uint8).reshape((height, width, 4))
|
|
im = im[:, :, :3].astype(np.float32) / 255.0
|
|
|
|
if h != im.shape[0]:
|
|
w = int(im.shape[1] / im.shape[0] * h)
|
|
im = cv2.resize(im, (w, h), interpolation=cv2.INTER_AREA)
|
|
|
|
return im
|
|
|
|
|
|
def colorize_np(
|
|
x: np.ndarray,
|
|
cmap_name: str = "jet",
|
|
mask: Optional[np.ndarray] = None,
|
|
range: Optional[Tuple[float, float]] = None,
|
|
append_cbar: bool = False,
|
|
cbar_in_image: bool = False,
|
|
cbar_precision: int = 2,
|
|
) -> np.ndarray:
|
|
"""
|
|
Turn a grayscale image into a color image.
|
|
|
|
Args:
|
|
x: Input grayscale image [H, W]
|
|
cmap_name: Colormap name
|
|
mask: Optional mask image [H, W]
|
|
range: Value range for scaling [min, max], automatic if None
|
|
append_cbar: Whether to append colorbar
|
|
cbar_in_image: Put colorbar inside image
|
|
cbar_precision: Colorbar tick precision
|
|
|
|
Returns:
|
|
Colorized image [H, W, 3]
|
|
"""
|
|
if range is not None:
|
|
vmin, vmax = range
|
|
elif mask is not None:
|
|
vmin = np.min(x[mask][np.nonzero(x[mask])])
|
|
vmax = np.max(x[mask])
|
|
x[np.logical_not(mask)] = vmin
|
|
else:
|
|
vmin, vmax = np.percentile(x, (1, 100))
|
|
vmax += 1e-6
|
|
|
|
x = np.clip(x, vmin, vmax)
|
|
x = (x - vmin) / (vmax - vmin)
|
|
|
|
cmap = cm.get_cmap(cmap_name)
|
|
x_new = cmap(x)[:, :, :3]
|
|
|
|
if mask is not None:
|
|
mask = np.float32(mask[:, :, np.newaxis])
|
|
x_new = x_new * mask + np.ones_like(x_new) * (1.0 - mask)
|
|
|
|
cbar = get_vertical_colorbar(
|
|
h=x.shape[0],
|
|
vmin=vmin,
|
|
vmax=vmax,
|
|
cmap_name=cmap_name,
|
|
cbar_precision=cbar_precision,
|
|
)
|
|
|
|
if append_cbar:
|
|
if cbar_in_image:
|
|
x_new[:, -cbar.shape[1]:, :] = cbar
|
|
else:
|
|
x_new = np.concatenate(
|
|
(x_new, np.zeros_like(x_new[:, :5, :]), cbar), axis=1
|
|
)
|
|
return x_new
|
|
else:
|
|
return x_new
|
|
|
|
|
|
def colorize(
|
|
x: torch.Tensor,
|
|
cmap_name: str = "jet",
|
|
mask: Optional[torch.Tensor] = None,
|
|
range: Optional[Tuple[float, float]] = None,
|
|
append_cbar: bool = False,
|
|
cbar_in_image: bool = False
|
|
) -> torch.Tensor:
|
|
"""
|
|
Turn a grayscale image into a color image (PyTorch tensor version).
|
|
|
|
Args:
|
|
x: Grayscale image tensor [H, W] or [B, H, W]
|
|
cmap_name: Colormap name
|
|
mask: Optional mask tensor [H, W] or [B, H, W]
|
|
range: Value range for scaling
|
|
append_cbar: Whether to append colorbar
|
|
cbar_in_image: Put colorbar inside image
|
|
|
|
Returns:
|
|
Colorized tensor
|
|
"""
|
|
device = x.device
|
|
x = x.cpu().numpy()
|
|
if mask is not None:
|
|
mask = mask.cpu().numpy() > 0.99
|
|
kernel = np.ones((3, 3), np.uint8)
|
|
|
|
if x.ndim == 2:
|
|
x = x[None]
|
|
if mask is not None:
|
|
mask = mask[None]
|
|
|
|
out = []
|
|
for x_ in x:
|
|
if mask is not None:
|
|
mask = cv2.erode(mask.astype(np.uint8), kernel, iterations=1).astype(bool)
|
|
|
|
x_ = colorize_np(x_, cmap_name, mask, range, append_cbar, cbar_in_image)
|
|
out.append(torch.from_numpy(x_).to(device).float())
|
|
out = torch.stack(out).squeeze(0)
|
|
return out
|