              '"a"' or '"b"' the side that is being processed
  
          Returns
          -------
          batch: :class:`numpy.ndarray`
              The centered faces cropped out of the loaded batch
          landmarks: :class:`numpy.ndarray`
              The aligned landmarks for this batch. NB: The aligned landmarks do not directly
              correspond to the size of the extracted face. They are scaled to the source training
              image, not the sub-image.
  
          Raises
          ------
          FaceswapError
              If Alignment information is not available for any of the images being loaded in
 
</s> remove         Raises
        ------
        FaceswapError
            If Alignment information is not available for any of the images being loaded in
            the batch
 </s> add  </s> remove             The list of filenames that correspond to this batch
        cache: list
            The list of cached items (aligned faces, masks etc.) corresponding to the batch
        batch: :class:`numpy.ndarray`
            The batch of faces that have been loaded from disk
        side: str
            '"a"' or '"b"' the side that is being processed
 </s> add             Filenames for current batch
        batch_src_points: :class:`np.ndarray`
            The source landmarks for the current batch </s> remove         filenames: list
            The list of filenames that correspond to this batch
        cache: list
            The list of cached items (aligned faces, masks etc.) corresponding to the batch
        batch: :class:`numpy.ndarray`
            The batch of faces that have been loaded from disk
        side: str
            '"a"' or '"b"' the side that is being processed
 </s> add         images: :class:`numpy.ndarray`
            The original batch of images as loaded from disk.
        detected_faces: list
            List of :class:`~lib.align.DetectedFace` for the current batch </s> remove         logger.trace("Cropping training images info: (filenames: %s, side: '%s')", filenames, side)
        aligned = [item["aligned_face"] for item in cache]
 </s> add         logger.trace("Caching closest matches")  # type:ignore
        dst_landmarks = list(landmarks.items())
        dst_points = np.array([lm[1] for lm in dst_landmarks])
        batch_closest_matches: List[Tuple[str, ...]] = [] </s> remove         filename: str
            The file path for the current image
        detected_face: :class:`lib.align.DetectedFace`
            The detected face object that holds the masks
 </s> add         filenames: list
            List of full paths to image file names for a single batch
        images: :class:`numpy.ndarray`
            The batch of faces corresponding to the filenames
        detected_faces: list
            List of :class:`~lib.align.DetectedFace` objects with aligned data and masks loaded for
            the current batch
        batch: :class:`numpy.ndarray`
            The pre-allocated batch with images and masks populated for the selected coverage and
            centering
        :class:`np.ndarray`
            Randomly selected closest matches from the other side's landmarks
        """
        logger.trace("Retrieving closest matched landmarks: (filenames: '%s', "  # type: ignore
                     "src_points: '%s')", filenames, batch_src_points)
        lm_side: Literal["a", "b"] = "a" if self._side == "b" else "b"
        other_cache = get_cache(lm_side)
        landmarks = other_cache.aligned_landmarks

        try:
            closest_matches = [self._nearest_landmarks[os.path.basename(filename)]
                               for filename in filenames]
        except KeyError:
            # Resize mismatched training image size landmarks
            sizes = {side: cache.size for side, cache in zip((self._side, lm_side),
                                                             (self._face_cache, other_cache))}
            if len(set(sizes.values())) > 1:
                scale = sizes[self._side] / sizes[lm_side]
                landmarks = {key: lms * scale for key, lms in landmarks.items()}
            closest_matches = self._cache_closest_matches(filenames, batch_src_points, landmarks)

        batch_dst_points = np.array([landmarks[choice(fname)] for fname in closest_matches])
        logger.trace("Returning: (batch_dst_points: %s)", batch_dst_points.shape)  # type: ignore
        return batch_dst_points

    def _cache_closest_matches(self,
                               filenames: List[str],
                               batch_src_points: np.ndarray,
                               landmarks: Dict[str, np.ndarray]) -> List[Tuple[str, ...]]:
        """ Cache the nearest landmarks for this batch

        Parameters
        ----------
        filenames: list
            Filenames for current batch
        batch_src_points: :class:`np.ndarray`
            The source landmarks for the current batch
        landmarks: dict
            The destination landmarks with associated filenames
              '"a"' or '"b"' the side that is being processed
  
          Returns
          -------
        :class:`np.ndarray`
            Randomly selected closest matches from the other side's landmarks
        """
        logger.trace("Retrieving closest matched landmarks: (filenames: '%s', "  # type: ignore
                     "src_points: '%s')", filenames, batch_src_points)
        lm_side: Literal["a", "b"] = "a" if self._side == "b" else "b"
        other_cache = get_cache(lm_side)
        landmarks = other_cache.aligned_landmarks

        try:
            closest_matches = [self._nearest_landmarks[os.path.basename(filename)]
                               for filename in filenames]
        except KeyError:
            # Resize mismatched training image size landmarks
            sizes = {side: cache.size for side, cache in zip((self._side, lm_side),
                                                             (self._face_cache, other_cache))}
            if len(set(sizes.values())) > 1:
                scale = sizes[self._side] / sizes[lm_side]
                landmarks = {key: lms * scale for key, lms in landmarks.items()}
            closest_matches = self._cache_closest_matches(filenames, batch_src_points, landmarks)

        batch_dst_points = np.array([landmarks[choice(fname)] for fname in closest_matches])
        logger.trace("Returning: (batch_dst_points: %s)", batch_dst_points.shape)  # type: ignore
        return batch_dst_points

    def _cache_closest_matches(self,
                               filenames: List[str],
                               batch_src_points: np.ndarray,
                               landmarks: Dict[str, np.ndarray]) -> List[Tuple[str, ...]]:
        """ Cache the nearest landmarks for this batch

        Parameters
        ----------
        filenames: list
            Filenames for current batch
        batch_src_points: :class:`np.ndarray`
            The source landmarks for the current batch
        landmarks: dict
            The destination landmarks with associated filenames
        :class:`np.ndarray`
            Randomly selected closest matches from the other side's landmarks
        """
        logger.trace("Retrieving closest matched landmarks: (filenames: '%s', "  # type: ignore
                     "src_points: '%s')", filenames, batch_src_points)
        lm_side: Literal["a", "b"] = "a" if self._side == "b" else "b"
        other_cache = get_cache(lm_side)
        landmarks = other_cache.aligned_landmarks

        try:
            closest_matches = [self._nearest_landmarks[os.path.basename(filename)]
                               for filename in filenames]
        except KeyError:
            # Resize mismatched training image size landmarks
            sizes = {side: cache.size for side, cache in zip((self._side, lm_side),
                                                             (self._face_cache, other_cache))}
            if len(set(sizes.values())) > 1:
                scale = sizes[self._side] / sizes[lm_side]
                landmarks = {key: lms * scale for key, lms in landmarks.items()}
            closest_matches = self._cache_closest_matches(filenames, batch_src_points, landmarks)

        batch_dst_points = np.array([landmarks[choice(fname)] for fname in closest_matches])
        logger.trace("Returning: (batch_dst_points: %s)", batch_dst_points.shape)  # type: ignore
        return batch_dst_points

    def _cache_closest_matches(self,
                               filenames: List[str],
                               batch_src_points: np.ndarray,
                               landmarks: Dict[str, np.ndarray]) -> List[Tuple[str, ...]]:
        """ Cache the nearest landmarks for this batch

        Parameters
        ----------
        filenames: list
            Filenames for current batch
        batch_src_points: :class:`np.ndarray`
            The source landmarks for the current batch
        landmarks: dict
            The destination landmarks with associated filenames
        :class:`np.ndarray`
            Randomly selected closest matches from the other side's landmarks
        """
        logger.trace("Retrieving closest matched landmarks: (filenames: '%s', "  # type: ignore
                     "src_points: '%s')", filenames, batch_src_points)
        lm_side: Literal["a", "b"] = "a" if self._side == "b" else "b"
        other_cache = get_cache(lm_side)
        landmarks = other_cache.aligned_landmarks

        try:
            closest_matches = [self._nearest_landmarks[os.path.basename(filename)]
                               for filename in filenames]
        except KeyError:
            # Resize mismatched training image size landmarks
            sizes = {side: cache.size for side, cache in zip((self._side, lm_side),
                                                             (self._face_cache, other_cache))}
            if len(set(sizes.values())) > 1:
                scale = sizes[self._side] / sizes[lm_side]
                landmarks = {key: lms * scale for key, lms in landmarks.items()}
            closest_matches = self._cache_closest_matches(filenames, batch_src_points, landmarks)

        batch_dst_points = np.array([landmarks[choice(fname)] for fname in closest_matches])
        logger.trace("Returning: (batch_dst_points: %s)", batch_dst_points.shape)  # type: ignore
        return batch_dst_points

    def _cache_closest_matches(self,
                               filenames: List[str],
                               batch_src_points: np.ndarray,
                               landmarks: Dict[str, np.ndarray]) -> List[Tuple[str, ...]]:
        """ Cache the nearest landmarks for this batch

        Parameters
        ----------
        filenames: list
            Filenames for current batch
        batch_src_points: :class:`np.ndarray`
            The source landmarks for the current batch
        landmarks: dict
            The destination landmarks with associated filenames
        :class:`np.ndarray`
            Randomly selected closest matches from the other side's landmarks
        """
        logger.trace("Retrieving closest matched landmarks: (filenames: '%s', "  # type: ignore
                     "src_points: '%s')", filenames, batch_src_points)
        lm_side: Literal["a", "b"] = "a" if self._side == "b" else "b"
        other_cache = get_cache(lm_side)
        landmarks = other_cache.aligned_landmarks

        try:
            closest_matches = [self._nearest_landmarks[os.path.basename(filename)]
                               for filename in filenames]
        except KeyError:
            # Resize mismatched training image size landmarks
            sizes = {side: cache.size for side, cache in zip((self._side, lm_side),
                                                             (self._face_cache, other_cache))}
            if len(set(sizes.values())) > 1:
                scale = sizes[self._side] / sizes[lm_side]
                landmarks = {key: lms * scale for key, lms in landmarks.items()}
            closest_matches = self._cache_closest_matches(filenames, batch_src_points, landmarks)

        batch_dst_points = np.array([landmarks[choice(fname)] for fname in closest_matches])
        logger.trace("Returning: (batch_dst_points: %s)", batch_dst_points.shape)  # type: ignore
        return batch_dst_points

    def _cache_closest_matches(self,
                               filenames: List[str],
                               batch_src_points: np.ndarray,
                               landmarks: Dict[str, np.ndarray]) -> List[Tuple[str, ...]]:
        """ Cache the nearest landmarks for this batch

        Parameters
        ----------
        filenames: list
            Filenames for current batch
        batch_src_points: :class:`np.ndarray`
            The source landmarks for the current batch
        landmarks: dict
            The destination landmarks with associated filenames
        :class:`np.ndarray`
            Randomly selected closest matches from the other side's landmarks
        """
        logger.trace("Retrieving closest matched landmarks: (filenames: '%s', "  # type: ignore
                     "src_points: '%s')", filenames, batch_src_points)
        lm_side: Literal["a", "b"] = "a" if self._side == "b" else "b"
        other_cache = get_cache(lm_side)
        landmarks = other_cache.aligned_landmarks

        try:
            closest_matches = [self._nearest_landmarks[os.path.basename(filename)]
                               for filename in filenames]
        except KeyError:
            # Resize mismatched training image size landmarks
            sizes = {side: cache.size for side, cache in zip((self._side, lm_side),
                                                             (self._face_cache, other_cache))}
            if len(set(sizes.values())) > 1:
                scale = sizes[self._side] / sizes[lm_side]
                landmarks = {key: lms * scale for key, lms in landmarks.items()}
            closest_matches = self._cache_closest_matches(filenames, batch_src_points, landmarks)

        batch_dst_points = np.array([landmarks[choice(fname)] for fname in closest_matches])
        logger.trace("Returning: (batch_dst_points: %s)", batch_dst_points.shape)  # type: ignore
        return batch_dst_points

    def _cache_closest_matches(self,
                               filenames: List[str],
                               batch_src_points: np.ndarray,
                               landmarks: Dict[str, np.ndarray]) -> List[Tuple[str, ...]]:
        """ Cache the nearest landmarks for this batch

        Parameters
        ----------
        filenames: list
            Filenames for current batch
        batch_src_points: :class:`np.ndarray`
            The source landmarks for the current batch
        landmarks: dict
            The destination landmarks with associated filenames
        :class:`np.ndarray`
            Randomly selected closest matches from the other side's landmarks
        """
        logger.trace("Retrieving closest matched landmarks: (filenames: '%s', "  # type: ignore
                     "src_points: '%s')", filenames, batch_src_points)
        lm_side: Literal["a", "b"] = "a" if self._side == "b" else "b"
        other_cache = get_cache(lm_side)
        landmarks = other_cache.aligned_landmarks

        try:
            closest_matches = [self._nearest_landmarks[os.path.basename(filename)]
                               for filename in filenames]
        except KeyError:
            # Resize mismatched training image size landmarks
            sizes = {side: cache.size for side, cache in zip((self._side, lm_side),
                                                             (self._face_cache, other_cache))}
            if len(set(sizes.values())) > 1:
                scale = sizes[self._side] / sizes[lm_side]
                landmarks = {key: lms * scale for key, lms in landmarks.items()}
            closest_matches = self._cache_closest_matches(filenames, batch_src_points, landmarks)

        batch_dst_points = np.array([landmarks[choice(fname)] for fname in closest_matches])
        logger.trace("Returning: (batch_dst_points: %s)", batch_dst_points.shape)  # type: ignore
        return batch_dst_points

    def _cache_closest_matches(self,
                               filenames: List[str],
                               batch_src_points: np.ndarray,
                               landmarks: Dict[str, np.ndarray]) -> List[Tuple[str, ...]]:
        """ Cache the nearest landmarks for this batch

        Parameters
        ----------
        filenames: list
            Filenames for current batch
        batch_src_points: :class:`np.ndarray`
            The source landmarks for the current batch
        landmarks: dict
            The destination landmarks with associated filenames
  
          Raises
          ------
          FaceswapError
              If Alignment information is not available for any of the images being loaded in
keep keep keep keep replace replace replace replace replace replace keep keep keep keep keep
 Data Augmentation update (#1263)

- lib.detected_face
    - Subclass Masks for Landmark based masks
    - Add training mask propery + methods to DetectedFace
  - lib.training_training
    - subclass TrainingDataGenerator for training and preview data
    - Split cache into own module
    - Reduce thread count to 1 to prevent image corruption + data re-use
    - Process on largest model input/output size rather than stored image size
    - Size and crop masks during caching stage
    - Implement ring buffer for data flow
    - Fix preview reload bug
  - augmentation
    - typing
    - switch color aug order
    - better initialization
    - Fix warp + landmark warp to correctly apply at different image scales
    - Slightly improved warp caching
    - Don't store whether image is_preview. Handle all data as training images implicitly
  - plugins.trainer: Typing and fixes to work with trainingdata refactor 
lib/training/generator.py