Skip to content

Feat/dynamic frame rate integration#446

Open
AlexBodner wants to merge 8 commits into
developfrom
feat/dynamic-frame-rate-integration
Open

Feat/dynamic frame rate integration#446
AlexBodner wants to merge 8 commits into
developfrom
feat/dynamic-frame-rate-integration

Conversation

@AlexBodner

@AlexBodner AlexBodner commented Jun 1, 2026

Copy link
Copy Markdown
Collaborator

What does this PR do?

This PR introduces Dynamic frame rate feature, which is activated by passing a timestamp to the tracker update function.

It is implemented in the State Estimator layer via an interaction with the new class KalmanMotionModel which handles the logic of swapping the motion model and noise matrices for the used model. Then, this is dispatched until tracklet predict level.

When using dynamic frame rate, we still use the linear velocity model, but we scale it by the delta time. To do the analog for the covariance matrix, it is not enough to just scale it, but we use the Discrete White Noise Acceleration Process matrix, which is the proper adjustment. This logic is implement in the ScalableProcessNoise class in src/trackers/utils/motion_models.py.

How much does this improve?

To evaluate this we tried by dropping frames trying to model network failures.

  • The most basic model is dropping frames based on independent bernoullis, so dropping a frame with probability p.
    Here we get:
Screenshot 2026-06-02 at 9 40 05 AM Screenshot 2026-06-02 at 9 33 28 AM

Then, we can model burst loss by correlating the frame drops.

  • S2 (Burst loss): Loss comes in clusters (good and bad periods), producing contiguous dropout bursts.

Here we get:
Screenshot 2026-06-02 at 9 33 42 AM

  • S3 (Burst + throttle): Same bursty loss as S2 plus a sustained mid-sequence low-frame-rate window that simulates temporary bandwidth throttling.
Screenshot 2026-06-02 at 9 39 42 AM

And the mean per dataset is:
image

We are seeing delta HOTA between using the new feature and not using it in pp. So we expect to see all positive results, but in DanceTrack static is performing up to 2pp better. This can happen due to usage of parameters tuned for static usage. Though in soccernet and sportsmot improves usage up to 6pps!

Here we see HOTA, but this gives an improvement to IDF1 and MOTA as well. Here we have a plot with ALL the results. Full is without frame dropping, to see the ceiling, dynamic is using this PRs feature, and static is tracking naively.
image

Type of Change

  • New feature (non-breaking change that adds functionality)
  • Documentation update

Testing

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

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)

Additional Context

