              The list of full paths to the images to load the metadata from
          side: str
              `"a"` or `"b"`. The side of the model being cached. Used for info output
          """
          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.
  
          Parameters
          ----------
          png_meta: dict
              The information held within the Faceswap PNG Header
 
</s> remove         Parameters
        ----------
        png_meta: dict
            The information held within the Faceswap PNG Header
        filename: str
            The full path to the file being validated
 </s> add         with futures.ThreadPoolExecutor() as executor:
            proc = {executor.submit(face.aligned.extract_face, img): idx
                    for idx, (face, img) in enumerate(zip(detected_faces, images))} </s> remove             The list of full paths to the images to load the metadata from
        side: str
            `"a"` or `"b"`. The side of the model being cached. Used for info output
 </s> add             The list of filenames that correspond to this batch
        images: :class:`numpy.ndarray`
            The batch of faces that have been loaded from disk
        detected_faces: list
            The list of :class:`lib.align.DetectedFace` items corresponding to the batch
        batch: :class:`np.ndarray`
            The pre-allocated array to hold this batch </s> remove         keys = [os.path.basename(filename) for filename in filenames]
        with self._lock:
            if _check_reset(self):
                self._reset_cache(False)

            needs_cache = [filename
                           for filename, key in zip(filenames, keys)
                           if not self._cache[key]["cached"]]
            logger.trace("Needs cache: %s", needs_cache)

            if not needs_cache:
                # Don't bother reading the metadata if no images in this batch need caching
                logger.debug("All metadata already cached for: %s", keys)
                return read_image_batch(filenames)

            batch, metadata = read_image_batch(filenames, with_metadata=True)

            if len(batch.shape) == 1:
                folder = os.path.dirname(filenames[0])
                details = [
                    "{0} ({1})".format(
                        key, f"{img.shape[1]}px" if isinstance(img, np.ndarray) else type(img))
                    for key, img in zip(keys, batch)]
                msg = (f"There are mismatched image sizes in the folder '{folder}'. All training "
                       "images for each side must have the same dimensions.\nThe batch that "
                       f"failed contains the following files:\n{details}.")
                raise FaceswapError(msg)

            # Populate items into cache
            for filename in needs_cache:
                key = os.path.basename(filename)
                meta = metadata[filenames.index(filename)]

                # Version Check
                self._validate_version(meta, filename)
                if self._partial_load:  # Faces already loaded for Warp-to-landmarks
                    detected_face = self._cache[key]["detected_face"]
                else:
                    detected_face = self._add_aligned_face(filename,
                                                           meta["alignments"],
                                                           batch.shape[1])

                self._add_mask(filename, detected_face)
                for area in ("eye", "mouth"):
                    self._add_localized_mask(filename, detected_face, area)

                self._cache[key]["cached"] = True
            # Update the :attr:`cache_full` attribute
            cache_full = all(item["cached"] for item in self._cache.values())
            if cache_full:
                logger.verbose("Cache filled: '%s'", os.path.dirname(filenames[0]))
                self._cache_full = cache_full

        return batch

    def pre_fill(self, filenames, side):
        """ When warp to landmarks is enabled, the cache must be pre-filled, as each side needs
        access to the other side's alignments.
 </s> add         if not self._face_cache.cache_full:
            raw_faces = self._face_cache.cache_metadata(filenames)
        else:
            raw_faces = read_image_batch(filenames)

        detected_faces = self._face_cache.get_items(filenames)
        logger.trace("filenames: %s, raw_faces: '%s', detected_faces: %s",  # type: ignore
                     filenames, raw_faces.shape, len(detected_faces))
        return raw_faces, detected_faces

    def _crop_to_coverage(self,
                          filenames: List[str],
                          images: np.ndarray,
                          detected_faces: List[DetectedFace],
                          batch: np.ndarray) -> None:
        """ Crops the training image out of the full extract image based on the centering and
        coveage used in the user's configuration settings.

        If legacy extract images are being used then this just returns the extracted batch with
        their corresponding landmarks.

        Uses thread pool execution for about a 33% speed increase @ 64 batch size </s> remove         Raises
        ------
        FaceswapError
            If a version 1.0 face appears in a 2.x set or vice versa
        """
        alignment_version = png_meta["source"]["alignments_version"]

        if not self._extract_version:
            logger.debug("Setting initial extract version: %s", alignment_version)
            self._extract_version = alignment_version
            if alignment_version == 1.0 and self._centering != "legacy":
                self._reset_cache(True)
            return
 </s> add             for future in futures.as_completed(proc):
                batch[proc[future], ..., :3] = future.result() </s> remove logger = logging.getLogger(__name__)  # pylint: disable=invalid-name

