From ef4ac14b3c795d158c23c5f9778349a260beda77 Mon Sep 17 00:00:00 2001 From: Brian Pugh Date: Fri, 3 Jan 2025 18:15:09 -0500 Subject: [PATCH 1/3] cv2.remap cannot handle float16; ensure output of sampler matches input dtype. --- py360convert/utils.py | 25 +++++++++++++++++++++++++ pyproject.toml | 1 + tests/test_main.py | 21 +++++++++++++++++++++ 3 files changed, 47 insertions(+) diff --git a/py360convert/utils.py b/py360convert/utils.py index bece5bc..05648ac 100644 --- a/py360convert/utils.py +++ b/py360convert/utils.py @@ -378,7 +378,19 @@ def __init__( def __call__(self, img: NDArray[DType]) -> NDArray[DType]: padded = self._pad(img) if self._use_cv2: + if padded.dtype == np.float16: + source_dtype = np.float16 + else: + source_dtype = None + + if source_dtype: + padded = padded.astype(np.float32) + + # cv2.remap can handle uint8, float32, float64 out = cv2.remap(padded, self._coor_x, self._coor_y, interpolation=self._order) # pyright: ignore + + if source_dtype: + out = out.astype(source_dtype) else: out = map_coordinates( padded, @@ -532,9 +544,22 @@ def __call__(self, cube_faces: NDArray[DType]) -> NDArray[DType]: padded = self._pad(cube_faces) if self._use_cv2: + if padded.dtype == np.float16: + source_dtype = np.float16 + else: + source_dtype = None + + if source_dtype: + padded = padded.astype(np.float32) + w = padded.shape[-1] v_img = padded.reshape(-1, w) + + # cv2.remap can handle uint8, float32, float64 out = cv2.remap(v_img, self._coor_x, self._coor_y, interpolation=self._order) # pyright: ignore + + if source_dtype: + out = out.astype(source_dtype) else: out = map_coordinates(padded, (self._tp, self._coor_y, self._coor_x), order=self._order) return out # pyright: ignore[reportReturnType] diff --git a/pyproject.toml b/pyproject.toml index ebea78d..7af62b8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -102,6 +102,7 @@ ignore = [ "E501", "F401", "N806", + "SIM108", # Use ternary operator "PGH003", # Use specific rule codes when ignoring type issues "TRY003", # Avoid specifying messages outside exception class; overly strict, especially for ValueError ] diff --git a/tests/test_main.py b/tests/test_main.py index 36de36a..39fa3a4 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -124,3 +124,24 @@ def test_e2c_list_mono(equirec_image_mono, list_image_mono): assert list_diff[3].mean() < AVG_DIFF_THRESH assert list_diff[4].mean() < AVG_DIFF_THRESH assert list_diff[5].mean() < AVG_DIFF_THRESH + + +@pytest.mark.parametrize("dtype", [np.float16, np.float32, np.float64]) +def test_e2c_dtype(equirec_image, dtype): + equirec_image = equirec_image.astype(dtype) + actual = py360convert.e2c(equirec_image, cube_format="horizon") + assert actual.dtype == dtype + + +@pytest.mark.parametrize("dtype", [np.float16, np.float32, np.float64]) +def test_e2p_dtype(equirec_image, dtype): + equirec_image = equirec_image.astype(dtype) + actual = py360convert.e2p(equirec_image, 90, 50, 120, (512, 512)) + assert actual.dtype == dtype + + +@pytest.mark.parametrize("dtype", [np.float16, np.float32, np.float64]) +def test_c2e_dtype(dice_image, dtype): + dice_image = dice_image.astype(dtype) + actual = py360convert.c2e(dice_image, 512, 1024, cube_format="dice") + assert actual.dtype == dtype From b6e59bc9898ba7b8d486c0858dcdf0084d6e7cd6 Mon Sep 17 00:00:00 2001 From: Brian Pugh Date: Fri, 3 Jan 2025 20:00:40 -0500 Subject: [PATCH 2/3] fix invalid py3.9 type hint. Fix perspective h/w --- py360convert/e2p.py | 15 +++++++++++++-- py360convert/utils.py | 16 +++++++++++----- 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/py360convert/e2p.py b/py360convert/e2p.py index 1557ae5..96c09bb 100644 --- a/py360convert/e2p.py +++ b/py360convert/e2p.py @@ -14,7 +14,7 @@ def e2p( e_img: NDArray[DType], - fov_deg: Union[float, int, tuple[float | int, float | int]], + fov_deg: Union[float, int, tuple[Union[float, int], Union[float, int]]], u_deg: float, v_deg: float, out_hw: tuple[int, int], @@ -65,7 +65,18 @@ def e2p( u = -float(np.deg2rad(u_deg)) v = float(np.deg2rad(v_deg)) in_rot = float(np.deg2rad(in_rot_deg)) - sampler = EquirecSampler.from_perspective(h_fov, v_fov, u, v, in_rot, h, w, order) + sampler = EquirecSampler.from_perspective( + h_fov, + v_fov, + u, + v, + in_rot, + h, + w, + out_hw[0], + out_hw[0], + order, + ) pers_img = np.stack([sampler(e_img[..., i]) for i in range(e_img.shape[2])], axis=-1) return pers_img[..., 0] if squeeze else pers_img diff --git a/py360convert/utils.py b/py360convert/utils.py index 05648ac..6163b09 100644 --- a/py360convert/utils.py +++ b/py360convert/utils.py @@ -431,7 +431,9 @@ def from_cubemap(cls, face_w: int, h: int, w: int, order: int): @classmethod @lru_cache(_CACHE_SIZE) - def from_perspective(cls, h_fov: float, v_fov: float, u, v, in_rot: float, h: int, w: int, order: int): + def from_perspective( + cls, h_fov: float, v_fov: float, u, v, in_rot: float, in_h: int, in_w: int, out_h, out_w, order: int + ): """Construct a EquirecSampler from perspective specs. Parameters @@ -446,16 +448,20 @@ def from_perspective(cls, h_fov: float, v_fov: float, u, v, in_rot: float, h: in Vertical viewing angle in radians in_rot: float Inplane rotation in radians. - h: int + in_h: int Height of input equirec image. - w: int + in_w: int Width of input equirec image. + out_h: int + Height of output perspective image. + out_w: int + Width of output perspective image. order: int The order of the spline interpolation. See ``scipy.ndimage.map_coordinates``. """ - xyz = xyzpers(h_fov, v_fov, u, v, (h, w), in_rot) + xyz = xyzpers(h_fov, v_fov, u, v, (out_h, out_w), in_rot) u, v = xyz2uv(xyz) - coor_x, coor_y = uv2coor(u, v, h, w) + coor_x, coor_y = uv2coor(u, v, in_h, in_w) return cls(coor_x, coor_y, order=order) From d814cdeb11d9f1544b5afeef671ccb204b1593a8 Mon Sep 17 00:00:00 2001 From: Brian Pugh Date: Fri, 3 Jan 2025 20:09:34 -0500 Subject: [PATCH 3/3] map_coordinates also cannot handle np.float16 --- py360convert/utils.py | 47 +++++++++++++++++++++++-------------------- 1 file changed, 25 insertions(+), 22 deletions(-) diff --git a/py360convert/utils.py b/py360convert/utils.py index 6163b09..84b1960 100644 --- a/py360convert/utils.py +++ b/py360convert/utils.py @@ -376,28 +376,29 @@ def __init__( self._order = order def __call__(self, img: NDArray[DType]) -> NDArray[DType]: - padded = self._pad(img) - if self._use_cv2: - if padded.dtype == np.float16: - source_dtype = np.float16 - else: - source_dtype = None + if img.dtype == np.float16: + source_dtype = np.float16 + else: + source_dtype = None - if source_dtype: - padded = padded.astype(np.float32) + if source_dtype: + img = img.astype(np.float32) # pyright: ignore + padded = self._pad(img) + if self._use_cv2: # cv2.remap can handle uint8, float32, float64 out = cv2.remap(padded, self._coor_x, self._coor_y, interpolation=self._order) # pyright: ignore - - if source_dtype: - out = out.astype(source_dtype) else: + # map_coordinates can handle uint8, float32, float64 out = map_coordinates( padded, (self._coor_y, self._coor_x), order=self._order, )[..., 0] + if source_dtype: + out = out.astype(source_dtype) + return out # pyright: ignore[reportReturnType] def _pad(self, img: NDArray[DType]) -> NDArray[DType]: @@ -548,26 +549,28 @@ def __call__(self, cube_faces: NDArray[DType]) -> NDArray[DType]: if w != self._w: raise ValueError("Input width {w} doesn't match expected height {self._w}.") - padded = self._pad(cube_faces) - if self._use_cv2: - if padded.dtype == np.float16: - source_dtype = np.float16 - else: - source_dtype = None + if cube_faces.dtype == np.float16: + source_dtype = np.float16 + else: + source_dtype = None - if source_dtype: - padded = padded.astype(np.float32) + if source_dtype: + cube_faces = cube_faces.astype(np.float32) # pyright: ignore + padded = self._pad(cube_faces) + if self._use_cv2: w = padded.shape[-1] v_img = padded.reshape(-1, w) # cv2.remap can handle uint8, float32, float64 out = cv2.remap(v_img, self._coor_x, self._coor_y, interpolation=self._order) # pyright: ignore - - if source_dtype: - out = out.astype(source_dtype) else: + # map_coordinates can handle uint8, float32, float64 out = map_coordinates(padded, (self._tp, self._coor_y, self._coor_x), order=self._order) + + if source_dtype: + out = out.astype(source_dtype) + return out # pyright: ignore[reportReturnType] def _pad(self, cube_faces: NDArray[DType]) -> NDArray[DType]: