v1.9.4: Export & Aug Correctness Fixes
RF-DETR 1.9.4 is a maintenance release: no public API removed or renamed, no new public API — the new arguments below sit on private ONNX/TFLite reference helpers. It fixes seven bugs — two in export (ONNX/TFLite background-logit-slot assumption, a keypoint tensor misidentified as a segmentation mask), two silent annotation-corruption bugs in augmentation (keypoint flip with empty flip-pairs, Albumentations TimeReverse/SquareSymmetry), one training-correctness bug (BestModelCallback scoring PyTorch Lightning's sanity-check pass as a real epoch), one non-square training resize bug, and one environment-dependent TFLite export failure.
Users exporting sparse-ID COCO checkpoints — including the official pretrained weights — or legacy background-first keypoint checkpoints, training keypoint models with the pydantic-default empty keypoint_flip_pairs, using custom Albumentations TimeReverse/SquareSymmetry configs, starting a new training run seeded from pretrain_weights, or training non-square models benefit most from this release. See the migration guide below — three behavior changes may need action on upgrade.
✨ Spotlights / highlights
Export: configurable background logit slot (#1397)
The ONNX/TFLite reference inference helpers assumed the background class always sits at the final logit index. Sparse-ID COCO checkpoints and legacy background-first keypoint checkpoints decoded wrong. A new background_class_id argument on the private _run_inference helpers makes the assumption explicit — -1 (default) preserves current behavior, None keeps every logit slot, 0 supports legacy background-first keypoint checkpoints. The -1 default is kept for backward compatibility; it still mis-decodes the checkpoints named above, including the official pretrained COCO weights (a real foreground category occupies the final slot there) — pass background_class_id=None explicitly for those. The same commit also replaces the TFLite helper's old guess-any-lone-rank-4-output-is-a-mask behavior with a new rank4_output argument, defaulting to None — a keypoint export's pred_keypoints tensor could previously get silently upsampled into Detections.mask.
Keypoint annotations no longer silently corrupted on flip (#1358)
RandomHorizontalFlip on the torchvision-native backend mirrored keypoint x-coordinates on every drawn flip, but only relabeled left/right joints if self.keypoint_flip_pairs:. With an empty list — the pydantic default — training samples got keypoints mirrored in position while keeping their original left/right label, with no warning. The flip is now dropped entirely for an empty-but-not-None keypoint_flip_pairs, matching the Albumentations backend's existing safety contract.
Albumentations TimeReverse/SquareSymmetry box and keypoint handling (#1386)
Custom Albumentations configs using TimeReverse had it treated as pixel-only — it flipped images while leaving boxes and keypoints unchanged. It now shares the same geometric-transform and replay-based keypoint handling as HorizontalFlip, and the no-pairs safety filter now also covers SquareSymmetry.
BestModelCallback no longer scores the PTL sanity check as a real epoch (#1357)
A positive validation score from PyTorch Lightning's pre-training sanity check — common when starting a new run initialized with pretrain_weights from a checkpoint pretrained on a different dataset — could get written out as the permanent "best" EMA checkpoint before a single real epoch ran, after which real training could never surpass it. EMA tracking now honors the same trainer.sanity_checking guard the regular checkpoint path already had. (This is distinct from PTL's own resume/ckpt_path restart, which PTL itself skips the sanity check for — resumed runs were never affected.)
🔄 Migration guide
No public API was removed or renamed. Three behavior changes may need action on upgrade:
- TFLite segmentation inference. The
_run_inferencereference helper no longer treats an anonymous rank-4 output as a mask. Passrank4_output="masks"for a name-stripped segmentation export. - Keypoint training with
keypoint_flip_pairs=[](the pydantic default). The default horizontal flip is now disabled instead of applied without relabeling. Provide left/right pairs to keep the augmentation. - Non-square training. The crop branch no longer resamples through a fixed
384x384intermediate, so the augmented pixel distribution differs from 1.9.3. Square training (the default for every shipped model config) is unchanged.
📝 Notable changes
🔧 Fixed
- ONNX and TFLite reference inference helpers now accept an explicit
background_class_id; a newrank4_outputargument replaces the old guess-any-lone-rank-4-output-is-a-mask behavior and defaults toNone. (#1397) - Non-square training resize no longer double-resamples crop-branch outputs through a fixed
384x384intermediate. (#1383) - Custom Albumentations
TimeReverseno longer leaves boxes and keypoints unflipped while the image flips; the no-pairs keypoint safety filter now also coversSquareSymmetry. (#1386) - TFLite export no longer fails when
onnx2tfcan't resolveonnxsimfrom a non-activated virtualenv; RF-DETR now temporarily adds the running interpreter's script directory toPATHduring conversion. (#1366, fixes #1365) - Torchvision-native training no longer silently mirrors keypoint positions without relabeling left/right joints when
keypoint_flip_pairs=[]. (#1358) BestModelCallbackno longer treats PyTorch Lightning's sanity-check pass as a real epoch's result. (#1357, fixes #1348)
🏆 Contributors
- Jesús Royeth (@JESUSROYETH) — export background-logit-slot fix, keypoint flip-pair safety fix, Albumentations
TimeReverse/SquareSymmetryfix,BestModelCallbacksanity-check fix. - Aman Harsh (@amanharshx, LinkedIn) — TFLite export
onnxsim-on-PATHfix. - jirka (@Borda, LinkedIn) — non-square training resize double-resample fix.
Full changelog: 1.9.3...1.9.4