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 onimport cv2withlibxcb.so.1: cannot open shared object file. boxmotis capped below 25.0.0. That release replacedcreate_tracker(name, config)withcreate_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/logsroot-owned, so the rolling logger could not create/app/logs/<run>and the container died with PermissionError. Files this stage copies are owned byappuseras 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_jobsverifies 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_trackno 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'sbatch_sizeactually batches the GPU. The inherited default callsprocess()once per accumulated frame, sobatch_size > 1previously bought nothing but accumulation latency. A window of frames, and every stream inside each of them, now reaches_infer_batchas a single model call.
ChangedDirect link to Changed
_process_inference_jobsruns 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 stalepython:X.Y.Z-slimpin, 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=Truetracker._predict_tracks_from_trackerreadtracker.active_tracksdirectly, but this filter builds its BoxMOT tracker withper_class=True(thetracker_per_classdefault, passed atfilter_rt_detr/filter.py:871). Under per-class tracking BoxMOT'supdate()runs one class at a time and swapsactive_tracksin/out per class, keeping the real aggregated state inper_class_active_tracks(dictclass_id -> [tracks]). On boxmot<=18 (boxmot/trackers/basetracker.pyper_class_decorator:active_tracks = per_class_active_tracks[cls_id]at L264, saved back at L273, loop overrange(nr_classes))active_tracksis left holding only the LAST iterated class after a keyframe, which is empty, so theorchain resolved to[],if not tracksfired, and every skipped frame returned the frozenstatic_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_tracksis re-aggregated, so it happened to work there. New version-tolerant helper_live_tracks_from_trackerprefers the aggregatedper_class_active_tracksdict when present and non-empty, and falls back toactive_tracks/tracked_stracksfor the boxmot>=19 aggregate and theper_class=Falsepath (filter_rt_detr/filter.py:444-477, consumed atfilter_rt_detr/filter.py:500). The existingis_activatedexclusion andlocal_track_iddedupe 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 withper_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'sframe_count; onlyupdate()does, once per KEYFRAME in sparse mode. BoxMOT prunes a lost track whenframe_count - track.end_frame > max_time_lost(max_time_lost = int(frame_rate/30 * track_buffer),track_bufferdefault 30 for ByteTrack; confirmed inboxmot/trackers/bytetrack/bytetrack.py:243-244,269,377on 18 andboxmot/trackers/bbox/bytetrack.py:61-62,88,223on 19). So a lost track survives ~track_bufferKEYFRAMES, i.e. ~track_buffer * keyframe_heartbeatREAL 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_motiondocstring).track_bufferis intentionally NOT rescaled by the heartbeat: it is shared with dense mode and interacts withframe_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 -> skipexclusion branch in_predict_tracks_from_tracker(filter_rt_detr/filter.py:507-508). The v0.1.13 fix gates propagated output onis_activatedalone, but the mocked sparse tests used a_MockTrackthat never setis_activated, so they always hit thegetattr(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 whenkeyframe_trigger_new_trackis off. New unit testtest_sparse_unactivated_track_excluded_from_propagationbuilds a mocked tracker holding oneis_activated=Truetrack (positive control) and oneis_activated=Falsetrack, calls_predict_tracks_from_trackeron a skipped frame, and asserts the activated track's box IS emitted while theis_activated=Falsetrack's box is NOT (tests/test_filter_rt_detr.py). Verified it fails if theis_activatedfilter 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_trackergated propagated output on atracker_min_hits/tracklet_len >= 3threshold, butmin_hits/tracklet_lenis a SORT-family confirmation concept ByteTrack never reads: ByteTrack's ownupdate()emits a track the momentis_activatedflips true, which happens attracklet_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 onis_activatedalone (keeping the existingconf_threshold/output_tracked_low_conf/output_coasting_trackshandling and thelocal_track_iddedupe), matching ByteTrack's actual output contract (filter_rt_detr/filter.py:496-548). The now-dead_track_hit_counthelper was removed;tracker_min_hitsremains only where the tracker constructor consumes it for SORT-family trackers (filter_rt_detr/filter.py:851). - Test gap closed.
tests/test_sparse_integration.pyseeded 5 realtracker.update()calls before the first propagation assertion, pushingtracklet_lento 4 (above the oldmin_hits=3), so it never exercised thetracklet_len1-2 window and could not catch the drop. Newtest_just_confirmed_track_is_not_dropped_on_propagationseeds a SINGLE real detector update (tracklet_len == 1,is_activatedTrue) 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
setupand cleared inshutdown:_last_keyframe_index,_force_keyframe,_new_track_born(filter_rt_detr/filter.py:198-203,filter_rt_detr/filter.py:279-281). processconsults the keyframe scheduler only in sparse mode (dense keeps the_advance_frame_statemodulo 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_idmint 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).
- New knobs in
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_zoowas removed in boxmot>=18; the factory moved toboxmot.trackers.registry. The import now triesboxmot.trackers.registryfirst and falls back toboxmot.trackers.tracker_zoo(older boxmot) per symbol, so_boxmot_create_tracker/_boxmot_get_tracker_configresolve 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_trackerpreviously emitted everyactive_track(including tentative/low-hit/low-conf) and could repeat alocal_track_idwithin one frame. It now mirrors the keyframe output contract from_merge_boxmot_tracks_with_detections(conf_threshold/output_tracked_low_conf/output_coasting_tracks, plusis_activatedand atracker_min_hitsgate) and dedupes to at most one box perlocal_track_idper frame, keeping the highest-conf box (filter_rt_detr/filter.py:476-559). New version-tolerant_track_hit_counthelper 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_trackerreturns boxes that MOVE across frames with stable, non-duplicatedlocal_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).
- Stale tracker-factory import made tracking dead code.
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.yamlopenfilter utility image tags to1.2.1and 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):
temperaturedocumented 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_sizedocumented as a square resize toinput_size×input_size(aspect ratio not preserved), not a longest-side resize.local_track_iddocumented as present only for detections matched to an active tracker track (absent fortrack_classes-excluded detections, no-row frames, newly seen objects, and the tracker fail-open path).compute_velocityanddebug_scoresmarked reserved / not yet implemented (accepted but inert; no velocity emitted, no percentiles printed).label_map_pathdocumented as read only whenmodel_pathis a single weights file; folders / hub models uselabel_map/class_namesor the model's ownid2label.tracker_max_age/tracker_min_hits/tracker_iou_thresholdmarked as legacy knobs mapped only when the active tracker exposes the key (no-op under the defaultbytetrack).- Run examples that pass
FILTER_*overrides point atmake run(reads env) instead ofmake run-image(runs the committed compose with literal values). docker-compose.yamldescribed as a multi-camera reference stack pinned to a published image, not whatmake build-imageproduces.
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 fromnormalize_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