                       frame, len(faces), len(valid_faces))
          return valid_faces
  
  
  class Legacy():
      """ Update legacy alignments:
          - Rotate landmarks and bounding boxes on legacy alignments
            and remove the 'r' parameter
          - Add face hashes to alignments file
      """
  
      def __init__(self, alignments, arguments, frames=None, faces=None, child_process=False):
          logger.debug("Initializing %s: (arguments: %s, child_process: %s)",
                       self.__class__.__name__, arguments, child_process)
          self.alignments = alignments
          if child_process:
              self.frames = frames
              self.faces = faces
          else:
              self.frames = Frames(arguments.frames_dir)
              self.faces = Faces(arguments.faces_dir)
          logger.debug("Initialized %s", self.__class__.__name__)
  
      def process(self):
          """ Run the rotate alignments process """
          rotated = self.alignments.get_legacy_rotation()
          hashes = self.alignments.get_legacy_no_hashes()
          if (not self.frames or not rotated) and (not self.faces or not hashes):
              return
          logger.info("[UPDATE LEGACY LANDMARKS]")  # Tidy up cli output
          if rotated and self.frames:
              logger.info("Legacy rotated frames found. Converting...")
              self.rotate_landmarks(rotated)
              self.alignments.save()
          if hashes and self.faces:
              logger.info("Legacy alignments found. Adding Face Hashes...")
              self.add_hashes(hashes)
              self.alignments.save()
  
      def rotate_landmarks(self, rotated):
          """ Rotate the landmarks """
          for rotate_item in tqdm(rotated, desc="Rotating Landmarks"):
              frame = self.frames.get(rotate_item, None)
              if frame is None:
                  continue
              self.alignments.rotate_existing_landmarks(rotate_item, frame)
  
      def add_hashes(self, hashes):
          """ Add Face Hashes to the alignments file """
          all_faces = dict()
          logger.info("Getting original filenames, indexes and hashes...")
          for face in self.faces.file_list_sorted:
              filename = face["face_name"]
              extension = face["face_extension"]
              if "_" not in face["face_name"]:
                  logger.warning("Unable to determine index of file. Skipping: '%s'", filename)
                  continue
              index = filename[filename.rfind("_") + 1:]
              if not index.isdigit():
                  logger.warning("Unable to determine index of file. Skipping: '%s'", filename)
                  continue
              orig_frame = filename[:filename.rfind("_")] + extension
              all_faces.setdefault(orig_frame, dict())[int(index)] = face["face_hash"]
  
          logger.info("Updating hashes to alignments...")
          for frame in hashes:
              if frame not in all_faces.keys():
                  logger.warning("Skipping missing frame: '%s'", frame)
                  continue
              self.alignments.add_face_hashes(frame, all_faces[frame])
  
  
  class Merge():
      """ Merge two alignments files into one """
      def __init__(self, alignments, arguments):
          self.alignments = alignments
          self.faces = self.get_faces(arguments)
 
</s> remove 

