def __init__(
        self,
        window_size: int = 3,
        channels_first: bool = False,
        clip_values: "CLIP_VALUES_TYPE" | None = None,
        apply_fit: bool = False,
        apply_predict: bool = True,
        device_type: str = "gpu",
    ) -> None:
        """
        Create an instance of local spatial smoothing.

        :param window_size: Size of spatial smoothing window.
        :param channels_first: Set channels first or last.
        :param clip_values: Tuple of the form `(min, max)` representing the minimum and maximum values allowed
               for features.
        :param apply_fit: True if applied during fitting/training.
        :param apply_predict: True if applied during predicting.
        :param device_type: Type of device on which the classifier is run, either `gpu` or `cpu`.
        """

        super().__init__(
            device_type=device_type,
            apply_fit=apply_fit,
            apply_predict=apply_predict,
        )

        self.channels_first = channels_first
        self.window_size = window_size
        self.clip_values = clip_values
        self._check_params()

        from kornia.filters import MedianBlur

        class MedianBlurCustom(MedianBlur):
            """
            An ongoing effort to reproduce the median blur function in SciPy.
            """

            def __init__(self, kernel_size: tuple[int, int]) -> None:
                super().__init__(kernel_size)

                # Half-pad the input so that the output keeps the same shape.
                # * center pixel located lower right
                half_pad = [int(k % 2 == 0) for k in kernel_size]
                if hasattr(self, "padding"):
                    # kornia < 0.5.0
                    padding = self.padding
                else:
                    # kornia >= 0.5.0
                    from kornia.filters.median import _compute_zero_padding

                    padding = _compute_zero_padding(kernel_size)  # type: ignore
                self.p2d = [
                    int(padding[-1]) + half_pad[-1],  # type: ignore
                    int(padding[-1]),  # type: ignore
                    int(padding[-2]) + half_pad[-2],  # type: ignore
                    int(padding[-2]),  # type: ignore
                ]
                # PyTorch requires padding size should be less than the corresponding input dimension.

                if not hasattr(self, "kernel"):
                    # kornia >= 0.5.0
                    from kornia.filters.kernels import get_binary_kernel2d

                    self.kernel = get_binary_kernel2d(kernel_size)

            # pylint: disable=redefined-builtin
            def forward(self, input: "torch.Tensor"):  # type: ignore
                import torch
                import torch.nn.functional as F

                if not torch.is_tensor(input):
                    raise TypeError(f"Input type is not a torch.Tensor. Got {type(input)}")
                if not len(input.shape) == 4:
                    raise ValueError(f"Invalid input shape, we expect BxCxHxW. Got: {input.shape}")
                # prepare kernel
                batch_size, channels, height, width = input.shape
                kernel: torch.Tensor = self.kernel.to(input.device).to(input.dtype)
                # map the local window to single vector

                _input = input.reshape(batch_size * channels, 1, height, width)
                if input.dtype == torch.int64:
                    # "reflection_pad2d" not implemented for 'Long'
                    # "reflect" in scipy.ndimage.median_filter has no equivalence in F.pad.
                    # "reflect" in PyTorch maps to "mirror" in scipy.ndimage.median_filter.
                    _input = _input.to(torch.float32)
                    _input = F.pad(_input, self.p2d, "reflect")
                    _input = _input.to(torch.int64)
                else:
                    _input = F.pad(_input, self.p2d, "reflect")

                features: torch.Tensor = F.conv2d(_input, kernel, stride=1)
                features = features.view(batch_size, channels, -1, height, width)  # BxCx(K_h * K_w)xHxW

                # compute the median along the feature axis
                # * torch.median(), if window size even, use smaller value (e.g. median(4,5)=4)
                median: torch.Tensor = torch.median(features, dim=2)[0]
                return median

        self.median_blur = MedianBlurCustom(kernel_size=(self.window_size, self.window_size))

        Create an instance of local spatial smoothing.

        :param window_size: Size of spatial smoothing window.
        :param channels_first: Set channels first or last.
        :param clip_values: Tuple of the form `(min, max)` representing the minimum and maximum values allowed
               for features.
        :param apply_fit: True if applied during fitting/training.
        :param apply_predict: True if applied during predicting.
        :param device_type: Type of device on which the classifier is run, either `gpu` or `cpu`.
        
art/defences/preprocessor/spatial_smoothing_pytorch.py