class_docstr: Box selector which selects the predicted boxes.
  The box selection is performed with the following steps:
  
  #. For each level, discard boxes with scores less than self.score_thresh.
  #. For each level, keep boxes with top self.topk_candidates_per_level scores.
  #. For the whole image, perform non-maximum suppression (NMS) on boxes, with overlapping threshold nms_thresh.
  #. For the whole image, keep boxes with top self.detections_per_img scores.
  
  Args:
      apply_sigmoid: whether to apply sigmoid to get scores from classification logits
      score_thresh: no box with scores less than score_thresh will be kept
      topk_candidates_per_level: max number of boxes to keep for each level
      nms_thresh: box overlapping threshold for NMS
      detections_per_img: max number of boxes to keep for each image
  
  Example:
  
      .. code-block:: python
  
          input_param = {
              "apply_sigmoid": True,
              "score_thresh": 0.1,
              "topk_candidates_per_level": 2,
              "nms_thresh": 0.1,
              "detections_per_img": 5,
          }
          box_selector = BoxSelector(**input_param)
          boxes = [torch.randn([3,6]), torch.randn([7,6])]
          logits = [torch.randn([3,3]), torch.randn([7,3])]
          spatial_size = (8,8,8)
          selected_boxes, selected_scores, selected_labels = box_selector.select_boxes_per_image(
              boxes, logits, spatial_size
          )
docstr: Postprocessing to generate detection result from classification logits and boxes.
  
  The box selection is performed with the following steps:
  
  #. For each level, discard boxes with scores less than self.score_thresh.
  #. For each level, keep boxes with top self.topk_candidates_per_level scores.
  #. For the whole image, perform non-maximum suppression (NMS) on boxes, with overlapping threshold nms_thresh.
  #. For the whole image, keep boxes with top self.detections_per_img scores.
  
  Args:
      boxes_list: list of predicted boxes from a single image,
          each element i is a Tensor sized (N_i, 2*spatial_dims)
      logits_list: list of predicted classification logits from a single image,
          each element i is a Tensor sized (N_i, num_classes)
      spatial_size: spatial size of the image
  
  Return:
      - selected boxes, Tensor sized (P, 2*spatial_dims)
      - selected_scores, Tensor sized (P, )
      - selected_labels, Tensor sized (P, )
docstr_tok: ["Postprocessing","to","generate","detection","result","from","classification","logits","and","boxes",".","The","box","selection","is","performed","with","the","following","steps",":","#",".","For","each","level",",","discard","boxes","with","scores","less","than","self.score_thresh",".","#",".","For","each","level",",","keep","boxes","with","top","self.topk_candidates_per_level","scores",".","#",".","For","the","whole","image",",","perform","non-maximum","suppression","(","NMS",")","on","boxes",",","with","overlapping","threshold","nms_thresh",".","#",".","For","the","whole","image",",","keep","boxes","with","top","self.detections_per_img","scores",".","Args",":","boxes_list",":","list","of","predicted","boxes","from","a","single","image",",","each","element","i","is","a","Tensor","sized","(","N_i",",","2","*","spatial_dims",")","logits_list",":","list","of","predicted","classification","logits","from","a","single","image",",","each","element","i","is","a","Tensor","sized","(","N_i",",","num_classes",")","spatial_size",":","spatial","size","of","the","image","Return",":","-","selected","boxes",",","Tensor","sized","(","P",",","2","*","spatial_dims",")","-","selected_scores",",","Tensor","sized","(","P",",",")","-","selected_labels",",","Tensor","sized","(","P",",",")"]
code: def select_boxes_per_image(
      self,
      boxes_list: list[Tensor],
      logits_list: list[Tensor],
      spatial_size: list[int] | tuple[int],
  ) -> tuple[Tensor, Tensor, Tensor]:
      """
      Postprocessing to generate detection result from classification logits and boxes.
  
      The box selection is performed with the following steps:
  
      #. For each level, discard boxes with scores less than self.score_thresh.
      #. For each level, keep boxes with top self.topk_candidates_per_level scores.
      #. For the whole image, perform non-maximum suppression (NMS) on boxes, with overlapping threshold nms_thresh.
      #. For the whole image, keep boxes with top self.detections_per_img scores.
  
      Args:
          boxes_list: list of predicted boxes from a single image,
              each element i is a Tensor sized (N_i, 2*spatial_dims)
          logits_list: list of predicted classification logits from a single image,
              each element i is a Tensor sized (N_i, num_classes)
          spatial_size: spatial size of the image
  
      Return:
          - selected boxes, Tensor sized (P, 2*spatial_dims)
          - selected_scores, Tensor sized (P, )
          - selected_labels, Tensor sized (P, )
      """
  
      if len(boxes_list) != len(logits_list):
          raise ValueError(
              "len(boxes_list) should equal to len(logits_list). "
              f"Got len(boxes_list)={len(boxes_list)}, len(logits_list)={len(logits_list)}"
          )
  
      image_boxes = []
      image_scores = []
      image_labels = []
  
      boxes_dtype = boxes_list[0].dtype
      logits_dtype = logits_list[0].dtype
  
      for boxes_per_level, logits_per_level in zip(
          boxes_list, logits_list
      ):
          # select topk boxes for each level
          topk_idxs: Tensor
          (
              topk_idxs,
              scores_per_level,
              labels_per_level,
          ) = self.select_top_score_idx_per_level(logits_per_level)
          boxes_per_level = boxes_per_level[topk_idxs]
  
          keep: Tensor
          boxes_per_level, keep = clip_boxes_to_image(  # type: ignore
              boxes_per_level, spatial_size, remove_empty=True
          )
          image_boxes.append(boxes_per_level)
          image_scores.append(scores_per_level[keep])
          image_labels.append(labels_per_level[keep])
  
      image_boxes_t: Tensor = torch.cat(image_boxes, dim=0)
      image_scores_t: Tensor = torch.cat(image_scores, dim=0)
      image_labels_t: Tensor = torch.cat(image_labels, dim=0)
  
      # non-maximum suppression on detected boxes from all levels
      keep_t: Tensor = batched_nms(  # type: ignore
          image_boxes_t,
          image_scores_t,
          image_labels_t,
          self.nms_thresh,
          box_overlap_metric=self.box_overlap_metric,
          max_proposals=self.detections_per_img,
      )
  
      selected_boxes = image_boxes_t[keep_t].to(boxes_dtype)
      selected_scores = image_scores_t[keep_t].to(logits_dtype)
      selected_labels = image_labels_t[keep_t]
  
      return selected_boxes, selected_scores, selected_labels
  
file_path: MONAI/monai/apps/detection/utils/box_selector.py
imports: from __future__ import annotations
  from collections.abc import Callable
  import torch
  from torch import Tensor
  from monai.data.box_utils import batched_nms, box_iou, clip_boxes_to_image
  from monai.transforms.utils_pytorch_numpy_unification import floor_divide
label_desc: Use image node_id 3 for calling the BoxSelector obj's underlying member method code with example usage: obj.select_boxes_per_image(boxes_list, logits_list, spatial_size) and returns: selected_boxes,  selected_scores,  selected_labels