class Legacy():
    """ Update legacy alignments:
        - Rotate landmarks and bounding boxes on legacy alignments
          and remove the 'r' parameter
        - Add face hashes to alignments file
        """
    def __init__(self, alignments, frames, faces_dir):
        self.alignments = alignments
        self.frames = {os.path.basename(frame): frame
                       for frame in frames}
        self.process(faces_dir)

    def process(self, faces_dir):
        """ Run the rotate alignments process """
        rotated = self.alignments.get_legacy_rotation()
        hashes = self.alignments.get_legacy_no_hashes()
        if not rotated and not hashes:
            return
        if rotated:
            logger.info("Legacy rotated frames found. Converting...")
            self.rotate_landmarks(rotated)
            self.alignments.save()
        if hashes and faces_dir:
            logger.info("Legacy alignments found. Adding Face Hashes...")
            self.add_hashes(hashes, faces_dir)
            self.alignments.save()

    def rotate_landmarks(self, rotated):
        """ Rotate the landmarks """
        for rotate_item in tqdm(rotated, desc="Rotating Landmarks"):
            frame = self.frames.get(rotate_item, None)
            if frame is None:
                logger.debug("Skipping missing frame: '%s'", rotate_item)
                continue
            self.alignments.rotate_existing_landmarks(rotate_item, frame)

    def add_hashes(self, hashes, faces_dir):
        """ Add Face Hashes to the alignments file """
        all_faces = dict()
        face_files = sorted(face for face in os.listdir(faces_dir) if "_" in face)
        for face in face_files:
            filename, extension = os.path.splitext(face)
            index = filename[filename.rfind("_") + 1:]
            if not index.isdigit():
                continue
            orig_frame = filename[:filename.rfind("_")] + extension
            all_faces.setdefault(orig_frame, dict())[int(index)] = os.path.join(faces_dir, face)

        for frame in tqdm(hashes):
            if frame not in all_faces.keys():
                logger.warning("Skipping missing frame: '%s'", frame)
                continue
            hash_faces = all_faces[frame]
            for index, face_path in hash_faces.items():
                hash_faces[index] = read_image_hash(face_path)
            self.alignments.add_face_hashes(frame, hash_faces)
 </s> add  </s> remove     def check_legacy(self):
        """ Legacy rotated alignments will not have the correct x, y
            positions. Faces without hashes won't process.
            Check for these and generate a warning and exit """
        rotated = self.alignments.get_legacy_rotation()
        hashes = self.alignments.get_legacy_no_hashes()
        if rotated or hashes:
            logger.error("Legacy alignments found. Sort cannot continue. You should run legacy "
                         "tool to update the file prior to running sort: 'python tools.py "
                         "alignments -j legacy -a <alignments_file> -fr <frames_folder> -fc "
                         "<faces_folder>'")
            exit(0)

 </s> add  </s> remove     @staticmethod
    def get_original_rotation_matrix(dimensions, angle):
        """ Calculate original rotation matrix and invert """
        logger.trace("Getting original rotation matrix: (dimensions: %s, angle: %s)",
                     dimensions, angle)
        height, width = dimensions
        center = (width/2, height/2)
        r_mat = cv2.getRotationMatrix2D(  # pylint: disable=no-member
            center, -1.0 * angle, 1.)

        abs_cos = abs(r_mat[0, 0])
        abs_sin = abs(r_mat[0, 1])
        rotated_width = int(height*abs_sin + width*abs_cos)
        rotated_height = int(height*abs_cos + width*abs_sin)
        r_mat[0, 2] += rotated_width/2 - center[0]
        r_mat[1, 2] += rotated_height/2 - center[1]
        logger.trace("Returning rotation matrix: %s", r_mat)
        return r_mat

    # <Face Hashes> #
    # The old index based method of face matching is problematic.
    # The SHA1 Hash of the extracted face is now stored in the alignments file.
    # This has it's own issues, but they are far reduced from the index/filename method
    # This can eventually be removed
    def get_legacy_no_hashes(self):
        """ Get alignments without face hashes """
        logger.debug("Getting alignments without face hashes")
        keys = list()
        for key, val in self.data.items():
            for alignment in val:
                if "hash" not in alignment.keys():
                    keys.append(key)
                    break
        logger.debug("Got alignments without face hashes: %s", len(keys))
        return keys

    def add_face_hashes(self, frame_name, hashes):
        """ Backward compatability fix. Add face hash to alignments """
        logger.trace("Adding face hash: (frame: '%s', hashes: %s)", frame_name, hashes)
        faces = self.get_faces_in_frame(frame_name)
        count_match = len(faces) - len(hashes)
        if count_match != 0:
            msg = "more" if count_match > 0 else "fewer"
            logger.warning("There are %s %s face(s) in the alignments file than exist in the "
                           "faces folder. Check your sources for frame '%s'.",
                           abs(count_match), msg, frame_name)
        for idx, i_hash in hashes.items():
            faces[idx]["hash"] = i_hash
 </s> add     # <File Format> #
    # Serializer is now a compressed pickle .fsa format. This used to be any number of serializers
    def update_file_format(self, folder, filename):
        """ Convert old style alignments format to new style format """
        logger.info("Reformatting legacy alignments file...")
        old_location = os.path.join(str(folder), filename)
        new_location = "{}.{}".format(os.path.splitext(old_location)[0],
                                      self.serializer.file_extension)
        logger.info("Old location: '%s', New location: '%s'", old_location, new_location)

        load_serializer = get_serializer_from_filename(old_location)
        data = load_serializer.load(old_location)
        self.serializer.save(new_location, data)
        return os.path.basename(new_location) </s> remove     # < Rotation > #
    # The old rotation method would rotate the image to find a face, then
    # store the rotated landmarks along with a rotation value to tell the
    # convert process that it had to rotate the frame to find the landmarks.
    # This is problematic for numerous reasons. The process now rotates the
    # landmarks to correctly correspond with the original frame. The below are
    # functions to convert legacy alignments to the currently supported
    # infrastructure.
    # This can eventually be removed

    def get_legacy_rotation(self):
        """ Return a list of frames with legacy rotations
            Looks for an 'r' value in the alignments file that
            is not zero """
        logger.debug("Getting alignments containing legacy rotations")
        keys = list()
        for key, val in self.data.items():
            if any(alignment.get("r", None) for alignment in val):
                keys.append(key)
        logger.debug("Got alignments containing legacy rotations: %s", len(keys))
        return keys

    def rotate_existing_landmarks(self, frame_name, frame):
        """ Backwards compatability fix. Rotates the landmarks to
            their correct position and deletes r

            NB: The original frame must be passed in otherwise
            the transformation cannot be performed """
        logger.trace("Rotating existing landmarks for frame: '%s'", frame_name)
        dims = frame.shape[:2]
        for face in self.get_faces_in_frame(frame_name):
            angle = face.get("r", 0)
            if not angle:
                logger.trace("Landmarks do not require rotation: '%s'", frame_name)
                return
            logger.trace("Rotating landmarks: (frame: '%s', angle: %s)", frame_name, angle)
            r_mat = self.get_original_rotation_matrix(dims, angle)
            rotate_landmarks(face, r_mat)
            del face["r"]
        logger.trace("Rotatated existing landmarks for frame: '%s'", frame_name)
 </s> add  </s> remove     def get_faces(self, arguments):
        """ If faces argument is specified, load faces_dir
            otherwise return None """
 </s> add     @staticmethod
    def get_faces(arguments):
        """ If faces argument is specified, load faces_dir otherwise return None """
                       frame, len(faces), len(valid_faces))
          return valid_faces
  
  




































































  class Merge():
      """ Merge two alignments files into one """
      def __init__(self, alignments, arguments):
          self.alignments = alignments
          self.faces = self.get_faces(arguments)
keep keep keep keep replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace keep keep keep keep keep
 Legacy alignments update

- Remove legacy update hashes
- Remove legacy job from alignment-tools
- Remove legacy landmark rotation
- Add rotate face method to plugins/extract/detect
- Update travis test for new alignments extension
- Alignments format to .fsa
- Remove serializer option from alignments-tool
- Auto update legacy format alignment files to new format 
tools/lib_alignments/jobs.py