Skip to content

releasing 2.4.0#394

Merged
Borda merged 1 commit into
developfrom
releasing/2.4
May 6, 2026
Merged

releasing 2.4.0#394
Borda merged 1 commit into
developfrom
releasing/2.4

Conversation

@Borda
Copy link
Copy Markdown
Member

@Borda Borda commented May 6, 2026

v2.4.0: BoT-SORT & hyperparameter tuning

Summary

trackers 2.4.0 ships the fourth tracker — BoT-SORT, built around camera motion compensation (CMC) — alongside a first-class hyperparameter tuning workflow, a unified Kalman implementation shared by all trackers, and a tracked_objects property on every tracker that exposes alive tracks even through occlusions.

All existing tracker.update(detections) calls continue to work without changes. One true breaking change: SORTTracker.update() no longer assigns tracker_id on the input object — read it from the returned value. Spawn order is now deterministic. See the Migration guide and Behaviour shifts below.

Spotlights

BoT-SORT tracker

BoT-SORT is designed for footage where the camera itself moves — handheld, drone, sport. Camera motion compensation (CMC) is what makes BoT-SORT distinct: it estimates the inter-frame homography and corrects Kalman predictions before association, so stationary subjects stay stationary in track coordinates even when the camera pans. CMC is on by default (enable_cmc=True); set enable_cmc=False only when the camera is fixed, though at that point BoT-SORT reduces to a ByteTrack variant with a different Kalman state estimator. Four CMC backends: sparseOptFlow (default), orb, sift, ecc. (#386)

Algorithm MOT17 HOTA SportsMOT HOTA SoccerNet HOTA DanceTrack val HOTA
SORT 58.4 70.8 81.6 45.0
ByteTrack 60.1 73.0 84.0 50.2
OC-SORT 61.9 71.7 78.4 51.8
BoT-SORT 63.7 73.8 84.5 50.5

MOT17 and SportsMOT use YOLOX detections. SoccerNet and DanceTrack use oracle (ground-truth) detections.

Note: Pass the raw video frame as the second argument to BoTSORTTracker.update() when CMC is enabled (the default). Without it, CMC silently no-ops and Kalman predictions may diverge. Existing calls to SORT, ByteTrack, and OC-SORT without frame are unaffected.

from trackers import BoTSORTTracker

tracker = BoTSORTTracker(
    track_activation_threshold=0.7,
    high_conf_det_threshold=0.6,
    enable_cmc=True,
    cmc_method="sparseOptFlow",  # "orb" | "sift" | "sparseOptFlow" | "ecc"
)

for frame in frames:
    detections = model(frame)
    tracked = tracker.update(detections, frame)  # frame required when enable_cmc=True

CLI: trackers track --tracker botsort ... — discoverable automatically.

Hyperparameter tuning

Each tracker now declares a search_space ClassVar. Tuner uses Optuna — a Bayesian hyperparameter optimisation framework — to sample from that space, runs the tracker over MOT sequences with pre-computed detections, evaluates with HOTA / MOTA / IDF1, and returns the best parameter set. (#301, #374)

from trackers.tune import Tuner

tuner = Tuner(
    tracker_id="bytetrack",
    gt_dir="data/gt/",
    detections_dir="data/det/",
    objective="HOTA",
    n_trials=100,
)
best_params = tuner.run()
trackers tune --tracker bytetrack \
    --gt-dir data/gt/ --detections-dir data/det/ \
    --objective HOTA --n-trials 200

tracked_objects — every confirmed track, including between detections

tracker.update(detections) returns only the tracks matched to a detection on the current frame. The new tracked_objects property returns every confirmed track still alive in the buffer, including those not matched on this frame — subjects temporarily occluded or skipped by the detector. Boxes come from the Kalman state (get_state_bbox()), not from a detection, so position may drift slightly for unmatched tracks.

Tracks stay in the result until time_since_update exceeds lost_track_buffer frames (scaled by frame_rate). After that they are pruned and will not appear in tracked_objects.

confidence and class_id are None on this result — sv.LabelAnnotator and other annotators that read those fields will raise TypeError unless guarded. (#373)

tracked = tracker.update(detections, frame)  # matched this frame only
alive = tracker.tracked_objects  # all confirmed alive tracks

# guard before using supervision annotators:
if alive.class_id is not None:
    frame = label_annotator.annotate(frame, alive, ...)

Migration guide

SORTTracker.update() no longer assigns tracker_id on the input object

SORTTracker previously wrote tracker_id directly onto the caller's sv.Detections object and returned that same instance. It now returns a fresh indexed copy — matching ByteTrack and OC-SORT. Code that read detections.tracker_id after calling tracker.update(detections) without using the return value will now get None. (#360)

# Before (v2.3.0) — tracker_id written back to input
tracker.update(detections)
print(detections.tracker_id)  # worked (aliased)

# After (v2.4.0) — read from returned value
tracked = tracker.update(detections)
print(tracked.tracker_id)

Behaviour shifts

These changes affect reproducibility or numerical outputs but do not break correct usage of the public API.

Spawn order is now deterministic

Track IDs assigned to detections that spawn in the same frame are now sorted rather than dependent on CPython set iteration order — reproducible across machines and Python versions for the first time. One-time impact: re-record any raw ID baselines compared against v2.3.0. (#361)

Notable changes

🚀 Added

  • BoT-SORT tracker (BoTSORTTracker) — camera-motion-aware tracker with CMC on by default (enable_cmc=True), configurable backends (sparseOptFlow, orb, sift, ecc), and three-stage score-fused association. (Feature: adding BoT-SORT tracker #386)
  • tracked_objects property on BaseTracker — returns every confirmed alive track (within lost_track_buffer) with Kalman-predicted boxes, not just tracks matched on the current frame. confidence and class_id are None; guard before using supervision annotators. (Return all alive tracks #373)
  • Tuner class (trackers.tune.Tuner) — Optuna-based hyperparameter optimisation with HOTA / MOTA / IDF1 objectives over MOT ground-truth and pre-computed detections. (Add Tuner class  #301)
  • trackers tune CLI subcommand — wires Tuner into the CLI. (Add tune cli #374)
  • load_mot_file is now public — exported from trackers.io.mot for custom evaluation and tuning scripts. (Add Tuner class  #301, Add tune cli #374)
  • frame parameter on BaseTracker.update()update(detections, frame=None). Required by BoT-SORT with CMC enabled; SORT/ByteTrack/OC-SORT emit UserWarning if frame is provided but not used. (Feature: adding BoT-SORT tracker #386)
  • Swappable Kalman state estimatorsBaseStateEstimator, XCYCWHStateEstimator, XCYCSRStateEstimator, XYXYStateEstimator in trackers.utils.state_representations. (Feat/core/refactor tracklet kalman filter #310)
  • search_space ClassVar on every tracker — declarative hyperparameter spaces consumed by Tuner. (Add Tuner class  #301)
  • TrackletProtocol structural type — formalises the tracklet contract used by BaseTracker.tracks.
  • Modern Python 3.10+ type hints across the public surface. (add missing and modern typehints #302)

⚠️ Impactful Changes

  • SORTTracker.update() no longer assigns tracker_id on the input object — previously wrote tracker_id onto the caller's sv.Detections in-place; now returns a fresh indexed copy. Code reading detections.tracker_id without using the return value will get None. (Avoid input mutation in SortTracker.update #360)

🌱 Changed

🔧 Fixed


🏆 Contributors

This release is brought to you by five community contributors and the maintainer team.

  • Tomasz Stańczyk (@tstanczyk95) (LinkedIn) — original BoT-SORT implementation and supervision integration
  • Alexander Bodner (@AlexBodner) (LinkedIn) — BoT-SORT refactoring, Kalman filter unification, OC-SORT tracklet adaptation
  • Omkar Kabde (@omkar-334) (LinkedIn) — Tuner class, tune CLI, tracked_objects, ByteTrack fix, type hints
  • Christoph Deil (@cdeil) — deterministic spawn order, SORT input-mutation fix
  • Piotr Skalski (@SkalskiP) (LinkedIn) — BoT-SORT branch integration, docs rewrite, comparison page expansion
  • Jirka Borovec (@Borda) (LinkedIn) — release engineering, tooling, docs infrastructure

Automated contributions: @dependabot, @pre-commit-ci, @copilot-swe-agent


Full changelog: 2.3.0...2.4.0

@Borda Borda force-pushed the releasing/2.4 branch from cc5e6b6 to 3922587 Compare May 6, 2026 19:32
@Borda Borda marked this pull request as ready for review May 6, 2026 20:43
@Borda Borda requested a review from SkalskiP as a code owner May 6, 2026 20:43
Copilot AI review requested due to automatic review settings May 6, 2026 20:43
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

Finalizes the trackers package release by promoting the project version from the 2.4.0.rc0 release candidate to the stable 2.4.0 in the packaging metadata.

Changes:

  • Update pyproject.toml project version to 2.4.0 for the stable release.

@Borda Borda requested review from AlexBodner and tstanczyk95 May 6, 2026 20:53
@Borda Borda merged commit 0796b74 into develop May 6, 2026
21 checks passed
@Borda Borda deleted the releasing/2.4 branch May 6, 2026 21:01
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.

2 participants