123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428 |
- # Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.
- #
- # Licensed under the Apache License, Version 2.0 (the "License");
- # you may not use this file except in compliance with the License.
- # You may obtain a copy of the License at
- #
- # http://www.apache.org/licenses/LICENSE-2.0
- #
- # Unless required by applicable law or agreed to in writing, software
- # distributed under the License is distributed on an "AS IS" BASIS,
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- # See the License for the specific language governing permissions and
- # limitations under the License.
- """
- This code is borrow from https://github.com/xingyizhou/CenterTrack/blob/master/src/tools/eval_kitti_track/munkres.py
- """
- import sys
- __all__ = ['Munkres', 'make_cost_matrix']
- class Munkres:
- """
- Calculate the Munkres solution to the classical assignment problem.
- See the module documentation for usage.
- """
- def __init__(self):
- """Create a new instance"""
- self.C = None
- self.row_covered = []
- self.col_covered = []
- self.n = 0
- self.Z0_r = 0
- self.Z0_c = 0
- self.marked = None
- self.path = None
- def make_cost_matrix(profit_matrix, inversion_function):
- """
- **DEPRECATED**
- Please use the module function ``make_cost_matrix()``.
- """
- import munkres
- return munkres.make_cost_matrix(profit_matrix, inversion_function)
- make_cost_matrix = staticmethod(make_cost_matrix)
- def pad_matrix(self, matrix, pad_value=0):
- """
- Pad a possibly non-square matrix to make it square.
- :Parameters:
- matrix : list of lists
- matrix to pad
- pad_value : int
- value to use to pad the matrix
- :rtype: list of lists
- :return: a new, possibly padded, matrix
- """
- max_columns = 0
- total_rows = len(matrix)
- for row in matrix:
- max_columns = max(max_columns, len(row))
- total_rows = max(max_columns, total_rows)
- new_matrix = []
- for row in matrix:
- row_len = len(row)
- new_row = row[:]
- if total_rows > row_len:
- # Row too short. Pad it.
- new_row += [0] * (total_rows - row_len)
- new_matrix += [new_row]
- while len(new_matrix) < total_rows:
- new_matrix += [[0] * total_rows]
- return new_matrix
- def compute(self, cost_matrix):
- """
- Compute the indexes for the lowest-cost pairings between rows and
- columns in the database. Returns a list of (row, column) tuples
- that can be used to traverse the matrix.
- :Parameters:
- cost_matrix : list of lists
- The cost matrix. If this cost matrix is not square, it
- will be padded with zeros, via a call to ``pad_matrix()``.
- (This method does *not* modify the caller's matrix. It
- operates on a copy of the matrix.)
- **WARNING**: This code handles square and rectangular
- matrices. It does *not* handle irregular matrices.
- :rtype: list
- :return: A list of ``(row, column)`` tuples that describe the lowest
- cost path through the matrix
- """
- self.C = self.pad_matrix(cost_matrix)
- self.n = len(self.C)
- self.original_length = len(cost_matrix)
- self.original_width = len(cost_matrix[0])
- self.row_covered = [False for i in range(self.n)]
- self.col_covered = [False for i in range(self.n)]
- self.Z0_r = 0
- self.Z0_c = 0
- self.path = self.__make_matrix(self.n * 2, 0)
- self.marked = self.__make_matrix(self.n, 0)
- done = False
- step = 1
- steps = {
- 1: self.__step1,
- 2: self.__step2,
- 3: self.__step3,
- 4: self.__step4,
- 5: self.__step5,
- 6: self.__step6
- }
- while not done:
- try:
- func = steps[step]
- step = func()
- except KeyError:
- done = True
- # Look for the starred columns
- results = []
- for i in range(self.original_length):
- for j in range(self.original_width):
- if self.marked[i][j] == 1:
- results += [(i, j)]
- return results
- def __copy_matrix(self, matrix):
- """Return an exact copy of the supplied matrix"""
- return copy.deepcopy(matrix)
- def __make_matrix(self, n, val):
- """Create an *n*x*n* matrix, populating it with the specific value."""
- matrix = []
- for i in range(n):
- matrix += [[val for j in range(n)]]
- return matrix
- def __step1(self):
- """
- For each row of the matrix, find the smallest element and
- subtract it from every element in its row. Go to Step 2.
- """
- C = self.C
- n = self.n
- for i in range(n):
- minval = min(self.C[i])
- # Find the minimum value for this row and subtract that minimum
- # from every element in the row.
- for j in range(n):
- self.C[i][j] -= minval
- return 2
- def __step2(self):
- """
- Find a zero (Z) in the resulting matrix. If there is no starred
- zero in its row or column, star Z. Repeat for each element in the
- matrix. Go to Step 3.
- """
- n = self.n
- for i in range(n):
- for j in range(n):
- if (self.C[i][j] == 0) and \
- (not self.col_covered[j]) and \
- (not self.row_covered[i]):
- self.marked[i][j] = 1
- self.col_covered[j] = True
- self.row_covered[i] = True
- self.__clear_covers()
- return 3
- def __step3(self):
- """
- Cover each column containing a starred zero. If K columns are
- covered, the starred zeros describe a complete set of unique
- assignments. In this case, Go to DONE, otherwise, Go to Step 4.
- """
- n = self.n
- count = 0
- for i in range(n):
- for j in range(n):
- if self.marked[i][j] == 1:
- self.col_covered[j] = True
- count += 1
- if count >= n:
- step = 7 # done
- else:
- step = 4
- return step
- def __step4(self):
- """
- Find a noncovered zero and prime it. If there is no starred zero
- in the row containing this primed zero, Go to Step 5. Otherwise,
- cover this row and uncover the column containing the starred
- zero. Continue in this manner until there are no uncovered zeros
- left. Save the smallest uncovered value and Go to Step 6.
- """
- step = 0
- done = False
- row = -1
- col = -1
- star_col = -1
- while not done:
- (row, col) = self.__find_a_zero()
- if row < 0:
- done = True
- step = 6
- else:
- self.marked[row][col] = 2
- star_col = self.__find_star_in_row(row)
- if star_col >= 0:
- col = star_col
- self.row_covered[row] = True
- self.col_covered[col] = False
- else:
- done = True
- self.Z0_r = row
- self.Z0_c = col
- step = 5
- return step
- def __step5(self):
- """
- Construct a series of alternating primed and starred zeros as
- follows. Let Z0 represent the uncovered primed zero found in Step 4.
- Let Z1 denote the starred zero in the column of Z0 (if any).
- Let Z2 denote the primed zero in the row of Z1 (there will always
- be one). Continue until the series terminates at a primed zero
- that has no starred zero in its column. Unstar each starred zero
- of the series, star each primed zero of the series, erase all
- primes and uncover every line in the matrix. Return to Step 3
- """
- count = 0
- path = self.path
- path[count][0] = self.Z0_r
- path[count][1] = self.Z0_c
- done = False
- while not done:
- row = self.__find_star_in_col(path[count][1])
- if row >= 0:
- count += 1
- path[count][0] = row
- path[count][1] = path[count - 1][1]
- else:
- done = True
- if not done:
- col = self.__find_prime_in_row(path[count][0])
- count += 1
- path[count][0] = path[count - 1][0]
- path[count][1] = col
- self.__convert_path(path, count)
- self.__clear_covers()
- self.__erase_primes()
- return 3
- def __step6(self):
- """
- Add the value found in Step 4 to every element of each covered
- row, and subtract it from every element of each uncovered column.
- Return to Step 4 without altering any stars, primes, or covered
- lines.
- """
- minval = self.__find_smallest()
- for i in range(self.n):
- for j in range(self.n):
- if self.row_covered[i]:
- self.C[i][j] += minval
- if not self.col_covered[j]:
- self.C[i][j] -= minval
- return 4
- def __find_smallest(self):
- """Find the smallest uncovered value in the matrix."""
- minval = 2e9 # sys.maxint
- for i in range(self.n):
- for j in range(self.n):
- if (not self.row_covered[i]) and (not self.col_covered[j]):
- if minval > self.C[i][j]:
- minval = self.C[i][j]
- return minval
- def __find_a_zero(self):
- """Find the first uncovered element with value 0"""
- row = -1
- col = -1
- i = 0
- n = self.n
- done = False
- while not done:
- j = 0
- while True:
- if (self.C[i][j] == 0) and \
- (not self.row_covered[i]) and \
- (not self.col_covered[j]):
- row = i
- col = j
- done = True
- j += 1
- if j >= n:
- break
- i += 1
- if i >= n:
- done = True
- return (row, col)
- def __find_star_in_row(self, row):
- """
- Find the first starred element in the specified row. Returns
- the column index, or -1 if no starred element was found.
- """
- col = -1
- for j in range(self.n):
- if self.marked[row][j] == 1:
- col = j
- break
- return col
- def __find_star_in_col(self, col):
- """
- Find the first starred element in the specified row. Returns
- the row index, or -1 if no starred element was found.
- """
- row = -1
- for i in range(self.n):
- if self.marked[i][col] == 1:
- row = i
- break
- return row
- def __find_prime_in_row(self, row):
- """
- Find the first prime element in the specified row. Returns
- the column index, or -1 if no starred element was found.
- """
- col = -1
- for j in range(self.n):
- if self.marked[row][j] == 2:
- col = j
- break
- return col
- def __convert_path(self, path, count):
- for i in range(count + 1):
- if self.marked[path[i][0]][path[i][1]] == 1:
- self.marked[path[i][0]][path[i][1]] = 0
- else:
- self.marked[path[i][0]][path[i][1]] = 1
- def __clear_covers(self):
- """Clear all covered matrix cells"""
- for i in range(self.n):
- self.row_covered[i] = False
- self.col_covered[i] = False
- def __erase_primes(self):
- """Erase all prime markings"""
- for i in range(self.n):
- for j in range(self.n):
- if self.marked[i][j] == 2:
- self.marked[i][j] = 0
- def make_cost_matrix(profit_matrix, inversion_function):
- """
- Create a cost matrix from a profit matrix by calling
- 'inversion_function' to invert each value. The inversion
- function must take one numeric argument (of any type) and return
- another numeric argument which is presumed to be the cost inverse
- of the original profit.
- This is a static method. Call it like this:
- .. python::
- cost_matrix = Munkres.make_cost_matrix(matrix, inversion_func)
- For example:
- .. python::
- cost_matrix = Munkres.make_cost_matrix(matrix, lambda x : sys.maxint - x)
- :Parameters:
- profit_matrix : list of lists
- The matrix to convert from a profit to a cost matrix
- inversion_function : function
- The function to use to invert each entry in the profit matrix
- :rtype: list of lists
- :return: The converted matrix
- """
- cost_matrix = []
- for row in profit_matrix:
- cost_matrix.append([inversion_function(value) for value in row])
- return cost_matrix
|