Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
44db71c
implement additional cvcuda infra for all branches to avoid duplicate…
justincdavis Nov 25, 2025
e3dd700
update make_image_cvcuda to have default batch dim
justincdavis Nov 25, 2025
c035df1
add stanardized setup to main for easier updating of PRs and branches
justincdavis Dec 2, 2025
98d7dfb
update is_cvcuda_tensor
justincdavis Dec 2, 2025
ddc116d
add cvcuda to pil compatible to transforms by default
justincdavis Dec 2, 2025
e51dc7e
remove cvcuda from transform class
justincdavis Dec 2, 2025
e14e210
merge with main
justincdavis Dec 4, 2025
4939355
resolve more formatting naming
justincdavis Dec 4, 2025
ac82cea
draft initial gaussian_blur cvcuda kernel implementation
justincdavis Nov 18, 2025
b18fedf
fix: move cvcuda tests to centralized class, more guards againist no …
justincdavis Nov 20, 2025
5df3a7d
consolidate gaussian_blur_image to use new validate_kernel_size_and_s…
justincdavis Nov 24, 2025
9cd7582
resolve more review comments
justincdavis Nov 24, 2025
d9e3f83
match type hint in validate to gaussian_blur_image
justincdavis Nov 24, 2025
ccd4c1d
resolve tests failing due to cvcuda.Tensor
justincdavis Nov 26, 2025
b3d7814
fix guassian border mode to adhere to torch/opencv
justincdavis Nov 26, 2025
160e047
remove unneeded cvcuda setup
justincdavis Dec 2, 2025
5ce83b1
use assert_close
justincdavis Dec 2, 2025
3bcc517
update gaussian blur with main standards
justincdavis Dec 4, 2025
2edfdff
check input type on kernel for signature test
justincdavis Dec 4, 2025
1155607
Merge branch 'main' into feat/gaussian_cvcuda
justincdavis Dec 5, 2025
8c2cb57
minimize diff
justincdavis Dec 12, 2025
e886fc0
drop more unused duplication for shared infra branch
justincdavis Dec 12, 2025
71ea23c
resolve formatting change
justincdavis Dec 12, 2025
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
58 changes: 51 additions & 7 deletions test/test_transforms_v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import torchvision.transforms.v2 as transforms

from common_utils import (
assert_close,
assert_equal,
cache,
cpu_and_cuda,
Expand All @@ -41,7 +42,6 @@
)

from torch import nn
from torch.testing import assert_close
from torch.utils._pytree import tree_flatten, tree_map
from torch.utils.data import DataLoader, default_collate
from torchvision import tv_tensors
Expand Down Expand Up @@ -3936,7 +3936,15 @@ def test_kernel_video(self):

@pytest.mark.parametrize(
"make_input",
[make_image_tensor, make_image_pil, make_image, make_video],
[
make_image_tensor,
make_image_pil,
make_image,
make_video,
pytest.param(
make_image_cvcuda, marks=pytest.mark.skipif(not CVCUDA_AVAILABLE, reason="CVCUDA is not available")
),
],
)
def test_functional(self, make_input):
check_functional(F.gaussian_blur, make_input(), kernel_size=(3, 3))
Expand All @@ -3948,14 +3956,31 @@ def test_functional(self, make_input):
(F._misc._gaussian_blur_image_pil, PIL.Image.Image),
(F.gaussian_blur_image, tv_tensors.Image),
(F.gaussian_blur_video, tv_tensors.Video),
pytest.param(
F._misc._gaussian_blur_image_cvcuda,
None,
marks=pytest.mark.skipif(not CVCUDA_AVAILABLE, reason="CVCUDA is not available"),
),
],
)
def test_functional_signature(self, kernel, input_type):
if kernel is F._misc._gaussian_blur_image_cvcuda:
input_type = _import_cvcuda().Tensor
check_functional_kernel_signature_match(F.gaussian_blur, kernel=kernel, input_type=input_type)

@pytest.mark.parametrize(
"make_input",
[make_image_tensor, make_image_pil, make_image, make_bounding_boxes, make_segmentation_mask, make_video],
[
make_image_tensor,
make_image_pil,
make_image,
make_bounding_boxes,
make_segmentation_mask,
make_video,
pytest.param(
make_image_cvcuda, marks=pytest.mark.skipif(not CVCUDA_AVAILABLE, reason="CVCUDA is not available")
),
],
)
@pytest.mark.parametrize("device", cpu_and_cuda())
@pytest.mark.parametrize("sigma", [5, 2.0, (0.5, 2), [1.3, 2.7]])
Expand Down Expand Up @@ -4018,11 +4043,22 @@ def test_make_params(self, sigma):
((1, 26, 28), (23, 23), 1.7),
],
)
@pytest.mark.parametrize("dtype", [torch.float32, torch.float64, torch.float16])
@pytest.mark.parametrize("dtype", [torch.uint8, torch.float32, torch.float64, torch.float16])
@pytest.mark.parametrize("device", cpu_and_cuda())
def test_functional_image_correctness(self, dimensions, kernel_size, sigma, dtype, device):
@pytest.mark.parametrize(
"input_type",
[
tv_tensors.Image,
pytest.param(
"cvcuda.Tensor", marks=pytest.mark.skipif(not CVCUDA_AVAILABLE, reason="CVCUDA not available")
),
],
)
def test_functional_image_correctness(self, dimensions, kernel_size, sigma, dtype, device, input_type):
if dtype is torch.float16 and device == "cpu":
pytest.skip("The CPU implementation of float16 on CPU differs from opencv")
if (dtype != torch.float32 and dtype != torch.uint8) and input_type == "cvcuda.Tensor":
pytest.skip("CVCUDA does not support non-float32 or uint8 dtypes for gaussian blur")

