code: def _add_ray_bundle_trace(
      fig: go.Figure,
      ray_bundle: Union[RayBundle, HeterogeneousRayBundle],
      trace_name: str,
      subplot_idx: int,
      ncols: int,
      max_rays: int,
      max_points_per_ray: int,
      marker_size: int,
      line_width: int,
  ) -> None:  # pragma: no cover
      """
      Adds a trace rendering a ray bundle object
      to the passed in figure, with a given name and in a specific subplot.
  
      Args:
          fig: plotly figure to add the trace within.
          ray_bundle: the RayBundle, ImplicitronRayBundle or HeterogeneousRaybundle 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_rays: maximum number of plotted rays in total. Randomly subsamples
              without replacement in case the number of rays is bigger than max_rays.
          max_points_per_ray: maximum number of points plotted per ray.
          marker_size: the size of the ray point markers.
          line_width: the width of the ray lines.
      """
  
      n_pts_per_ray = ray_bundle.lengths.shape[-1]
      n_rays = ray_bundle.lengths.shape[:-1].numel()
  
      # flatten all batches of rays into a single big bundle
      ray_bundle_flat = RayBundle(
          **{
              attr: torch.flatten(
                  getattr(ray_bundle, attr), start_dim=0, end_dim=-2
              )
              for attr in ["origins", "directions", "lengths", "xys"]
          }
      )
  
      # subsample the rays (if needed)
      if n_rays > max_rays:
          indices_rays = torch.randperm(n_rays)[:max_rays]
          ray_bundle_flat = RayBundle(
              **{
                  attr: getattr(ray_bundle_flat, attr)[indices_rays]
                  for attr in [
                      "origins",
                      "directions",
                      "lengths",
                      "xys",
                  ]
              }
          )
  
      # make ray line endpoints
      min_max_ray_depth = torch.stack(
          [
              ray_bundle_flat.lengths.min(dim=1).values,
              ray_bundle_flat.lengths.max(dim=1).values,
          ],
          dim=-1,
      )
      ray_lines_endpoints = ray_bundle_to_ray_points(
          ray_bundle_flat._replace(lengths=min_max_ray_depth)
      )
  
      # make the ray lines for plotly plotting
      nan_tensor = torch.tensor(
          [[float("NaN")] * 3],
          device=ray_lines_endpoints.device,
          dtype=ray_lines_endpoints.dtype,
      )
      ray_lines = torch.empty(
          size=(1, 3), device=ray_lines_endpoints.device
      )
      for ray_line in ray_lines_endpoints:
          # We combine the ray lines into a single tensor to plot them in a
          # single trace. The NaNs are inserted between sets of ray lines
          # so that the lines drawn by Plotly are not drawn between
          # lines that belong to different rays.
          ray_lines = torch.cat((ray_lines, nan_tensor, ray_line))
      x, y, z = ray_lines.detach().cpu().numpy().T.astype(float)
      row, col = subplot_idx // ncols + 1, subplot_idx % ncols + 1
      fig.add_trace(
          go.Scatter3d(
              x=x,
              y=y,
              z=z,
              marker={"size": 0.1},
              line={"width": line_width},
              name=trace_name,
          ),
          row=row,
          col=col,
      )
  
      # subsample the ray points (if needed)
      if n_pts_per_ray > max_points_per_ray:
          indices_ray_pts = torch.cat(
              [
                  torch.randperm(n_pts_per_ray)[:max_points_per_ray]
                  + ri * n_pts_per_ray
                  for ri in range(ray_bundle_flat.lengths.shape[0])
              ]
          )
          ray_bundle_flat = ray_bundle_flat._replace(
              lengths=ray_bundle_flat.lengths.reshape(-1)[
                  indices_ray_pts
              ].reshape(ray_bundle_flat.lengths.shape[0], -1)
          )
  
      # plot the ray points
      ray_points = (
          ray_bundle_to_ray_points(ray_bundle_flat)
          .view(-1, 3)
          .detach()
          .cpu()
          .numpy()
          .astype(float)
      )
      fig.add_trace(
          go.Scatter3d(
              x=ray_points[:, 0],
              y=ray_points[:, 1],
              z=ray_points[:, 2],
              mode="markers",
              name=trace_name + "_points",
              marker={"size": marker_size},
          ),
          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
      all_ray_points = ray_bundle_to_ray_points(ray_bundle).reshape(
          -1, 3
      )
      ray_points_center = all_ray_points.mean(dim=0)
      max_expand = (
          (all_ray_points.max(0)[0] - all_ray_points.min(0)[0])
          .max()
          .item()
      )
      _update_axes_bounds(
          ray_points_center, float(max_expand), current_layout
      )
  
imports: import warnings
  from typing import Dict, List, NamedTuple, Optional, Tuple, Union
  import plotly.graph_objects
  import torch
  from plotly.subplots import make_subplots
  from pytorch3d.renderer import HeterogeneousRayBundle, ray_bundle_to_ray_points, RayBundle, TexturesAtlas, TexturesVertex
  from pytorch3d.renderer.camera_utils import camera_to_eye_at_up
  from pytorch3d.renderer.cameras import CamerasBase
  from pytorch3d.structures import join_meshes_as_scene, Meshes, Pointclouds
label_desc: Use image node_id 11 for calling a global function with example usage: _add_ray_bundle_trace(fig, ray_bundle, trace_name, subplot_idx, ncols, max_rays, max_points_per_ray, marker_size, line_width) without return types