fix: replace deprecated 2-D np.cross with explicit determinant (#2386)
- Add filterwarnings = ["error::DeprecationWarning"] to pyproject.toml so future np.cross 2-D reintroductions fail CI immediately (closes #2384) - Add test_get_polygon_center_no_deprecation_warning: asserts no DeprecationWarning from get_polygon_center (Copilot inline comment) - Add test_cross_product_no_deprecation_warning: asserts no DeprecationWarning from cross_product (Copilot inline comment) - Add test_cross_product_sign (4 parametrised cases): above / below / on-line / offset-start — directly tests the inline determinant correctness - Improve cross_product docstring: blank line after summary, adds Examples section with correct output, notes NumPy 2.0 rationale --------- Co-authored-by: jirka <6035284+Borda@users.noreply.github.com> Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
This commit is contained in:
parent
99049d84e1
commit
f196e15f26
|
|
@ -199,6 +199,9 @@ ini_options.addopts = [
|
|||
"--doctest-modules",
|
||||
"--color=yes",
|
||||
]
|
||||
ini_options.filterwarnings = [
|
||||
"error::DeprecationWarning",
|
||||
]
|
||||
ini_options.doctest_optionflags = "ELLIPSIS NORMALIZE_WHITESPACE"
|
||||
|
||||
[tool.autoflake]
|
||||
|
|
|
|||
|
|
@ -653,14 +653,28 @@ def get_data_item(
|
|||
def cross_product(
|
||||
anchors: npt.NDArray[np.number], vector: Vector
|
||||
) -> npt.NDArray[np.number]:
|
||||
"""
|
||||
Get array of cross products of each anchor with a vector.
|
||||
"""Get signed z-component of cross product (2-D determinant) per anchor.
|
||||
|
||||
Replaces the deprecated `np.cross` 2-D path (NumPy 2.0) with an explicit
|
||||
determinant: ``a[..., 0] * b[..., 1] - a[..., 1] * b[..., 0]``.
|
||||
|
||||
Args:
|
||||
anchors: Array of anchors of shape (number of anchors, detections, 2)
|
||||
vector: Vector to calculate cross product with
|
||||
anchors: Array of anchors of shape (number of anchors, detections, 2).
|
||||
vector: Vector to calculate cross product with.
|
||||
|
||||
Returns:
|
||||
Array of cross products of shape (number of anchors, detections)
|
||||
Array of signed cross-product values, shape (number of anchors,
|
||||
detections). Positive = anchor is to the left of the vector direction;
|
||||
negative = to the right; zero = on the line.
|
||||
|
||||
Examples:
|
||||
>>> import numpy as np
|
||||
>>> from supervision.geometry.core import Point, Vector
|
||||
>>> anchors = np.array([[[10.0, 5.0]], [[20.0, 5.0]]])
|
||||
>>> vector = Vector(start=Point(0, 0), end=Point(1, 0))
|
||||
>>> cross_product(anchors, vector)
|
||||
array([[5.],
|
||||
[5.]])
|
||||
"""
|
||||
vector_at_zero = np.array(
|
||||
[
|
||||
|
|
@ -669,6 +683,8 @@ def cross_product(
|
|||
]
|
||||
)
|
||||
vector_start = np.array([vector.start.x, vector.start.y])
|
||||
diff = anchors - vector_start
|
||||
return cast(
|
||||
npt.NDArray[np.number], np.cross(vector_at_zero, anchors - vector_start)
|
||||
npt.NDArray[np.number],
|
||||
vector_at_zero[0] * diff[..., 1] - vector_at_zero[1] * diff[..., 0],
|
||||
)
|
||||
|
|
|
|||
|
|
@ -41,7 +41,10 @@ def get_polygon_center(polygon: npt.NDArray[np.float64]) -> Point:
|
|||
raise ValueError("Polygon must have at least one vertex.")
|
||||
|
||||
shift_polygon = np.roll(polygon, -1, axis=0)
|
||||
signed_areas = np.cross(polygon, shift_polygon) / 2
|
||||
signed_areas = (
|
||||
polygon[..., 0] * shift_polygon[..., 1]
|
||||
- polygon[..., 1] * shift_polygon[..., 0]
|
||||
) / 2
|
||||
if signed_areas.sum() == 0:
|
||||
center = np.mean(polygon, axis=0).round()
|
||||
return Point(x=center[0], y=center[1])
|
||||
|
|
|
|||
|
|
@ -879,6 +879,26 @@ def test_line_zone_tracker_id_reuse_with_different_classes(
|
|||
assert line_zone.out_count_per_class == expected_out_count_per_class
|
||||
|
||||
|
||||
def test_line_zone_trigger_does_not_call_np_cross(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
"""Guard against reintroducing np.cross, deprecated for 2-D input in NumPy 2.0."""
|
||||
|
||||
def _raise(*args, **kwargs):
|
||||
raise AssertionError("np.cross must not be called on 2-D vectors")
|
||||
|
||||
monkeypatch.setattr(np, "cross", _raise)
|
||||
|
||||
line_zone = LineZone(start=Point(0, 0), end=Point(0, 10))
|
||||
for xyxy in [[4, 4, 6, 6], [-6, 4, -4, 6]]:
|
||||
detections = _create_detections(xyxy=[xyxy], tracker_id=[0])
|
||||
crossed_in, crossed_out = line_zone.trigger(detections)
|
||||
|
||||
assert not crossed_in[0]
|
||||
assert crossed_out[0]
|
||||
assert line_zone.out_count == 1
|
||||
|
||||
|
||||
def test_line_zone_annotator_multiclass_supports_none_class_id() -> None:
|
||||
line_zone = LineZone(start=Point(0, 0), end=Point(0, 10))
|
||||
for xyxy in [[4, 4, 6, 6], [-6, 4, -4, 6]]:
|
||||
|
|
|
|||
|
|
@ -1125,3 +1125,58 @@ def test_process_roboflow_result_compact_masks_rle_mask_size_mismatch() -> None:
|
|||
|
||||
assert isinstance(compact_result[3], CompactMask)
|
||||
np.testing.assert_array_equal(compact_result[3].to_dense(), dense_result[3])
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# cross_product — regression + unit tests (GitHub #2384)
|
||||
# ---------------------------------------------------------------------------
|
||||
import warnings # noqa: E402
|
||||
|
||||
from supervision.detection.utils.internal import cross_product # noqa: E402
|
||||
from supervision.geometry.core import Point, Vector # noqa: E402
|
||||
|
||||
|
||||
def test_cross_product_no_deprecation_warning() -> None:
|
||||
"""Regression for #2384: cross_product must not fire DeprecationWarning."""
|
||||
anchors = np.array([[[5.0, 5.0]]])
|
||||
v = Vector(start=Point(0, 0), end=Point(10, 0))
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter("error", DeprecationWarning)
|
||||
cross_product(anchors, v)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("anchors", "vector", "expected_sign"),
|
||||
[
|
||||
pytest.param(
|
||||
np.array([[[5.0, 5.0]]]),
|
||||
Vector(Point(0, 0), Point(10, 0)),
|
||||
1,
|
||||
id="above",
|
||||
),
|
||||
pytest.param(
|
||||
np.array([[[5.0, -5.0]]]),
|
||||
Vector(Point(0, 0), Point(10, 0)),
|
||||
-1,
|
||||
id="below",
|
||||
),
|
||||
pytest.param(
|
||||
np.array([[[5.0, 0.0]]]),
|
||||
Vector(Point(0, 0), Point(10, 0)),
|
||||
0,
|
||||
id="on-line",
|
||||
),
|
||||
pytest.param(
|
||||
np.array([[[3.0, 3.0]]]),
|
||||
Vector(Point(1, 1), Point(5, 1)),
|
||||
1,
|
||||
id="offset-start",
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_cross_product_sign(
|
||||
anchors: np.ndarray, vector: Vector, expected_sign: int
|
||||
) -> None:
|
||||
"""Verify cross_product returns correct sign for known anchor/vector pairs."""
|
||||
result = cross_product(anchors, vector)
|
||||
assert int(np.sign(result[0, 0])) == expected_sign
|
||||
|
|
|
|||
|
|
@ -58,3 +58,28 @@ def test_get_polygon_center(polygon: np.ndarray, expected_result: Point) -> None
|
|||
"""
|
||||
result = get_polygon_center(polygon)
|
||||
assert result == expected_result
|
||||
|
||||
|
||||
def test_get_polygon_center_no_deprecation_warning() -> None:
|
||||
"""Regression for #2384: get_polygon_center must not fire DeprecationWarning."""
|
||||
import warnings
|
||||
|
||||
polygon = np.array([[0, 0], [0, 2], [2, 2], [2, 0]], dtype=float)
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter("error", DeprecationWarning)
|
||||
get_polygon_center(polygon=polygon)
|
||||
|
||||
|
||||
def test_get_polygon_center_does_not_call_np_cross(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
"""Guard against reintroducing np.cross, deprecated for 2-D input in NumPy 2.0."""
|
||||
|
||||
def _raise(*args, **kwargs):
|
||||
raise AssertionError("np.cross must not be called on 2-D vectors")
|
||||
|
||||
monkeypatch.setattr(np, "cross", _raise)
|
||||
|
||||
result = get_polygon_center(generate_test_polygon(100))
|
||||
|
||||
assert result == Point(x=50.0, y=121.0)
|
||||
|
|
|
|||
Loading…
Reference in New Issue