Skip to content

Hungarian matching fails with infeasible cost matrix when scores are all NaN #491

@talmo

Description

@talmo

Bug

hungarian_matching() in sleap_nn/tracking/utils.py crashes with ValueError: cost matrix is infeasible when the cost matrix passed to scipy.optimize.linear_sum_assignment() contains only inf values.

How to reproduce

Run tracking with --track_matching_method hungarian on data where some frames produce all-NaN scores (e.g., when no candidates pass the min_match_points filter or when keypoints contain NaN values):

sleap-nn track \
    -i predictions.slp \
    -o tracked.slp \
    --tracking \
    --track_matching_method hungarian \
    --scoring_method euclidean_dist \
    --features keypoints \
    --candidates_method local_queues \
    --max_tracks 2

Root cause

In tracker.py, scores_to_cost_matrix() converts NaN scores to inf costs:

def scores_to_cost_matrix(self, scores: np.ndarray):
    cost_matrix = -scores
    cost_matrix[np.isnan(cost_matrix)] = np.inf  # NaN → inf
    return cost_matrix

When all scores for a frame are NaN (e.g., empty scores_trackid list → np.nanmean([]) returns NaN), the entire cost matrix becomes inf, which linear_sum_assignment() considers infeasible.

greedy_matching() does not have this problem because np.argsort() silently handles inf values by sorting them to the end.

The current hungarian_matching() has no such handling:

def hungarian_matching(cost_matrix: np.ndarray):
    row_ids, col_ids = linear_sum_assignment(cost_matrix)  # fails on all-inf matrix
    return row_ids, col_ids

Suggested fix

Replace inf/nan values with a large finite number before calling linear_sum_assignment:

def hungarian_matching(cost_matrix: np.ndarray):
    cost_matrix = np.copy(cost_matrix)
    bad = ~np.isfinite(cost_matrix)
    if bad.any():
        finite_vals = cost_matrix[~bad]
        fill = (np.abs(finite_vals).max() * 10 + 1) if finite_vals.size > 0 else 1e6
        cost_matrix[bad] = fill
    row_ids, col_ids = linear_sum_assignment(cost_matrix)
    return row_ids, col_ids

This makes the assignment feasible while still strongly penalizing unmatched pairs, preserving the intended behavior.

Environment

  • sleap-nn 0.1.0 (installed from source, main branch)
  • scipy 1.17.1
  • Python 3.12

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions