docstr: Searches for the requested number of prototypes. Note that the algorithm can return a lower number of
  prototypes than the requested one. To increase the number of prototypes, reduce the epsilon-ball radius
  (`eps`), and the penalty for adding a prototype (`lambda_penalty`).
  
  Parameters
  ----------
  num_prototypes
      Maximum number of prototypes to be selected.
  
  Returns
  -------
  An `Explanation` object containing the prototypes, prototype indices and prototype labels with additional         metadata as attributes.
docstr_tok: ["Searches","for","the","requested","number","of","prototypes",".","Note","that","the","algorithm","can","return","a","lower","number","of","prototypes","than","the","requested","one",".","To","increase","the","number","of","prototypes",",","reduce","the","epsilon-ball","radius","(","`","eps","`",")",",","and","the","penalty","for","adding","a","prototype","(","`","lambda_penalty","`",")",".","Parameters","--","--","--","--","--","num_prototypes","Maximum","number","of","prototypes","to","be","selected",".","Returns","--","--","--","-","An","`","Explanation","`","object","containing","the","prototypes",",","prototype","indices","and","prototype","labels","with","additional","metadata","as","attributes","."]
code: def summarise(self, num_prototypes: int = 1) -> Explanation:
      """
          Searches for the requested number of prototypes. Note that the algorithm can return a lower number of
          prototypes than the requested one. To increase the number of prototypes, reduce the epsilon-ball radius
          (`eps`), and the penalty for adding a prototype (`lambda_penalty`).
  
          Parameters
          ----------
          num_prototypes
              Maximum number of prototypes to be selected.
  
          Returns
          -------
          An `Explanation` object containing the prototypes, prototype indices and prototype labels with additional \
          metadata as attributes.
          """
      if num_prototypes > len(self.Z):
          num_prototypes = len(self.Z)
          logger.warning(
              "The number of prototypes requested is larger than the number of elements from "
              f"the prototypes selection set. Automatically setting `num_prototypes={num_prototypes}`."
          )
  
      # dictionary of prototypes indices for each class
      protos: Dict[int, List[int]] = {
          l: [] for l in range(len(self.label_mapping))
      }  # noqa: E741
      # set of available prototypes indices. Note that initially we start with the entire set of Z,
      # but as the algorithm progresses, we remove the indices of the prototypes that we already selected.
      available_indices = set(range(len(self.Z)))
      # matrix of size [NZ, NX], where NZ = len(Z) and NX = len(X)
      # represents a mask which indicates for each element z in Z what are the elements of X that are in an
      # epsilon ball centered in z.
      B = (self.kmatrix <= self.eps).astype(np.int32)
      # matrix of size [L, NX], where L is the number of labels
      # each row l indicates the elements from X that are covered by prototypes belonging to class l.
      B_P = np.zeros(
          (len(self.label_mapping), len(self.X)), dtype=np.int32
      )
      # matrix of size [L, NX]. Each row l indicates which elements form X are labeled as l.
      Xl = np.concatenate(
          [
              (self.y == l).reshape(1, -1)
              for l in range(len(self.label_mapping))
          ],
          axis=0,
      ).astype(
          np.int32
      )  # noqa: E741
  
      # vectorized implementation of the prototypes scores.
      # See paper (pag 8): https://arxiv.org/pdf/1202.5933.pdf for more details
      B_diff = (
          B[:, np.newaxis, :] - B_P[np.newaxis, :, :]
      )  # [NZ, 1, NX] - [1, L, NX] -> [NZ, L, NX]
      # [NZ, L, NX] + [1, L, NX] -> [NZ, L, NX]
      delta_xi_all = B_diff + Xl[np.newaxis, ...] >= 2
      # [NZ, L]. For every row z and every column l, we compute how many new instances belonging to class
      # l will be covered if we add the prototype z.
      delta_xi_summed = np.sum(delta_xi_all, axis=-1)
      # [NZ, 1, NX] +  [1, L, NX] -> [NZ, L, NX]
      delta_nu_all = (
          B[:, np.newaxis, :] + (1 - Xl[np.newaxis, ...]) >= 2
      )
      # [NZ, L]. For every row z and every column l, we compute how many new instances belonging to all the
      # other classes different from l will be covered if we add the prototype z.
      delta_nu_summed = np.sum(delta_nu_all, axis=-1)
      # compute the tradeoff score - each prototype tries to cover as many new elements as possible
      # belonging to the same class, while trying to avoid covering elements belonging to another class
      scores_all = (
          delta_xi_summed - delta_nu_summed - self.lambda_penalty
      )
  
      for _ in tqdm(range(num_prototypes), disable=(not self.verbose)):
          j = np.array(list(available_indices)).astype(np.int32)
          scores = scores_all[j]
  
          # stopping criterion. The number of the returned prototypes might be lower than
          # the number of requested prototypes.
          if np.all(scores < 0):
              break
  
          # find the index i of the best prototype and the class l that it covers.
          row, col = np.unravel_index(np.argmax(scores), scores.shape)
          i, l = j[row.item()], col.item()  # noqa: E741
  
          # update the score.
          covered = np.sum(
              delta_xi_all[:, l, B[i].astype(bool)], axis=-1
          )
          delta_xi_all[:, l, B[i].astype(bool)] = 0
          delta_xi_summed[:, l] -= covered
          scores_all[:, l] -= covered
  
          # add prototype to the corresponding list according to the class label l that it covers
          # and remove the index i from list of available indices.
          protos[l].append(i)
          available_indices.remove(i)
  
      return self._build_summary(protos)
  
imports: import logging
  from copy import deepcopy
  from typing import Callable, Dict, List, Optional, Tuple, Union
  import matplotlib.pyplot
  import numpy
  from matplotlib.offsetbox import AnnotationBbox, OffsetImage
  from skimage.transform import resize
  from sklearn.model_selection import KFold
  from sklearn.neighbors import KNeighborsClassifier
  from tqdm import tqdm
  from alibi.api.defaults import DEFAULT_DATA_PROTOSELECT, DEFAULT_META_PROTOSELECT
  from alibi.api.interfaces import Explanation, FitMixin, Summariser
  from alibi.utils.distance import batch_compute_kernel_matrix
  from alibi.utils.kernel import EuclideanDistance
label_desc: Use image node_id 3 for calling the ProtoSelect obj's underlying member method code with example usage: obj.summarise(num_prototypes) and returns: self