From 413686dc0d8aab775a80525273cfe2c6f39b5204 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Royeth?= Date: Sat, 22 Aug 2026 09:51:32 -0400 Subject: [PATCH] perf(eval): reuse contiguous prepared IDs --- src/trackers/eval/clear.py | 6 ++++-- src/trackers/eval/hota.py | 23 +++++++++++++++-------- src/trackers/eval/identity.py | 18 +++++++++++++----- 3 files changed, 32 insertions(+), 15 deletions(-) diff --git a/src/trackers/eval/clear.py b/src/trackers/eval/clear.py index 180235a99..9a40d17d5 100644 --- a/src/trackers/eval/clear.py +++ b/src/trackers/eval/clear.py @@ -157,6 +157,8 @@ def compute_clear_metrics( "CLR_Frames": num_frames, } + gt_contiguous = unique_gt_ids.dtype.kind in "iu" and unique_gt_ids[0] == 0 and unique_gt_ids[-1] == num_gt_ids - 1 + # Initialize counters clr_tp = 0 clr_fn = 0 @@ -176,8 +178,8 @@ def compute_clear_metrics( # Process each timestep for t, (gt_ids_t, tracker_ids_t) in enumerate(zip(gt_ids, tracker_ids)): - # Map GT IDs to indices using searchsorted (vectorized) - gt_indices_t = np.atleast_1d(np.searchsorted(unique_gt_ids, gt_ids_t)) + # Map GT IDs directly or use the searchsorted fallback. + gt_indices_t = np.atleast_1d(gt_ids_t if gt_contiguous else np.searchsorted(unique_gt_ids, gt_ids_t)) # Handle empty frames if len(gt_ids_t) == 0: diff --git a/src/trackers/eval/hota.py b/src/trackers/eval/hota.py index 54f181a30..dccd8eb41 100644 --- a/src/trackers/eval/hota.py +++ b/src/trackers/eval/hota.py @@ -130,10 +130,17 @@ def compute_hota_metrics( num_tracker_ids = len(unique_tracker_ids) # `unique_gt_ids` / `unique_tracker_ids` are sorted (np.unique returns sorted - # output), so an id's row/column index is simply its position found by binary - # search. This replaces per-frame Python dict lookups in the hot loops below. + # output). Prepared zero-based integer IDs can be used as indices directly. + # Other ID layouts keep the binary-search mapping used by public callers. + # The check is done once before both passes. # Precondition: all per-frame IDs are present in unique_*_ids (guaranteed — # unique arrays are built from concatenation of all frames). + gt_contiguous = unique_gt_ids.dtype.kind in "iu" and unique_gt_ids[0] == 0 and unique_gt_ids[-1] == num_gt_ids - 1 + tracker_contiguous = ( + unique_tracker_ids.dtype.kind in "iu" + and unique_tracker_ids[0] == 0 + and unique_tracker_ids[-1] == num_tracker_ids - 1 + ) # Variables for global association (ref: hota.py:48-50) potential_matches_count: np.ndarray = np.zeros((num_gt_ids, num_tracker_ids), dtype=np.float64) @@ -145,15 +152,15 @@ def compute_hota_metrics( if len(gt_ids_t) == 0 or len(tracker_ids_t) == 0: # Still count IDs even if no matches possible if len(gt_ids_t) > 0: - gt_indices = np.searchsorted(unique_gt_ids, gt_ids_t) + gt_indices = gt_ids_t if gt_contiguous else np.searchsorted(unique_gt_ids, gt_ids_t) gt_id_count[gt_indices] += 1 if len(tracker_ids_t) > 0: - tr_indices = np.searchsorted(unique_tracker_ids, tracker_ids_t) + tr_indices = tracker_ids_t if tracker_contiguous else np.searchsorted(unique_tracker_ids, tracker_ids_t) tracker_id_count[0, tr_indices] += 1 continue - gt_indices = np.searchsorted(unique_gt_ids, gt_ids_t) - tr_indices = np.searchsorted(unique_tracker_ids, tracker_ids_t) + gt_indices = gt_ids_t if gt_contiguous else np.searchsorted(unique_gt_ids, gt_ids_t) + tr_indices = tracker_ids_t if tracker_contiguous else np.searchsorted(unique_tracker_ids, tracker_ids_t) similarity = similarity_scores[t] @@ -188,8 +195,8 @@ def compute_hota_metrics( hota_fn += len(gt_ids_t) continue - gt_indices = np.searchsorted(unique_gt_ids, gt_ids_t) - tr_indices = np.searchsorted(unique_tracker_ids, tracker_ids_t) + gt_indices = gt_ids_t if gt_contiguous else np.searchsorted(unique_gt_ids, gt_ids_t) + tr_indices = tracker_ids_t if tracker_contiguous else np.searchsorted(unique_tracker_ids, tracker_ids_t) similarity = similarity_scores[t] diff --git a/src/trackers/eval/identity.py b/src/trackers/eval/identity.py index 9195eff87..0970099b8 100644 --- a/src/trackers/eval/identity.py +++ b/src/trackers/eval/identity.py @@ -109,7 +109,15 @@ def compute_identity_metrics( num_tracker_ids = len(unique_tracker_ids) # `np.unique` sorts the IDs, and every per-frame ID is included in those - # arrays, so searchsorted maps all IDs directly to their global indices. + # arrays. Prepared zero-based integer IDs can be used as indices directly. + # Other ID layouts retain searchsorted, preserving the public-call fallback. + # The check is performed once before entering the frame loop. + gt_contiguous = unique_gt_ids.dtype.kind in "iu" and unique_gt_ids[0] == 0 and unique_gt_ids[-1] == num_gt_ids - 1 + tracker_contiguous = ( + unique_tracker_ids.dtype.kind in "iu" + and unique_tracker_ids[0] == 0 + and unique_tracker_ids[-1] == num_tracker_ids - 1 + ) # Variables for global association (ref: identity.py:48-50) potential_matches_count = np.zeros((num_gt_ids, num_tracker_ids)) @@ -121,15 +129,15 @@ def compute_identity_metrics( if len(gt_ids_t) == 0 or len(tracker_ids_t) == 0: # Still count IDs even if no matches possible if len(gt_ids_t) > 0: - gt_indices = np.searchsorted(unique_gt_ids, gt_ids_t) + gt_indices = gt_ids_t if gt_contiguous else np.searchsorted(unique_gt_ids, gt_ids_t) gt_id_count[gt_indices] += 1 if len(tracker_ids_t) > 0: - tr_indices = np.searchsorted(unique_tracker_ids, tracker_ids_t) + tr_indices = tracker_ids_t if tracker_contiguous else np.searchsorted(unique_tracker_ids, tracker_ids_t) tracker_id_count[tr_indices] += 1 continue - gt_indices = np.searchsorted(unique_gt_ids, gt_ids_t) - tr_indices = np.searchsorted(unique_tracker_ids, tracker_ids_t) + gt_indices = gt_ids_t if gt_contiguous else np.searchsorted(unique_gt_ids, gt_ids_t) + tr_indices = tracker_ids_t if tracker_contiguous else np.searchsorted(unique_tracker_ids, tracker_ids_t) similarity = similarity_scores[t]