Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cv2.remap cannot handle float16; ensure output of sampler matches input dtype. #43

Merged
merged 3 commits into from
Jan 4, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions py360convert/e2p.py
Original file line number Diff line number Diff line change
Expand Up @@ -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],
Expand Down Expand Up @@ -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
44 changes: 39 additions & 5 deletions py360convert/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -376,16 +376,29 @@ def __init__(
self._order = order

def __call__(self, img: NDArray[DType]) -> NDArray[DType]:
if img.dtype == np.float16:
source_dtype = np.float16
else:
source_dtype = None

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
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]:
Expand Down Expand Up @@ -419,7 +432,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
Expand All @@ -434,16 +449,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)


Expand Down Expand Up @@ -530,13 +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}.")

if cube_faces.dtype == np.float16:
source_dtype = np.float16
else:
source_dtype = None

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
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]:
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
]
Expand Down
21 changes: 21 additions & 0 deletions tests/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Loading