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
103 lines
3.9 KiB
Python
103 lines
3.9 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 rfdetr.models.math utility functions."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import pytest
|
|
import torch
|
|
|
|
from rfdetr.models.math import accuracy, interpolate, inverse_sigmoid
|
|
|
|
|
|
class TestInterpolate:
|
|
"""Verify interpolate() delegates to F.interpolate across torchvision versions."""
|
|
|
|
def test_resizes_to_target_size(self) -> None:
|
|
"""Interpolate() upsamples a 4-D tensor to the requested spatial size."""
|
|
x = torch.randn(2, 3, 4, 4)
|
|
|
|
out = interpolate(x, size=[8, 8], mode="bilinear", align_corners=False)
|
|
|
|
assert out.shape == (2, 3, 8, 8)
|
|
|
|
def test_handles_empty_batch(self) -> None:
|
|
"""Interpolate() supports an empty batch dimension without error."""
|
|
x = torch.randn(0, 3, 4, 4)
|
|
|
|
out = interpolate(x, size=[8, 8], mode="nearest")
|
|
|
|
assert out.shape == (0, 3, 8, 8)
|
|
|
|
|
|
class TestAccuracy:
|
|
"""Verify accuracy() computes precision@k correctly."""
|
|
|
|
def test_top1_perfect_batch(self) -> None:
|
|
"""All predictions correct returns top-1 accuracy of 100.0."""
|
|
output = torch.tensor([[0.0, 10.0], [10.0, 0.0], [0.0, 10.0]])
|
|
target = torch.tensor([1, 0, 1])
|
|
result = accuracy(output, target, topk=(1,))
|
|
assert len(result) == 1
|
|
assert result[0].item() == pytest.approx(100.0)
|
|
|
|
def test_top1_zero_accuracy(self) -> None:
|
|
"""All predictions wrong returns top-1 accuracy of 0.0."""
|
|
output = torch.tensor([[10.0, 0.0], [0.0, 10.0]])
|
|
target = torch.tensor([1, 0])
|
|
result = accuracy(output, target, topk=(1,))
|
|
assert result[0].item() == pytest.approx(0.0)
|
|
|
|
def test_topk_returns_list_of_correct_length(self) -> None:
|
|
"""Topk=(1, 5) returns a list of length 2."""
|
|
output = torch.randn(10, 10)
|
|
target = torch.zeros(10, dtype=torch.long)
|
|
result = accuracy(output, target, topk=(1, 5))
|
|
assert len(result) == 2
|
|
|
|
def test_empty_target_returns_single_zero_regardless_of_topk(self) -> None:
|
|
"""Empty target returns list of length 1 with value 0 regardless of topk length."""
|
|
output = torch.zeros(0, 5)
|
|
target = torch.zeros(0, dtype=torch.long)
|
|
result = accuracy(output, target, topk=(1, 5))
|
|
assert len(result) == 1
|
|
assert result[0].item() == pytest.approx(0.0)
|
|
|
|
|
|
class TestInverseSigmoid:
|
|
"""Verify inverse_sigmoid() computes the logit function correctly."""
|
|
|
|
def test_identity_at_half(self) -> None:
|
|
"""inverse_sigmoid(0.5) equals 0.0 since sigmoid(0.0) = 0.5."""
|
|
x = torch.tensor([0.5])
|
|
result = inverse_sigmoid(x)
|
|
assert result.item() == pytest.approx(0.0, abs=1e-5)
|
|
|
|
def test_clamping_at_zero_is_finite(self) -> None:
|
|
"""inverse_sigmoid(0.0) is finite due to eps clamping."""
|
|
x = torch.tensor([0.0])
|
|
result = inverse_sigmoid(x)
|
|
assert torch.isfinite(result).all()
|
|
|
|
def test_clamping_at_one_is_finite(self) -> None:
|
|
"""inverse_sigmoid(1.0) is finite due to eps clamping."""
|
|
x = torch.tensor([1.0])
|
|
result = inverse_sigmoid(x)
|
|
assert torch.isfinite(result).all()
|
|
|
|
def test_output_shape_matches_input(self) -> None:
|
|
"""Output shape matches input shape for a multi-dimensional tensor."""
|
|
x = torch.rand(3, 4)
|
|
result = inverse_sigmoid(x)
|
|
assert result.shape == x.shape
|
|
|
|
def test_gradient_flows_for_non_saturated_input(self) -> None:
|
|
"""Gradients are non-zero for a non-saturated input value."""
|
|
x = torch.tensor([0.3], requires_grad=True)
|
|
inverse_sigmoid(x).sum().backward()
|
|
assert x.grad is not None
|
|
assert x.grad.abs().item() > 0.0
|