def _apply_preprocessing_gradient(self, x, gradients, fit=False):
        """
        Apply the backward pass to the gradients through all preprocessing defences that have been applied to `x`
        and `y` in the forward pass. This function should only be called from function
        `_apply_preprocessing_gradient`.

        The method overrides art.estimators.estimator::LossGradientsMixin._apply_preprocessing_gradient().
        It requires all defenses to have a method estimate_forward().
        It converts numpy arrays to TensorFlow tensors first, then chains a series of defenses by calling
        defence.estimate_forward() which contains differentiable estimate of the operations. At the end,
        it converts TensorFlow tensors back to numpy arrays.

        :param x: Samples.
        :type x: Format as expected by the `model`
        :param gradients: Gradients before backward pass through preprocessing defences.
        :type gradients: Format as expected by the `model`
        :param fit: `True` if the gradients are computed during training.
        :return: Gradients after backward pass through preprocessing defences.
        :rtype: Format as expected by the `model`
        """
        import tensorflow as tf
        from art.preprocessing.standardisation_mean_std.numpy import StandardisationMeanStd
        from art.preprocessing.standardisation_mean_std.tensorflow import StandardisationMeanStdTensorFlow

        if not self.preprocessing_operations:
            return gradients

        input_is_tensor = isinstance(x, tf.Tensor)

        if self.all_framework_preprocessing and not (not input_is_tensor and x.dtype == object):
            with tf.GradientTape() as tape:
                # Convert np arrays to TensorFlow tensors.
                x = tf.convert_to_tensor(x, dtype=config.ART_NUMPY_DTYPE)
                tape.watch(x)
                gradients = tf.convert_to_tensor(gradients, dtype=config.ART_NUMPY_DTYPE)
                x_orig = x

                for preprocess in self.preprocessing_operations:
                    if fit:
                        if preprocess.apply_fit:
                            x = preprocess.estimate_forward(x)
                    else:
                        if preprocess.apply_predict:
                            x = preprocess.estimate_forward(x)

            x_grad = tape.gradient(target=x, sources=x_orig, output_gradients=gradients)

            # Convert torch tensors back to np arrays.
            gradients = x_grad.numpy()
            if gradients.shape != x_orig.shape:  # pragma: no cover
                raise ValueError(f"The input shape is {x.shape} while the gradient shape is {gradients.shape}")

        elif len(self.preprocessing_operations) == 1 or (
            len(self.preprocessing_operations) == 2
            and isinstance(
                self.preprocessing_operations[-1], (StandardisationMeanStd, StandardisationMeanStdTensorFlow)
            )
        ):
            # Compatible with non-TensorFlow defences if no chaining.
            for preprocess in self.preprocessing_operations[::-1]:
                if fit:
                    if preprocess.apply_fit:
                        gradients = preprocess.estimate_gradient(x, gradients)
                else:
                    if preprocess.apply_predict:
                        gradients = preprocess.estimate_gradient(x, gradients)

        else:
            raise NotImplementedError("The current combination of preprocessing types is not supported.")

        return gradients

        Apply the backward pass to the gradients through all preprocessing defences that have been applied to `x`
        and `y` in the forward pass. This function should only be called from function
        `_apply_preprocessing_gradient`.

        The method overrides art.estimators.estimator::LossGradientsMixin._apply_preprocessing_gradient().
        It requires all defenses to have a method estimate_forward().
        It converts numpy arrays to TensorFlow tensors first, then chains a series of defenses by calling
        defence.estimate_forward() which contains differentiable estimate of the operations. At the end,
        it converts TensorFlow tensors back to numpy arrays.

        :param x: Samples.
        :type x: Format as expected by the `model`
        :param gradients: Gradients before backward pass through preprocessing defences.
        :type gradients: Format as expected by the `model`
        :param fit: `True` if the gradients are computed during training.
        :return: Gradients after backward pass through preprocessing defences.
        :rtype: Format as expected by the `model`
        
art/estimators/tensorflow.py