Skip to content

Replace global class-variable track ID counters with per-instance allocators#419

Open
cdeil wants to merge 1 commit into
roboflow:developfrom
cdeil:tracker-class-to-instance-state
Open

Replace global class-variable track ID counters with per-instance allocators#419
cdeil wants to merge 1 commit into
roboflow:developfrom
cdeil:tracker-class-to-instance-state

Conversation

@cdeil
Copy link
Copy Markdown
Contributor

@cdeil cdeil commented May 15, 2026

What does this PR do?

Fix duplicate track IDs when multiple tracker instances of the same type are used in one process.

Track IDs are the labels returned in detections.tracker_id, for example person A gets tracker_id=0, person B gets tracker_id=1, and the next new track should get tracker_id=2. Previously, that "next ID" counter lived on the internal tracklet class, so all SORTTracker() instances shared it.

That meant resetting one tracker could accidentally affect another live tracker:

tracker_a = SORTTracker()
tracker_b = SORTTracker()

# tracker_a creates a live track with ID 0
tracker_a.update(detections_for_person_a)

# tracker_b resets the shared SORTTracklet counter back to 0
tracker_b.reset()

# tracker_a later creates another live track
# before this PR, that second live track could also get ID 0
tracker_a.update(detections_for_person_a_and_b)

This PR moves the counter to each tracker instance. tracker_b.reset() still restarts IDs from zero for tracker_b, but it no longer changes the IDs that tracker_a will allocate.

This can affect normal scripts and notebooks, not only multi-threaded applications. It is enough to have two trackers of the same type alive at once, for example in multi-camera tracking, class-specific tracking, side-by-side config comparisons, services handling multiple requests, or notebook experiments.

Type of Change

I think this is a bug fix with a small internal breaking change: BaseTracklet.count_id / Tracklet.get_next_tracker_id() were removed because tracklets no longer own ID allocation.

Testing

  • I have tested this change locally
  • I have added/updated tests for this change

Red/green TDD:

  • Added test_tracker_instances_do_not_share_id_allocators, which failed for SORT, ByteTrack, OC-SORT, and BoT-SORT with duplicate live IDs [0, 0].
  • Applied the per-instance allocator fix and reran the regression plus existing reset/tracklet contracts successfully.
  • Ran the full pytest suite and ruff checks successfully.

Checklist

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • I have commented my code where necessary, particularly in hard-to-understand areas
  • My changes generate no new warnings or errors
  • I have updated the documentation accordingly (if applicable)

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes duplicate track IDs when multiple tracker instances of the same type coexist by moving track ID allocation from global (tracklet class-level) counters to per-tracker-instance allocators.

Changes:

  • Removed BaseTracklet.count_id / get_next_tracker_id() and all tracklet subclass counters.
  • Added per-instance ID allocation helpers to BaseTracker, and updated SORT/ByteTrack/OC-SORT/BoT-SORT to use them (including on reset()).
  • Updated tests to cover the “two live instances must not share/reset each other’s IDs” regression, and removed the now-obsolete class-counter test.

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated no comments.

Show a summary per file
File Description
tests/core/test_tracklets.py Removes the test that asserted per-tracklet-subclass global counters (no longer applicable).
tests/core/test_trackers.py Adds a regression test ensuring two tracker instances don’t share ID allocation; adds a small detections helper.
src/trackers/utils/base_tracklet.py Removes the global class-variable ID counter and allocator from the base tracklet.
src/trackers/core/sort/tracklet.py Removes SORT tracklet’s class-level ID counter.
src/trackers/core/sort/tracker.py Switches SORT to per-instance ID allocation and resets allocator on init/reset.
src/trackers/core/ocsort/tracklet.py Removes class-level counter and threads an allocator callable into resolve_tracker_id().
src/trackers/core/ocsort/tracker.py Uses per-instance allocator and passes it into resolve_tracker_id(); resets allocator on init/reset.
src/trackers/core/bytetrack/tracklet.py Removes ByteTrack tracklet’s class-level ID counter.
src/trackers/core/bytetrack/tracker.py Switches ByteTrack to per-instance ID allocation and resets allocator on init/reset.
src/trackers/core/botsort/tracklet.py Removes BoT-SORT tracklet’s class-level ID counter.
src/trackers/core/botsort/tracker.py Switches BoT-SORT to per-instance ID allocation and resets allocator on init/reset.
src/trackers/core/base.py Adds _reset_id_allocator() / _allocate_tracker_id() helpers to BaseTracker.
CHANGELOG.md Documents the internal breaking change and the user-facing fix.

@AlexBodner
Copy link
Copy Markdown
Collaborator

@cdeil thanks for this PR!. @Borda this is something i wanted to do some time ago, so its great. the only problem is that it will generate conflicts with CBIou and mcbyte. So I would first merge those PRs, then extend this one to cover those trackers and finally merging this PR

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants