Skip to main content

Changelog

Rt Detr filter release notes

v0.1.20 - 2026-09-23Direct link to v0.1.20 - 2026-09-23

FixedDirect link to Fixed

  • The image now installs the native libraries OpenCV loads at import time (libxcb1, libgl1, libglib2.0-0). Without them every container died on import cv2 with libxcb.so.1: cannot open shared object file.
  • boxmot is capped below 25.0.0. That release replaced create_tracker(name, config) with create_tracker(TrackerSpec), which made the tracking path fail to import and took four tests down with it.
  • A local logs/ directory is no longer copied into the image. It was landing on /app/logs root-owned, so the rolling logger could not create /app/logs/<run> and the container died with PermissionError. Files this stage copies are owned by appuser as well.

ChangedDirect link to Changed

  • Bump the openfilter dependency to 1.4.0. This release also carries the 1.3.0 bump, which merged but was never cut into a release of this filter.

v0.1.19 - 2026-08-11Direct link to v0.1.19 - 2026-08-11

ChangedDirect link to Changed

  • The sparse-mode batching fallback now also requires track_objects. The forced-keyframe signal originates in the tracker, so with tracking off the feedback loop cannot fire and the batched path is safe; the previous condition gave up the speedup for a configuration that never had the problem.
  • _process_inference_jobs verifies the model returned one result per image before re-attaching results positionally, and drops the batch with an error if it did not. Re-attachment is positional, so a shortfall would shift results onto the wrong frames rather than merely losing the tail.

v0.1.18 - 2026-08-11Direct link to v0.1.18 - 2026-08-11

FixedDirect link to Fixed

  • Sparse mode with keyframe_trigger_new_track no longer batches. The decision for frame N+1 depends on the result of frame N (a new track born during apply forces the next frame to be a keyframe), and a batch fixes every frame's keyframe status before any apply, so batched output could diverge from unbatched. That path now falls back to per-frame processing, making the equal-output guarantee hold by construction. Dense mode, the default, is unaffected.
  • An inference result missing for a frame that requested one is applied as no detections and logged, instead of silently reusing the previous frame's detections as though the frame had been skipped.

v0.1.17 - 2026-08-11Direct link to v0.1.17 - 2026-08-11

AddedDirect link to Added

  • process_batch() override so OpenFilter's batch_size actually batches the GPU. The inherited default calls process() once per accumulated frame, so batch_size > 1 previously bought nothing but accumulation latency. A window of frames, and every stream inside each of them, now reaches _infer_batch as a single model call.

ChangedDirect link to Changed

  • _process_inference_jobs runs the model over the jobs that need it, then applies every job in original order. Applying inside the model loop let a skipped frame in the middle of a window reuse detections from before the window instead of from the frame before it; batched output is now identical to unbatched output. Sparse-mode track propagation and forced-keyframe handling both run from the same ordered apply step.

v0.1.16 - 2026-08-10Direct link to v0.1.16 - 2026-08-10

ChangedDirect link to Changed

  • Build the image on openfilter-base (weekly apt-upgraded python-slim) instead of a stale python:X.Y.Z-slim pin, clearing the OS-package CVEs the pin carried.
  • Update the openfilter dependency to 1.2.2

v0.1.15 - 2026-08-04Direct link to v0.1.15 - 2026-08-04

FixedDirect link to Fixed

  • Sparse propagation no longer freezes boxes under the default per_class=True tracker. _predict_tracks_from_tracker read tracker.active_tracks directly, but this filter builds its BoxMOT tracker with per_class=True (the tracker_per_class default, passed at filter_rt_detr/filter.py:871). Under per-class tracking BoxMOT's update() runs one class at a time and swaps active_tracks in/out per class, keeping the real aggregated state in per_class_active_tracks (dict class_id -> [tracks]). On boxmot<=18 (boxmot/trackers/basetracker.py per_class_decorator: active_tracks = per_class_active_tracks[cls_id] at L264, saved back at L273, loop over range(nr_classes)) active_tracks is left holding only the LAST iterated class after a keyframe, which is empty, so the or chain resolved to [], if not tracks fired, and every skipped frame returned the frozen static_fallback (last-keyframe boxes), defeating the whole point of sparse mode. On boxmot>=19 (boxmot/trackers/common/tracking/per_class.py _restore_class_track_collections: self.active_tracks = self.all_class_tracks("active") at L148) active_tracks is re-aggregated, so it happened to work there. New version-tolerant helper _live_tracks_from_tracker prefers the aggregated per_class_active_tracks dict when present and non-empty, and falls back to active_tracks / tracked_stracks for the boxmot>=19 aggregate and the per_class=False path (filter_rt_detr/filter.py:444-477, consumed at filter_rt_detr/filter.py:500). The existing is_activated exclusion and local_track_id dedupe are unchanged. Dense mode (detect_mode="dense", the default) is unaffected. Verified against BoxMOT source on both 18.0.0 and 19.0.0.

AddedDirect link to Added

  • Live (non-mocked, real BoxMOT) regression test test_per_class_propagation_moves_boxes_not_static_fallback (tests/test_sparse_integration.py:195). It builds the tracker through the filter's own production factory (_get_boxmot_tracker, per_class=True), seeds a moving detection over several keyframes, seeds the static fallback with a FROZEN clone of the last keyframe box, and asserts the propagated boxes are the live motion-advanced tracks, NOT the frozen fallback. The prior integration tests missed the finding because they build the tracker with per_class=False. Verified fail-before / pass-after: pre-fix the test FAILS on boxmot 18.0.0 ([[124.0, 100.0, 224.0, 200.0]] == [[124.0, 100.0, 224.0, 200.0]], i.e. the frozen fallback) and PASSES on boxmot 19.0.0; post-fix the full suite is green on both (49 passed, integration tests run not skipped).

ChangedDirect link to Changed

  • Documented the sparse keyframe-vs-frame retention-horizon interaction (docs-only; no behavior change). Advancing motion on skipped frames (multi_predict/predict) moves each track's Kalman mean but does not increment the tracker's frame_count; only update() does, once per KEYFRAME in sparse mode. BoxMOT prunes a lost track when frame_count - track.end_frame > max_time_lost (max_time_lost = int(frame_rate/30 * track_buffer), track_buffer default 30 for ByteTrack; confirmed in boxmot/trackers/bytetrack/bytetrack.py:243-244,269,377 on 18 and boxmot/trackers/bbox/bytetrack.py:61-62,88,223 on 19). So a lost track survives ~track_buffer KEYFRAMES, i.e. ~track_buffer * keyframe_heartbeat REAL frames (~240 at the default heartbeat=8) instead of ~30, roughly 8x the dense-mode coasting/re-association window. Documented at the propagation site (filter_rt_detr/filter.py:597-617, _advance_tracker_motion docstring). track_buffer is intentionally NOT rescaled by the heartbeat: it is shared with dense mode and interacts with frame_rate, so scaling it would change re-association behavior; left for review rather than guessed.

v0.1.14 - 2026-08-04Direct link to v0.1.14 - 2026-08-04

TestedDirect link to Tested

  • Covered the is_activated is False -> skip exclusion branch in _predict_tracks_from_tracker (filter_rt_detr/filter.py:507-508). The v0.1.13 fix gates propagated output on is_activated alone, but the mocked sparse tests used a _MockTrack that never set is_activated, so they always hit the getattr(track, "is_activated", True) default (True) and never exercised the exclusion path that stops an unconfirmed, just-born track (tracklet_len == 0, is_activated == False) from leaking into a propagated frame when keyframe_trigger_new_track is off. New unit test test_sparse_unactivated_track_excluded_from_propagation builds a mocked tracker holding one is_activated=True track (positive control) and one is_activated=False track, calls _predict_tracks_from_tracker on a skipped frame, and asserts the activated track's box IS emitted while the is_activated=False track's box is NOT (tests/test_filter_rt_detr.py). Verified it fails if the is_activated filter is removed. Full suite green: 48 passed (including the 3 real-boxmot integration tests, boxmot 19.0.0).

v0.1.13 - 2026-08-04Direct link to v0.1.13 - 2026-08-04

