from typing import Optional, Tuple import matplotlib.pyplot as plt import numpy as np from .point_cloud import PointCloud def plot_point_cloud( pc: PointCloud, color: bool = True, grid_size: int = 1, fixed_bounds: Optional[Tuple[Tuple[float, float, float], Tuple[float, float, float]]] = ( (-0.75, -0.75, -0.75), (0.75, 0.75, 0.75), ), ): """ Render a point cloud as a plot to the given image path. :param pc: the PointCloud to plot. :param image_path: the path to save the image, with a file extension. :param color: if True, show the RGB colors from the point cloud. :param grid_size: the number of random rotations to render. """ fig = plt.figure(figsize=(8, 8)) for i in range(grid_size): for j in range(grid_size): ax = fig.add_subplot(grid_size, grid_size, 1 + j + i * grid_size, projection="3d") color_args = {} if color: color_args["c"] = np.stack( [pc.channels["R"], pc.channels["G"], pc.channels["B"]], axis=-1 ) c = pc.coords if grid_size > 1: theta = np.pi * 2 * (i * grid_size + j) / (grid_size**2) rotation = np.array( [ [np.cos(theta), -np.sin(theta), 0.0], [np.sin(theta), np.cos(theta), 0.0], [0.0, 0.0, 1.0], ] ) c = c @ rotation ax.scatter(c[:, 0], c[:, 1], c[:, 2], **color_args) if fixed_bounds is None: min_point = c.min(0) max_point = c.max(0) size = (max_point - min_point).max() / 2 center = (min_point + max_point) / 2 ax.set_xlim3d(center[0] - size, center[0] + size) ax.set_ylim3d(center[1] - size, center[1] + size) ax.set_zlim3d(center[2] - size, center[2] + size) else: ax.set_xlim3d(fixed_bounds[0][0], fixed_bounds[1][0]) ax.set_ylim3d(fixed_bounds[0][1], fixed_bounds[1][1]) ax.set_zlim3d(fixed_bounds[0][2], fixed_bounds[1][2]) return fig