Files
roboflow--rf-detr/tests/models/test_criterion_keypoints.py
wehub-resource-sync 16031aae96
CPU tests Workflow / Testing (ubuntu-latest, 3.12) (push) Failing after 1s
CPU tests Workflow / Testing (ubuntu-latest, 3.13) (push) Failing after 0s
Mypy Type Check / Type Check (push) Failing after 0s
Docs/Test WorkFlow / Test docs build (push) Failing after 1s
PR Conflict Labeler / labeling (push) Failing after 1s
Dependency resolution / Resolve [tflite] extra — Python 3.12 (push) Failing after 0s
Smoke Tests / try-all-models (ubuntu-latest, 3.10) (push) Failing after 0s
Smoke Tests / try-all-models (ubuntu-latest, 3.13) (push) Failing after 1s
CPU tests Workflow / build-pkg (push) Failing after 1s
CPU tests Workflow / Testing (ubuntu-latest, 3.10) (push) Failing after 0s
CPU tests Workflow / Testing (ubuntu-latest, 3.11) (push) Failing after 0s
Smoke Tests / try-all-models (macos-latest, 3.10) (push) Has been cancelled
Smoke Tests / try-all-models (macos-latest, 3.13) (push) Has been cancelled
Smoke Tests / try-all-models (windows-latest, 3.10) (push) Has been cancelled
Smoke Tests / try-all-models (windows-latest, 3.13) (push) Has been cancelled
CPU tests Workflow / Testing (macos-latest, 3.10) (push) Has been cancelled
CPU tests Workflow / Testing (macos-latest, 3.13) (push) Has been cancelled
CPU tests Workflow / Testing (windows-latest, 3.10) (push) Has been cancelled
CPU tests Workflow / Testing (windows-latest, 3.13) (push) Has been cancelled
CPU tests Workflow / testing-guardian (push) Has been cancelled
GPU tests Workflow / Testing (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:26:24 +08:00

162 lines
5.4 KiB
Python

# ------------------------------------------------------------------------
# RF-DETR
# Copyright (c) 2025 Roboflow. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 [see LICENSE for details]
# ------------------------------------------------------------------------
"""Unit tests for keypoint losses in SetCriterion."""
import torch
from rfdetr.models.criterion import SetCriterion
class _MatcherStub:
"""Matcher stub used to avoid depending on Hungarian matching internals."""
def __call__(self, outputs, targets, group_detr=1):
indices = []
for target in targets:
num_targets = int(target["labels"].shape[0])
idx = torch.arange(num_targets, dtype=torch.int64)
indices.append((idx, idx))
return indices
def _make_outputs(
batch_size: int,
num_queries: int,
num_keypoints: int,
) -> dict[str, torch.Tensor]:
return {
"pred_logits": torch.zeros(batch_size, num_queries, 2),
"pred_boxes": torch.rand(batch_size, num_queries, 4).clamp(0.05, 0.95),
"pred_keypoints": torch.randn(batch_size, num_queries, num_keypoints, 8),
}
def test_loss_keypoints_list_of_dicts_targets() -> None:
"""Keypoint loss should consume list-of-dicts targets used by public training."""
criterion = SetCriterion(
num_classes=2,
matcher=_MatcherStub(),
weight_dict={},
focal_alpha=0.25,
losses=["keypoints"],
num_keypoints_per_class=[17],
)
outputs = _make_outputs(batch_size=1, num_queries=1, num_keypoints=17)
targets = [
{
"labels": torch.tensor([0], dtype=torch.int64),
"boxes": torch.tensor([[0.5, 0.5, 0.4, 0.4]], dtype=torch.float32),
"keypoints": torch.cat(
[
torch.rand(1, 17, 2),
torch.full((1, 17, 1), 2.0),
],
dim=-1,
),
}
]
losses = criterion(outputs, targets)
assert "loss_keypoints_l1" in losses
assert "loss_keypoints_findable" in losses
assert "loss_keypoints_visible" in losses
assert "loss_keypoints_nll" in losses
assert all(torch.isfinite(value) for value in losses.values())
def test_loss_keypoints_empty_targets() -> None:
"""Empty target batches should produce finite zero-valued keypoint losses."""
criterion = SetCriterion(
num_classes=2,
matcher=_MatcherStub(),
weight_dict={},
focal_alpha=0.25,
losses=["keypoints"],
num_keypoints_per_class=[17],
)
outputs = _make_outputs(batch_size=1, num_queries=1, num_keypoints=17)
targets = [
{
"labels": torch.zeros((0,), dtype=torch.int64),
"boxes": torch.zeros((0, 4), dtype=torch.float32),
"keypoints": torch.zeros((0, 17, 3), dtype=torch.float32),
}
]
losses = criterion(outputs, targets)
assert losses["loss_keypoints_l1"].item() == 0.0
assert losses["loss_keypoints_findable"].item() == 0.0
assert losses["loss_keypoints_visible"].item() == 0.0
assert losses["loss_keypoints_nll"].item() == 0.0
def test_loss_keypoints_person_schema_shape() -> None:
"""Person-only schema `[17]` should be consumed without shape mismatches."""
criterion = SetCriterion(
num_classes=2,
matcher=_MatcherStub(),
weight_dict={},
focal_alpha=0.25,
losses=["keypoints"],
num_keypoints_per_class=[17],
)
outputs = _make_outputs(batch_size=2, num_queries=2, num_keypoints=17)
targets = [
{
"labels": torch.tensor([0], dtype=torch.int64),
"boxes": torch.tensor([[0.5, 0.5, 0.4, 0.4]], dtype=torch.float32),
"keypoints": torch.rand(1, 17, 3),
},
{
"labels": torch.tensor([0], dtype=torch.int64),
"boxes": torch.tensor([[0.4, 0.6, 0.3, 0.5]], dtype=torch.float32),
"keypoints": torch.rand(1, 17, 3),
},
]
losses = criterion(outputs, targets)
assert losses["loss_keypoints_l1"].ndim == 0
assert losses["loss_keypoints_findable"].ndim == 0
assert losses["loss_keypoints_visible"].ndim == 0
assert losses["loss_keypoints_nll"].ndim == 0
def test_loss_keypoints_multiclass_schema_kmax_targets() -> None:
"""Heterogeneous keypoint classes should consume Kmax-padded targets."""
criterion = SetCriterion(
num_classes=3,
matcher=_MatcherStub(),
weight_dict={},
focal_alpha=0.25,
losses=["keypoints"],
num_keypoints_per_class=[2, 1],
)
outputs = _make_outputs(batch_size=1, num_queries=2, num_keypoints=4)
targets = [
{
"labels": torch.tensor([0, 1], dtype=torch.int64),
"boxes": torch.tensor([[0.5, 0.5, 0.4, 0.4], [0.4, 0.6, 0.3, 0.5]], dtype=torch.float32),
"keypoints": torch.tensor(
[
[[0.2, 0.3, 2.0], [0.4, 0.5, 2.0]],
[[0.6, 0.7, 2.0], [0.0, 0.0, 0.0]],
],
dtype=torch.float32,
),
}
]
losses = criterion(outputs, targets)
assert losses["loss_keypoints_l1"].ndim == 0
assert losses["loss_keypoints_findable"].ndim == 0
assert losses["loss_keypoints_visible"].ndim == 0
assert losses["loss_keypoints_nll"].ndim == 0
assert all(torch.isfinite(value) for value in losses.values())