#include "EigenLinearAssignment.h" namespace ByteTrackEigen { /** * @brief Generates matches based on a cost matrix and a set of indices, considering a threshold. * * This function iterates through the provided indices and checks if the corresponding cost in the * cost matrix is below the specified threshold. If so, the index pair is considered a match. It also * keeps track of unmatched indices in both dimensions of the cost matrix. * * @param cost_matrix The matrix representing the cost of assigning each pair of elements. * @param indices The matrix of indices representing potential matches. * @param thresh A threshold value to determine acceptable assignments. * @return A tuple containing matched indices, and sets of unmatched indices in both dimensions. */ std::tuple>, std::set, std::set> LinearAssignment::indices_to_matches( const Eigen::MatrixXd& cost_matrix, const Eigen::MatrixXi& indices, double thresh) { if (cost_matrix.rows() <= 0 || cost_matrix.cols() <= 0) { throw std::invalid_argument("Cost matrix dimensions must be positive."); } std::vector> matches; std::set unmatched_a, unmatched_b; int num_rows = cost_matrix.rows(); int num_cols = cost_matrix.cols(); // Initialize unmatched indices for both dimensions. for (int i = 0; i < num_rows; i++) unmatched_a.insert(i); for (int j = 0; j < num_cols; j++) unmatched_b.insert(j); // Iterate through the indices to find valid matches. for (int k = 0; k < indices.rows(); k++) { int i = indices(k, 0); int j = indices(k, 1); if (i != -1 && j != -1) { if (cost_matrix(i, j) <= thresh) { matches.push_back({ i, j }); unmatched_a.erase(i); unmatched_b.erase(j); } } } return { matches, unmatched_a, unmatched_b }; } /** * @brief Solves the linear assignment problem for a given cost matrix and threshold. * * This function first checks if the cost matrix is empty. If not, it modifies the cost matrix * to mark values above the threshold as effectively infinite. Then it calls the Hungarian Algorithm * to solve the assignment problem and converts the results into indices. These indices are then passed * to `indices_to_matches` to extract the actual matches and unmatched indices. * * @param cost_matrix The matrix representing the cost of assigning each pair of elements. * @param thresh A threshold value to determine acceptable assignments. * @return A tuple containing matched indices, and sets of unmatched indices in both dimensions. */ std::tuple>, std::set, std::set> LinearAssignment::linear_assignment( const Eigen::MatrixXd& cost_matrix, double thresh) { int num_rows = cost_matrix.rows(); int num_cols = cost_matrix.cols(); // Handle empty cost matrix scenario. if (num_rows == 0 || num_cols == 0) { std::set unmatched_indices_first; std::set unmatched_indices_second; for (int i = 0; i < num_rows; i++) { unmatched_indices_first.insert(i); } for (int i = 0; i < num_cols; i++) { unmatched_indices_second.insert(i); } return { {}, unmatched_indices_first, unmatched_indices_second }; } // Modify the cost matrix to mark values above the threshold. Eigen::MatrixXd modified_cost_matrix = cost_matrix.unaryExpr([thresh](double val) { return (val > thresh) ? thresh + 1e-4 : val; }); Eigen::VectorXi assignment = Eigen::VectorXi::Constant(modified_cost_matrix.rows(), -1); // Solve the assignment problem using the Hungarian Algorithm. this->hungarian.solve_assignment_problem(modified_cost_matrix, assignment); // Convert the solution to indices format. Eigen::MatrixXi indices = Eigen::MatrixXi::Zero(num_rows, 2); indices.col(0) = Eigen::VectorXi::LinSpaced(num_rows, 0, num_rows - 1); indices.col(1) = assignment; // Use indices_to_matches to get the final matching result. return indices_to_matches(cost_matrix, indices, thresh); } }