Visitar URL original
Fix Faster R-CNN detector inference crashing on box_score_thresh: null by rysk-t · Pull Request #3470 · DeepLabCut/DeepLabCut · GitHub
Skip to content

Fix Faster R-CNN detector inference crashing on box_score_thresh: null - #3470

Merged
deruyter92 merged 14 commits into
DeepLabCut:devfrom
rysk-t:detector-bugfixes
Sep 3, 2026
Merged

deruyter92 merged 14 commits into
DeepLabCut:devfrom
rysk-t:detector-bugfixes

Conversation

@rysk-t

@rysk-t rysk-t commented Aug 30, 2026

Copy link
Copy Markdown
Contributor

Hello! This is my first PR here — thank you for DeepLabCut and its important contributions to the scientific community.

Faster R-CNN detectors trained through DeepLabCut cannot run inference.
Generated pytorch_config.yaml files store box_score_thresh: null in the
detector model section (DetectorModelConfig.box_score_thresh defaults to
None since the structured-config migration in #3198, first released in
v3.0.1), and DETECTORS.build forwards that None verbatim, overriding the
constructor default. torchvision's Faster R-CNN then keeps
score_thresh=None and every inference call crashes in
postprocess_detections:

TypeError: '>' not supported between instances of 'Tensor' and 'NoneType'

The crash hits every eval-mode forward, on every device and OS: inference
with a trained detector, and also the validation pass that detector
training runs every eval_interval epochs (10 by default) — so a
default-length Faster R-CNN detector training run crashes at its first
validation epoch. Training-mode forwards are unaffected (score_thresh is
only used in postprocess_detections). The default ssdlite detector is
not affected (torchvision's SSD ignores this kwarg entirely; its own
parameter is score_thresh), and the SuperAnimal model configs pin an
explicit box_score_thresh: 0.6 — which is why the bug only hits projects
that explicitly select a Faster R-CNN detector_type, and went unnoticed
on the default paths.

Reproduction:

from deeplabcut import Engine
deeplabcut.create_training_dataset(config, net_type="top_down_resnet_50",
                                   detector_type="fasterrcnn_resnet50_fpn_v2",
                                   engine=Engine.PYTORCH)
deeplabcut.train_network(config, detector_epochs=1, epochs=1)
deeplabcut.analyze_videos(config, [video])  # -> TypeError

