| 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 | |