def class_gradient(
        self,
        x: np.ndarray,
        label: int | list[int] | np.ndarray | None = None,
        training_mode: bool = False,
        **kwargs,
    ) -> np.ndarray:
        """
        Compute per-class derivatives w.r.t. `x`.

        :param x: Sample input with shape as expected by the model.
        :param label: Index of a specific per-class derivative. If an integer is provided, the gradient of that class
                      output is computed for all samples. If multiple values as provided, the first dimension should
                      match the batch size of `x`, and each value will be used as target for its corresponding sample in
                      `x`. If `None`, then gradients for all classes will be computed for each sample.
        :param training_mode: `True` for model set to training mode and `False` for model set to evaluation mode.
        :return: Array of gradients of input features w.r.t. each class in the form
                 `(batch_size, nb_classes, input_shape)` when computing for all classes, otherwise shape becomes
                 `(batch_size, 1, input_shape)` when `label` parameter is specified.
        """
        if not (  # pragma: no cover
            (label is None)
            or (isinstance(label, int) and label in range(self.nb_classes))
            or (
                isinstance(label, np.ndarray)
                and len(label.shape) == 1
                and (label < self.nb_classes).all()
                and label.shape[0] == x.shape[0]
            )
        ):
            raise ValueError(f"Label {label} is out of range.")

        # Compute the gradient and return
        if label is None:
            combined_grads = self._compute_combined_grads(x, label=None)

        elif isinstance(label, int):
            if label < self.nb_classes - 1:
                # Compute and return from the classifier gradients
                combined_grads = self.classifier.class_gradient(x=x, label=label, training_mode=training_mode, **kwargs)

            else:
                # First compute the classifier gradients
                classifier_grads = self.classifier.class_gradient(
                    x=x, label=None, training_mode=training_mode, **kwargs
                )

                # Then compute the detector gradients
                detector_grads = self.detector.class_gradient(x=x, label=0, training_mode=training_mode, **kwargs)

                # Chain the detector gradients for the first component
                classifier_preds = self.classifier.predict(x=x)
                maxind_classifier_preds = np.argmax(classifier_preds, axis=1)
                max_classifier_preds = classifier_preds[np.arange(x.shape[0]), maxind_classifier_preds]
                first_detector_grads = max_classifier_preds[:, None, None, None, None] * detector_grads

                # Chain the detector gradients for the second component
                max_classifier_grads = classifier_grads[np.arange(len(classifier_grads)), maxind_classifier_preds]
                detector_preds = self.detector.predict(x=x)
                second_detector_grads = max_classifier_grads * (detector_preds + 1)[:, None, None]
                second_detector_grads = second_detector_grads[None, ...]
                second_detector_grads = np.swapaxes(second_detector_grads, 0, 1)

                # Update detector gradients
                combined_grads = first_detector_grads + second_detector_grads

        else:
            # Compute indexes for classifier labels and detector labels
            classifier_idx = np.where(label < self.nb_classes - 1)
            detector_idx = np.where(label == self.nb_classes - 1)

            # Initialize the combined gradients
            combined_grads = np.zeros(shape=(x.shape[0], 1, x.shape[1], x.shape[2], x.shape[3]))

            # First compute the classifier gradients for classifier_idx
            if classifier_idx:
                combined_grads[classifier_idx] = self.classifier.class_gradient(
                    x=x[classifier_idx], label=label[classifier_idx].tolist(), training_mode=training_mode, **kwargs
                )

            # Then compute the detector gradients for detector_idx
            if detector_idx:
                # First compute the classifier gradients for detector_idx
                classifier_grads = self.classifier.class_gradient(
                    x=x[detector_idx], label=None, training_mode=training_mode, **kwargs
                )

                # Then compute the detector gradients for detector_idx
                detector_grads = self.detector.class_gradient(
                    x=x[detector_idx], label=0, training_mode=training_mode, **kwargs
                )

                # Chain the detector gradients for the first component
                classifier_preds = self.classifier.predict(x=x[detector_idx])
                maxind_classifier_preds = np.argmax(classifier_preds, axis=1)
                max_classifier_preds = classifier_preds[np.arange(len(detector_idx)), maxind_classifier_preds]
                first_detector_grads = max_classifier_preds[:, None, None, None, None] * detector_grads

                # Chain the detector gradients for the second component
                max_classifier_grads = classifier_grads[np.arange(len(classifier_grads)), maxind_classifier_preds]
                detector_preds = self.detector.predict(x=x[detector_idx])
                second_detector_grads = max_classifier_grads * (detector_preds + 1)[:, None, None]
                second_detector_grads = second_detector_grads[None, ...]
                second_detector_grads = np.swapaxes(second_detector_grads, 0, 1)

                # Update detector gradients
                detector_grads = first_detector_grads + second_detector_grads

                # Reassign the combined gradients
                combined_grads[detector_idx] = detector_grads

        return combined_grads

        Compute per-class derivatives w.r.t. `x`.

        :param x: Sample input with shape as expected by the model.
        :param label: Index of a specific per-class derivative. If an integer is provided, the gradient of that class
                      output is computed for all samples. If multiple values as provided, the first dimension should
                      match the batch size of `x`, and each value will be used as target for its corresponding sample in
                      `x`. If `None`, then gradients for all classes will be computed for each sample.
        :param training_mode: `True` for model set to training mode and `False` for model set to evaluation mode.
        :return: Array of gradients of input features w.r.t. each class in the form
                 `(batch_size, nb_classes, input_shape)` when computing for all classes, otherwise shape becomes
                 `(batch_size, 1, input_shape)` when `label` parameter is specified.
        
art/estimators/classification/detector_classifier.py