      @staticmethod
      def pad_image(image, target):
          """ Pad an image to a square """
          height, width = image.shape[:2]
          if width < target[0] or height < target[1]:
              pad_l = (target[0] - width) // 2
              pad_r = (target[0] - width) - pad_l
              pad_t = (target[1] - height) // 2
              pad_b = (target[1] - height) - pad_t
              img = cv2.copyMakeBorder(  # pylint:disable=no-member
                  image, pad_t, pad_b, pad_l, pad_r,
                  cv2.BORDER_CONSTANT, (0, 0, 0)  # pylint:disable=no-member
              )
              return img
          return image
  
      # <<< IMAGE ROTATION METHODS >>> #
      @staticmethod
      def get_rotation_angles(rotation):
 
</s> remove     @staticmethod
    def pad_image(image, target):
        """ Pad an image to a square """
 </s> add     def _pad_image(self, image):
        """ Pad a resized image to input size """ </s> remove     def compile_detection_image(self, input_image,  # pylint:disable=too-many-arguments
                                is_square=False, scale_up=False, to_rgb=False,
                                to_grayscale=False, pad_to=None):
        """ Compile the detection image """
        image = input_image.copy()
        if to_rgb:
            image = image[:, :, ::-1]
        elif to_grayscale:
            image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)  # pylint: disable=no-member
        scale = self.set_scale(image, is_square=is_square, scale_up=scale_up)
        image = self.scale_image(image, scale, pad_to)
        if pad_to is None:
            return [image, scale]
        pad_left = int(pad_to[0] - int(input_image.shape[1] * scale)) // 2
        pad_top = int(pad_to[1] - int(input_image.shape[0] * scale)) // 2
        return [image, scale, (pad_left, pad_top)]

    def set_scale(self, image, is_square=False, scale_up=False):
        """ Set the scale factor for incoming image """
        height, width = image.shape[:2]
        if is_square:
            if isinstance(self.target, int):
                dims = (self.target ** 0.5, self.target ** 0.5)
                self.target = dims
            source = max(height, width)
            target = max(self.target)
        else:
            source = (width * height) ** 0.5
            if isinstance(self.target, tuple):
                self.target = self.target[0] * self.target[1]
            target = self.target ** 0.5
 </s> add     def _compile_detection_image(self, input_image):
        """ Compile the detection image for feeding into the model"""
        image = self._convert_color(input_image) </s> remove     def crop(self, image, center, scale):  # pylint:disable=too-many-locals
        """ Crop image around the center point """
        logger.trace("Cropping image")
        is_color = image.ndim > 2
        v_ul = self.transform([1, 1], center, scale, self.input_size).astype(np.int)
        v_br = self.transform([self.input_size, self.input_size],
                              center,
                              scale,
                              self.input_size).astype(np.int)
        if is_color:
            new_dim = np.array([v_br[1] - v_ul[1],
                                v_br[0] - v_ul[0],
                                image.shape[2]],
                               dtype=np.int32)
            new_img = np.zeros(new_dim, dtype=np.uint8)
        else:
            new_dim = np.array([v_br[1] - v_ul[1],
                                v_br[0] - v_ul[0]],
                               dtype=np.int)
            new_img = np.zeros(new_dim, dtype=np.uint8)
        height = image.shape[0]
        width = image.shape[1]
        new_x = np.array([max(1, -v_ul[0] + 1), min(v_br[0], width) - v_ul[0]],
                         dtype=np.int32)
        new_y = np.array([max(1, -v_ul[1] + 1),
                          min(v_br[1], height) - v_ul[1]],
                         dtype=np.int32)
        old_x = np.array([max(1, v_ul[0] + 1), min(v_br[0], width)],
                         dtype=np.int32)
        old_y = np.array([max(1, v_ul[1] + 1), min(v_br[1], height)],
                         dtype=np.int32)
        if is_color:
            new_img[new_y[0] - 1:new_y[1],
                    new_x[0] - 1:new_x[1]] = image[old_y[0] - 1:old_y[1],
                                                   old_x[0] - 1:old_x[1], :]
        else:
            new_img[new_y[0] - 1:new_y[1],
                    new_x[0] - 1:new_x[1]] = image[old_y[0] - 1:old_y[1],
                                                   old_x[0] - 1:old_x[1]]

        if new_img.shape[0] < self.input_size:
            interpolation = cv2.INTER_CUBIC  # pylint:disable=no-member
        else:
            interpolation = cv2.INTER_AREA  # pylint:disable=no-member

        new_img = cv2.resize(new_img,  # pylint:disable=no-member
                             dsize=(int(self.input_size), int(self.input_size)),
                             interpolation=interpolation)
        logger.trace("Cropped image")
        return new_img
 </s> add             new_images.append(cv2.resize(new_img,  # pylint:disable=no-member
                                         dsize=(int(self.input_size), int(self.input_size)),
                                         interpolation=interpolation))
        logger.trace("Cropped images")
        return new_images </s> remove 
        box = (detected_face["left"],
               detected_face["top"],
               detected_face["right"],
               detected_face["bottom"])
        height = detected_face["bottom"] - detected_face["top"]
        width = detected_face["right"] - detected_face["left"]
        diff_height_width = height - width
        offset_y = int(abs(diff_height_width / 2))
        box_moved = self.move_box(box, [0, offset_y])

        # Make box square.
        roi = self.get_square_box(box_moved)
        # Pad the image if face is outside of boundaries
        image = self.pad_image(roi, image)
        face = image[roi[1]: roi[3], roi[0]: roi[2]]

        if face.shape[0] < self.input_size:
            interpolation = cv2.INTER_CUBIC  # pylint:disable=no-member
        else:
            interpolation = cv2.INTER_AREA  # pylint:disable=no-member

        face = cv2.resize(face,  # pylint:disable=no-member
                          dsize=(int(self.input_size), int(self.input_size)),
                          interpolation=interpolation)
        return dict(image=face, roi=roi)
 </s> add         rois = []
        faces = []
        for face in detected_faces:
            box = (face.left,
                   face.top,
                   face.right,
                   face.bottom)
            diff_height_width = face.h - face.w
            offset_y = int(abs(diff_height_width / 2))
            box_moved = self.move_box(box, [0, offset_y])

            # Make box square.
            roi = self.get_square_box(box_moved)
            # Pad the image if face is outside of boundaries
            image = self.pad_image(roi, face.image)
            face = image[roi[1]: roi[3], roi[0]: roi[2]]

            if face.shape[0] < self.input_size:
                interpolation = cv2.INTER_CUBIC  # pylint:disable=no-member
            else:
                interpolation = cv2.INTER_AREA  # pylint:disable=no-member

            face = cv2.resize(face,  # pylint:disable=no-member
                              dsize=(int(self.input_size), int(self.input_size)),
                              interpolation=interpolation)
            faces.append(face)
            rois.append(roi)
        return faces, rois
        if width < self.input_size or height < self.input_size:
            pad_l = (self.input_size - width) // 2
            pad_r = (self.input_size - width) - pad_l
            pad_t = (self.input_size - height) // 2
            pad_b = (self.input_size - height) - pad_t
            image = cv2.copyMakeBorder(  # pylint:disable=no-member
                image,
                pad_t,
                pad_b,
                pad_l,
                pad_r,
                cv2.BORDER_CONSTANT)  # pylint:disable=no-member
        logger.trace("Padded image shape: %s", image.shape)
      @staticmethod
      def pad_image(image, target):
          """ Pad an image to a square """
          height, width = image.shape[:2]
        if width < self.input_size or height < self.input_size:
            pad_l = (self.input_size - width) // 2
            pad_r = (self.input_size - width) - pad_l
            pad_t = (self.input_size - height) // 2
            pad_b = (self.input_size - height) - pad_t
            image = cv2.copyMakeBorder(  # pylint:disable=no-member
                image,
                pad_t,
                pad_b,
                pad_l,
                pad_r,
                cv2.BORDER_CONSTANT)  # pylint:disable=no-member
        logger.trace("Padded image shape: %s", image.shape)
        if width < self.input_size or height < self.input_size:
            pad_l = (self.input_size - width) // 2
            pad_r = (self.input_size - width) - pad_l
            pad_t = (self.input_size - height) // 2
            pad_b = (self.input_size - height) - pad_t
            image = cv2.copyMakeBorder(  # pylint:disable=no-member
                image,
                pad_t,
                pad_b,
                pad_l,
                pad_r,
                cv2.BORDER_CONSTANT)  # pylint:disable=no-member
        logger.trace("Padded image shape: %s", image.shape)
        if width < self.input_size or height < self.input_size:
            pad_l = (self.input_size - width) // 2
            pad_r = (self.input_size - width) - pad_l
            pad_t = (self.input_size - height) // 2
            pad_b = (self.input_size - height) - pad_t
            image = cv2.copyMakeBorder(  # pylint:disable=no-member
                image,
                pad_t,
                pad_b,
                pad_l,
                pad_r,
                cv2.BORDER_CONSTANT)  # pylint:disable=no-member
        logger.trace("Padded image shape: %s", image.shape)
        if width < self.input_size or height < self.input_size:
            pad_l = (self.input_size - width) // 2
            pad_r = (self.input_size - width) - pad_l
            pad_t = (self.input_size - height) // 2
            pad_b = (self.input_size - height) - pad_t
            image = cv2.copyMakeBorder(  # pylint:disable=no-member
                image,
                pad_t,
                pad_b,
                pad_l,
                pad_r,
                cv2.BORDER_CONSTANT)  # pylint:disable=no-member
        logger.trace("Padded image shape: %s", image.shape)
        if width < self.input_size or height < self.input_size:
            pad_l = (self.input_size - width) // 2
            pad_r = (self.input_size - width) - pad_l
            pad_t = (self.input_size - height) // 2
            pad_b = (self.input_size - height) - pad_t
            image = cv2.copyMakeBorder(  # pylint:disable=no-member
                image,
                pad_t,
                pad_b,
                pad_l,
                pad_r,
                cv2.BORDER_CONSTANT)  # pylint:disable=no-member
        logger.trace("Padded image shape: %s", image.shape)
        if width < self.input_size or height < self.input_size:
            pad_l = (self.input_size - width) // 2
            pad_r = (self.input_size - width) - pad_l
            pad_t = (self.input_size - height) // 2
            pad_b = (self.input_size - height) - pad_t
            image = cv2.copyMakeBorder(  # pylint:disable=no-member
                image,
                pad_t,
                pad_b,
                pad_l,
                pad_r,
                cv2.BORDER_CONSTANT)  # pylint:disable=no-member
        logger.trace("Padded image shape: %s", image.shape)
        if width < self.input_size or height < self.input_size:
            pad_l = (self.input_size - width) // 2
            pad_r = (self.input_size - width) - pad_l
            pad_t = (self.input_size - height) // 2
            pad_b = (self.input_size - height) - pad_t
            image = cv2.copyMakeBorder(  # pylint:disable=no-member
                image,
                pad_t,
                pad_b,
                pad_l,
                pad_r,
                cv2.BORDER_CONSTANT)  # pylint:disable=no-member
        logger.trace("Padded image shape: %s", image.shape)
        if width < self.input_size or height < self.input_size:
            pad_l = (self.input_size - width) // 2
            pad_r = (self.input_size - width) - pad_l
            pad_t = (self.input_size - height) // 2
            pad_b = (self.input_size - height) - pad_t
            image = cv2.copyMakeBorder(  # pylint:disable=no-member
                image,
                pad_t,
                pad_b,
                pad_l,
                pad_r,
                cv2.BORDER_CONSTANT)  # pylint:disable=no-member
        logger.trace("Padded image shape: %s", image.shape)
        if width < self.input_size or height < self.input_size:
            pad_l = (self.input_size - width) // 2
            pad_r = (self.input_size - width) - pad_l
            pad_t = (self.input_size - height) // 2
            pad_b = (self.input_size - height) - pad_t
            image = cv2.copyMakeBorder(  # pylint:disable=no-member
                image,
                pad_t,
                pad_b,
                pad_l,
                pad_r,
                cv2.BORDER_CONSTANT)  # pylint:disable=no-member
        logger.trace("Padded image shape: %s", image.shape)
        if width < self.input_size or height < self.input_size:
            pad_l = (self.input_size - width) // 2
            pad_r = (self.input_size - width) - pad_l
            pad_t = (self.input_size - height) // 2
            pad_b = (self.input_size - height) - pad_t
            image = cv2.copyMakeBorder(  # pylint:disable=no-member
                image,
                pad_t,
                pad_b,
                pad_l,
                pad_r,
                cv2.BORDER_CONSTANT)  # pylint:disable=no-member
        logger.trace("Padded image shape: %s", image.shape)
          return image
  
      # <<< IMAGE ROTATION METHODS >>> #
      @staticmethod
      def get_rotation_angles(rotation):
keep keep keep keep replace replace replace replace replace replace replace replace replace 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 
plugins/extract/detect/_base.py