code: def generate_captum_explanation(
      model,
      inputs: np.ndarray,
      targets: np.ndarray,
      device: Optional[str] = None,
      **kwargs,
  ) -> np.ndarray:
      """
      Generate explanation for a torch model with captum.
      Parameters
      ----------
      model: torch.nn.Module
          A model that is used for explanation.
      inputs: np.ndarray
           The inputs that ought to be explained.
      targets: np.ndarray
           The target lables that should be used in the explanation.
      device: string
          Indicated the device on which a torch.Tensor is or will be allocated: "cpu" or "gpu".
      kwargs: optional
              Keyword arguments. Pass as "explain_func_kwargs" dictionary when working with a metric class.
              Pass as regular kwargs when using the stnad-alone function. May include xai_lib_kwargs dictionary which includes keyword arguments for a method call.
  
              xai_lib: string
                  XAI library: captum, tf-explain or zennit.
              method: string
                  XAI method.
              xai_lib_kwargs: dict
                  Keyword arguments to be passed to the attribution function.
              channel_first: boolean, optional
                  Indicates of the image dimensions are channel first, or channel last.
                  Inferred from the input shape if None.
              reduce_axes: tuple
                  Indicates the indices of dimensions of the output explanation array to be summed. For example, an input
                  array of shape (8, 28, 28, 3) with keepdims=True and reduce_axes = (-1,) will return an array of shape
                  (8, 28, 28, -1). Passing "()" will keep the original dimensions.
              keepdims: boolean
                  Indicated if the reduced axes shall be preserved (True) or removed (False).
      Returns
      -------
      explanation: np.ndarray
           Returns np.ndarray of same shape as inputs.
      """
  
      channel_first = (
          kwargs["channel_first"]
          if "channel_first" in kwargs
          else infer_channel_first(inputs)
      )
  
      softmax = kwargs.get("softmax", None)
      if softmax is not None:
          warnings.warn(
              f"Softmax argument has been passed to the explanation function. Different XAI "
              f"methods may or may not require the output to go through softmax activation. "
              f"Make sure that your softmax argument choice aligns with the method intended usage.\n",
              category=UserWarning,
          )
          wrapped_model = get_wrapped_model(
              model, softmax=softmax, channel_first=channel_first
          )
          model = wrapped_model.get_softmax_arg_model()
  
      method = kwargs.get("method", "Gradient")
      xai_lib_kwargs = kwargs.get("xai_lib_kwargs", {})
  
      # Set model in evaluate mode.
      model.to(device)
      model.eval()
  
      if not isinstance(inputs, torch.Tensor):
          inputs = torch.Tensor(inputs).to(device)
  
      if not isinstance(targets, torch.Tensor):
          targets = torch.as_tensor(targets).to(device)
  
      assert 0 not in kwargs.get(
          "reduce_axes", [1]
      ), "Reduction over batch_axis is not available, please do not include axis 0 in 'reduce_axes' kwargs."
      assert len(kwargs.get("reduce_axes", [1])) <= inputs.ndim - 1, (
          "Cannot reduce attributions over more axes than each sample has dimensions, but got "
          "{} and  {}.".format(
              len(kwargs.get("reduce_axes", [1])), inputs.ndim - 1
          )
      )
  
      reduce_axes = {
          "axis": tuple(kwargs.get("reduce_axes", [1])),
          "keepdims": kwargs.get("keepdims", True),
      }
  
      # Prevent attribution summation for 2D-data. Recreate np.sum behavior when passing reduce_axes=(), i.e. no change.
      if (len(tuple(kwargs.get("reduce_axes", [1]))) == 0) | (
          inputs.ndim < 3
      ):
  
          def f_reduce_axes(a):
              return a
  
      else:
  
          def f_reduce_axes(a):
              return a.sum(**reduce_axes)
  
      explanation: torch.Tensor = torch.zeros_like(inputs)
  
      if method in constants.DEPRECATED_XAI_METHODS_CAPTUM:
          warnings.warn(
              f"Explanaiton method string {method} is deprecated. Use "
              f"{constants.DEPRECATED_XAI_METHODS_CAPTUM[method]} instead.\n",
              category=UserWarning,
          )
          method = constants.DEPRECATED_XAI_METHODS_CAPTUM[method]
  
      if method in ["GradientShap", "DeepLift", "DeepLiftShap"]:
          baselines = (
              kwargs["baseline"]
              if "baseline" in kwargs
              else torch.zeros_like(inputs)
          )
          attr_func = eval(method)
          explanation = f_reduce_axes(
              attr_func(model, **xai_lib_kwargs).attribute(
                  inputs=inputs,
                  target=targets,
                  baselines=baselines,
              )
          )
  
      elif method == "IntegratedGradients":
          baselines = (
              kwargs["baseline"]
              if "baseline" in kwargs
              else torch.zeros_like(inputs)
          )
          attr_func = eval(method)
          explanation = f_reduce_axes(
              attr_func(model, **xai_lib_kwargs).attribute(
                  inputs=inputs,
                  target=targets,
                  baselines=baselines,
                  n_steps=10,
                  method="riemann_trapezoid",
              )
          )
  
      elif method in [
          "InputXGradient",
          "Saliency",
          "FeatureAblation",
          "Deconvolution",
          "FeaturePermutation",
          "Lime",
          "KernelShap",
          "LRP",
      ]:
          attr_func = eval(method)
          explanation = f_reduce_axes(
              attr_func(model, **xai_lib_kwargs).attribute(
                  inputs=inputs, target=targets
              )
          )
  
      elif method == "Gradient":
          explanation = f_reduce_axes(
              Saliency(model, **xai_lib_kwargs).attribute(
                  inputs=inputs, target=targets, abs=False
              )
          )
  
      elif method == "Occlusion":
          window_shape = kwargs.get(
              "window", (1, *([4] * (inputs.ndim - 2)))
          )
          explanation = f_reduce_axes(
              Occlusion(model).attribute(
                  inputs=inputs,
                  target=targets,
                  sliding_window_shapes=window_shape,
              )
          )
  
      elif method in [
          "LayerGradCam",
          "GuidedGradCam",
          "LayerConductance",
          "LayerActivation",
          "InternalInfluence",
          "LayerGradientXActivation",
      ]:
          if "gc_layer" in kwargs:
              xai_lib_kwargs["layer"] = kwargs["gc_layer"]
  
          if "layer" not in xai_lib_kwargs:
              raise ValueError(
                  "Specify a convolutional layer name as 'gc_layer' to run GradCam."
              )
  
          if isinstance(xai_lib_kwargs["layer"], str):
              xai_lib_kwargs["layer"] = eval(xai_lib_kwargs["layer"])
  
          attr_func = eval(method)
  
          if method != "LayerActivation":
              explanation = attr_func(
                  model, **xai_lib_kwargs
              ).attribute(inputs=inputs, target=targets)
          else:
              explanation = attr_func(
                  model, **xai_lib_kwargs
              ).attribute(inputs=inputs)
  
          if "interpolate" in kwargs:
              if isinstance(kwargs["interpolate"], tuple):
                  if "interpolate_mode" in kwargs:
                      explanation = LayerGradCam.interpolate(
                          explanation,
                          kwargs["interpolate"],
                          interpolate_mode=kwargs["interpolate_mode"],
                      )
                  else:
                      explanation = LayerGradCam.interpolate(
                          explanation, kwargs["interpolate"]
                      )
          else:
              if explanation.shape[-1] != inputs.shape[-1]:
                  warnings.warn(
                      "Quantus requires GradCam attribution and input to correspond in "
                      "last dimensions, but got shapes {} and {}\n "
                      "Pass 'interpolate' argument to explanation function get matching dimensions.".format(
                          explanation.shape, inputs.shape
                      ),
                      category=UserWarning,
                  )
  
          explanation = f_reduce_axes(explanation)
  
      elif method == "Control Var. Sobel Filter":
          explanation = torch.zeros(size=inputs.shape)
  
          for i in range(len(explanation)):
              explanation[i] = torch.Tensor(
                  np.clip(
                      scipy.ndimage.sobel(inputs[i].cpu().numpy()), 0, 1
                  )
              )
          explanation = explanation.mean(**reduce_axes)
  
      elif method == "Control Var. Random Uniform":
          explanation = torch.rand(
              size=(inputs.shape[0], *inputs.shape[2:])
          )
  
      elif method == "Control Var. Constant":
          assert (
              "constant_value" in kwargs
          ), "Specify a 'constant_value' e.g., 0.0 or 'black' for pixel replacement."
  
          explanation = torch.zeros(size=inputs.shape)
  
          # Update the tensor with values per input x.
          for i in range(explanation.shape[0]):
              constant_value = get_baseline_value(
                  value=kwargs["constant_value"],
                  arr=inputs[i],
                  return_shape=(1,),
              )[0]
              explanation[i] = torch.Tensor().new_full(
                  size=explanation[0].shape, fill_value=constant_value
              )
  
          explanation = explanation.mean(**reduce_axes)
  
      else:
          raise KeyError(
              f"The selected {method} XAI method is not in the list of supported built-in Quantus XAI methods for Captum. "
              f"Please choose an XAI method that has already been implemented {constants.AVAILABLE_XAI_METHODS_CAPTUM}."
          )
  
      if isinstance(explanation, torch.Tensor):
          if explanation.requires_grad:
              explanation = explanation.cpu().detach().numpy()
          else:
              explanation = explanation.cpu().numpy()
  
      return explanation
  
file_path: Quantus/quantus/functions/explanation_func.py
imports: import warnings
  from importlib import util
  from typing import Optional, Union
  import numpy
  import quantus
  import scipy
  from quantus.helpers import constants
  from quantus.helpers import __EXTRAS__
  from quantus.helpers.model.model_interface import ModelInterface
  from quantus.helpers.utils import get_baseline_value, infer_channel_first, make_channel_last, get_wrapped_model
label_desc: Use image node_id 4 for calling a global function with example usage: generate_captum_explanation(model, inputs, targets, device) and returns: explanation, a, a