def _apply_texture(
        self,
        videos: "torch.Tensor",
        patch: "torch.Tensor",
        foreground: "torch.Tensor" | None,
        patch_points: np.ndarray | None,
    ) -> "torch.Tensor":
        """
        Apply texture over background and overlay foreground.

        :param videos: Video samples.
        :param patch: Patch to apply.
        :param foreground: Foreground mask.
        :param patch_points: Array of shape (nb_frames, 4, 2) containing four pairs of integers (height, width)
                             corresponding to the coordinates of the four corners top-left, top-right, bottom-right,
                             bottom-left of the transformed image in the coordinate-system of the original image.
        :return: Patched videos.
        """
        import torch
        import torchvision

        nb_samples = videos.shape[0]
        nb_frames = videos.shape[1]
        frame_height = videos.shape[2]
        frame_width = videos.shape[3]

        image_mask = self._get_patch_mask(nb_samples=nb_samples)
        image_mask = image_mask.float()

        patch = patch.float()
        padded_patch = torch.stack([patch] * nb_samples)

        if patch_points is None:
            pad_h_before = self.x_min
            pad_h_after = int(videos.shape[self.i_h + 1] - pad_h_before - image_mask.shape[self.i_h_patch + 1])

            pad_w_before = self.y_min
            pad_w_after = int(videos.shape[self.i_w + 1] - pad_w_before - image_mask.shape[self.i_w_patch + 1])

            image_mask = image_mask.permute(0, 3, 1, 2)

            image_mask = torchvision.transforms.functional.pad(
                img=image_mask,
                padding=[pad_w_before, pad_h_before, pad_w_after, pad_h_after],
                fill=0,
                padding_mode="constant",
            )

            image_mask = image_mask.permute(0, 2, 3, 1)

            image_mask = torch.unsqueeze(image_mask, dim=1)
            image_mask = torch.repeat_interleave(image_mask, dim=1, repeats=nb_frames)
            image_mask = image_mask.float()

            padded_patch = padded_patch.permute(0, 3, 1, 2)

            padded_patch = torchvision.transforms.functional.pad(
                img=padded_patch,
                padding=[pad_w_before, pad_h_before, pad_w_after, pad_h_after],
                fill=0,
                padding_mode="constant",
            )

            padded_patch = padded_patch.permute(0, 2, 3, 1)

            padded_patch = torch.unsqueeze(padded_patch, dim=1)
            padded_patch = torch.repeat_interleave(padded_patch, dim=1, repeats=nb_frames)

            padded_patch = padded_patch.float()

        else:

            startpoints = [[0, 0], [frame_width, 0], [frame_width, frame_height], [0, frame_height]]
            endpoints = np.zeros_like(patch_points)
            endpoints[:, :, 0] = patch_points[:, :, 1]
            endpoints[:, :, 1] = patch_points[:, :, 0]

            image_mask = image_mask.permute(0, 3, 1, 2)

            image_mask = torchvision.transforms.functional.resize(
                img=image_mask,
                size=[int(videos.shape[2]), int(videos.shape[3])],
                interpolation=torchvision.transforms.InterpolationMode.BILINEAR,
            )

            image_mask_list = []

            for i_frame in range(nb_frames):

                image_mask_i = torchvision.transforms.functional.perspective(
                    img=image_mask,
                    startpoints=startpoints,
                    endpoints=endpoints[i_frame],
                    interpolation=torchvision.transforms.InterpolationMode.BILINEAR,
                    fill=0,
                )

                image_mask_i = image_mask_i.permute(0, 2, 3, 1)

                image_mask_list.append(image_mask_i)

            image_mask = torch.stack(image_mask_list, dim=1)
            image_mask = image_mask.float()

            padded_patch = padded_patch.permute(0, 3, 1, 2)

            padded_patch = torchvision.transforms.functional.resize(
                img=padded_patch,
                size=[int(videos.shape[2]), int(videos.shape[3])],
                interpolation=torchvision.transforms.InterpolationMode.BILINEAR,
            )

            padded_patch_list = []

            for i_frame in range(nb_frames):
                padded_patch_i = torchvision.transforms.functional.perspective(
                    img=padded_patch,
                    startpoints=startpoints,
                    endpoints=endpoints[i_frame],
                    interpolation=torchvision.transforms.InterpolationMode.BILINEAR,
                    fill=0,
                )

                padded_patch_i = padded_patch_i.permute(0, 2, 3, 1)

                padded_patch_list.append(padded_patch_i)

            padded_patch = torch.stack(padded_patch_list, dim=1)
            padded_patch = padded_patch.float()

        inverted_mask = (
            torch.from_numpy(np.ones(shape=image_mask.shape, dtype=np.float32)).to(self.estimator.device) - image_mask
        )

        if foreground is not None:
            combined = (
                videos * inverted_mask
                + padded_patch * image_mask
                - padded_patch * ~foreground.bool()
                + videos * ~foreground.bool() * image_mask
            )
        else:
            combined = videos * inverted_mask + padded_patch * image_mask

        return combined

        Apply texture over background and overlay foreground.

        :param videos: Video samples.
        :param patch: Patch to apply.
        :param foreground: Foreground mask.
        :param patch_points: Array of shape (nb_frames, 4, 2) containing four pairs of integers (height, width)
                             corresponding to the coordinates of the four corners top-left, top-right, bottom-right,
                             bottom-left of the transformed image in the coordinate-system of the original image.
        :return: Patched videos.
        
art/attacks/evasion/adversarial_texture/adversarial_texture_pytorch.py