docstr: Compute matches according to ATTS for a single image
  Adapted from
  (https://github.com/sfzhang15/ATSS/blob/79dfb28bd1/atss_core/modeling/rpn/atss/loss.py#L180-L184)
  
  Args:
      boxes: bounding boxes, Nx4 or Nx6 torch tensor or ndarray. The box mode is assumed to be ``StandardMode``
      anchors: anchors to match Mx4 or Mx6, also assumed to be ``StandardMode``.
      num_anchors_per_level: number of anchors per feature pyramid level
      num_anchors_per_loc: number of anchors per position
  
  Returns:
      - matrix which contains the similarity from each boxes to each anchor [N, M]
      - vector which contains the matched box index for all
        anchors (if background `BELOW_LOW_THRESHOLD` is used
        and if it should be ignored `BETWEEN_THRESHOLDS` is used) [M]
  
  Note:
      ``StandardMode`` = :class:`~monai.data.box_utils.CornerCornerModeTypeA`,
      also represented as "xyxy" ([xmin, ymin, xmax, ymax]) for 2D
      and "xyzxyz" ([xmin, ymin, zmin, xmax, ymax, zmax]) for 3D.
docstr_tok: ["Compute","matches","according","to","ATTS","for","a","single","image","Adapted","from","(","https",":","\/\/github.com\/sfzhang15\/ATSS\/blob\/79dfb28bd1\/atss_core\/modeling\/rpn\/atss\/loss.py","#","L180-L184",")","Args",":","boxes",":","bounding","boxes",",","Nx4","or","Nx6","torch","tensor","or","ndarray",".","The","box","mode","is","assumed","to","be","``","StandardMode","``","anchors",":","anchors","to","match","Mx4","or","Mx6",",","also","assumed","to","be","``","StandardMode","``",".","num_anchors_per_level",":","number","of","anchors","per","feature","pyramid","level","num_anchors_per_loc",":","number","of","anchors","per","position","Returns",":","-","matrix","which","contains","the","similarity","from","each","boxes","to","each","anchor","[","N",",","M","]","-","vector","which","contains","the","matched","box","index","for","all","anchors","(","if","background","`","BELOW_LOW_THRESHOLD","`","is","used","and","if","it","should","be","ignored","`","BETWEEN_THRESHOLDS","`","is","used",")","[","M","]","Note",":","``","StandardMode","``","=",":","class",":","`","~monai.data.box_utils.CornerCornerModeTypeA","`",",","also","represented","as","``","xyxy","''","(","[","xmin",",","ymin",",","xmax",",","ymax","]",")","for","2D","and","``","xyzxyz","''","(","[","xmin",",","ymin",",","zmin",",","xmax",",","ymax",",","zmax","]",")","for","3D","."]
code: def compute_matches(
      self,
      boxes: torch.Tensor,
      anchors: torch.Tensor,
      num_anchors_per_level: Sequence[int],
      num_anchors_per_loc: int,
  ) -> tuple[torch.Tensor, torch.Tensor]:
      """
      Compute matches according to ATTS for a single image
      Adapted from
      (https://github.com/sfzhang15/ATSS/blob/79dfb28bd1/atss_core/modeling/rpn/atss/loss.py#L180-L184)
  
      Args:
          boxes: bounding boxes, Nx4 or Nx6 torch tensor or ndarray. The box mode is assumed to be ``StandardMode``
          anchors: anchors to match Mx4 or Mx6, also assumed to be ``StandardMode``.
          num_anchors_per_level: number of anchors per feature pyramid level
          num_anchors_per_loc: number of anchors per position
  
      Returns:
          - matrix which contains the similarity from each boxes to each anchor [N, M]
          - vector which contains the matched box index for all
            anchors (if background `BELOW_LOW_THRESHOLD` is used
            and if it should be ignored `BETWEEN_THRESHOLDS` is used) [M]
  
      Note:
          ``StandardMode`` = :class:`~monai.data.box_utils.CornerCornerModeTypeA`,
          also represented as "xyxy" ([xmin, ymin, xmax, ymax]) for 2D
          and "xyzxyz" ([xmin, ymin, zmin, xmax, ymax, zmax]) for 3D.
      """
      num_gt = boxes.shape[0]
      num_anchors = anchors.shape[0]
  
      distances_, _, anchors_center = boxes_center_distance(
          boxes, anchors
      )  # num_boxes x anchors
      distances = convert_to_tensor(distances_)
  
      # select candidates based on center distance
      candidate_idx_list = []
      start_idx = 0
      for _, apl in enumerate(num_anchors_per_level):
          end_idx = start_idx + apl * num_anchors_per_loc
  
          # topk: total number of candidates per position
          topk = min(self.num_candidates * num_anchors_per_loc, apl)
          # torch.topk() does not support float16 cpu, need conversion to float32 or float64
          _, idx = (
              distances[:, start_idx:end_idx]
              .to(COMPUTE_DTYPE)
              .topk(topk, dim=1, largest=False)
          )
          # idx: shape [num_boxes x topk]
          candidate_idx_list.append(idx + start_idx)
  
          start_idx = end_idx
      # [num_boxes x num_candidates] (index of candidate anchors)
      candidate_idx = torch.cat(candidate_idx_list, dim=1)
  
      match_quality_matrix = self.similarity_fn(
          boxes, anchors
      )  # [num_boxes x anchors]
      candidate_ious = match_quality_matrix.gather(
          1, candidate_idx
      )  # [num_boxes, n_candidates]
  
      # corner case, n_candidates<=1 will make iou_std_per_gt NaN
      if candidate_idx.shape[1] <= 1:
          matches = -1 * torch.ones(
              (num_anchors,), dtype=torch.long, device=boxes.device
          )
          matches[candidate_idx] = 0
          return match_quality_matrix, matches
  
      # compute adaptive iou threshold
      iou_mean_per_gt = candidate_ious.mean(dim=1)  # [num_boxes]
      iou_std_per_gt = candidate_ious.std(dim=1)  # [num_boxes]
      iou_thresh_per_gt = (
          iou_mean_per_gt + iou_std_per_gt
      )  # [num_boxes]
      is_pos = (
          candidate_ious >= iou_thresh_per_gt[:, None]
      )  # [num_boxes x n_candidates]
      if self.debug:
          print(f"Anchor matcher threshold: {iou_thresh_per_gt}")
  
      if (
          self.center_in_gt
      ):  # can discard all candidates in case of very small objects :/
          # center point of selected anchors needs to lie within the ground truth
          boxes_idx = (
              torch.arange(
                  num_gt, device=boxes.device, dtype=torch.long
              )[:, None]
              .expand_as(candidate_idx)
              .contiguous()
          )  # [num_boxes x n_candidates]
          is_in_gt_ = centers_in_boxes(
              anchors_center[candidate_idx.view(-1)],
              boxes[boxes_idx.view(-1)],
              eps=self.min_dist,
          )
          is_in_gt = convert_to_tensor(is_in_gt_)
          is_pos = is_pos & is_in_gt.view_as(
              is_pos
          )  # [num_boxes x n_candidates]
  
      # in case on anchor is assigned to multiple boxes, use box with highest IoU
      # TODO: think about a better way to do this
      for ng in range(num_gt):
          candidate_idx[ng, :] += ng * num_anchors
      ious_inf = torch.full_like(match_quality_matrix, -INF).view(-1)
      index = candidate_idx.view(-1)[is_pos.view(-1)]
      ious_inf[index] = match_quality_matrix.view(-1)[index]
      ious_inf = ious_inf.view_as(match_quality_matrix)
  
      matched_vals, matches = ious_inf.to(COMPUTE_DTYPE).max(dim=0)
      matches[matched_vals == -INF] = self.BELOW_LOW_THRESHOLD
      return match_quality_matrix, matches
  
file_path: MONAI/monai/apps/detection/utils/ATSS_matcher.py
imports: from __future__ import annotations
  import logging
  from abc import ABC, abstractmethod
  from collections.abc import Callable, Sequence
  from typing import TypeVar
  import torch
  from torch import Tensor
  from monai.data.box_utils import COMPUTE_DTYPE, box_iou, boxes_center_distance, centers_in_boxes
  from monai.utils.type_conversion import convert_to_tensor
label_desc: Use image node_id 2 for calling the ATSSMatcher obj's underlying member method code with example usage: obj.compute_matches(boxes, anchors, num_anchors_per_level, num_anchors_per_loc) and returns: match_quality_matrix,  matches, match_quality_matrix,  matches