                       "w": right - left,
                       "y": top,
                       "h": bottom - top,
                       "hash": f_hash,
                       "landmarksXY": dfl_alignments["source_landmarks"]}
          logger.trace("Adding alignment: (frame: '%s', alignment: %s", sourcefile, alignment)
          alignments.setdefault(sourcefile, list()).append(alignment)
  
  
  class RemoveAlignments():
 
</s> remove             self.alignments.data[frame][0]["landmarksXY"] = landmarks_xy
 </s> add             self.alignments.data[frame][0]["landmarks_xy"] = landmarks_xy </s> remove         """ Convert a face alignment to detected face object """
 </s> add         """ Set the attributes of this class from an alignments file and optionally load the face
        into the ``image`` attribute.

        Parameters
        ----------
        alignment: dict
            A dictionary entry for a face from an alignments file containing the keys
            ``x``, ``w``, ``y``, ``h``, ``landmarks_xy``. Optionally the key ``hash``
            will be provided, but not all use cases will know the face hash at this time.
        image: numpy.ndarray, optional
            If an image is passed in, then the ``image`` attribute will
            be set to the cropped face based on the passed in bounding box co-ordinates
        """
 </s> remove     For each source frame, the plugin must pass a dict to finalize containing:
    {"filename": <filename of source frame>,
     "image": <source image>,
     "detected_faces": <list of dicts containing bounding box points>}}
 </s> add For each source frame, the plugin must pass a dict to finalize containing: </s> remove     - Use the function self.to_bounding_box_dict(left, right, top, bottom) to define the dict
    """

import logging
import os
import traceback
from io import StringIO
 </s> add >>> {'filename': <filename of source frame>,
>>>  'image':  <source image>,
>>>  'detected_faces': <list of DetectedFace objects containing bounding box points}} </s> remove     def filter_small_faces(self, detected_faces):
        """ Filter out any faces smaller than the min size threshold """
        retval = list()
        for face in detected_faces:
            width = face["right"] - face["left"]
            height = face["bottom"] - face["top"]
            face_size = (width ** 2 + height ** 2) ** 0.5
            if face_size < self.min_size:
                logger.debug("Removing detected face: (face_size: %s, min_size: %s",
                             face_size, self.min_size)
                continue
            retval.append(face)
        return retval
 </s> add     @staticmethod
    def to_detected_face(left, top, right, bottom):
        """ Return a :class:`~lib.faces_detect.DetectedFace` object for the bounding box """
        return DetectedFace(x=int(round(left)),
                            w=int(round(right - left)),
                            y=int(round(top)),
                            h=int(round(bottom - top)))

    # <<< PROTECTED ACCESS METHODS >>> #
    # <<< PREDICT WRAPPER >>> #
    def _predict(self, batch):
        """ Wrap models predict function in rotations """
        batch["rotmat"] = [np.array([]) for _ in range(len(batch["feed"]))]
        found_faces = [np.array([]) for _ in range(len(batch["feed"]))]
        for angle in self.rotation:
            # Rotate the batch and insert placeholders for already found faces
            self._rotate_batch(batch, angle)
            batch = self.predict(batch)

            if angle != 0 and any([face.any() for face in batch["prediction"]]):
                logger.verbose("found face(s) by rotating image %s degrees", angle)

            found_faces = [face if not found.any() else found
                           for face, found in zip(batch["prediction"], found_faces)]

            if all([face.any() for face in found_faces]):
                logger.trace("Faces found for all images")
                break

        batch["prediction"] = found_faces
        logger.trace("detect_prediction output: (filenames: %s, prediction: %s, rotmat: %s)",
                     batch["filename"], batch["prediction"], batch["rotmat"])
        return batch
                     "landmarks_xy": dfl_alignments["source_landmarks"]}
                       "w": right - left,
                       "y": top,
                       "h": bottom - top,
                       "hash": f_hash,
                     "landmarks_xy": dfl_alignments["source_landmarks"]}
          logger.trace("Adding alignment: (frame: '%s', alignment: %s", sourcefile, alignment)
          alignments.setdefault(sourcefile, list()).append(alignment)
  
  
  class RemoveAlignments():
keep keep keep keep replace keep keep keep keep keep
 De-Multiprocess Extract (#871)

* requirements.txt: - Pin opencv to 4.1.1 (fixes cv2-dnn error)

* lib.face_detect.DetectedFace: change LandmarksXY to landmarks_xy. Add left, right, top, bottom attributes

* lib.model.session: Session manager for loading models into different graphs (for Nvidia + CPU)

* plugins.extract._base: New parent class for all extract plugins

* plugins.extract.pipeline. Remove MultiProcessing. Dynamically limit batchsize for Nvidia cards. Remove loglevel input

* S3FD + FAN plugins. Standardise to Keras version for all backends

* Standardize all extract plugins to new threaded codebase

* Documentation. Start implementing Numpy style docstrings for Sphinx Documentation

* Remove s3fd_amd. Change convert OTF to expect DetectedFace object

* faces_detect - clean up and documentation

* Remove PoolProcess

* Migrate manual tool to new extract workflow

* Remove AMD specific extractor code from cli and plugins

* Sort tool to new extract workflow

* Remove multiprocessing from project

* Remove multiprocessing queues from QueueManager

* Remove multiprocessing support from logger

* Move face_filter to new extraction pipeline

* Alignments landmarksXY > landmarks_xy and legacy handling

* Intercept get_backend for sphinx doc build

# Add Sphinx documentation 
tools/lib_alignments/jobs.py