Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),

### 🌱 Changed

- **McByte skips mask-only association work when mask evidence is unavailable** — clear-match locking and reduced assignment are preserved, while ambiguity and isolated-candidate matrices are no longer built for the default mask-disabled path.
- **McByte CMC now defaults to `cmc_downscale=6`** — this aggregate-performance default halves median CMC latency versus factor `2` on the complete 45-clip, 1280x720 SportsMOT validation split and passes the dataset-level mean/median quality criterion. The benchmark used ground-truth detections with masks disabled; 9/45 clips regressed under the previous strict per-clip gate. Pass `cmc_downscale=2` to preserve the previous conservative behavior. Generic `CMCConfig` and `BoTSORTTracker` remain at `2`.
- **Mask stack moved from `trackers.core.mcbyte.masks` to `trackers.core.masks`** — SAM mask generation, Cutie propagation, and `MaskManager` reference no tracker and are not McByte-specific, so they now live beside the trackers rather than inside one. Import from `trackers.core.masks` instead ([#543](https://github.com/roboflow/trackers/pull/543)).

Expand Down
23 changes: 19 additions & 4 deletions src/trackers/core/mcbyte/mask_association.py
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ def _apply_mask_similarity_boosts(
remaining_detection_indices: list[int],
tracklet_ids: list[int],
detection_boxes: np.ndarray,
mask_output: MaskOutput | None,
mask_output: MaskOutput,
minimum_mask_average_confidence: float,
minimum_mask_coverage: float,
minimum_mask_fill_ratio: float,
Expand Down Expand Up @@ -285,8 +285,8 @@ def _apply_mask_similarity_boosts(
mask_output: Current propagated mask output. Its
``tracklet_mask_dict`` maps stable tracklet IDs to local mask-array
indices, while ``mask_avg_prob_dict`` stores average mask confidence
keyed by stable tracklet ID. If the output, masks, or confidence
mapping is unavailable, no scores are modified.
keyed by stable tracklet ID. Callers must have already established
that masks and confidences are present.
minimum_mask_average_confidence: Minimum average propagated-mask
confidence required before mask evidence may be used.
minimum_mask_coverage: Minimum fraction of the complete visible mask
Expand All @@ -297,7 +297,7 @@ def _apply_mask_similarity_boosts(
Returns:
None. ``conditioned_similarity`` is updated in place.
"""
if mask_output is None or mask_output.masks is None or mask_output.mask_avg_prob_dict is None:
if mask_output.masks is None or mask_output.mask_avg_prob_dict is None:
return

# Local indices in the reduced matrix.
Expand Down Expand Up @@ -477,6 +477,21 @@ def condition_similarity_with_masks(
)
].copy()

if (
mask_output is None
or mask_output.masks is None
or mask_output.masks.shape[0] == 0
or not mask_output.tracklet_mask_dict
or not mask_output.mask_avg_prob_dict
):
# No tracklet can receive mask evidence, so the reduced problem is final.
return MaskConditionedAssociation(
conditioned_similarity=reduced_similarity,
locked_matches=locked_matches,
remaining_track_indices=remaining_track_indices,
remaining_detection_indices=remaining_detection_indices,
)

# Ambiguity is a property of the original association situation before any
# modifications, hence computed from base_similarity.
ambiguous_candidates = _get_ambiguous_candidate_matrix(
Expand Down
99 changes: 80 additions & 19 deletions tests/core/test_mcbyte_mask_association.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

from __future__ import annotations

from unittest.mock import patch

import numpy as np
import pytest

Expand Down Expand Up @@ -341,28 +343,87 @@ def test_mask_bonus_is_not_clamped_to_one() -> None:
assert np.isclose(result.conditioned_similarity[0, 0], 1.8)


def test_missing_mask_output_keeps_ambiguous_scores_unchanged() -> None:
similarity = np.array([[0.7, 0.6]], dtype=np.float32)

result = condition_similarity_with_masks(
similarity=similarity,
raw_iou_similarity=similarity,
tracklet_ids=[10],
detection_boxes=np.array(
[
[0, 0, 5, 5],
[5, 5, 10, 10],
],
dtype=np.float32,
@pytest.mark.parametrize(
"mask_output",
[
pytest.param(None, id="missing-output"),
pytest.param(
MaskOutput(masks=None, tracklet_mask_dict={}, mask_avg_prob_dict={}),
id="missing-masks",
),
mask_output=None,
minimum_similarity=0.5,
pytest.param(
MaskOutput(
masks=np.ones((1, 10, 10), dtype=bool),
tracklet_mask_dict={10: 0},
mask_avg_prob_dict=None,
),
id="missing-confidence-map",
),
pytest.param(
MaskOutput(
masks=np.zeros((0, 10, 10), dtype=bool),
tracklet_mask_dict={10: 0},
mask_avg_prob_dict={10: 0.9},
),
id="zero-masks",
),
pytest.param(
MaskOutput(
masks=np.ones((1, 10, 10), dtype=bool),
tracklet_mask_dict={},
mask_avg_prob_dict={10: 0.9},
),
id="empty-tracklet-map",
),
pytest.param(
MaskOutput(
masks=np.ones((1, 10, 10), dtype=bool),
tracklet_mask_dict={10: 0},
mask_avg_prob_dict={},
),
id="empty-confidence-map",
),
],
)
def test_missing_mask_evidence_skips_candidate_matrix_work(mask_output: MaskOutput | None) -> None:
similarity = np.array(
[
[0.9, 0.1, 0.0],
[0.1, 0.7, 0.6],
[0.0, 0.6, 0.7],
],
dtype=np.float32,
)
original = similarity.copy()

np.testing.assert_array_equal(
result.conditioned_similarity,
similarity,
)
with (
patch("trackers.core.mcbyte.mask_association._get_ambiguous_candidate_matrix") as ambiguous_candidates,
patch("trackers.core.mcbyte.mask_association._get_isolated_candidate_matrix") as isolated_candidates,
):
result = condition_similarity_with_masks(
similarity=similarity,
raw_iou_similarity=similarity,
tracklet_ids=[10, 20, 30],
detection_boxes=np.array(
[
[0, 0, 5, 5],
[5, 5, 10, 10],
[10, 10, 15, 15],
],
dtype=np.float32,
),
mask_output=mask_output,
minimum_similarity=0.5,
enable_isolated_mask_matching=True,
)

ambiguous_candidates.assert_not_called()
isolated_candidates.assert_not_called()
assert result.locked_matches == [(0, 0)]
assert result.remaining_track_indices == [1, 2]
assert result.remaining_detection_indices == [1, 2]
np.testing.assert_array_equal(result.conditioned_similarity, original[1:, 1:])
np.testing.assert_array_equal(similarity, original)


def test_missing_tracklet_mask_keeps_scores_unchanged() -> None:
Expand Down