Spaces:
Running
Running
| # 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 | |
| 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 | |