code: def compute_vote_loss(input, output: VoteNetResults):
      """Compute vote loss: Match predicted votes to GT votes.
  
      Args:
          end_points: dict (read-only)
  
      Returns:
          vote_loss: scalar Tensor
  
      Overall idea:
          If the seed point belongs to an object (votes_label_mask == 1),
          then we require it to vote for the object center.
  
          Each seed point may vote for multiple translations v1,v2,v3
          A seed point may also be in the boxes of multiple objects:
          o1,o2,o3 with corresponding GT votes c1,c2,c3
  
          Then the loss for this seed point is:
              min(d(v_i,c_j)) for i=1,2,3 and j=1,2,3
      """
  
      # Load ground truth votes and assign them to seed points
      batch_size = output["seed_pos"].shape[0]
      num_seed = output["seed_pos"].shape[1]  # B,num_seed,3
      vote_xyz = output["seed_votes"]  # B,num_seed*vote_factor,3
      seed_inds = output[
          "seed_inds"
      ].long()  # B,num_seed in [0,num_points-1]
  
      # Get groundtruth votes for the seed points
      # vote_label_mask: Use gather to select B,num_seed from B,num_point
      #   non-object point has no GT vote mask = 0, object point has mask = 1
      # vote_label: Use gather to select B,num_seed,9 from B,num_point,9
      #   with inds in shape B,num_seed,9 and 9 = GT_VOTE_FACTOR * 3
      if seed_inds.dim() == 1:
          seed_gt_votes_mask = torch.gather(
              input["vote_label_mask"], 0, seed_inds
          ).view((batch_size, -1))
          seed_gt_votes = torch.gather(
              input["vote_label"],
              0,
              seed_inds.unsqueeze(-1).repeat(1, 3 * GT_VOTE_FACTOR),
          )
          seed_gt_votes += (
              output["seed_pos"].view((-1, 3)).repeat((1, 3))
          )
      else:
          seed_gt_votes_mask = torch.gather(
              input["vote_label_mask"], 1, seed_inds
          )
          seed_inds_expand = seed_inds.view(
              batch_size, num_seed, 1
          ).repeat(1, 1, 3 * GT_VOTE_FACTOR)
          seed_gt_votes = torch.gather(
              input["vote_label"], 1, seed_inds_expand
          )
          seed_gt_votes += output["seed_pos"].repeat(1, 1, 3)
  
      # Compute the min of min of distance
      vote_xyz_reshape = vote_xyz.view(
          batch_size * num_seed, -1, 3
      )  # from B,num_seed*vote_factor,3 to B*num_seed,vote_factor,3
      seed_gt_votes_reshape = seed_gt_votes.view(
          batch_size * num_seed, GT_VOTE_FACTOR, 3
      )  # from B,num_seed,3*GT_VOTE_FACTOR to B*num_seed,GT_VOTE_FACTOR,3
      # A predicted vote to no where is not penalized as long as there is a good vote near the GT vote.
      dist1, _, dist2, _ = nn_distance(
          vote_xyz_reshape, seed_gt_votes_reshape, l1=True
      )
      votes_dist, _ = torch.min(
          dist2, dim=1
      )  # (B*num_seed,vote_factor) to (B*num_seed,)
      votes_dist = votes_dist.view(batch_size, num_seed)
      vote_loss = torch.sum(votes_dist * seed_gt_votes_mask.float()) / (
          torch.sum(seed_gt_votes_mask.float()) + 1e-6
      )
      return vote_loss
  
file_path: torch-points3d/torch_points3d/modules/VoteNet/loss_helper.py
imports: import torch
  import torch.nn
  import numpy
  from torch_points3d.core.losses import huber_loss, nn_distance
  from .votenet_results import VoteNetResults
label_desc: Use image node_id 1 for calling a global function with example usage: compute_vote_loss(input, output) and returns: vote_loss