def _add_pointcloud_trace( fig: go.Figure, pointclouds: Pointclouds, trace_name: str, subplot_idx: int, ncols: int, max_points_per_pointcloud: int, marker_size: int, ): """ Adds a trace rendering a Pointclouds object to the passed in figure, with a given name and in a specific subplot. Args: fig: plotly figure to add the trace within. pointclouds: Pointclouds object to render. It can be batched. trace_name: name to label the trace with. subplot_idx: identifies the subplot, with 0 being the top left. ncols: the number of sublpots per row. max_points_per_pointcloud: the number of points to render, which are randomly sampled. marker_size: the size of the rendered points """ pointclouds = pointclouds.detach().cpu() verts = pointclouds.points_packed() features = pointclouds.features_packed() indices = None if pointclouds.num_points_per_cloud().max() > max_points_per_pointcloud: start_index = 0 index_list = [] for num_points in pointclouds.num_points_per_cloud(): if num_points > max_points_per_pointcloud: indices_cloud = np.random.choice(num_points, max_points_per_pointcloud, replace=False) index_list.append(start_index + indices_cloud) else: index_list.append(start_index + np.arange(num_points)) start_index += num_points indices = np.concatenate(index_list) verts = verts[indices] color = None if features is not None: if indices is not None: # Only select features if we selected vertices above features = features[indices] if features.shape[1] == 4: # rgba template = "rgb(%d, %d, %d, %f)" rgb = (features[:, :3].clamp(0.0, 1.0) * 255).int() color = [ template % (*rgb_, a_) for rgb_, a_ in zip(rgb, features[:, 3]) ] if features.shape[1] == 3: template = "rgb(%d, %d, %d)" rgb = (features.clamp(0.0, 1.0) * 255).int() color = [template % (r, g, b) for r, g, b in rgb] row = subplot_idx // ncols + 1 col = subplot_idx % ncols + 1 fig.add_trace( go.Scatter3d( # pyre-ignore[16] x=verts[:, 0], y=verts[:, 1], z=verts[:, 2], marker={ "color": color, "size": marker_size }, mode="markers", name=trace_name, ), row=row, col=col, ) # Access the current subplot's scene configuration plot_scene = "scene" + str(subplot_idx + 1) current_layout = fig["layout"][plot_scene] # update the bounds of the axes for the current trace verts_center = verts.mean(0) max_expand = (verts.max(0)[0] - verts.min(0)[0]).max() _update_axes_bounds(verts_center, max_expand, current_layout)
def _add_pointcloud_trace( fig: go.Figure, pointclouds: Pointclouds, trace_name: str, subplot_idx: int, ncols: int, max_points_per_pointcloud: int, marker_size: int, ): # pragma: no cover """ Adds a trace rendering a Pointclouds object to the passed in figure, with a given name and in a specific subplot. Args: fig: plotly figure to add the trace within. pointclouds: Pointclouds object to render. It can be batched. trace_name: name to label the trace with. subplot_idx: identifies the subplot, with 0 being the top left. ncols: the number of subplots per row. max_points_per_pointcloud: the number of points to render, which are randomly sampled. marker_size: the size of the rendered points """ pointclouds = pointclouds.detach().cpu().subsample( max_points_per_pointcloud) verts = pointclouds.points_packed() features = pointclouds.features_packed() color = None if features is not None: if features.shape[1] == 4: # rgba template = "rgb(%d, %d, %d, %f)" rgb = (features[:, :3].clamp(0.0, 1.0) * 255).int() color = [ template % (*rgb_, a_) for rgb_, a_ in zip(rgb, features[:, 3]) ] if features.shape[1] == 3: template = "rgb(%d, %d, %d)" rgb = (features.clamp(0.0, 1.0) * 255).int() color = [template % (r, g, b) for r, g, b in rgb] row = subplot_idx // ncols + 1 col = subplot_idx % ncols + 1 fig.add_trace( go.Scatter3d( x=verts[:, 0], y=verts[:, 1], z=verts[:, 2], marker={ "color": color, "size": marker_size }, mode="markers", name=trace_name, ), row=row, col=col, ) # Access the current subplot's scene configuration plot_scene = "scene" + str(subplot_idx + 1) current_layout = fig["layout"][plot_scene] # update the bounds of the axes for the current trace verts_center = verts.mean(0) max_expand = (verts.max(0)[0] - verts.min(0)[0]).max() _update_axes_bounds(verts_center, max_expand, current_layout)
def plot_pointclouds( pointclouds: Pointclouds, *, in_subplots: bool = False, ncols: int = 1, max_points: int = 20000, **kwargs, ): """ Takes a Pointclouds object and generates a plotly figure. If there is more than one pointcloud in the batch, and in_subplots is set to be True, each pointcloud will be visualized in an individual subplot with ncols number of subplots in the same row. Otherwise, each pointcloud in the batch will be visualized as an individual trace in the same plot. If the Pointclouds object has features that are size (3) or (4) then those rgb/rgba values will be used for the plotly figure. Otherwise, plotly's default colors will be used. Assumes that all rgb/rgba feature values are in the range [0,1]. Args: pointclouds: Pointclouds object which can contain a batch of pointclouds. in_subplots: if each pointcloud should be visualized in an individual subplot. ncols: number of subplots in the same row if in_subplots is set to be True. Otherwise ncols will be ignored. max_points: maximum number of points to plot. If the cloud has more, they are randomly subsampled. **kwargs: Accepts lighting (a Lighting object) and lightposition for Scatter3D and any of the args xaxis, yaxis and zaxis which scene accepts. Accepts axis_args which is an AxisArgs object that is applied to the 3 axes. Also accepts subplot_titles, whichshould be a list of string titles matching the number of subplots. Example settings for axis_args and lighting are given at the top of this file. Returns: Plotly figure of the pointcloud(s). If there is more than one pointcloud in the batch, the plotly figure will contain a plot with one trace per pointcloud, or with each pointcloud in a separate subplot if in_subplots is True. """ pointclouds = pointclouds.detach().cpu() subplot_titles = kwargs.get("subplot_titles", None) fig = _gen_fig_with_subplots(len(pointclouds), in_subplots, ncols, subplot_titles) for i in range(len(pointclouds)): verts = pointclouds[i].points_packed() features = pointclouds[i].features_packed() indices = None if max_points is not None and verts.shape[0] > max_points: indices = np.random.choice(verts.shape[0], max_points, replace=False) verts = verts[indices] color = None if features is not None: features = features[indices] if features.shape[1] == 4: # rgba template = "rgb(%d, %d, %d, %f)" rgb = (features[:, :3] * 255).int() color = [ template % (*rgb_, a_) for rgb_, a_ in zip(rgb, features[:, 3]) ] if features.shape[1] == 3: template = "rgb(%d, %d, %d)" rgb = (features * 255).int() color = [template % (r, g, b) for r, g, b in rgb] trace_row = i // ncols + 1 if in_subplots else 1 trace_col = i % ncols + 1 if in_subplots else 1 fig.add_trace( go.Scatter3d( # pyre-ignore[16] x=verts[:, 0], y=verts[:, 1], z=verts[:, 2], marker={ "color": color, "size": 1 }, mode="markers", ), row=trace_row, col=trace_col, ) # Ensure update for every subplot. plot_scene = "scene" + str(i + 1) if in_subplots else "scene" current_layout = fig["layout"][plot_scene] verts_center = verts.mean(0) axis_args = kwargs.get("axis_args", AxisArgs()) xaxis, yaxis, zaxis = _gen_updated_axis_bounds(verts, verts_center, current_layout, axis_args) xaxis.update(**kwargs.get("xaxis", {})) yaxis.update(**kwargs.get("yaxis", {})) zaxis.update(**kwargs.get("zaxis", {})) current_layout.update({ "xaxis": xaxis, "yaxis": yaxis, "zaxis": zaxis, "aspectmode": "cube" }) return fig