docstr: Fit the summariser. This step forms the kernel matrix in memory which has a shape of `NX x NX`,
  where `NX` is  the number of instances in `X`, if the optional dataset `Z` is not provided. Otherwise, if
  the optional dataset `Z` is provided, the kernel matrix has a shape of `NZ x NX`, where `NZ` is the
  number of instances in `Z`.
  
  Parameters
  ---------
  X
      Dataset to be summarised.
  y
      Labels of the dataset `X` to be summarised. The labels are expected to be represented as integers
      `[0, 1, ..., L-1]`, where `L` is the number of classes in the dataset `X`.
  Z
      Optional dataset to choose the prototypes from. If ``Z=None``, the prototypes will be selected from the
      dataset `X`. Otherwise, if `Z` is provided, the dataset to be summarised is still `X`, but
      it is summarised by prototypes belonging to the dataset `Z`.
  
  Returns
  -------
  self
      Reference to itself.
docstr_tok: ["Fit","the","summariser",".","This","step","forms","the","kernel","matrix","in","memory","which","has","a","shape","of","`","NX","x","NX","`",",","where","`","NX","`","is","the","number","of","instances","in","`","X","`",",","if","the","optional","dataset","`","Z","`","is","not","provided",".","Otherwise",",","if","the","optional","dataset","`","Z","`","is","provided",",","the","kernel","matrix","has","a","shape","of","`","NZ","x","NX","`",",","where","`","NZ","`","is","the","number","of","instances","in","`","Z","`",".","Parameters","--","--","--","--","-","X","Dataset","to","be","summarised",".","y","Labels","of","the","dataset","`","X","`","to","be","summarised",".","The","labels","are","expected","to","be","represented","as","integers","`","[","0",",","1",",","...",",","L-1","]","`",",","where","`","L","`","is","the","number","of","classes","in","the","dataset","`","X","`",".","Z","Optional","dataset","to","choose","the","prototypes","from",".","If","``","Z=None","``",",","the","prototypes","will","be","selected","from","the","dataset","`","X","`",".","Otherwise",",","if","`","Z","`","is","provided",",","the","dataset","to","be","summarised","is","still","`","X","`",",","but","it","is","summarised","by","prototypes","belonging","to","the","dataset","`","Z","`",".","Returns","--","--","--","-","self","Reference","to","itself","."]
code: def fit(
      self,  # type: ignore[override]
      X: Union[list, np.ndarray],
      y: Optional[np.ndarray] = None,
      Z: Optional[Union[list, np.ndarray]] = None,
  ) -> "ProtoSelect":
      """
      Fit the summariser. This step forms the kernel matrix in memory which has a shape of `NX x NX`,
      where `NX` is  the number of instances in `X`, if the optional dataset `Z` is not provided. Otherwise, if
      the optional dataset `Z` is provided, the kernel matrix has a shape of `NZ x NX`, where `NZ` is the
      number of instances in `Z`.
  
      Parameters
      ---------
      X
          Dataset to be summarised.
      y
          Labels of the dataset `X` to be summarised. The labels are expected to be represented as integers
          `[0, 1, ..., L-1]`, where `L` is the number of classes in the dataset `X`.
      Z
          Optional dataset to choose the prototypes from. If ``Z=None``, the prototypes will be selected from the
          dataset `X`. Otherwise, if `Z` is provided, the dataset to be summarised is still `X`, but
          it is summarised by prototypes belonging to the dataset `Z`.
  
      Returns
      -------
      self
          Reference to itself.
      """
      if y is not None:
          y = y.flatten()
          if len(X) != len(y):
              raise ValueError(
                  "The number of data instances does not match the number of labels. "
                  f"Got len(X)={len(X)} and len(y)={len(y)}."
              )
  
      self.X = X
      # if the y is not provided, then consider that all elements belong to the same class. This means
      # that loss term which tries to avoid including in an epsilon ball elements belonging to other classes
      # will always be 0. Still the first term of the loss tries to cover as many examples as possible with
      # minimal overlap between the epsilon balls corresponding to the other prototypes.
      self.y = (
          y.astype(np.int32)
          if (y is not None)
          else np.zeros((len(X),), dtype=np.int32)
      )
  
      # redefine the labels, so they are in the interval [0, len(np.unique(y)) - 1].
      # For example, if the labels provided were [40, 51], internally, we relabel them as [0, 1].
      # This approach can reduce computation and memory allocation, as without the intermediate mapping we would
      # have to allocate memory corresponding to 52 labels, [0, ..., 51], for some internal matrices.
      self.label_mapping = {
          l: i for i, l in enumerate(np.unique(self.y))
      }
      self.label_inv_mapping = {
          v: k for k, v in self.label_mapping.items()
      }
      idx = np.nonzero(
          np.asarray(list(self.label_mapping.keys())) == self.y[:, None]
      )[1]
      self.y = np.asarray(list(self.label_mapping.values()))[idx]
  
      # if the set of prototypes is not provided, then find the prototypes belonging to the X dataset.
      self.Z = Z if (Z is not None) else self.X
      # initialize penalty for adding a prototype
      if self.lambda_penalty is None:
          self.lambda_penalty = 1.0 / len(self.Z)
          self.meta["params"].update(
              {"lambda_penalty": self.lambda_penalty}
          )
  
      self.kmatrix = batch_compute_kernel_matrix(
          x=self.Z,
          y=self.X,
          kernel=self.kernel_distance,
          batch_size=self.batch_size,
          preprocess_fn=self.preprocess_fn,
      )
  
      return self
  
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 2 for calling the ProtoSelect obj's underlying member method code with example usage: obj.fit(X, y, Z) and returns: self