AlexBodner and others added 2 commits May 27, 2026 10:36
…ame rate (PR1) (#438)

* feat(trackers): time-parameterized Kalman foundations for variable frame rate

Establish the Kalman filter machinery needed to advance state by an
arbitrary time step, while leaving every existing call site behaving
byte-for-byte identically.

* `KalmanFilter` gains an optional `predict(dt=1.0)` and a
  `set_motion_model_builders(F_builder, Q_builder)` opt-in. Callers that
  do not register builders ignore `dt` entirely. The first time-aware
  call with `dt = 1.0` preserves the caller-supplied reference Q so
  existing calibration survives.
* `BaseStateEstimator` declares abstract `build_F(dt)` / `build_Q(dt)` /
  `_kinematic_indices` and registers them with the underlying KF on
  construction. `set_kf_covariances(Q=...)` back-calibrates a
  per-coordinate acceleration variance `σ_a²` from the velocity diagonal
  of the supplied Q and captures any non-kinematic diagonal entries
  (e.g. XCYCSR aspect-ratio random walk) for restoration on rebuild.
* XYXY, XCYCSR, and XCYCWH state estimators each implement the
  Discrete White Noise Acceleration (DWNA) Q(dt) discretization and the
  matching constant-velocity F(dt).
* `BaseTracklet.predict` and all four concrete tracklets (SORT,
  ByteTrack, OC-SORT, BoT-SORT) accept and forward `dt=1.0`.

Public API surface unchanged. Default `dt=1.0` everywhere reproduces
previous behaviour; the full existing test suite (567 tests) passes
unmodified. Adds 19 new tests covering backward compatibility, DWNA
structure, back-calibration, and a synthetic constant-velocity
trajectory under non-uniform sampling intervals.

See `docs/design/dynamic-frame-rate.md` for the full feature spec and
the phased rollout plan (this commit lands PR 1 of 3 in the MVP spike).

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(pre_commit): 🎨 auto format pre-commit hooks

* refactor(kalman): gate dt-aware predict with flag set at registration

Replace the dual None-check on _F_builder/_Q_builder in predict() with
_motion_model_builders_enabled, flipped on in set_motion_model_builders().
The API always registers both builders atomically, so the hot path no
longer re-validates what registration already guarantees.

Co-authored-by: Cursor <cursoragent@cursor.com>

* refactor(kalman): install dt-aware sync hook at registration time

Replace builder None-checks and flags in predict() with a _sync_motion_model
callable defaulting to a no-op. set_motion_model_builders() installs the real
sync closure; predict() always calls it unconditionally.

Co-authored-by: Cursor <cursoragent@cursor.com>

* refactor(kalman): use instance state for motion-model dt cache

Replace closure/nonlocal cached_dt with self._cached_dt and a plain
_sync_motion_model method. Builders are stored on the instance at
registration time.

Co-authored-by: Cursor <cursoragent@cursor.com>

* added clarifying comment

* chore(docs): stop tracking internal dynamic-frame-rate design spec

Remove docs/design/dynamic-frame-rate.md from the repo while keeping it
local via .gitignore. Point in-repo references to the user guide instead.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
…ckers (PR2) (#439)

* feat(trackers): timestamp plumbing and time-based pruning for SORT and ByteTrack

PR 2 of the dynamic frame-rate feature series.

- BaseTracker.update() abstract signature extended with optional `timestamp`
  argument (float | None); all four concrete trackers updated accordingly.
- BaseTracker._compute_dt() helper: derives per-step dt from wall-clock
  timestamps, bootstraps to 1/frame_rate on first call, returns 1.0 in
  fixed-rate mode, emits a once-per-instance UserWarning and returns 0.0 on
  non-monotonic timestamps (predict skipped for that frame).
- BaseTracklet gains time_since_update_seconds (float), incremented by dt in
  predict() and reset to 0.0 in update(); mirroring the integer frame counter.
- SORTTracker and ByteTrackTracker: store _frame_rate, _last_timestamp,
  _dt_nonmonotonic_warned; compute dt via _compute_dt; pass it to
  tracklet.predict(dt); switch pruning to seconds-based
  (time_since_update_seconds < maximum_time_without_update) when timestamps
  are active, falling back to frame-count otherwise.
- sort/utils.py and bytetrack/utils.py: _get_alive_tracklets accepts optional
  maximum_time_without_update; switches criterion accordingly.
- OCSORTTracker and BoTSORTTracker: accept timestamp kwarg, emit a one-time
  UserWarning and ignore it (variable-rate support deferred to a later PR).
- 24 new tests covering: backward compat, _compute_dt bootstrap/non-monotonic,
  time accumulation, seconds-based pruning, and OC-SORT/BoT-SORT warn policy.
- All 721 tests pass.

Co-authored-by: Cursor <cursoragent@cursor.com>

* docs(learn): add variable frame rate guide for timestamp-based tracking

Document the optional timestamp argument on update(), supported trackers,
and fixed vs dynamic behaviour for SORT and ByteTrack.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Document dual dt convention for fixed vs dynamic frame rate.

* fix(pre_commit): 🎨 auto format pre-commit hooks

* dynamic frame rate working in the 'tidy' way + tests

* fix(pre_commit): 🎨 auto format pre-commit hooks

* Add motion model, predict timing, and lifecycle modules omitted from prior commit

* fix(pre_commit): 🎨 auto format pre-commit hooks

* apply fixes for timestamp plumbing PR.

* fixed ruff/mypy things

* Extend timestamp mode to all trackers and refactor Kalman motion models

* fix(pre_commit): 🎨 auto format pre-commit hooks

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

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 adds optional timestamp-driven “dynamic frame rate” support across the tracker stack (trackers → tracklets → state estimators → Kalman motion/noise), enabling variable time gaps between update() calls to scale Kalman prediction and to prune lost tracks using a seconds-based budget while preserving fixed-rate behavior when timestamp is omitted.

Changes:

  • Adds timestamp: float | None = None plumbing to BaseTracker.update() and concrete trackers, plus a PredictTiming helper to convert elapsed seconds into Kalman “frame-step” units.
  • Introduces a motion-model layer (KalmanMotionModel + ScalableProcessNoise) to build F(frame_step) and DWNA-scaled Q(frame_step) for gaps.
  • Adds docs + extensive unit/integration tests for timing, pruning, and motion/noise scaling.

Reviewed changes

Copilot reviewed 25 out of 26 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
CHANGELOG.md Documents the new optional timestamp= API and motion-model additions.
docs/learn/track.md Adds user-facing documentation for variable frame rate behavior and per-call timestamp mode.
src/trackers/core/base.py Centralizes timestamp bookkeeping (_predict_timing) and predict dispatch to tracklets.
src/trackers/core/sort/tracker.py Threads timestamp through SORT update flow and time-based pruning budget.
src/trackers/core/bytetrack/tracker.py Threads timestamp through ByteTrack update flow and time-based pruning budget.
src/trackers/core/ocsort/tracker.py Threads timestamp through OC-SORT update flow and adds seconds-budget pruning path.
src/trackers/core/botsort/tracker.py Threads timestamp through BoTSORT update flow and time-based pruning budget.
src/trackers/core/sort/tracklet.py Uses PredictTiming to scale predict steps and advance seconds miss clock.
src/trackers/core/bytetrack/tracklet.py Uses PredictTiming to scale predict steps and advance seconds miss clock.
src/trackers/core/ocsort/tracklet.py Uses PredictTiming to scale predict steps and advance seconds miss clock (plus ORU behavior).
src/trackers/core/botsort/tracklet.py Uses PredictTiming to scale predict steps and advance seconds miss clock.
src/trackers/utils/base_tracklet.py Adds shared seconds-based miss clock + unified “within budget” helper.
src/trackers/utils/predict_timing.py Introduces PredictTiming and FIXED_RATE_TIMING.
src/trackers/utils/motion_models.py Adds KalmanMotionModel and ScalableProcessNoise for F/Q scaling.
src/trackers/utils/state_representations.py Routes frame_step into motion application before kf.predict().
src/trackers/utils/kalman_filter.py Ensures predict/update algebra supports “no observation” updates cleanly.
tests/core/test_timestamp_plumbing.py Integration tests for timestamp timing conversion and time-based pruning behavior.
tests/core/test_tracklets.py Validates OC-SORT ORU unfreeze behavior stays fixed-rate (unit-step predicts).
tests/core/test_registration.py Updates/extends registration coverage for new API surface (diff truncated in prompt).
tests/utils/test_state_estimators.py Smoke tests for estimator predict defaults and motion-cache reset.
tests/utils/test_motion_models.py Unit tests for F scaling and DWNA Q polynomial scaling properties.
tests/utils/test_kalman_filter.py Unit tests for predict using stored matrices and update(None) semantics.

Comment thread src/trackers/core/base.py Outdated
Comment on lines +410 to +429
last = self._last_timestamp
self._last_timestamp = timestamp

if last is None:
# Bootstrap: no prior timestamp, so we cannot compute t - t_prev.
# Use one nominal frame period (1 / frame_rate) so the first Kalman
# step is frame_step=1.0 — matching fixed-rate behaviour rather than
# using the absolute timestamp value (e.g. 37.2 s would break tuning).
return 1.0 / self._frame_rate

elapsed = timestamp - last
if elapsed <= 0:
warnings.warn(
f"{type(self).__name__}: non-positive elapsed={elapsed:.6f}s from "
"non-monotonic timestamp. Skipping predict for this step.",
UserWarning,
stacklevel=3,
)
return 0.0
return elapsed

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I think its better to assume monotonicity of inputs. Wdyt @Borda ?

Comment thread src/trackers/core/base.py Outdated
Comment thread src/trackers/core/ocsort/tracker.py
@AlexBodner AlexBodner requested a review from Borda June 2, 2026 13:09
AlexBodner and others added 6 commits June 2, 2026 10:10
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Extract the full variable-frame-rate documentation into a dedicated
learn page and link to it from track.md.

Co-authored-by: Cursor <cursoragent@cursor.com>
Assert frame_step=1.0 in ORU sub-step test, restore BaseStateEstimator
noise-configuration note, and clarify monotonic timestamp requirement in
the dynamic frame rate guide.

Co-authored-by: Cursor <cursoragent@cursor.com>
@AlexBodner AlexBodner marked this pull request as ready for review June 3, 2026 16:25
@AlexBodner AlexBodner requested a review from SkalskiP as a code owner June 3, 2026 16:25
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