Skip to content

Commit 02c320a

Browse files
committed
Add CVCUDA backend for horizontal and vertical flip transforms
Summary: Implemented _horizontal_flip_image_cvcuda and _vertical_flip_image_cvcuda kernels using cvcuda.flip operator. The kernels are automatically registered when CVCUDA is available and route cvcuda.Tensor inputs appropriately. Test Plan: - Added test_functional_cvcuda and test_image_correctness_cvcuda tests - Verified parity between PyTorch and CVCUDA implementations - All tests pass with CVCUDA backend Reviewers: Subscribers: Tasks: Tags:
1 parent 617079d commit 02c320a

File tree

2 files changed

+56
-2
lines changed

2 files changed

+56
-2
lines changed

test/test_transforms_v2.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1249,6 +1249,10 @@ def test_kernel_video(self):
12491249
def test_functional(self, make_input):
12501250
check_functional(F.horizontal_flip, make_input())
12511251

1252+
@pytest.mark.skipif(not CVCUDA_AVAILABLE, reason="CVCUDA is not available")
1253+
def test_functional_cvcuda(self):
1254+
check_functional(F.horizontal_flip, make_image_cvcuda())
1255+
12521256
@pytest.mark.parametrize(
12531257
("kernel", "input_type"),
12541258
[
@@ -1291,6 +1295,20 @@ def test_image_correctness(self, fn):
12911295

12921296
torch.testing.assert_close(actual, expected)
12931297

1298+
@pytest.mark.skipif(not CVCUDA_AVAILABLE, reason="CVCUDA is not available")
1299+
@pytest.mark.parametrize(
1300+
"fn", [F.horizontal_flip, transform_cls_to_functional(transforms.RandomHorizontalFlip, p=1)]
1301+
)
1302+
def test_image_correctness_cvcuda(self, fn):
1303+
# Test parity between cvcuda and torchvision transforms
1304+
image = make_image_cvcuda()
1305+
1306+
actual = fn(image)
1307+
# CVCUDA output should match: torchvision(image) converted to CVCUDA
1308+
expected = F.to_cvcuda_tensor(fn(F.cvcuda_to_tensor(image)))
1309+
1310+
torch.testing.assert_close(F.cvcuda_to_tensor(actual), F.cvcuda_to_tensor(expected))
1311+
12941312
def _reference_horizontal_flip_bounding_boxes(self, bounding_boxes: tv_tensors.BoundingBoxes):
12951313
affine_matrix = np.array(
12961314
[
@@ -1865,6 +1883,10 @@ def test_kernel_video(self):
18651883
def test_functional(self, make_input):
18661884
check_functional(F.vertical_flip, make_input())
18671885

1886+
@pytest.mark.skipif(not CVCUDA_AVAILABLE, reason="CVCUDA is not available")
1887+
def test_functional_cvcuda(self):
1888+
check_functional(F.vertical_flip, make_image_cvcuda())
1889+
18681890
@pytest.mark.parametrize(
18691891
("kernel", "input_type"),
18701892
[
@@ -1905,6 +1927,18 @@ def test_image_correctness(self, fn):
19051927

19061928
torch.testing.assert_close(actual, expected)
19071929

1930+
@pytest.mark.skipif(not CVCUDA_AVAILABLE, reason="CVCUDA is not available")
1931+
@pytest.mark.parametrize("fn", [F.vertical_flip, transform_cls_to_functional(transforms.RandomVerticalFlip, p=1)])
1932+
def test_image_correctness_cvcuda(self, fn):
1933+
# Test parity between cvcuda and torchvision transforms
1934+
image = make_image_cvcuda()
1935+
1936+
actual = fn(image)
1937+
# CVCUDA output should match: torchvision(image) converted to CVCUDA
1938+
expected = F.to_cvcuda_tensor(fn(F.cvcuda_to_tensor(image)))
1939+
1940+
torch.testing.assert_close(F.cvcuda_to_tensor(actual), F.cvcuda_to_tensor(expected))
1941+
19081942
def _reference_vertical_flip_bounding_boxes(self, bounding_boxes: tv_tensors.BoundingBoxes):
19091943
affine_matrix = np.array(
19101944
[

torchvision/transforms/v2/functional/_geometry.py

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import numbers
33
import warnings
44
from collections.abc import Sequence
5-
from typing import Any, Optional, Union
5+
from typing import Any, Optional, TYPE_CHECKING, Union
66

77
import PIL.Image
88
import torch
@@ -26,7 +26,13 @@
2626

2727
from ._meta import _get_size_image_pil, clamp_bounding_boxes, convert_bounding_box_format
2828

29-
from ._utils import _FillTypeJIT, _get_kernel, _register_five_ten_crop_kernel_internal, _register_kernel_internal
29+
from ._utils import _FillTypeJIT, _get_kernel, _import_cvcuda, _is_cvcuda_available, _register_five_ten_crop_kernel_internal, _register_kernel_internal
30+
31+
CVCUDA_AVAILABLE = _is_cvcuda_available()
32+
if TYPE_CHECKING:
33+
import cvcuda
34+
if CVCUDA_AVAILABLE:
35+
cvcuda = _import_cvcuda()
3036

3137

3238
def _check_interpolation(interpolation: Union[InterpolationMode, int]) -> InterpolationMode:
@@ -61,6 +67,12 @@ def horizontal_flip_image(image: torch.Tensor) -> torch.Tensor:
6167
def _horizontal_flip_image_pil(image: PIL.Image.Image) -> PIL.Image.Image:
6268
return _FP.hflip(image)
6369

70+
def _horizontal_flip_image_cvcuda(image: "cvcuda.Tensor") -> "cvcuda.Tensor":
71+
return cvcuda.flip(image, flipCode=1)
72+
73+
74+
if CVCUDA_AVAILABLE:
75+
_horizontal_flip_image_cvcuda_registered = _register_kernel_internal(horizontal_flip, cvcuda.Tensor)(_horizontal_flip_image_cvcuda)
6476

6577
@_register_kernel_internal(horizontal_flip, tv_tensors.Mask)
6678
def horizontal_flip_mask(mask: torch.Tensor) -> torch.Tensor:
@@ -150,6 +162,14 @@ def _vertical_flip_image_pil(image: PIL.Image.Image) -> PIL.Image.Image:
150162
return _FP.vflip(image)
151163

152164

165+
def _vertical_flip_image_cvcuda(image: "cvcuda.Tensor") -> "cvcuda.Tensor":
166+
return cvcuda.flip(image, flipCode=0)
167+
168+
169+
if CVCUDA_AVAILABLE:
170+
_vertical_flip_image_cvcuda_registered = _register_kernel_internal(vertical_flip, cvcuda.Tensor)(_vertical_flip_image_cvcuda)
171+
172+
153173
@_register_kernel_internal(vertical_flip, tv_tensors.Mask)
154174
def vertical_flip_mask(mask: torch.Tensor) -> torch.Tensor:
155175
return vertical_flip_image(mask)

0 commit comments

Comments
 (0)