              alignments = Alignments(path, filename=filename)
              landmarks[side] = self.transform_landmarks(alignments)
          return landmarks
  
      def transform_landmarks(self, alignments):
          """ For each face transform landmarks and return """
          landmarks = dict()
          for _, faces, _, _ in alignments.yield_faces():
              for face in faces:
                  detected_face = DetectedFace()
                  detected_face.from_alignment(face)
                  detected_face.load_aligned(None, size=self.size)
                  landmarks[detected_face.hash] = detected_face.aligned_landmarks
          return landmarks
 
</s> remove             landmarks[side] = self.transform_landmarks(alignments)
        return landmarks
 </s> add             retval[side] = self._to_detected_faces(alignments, side)
        logger.debug("Returning: %s", {k: len(v) for k, v in retval.items()})
        return retval </s> remove                 detected_face.load_aligned(None, size=self.size)
                landmarks[detected_face.hash] = detected_face.aligned_landmarks
 </s> add                 detected_faces.append(detected_face)
        logger.debug("Detected Faces count: %s, Skipped faces count: %s",
                     len(detected_faces), skip_count)
        if skip_count != 0:
            logger.warning("%s alignments have been removed as their corresponding faces do not "
                           "exist in the input folder for side %s. Run in verbose mode if you "
                           "wish to see which alignments have been excluded.",
                           skip_count, side.upper())
        return detected_faces

    def _validate_face(self, face, filename, idx, side, side_hashes):
        """ Validate that the currently processing face has a corresponding hash entry and the
        requested mask exists

        Parameters
        ----------
        face: dict
            A face retrieved from an alignments file
        filename: str
            The original frame filename that the given face comes from
        idx: int
            The index of the face in the frame
        side: {'A', 'B'}
            The side that this face belongs to
        side_hashes: set
            A set of hashes that exist in the alignments folder for these faces

        Returns
        -------
        bool
            ``True`` if the face is valid otherwise ``False``

        Raises
        ------
        FaceswapError
            If the current face doesn't pass validation
        """
        mask_type = self._training_opts["mask_type"]
        if mask_type is not None and "mask" not in face:
            msg = ("You have selected a Mask Type in your training configuration options but at "
                   "least one face has no mask stored for it.\nYou should generate the required "
                   "masks with the Mask Tool or set the Mask Type configuration option to `none`."
                   "\nThe face that caused this failure was side: `{}`, frame: `{}`, index: {}. "
                   "However there are probably more faces without masks".format(
                       side.upper(), filename, idx))
            raise FaceswapError(msg)

        if mask_type is not None and mask_type not in face["mask"]:
            msg = ("At least one of your faces does not have the mask `{}` stored for it.\nYou "
                   "should run the Mask Tool to generate this mask for your faceset or "
                   "select a different mask in the training configuration options.\n"
                   "The face that caused this failure was [side: `{}`, frame: `{}`, index: {}]. "
                   "The masks that exist for this face are: {}.\nBe aware that there are probably "
                   "more faces without this Mask Type".format(
                       mask_type, side.upper(), filename, idx, list(face["mask"].keys())))
            raise FaceswapError(msg)

        if face["hash"] not in side_hashes:
            logger.verbose("Skipping alignment for non-existant face in frame '%s' index: %s",
                           filename, idx)
            return False
        return True

    def _check_all_faces(self):
        """ Ensure that all faces in the training folder exist in the alignments file.
        If not, output missing filenames

        Raises
        ------
        FaceswapError
            If there are faces in the training folder which do not exist in the alignments file
        """
        logger.debug("Checking faces exist in alignments")
        missing_alignments = dict()
        for side, train_hashes in self._hashes.items():
            align_hashes = set(face.hash for face in self._detected_faces[side])
            if not align_hashes.issuperset(train_hashes["hashes"]):
                missing_alignments[side] = [
                    os.path.basename(filename)
                    for hsh, filename in zip(train_hashes["hashes"], train_hashes["filenames"])
                    if hsh not in align_hashes]
        if missing_alignments:
            msg = ("There are faces in your training folder(s) which do not exist in your "
                   "alignments file. Training cannot continue. See above for a full list of "
                   "files missing alignments.")
            for side, filelist in missing_alignments.items():
                logger.error("Faces missing alignments for side %s: %s",
                             side.capitalize(), filelist)
            raise FaceswapError(msg)

    # Get landmarks
    def _transform_landmarks(self, side, detected_faces):
        """ Transform frame landmarks to their aligned face variant.

        Parameters
        ----------
        side: {"a" or "b"}
            The side currently being processed
        detected_faces: list
            A list of :class:`lib.faces_detect.DetectedFace` objects

        Returns
        -------
        dict
            The face filenames as keys with the aligned landmarks as value.
        """
        landmarks = dict()
        for face in detected_faces:
            face.load_aligned(None, size=self._training_opts["training_size"])
            for filename in self._hash_to_filenames(side, face.hash):
                landmarks[filename] = face.aligned_landmarks </s> remove     def get_alignments(self):
        """ Obtain the landmarks for each faceset """
        landmarks = dict()
        for side, fullpath in self.paths.items():
 </s> add     # Load alignments
    @staticmethod
    def _get_image_hashes(image_list):
        """ Return the hashes for all images used for training.

        Parameters
        ----------
        image_list: dict
            The file paths for the images to be trained on for each side. The dictionary should
            contain 2 keys ("a" and "b") with the values being a list of full paths corresponding
            to each side.

        Returns
        -------
        dict
            For keys "a" and "b" the values are a ``dict`` containing keys "hashes" and "filenames"
            with their values being a list of hashes and filenames that exist within the training
            data folder
        """
        hashes = {key: dict(hashes=[], filenames=[]) for key in image_list}
        pbar = tqdm(desc="Reading training images",
                    total=sum(len(val) for val in image_list.values()))
        for side, filelist in image_list.items():
            logger.debug("side: %s, file count: %s", side, len(filelist))
            for filename, hsh in read_image_hash_batch(filelist):
                hashes[side]["hashes"].append(hsh)
                hashes[side]["filenames"].append(filename)
                pbar.update(1)
        pbar.close()
        logger.trace(hashes)
        return hashes

    def _load_alignments(self):
        """ Load the alignments and convert to :class:`lib.faces_detect.DetectedFace` objects.

        Returns
        -------
        dict
            For keys "a" and "b" values are a list of :class:`lib.faces_detect.DetectedFace`
            objects.
        """
        logger.debug("Loading alignments")
        retval = dict()
        for side, fullpath in self._training_opts["alignments"].items():
            logger.debug("side: '%s', path: '%s'", side, fullpath) </s> remove     def process_training_opts(self):
        """ Override for processing model specific training options """
        logger.debug(self.model.training_opts)
        if self.landmarks_required:
            landmarks = Landmarks(self.model.training_opts).landmarks
            self.model.training_opts["landmarks"] = landmarks
 </s> add     def _process_training_opts(self):
        """ Extrapolate alignments and masks from the alignments file into
        :attr:`_model.training_opts`."""
        logger.debug(self._model.training_opts)
        if not self._landmarks_required and not self._use_mask:
            return </s> remove class Landmarks():
    """ Set Landmarks for training into the model's training options"""
    def __init__(self, training_opts):
        logger.debug("Initializing %s: (training_opts: '%s')",
                     self.__class__.__name__, training_opts)
        self.size = training_opts.get("training_size", 256)
        self.paths = training_opts["alignments"]
        self.landmarks = self.get_alignments()
        logger.debug("Initialized %s", self.__class__.__name__)
 </s> add     @property
    def masks(self):
        """ dict: The :class:`lib.faces_detect.Mask` objects of requested mask type for
        keys a" and "b"
        """
        retval = {side: self._get_masks(side, detected_faces)
                  for side, detected_faces in self._detected_faces.items()}
        logger.trace(retval)
        return retval
    def _to_detected_faces(self, alignments, side):
        """ Convert alignments to DetectedFace objects.

        Filter the detected faces to only those that exist in the training folders.

        Parameters
        ----------
        alignments: :class:`lib.alignments.Alignments`
            The alignments for the current faces
        side: {"a" or "b"}
            The side being processed

        Returns
        -------
        list
            List of :class:`lib.faces_detect.DetectedFace` objects
        """
        skip_count = 0
        side_hashes = set(self._hashes[side]["hashes"])
        detected_faces = []
        for _, faces, _, filename in alignments.yield_faces():
            for idx, face in enumerate(faces):
                if not self._validate_face(face, filename, idx, side, side_hashes):
                    skip_count += 1
                    continue
              alignments = Alignments(path, filename=filename)
              landmarks[side] = self.transform_landmarks(alignments)
          return landmarks
  
    def _to_detected_faces(self, alignments, side):
        """ Convert alignments to DetectedFace objects.

        Filter the detected faces to only those that exist in the training folders.

        Parameters
        ----------
        alignments: :class:`lib.alignments.Alignments`
            The alignments for the current faces
        side: {"a" or "b"}
            The side being processed

        Returns
        -------
        list
            List of :class:`lib.faces_detect.DetectedFace` objects
        """
        skip_count = 0
        side_hashes = set(self._hashes[side]["hashes"])
        detected_faces = []
        for _, faces, _, filename in alignments.yield_faces():
            for idx, face in enumerate(faces):
                if not self._validate_face(face, filename, idx, side, side_hashes):
                    skip_count += 1
                    continue
    def _to_detected_faces(self, alignments, side):
        """ Convert alignments to DetectedFace objects.

        Filter the detected faces to only those that exist in the training folders.

        Parameters
        ----------
        alignments: :class:`lib.alignments.Alignments`
            The alignments for the current faces
        side: {"a" or "b"}
            The side being processed

        Returns
        -------
        list
            List of :class:`lib.faces_detect.DetectedFace` objects
        """
        skip_count = 0
        side_hashes = set(self._hashes[side]["hashes"])
        detected_faces = []
        for _, faces, _, filename in alignments.yield_faces():
            for idx, face in enumerate(faces):
                if not self._validate_face(face, filename, idx, side, side_hashes):
                    skip_count += 1
                    continue
    def _to_detected_faces(self, alignments, side):
        """ Convert alignments to DetectedFace objects.

        Filter the detected faces to only those that exist in the training folders.

        Parameters
        ----------
        alignments: :class:`lib.alignments.Alignments`
            The alignments for the current faces
        side: {"a" or "b"}
            The side being processed

        Returns
        -------
        list
            List of :class:`lib.faces_detect.DetectedFace` objects
        """
        skip_count = 0
        side_hashes = set(self._hashes[side]["hashes"])
        detected_faces = []
        for _, faces, _, filename in alignments.yield_faces():
            for idx, face in enumerate(faces):
                if not self._validate_face(face, filename, idx, side, side_hashes):
                    skip_count += 1
                    continue
    def _to_detected_faces(self, alignments, side):
        """ Convert alignments to DetectedFace objects.

        Filter the detected faces to only those that exist in the training folders.

        Parameters
        ----------
        alignments: :class:`lib.alignments.Alignments`
            The alignments for the current faces
        side: {"a" or "b"}
            The side being processed

        Returns
        -------
        list
            List of :class:`lib.faces_detect.DetectedFace` objects
        """
        skip_count = 0
        side_hashes = set(self._hashes[side]["hashes"])
        detected_faces = []
        for _, faces, _, filename in alignments.yield_faces():
            for idx, face in enumerate(faces):
                if not self._validate_face(face, filename, idx, side, side_hashes):
                    skip_count += 1
                    continue
    def _to_detected_faces(self, alignments, side):
        """ Convert alignments to DetectedFace objects.

        Filter the detected faces to only those that exist in the training folders.

        Parameters
        ----------
        alignments: :class:`lib.alignments.Alignments`
            The alignments for the current faces
        side: {"a" or "b"}
            The side being processed

        Returns
        -------
        list
            List of :class:`lib.faces_detect.DetectedFace` objects
        """
        skip_count = 0
        side_hashes = set(self._hashes[side]["hashes"])
        detected_faces = []
        for _, faces, _, filename in alignments.yield_faces():
            for idx, face in enumerate(faces):
                if not self._validate_face(face, filename, idx, side, side_hashes):
                    skip_count += 1
                    continue
                  detected_face = DetectedFace()
                  detected_face.from_alignment(face)
                  detected_face.load_aligned(None, size=self.size)
                  landmarks[detected_face.hash] = detected_face.aligned_landmarks
          return landmarks
keep keep keep keep replace replace replace replace replace keep keep keep keep keep
 Smart Masks - Training Implementation (#914)

* Smart Masks - Training

- Reinstate smart mask training code
- Reinstate mask_type back to model.config
- change 'replicate_input_mask to 'learn_mask'
- Add learn mask option
- Add mask loading from alignments to plugins.train.trainer
- Add mask_blur and mask threshold options
- _base.py - Pass mask options through training_opts dict
- plugins.train.model - check for mask_type not None for learn_mask and penalized_mask_loss
- Limit alignments loading to just those faces that appear in the training folder
- Raise error if not all training images have an alignment, and alignment file is required
- lib.training_data - Mask generation code
- lib.faces_detect - cv2 dimension stripping bugfix
- Remove cv2 linting code

* Update mask helptext in cli.py

* Fix Warp to Landmarks
Remove SHA1 hashing from training data

* Update mask training config

* Capture missing masks at training init

* lib.image.read_image_batch - Return filenames with batch for ordering

* scripts.train - Documentation

* plugins.train.trainer - documentation

* Ensure backward compatibility.
Fix convert for new predicted masks

* Update removed masks to components for legacy models. 
plugins/train/trainer/_base.py