Fix: treat box_score_thresh=None as "use the default" (0.01) in the shared
TorchvisionDetectorAdaptor, which also heals existing on-disk configs that
already contain null — no config regeneration needed. Behavior for any
non-None value is unchanged, and SSDLite construction is unchanged (we
verified torchvision's SSD resolves to the same score_thresh either way).
The code that reads the config value for plotting cutoffs
(evaluate_snapshot, create_labeled_video, via ... or 0.6) reads the
YAML, not the model instance, and is unaffected. Subclass signatures are
updated to float | None to match.

A unit test covers the None and explicit-value cases
(tests/pose_estimation_pytorch/models/test_detectors.py).

Related: #2642 is a sibling dtype/config issue in the same detector target
path (int32 labels where torchvision expects int64).

Testing

  • New unit test: tests/pose_estimation_pytorch/models/test_detectors.py
  • pytest tests/pose_estimation_pytorch: 1011 passed, 29 skipped
    (macOS arm64, Python 3.12, torch 2.12.1 / torchvision 0.27.1)
  • End-to-end: a Faster R-CNN detector trained on a real multi-animal project
    reproduces the crash on CPU with the generated config, and runs correctly
    with this fix
  • pre-commit run (manual stage) and ruff check / ruff format --check: clean
  • The change was independently double-checked by two AI code reviewers
    (Anthropic Claude Fable 5 and OpenAI Codex 5.6) in addition to the test
    suite

Generated pytorch configs store box_score_thresh: null in the detector
model section. DETECTORS.build forwards that None verbatim, overriding
the class default, and torchvision's Faster R-CNN then crashes in
postprocess_detections ('>' between Tensor and NoneType) on every
device — Faster R-CNN detectors trained through DeepLabCut could not
run inference at all. Coerce None to the 0.01 default in the shared
torchvision adaptor; SSD ignores the kwarg entirely (its own parameter
is score_thresh), so SSDLite behavior is unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@rysk-t rysk-t changed the title Fix Faster R-CNN detector inference crash (box_score_thresh: null) and float64 detector targets on MPSDetector bugfixes Fix Faster R-CNN detector inference crashing on box_score_thresh: null Aug 30, 2026
@C-Achard C-Achard added pytorch config Related to config.yaml, ruamel, YAML parsing, ... bug fix labels Aug 31, 2026
Set a consistent default for `box_score_thresh` and normalize `None` values before model construction. `DetectorModelConfig` now defaults to `0.01` and uses a validator to translate `null` config values to that default, while the torchvision detector adaptor reuses the shared constant instead of hardcoded literals. This prevents Faster R-CNN inference crashes caused by `None` threshold comparisons.
Replace FasterRCNN construction in detector tests with direct `DetectorModelConfig` checks for `box_score_thresh`. This keeps coverage of `None` fallback to `0.01`, preserves explicit value behavior, and adds a default-config assertion while making the test lighter and less coupled to a specific model initialization.
@C-Achard

Copy link
Copy Markdown
Member

Hello @rysk-t,

Thanks a lot for the fix! This is indeed an issue with the current handling.
I think we can make the solution slightly easier to maintain by centralizing the default value, and ensuring the config schema itself validates None upon loading.

I've pushed these additions in 6db842d, please let me know if you agree.
Otherwise everything looks good, thanks for the additional regression coverage as well.

Best,
Cyril

@C-Achard
C-Achard requested a review from deruyter92 August 31, 2026 10:20
@C-Achard C-Achard added the 3.1 label Aug 31, 2026
@C-Achard
C-Achard changed the base branch from main to dev August 31, 2026 10:20
@rysk-t

rysk-t commented Aug 31, 2026

Copy link
Copy Markdown
Contributor Author

Thanks, Cyril (@C-Achard)!

I agree with centralizing the default and normalizing None at the schema level — that's cleaner than the adaptor-only guard, and it also heals existing configs on load.

One note: the original PR applied 0.01 only when constructing the model, so the config value itself stayed None. Moving the default into the schema changes that value, which causes two problems when running the branch locally (macOS arm64, torch 2.12.1):

  1. A test case fails. tests/pose_estimation_pytorch/config/test_pose_config_creation.py::test_pose_config_creation[multi_resnet_50_td_ssdlite] asserts that the generated ssdlite config has box_score_thresh is None, and the schema default is now 0.01. The assertion needs updating to the new default (everything else passes: 1011 passed).

  2. The bbox plotting cutoff changes. evaluate_snapshot (evaluation.py:625) and create_labeled_video (make_labeled_video.py:608, :1196) read the config value with ... or 0.6 for the bbox plotting cutoff. Previously null fell through to 0.6; now they get 0.01, so evaluation plots and labeled videos will draw many more low-confidence boxes. If that's not intended, those call sites could keep a dedicated plotting default instead of reusing the model threshold.

Happy to push either fix if you'd like — just let me know.

Add `bboxes_pcutoff` to the project config and use it consistently for evaluation and labeled video rendering. This moves detector box confidence filtering out of model-specific defaults so plotting and evaluation share the same user-configurable threshold.
Make `DetectorModelConfig.box_score_thresh` non-optional with an explicit default so detector configs always carry a numeric threshold. Tests were updated to compare threshold values (not object identity) and to cover `0.0`, ensuring falsy-but-valid thresholds are preserved.
@C-Achard

C-Achard commented Sep 1, 2026

Copy link
Copy Markdown
Member

No worries @rysk-t, thanks for the extra checks! I think a possibly cleaner option moving forward would be to have a dedicated cutoff config field, this broadens the PR scope a bit but should retain backwards comp and avoid having ad-hoc defaults spread everywhere.

Even if that particular change is reverted, we still intend to merge your fix with the next PR batch from dev. Thanks again!

Improve comments around `box_score_thresh` normalization in the torchvision detector and clarify that `bboxes_pcutoff` controls the minimum confidence for plotting boxes, falling back to the project configuration when unset.
@rysk-t

rysk-t commented Sep 1, 2026

Copy link
Copy Markdown
Contributor Author

Thanks, Cyril.

That seems like the right decision. Decoupling the cutoff value for the plot from the model's threshold yields a cleaner result than what I originally proposed.

Thank you for the quick review — glad I could contribute to DeepLabCut.

- Remove `DEFAULT_BOX_SCORE_THRESH`
- Make field non-optional: None-normalization happens only in the config.

Coupling the class defaults to the model config `DEFAULT_BOX_SCORE_THRESH` is not required.
The shared constant looked like a single source of truth but inside DeepLabCut the value is always the `box_score_thresh` already serialized into its pytorch_config.yaml. The class defaults only apply to direct instantiation never happens in our own repo and are are just here as courtesy for developers that use the class outside of DeepLabCut

@deruyter92 deruyter92 left a comment •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

@rysk-t, excellent PR! Thanks a lot for spotting this real bug, providing a clear description and for the well-designed solution.

@C-Achard and @rysk-t, I like your proposed solutions. A few things worth discussing:

  1. the model box_score_threshold is used internally in the torchvision detectors and is a completely separate concept from the bboxes_pcutoff which is used for plotting. I think you've both addressed that well, and I agree that bboxes_pcutoff deserves it's place in the ProjectConfig. Thanks for adding that @C-Achard. We might need to discuss the forward compatibility in older versions. (The current change means that migrating a 3.1 config to 3.0.1 will break).
  2. There was another call site (create_video_with_all_detections) that wrongly used the old parameter from the detector config, I've pushed some extra changes to address this.
  3. The null normalization to default in the DetectorModelConfig is an elegant solution! I am opening a follow-up PR (#3475) to make this even available for other config models as well. This pattern may be needed in more places.
  4. One solution I did not entirely agree with entirely is the shared DEFAULT_BOX_SCORE_THRESH: since null-normalization happens in the config, we do not need to support box_score_thresh as an optional argument, we can always require a float. I prefer keeping the null-normalization just in a single place (the config). Also, coupling the class defaults to the config model default feels a bit wrong, as I would prefer to always use the serialized value. Inside DeepLabCut it always goes via DetectorModelConfig anyway, which is authorative.

Sorry for the elaborate review, but I think it is a good first example case, so I think it is worth some discussion. Happy to hear if both of you agree. Open for suggestions.

@C-Achard

C-Achard commented Sep 1, 2026

Copy link
Copy Markdown
Member

We might need to discuss the forward compatibility in older versions. (The current change means that migrating a 3.1 config to 3.0.1 will break).

Agreed, it does create the need for some minimal migration logic, let's make a follow-up PR.

@deruyter92

Copy link
Copy Markdown
Collaborator

(Note to self and other devs: before the config refactors None was not valid for the torchvision detectors box_score_thresh: float = 0.01)

This avoids breaking compatibility, and keeps the config schema internally consistent for the current version
@deruyter92

Copy link
Copy Markdown
Collaborator

@C-Achard, after our internal discussion I've updated bboxes_pcutoff to:

  • be a property rather than a field in ProjectConfig to avoid breaking the schema right now (and get this PR merged first)
  • be configurable in every caller, so that it does not rely on the config alone
  • xfail until it's added as a field in ProjectConfig, as a reminder for ourselves that we can adjust this in the config schema update

@deruyter92

Copy link
Copy Markdown
Collaborator

Let me know if you agree. I will mark you as a reviewer! I'm approving both of your changes

@rysk-t

rysk-t commented Sep 1, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the thorough review. I agree with all four points, and your reasoning on DEFAULT_BOX_SCORE_THRESH makes sense.
Everything looks good on my side, so let me know if there's anything else you need from me.

@C-Achard

C-Achard commented Sep 1, 2026

Copy link
Copy Markdown
Member

Perfect, thanks @deruyter92!

@deruyter92
deruyter92 merged commit b1dd957 into DeepLabCut:dev Sep 3, 2026
2 checks passed
@deruyter92 deruyter92 added 3.0.2 and removed 3.1 labels Sep 7, 2026
@C-Achard C-Achard mentioned this pull request Sep 14, 2026
2 tasks done
@C-Achard C-Achard linked an issue Sep 14, 2026 that may be closed by this pull request
2 tasks done
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

3.0.2 bug fix config Related to config.yaml, ruamel, YAML parsing, ... pytorch

Projects

None yet

Development

Successfully merging this pull request may close these issues.

issue with tensor types

3 participants