FixedDirect link to Fixed

  • Sparse propagation no longer drops just-confirmed tracks. _predict_tracks_from_tracker gated propagated output on a tracker_min_hits / tracklet_len >= 3 threshold, but min_hits/tracklet_len is a SORT-family confirmation concept ByteTrack never reads: ByteTrack's own update() emits a track the moment is_activated flips true, which happens at tracklet_len == 1. Under the old gate a track already visible in the keyframe output was silently dropped from every propagated frame until it accumulated two more real detector matches, exactly the newly-confirmed / high-churn tracks sparse mode targets. Propagation now gates on is_activated alone (keeping the existing conf_threshold / output_tracked_low_conf / output_coasting_tracks handling and the local_track_id dedupe), matching ByteTrack's actual output contract (filter_rt_detr/filter.py:496-548). The now-dead _track_hit_count helper was removed; tracker_min_hits remains only where the tracker constructor consumes it for SORT-family trackers (filter_rt_detr/filter.py:851).
  • Test gap closed. tests/test_sparse_integration.py seeded 5 real tracker.update() calls before the first propagation assertion, pushing tracklet_len to 4 (above the old min_hits=3), so it never exercised the tracklet_len 1-2 window and could not catch the drop. New test_just_confirmed_track_is_not_dropped_on_propagation seeds a SINGLE real detector update (tracklet_len == 1, is_activated True) and asserts the next propagated frame EMITS that track's box (tests/test_sparse_integration.py).

v0.1.12 - 2026-08-04Direct link to v0.1.12 - 2026-08-04

AddedDirect link to Added

  • Sparse detection + track-propagation mode (default OFF; dense behavior unchanged). When detect_mode="sparse", the detector runs only on keyframes and boxes are propagated forward from the live BoxMOT tracker's motion model on the frames in between, cutting detector calls without freezing boxes.
    • New knobs in normalize_config: detect_mode (dense|sparse), keyframe_heartbeat (max frames between forced detections), keyframe_trigger_new_track (force a keyframe right after a new track is born) (filter_rt_detr/filter.py:90-94).
    • New per-stream keyframe state initialized in setup and cleared in shutdown: _last_keyframe_index, _force_keyframe, _new_track_born (filter_rt_detr/filter.py:198-203, filter_rt_detr/filter.py:279-281).
    • process consults the keyframe scheduler only in sparse mode (dense keeps the _advance_frame_state modulo untouched), and skipped frames propagate the tracker's tracks instead of statically cloning the last detections (filter_rt_detr/filter.py:301-302, filter_rt_detr/filter.py:312-322).
    • New sparse helpers _sparse_mode / _should_run_keyframe / _maybe_force_next_keyframe / _predict_tracks_from_tracker / _advance_tracker_motion / _track_predicted_xyxy / _coerce_xyxy / _tlwh_to_xyxy (filter_rt_detr/filter.py:394-577). The propagation adapter reaches BoxMOT internal track state via getattr-guarded, version-tolerant access and fails open to a static clone of the last detections.
    • New-track trigger: the _public_local_track_id mint branch flags a newly born track (filter_rt_detr/filter.py:1042), consumed after the keyframe's tracking pass to force the next frame to be a keyframe (filter_rt_detr/filter.py:361).

FixedDirect link to Fixed

  • Sparse track-propagation blockers found by a live integration run against real BoxMOT (boxmot>=18, resolves to the pinned 22.x):
    • Stale tracker-factory import made tracking dead code. boxmot.trackers.tracker_zoo was removed in boxmot>=18; the factory moved to boxmot.trackers.registry. The import now tries boxmot.trackers.registry first and falls back to boxmot.trackers.tracker_zoo (older boxmot) per symbol, so _boxmot_create_tracker / _boxmot_get_tracker_config resolve on both while a genuinely-missing BoxMOT still fails open (filter_rt_detr/filter.py:30-53).
    • Propagated (skipped-frame) output is now filtered like keyframes. _predict_tracks_from_tracker previously emitted every active_track (including tentative/low-hit/low-conf) and could repeat a local_track_id within one frame. It now mirrors the keyframe output contract from _merge_boxmot_tracks_with_detections (conf_threshold / output_tracked_low_conf / output_coasting_tracks, plus is_activated and a tracker_min_hits gate) and dedupes to at most one box per local_track_id per frame, keeping the highest-conf box (filter_rt_detr/filter.py:476-559). New version-tolerant _track_hit_count helper reads the track hit/streak counter and fails open when the attribute is unreadable (filter_rt_detr/filter.py:601-617).
    • New real (non-mocked) integration test exercises the resolved factory and live Kalman motion: asserts _boxmot_create_tracker is not None, instantiates a real bytetrack tracker, and asserts _predict_tracks_from_tracker returns boxes that MOVE across frames with stable, non-duplicated local_track_ids. Guarded with @unittest.skipUnless(<boxmot importable>) so the mocked unit suite still passes where BoxMOT is absent, and it would have failed on the stale import where BoxMOT is present (tests/test_sparse_integration.py).

