code: def explain_prediction_keras_image(
      model,
      doc,
      image=None,  # type: Optional['PIL.Image.Image']
      targets=None,
      layer=None,
  ):
      """
      Explain an image-based model, highlighting what contributed in the image.
  
      :param numpy.ndarray doc:
          Input representing an image.
  
          Must have suitable format. Some models require tensors to be
          rank 4 in format `(batch_size, dims, ..., channels)` (channels last)
          or `(batch_size, channels, dims, ...)` (channels first),
          where `dims` is usually in order `height, width`
          and `batch_size` is 1 for a single image.
  
          If ``image`` argument is not given, an image will be created
          from ``doc``, where possible.
  
      :param image:
          Pillow image over which to overlay the heatmap.
          Corresponds to the input ``doc``.
      :type image: PIL.Image.Image, optional
  
  
      See :func:`eli5.keras.explain_prediction.explain_prediction_keras` 
      for a description of ``model``, ``doc``, ``targets``, and ``layer`` parameters.
  
  
      Returns
      -------
      expl : eli5.base.Explanation
        An :class:`eli5.base.Explanation` object with the following attributes:
            * ``image`` a Pillow image representing the input.
            * ``targets`` a list of :class:`eli5.base.TargetExplanation` objects \
                for each target. Currently only 1 target is supported.
        The :class:`eli5.base.TargetExplanation` objects will have the following attributes:
            * ``heatmap`` a rank 2 numpy array with the localization map \
              values as floats.
            * ``target`` ID of target class.
            * ``score`` value for predicted class.
      """
      if image is None:
          image = _extract_image(doc)
      _validate_doc(model, doc)
      activation_layer = _get_activation_layer(model, layer)
  
      # TODO: maybe do the sum / loss calculation in this function and pass it to gradcam.
      # This would be consistent with what is done in
      # https://github.com/ramprs/grad-cam/blob/master/misc/utils.lua
      # and https://github.com/ramprs/grad-cam/blob/master/classification.lua
      values = gradcam_backend(model, doc, targets, activation_layer)
      weights, activations, grads, predicted_idx, predicted_val = values
      heatmap = gradcam(weights, activations)
  
      return Explanation(
          model.name,
          description=DESCRIPTION_KERAS,
          error="",
          method="Grad-CAM",
          image=image,
          targets=[
              TargetExplanation(
                  predicted_idx,
                  score=predicted_val,  # for now we keep the prediction in the .score field (not .proba)
                  heatmap=heatmap,  # 2D [0, 1] numpy array
              )
          ],
          is_regression=False,  # might be relevant later when explaining for regression tasks
          highlight_spaces=None,  # might be relevant later when explaining text models
      )
  
imports: from __future__ import absolute_import
  from typing import Union, Optional, Callable, Tuple, List, TYPE_CHECKING
  import numpy
  import keras
  import keras.backend
  from keras.models import Model
  from keras.layers import Layer
  from keras.layers import Conv2D, MaxPooling2D, AveragePooling2D, GlobalMaxPooling2D, GlobalAveragePooling2D
  from keras.preprocessing.image import array_to_img
  from eli5.base import Explanation, TargetExplanation
  from eli5.explain import explain_prediction
  from .gradcam import gradcam, gradcam_backend
label_desc: Use image node_id 3 for calling a global function with example usage: explain_prediction_keras_image(model, doc, image, targets, layer) and returns: Explanation