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