v0.1.11 - 2026-08-04Direct link to v0.1.11 - 2026-08-04

ChangedDirect link to Changed

  • Update openfilter[all] to >=1.2.1
  • Pin opencv-python-headless to 5.0.0.93 to match openfilter 1.2.0 (OpenCV 4→5)
  • Pin the Docker base to python:3.11.12-slim.
  • Refresh the docker-compose.yaml openfilter utility image tags to 1.2.1 and pin the filter's own image to the release version.

v0.1.10 - 2026-08-03Direct link to v0.1.10 - 2026-08-03

ChangedDirect link to Changed

  • Correct README to match implemented behavior (docs-only, no code change):
    • temperature documented as a logit/sigmoid transform (sigmoid(logit(score) / temperature)) on per-class scores, not "on logits before softmax". RT-DETR post-processing yields independent per-class sigmoid scores, so there is no softmax (filter_rt_detr/filter.py:463-467).

v0.1.9 - 2026-08-01Direct link to v0.1.9 - 2026-08-01

ChangedDirect link to Changed

  • Correct README to match implemented behavior (docs-only, no code change):
    • input_size documented as a square resize to input_size × input_size (aspect ratio not preserved), not a longest-side resize.
    • local_track_id documented as present only for detections matched to an active tracker track (absent for track_classes-excluded detections, no-row frames, newly seen objects, and the tracker fail-open path).
    • compute_velocity and debug_scores marked reserved / not yet implemented (accepted but inert; no velocity emitted, no percentiles printed).
    • label_map_path documented as read only when model_path is a single weights file; folders / hub models use label_map / class_names or the model's own id2label.
    • tracker_max_age / tracker_min_hits / tracker_iou_threshold marked as legacy knobs mapped only when the active tracker exposes the key (no-op under the default bytetrack).
    • Run examples that pass FILTER_* overrides point at make run (reads env) instead of make run-image (runs the committed compose with literal values).
    • docker-compose.yaml described as a multi-camera reference stack pinned to a published image, not what make build-image produces.

v0.1.8 - 2026-08-01Direct link to v0.1.8 - 2026-08-01

ChangedDirect link to Changed

  • Clarify README: document the filter's generic trained-detector role, the frame.data["tracks"] output contract and frame metadata, a generic run/usage flow (pointing at any model bundle and label map), and a full parameters table with defaults read from normalize_config. Docs-only, no behavior change.

v0.1.7 - 2026-05-11Direct link to v0.1.7 - 2026-05-11

AddedDirect link to Added

  • Add new tracking algorithms and optimization

v0.1.6 - 2026-04-27Direct link to v0.1.6 - 2026-04-27

AddedDirect link to Added

  • Add QUICKSTART.md with Docker Compose and local script examples
  • Add .env.example with all FILTER_* parameters documented

v0.1.5 - 2026-04-24Direct link to v0.1.5 - 2026-04-24

FixedDirect link to Fixed

  • Restore RELEASE.md heading format

v0.1.4 - 2026-04-23Direct link to v0.1.4 - 2026-04-23

ChangedDirect link to Changed

  • Bump openfilter SDK, align CI workflow with shared release gate (source-paths)

  • Bump openfilter dependency to >=0.1.30.

v0.1.3 - 2026-04-20Direct link to v0.1.3 - 2026-04-20

ChangedDirect link to Changed

  • Simplify filter.mk build-image (plain docker build, DOCKER_TAG)

v0.1.2 - 2026-04-17Direct link to v0.1.2 - 2026-04-17

ChangedDirect link to Changed

  • Add create-release.yaml workflow for GAR premium publishing (push + PR + workflow_dispatch)
  • Remove old ci.yaml (shared premium workflow replaces it, removes JFROG secrets)
  • Add security-scan.yaml using shared workflow
  • Migrate Dockerfile from filter_base to python:3.11-slim source install
  • Bump openfilter dependency to >=0.1.27
  • Update Makefile IMAGE to premium-filters/filter-rt-detr
  • Add .dockerignore

v0.1.1 - 2025-04-17Direct link to v0.1.1 - 2025-04-17

AddedDirect link to Added

  • Updating filter for tracking

v0.1.0 - 2025-02-26Direct link to v0.1.0 - 2025-02-26

AddedDirect link to Added

  • Initial Release: new Rt Detr filter