chore: import upstream snapshot with attribution
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
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
This commit is contained in:
@@ -0,0 +1,163 @@
|
||||
# ------------------------------------------------------------------------
|
||||
# RF-DETR
|
||||
# Copyright (c) 2025 Roboflow. All Rights Reserved.
|
||||
# Licensed under the Apache License, Version 2.0 [see LICENSE for details]
|
||||
# ------------------------------------------------------------------------
|
||||
"""Tests for keypoint utility functions in rfdetr.utilities.keypoints."""
|
||||
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
from rfdetr.utilities.keypoints import (
|
||||
_is_bg_first_schema,
|
||||
_to_active_first,
|
||||
_to_bg_first,
|
||||
precision_cholesky_to_pixel_covariance,
|
||||
schemas_semantically_equal,
|
||||
)
|
||||
|
||||
|
||||
class TestIsBgFirstSchema:
|
||||
"""Group: is_bg_first_schema — schema classification."""
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("schema", "expected"),
|
||||
[
|
||||
pytest.param([0, 17], True, id="bg-first-single-class"),
|
||||
pytest.param([0, 17, 4], True, id="bg-first-multi-class"),
|
||||
pytest.param([0], True, id="bg-only-slot"),
|
||||
pytest.param([17], False, id="active-first-single"),
|
||||
pytest.param([17, 4], False, id="active-first-multi"),
|
||||
pytest.param([], False, id="empty-schema"),
|
||||
],
|
||||
)
|
||||
def test_classification(self, schema: list[int], expected: bool) -> None:
|
||||
"""is_bg_first_schema returns expected bool for each schema form."""
|
||||
assert _is_bg_first_schema(schema) == expected
|
||||
|
||||
|
||||
class TestToActiveFirst:
|
||||
"""Group: to_active_first — strip leading background slot."""
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("schema", "expected"),
|
||||
[
|
||||
pytest.param([0, 17], [17], id="bg-first-to-active"),
|
||||
pytest.param([0, 17, 4], [17, 4], id="bg-first-multi-class"),
|
||||
pytest.param([0], [], id="bg-only-to-empty"),
|
||||
pytest.param([17], [17], id="already-active-first"),
|
||||
pytest.param([17, 4], [17, 4], id="already-active-multi"),
|
||||
pytest.param([], [], id="empty-schema"),
|
||||
pytest.param([0, 0, 17], [0, 17], id="multi-leading-zero-strips-one"),
|
||||
],
|
||||
)
|
||||
def test_conversion(self, schema: list[int], expected: list[int]) -> None:
|
||||
"""to_active_first strips only the first leading zero slot."""
|
||||
assert _to_active_first(schema) == expected
|
||||
|
||||
def test_returns_new_list(self) -> None:
|
||||
"""to_active_first always returns a new list, never the input object."""
|
||||
schema = [17]
|
||||
result = _to_active_first(schema)
|
||||
assert result is not schema
|
||||
|
||||
|
||||
class TestToBgFirst:
|
||||
"""Group: to_bg_first — prepend background slot."""
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("schema", "expected"),
|
||||
[
|
||||
pytest.param([17], [0, 17], id="active-first-to-bg"),
|
||||
pytest.param([17, 4], [0, 17, 4], id="active-first-multi"),
|
||||
pytest.param([0, 17], [0, 17], id="already-bg-first-no-op"),
|
||||
pytest.param([], [], id="empty-schema-no-op"),
|
||||
],
|
||||
)
|
||||
def test_conversion(self, schema: list[int], expected: list[int]) -> None:
|
||||
"""to_bg_first prepends 0 only when schema is active-first and non-empty."""
|
||||
assert _to_bg_first(schema) == expected
|
||||
|
||||
def test_returns_new_list(self) -> None:
|
||||
"""to_bg_first always returns a new list, never the input object."""
|
||||
schema = [0, 17]
|
||||
result = _to_bg_first(schema)
|
||||
assert result is not schema
|
||||
|
||||
|
||||
class TestSchemasSemanticallyEqual:
|
||||
"""Group: schemas_semantically_equal — cross-form equality."""
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("a", "b", "expected"),
|
||||
[
|
||||
pytest.param([0, 17], [17], True, id="bg-first-eq-active-first"),
|
||||
pytest.param([17], [17], True, id="identical-active-first"),
|
||||
pytest.param([0, 17], [0, 17], True, id="identical-bg-first"),
|
||||
pytest.param([0, 17], [0, 33], False, id="different-keypoint-counts"),
|
||||
pytest.param([17], [33], False, id="active-first-mismatch"),
|
||||
pytest.param([0], [], True, id="bg-only-eq-empty"),
|
||||
pytest.param([], [], True, id="empty-eq-empty"),
|
||||
pytest.param([0, 17, 4], [17, 4], True, id="multi-class-cross-form"),
|
||||
],
|
||||
)
|
||||
def test_equality(self, a: list[int], b: list[int], expected: bool) -> None:
|
||||
"""schemas_semantically_equal returns expected result for each pair."""
|
||||
assert schemas_semantically_equal(a, b) == expected
|
||||
|
||||
def test_symmetric(self) -> None:
|
||||
"""schemas_semantically_equal(a, b) == schemas_semantically_equal(b, a)."""
|
||||
assert schemas_semantically_equal([0, 17], [17]) == schemas_semantically_equal([17], [0, 17])
|
||||
|
||||
|
||||
class TestPrecisionCholeskyToPixelCovariance:
|
||||
"""Group: precision_cholesky_to_pixel_covariance — non-finite input handling."""
|
||||
|
||||
def test_nan_in_single_slot_produces_nan_only_in_that_slot(self) -> None:
|
||||
"""NaN params in one detection slot should propagate NaN only to that slot's output."""
|
||||
# N=2, K=1: first slot valid, second slot has NaN in all three params.
|
||||
params = np.array(
|
||||
[[[0.0, 0.0, 0.0]], [[np.nan, 0.0, 0.0]]],
|
||||
dtype=np.float32,
|
||||
)
|
||||
source_shape = np.array([[10.0, 20.0], [10.0, 20.0]], dtype=np.float32)
|
||||
|
||||
covariance = precision_cholesky_to_pixel_covariance(
|
||||
precision_cholesky=params,
|
||||
source_shape=source_shape,
|
||||
)
|
||||
|
||||
# First slot (valid) should be all-finite.
|
||||
assert np.isfinite(covariance[0, 0]).all(), f"First slot expected all-finite, got {covariance[0, 0]}"
|
||||
# Second slot (NaN input) should be all-NaN.
|
||||
assert np.isnan(covariance[1, 0]).all(), f"Second slot expected all-NaN, got {covariance[1, 0]}"
|
||||
|
||||
def test_all_inf_params_produce_all_nan_covariance(self) -> None:
|
||||
"""Infinite precision params should produce all-NaN pixel covariances."""
|
||||
params = np.full((1, 1, 3), np.inf, dtype=np.float32)
|
||||
source_shape = np.array([[10.0, 20.0]], dtype=np.float32)
|
||||
|
||||
covariance = precision_cholesky_to_pixel_covariance(
|
||||
precision_cholesky=params,
|
||||
source_shape=source_shape,
|
||||
)
|
||||
|
||||
assert np.isnan(covariance).all(), f"Expected all-NaN output for all-inf inputs, got {covariance}"
|
||||
|
||||
def test_mixed_valid_and_nan_rows_isolates_nan_to_bad_row(self) -> None:
|
||||
"""First detection valid, second detection NaN — only second row should be NaN."""
|
||||
params = np.array(
|
||||
[[[0.0, 0.0, 0.0]], [[np.nan, np.nan, np.nan]]],
|
||||
dtype=np.float32,
|
||||
)
|
||||
source_shape = np.array([[10.0, 20.0], [5.0, 8.0]], dtype=np.float32)
|
||||
|
||||
covariance = precision_cholesky_to_pixel_covariance(
|
||||
precision_cholesky=params,
|
||||
source_shape=source_shape,
|
||||
)
|
||||
|
||||
# Row 0 — valid identity input, covariance should be finite.
|
||||
assert np.isfinite(covariance[0]).all(), f"Row 0 expected all-finite, got {covariance[0]}"
|
||||
# Row 1 — NaN input, covariance should be all-NaN.
|
||||
assert np.isnan(covariance[1]).all(), f"Row 1 expected all-NaN, got {covariance[1]}"
|
||||
Reference in New Issue
Block a user