text: import numpy as np
  import configparser
  import os
  import motmetrics as mm
  # from numba import jit
  import torch
  from torch.nn.functional import cosine_similarity
  from scipy.optimize import linear_sum_assignment as linear_assignment
  from .tn_utils import *
  from .kalman_box import KalmanBoxTracker, TrackState
  
  # read config file
  config = configparser.ConfigParser()
  config.read(os.path.join("src", "configs", "tracking.config"))
  
  # detector params
  det_mode = config.get("Detector", "det_mode")
  nms_thres = config.getfloat("Detector", "nms_thres")
  nms_flag = config.getboolean("Detector", "nms_flag")
  high_thres = config.getfloat("Detector", "high_thres")
  low_thres = config.getfloat("Detector", "low_thres")
  track_init_thres = config.getfloat("Detector", "track_init_thres")
  exp_file = config.get("Detector", "exp_file")
  detector_model_file = config.get("Detector", "chkpt_file")
  half = config.getboolean("Detector", "half")
  # TrackerInfo
  pretext_model_path = config.get("TrackerInfo", "pretext_model_path")
  embedding_size = config.getint("TrackerInfo", "embedding_size")
  tracking_filename = config.get("TrackerInfo", "tracking_filename")
  experiment_name = config.get("TrackerInfo", "experiment_name")
  # TrackerParams
  max_age = config.getint("TrackerParams", "max_age")
  min_hits = config.getint("TrackerParams", "min_hits")
  beta = config.getfloat("TrackerParams", "beta")
  iou_thres = config.getfloat("TrackerParams", "iou_thres")
  min_box_area = config.getfloat("TrackerParams", "min_box_area")
  iou_thres_2 = config.getfloat("TrackerParams", "iou_thres_2")
  visual = config.getboolean("TrackerParams", "visual")
  
  
  # @jit
  def iou(bb_test, bb_gt):
      """
    Computes IUO between two bboxes in the form [x1,y1,x2,y2]
    """
      xx1 = np.maximum(bb_test[0], bb_gt[0])
      yy1 = np.maximum(bb_test[1], bb_gt[1])
      xx2 = np.minimum(bb_test[2], bb_gt[2])
      yy2 = np.minimum(bb_test[3], bb_gt[3])
      w = np.maximum(0., xx2 - xx1)
      h = np.maximum(0., yy2 - yy1)
      wh = w * h
      o = wh / ((bb_test[2] - bb_test[0]) * (bb_test[3] - bb_test[1])
                + (bb_gt[2] - bb_gt[0]) * (bb_gt[3] - bb_gt[1]) - wh)
      return (o)
  
  
  def initialize_tracks(detections, scores, detection_img, detection_embeddings, frame_id):
      trackers = []
      # remove all invalid detections before for loop
      for d, det in enumerate(detections):
          if scores[d] >= track_init_thres:
              track = KalmanBoxTracker(det, detection_img[d], detection_embeddings[d], scores[d], frame_id)
              trackers.append(track)
      return trackers
  
  
  def get_track_states(trackers, time):
      ret = []
      # iterate over all trackers
      # parallelize this
      for t, track in enumerate(trackers):
          # append to results if active
          if track.status == TrackState.Tracked:
              d = track.get_state().astype(int)
              s = np.array([track.score])
              d = convert_corners_to_wh(d.reshape(1, -1))[0]
              vertical = d[2] / d[3] > 1.6
              if d[2] * d[3] > min_box_area and not vertical:
                  ret.append(np.concatenate(([time, track.id], d, s)).reshape(1, -1))
          # terminate tracks with age more than max age
          if track.time_since_update > max_age:
              track.status = TrackState.Removed
          if track.status == TrackState.New and track.hit_streak >= min_hits:
              track.status = TrackState.Removed
      return ret, trackers
  
  
  def embedding_distance_matrix(track_embeddings, detection_embedding):
      track_embeddings = torch.from_numpy(track_embeddings)
      detection_embedding = torch.from_numpy(detection_embedding)
      D_cos = 1 - cosine_similarity(track_embeddings.unsqueeze(1),
                                    detection_embedding, -1)
      D_cos = D_cos.numpy()
      # D_cos = D_cos/D_cos. max(1).reshape(-1,1)
      return D_cos
  
  def embedding_similarity_matrix(track_embeddings, detection_embedding):
      track_embeddings = torch.from_numpy(track_embeddings)
      detection_embedding = torch.from_numpy(detection_embedding)
      D_cos = cosine_similarity(track_embeddings.unsqueeze(1),
                                    detection_embedding, -1)
      D_cos = D_cos.numpy()
      # D_cos = D_cos/D_cos. max(1).reshape(-1,1)
      return D_cos
  
  
  def visibility_matrix(track_bbox, detection_bbox):
      D_bbox = np.zeros((len(track_bbox), len(detection_bbox)), dtype=np.float32)
      for t, trk in enumerate(track_bbox):
          for d, det in enumerate(detection_bbox):
              D_bbox[t, d] = 1 - iou(trk, det)
      return D_bbox
  
  def iou_matrix(track_bbox, detection_bbox):
      D_bbox = np.zeros((len(track_bbox), len(detection_bbox)), dtype=np.float32)
      for t, trk in enumerate(track_bbox):
          for d, det in enumerate(detection_bbox):
              D_bbox[t, d] = iou(trk, det)
      return D_bbox
  
  
  def bbox_distance_matrix(track_bbox, detection_bbox):
      bbox_difference = np.expand_dims(track_bbox, 1) - detection_bbox
      track_hw = convert_corners_to_wh(track_bbox)[:, 2:]
      perimeter_track = 2 * track_hw.sum(1)
      D_bbox = np.linalg.norm(bbox_difference, axis=2) / (2 * np.expand_dims(perimeter_track, 1))
      return D_bbox
  
  
  def associate_detections_to_tracks(detections, detection_img_list, scores, tracks, detection_embeddings):
      """ Step 1 : Split detections into low and high score groups """
      D_high_idx = np.where(scores >= high_thres)
      D_low_idx = np.where((scores >= low_thres) & (scores < high_thres))
  
      """ Step 2: Predict new locations of Tracks """
      predicted_tracks = []
      track_embeddings = []
      # parallelize this
      for t in tracks:
          predicted_tracks.append(t.predict())
          track_embeddings.append(t.embedding)
      predicted_tracks = np.array(predicted_tracks)
      track_embeddings = np.array(track_embeddings)
  
      """ Step 3: Get cost matrix for (tracks, detections) using IOU and visual embeddings """
      # get iou matrix for predicted tracks and detections
      # D_bbox = bbox_distance_matrix(predicted_tracks.copy(), detections.copy())
      D_iou = iou_matrix(predicted_tracks, detections)
      # get cosine distance between track and detections embeddings
      if visual:
          D_cos = embedding_similarity_matrix(track_embeddings, detection_embeddings)
          # t = track_embeddings / np.linalg.norm(track_embeddings, axis=1).reshape(-1, 1)
          # d = detection_embeddings / np.linalg.norm(detection_embeddings, axis=1).reshape(-1, 1)
          # a = np.exp(t.dot(d.T))
          # term1 = (a.T / a.sum(0).reshape(-1, 1)).T
          # term2 = a / a.sum(1).reshape(-1, 1)
          # D_total = (term1 + term2) / 2.0
          D_total = D_cos * D_iou
          # D_total = (D_cos + D_iou )/2
  
          # # get cosine distance
          # D_cos = embedding_distance_matrix(track_embeddings, detection_embeddings)
          # for i in range(D_bbox.shape[0]):
          #     closest_boxes = D_bbox[i].argsort()
          #     D_cos[i][closest_boxes[10:]] = 100
          #
          # D_cos = embedding_distance_matrix(track_embeddings, detection_embeddings)
          # # sum up bbox and embedding distances
          # D_total = D_cos
          # # D_total = 0.7 * D_cos + 0.3*D_bbox
      else:
          D_total = D_iou
      ''' Step 4: Perform first match between D_high and predicted tracks '''
      iou_matrix_high = D_total[:, D_high_idx[0].T]
      first_matches = linear_assignment(-iou_matrix_high)
      first_match_costs = iou_matrix_high[first_matches]
      # find accept matches
      accept_matches_idx = np.where(first_match_costs >= iou_thres)
      accept_tracks = first_matches[0][accept_matches_idx]
      accept_detections = D_high_idx[0][first_matches[1]][accept_matches_idx]
      # find unmatched tracks
      all_tracks = np.arange(len(tracks))
      unmatched_tracks = find_set_difference(all_tracks, accept_tracks)
      # find unmatched detections
      unmatched_detections = find_set_difference(D_high_idx[0], accept_detections)
      ''' Step 5: Perform second match '''
      iou_matrix_low = D_iou[unmatched_tracks][:, D_low_idx[0]]
      second_matches = linear_assignment(-iou_matrix_low)
      second_match_costs = iou_matrix_low[second_matches]
  
      # find accept second matches
      accept_second_matches_idx = np.where(second_match_costs >= iou_thres_2)
      accept_tracks_second = unmatched_tracks[second_matches[0]][accept_second_matches_idx]
      accept_detections_second = D_low_idx[0][second_matches[1]][accept_second_matches_idx]
  
      # find second unmatched tracks
      all_second_tracks = unmatched_tracks
      unmatched_second_tracks = find_set_difference(all_second_tracks, accept_tracks_second)
  
      # find second unmatched detections
      unmatched_second_detections = find_set_difference(D_low_idx[0], accept_detections_second)
  
      # find total unmatched detections
      unmatched_detections = np.concatenate([unmatched_detections, unmatched_second_detections])
  
      # find total matches
      matched_tracks = np.concatenate([accept_tracks, accept_tracks_second])
      matched_detections = np.concatenate([accept_detections, accept_detections_second])
  
      return matched_tracks, matched_detections, unmatched_second_tracks, unmatched_detections
  