              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
              the batch
          """
          logger.trace("Cropping training images info: (filenames: %s, side: '%s')", filenames, side)
          aligned = [item["aligned_face"] for item in cache]
  
          if self._face_cache.extract_version == 1.0:
 
</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         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.
 </s> add         :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 </s> remove         if self._face_cache.extract_version == 1.0:
            # Legacy extract. Don't crop, just return batch with landmarks
            return batch, np.array([face.landmarks for face in aligned])
 </s> add         for filename, src_points in zip(filenames, batch_src_points):
            closest = (np.mean(np.square(src_points - dst_points), axis=(1, 2))).argsort()[:10]
            closest_matches = tuple(dst_landmarks[i][0] for i in closest)
            self._nearest_landmarks[os.path.basename(filename)] = closest_matches
            batch_closest_matches.append(closest_matches)
        logger.trace("Cached closest matches")  # type:ignore
        return batch_closest_matches </s> remove         with self._lock:
            for filename, meta in tqdm(read_image_meta_batch(filenames),
                                       desc="WTL: Caching Landmarks ({})".format(side.upper()),
                                       total=len(filenames),
                                       leave=False):
                if "itxt" not in meta or "alignments" not in meta["itxt"]:
                    raise FaceswapError(f"Invalid face image found. Aborting: '{filename}'")

                size = meta["width"]
                meta = meta["itxt"]
                # Version Check
                self._validate_version(meta, filename)
                detected_face = self._add_aligned_face(filename, meta["alignments"], size)
                self._cache[os.path.basename(filename)]["detected_face"] = detected_face
            self._partial_load = True

    def _validate_version(self, png_meta, filename):
        """ Validate that there are not a mix of v1.0 extracted faces and v2.x faces.
 </s> add         logger.trace("Cropping training images info: (filenames: %s, side: '%s')",  # type: ignore
                     filenames, self._side) </s> remove             List of full paths to image file names
        side: str
            The side of the model being trained on (`a` or `b`)
        """
        logger.trace("Process batch: (filenames: '%s', side: '%s')", filenames, side)

        if not self._face_cache.cache_full:
            batch = self._face_cache.cache_metadata(filenames)
        else:
            batch = read_image_batch(filenames)

        cache = self._face_cache.get_items(filenames)
        batch, landmarks = self._crop_to_center(filenames, cache, batch, side)
        batch = self._apply_mask(filenames, cache, batch, side)
        processed = dict()

        # Initialize processing training size on first image
        if not self._processing.initialized:
            self._processing.initialize(batch.shape[1])
 </s> add             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
              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.
  





          """
          logger.trace("Cropping training images info: (filenames: %s, side: '%s')", filenames, side)
          aligned = [item["aligned_face"] for item in cache]
  
          if self._face_cache.extract_version == 1.0:
keep keep keep keep 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