diff --git a/t4_devkit/common/geometry.py b/t4_devkit/common/geometry.py index f065365..641d431 100644 --- a/t4_devkit/common/geometry.py +++ b/t4_devkit/common/geometry.py @@ -68,7 +68,7 @@ def view_points( points = points[:3, :] if normalize: - points = points / points[2:3, :].repeat(3, 0).reshape(3, nbr_points) + points /= points[2:3, :] return points @@ -90,21 +90,21 @@ def is_box_in_image( Returns: Return True if visibility condition is satisfied. """ - corners_3d = box.corners() - corners_on_img = view_points(corners_3d.T, intrinsic, normalize=True)[:2, :] + corners_3d = box.corners().T # (3, 8) + corners_on_img = view_points(corners_3d, intrinsic, normalize=True)[:2, :] img_w, img_h = img_size - is_visible = np.logical_and(corners_on_img[0, :] > 0, corners_on_img[0, :] < img_w) - is_visible = np.logical_and(is_visible, corners_on_img[1, :] < img_h) - is_visible = np.logical_and(is_visible, corners_on_img[1, :] > 0) - is_visible = np.logical_and(is_visible, corners_on_img[2, :] > 1) + is_visible = np.logical_and(corners_on_img[0, :] >= 0, corners_on_img[0, :] <= img_w) + is_visible = np.logical_and(is_visible, corners_on_img[1, :] <= img_h) + is_visible = np.logical_and(is_visible, corners_on_img[1, :] >= 0) + is_visible = np.logical_and(is_visible, corners_3d[2, :] > 1) in_front = corners_3d[2, :] > 0.1 # True if a corner is at least 0.1 meter in front of camera. if visibility == VisibilityLevel.FULL: - return all(is_visible) and all(in_front) + return np.all(is_visible) and np.all(in_front) elif visibility in (VisibilityLevel.MOST, VisibilityLevel.PARTIAL): - return any(is_visible) + return np.any(is_visible) elif visibility == VisibilityLevel.NONE: return True else: diff --git a/tests/common/test_geometry.py b/tests/common/test_geometry.py index 1c9040f..a22754b 100644 --- a/tests/common/test_geometry.py +++ b/tests/common/test_geometry.py @@ -1,6 +1,7 @@ import numpy as np -from t4_devkit.common.geometry import view_points +from t4_devkit.common.geometry import is_box_in_image, view_points +from t4_devkit.schema import VisibilityLevel def test_view_points_by_perspective_projection() -> None: @@ -77,3 +78,46 @@ def test_view_points_with_distortion() -> None: ) assert np.allclose(project, expect) + + +# TODO(ktro2828): add unit testing for VisibilityLevel.FULL + + +def test_box_partial_visible(dummy_box3d, dummy_camera_calibration) -> None: + """Test `is_box_in_image` function in the case of the box is partially visible.""" + img_size, intrinsic = dummy_camera_calibration + + dummy_box3d.position = (0.0, 0.0, 2.0) + assert is_box_in_image( + dummy_box3d, + intrinsic=intrinsic, + img_size=img_size, + visibility=VisibilityLevel.PARTIAL, + ) + + +def test_box_not_visible(dummy_box3d, dummy_camera_calibration) -> None: + """Test `is_box_in_image` function in the case of the box is not visible.""" + img_size, intrinsic = dummy_camera_calibration + + dummy_box3d.position = (100.0, 100.0, 1.0) + assert not is_box_in_image( + dummy_box3d, + intrinsic=intrinsic, + img_size=img_size, + visibility=VisibilityLevel.PARTIAL, + ) + + +def test_box_behind_camera(dummy_box3d, dummy_camera_calibration) -> None: + """Test `is_box_in_image` function in the case of the box is behind of the camera.""" + + img_size, intrinsic = dummy_camera_calibration + + dummy_box3d.position = (100.0, 100.0, -1.0) + assert not is_box_in_image( + dummy_box3d, + intrinsic=intrinsic, + img_size=img_size, + visibility=VisibilityLevel.FULL, + ) diff --git a/tests/conftest.py b/tests/conftest.py index bf5cf77..09224fa 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,3 +1,8 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +import numpy as np import pytest from pyquaternion import Quaternion @@ -12,6 +17,9 @@ TransformBuffer, ) +if TYPE_CHECKING: + from t4_devkit.typing import NDArrayFloat + @pytest.fixture(scope="module") def label2id() -> dict[str, int]: @@ -176,3 +184,18 @@ def dummy_tf_buffer() -> TransformBuffer: ) return tf_buffer + + +@pytest.fixture(scope="function") +def dummy_camera_calibration() -> tuple[tuple[int, int], NDArrayFloat]: + img_size = (1280, 720) + + intrinsic = np.array( + [ + [1000, 0, 640], + [0, 1000, 360], + [0, 0, 1], + ] + ) + + return img_size, intrinsic