_FACE_CACHES = dict()


def _get_cache(side, filenames, config):
    """ Obtain a :class:`_Cache` object for the given side. If the object does not pre-exist then
    create it.

    Parameters
    ----------
    side: str
        `"a"` or `"b"`. The side of the model to obtain the cache for
    filenames: list
        The filenames of all the images. This can either be the full path or the base name. If the
        full paths are passed in, they are stripped to base name for use as the cache key.
    config: dict
        The user selected training configuration options

    Returns
    -------
    :class:`_Cache`
        The face meta information cache for the requested side
    """
    if not _FACE_CACHES.get(side):
        logger.debug("Creating cache. Side: %s", side)
        _FACE_CACHES[side] = _Cache(filenames, config)
    return _FACE_CACHES[side]


def _check_reset(face_cache):
    """ Check whether a given cache needs to be reset because a face centering change has been
    detected in the other cache.

    Parameters
    ----------
    face_cache: :class:`_Cache`
        The cache object that is checking whether it should reset

    Returns
    -------
    bool
        ``True`` if the given object should reset the cache, otherwise ``False``
    """
    check_cache = next((cache for cache in _FACE_CACHES.values() if cache != face_cache), None)
    retval = check_cache if check_cache is None else check_cache.check_reset()
    return retval
 </s> add if sys.version_info < (3, 8):
    from typing_extensions import get_args, Literal
else:
    from typing import get_args, Literal
        logger.trace("Cropping training images info: (filenames: %s, side: '%s')",  # type: ignore
                     filenames, self._side)
              The list of full paths to the images to load the metadata from
          side: str
              `"a"` or `"b"`. The side of the model being cached. Used for info output
          """
        logger.trace("Cropping training images info: (filenames: %s, side: '%s')",  # type: ignore
                     filenames, self._side)
        logger.trace("Cropping training images info: (filenames: %s, side: '%s')",  # type: ignore
                     filenames, self._side)
        logger.trace("Cropping training images info: (filenames: %s, side: '%s')",  # type: ignore
                     filenames, self._side)
        logger.trace("Cropping training images info: (filenames: %s, side: '%s')",  # type: ignore
                     filenames, self._side)
        logger.trace("Cropping training images info: (filenames: %s, side: '%s')",  # type: ignore
                     filenames, self._side)
        logger.trace("Cropping training images info: (filenames: %s, side: '%s')",  # type: ignore
                     filenames, self._side)
        logger.trace("Cropping training images info: (filenames: %s, side: '%s')",  # type: ignore
                     filenames, self._side)
        logger.trace("Cropping training images info: (filenames: %s, side: '%s')",  # type: ignore
                     filenames, self._side)
        logger.trace("Cropping training images info: (filenames: %s, side: '%s')",  # type: ignore
                     filenames, self._side)
        logger.trace("Cropping training images info: (filenames: %s, side: '%s')",  # type: ignore
                     filenames, self._side)
        logger.trace("Cropping training images info: (filenames: %s, side: '%s')",  # type: ignore
                     filenames, self._side)
        logger.trace("Cropping training images info: (filenames: %s, side: '%s')",  # type: ignore
                     filenames, self._side)
        logger.trace("Cropping training images info: (filenames: %s, side: '%s')",  # type: ignore
                     filenames, self._side)
        logger.trace("Cropping training images info: (filenames: %s, side: '%s')",  # type: ignore
                     filenames, self._side)
        logger.trace("Cropping training images info: (filenames: %s, side: '%s')",  # type: ignore
                     filenames, self._side)
        logger.trace("Cropping training images info: (filenames: %s, side: '%s')",  # type: ignore
                     filenames, self._side)
        logger.trace("Cropping training images info: (filenames: %s, side: '%s')",  # type: ignore
                     filenames, self._side)
        logger.trace("Cropping training images info: (filenames: %s, side: '%s')",  # type: ignore
                     filenames, self._side)
  
          Parameters
          ----------
          png_meta: dict
              The information held within the Faceswap PNG Header
keep keep keep keep replace replace replace replace replace replace replace replace replace replace replace replace 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