""" 3D visualization based on plotly. Works for a small number of points and cameras, might be slow otherwise. 1) Initialize a figure with `init_figure` 2) Add 3D points, camera frustums, or both as a pycolmap.Reconstruction Written by Paul-Edouard Sarlin and Philipp Lindenberger. """ from typing import Optional import numpy as np import pycolmap import plotly.graph_objects as go def to_homogeneous(points): pad = np.ones((points.shape[:-1] + (1,)), dtype=points.dtype) return np.concatenate([points, pad], axis=-1) def init_figure(height: int = 800) -> go.Figure: """Initialize a 3D figure.""" fig = go.Figure() axes = dict( visible=False, showbackground=False, showgrid=False, showline=False, showticklabels=True, autorange=True, ) fig.update_layout( template="plotly_dark", height=height, scene_camera=dict( eye=dict(x=0.0, y=-0.1, z=-2), up=dict(x=0, y=-1.0, z=0), projection=dict(type="orthographic"), ), scene=dict( xaxis=axes, yaxis=axes, zaxis=axes, aspectmode="data", dragmode="orbit", ), margin=dict(l=0, r=0, b=0, t=0, pad=0), legend=dict( orientation="h", yanchor="top", y=0.99, xanchor="left", x=0.1 ), ) return fig def plot_points( fig: go.Figure, pts: np.ndarray, color: str = "rgba(255, 0, 0, 1)", ps: int = 2, colorscale: Optional[str] = None, name: Optional[str] = None, ): """Plot a set of 3D points.""" x, y, z = pts.T tr = go.Scatter3d( x=x, y=y, z=z, mode="markers", name=name, legendgroup=name, marker=dict( size=ps, color=color, line_width=0.0, colorscale=colorscale ), ) fig.add_trace(tr) def plot_camera( fig: go.Figure, R: np.ndarray, t: np.ndarray, K: np.ndarray, color: str = "rgb(0, 0, 255)", name: Optional[str] = None, legendgroup: Optional[str] = None, size: float = 1.0, ): """Plot a camera frustum from pose and intrinsic matrix.""" W, H = K[0, 2] * 2, K[1, 2] * 2 corners = np.array([[0, 0], [W, 0], [W, H], [0, H], [0, 0]]) if size is not None: image_extent = max(size * W / 1024.0, size * H / 1024.0) world_extent = max(W, H) / (K[0, 0] + K[1, 1]) / 0.5 scale = 0.5 * image_extent / world_extent else: scale = 1.0 corners = to_homogeneous(corners) @ np.linalg.inv(K).T corners = (corners / 2 * scale) @ R.T + t x, y, z = corners.T rect = go.Scatter3d( x=x, y=y, z=z, line=dict(color=color), legendgroup=legendgroup, name=name, marker=dict(size=0.0001), showlegend=False, ) fig.add_trace(rect) x, y, z = np.concatenate(([t], corners)).T i = [0, 0, 0, 0] j = [1, 2, 3, 4] k = [2, 3, 4, 1] pyramid = go.Mesh3d( x=x, y=y, z=z, color=color, i=i, j=j, k=k, legendgroup=legendgroup, name=name, showlegend=False, ) fig.add_trace(pyramid) triangles = np.vstack((i, j, k)).T vertices = np.concatenate(([t], corners)) tri_points = np.array([vertices[i] for i in triangles.reshape(-1)]) x, y, z = tri_points.T pyramid = go.Scatter3d( x=x, y=y, z=z, mode="lines", legendgroup=legendgroup, name=name, line=dict(color=color, width=1), showlegend=False, ) fig.add_trace(pyramid) def plot_camera_colmap( fig: go.Figure, image: pycolmap.Image, camera: pycolmap.Camera, name: Optional[str] = None, **kwargs ): """Plot a camera frustum from PyCOLMAP objects""" plot_camera( fig, image.rotmat().T, image.projection_center(), camera.calibration_matrix(), name=name or str(image.image_id), **kwargs ) def plot_cameras( fig: go.Figure, reconstruction: pycolmap.Reconstruction, **kwargs ): """Plot a camera as a cone with camera frustum.""" for image_id, image in reconstruction.images.items(): plot_camera_colmap( fig, image, reconstruction.cameras[image.camera_id], **kwargs ) def plot_reconstruction( fig: go.Figure, rec: pycolmap.Reconstruction, max_reproj_error: float = 6.0, color: str = "rgb(0, 0, 255)", name: Optional[str] = None, min_track_length: int = 2, points: bool = True, cameras: bool = True, cs: float = 1.0, ): # Filter outliers bbs = rec.compute_bounding_box(0.001, 0.999) # Filter points, use original reproj error here xyzs = [ p3D.xyz for _, p3D in rec.points3D.items() if ( (p3D.xyz >= bbs[0]).all() and (p3D.xyz <= bbs[1]).all() and p3D.error <= max_reproj_error and p3D.track.length() >= min_track_length ) ] if points: plot_points(fig, np.array(xyzs), color=color, ps=1, name=name) if cameras: plot_cameras(fig, rec, color=color, legendgroup=name, size=cs)