text: import numpy as np
  from kalman_filter import KalmanFilter
  
  from scipy.optimize import linear_sum_assignment
  
  
  class Track(object):
      # Track class for every object to be tracked
      def __init__(self, prediction, trackIdCount):
          # prediction: predicted centroids of object to be tracked
          # trackIdCount: identification of each track object
          self.track_id = trackIdCount  # identification of each track object
          self.KF = KalmanFilter()  # KF instance to track this object
          self.prediction = np.asarray(prediction)  # predicted centroids (x,y)
          self.skipped_frames = 0  # number of frames skipped undetected
          self.trace = []  # trace path
  
  
  class Tracker(object):
      # Tracker class that updates track vectors of object tracked
      
      def __init__(self, dist_thresh, max_trace_length,
                   trackIdCount):
          '''Initialize variable used by Tracker class
          Args:
              dist_thresh: distance threshold. When exceeds the threshold,
                           track will be deleted and new track is created
              max_trace_length: maximum traces length shown on screen
              trackIdCount: identification of each track object
          Return:
              None
          '''
          self.dist_thresh = dist_thresh
          self.max_frames_to_skip = 0
          self.max_trace_length = max_trace_length
          self.tracks = []
          self.trackIdCount = trackIdCount
  
      def Update(self, detections):
          '''Update tracks vector using following steps:
              - Create tracks if no tracks vector found
              - Calculate cost using sum of square distance
                between predicted vs detected centroids
              - Using Hungarian Algorithm assign the correct
                detected measurements to predicted tracks
                https://en.wikipedia.org/wiki/Hungarian_algorithm
              - Identify tracks with no assignment, if any
              - If tracks are not detected for long time, remove them
              - Now look for un_assigned detects
              - Start new tracks
              - Update KalmanFilter state, lastResults and tracks trace
          Args:
              detections: location of centroids of object to be tracked
          '''
  
          # Create tracks if no tracks vector found
          if (len(self.tracks) == 0):
              for i in range(len(detections)):
                  track = Track(detections[i], self.trackIdCount)
                  self.trackIdCount += 1
                  self.tracks.append(track)
  
          # Calculate cost using sum of square distance between
          # predicted vs detected centroids
          N = len(self.tracks)
          M = len(detections)
          cost = np.zeros(shape=(N, M))   # Cost matrix
          for i in range(len(self.tracks)):
              for j in range(len(detections)):
                  try:
                      diff = self.tracks[i].prediction - detections[j]
                      distance = np.sqrt(diff[0][0]*diff[0][0] +
                                         diff[1][0]*diff[1][0])
                      cost[i][j] = distance
                  except:
                      pass
  
          # Average the squared ERROR
          cost = (0.5) * cost
          # Using Hungarian Algorithm assign the correct detected measurements
          # to predicted tracks
          assignment = []
          for _ in range(N):
              assignment.append(-1)
          row_ind, col_ind = linear_sum_assignment(cost)
          for i in range(len(row_ind)):
              assignment[row_ind[i]] = col_ind[i]
  
          # Identify tracks with no assignment, if any
          un_assigned_tracks = []
          for i in range(len(assignment)):
              if (assignment[i] != -1):
                  # check for cost distance threshold.
                  # If cost is very high then un_assign (delete) the track
                  if (cost[i][assignment[i]] > self.dist_thresh):
                      assignment[i] = -1
                      un_assigned_tracks.append(i)
                  pass
              else:
                  self.tracks[i].skipped_frames += 1
  
          # If tracks are not detected for long time, remove them
          del_tracks = []
          for i in range(len(self.tracks)):
              if (self.tracks[i].skipped_frames > self.max_frames_to_skip):
                  del_tracks.append(i)
          if len(del_tracks) > 0:  # only when skipped frame exceeds max
              for id in del_tracks:
                  if id < len(self.tracks):
                      del self.tracks[id]
                      del assignment[id]
  
          # Now look for un_assigned detects
          un_assigned_detects = []
          for i in range(len(detections)):
                  if i not in assignment:
                      un_assigned_detects.append(i)
  
          # Start new tracks
          if(len(un_assigned_detects) != 0):
              for i in range(len(un_assigned_detects)):
                  track = Track(detections[un_assigned_detects[i]],
                                self.trackIdCount)
                  self.trackIdCount += 1
                  self.tracks.append(track)
  
          # Update KalmanFilter state, lastResults and tracks trace
          for i in range(len(assignment)):
              self.tracks[i].KF.predict()
  
              if(assignment[i] != -1):
                  self.tracks[i].skipped_frames = 0
                  self.tracks[i].prediction = self.tracks[i].KF.correct(
                                              detections[assignment[i]], 1)
              else:
                  self.tracks[i].prediction = self.tracks[i].KF.correct(
                                              np.array([[0], [0]]), 0)
  
              if(len(self.tracks[i].trace) > self.max_trace_length):
                  for j in range(len(self.tracks[i].trace) -
                                 self.max_trace_length):
                      del self.tracks[i].trace[j]
  
              self.tracks[i].trace.append(self.tracks[i].prediction)
              self.tracks[i].KF.lastResult = self.tracks[i].prediction
  