num_channels, height, width = dimensions

Expand All @@ -4042,9 +4078,17 @@ def test_functional_image_correctness(self, dimensions, kernel_size, sigma, dtyp
device=device,
)

actual = F.gaussian_blur_image(image, kernel_size=kernel_size, sigma=sigma)
if input_type == "cvcuda.Tensor":
image = image.unsqueeze(0)
image = F.to_cvcuda_tensor(image)

torch.testing.assert_close(actual, expected, rtol=0, atol=1)
actual = F.gaussian_blur(image, kernel_size=kernel_size, sigma=sigma)

if input_type == "cvcuda.Tensor":
actual = F.cvcuda_to_tensor(actual)
actual = actual.squeeze(0).to(device=device)

assert_close(actual, expected, rtol=0, atol=1)


class TestGaussianNoise:
Expand Down
3 changes: 3 additions & 0 deletions torchvision/transforms/v2/_misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

from torchvision import transforms as _transforms, tv_tensors
from torchvision.transforms.v2 import functional as F, Transform
from torchvision.transforms.v2.functional._utils import _is_cvcuda_tensor

from ._utils import (
_parse_labels_getter,
Expand Down Expand Up @@ -192,6 +193,8 @@ class GaussianBlur(Transform):

_v1_transform_cls = _transforms.GaussianBlur

_transformed_types = Transform._transformed_types + (_is_cvcuda_tensor,)

def __init__(
self, kernel_size: Union[int, Sequence[int]], sigma: Union[int, float, Sequence[float]] = (0.1, 2.0)
) -> None:
Expand Down
47 changes: 40 additions & 7 deletions torchvision/transforms/v2/functional/_misc.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import math
from typing import Optional
from typing import List, Optional, Tuple, TYPE_CHECKING

import PIL.Image
import torch
Expand All @@ -13,7 +13,12 @@

from ._meta import _convert_bounding_box_format

from ._utils import _get_kernel, _register_kernel_internal, is_pure_tensor
from ._utils import _get_kernel, _import_cvcuda, _is_cvcuda_available, _register_kernel_internal, is_pure_tensor

CVCUDA_AVAILABLE = _is_cvcuda_available()

if TYPE_CHECKING:
import cvcuda # type: ignore[import-not-found]


def normalize(
Expand Down Expand Up @@ -99,11 +104,10 @@ def _get_gaussian_kernel2d(
return kernel2d


@_register_kernel_internal(gaussian_blur, torch.Tensor)
@_register_kernel_internal(gaussian_blur, tv_tensors.Image)
def gaussian_blur_image(
image: torch.Tensor, kernel_size: list[int], sigma: Optional[list[float]] = None
) -> torch.Tensor:
def _validate_kernel_size_and_sigma(
kernel_size: List[int],
sigma: Optional[List[float]] = None,
) -> Tuple[List[int], List[float]]:
# TODO: consider deprecating integers from sigma on the future
if isinstance(kernel_size, int):
kernel_size = [kernel_size, kernel_size]
Expand Down Expand Up @@ -132,6 +136,16 @@ def gaussian_blur_image(
if s <= 0.0:
raise ValueError(f"sigma should have positive values. Got {sigma}")

return kernel_size, sigma


@_register_kernel_internal(gaussian_blur, torch.Tensor)
@_register_kernel_internal(gaussian_blur, tv_tensors.Image)
def gaussian_blur_image(
image: torch.Tensor, kernel_size: list[int], sigma: Optional[list[float]] = None
) -> torch.Tensor:
kernel_size, sigma = _validate_kernel_size_and_sigma(kernel_size, sigma)

if image.numel() == 0:
return image

Expand Down Expand Up @@ -181,6 +195,25 @@ def gaussian_blur_video(
return gaussian_blur_image(video, kernel_size, sigma)


def _gaussian_blur_image_cvcuda(
image: "cvcuda.Tensor", kernel_size: list[int], sigma: Optional[list[float]] = None
) -> "cvcuda.Tensor":
cvcuda = _import_cvcuda()

kernel_size, sigma = _validate_kernel_size_and_sigma(kernel_size, sigma)

return cvcuda.gaussian(
image,
tuple(kernel_size),
tuple(sigma),
border=cvcuda.Border.REFLECT101,
)


if CVCUDA_AVAILABLE:
_register_kernel_internal(gaussian_blur, _import_cvcuda().Tensor)(_gaussian_blur_image_cvcuda)


def gaussian_noise(inpt: torch.Tensor, mean: float = 0.0, sigma: float = 0.1, clip: bool = True) -> torch.Tensor:
"""See :class:`~torchvision.transforms.v2.GaussianNoise`"""
if torch.jit.is_scripting():
Expand Down