text: """
  测试检测器的精度
  """
  import torch
  import json
  import time
  import os, cv2
  import tqdm
  
  import numpy as np
  from torchvision.transforms import transforms as cvtransforms
  from torch.utils.data.dataloader import DataLoader
  from lib.models.model_factory import create_model, load_model
  from lib.datasets.jde import OriginDetDataset
  from lib.transform.train_transform import collate_fn
  
  from lib.models.utils.decode import mot_decode
  from lib.utils.post_process import ctdet_post_process, simple_ctdet_post_process
  
  from evaluate.utils import get_annotations_cache, cache_annotations
  
  from config.exp import config_factory
  
  def post_process(opt, dets_in_one_image, meta, simple_post_map = False):
      """
      对单幅图像中的所有检测信息框进行后处理
      Args:
          opt: 配置信息
          dets_in_one_image: 单幅图像中经过解码和最大值抑制之后的所有原始检测框信息 1 * k * (bboxes + scores + clses = 6)
          meta: 图像描述信息 meta['c']为原始尺寸均除以2 meta['s']为输入尺寸中最大的那个边
          simple_post_map: 是否进行简单后处理
          其中scores已经降序排列
      Returns:
          dets_mapped_results: 针对某张图像返回他对应的各个类别的一个字典
      """
      dets_in_one_image = dets_in_one_image.detach().cpu().numpy()
      dets_in_one_image = dets_in_one_image.reshape(1, -1, dets_in_one_image.shape[2])   # 1 * k * 6
  
      # 检测框处理流程 将输出得到的bbox给映射回原来的图像中的大小尺寸
      if not simple_post_map:
          dets_mapped_results = ctdet_post_process(
              dets_in_one_image.copy(), 
              [meta['c']],
              [meta['s']],
              meta['out_height'], 
              meta['out_width'], 
              opt.num_classes
          )
      else:
          dets_mapped_results = simple_ctdet_post_process(
              dets_in_one_image.copy(),
              meta['output_shape'],
              meta['origin_shape'],
              opt.num_classes
          )
  
      # 将这一副图像中的各个类别作为numpy中的形式返回
      for j in range(opt.num_classes):
          dets_mapped_results[0][j] = np.array(dets_mapped_results[0][j], dtype=np.float32).reshape(-1, 5)
      return dets_mapped_results[0]
  
  def merge_outputs_into_dict(opt, detections):
      """
      合并多个输出结果
      Args:
          detections: 单幅图像对应的字典所构成的列表 [det_dict]
      Returns:
          results: 将所有结果按照类别归类之后的结果
      """
      results = {}
      for j in range(opt.num_classes):
          # 将各个图像中的输出结果整合到一个字典中
          results[j] = np.concatenate([detection[j] for detection in detections], axis=0).astype(np.float32)
      
      # 所有类别的得分汇总起来 得到一个1维的得分 是按照类别的顺序将所有的图像中对应的对象的得分给堆叠成1维
      # scores = np.hstack([results[j][:, 4] for j in range(opt.num_classes)])
  
      # 如果所有的对象的数目超过了128 则需要做一下过滤
      # not necessary
      # if len(scores) > 128:
      #     # 在所有的对象中挑选前k个最大的值 如果>=2*128 ???
      #     kth = len(scores) - 128
      #     thresh = np.partition(scores, kth)[kth]
      #     # 每个类别中只有超过了得分阈值的才可以作为最后结果
      #     for j in range(opt.num_classes):
      #         keep_inds = (results[j][:, 4] >= thresh)
      #         results[j] = results[j][keep_inds]
      # 按照类别将结果字典输出 key为类别id
  
      return results
  
  def test_det(
          opt,
          batch_size,
          img_size,
          iou_thres,
          print_interval=40,
  ):
      # ===================================
      # 数据加载
      # ===================================
      data_cfg = opt.data_cfg
      f = open(data_cfg)
      data_cfg_dict = json.load(f)
      f.close()
      nC = opt.num_classes
      test_path = data_cfg_dict['test']
      dataset_root = data_cfg_dict['root']
  
      # Get dataloader
      transforms = cvtransforms.Compose([
          cvtransforms.ToTensor(),
          cvtransforms.Normalize(opt.mean, opt.std)
          ])
      dataset = OriginDetDataset(dataset_root, test_path, augment=False, transforms=transforms)
  
      # Anno Cache
      anno_cache_file = "det_annos_cache.pkl"
      cls_ref_annos = get_annotations_cache(anno_cache_file)
      if cls_ref_annos is None:
          annosloader = DataLoader(dataset, 1, shuffle=False, num_workers=1, drop_last=False)
          cache_annotations(annosloader, 2, anno_cache_file)
          cls_ref_annos = get_annotations_cache(anno_cache_file)
      print("=" * 60)
      print("Annos Summary:")
  
      # =================================================================
      # TODO only for evaluating ball or players. ignore it.
      # =================================================================
      cls_ref_annos[0] = cls_ref_annos[1]
      del cls_ref_annos[1]
  
      for cls_id in range(nC):
          print("Class Samples:", cls_id, len(cls_ref_annos[cls_id]), "Total Objs:", cls_ref_annos[cls_id]["gt_total"])
      print("=" * 60)
  
      # ===================================
      # 验证数据集得到结果
      # ===================================
      # ===================================
      # 环境设置
      # ===================================
      os.environ['CUDA_VISIBLE_DEVICES'] = "0"
      if opt.gpus[0] >= 0:
          opt.device = torch.device('cuda')
      else:
          opt.device = torch.device('cpu')
      # opt.device = torch.device('cpu')
      # ===================================
      # 创建和加载模型
      # ===================================
      print('Creating model...')
      model = create_model("fairmot", opt.arch, opt.heads, opt.head_conv)
      model = load_model(model, opt.load_model)
      model = model.to(opt.device)
      model.eval()
  
      dataloader = DataLoader(dataset, batch_size=1, shuffle=False, num_workers=1, drop_last=False, collate_fn=collate_fn)
  
      # ==================
      # tiling mechanism
      # ==================
      tiling_size = 256
      step = 128
  
      for i, batch in enumerate(tqdm.tqdm(dataloader, desc="Detect")):
  
          (imgs, _, paths, shapes, _) = batch
          # 1 * 3 * origin_h * origin_w
          imgs = imgs.to(opt.device)
          origin_shape = shapes[0]
          width = origin_shape[1]
          height = origin_shape[0]
          
          # TODO special adjust for img smaller than 256 * 256
          width_corps = (width - tiling_size) // step + 1
          height_corps = (height - tiling_size) // step + 1
  
          # record
          best_result = {
              "crop_y": 0,
              "crop_x": 0,
              "hm_score": 0,
              "detections": None,
          }
  
          # sliding detection
          for col in range(height_corps):
              for row in range(width_corps):
                  crop_y = col * step
                  crop_x = row * step
                  if crop_y + tiling_size > height:
                      crop_y = height - tiling_size
                  if crop_x + tiling_size > width:
                      crop_x = width - tiling_size
                  patch = imgs[:, :, crop_y:crop_y + tiling_size, crop_x:crop_x + tiling_size]
  
                  # output
  
                  output = model({'input':patch})[-1]
  
                  # select
                  hm = output['hm'][:, 0:1].sigmoid_()
                  if hm.max() > best_result["hm_score"]:
                      best_result['hm_score'] = hm.max()
                  else:
                      continue
  
                  # detection
                  wh = output['wh']
                  reg = output['reg'] if opt.reg_offset else None
  
                  opt.K = 1
                  # !!! 从输出到得到detections的解码过程
                  detections, inds = mot_decode(hm, wh, center_offset_reg=reg, ltrb=opt.ltrb, K=opt.K)
  
                  # record
                  best_result["crop_y"] = crop_y
                  best_result["crop_x"] = crop_x
                  best_result["detections"] = detections
  
          # calc
          detection = best_result["detections"].squeeze(0).squeeze(0).cpu().numpy()
          detection *= opt.down_ratio
  
          dets_in_one_image_dict = {}
          dets_in_one_image_dict[0] = np.asarray([detection[:-1]])
          image_path = paths[0]
          
          # patch = origin_img[best_result["crop_y"]:best_result["crop_y"] + tiling_size, best_result["crop_x"]:best_result["crop_x"] + tiling_size]
          dets_in_one_image_dict[0][:, 0] += best_result["crop_x"]
          dets_in_one_image_dict[0][:, 1] += best_result["crop_y"]
          dets_in_one_image_dict[0][:, 2] += best_result["crop_x"]
          dets_in_one_image_dict[0][:, 3] += best_result["crop_y"]
  
          # display
          # pred and gt
          origin_img = cv2.imread(image_path)
          labels = np.loadtxt(image_path.replace("images", "labels_with_ids").replace(".jpg", ".txt"))
          gt_labels = labels[labels[:, 0] == 1]
          if gt_labels.shape[0] != 0:
              gt_label = gt_labels[0][2:6]
              bbox = gt_label.copy()
              bbox[0] = int((gt_label[0] - gt_label[2] / 2) * origin_shape[1])
              bbox[1] = int((gt_label[1] - gt_label[3] / 2) * origin_shape[0])
              bbox[2] = int((gt_label[0] + gt_label[2] / 2) * origin_shape[1])
              bbox[3] = int((gt_label[1] + gt_label[3] / 2) * origin_shape[0])
              bbox = bbox.astype(np.int32)
              cv2.rectangle(origin_img, (bbox[0], bbox[1]), (bbox[2], bbox[3]), color=(0,0,255), thickness=3)
  
          bbox = dets_in_one_image_dict[0][0, :4].astype(np.int32)
          cv2.rectangle(origin_img, (bbox[0], bbox[1]), (bbox[2], bbox[3]), color=(255,255,0), thickness=3)
          cv2.imwrite("../demos/det_%d_%.06f_result.jpg" % (i, best_result["hm_score"]), origin_img)
  
  if __name__ == '__main__':
      # opt = config_factory.get_config("det_val_resdcn18_4x")()
      # opt = config_factory.get_config("det_val_resdcn18_2x")()
      # opt = config_factory.get_config("det_val_flynet_tiny")()
      # opt = config_factory.get_config("det_val_gridnet_tiny")()
      opt = config_factory.get_config("det_val_gridnet_ball")()
      opt.recipe = "fairmot"
      opt.task = "ball"
  
      opt.load_model = "../models/mnet_det_ball.pth"
  
      with torch.no_grad():
          map = test_det(opt, batch_size=1, img_size=opt.img_size, iou_thres=0.2)
  